Initial Release

This commit is contained in:
Drew Heavner
2019-11-07 07:08:18 -05:00
parent e7c5891587
commit be955aabfc
7 changed files with 179 additions and 122 deletions
+26 -92
View File
@@ -1,113 +1,47 @@
# Create a JavaScript Action using TypeScript
# Sign Android Release Action
Use this template to bootstrap the creation of a JavaScript action.:rocket:
This action will help you sign an Android `.apk` or `.aab` (Android App Bundle) file for release.
This template includes compilication support, tests, a validation workflow, publishing, and versioning guidance.
## Inputs
If you are new, there's also a simpler introduction. See the [Hello World JavaScript Action](https://github.com/actions/hello-world-javascript-action)
### `releaseDirectory`
## Create an action from this template
**Required:** The relative directory path in your project where your Android release file will be located
Click the `Use this Template` and provide the new repo details for your action
### `signingKeyBase64`
## Code in Master
**Required:** The base64 encoded signing key used to sign your app
Install the dependencies
```bash
$ npm install
```
### `alias`
Build the typescript
```bash
$ npm run build
```
**Required:** The alias of your signing key
Run the tests :heavy_check_mark:
```bash
$ npm test
### `keyStorePassword`
PASS ./index.test.js
✓ throws invalid number (3ms)
wait 500 ms (504ms)
test runs (95ms)
**Required:** The password to your signing keystore
...
```
### `keyPassword`
## Change action.yml
**Required:** The private key password for your signing keystore
The action.yml contains defines the inputs and output for your action.
## Outputs
Update the action.yml with your name, description, inputs and outputs for your action.
### `signedReleaseFile`
See the [documentation](https://help.github.com/en/articles/metadata-syntax-for-github-actions)
The path to the signed release file from this action
## Change the Code
### ENV: `SIGNED_RELEASE_FILE`
Most toolkit and CI/CD operations involve async operations so the action is run in an async function.
This also set's an environment variable that points to the signed release file
```javascript
import * as core from '@actions/core';
...
async function run() {
try {
...
}
catch (error) {
core.setFailed(error.message);
}
}
run()
```
See the [toolkit documentation](https://github.com/actions/toolkit/blob/master/README.md#packages) for the various packages.
## Publish to a distribution branch
Actions are run from GitHub repos. We will create a releases branch and only checkin production modules (core in this case).
Comment out node_modules in .gitignore and create a releases/v1 branch
```bash
# comment out in distribution branches
# node_modules/
```
```bash
$ git checkout -b releases/v1
$ git commit -a -m "prod dependencies"
```
```bash
$ npm prune --production
$ git add node_modules
$ git commit -a -m "prod dependencies"
$ git push origin releases/v1
```
Your action is now published! :rocket:
See the [versioning documentation](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md)
## Validate
You can now validate the action by referencing the releases/v1 branch
## Example usage
```yaml
uses: actions/typescript-action@releases/v1
uses: actions/sign-android-release@v1
with:
milliseconds: 1000
```
See the [actions tab](https://github.com/actions/javascript-action/actions) for runs of this action! :rocket:
## Usage:
After testing you can [create a v1 tag](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md) to reference the stable and tested action
```yaml
uses: actions/typescript-action@v1
with:
milliseconds: 1000
```
releaseDirectory: /app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
```
+24 -6
View File
@@ -1,10 +1,28 @@
name: 'Your name here'
description: 'Provide a description here'
author: 'Your name or organization here'
name: 'Drew Heavner'
description: 'An action to sign an Android release APK or AAB'
author: 'r0adkll'
branding:
icon: 'edit'
color: 'orange'
inputs:
myInput: # change this
description: 'input description here'
default: 'default value if applicable'
releaseDirectory:
description: 'The directory to find your release to sign'
required: true
signingKeyBase64:
description: 'The key used to sign your release in base64 encoded format'
required: true
alias:
description: 'The key alias'
required: true
keyStorePassword:
description: 'The password to the keystore'
required: true
keyPassword:
description: 'The password for the key'
required: true
outputs:
signedReleaseFile:
description: 'The signed release APK or AAB file'
runs:
using: 'node12'
main: 'lib/main.js'
+17 -12
View File
@@ -1,6 +1,6 @@
{
"name": "node12-template-action",
"version": "0.0.0",
"name": "sign-android-release",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -9,6 +9,11 @@
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.0.0.tgz",
"integrity": "sha512-aMIlkx96XH4E/2YZtEOeyrYQfhlas9jIRkfGPqMwXD095Rdkzo4lB6ZmbxPQSzD+e1M+Xsm98ZhuSMYGv/AlqA=="
},
"@actions/exec": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.1.tgz",
"integrity": "sha512-nvFkxwiicvpzNiCBF4wFBDfnBvi7xp/as7LE1hBxBxKG2L29+gkIPBiLKMVORL+Hg3JNf07AKRfl0V5djoypjQ=="
},
"@babel/code-frame": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
@@ -1080,9 +1085,9 @@
}
},
"commander": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true,
"optional": true
},
@@ -2293,9 +2298,9 @@
"dev": true
},
"handlebars": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz",
"integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==",
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
"dev": true,
"requires": {
"neo-async": "^2.6.0",
@@ -4936,13 +4941,13 @@
"dev": true
},
"uglify-js": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
"integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
"version": "3.6.8",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz",
"integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==",
"dev": true,
"optional": true,
"requires": {
"commander": "~2.20.0",
"commander": "~2.20.3",
"source-map": "~0.6.1"
}
},
+7 -6
View File
@@ -1,8 +1,8 @@
{
"name": "typescript-action",
"version": "0.0.0",
"name": "sign-android-release",
"version": "1.0.0",
"private": true,
"description": "TypeScript template action",
"description": "GitHub action used to sign Android release packages",
"main": "lib/main.js",
"scripts": {
"build": "tsc",
@@ -10,17 +10,18 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/actions/typescript-action.git"
"url": "git+https://github.com/r0adkll/sign-android-release.git"
},
"keywords": [
"actions",
"node",
"setup"
],
"author": "YourNameOrOrganization",
"author": "r0adkll",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.0.0"
"@actions/core": "^1.0.0",
"@actions/exec": "^1.0.1"
},
"devDependencies": {
"@types/jest": "^24.0.13",
+14
View File
@@ -0,0 +1,14 @@
import {Dirent} from "fs";
const path = require('path');
const fs = require('fs');
export function findReleaseFile(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"));
if (releaseFiles.length > 0) {
return releaseFiles[0]
}
}
+38 -6
View File
@@ -1,16 +1,48 @@
import * as core from '@actions/core';
import {wait} from './wait'
import {signAabFile, signApkFile} from "./signing";
const path = require('path');
const fs = require('fs');
const io = require('io-utils');
const sign = require('signing');
async function run() {
try {
const ms = core.getInput('milliseconds');
console.log(`Waiting ${ms} milliseconds ...`)
const releaseDir = core.getInput('releaseDirectory');
const signingKeyBase64 = core.getInput('signingKeyBase64');
const alias = core.getInput('alias');
const keyStorePassword = core.getInput('keyStorePassword');
const keyPassword = core.getInput('keyPassword');
core.debug((new Date()).toTimeString())
await wait(parseInt(ms, 10));
core.debug((new Date()).toTimeString())
console.log(`Preparing to sign key @ ${releaseDir} with signing key`);
core.setOutput('time', new Date().toTimeString());
// 1. Find release file
const releaseFile = io.findReleaseFile(releaseDir);
if (releaseFile !== undefined) {
core.debug(`Found release to sign: ${releaseFile.name}`);
// 2. Now that we have a release file, decode and save the signing key
const signingKey = path.join(releaseDir, 'signingKey.jks');
fs.writeFileSync(signingKey, signingKeyBase64, 'base64');
// 3. 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.');
}
core.exportVariable("SIGNED_RELEASE_FILE", signedReleaseFile);
core.setOutput('signedReleaseFile', signedReleaseFile);
} else {
core.error("No release file (.apk or .aab) could be found. Abort.")
core.setFailed('No release file (.apk or .aab) could be found.');
}
} catch (error) {
core.setFailed(error.message);
}
+53
View File
@@ -0,0 +1,53 @@
import * as exec from '@actions/exec';
import * as core from '@actions/core';
export async function signApkFile(
apkFile: string,
signingKeyFile: string,
alias: string,
keyStorePassword: string,
keyPassword: string
): Promise<string> {
core.debug("Zipaligning APK file");
// Align the apk file
const alignedApkFile = apkFile.replace('.apk', '-aligned.apk');
await exec.exec('zipalign', [
'-v', '-p 4',
apkFile,
alignedApkFile
]);
core.debug("Signing APK file");
// apksigner sign --ks my-release-key.jks --out my-app-release.apk my-app-unsigned-aligned.apk
const signedApkFile = apkFile.replace('.apk', '-signed.apk');
await exec.exec('apksigner', [
'sign',
'--ks', signingKeyFile,
'--out', signedApkFile,
alignedApkFile
]);
return signedApkFile
}
export async function signAabFile(
aabFile: string,
signingKeyFile: string,
alias: string,
keyStorePassword: string,
keyPassword: string
): Promise<string> {
core.debug("Signing AAB file");
await exec.exec(`jarsigner`, [
'-keystore', signingKeyFile,
'-storepass', keyStorePassword,
'-keypass', keyPassword,
aabFile,
alias
]);
return aabFile
}