Merge pull request #49 from victorpaleologue/multiple_signing

Support for signing multiple release files
This commit is contained in:
Drew Heavner
2021-08-13 08:52:33 -04:00
committed by GitHub
7 changed files with 191 additions and 82 deletions
+81 -7
View File
@@ -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 }}
```
+3 -11
View File
@@ -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"));
});
+31 -1
View File
@@ -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'
+7 -10
View File
@@ -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;
+25 -23
View File
@@ -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) {
+6 -8
View File
@@ -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
}
}
+38 -22
View File
@@ -19,35 +19,51 @@ 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 && 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 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
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);
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.');
}
// 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;
}
console.log('Release signed!');
core.debug('Release signed! Setting outputs');
// 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}`);
core.exportVariable("SIGNED_RELEASE_FILE", signedReleaseFile);
core.setOutput('signedReleaseFile', signedReleaseFile);
// 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 {
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);