21 Commits

Author SHA1 Message Date
Jack Boswell (boswelja)
7f42989517 Ensure deps are up to date and do a build 2022-06-18 18:57:34 +12:00
Jack Boswell (boswelja)
6fefbe4681 Prep for release 2022-06-18 18:54:49 +12:00
Jack Boswell
f1479e715c Chore: Update readme (#115)
* Update README formatting

* Sort inputs by required

* Sort track values by priority

* Table-ify inputs in README

* Table-ify outputs too

* Bring back the `whatsNewDirectory` example
2022-06-18 18:52:48 +12:00
Jack Boswell
7b7381ed55 Added cleanup to generated service account file (#116)
Co-authored-by: Drew Heavner <veedubusc@gmail.com>
2022-06-18 18:32:34 +12:00
Jack Boswell
642ba59b8a Create pr-validation.yml (#114) 2022-06-15 17:30:19 +12:00
Jack Boswell
dd8dcab872 Chore: Update dependencies (#113)
* Bump minor versions

* Fix error not having a type

* Remove jest-circus

* Bump ncc

* Remove ts-jest

* Drop googleapis for androidpublisher in particular
Way smaller compiled size, and way shorter compile time

* Bump jest

* Bring back ts-jest

* Bump @types/node
2022-06-15 17:13:36 +12:00
dependabot[bot]
cf500ad293 Bump tmpl from 1.0.4 to 1.0.5 (#110)
Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/daaku/nodejs-tmpl/releases)
- [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5)

---
updated-dependencies:
- dependency-name: tmpl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 13:53:24 +12:00
dependabot[bot]
354ae8f121 Bump ansi-regex from 5.0.0 to 5.0.1 (#107)
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 13:52:46 +12:00
dependabot[bot]
b36eb787b2 Bump node-fetch from 2.6.1 to 2.6.7 (#108)
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 13:52:27 +12:00
dependabot[bot]
c23d6291fa Bump lodash from 4.17.20 to 4.17.21 (#106)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

---
updated-dependencies:
- dependency-name: lodash
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 13:52:13 +12:00
dependabot[bot]
8eb5399590 Bump minimist from 1.2.5 to 1.2.6 (#99)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 13:51:01 +12:00
Tobias Schürg
51e4495788 docs: add status property to examples (#94) 2022-06-14 20:09:37 -04:00
Doug Keen
15ee0081ab fix: default behavior when status is unspecified (#80) (#87) 2022-06-14 20:03:26 -04:00
Pablo A Martínez Andrés
8c2ebfdb6e Fix: Support BCP 47 language codes without contry code (#73)
* Updated regex to support BCP 47 language codes without country code

* Updated whatsnew tests

Co-authored-by: Pablo A. Martínez <pamartinez@boiradev.com>
2021-09-21 12:26:56 -04:00
Doug Keen
060a2efa0e feat: Add support for explicitly specifying release status (#78) 2021-09-21 10:57:13 -04:00
Drew Heavner
9745ef904e Merge pull request #66 from boswelja/update-deps
Update dependencies
2021-04-01 19:10:50 -04:00
Jack Boswell (boswelja)
f4405077a3 Update types and run audit to fix high severity vulnerability 2021-03-30 19:38:15 +13:00
Drew Heavner
f7ef0f909f Updating workflow to run 'npm ci' before tests 2021-03-19 15:33:29 +00:00
Jack Boswell (boswelja)
dfb1c38c76 Update all dependencies and rebuild
index.js appears to be a bit smaller after this
2021-03-19 13:43:18 +13:00
Jack Boswell (boswelja)
413cd4d781 Merge branch 'ncc-build' into update-googleapis 2021-03-19 13:38:41 +13:00
Jack Boswell (boswelja)
a7fe768d8d Update googleapis
Hoping to fix error 502 with this
2021-01-26 19:33:41 +13:00
14 changed files with 3133 additions and 14227 deletions

30
.github/workflows/pr-validation.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
# This is a basic workflow to help you get started with Actions
name: PR Validation
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the "master" branch
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Runs a single command using the runners shell
- name: Set up NPM
run: npm ci
# Runs a set of commands using the runners shell
- name: Run Build
run: npm run build
- name: Run Tests
run: npm test

View File

@@ -11,6 +11,9 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Install
run: npm ci
- name: Run Tests
run: npm run test

125
README.md
View File

@@ -4,115 +4,52 @@ This action will help you upload an Android `.apk` or `.aab` (Android App Bundle
## Inputs
_You must provide one of either `serviceAccountJson` or `serviceAccountJsonPlainText`_
### `serviceAccountJson`
The service account json private key file to authorize the upload request
### `serviceAccountJsonPlainText`
The service account json in plain text, provided via a secret, etc.
### `packageName`
**Required:** The package name, or Application Id, of the app you are uploading
### `releaseFile`
**DEPRECATED:** Please switch to using `releaseFiles` as this will be removed in the future
The Android release file to upload (.apk or .aab)
### `releaseFiles`
**CAVEAT:** Either this or `releaseFile` are required
The Android release file(s) to upload (.apk or .aab). Multiple files are separated by ','. Supports glob.
### `releaseName`
The release name. Not required to be unique. If not set, the name is generated from the APK's versionName. If the release contains multiple APKs, the name is generated from the date.
### `track`
**Required:** The track in which you want to assign the uploaded app.
**Default:** `production`
_Values:_ `alpha`, `beta`, `internal`, `production`, `internalsharing`
### `inAppUpdatePriority`
In-app update priority of the release. All newly added APKs in the release will be considered at this priority. Can take values in the range [0, 5], with 5 the highest priority.
**Default:** `0`
_Values:_ `[0, 5]`
### `userFraction`
Portion of users who should get the staged version of the app. Accepts values between 0.0 and 1.0 (exclusive-exclusive). Omitting this value will execute a full rollout.
### `whatsNewDirectory`
The directory of localized whats new files to upload as the release notes. The files contained in the `whatsNewDirectory` MUST use the pattern `whatsnew-<LOCALE>` where `LOCALE` is using the [`BCP 47`](https://tools.ietf.org/html/bcp47) format, e.g.
* `en-US` - English/America
* `de-DE` - German/Germany
* `ja-JP` - Japanese/Japan
and contain plain `utf8` encoded text with no extension on the file. The resulting directory in your project should look something like this:
```
projectDir/
└── whatsNewDirectory/
├── whatsnew-en-US
├── whatsnew-de-DE
└── whatsnew-ja-JP
```
where `whatsNewDirectory` is the path you pass to the action.
### `mappingFile`
The mapping.txt file used to de-obfuscate your stack traces from crash reports
| Input | Description | Value | Required |
| --- | --- | --- | --- |
| releaseFiles | The Android release file(s) to upload (.apk or .aab) | Comma-separated paths. Supports glob via [fast-glob](https://github.com/mrmlnc/fast-glob) | true |
| serviceAccountJsonPlainText | The service account json in plain text, provided via a secret, etc | The contents of your `service-account.json` | true (or serviceAccountJson) |
| packageName | The package name, or Application Id, of the app you are uploading | A valid package name, e.g. `com.example.myapp` | true |
| track | The track in which you want to assign the uploaded app. Defaults to `production` | One of `production`, `beta`, `alpha`, `internalsharing`, `internal`, or a custom track name (case sensitive) | true |
| releaseName | The release name. Not required to be unique. Default is configured by Google Play Console | A user-friendly update name, e.g. `v1.0.0` | false |
| inAppUpdatePriority | In-app update priority of the release. All newly added APKs in the release will be considered at this priority. Defaults to `0` | `[0-5]`, where `5` is the highest priority | false |
| userFraction | Percentage of users who should get the staged version of the app. Defaults to `1.0` | `(0.0-1.0)` | false |
| status | Release status. This can be set to `draft` to complete the release at some other time. Defaults to `completed` if targeting 100% rollout, else `inProgress` | One of `completed`, `inProgress`, `halted`, `draft` | false |
| whatsNewDirectory | The directory of localized "whats new" files to upload as the release notes. The files contained in the `whatsNewDirectory` MUST use the pattern `whatsnew-<LOCALE>` where `LOCALE` is using the [`BCP 47`](https://tools.ietf.org/html/bcp47) format | A path to a valid `whatsNewDirectory` | false |
| mappingFile | The mapping.txt file used to de-obfuscate your stack traces from crash reports | A path to a valid `mapping.txt` file | false |
| serviceAccountJson | The service account json private key file to authorize the upload request. Can be used instead of `serviceAccountJsonPlainText` to specify a file rather than provide a secret | A path to a valid `service-account.json` file | true (or serviceAccountJsonPlainText) |
| ~~releaseFile~~ | Please switch to using `releaseFiles` as this will be removed in the future | | false |
## Outputs
### `internalSharingDownloadUrls`
A JSON list containing the download urls for every release file uploaded using the `track` `internalsharing`
### ENV VAR: `INTERNAL_SHARING_DOWNLOAD_URLS`
The environment variable that is set when using the `track` `internalsharing`
### `internalSharingDownloadUrl`
The download url for the last release file uploaded using the `track` `internalsharing`, useful when a single release file is uploaded
### ENV VAR: `INTERNAL_SHARING_DOWNLOAD_URL`
The environment variable that is set when using the `track` `internalsharing`
| Output | Environment Variable | Description |
| --- | --- | --- |
| internalSharingDownloadUrls | INTERNAL_SHARING_DOWNLOAD_URLS | A JSON list containing the download urls for every release file uploaded using the `internalsharing` track |
| internalSharingDownloadUrl | INTERNAL_SHARING_DOWNLOAD_URL | The download url for the last release file uploaded using the `internalsharing` track |
## Example usage
The below example publishes `MyApp` to Google Play, targetting 33% (`0.33`) of users with a priority of `2`.
```yaml
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJson: ${{ SERVICE_ACCOUNT_JSON }}
serviceAccountJsonPlainText: ${{ SERVICE_ACCOUNT_JSON }}
packageName: com.example.MyApp
releaseFile: ${{ SIGNED_RELEASE_FILE}}
releaseFiles: app/build/outputs/bundle/release/app-release.aab
track: production
status: inProgress
inAppUpdatePriority: 2
userFraction: 0.33
whatsNewDirectory: distribution/whatsnew
mappingFile: app/build/outputs/mapping/release/mapping.txt
```
Using glob to get release files
```yaml
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJson: ${{ SERVICE_ACCOUNT_JSON }}
packageName: com.example.MyApp
releaseFiles: app/build/outputs/bundle/release/*.aab
track: production
inAppUpdatePriority: 2
userFraction: 0.33
whatsNewDirectory: distribution/whatsnew
mappingFile: app/build/outputs/mapping/release/mapping.txt
The `whatsNewDirectory` in this example supplies changelogs for English, German and Japanese
```
distribution/
└─ whatsnew/
├─ whatsnew-en-US
├─ whatsnew-de-DE
└─ whatsnew-ja-JP
```

1
__tests__/whatsnew-ca Normal file
View File

@@ -0,0 +1 @@
test_changelog_file_catalan

1
__tests__/whatsnew-es-ES Normal file
View File

@@ -0,0 +1 @@
test_changelog_file_spanish

1
__tests__/whatsnew-et Normal file
View File

@@ -0,0 +1 @@
test_changelog_file_estonian

View File

@@ -2,12 +2,12 @@ import * as process from 'process'
import * as cp from 'child_process'
import * as path from 'path'
import {readLocalizedReleaseNotes} from "../src/whatsnew";
import {androidpublisher_v3} from "googleapis";
import {androidpublisher_v3} from "@googleapis/androidpublisher";
import LocalizedText = androidpublisher_v3.Schema$LocalizedText;
test("read localized whatsnew files", async () => {
let texts = await readLocalizedReleaseNotes("./__tests__");
expect(texts).toHaveLength(3);
expect(texts).toHaveLength(6);
expect(texts).toContainEqual({
language: "en-US",
text: "test_changelog_file"
@@ -20,4 +20,16 @@ test("read localized whatsnew files", async () => {
language: "ja-JP",
text: "test_changelog_file_japanese"
});
expect(texts).toContainEqual({
language: "ca",
text: "test_changelog_file_catalan"
});
expect(texts).toContainEqual({
language: "es-ES",
text: "test_changelog_file_spanish"
});
expect(texts).toContainEqual({
language: "et",
text: "test_changelog_file_estonian"
});
});

View File

@@ -34,6 +34,9 @@ inputs:
userFraction:
description: "Portion of users who should get the staged version of the app. Accepts values between 0.0 and 1.0 (exclusive-exclusive)."
required: false
status:
description: "Release status. This can be set to 'draft' to complete the release at some other time."
required: false
whatsNewDirectory:
description: "The directory of localized whats new files"
required: false
@@ -44,5 +47,5 @@ outputs:
internalSharingDownloadUrl:
description: "The internal app sharing download url if track was 'internalsharing'"
runs:
using: 'node12'
using: 'node16'
main: 'lib/index.js'

File diff suppressed because one or more lines are too long

17066
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "upload-google-play",
"version": "1.0.13",
"version": "1.0.17",
"private": true,
"description": "Upload an Android release (.apk or .aab) to Google Play Console",
"main": "lib/index.js",
@@ -20,18 +20,16 @@
"author": "r0adkll",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.4",
"fast-glob": "^3.2.5",
"googleapis": "^64.0.0"
"@actions/core": "^1.9.0",
"@googleapis/androidpublisher": "^3.0.0",
"fast-glob": "^3.2.5"
},
"devDependencies": {
"@types/jest": "^24.9.1",
"@types/node": "^12.12.26",
"@vercel/ncc": "^0.27.0",
"jest": "^26.6.3",
"jest-circus": "^26.6.3",
"ts-jest": "^26.4.4",
"typescript": "^4.1.3"
"@types/jest": "^28.1.2",
"@types/node": "^18.0.0",
"@vercel/ncc": "^0.34.0",
"jest": "^28.1.1",
"ts-jest": "^28.0.5",
"typescript": "^4.7.4"
}
}

View File

@@ -2,8 +2,8 @@ import * as core from '@actions/core';
import * as fs from "fs";
import {readFileSync} from "fs";
import {google} from 'googleapis';
import {androidpublisher_v3} from "googleapis";
import * as google from '@googleapis/androidpublisher';
import {androidpublisher_v3} from "@googleapis/androidpublisher";
import AndroidPublisher = androidpublisher_v3.Androidpublisher;
import AppEdit = androidpublisher_v3.Schema$AppEdit;
@@ -12,14 +12,13 @@ import Bundle = androidpublisher_v3.Schema$Bundle;
import Track = androidpublisher_v3.Schema$Track;
import InternalAppSharingArtifact = androidpublisher_v3.Schema$InternalAppSharingArtifact;
import {Compute} from "google-auth-library/build/src/auth/computeclient";
import {JWT} from "google-auth-library/build/src/auth/jwtclient";
import {UserRefreshClient} from "google-auth-library/build/src/auth/refreshclient";
import {JSONClient} from "google-auth-library/build/src/auth/googleauth"
import {readLocalizedReleaseNotes} from "./whatsnew";
const androidPublisher: AndroidPublisher = google.androidpublisher('v3');
export interface EditOptions {
auth: Compute | JWT | UserRefreshClient;
auth: Compute | JSONClient;
applicationId: string;
track: string;
inAppUpdatePriority: number;
@@ -27,6 +26,7 @@ export interface EditOptions {
whatsNewDir?: string;
mappingFile?: string;
name?: string;
status?: string;
}
export async function uploadToPlayStore(options: EditOptions, releaseFiles: string[]): Promise<string | undefined> {
@@ -143,14 +143,16 @@ async function validateSelectedTrack(appEdit: AppEdit, options: EditOptions): Pr
}
async function addReleasesToTrack(appEdit: AppEdit, options: EditOptions, versionCodes: number[]): Promise<Track> {
let status: string;
if (options.userFraction != undefined) {
status = 'inProgress';
} else {
status = 'completed';
let status: string | undefined = options.status;
if (!status) {
if (options.userFraction != undefined) {
status = 'inProgress';
} else {
status = 'completed';
}
}
core.debug(`Creating Track Release for Edit(${appEdit.id}) for Track(${options.track}) with a UserFraction(${options.userFraction}) and VersionCodes(${versionCodes})`);
core.debug(`Creating Track Release for Edit(${appEdit.id}) for Track(${options.track}) with a UserFraction(${options.userFraction}), Status(${status}), and VersionCodes(${versionCodes})`);
const res = await androidPublisher.edits.tracks
.update({
auth: options.auth,
@@ -254,4 +256,4 @@ async function uploadBundle(appEdit: AppEdit, options: EditOptions, bundleReleas
});
return res.data
}
}

View File

@@ -1,8 +1,8 @@
import * as core from '@actions/core';
import * as fs from "fs";
import fg from "fast-glob";
import { uploadToPlayStore } from "./edits";
const {google} = require('googleapis');
import {uploadToPlayStore} from "./edits";
import * as google from '@googleapis/androidpublisher';
const auth = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/androidpublisher']
@@ -21,6 +21,7 @@ async function run() {
const track = core.getInput('track', { required: true });
const inAppUpdatePriority = core.getInput('inAppUpdatePriority', { required: false });
const userFraction = core.getInput('userFraction', { required: false });
const status = core.getInput('status', { required: false });
const whatsNewDir = core.getInput('whatsNewDirectory', { required: false });
const mappingFile = core.getInput('mappingFile', { required: false });
@@ -112,14 +113,21 @@ async function run() {
track: track,
inAppUpdatePriority: inAppUpdatePriorityInt || 0,
userFraction: userFractionFloat,
status: status,
whatsNewDir: whatsNewDir,
mappingFile: mappingFile,
name: releaseName
}, validatedReleaseFiles);
console.log(`Finished uploading to the Play Store: ${result}`)
} catch (error) {
} catch (error: any) {
core.setFailed(error.message)
} finally {
if (core.getInput('serviceAccountJsonPlainText', { required: false})) {
// Cleanup our auth file that we created.
core.debug('Cleaning up service account json file');
fs.unlinkSync('./serviceAccountJson.json');
}
}
}

View File

@@ -2,14 +2,15 @@ import * as core from '@actions/core';
import * as fs from "fs";
import * as path from "path";
import {readFileSync} from "fs";
import {androidpublisher_v3} from "googleapis";
import {androidpublisher_v3} from "@googleapis/androidpublisher";
import LocalizedText = androidpublisher_v3.Schema$LocalizedText;
export async function readLocalizedReleaseNotes(whatsNewDir: string | undefined): Promise<LocalizedText[] | undefined> {
core.debug(`Executing readLocalizedReleaseNotes`);
if (whatsNewDir != undefined && whatsNewDir.length > 0) {
const releaseNotes = fs.readdirSync(whatsNewDir)
.filter(value => /whatsnew-.*-.{2,3}\b/.test(value));
const pattern = /whatsnew-(?<local>.*-.*)/;
.filter(value => /whatsnew-((.*-.*)|(.*))\b/.test(value));
const pattern = /whatsnew-(?<local>(.*-.*)|(.*))/;
let localizedReleaseNotes: LocalizedText[] = [];
@@ -17,7 +18,7 @@ export async function readLocalizedReleaseNotes(whatsNewDir: string | undefined)
releaseNotes.forEach(value => {
const matches = value.match(pattern);
core.debug(`Matches for ${value} = ${matches}`);
if (matches != undefined && matches.length == 2) {
if (matches != undefined && matches.length == 4) {
const lang = matches[1];
const filePath = path.join(whatsNewDir, value);
const content = readFileSync(filePath, 'utf-8');