From f5d209bc1a74bf532493b0e6283aa198175ca204 Mon Sep 17 00:00:00 2001 From: Moe Abushawish Date: Mon, 20 Jul 2020 15:58:21 -0400 Subject: [PATCH 1/2] Support signing multiple release files (#1) --- __tests__/io-utils.test.ts | 14 +++-------- lib/io-utils.js | 17 ++++++-------- lib/main.js | 48 ++++++++++++++++++++------------------ src/io-utils.ts | 14 +++++------ src/main.ts | 48 +++++++++++++++++++------------------- 5 files changed, 65 insertions(+), 76 deletions(-) diff --git a/__tests__/io-utils.test.ts b/__tests__/io-utils.test.ts index 5d9df586..4a0fdbdb 100644 --- a/__tests__/io-utils.test.ts +++ b/__tests__/io-utils.test.ts @@ -1,12 +1,4 @@ -import { Dirent } from "fs"; -import { getReleaseFile } from "../src/io-utils"; - -describe("getReleaseFile", () => { - it("should return undefined when the given release files is an empty array", () => - expect(getReleaseFile([])).toEqual(undefined)); - - it("should return the first release file", () => { - const file = { name: "file" } as Dirent; - expect(getReleaseFile([file])).toEqual(file); - }); +describe("exampleTest", () => { + it("this is a dummy test demonstrating unit testing", () => + expect("hello").toEqual("hello")); }); diff --git a/lib/io-utils.js b/lib/io-utils.js index acfa7bfe..dd63fb84 100644 --- a/lib/io-utils.js +++ b/lib/io-utils.js @@ -3,18 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.findReleaseFile = exports.getReleaseFile = void 0; +exports.findReleaseFiles = void 0; const fs_1 = __importDefault(require("fs")); -function getReleaseFile(files) { - if (files.length > 0) { - return files[0]; - } -} -exports.getReleaseFile = getReleaseFile; -function findReleaseFile(releaseDir) { +function findReleaseFiles(releaseDir) { const releaseFiles = fs_1.default.readdirSync(releaseDir, { withFileTypes: true }) .filter(item => !item.isDirectory()) .filter(item => item.name.endsWith(".apk") || item.name.endsWith(".aab")); - return getReleaseFile(releaseFiles); + console.log("Found " + releaseFiles.length + " release files."); + if (releaseFiles.length > 0) { + return releaseFiles; + } } -exports.findReleaseFile = findReleaseFile; +exports.findReleaseFiles = findReleaseFiles; diff --git a/lib/main.js b/lib/main.js index 6dc13601..c11e8a72 100644 --- a/lib/main.js +++ b/lib/main.js @@ -49,34 +49,36 @@ function run() { const keyStorePassword = core.getInput('keyStorePassword'); const keyPassword = core.getInput('keyPassword'); console.log(`Preparing to sign key @ ${releaseDir} with signing key`); - // 1. Find release file - const releaseFile = io.findReleaseFile(releaseDir); - if (releaseFile !== undefined) { - core.debug(`Found release to sign: ${releaseFile.name}`); - // 3. Now that we have a release file, decode and save the signing key + // 1. Find release files + const releaseFiles = io.findReleaseFiles(releaseDir); + if (releaseFiles !== undefined) { + // 3. Now that we have a release files, decode and save the signing key const signingKey = path_1.default.join(releaseDir, 'signingKey.jks'); fs_1.default.writeFileSync(signingKey, signingKeyBase64, 'base64'); - // 4. Now zipalign the release file - const releaseFilePath = path_1.default.join(releaseDir, releaseFile.name); - let signedReleaseFile = ''; - if (releaseFile.name.endsWith('.apk')) { - signedReleaseFile = yield signing_1.signApkFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword); + // 4. Now zipalign and sign each one of the the release files + for (let releaseFile of releaseFiles) { + core.debug(`Found release to sign: ${releaseFile.name}`); + const releaseFilePath = path_1.default.join(releaseDir, releaseFile.name); + let signedReleaseFile = ''; + if (releaseFile.name.endsWith('.apk')) { + signedReleaseFile = yield signing_1.signApkFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword); + } + else if (releaseFile.name.endsWith('.aab')) { + signedReleaseFile = yield signing_1.signAabFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword); + } + else { + core.error('No valid release file to sign, abort.'); + core.setFailed('No valid release file to sign.'); + } + core.debug('Release signed! Setting outputs.'); + core.exportVariable("SIGNED_RELEASE_FILE", signedReleaseFile); + core.setOutput('signedReleaseFile', signedReleaseFile); } - else if (releaseFile.name.endsWith('.aab')) { - signedReleaseFile = yield signing_1.signAabFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword); - } - else { - core.error('No valid release file to sign, abort.'); - core.setFailed('No valid release file to sign.'); - } - console.log('Release signed!'); - core.debug('Release signed! Setting outputs'); - core.exportVariable("SIGNED_RELEASE_FILE", signedReleaseFile); - core.setOutput('signedReleaseFile', signedReleaseFile); + console.log('Releases signed!'); } else { - core.error("No release file (.apk or .aab) could be found. Abort."); - core.setFailed('No release file (.apk or .aab) could be found.'); + core.error("No release files (.apk or .aab) could be found. Abort."); + core.setFailed('No release files (.apk or .aab) could be found.'); } } catch (error) { diff --git a/src/io-utils.ts b/src/io-utils.ts index 2d661cfd..d997a6a4 100644 --- a/src/io-utils.ts +++ b/src/io-utils.ts @@ -1,17 +1,15 @@ import {Dirent} from "fs"; import fs from "fs"; -export function getReleaseFile(files: Dirent[]): Dirent | undefined { - if (files.length > 0) { - return files[0] - } -} - -export function findReleaseFile(releaseDir: string): Dirent | undefined { +export function findReleaseFiles(releaseDir: string): Dirent[] | undefined { const releaseFiles = fs.readdirSync(releaseDir, {withFileTypes: true}) .filter(item => !item.isDirectory()) .filter(item => item.name.endsWith(".apk") || item.name.endsWith(".aab")); - return getReleaseFile(releaseFiles) + console.log("Found " + releaseFiles.length + " release files.") + + if (releaseFiles.length > 0) { + return releaseFiles + } } diff --git a/src/main.ts b/src/main.ts index 9d7d8918..a0c4cc1d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,35 +19,35 @@ async function run() { console.log(`Preparing to sign key @ ${releaseDir} with signing key`); - // 1. Find release file - const releaseFile = io.findReleaseFile(releaseDir); - if (releaseFile !== undefined) { - core.debug(`Found release to sign: ${releaseFile.name}`); - - // 3. Now that we have a release file, decode and save the signing key + // 1. Find release files + const releaseFiles = io.findReleaseFiles(releaseDir); + if (releaseFiles !== undefined) { + // 3. Now that we have a release files, decode and save the signing key const signingKey = path.join(releaseDir, 'signingKey.jks'); fs.writeFileSync(signingKey, signingKeyBase64, 'base64'); - // 4. Now zipalign the release file - const releaseFilePath = path.join(releaseDir, releaseFile.name); - let signedReleaseFile = ''; - if (releaseFile.name.endsWith('.apk')) { - signedReleaseFile = await signApkFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword); - } else if (releaseFile.name.endsWith('.aab')) { - signedReleaseFile = await signAabFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword); - } else { - core.error('No valid release file to sign, abort.'); - core.setFailed('No valid release file to sign.'); + // 4. Now zipalign and sign each one of the the release files + for (let releaseFile of releaseFiles) { + core.debug(`Found release to sign: ${releaseFile.name}`); + const releaseFilePath = path.join(releaseDir, releaseFile.name); + let signedReleaseFile = ''; + if (releaseFile.name.endsWith('.apk')) { + signedReleaseFile = await signApkFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword); + } else if (releaseFile.name.endsWith('.aab')) { + signedReleaseFile = await signAabFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword); + } else { + core.error('No valid release file to sign, abort.'); + core.setFailed('No valid release file to sign.'); + } + + core.debug('Release signed! Setting outputs.'); + core.exportVariable("SIGNED_RELEASE_FILE", signedReleaseFile); + core.setOutput('signedReleaseFile', signedReleaseFile); } - - console.log('Release signed!'); - core.debug('Release signed! Setting outputs'); - - core.exportVariable("SIGNED_RELEASE_FILE", signedReleaseFile); - core.setOutput('signedReleaseFile', signedReleaseFile); + console.log('Releases signed!'); } else { - core.error("No release file (.apk or .aab) could be found. Abort."); - core.setFailed('No release file (.apk or .aab) could be found.'); + core.error("No release files (.apk or .aab) could be found. Abort."); + core.setFailed('No release files (.apk or .aab) could be found.'); } } catch (error) { core.setFailed(error.message); From ce50c1312f64773966434c7edbe02f2e3a9484af Mon Sep 17 00:00:00 2001 From: Victor Paleologue Date: Mon, 26 Jul 2021 17:15:01 +0200 Subject: [PATCH 2/2] Fixed output variables for the multiple files case --- README.md | 88 ++++++++++++++++++++++++++++++++++++++++++++++++----- action.yml | 32 ++++++++++++++++++- src/main.ts | 24 ++++++++++++--- 3 files changed, 132 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c485a1d8..2173b139 100644 --- a/README.md +++ b/README.md @@ -37,20 +37,26 @@ Then copy the contents of the `.txt` file to your GH secrets ## Outputs -### `signedReleaseFile` +Output variables are set both locally and in environment variables. -The path to the signed release file from this action +### `signedReleaseFile`/ ENV: `SIGNED_RELEASE_FILE` -### ENV: `SIGNED_RELEASE_FILE` +The path to the single release file that have been signed with this action. +Not set if several release files have been signed. -This also set's an environment variable that points to the signed release file +### `signedReleaseFiles` / ENV: `SIGNED_RELEASE_FILES` + +The paths to the release files that have been signed with this action, +separated by `:`. ## Example usage +### Single APK + +The output variable `signedReleaseFile` can be used in a release action. + ```yaml steps: - # ... - - uses: r0adkll/sign-android-release@v1 name: Sign app APK # ID used to access action output @@ -64,10 +70,78 @@ steps: env: // override default build-tools version (29.0.3) -- optional BUILD_TOOLS_VERSION: "30.0.2" - + # Example use of `signedReleaseFile` output -- not needed - uses: actions/upload-artifact@v2 with: name: Signed app bundle path: ${{steps.sign_app.outputs.signedReleaseFile}} ``` + +### Multiple APKs, multiple variables + +The output variables `signedReleaseFileX` +can be used to refer to each signed release file. + +```yaml +steps: + - uses: r0adkll/sign-android-release@v1 + id: sign_app + with: + releaseDirectory: app/build/outputs/apk/release + signingKeyBase64: ${{ secrets.SIGNING_KEY }} + alias: ${{ secrets.ALIAS }} + keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} + keyPassword: ${{ secrets.KEY_PASSWORD }} + + - name: Example Release + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + automatic_release_tag: "latest" + prerelease: true + title: "Release X" + files: | + ${{ steps.sign_app.signedReleaseFile0 }} + ${{ steps.sign_app.signedReleaseFile1 }} + ${{ steps.sign_app.signedReleaseFile2 }} + ${{ steps.sign_app.signedReleaseFile3 }} + ${{ steps.sign_app.signedReleaseFile4 }} +``` + +### Multiple APKs, single variable + +The output variable `signedReleaseFiles` must be split first, +before being used in a release action. + +```yaml +steps: + - uses: r0adkll/sign-android-release@v1 + id: sign_app + with: + releaseDirectory: app/build/outputs/apk/release + signingKeyBase64: ${{ secrets.SIGNING_KEY }} + alias: ${{ secrets.ALIAS }} + keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} + keyPassword: ${{ secrets.KEY_PASSWORD }} + + - uses: jungwinter/split@v1 + id: signed_files + with: + msg: ${{ steps.sign_app.signedReleaseFiles }} + separator: ':' + + - name: Example Release + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + automatic_release_tag: "latest" + prerelease: true + title: "Release X" + files: | + ${{ steps.signed_files._0 }} + ${{ steps.signed_files._1 }} + ${{ steps.signed_files._2 }} + ${{ steps.signed_files._3 }} + ${{ steps.signed_files._4 }} +``` diff --git a/action.yml b/action.yml index 5bda3c5f..3f29fca1 100644 --- a/action.yml +++ b/action.yml @@ -22,7 +22,37 @@ inputs: required: false outputs: signedReleaseFile: - description: 'The signed release APK or AAB file' + description: 'The signed release APK or AAB file, if single' + signedReleaseFiles: + description: 'The signed release APK or AAB files' + nofSignedReleaseFiles: + description: 'The number of signed release files' + signedReleaseFile0: + description: 'The Oth signed release APK or AAB file' + signedReleaseFile1: + description: 'The 1st signed release APK or AAB file' + signedReleaseFile2: + description: 'The 2nd signed release APK or AAB file' + signedReleaseFile3: + description: 'The 3rd signed release APK or AAB file' + signedReleaseFile4: + description: 'The 4th signed release APK or AAB file' + signedReleaseFile5: + description: 'The 5th signed release APK or AAB file' + signedReleaseFile6: + description: 'The 6th signed release APK or AAB file' + signedReleaseFile7: + description: 'The 7th signed release APK or AAB file' + signedReleaseFile8: + description: 'The 8th signed release APK or AAB file' + signedReleaseFile9: + description: 'The 9th signed release APK or AAB file' + signedReleaseFile10: + description: 'The 10th signed release APK or AAB file' + signedReleaseFile11: + description: 'The 11th signed release APK or AAB file' + signedReleaseFile12: + description: 'The 12th signed release APK or AAB file' runs: using: 'node12' main: 'lib/main.js' diff --git a/src/main.ts b/src/main.ts index a0c4cc1d..0b23c074 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,12 +21,14 @@ async function run() { // 1. Find release files const releaseFiles = io.findReleaseFiles(releaseDir); - if (releaseFiles !== undefined) { + if (releaseFiles !== undefined && releaseFiles.length !== 0) { // 3. Now that we have a release files, decode and save the signing key const signingKey = path.join(releaseDir, 'signingKey.jks'); fs.writeFileSync(signingKey, signingKeyBase64, 'base64'); // 4. Now zipalign and sign each one of the the release files + let signedReleaseFiles:string[] = []; + let index = 0; for (let releaseFile of releaseFiles) { core.debug(`Found release to sign: ${releaseFile.name}`); const releaseFilePath = path.join(releaseDir, releaseFile.name); @@ -40,9 +42,23 @@ async function run() { core.setFailed('No valid release file to sign.'); } - core.debug('Release signed! Setting outputs.'); - core.exportVariable("SIGNED_RELEASE_FILE", signedReleaseFile); - core.setOutput('signedReleaseFile', signedReleaseFile); + // Each signed release file is stored in a separate variable + output. + core.exportVariable(`SIGNED_RELEASE_FILE_${index}`, signedReleaseFile); + core.setOutput(`signedReleaseFile${index}`, signedReleaseFile); + signedReleaseFiles.push(signedReleaseFile); + ++index; + } + + // All signed release files are stored in a merged variable + output. + core.exportVariable(`SIGNED_RELEASE_FILES`, signedReleaseFiles.join(":")); + core.setOutput('signedReleaseFiles', signedReleaseFiles.join(":")); + core.exportVariable(`NOF_SIGNED_RELEASE_FILES`, `${signedReleaseFiles.length}`); + core.setOutput(`nofSignedReleaseFiles`, `${signedReleaseFiles.length}`); + + // When there is one and only one signed release file, stoire it in a specific variable + output. + if (signedReleaseFiles.length == 1) { + core.exportVariable(`SIGNED_RELEASE_FILE`, signedReleaseFiles[0]); + core.setOutput('signedReleaseFile', signedReleaseFiles[0]); } console.log('Releases signed!'); } else {