18 Commits

Author SHA1 Message Date
Jack Boswell
935ef9c68b Update dependencies & target node20 (#212)
* Update dependencies & target Node 20

* Bump version
2024-02-10 00:01:23 +13:00
dependabot[bot]
84ffa89623 Bump tj-actions/verify-changed-files in /.github/workflows (#206)
Bumps [tj-actions/verify-changed-files](https://github.com/tj-actions/verify-changed-files) from 9.2 to 17.
- [Release notes](https://github.com/tj-actions/verify-changed-files/releases)
- [Changelog](https://github.com/tj-actions/verify-changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/verify-changed-files/compare/v9.2...v17)

---
updated-dependencies:
- dependency-name: tj-actions/verify-changed-files
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-03 11:18:09 +13:00
Emil Tabakov
b6837fa35c Add instructions to configure service account in README (#201) 2023-11-13 22:57:13 +13:00
dependabot[bot]
7c9dfbb03e Bump @babel/traverse from 7.19.6 to 7.23.2 (#198)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.19.6 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 11:57:55 +13:00
Jack Boswell
9cf8fd30c9 Update documentation to link to Google's track name information (#191) 2023-09-19 16:44:00 +12:00
Jack Boswell
101c2cddf9 Update all dependencies and bump version (#192) 2023-09-19 16:43:48 +12:00
dependabot[bot]
d17e3fc745 Bump word-wrap from 1.2.3 to 1.2.4 (#184)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-19 10:14:15 +12:00
Jack Boswell
70c7397e86 Fix deprecated releaseFile input not being considered (#174) 2023-02-11 12:10:44 +13:00
Jack Boswell
853233422d Fix a bug where our new timeout caused the action to stay "alive" (#173) 2023-02-11 12:06:30 +13:00
Jack Boswell
9a5e7fcdd5 Add an upload timeout of 60 minutes (#171)
* Add an upload timeout of 60 minutes

* Remove now defunct tests
2023-02-10 22:59:20 +13:00
Sindre Moen
02c72d76fe Remove status default from README (#164)
* Remove status default from README

* Default status to `completed`
2023-02-10 22:00:07 +13:00
Shi Chen
cd131e853c feature: Add support for App Bundle Explorer's Internal app sharing link. (#166)
Updated json5 dependency to patch vulnerability.
2023-01-13 16:55:12 +13:00
Saalihou Ndiaye
8a353d6e46 Add doc about packageName needing to exist first (#168) 2023-01-13 15:31:31 +13:00
dependabot[bot]
d5cf6e9445 Bump json5 from 2.2.1 to 2.2.3 (#167)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-13 15:25:47 +13:00
Ivan Milisavljevic
b9dc4464a6 Dont fail the action if optional files are not present on the dics (#162) 2022-12-07 15:19:47 +13:00
Keisuke Yamaguchi
974d2d7566 Update userFraction reuqired to false in README (#157)
* Update userFraction reuqired to false

* Update userFraction to exclusive-exclusive and delete default value
2022-11-23 17:13:43 +13:00
Jack Boswell
536f822b60 Update release workflow (#155) 2022-11-03 19:22:11 +13:00
Jack Boswell
d7e6e42414 fix: Bring back the default release status (#154)
* Bring back the default release status

* Create a test for the bare minimum inputs
2022-11-03 19:11:20 +13:00
10 changed files with 2044 additions and 1860 deletions

View File

@@ -28,7 +28,7 @@ jobs:
# Check whether the compiled library changed
- name: Check Build Updated
uses: tj-actions/verify-changed-files@v9.2
uses: tj-actions/verify-changed-files@v17
id: verify-built-lib
with:
files: lib/index.js

View File

@@ -1,24 +1,16 @@
name: "Tag & Deploy Action"
name: "Update v1 tag"
on:
push:
branches:
- master
release:
types: [released]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2.5.0
- name: Install
run: npm ci
- name: Run Tests
run: npm run test
- name: Update v1 Tag
uses: hole19/git-tag-action@master
env:
TAG: v1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: rickstaa/action-create-tag@v1
with:
tag: v1
force_push_tag: true

View File

@@ -8,12 +8,12 @@ This action will help you upload an Android `.apk` or `.aab` (Android App Bundle
| --- | --- | --- | --- |
| 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 |
| packageName | The package name, or Application Id, of the app you are uploading | A valid package name, e.g. `com.example.myapp`. The packageName must already exist in the play console account, so make sure you upload a manual apk or aab first through the console | true |
| track | The track in which you want to assign the uploaded app. Defaults to `production` | See [APKs and Tracks](https://developers.google.com/android-publisher/tracks#adding_and_modifying_apks) documentation for calculating your target track name | 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]` | true |
| status | Release status. Defaults to `completed` if targeting 100% rollout, `halted` for 0%, and `inProgress` for anything in between | One of `completed`, `inProgress`, `halted`, `draft` | false |
| userFraction | Percentage of users who should get the staged version of the app. | `(0.0-1.0)` | false |
| status | Release status. Defaults to `completed`. | One of `completed`, `inProgress`, `halted`, `draft`. Cannot be null. | 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 |
| debugSymbols | The native-debug-symbols.zip file or folder that contains your debug symbols | A path to a valid `native-debug-symbols.zip file` file or a folder | false |
@@ -48,6 +48,24 @@ with:
debugSymbols: app/intermediates/merged_native_libs/release/out/lib
```
## Configure service account
1. Configure service account in Google Cloud Platform
1. Navigate to https://cloud.google.com/gcp
1. Open IAM and admin > Service accounts > Create service account
1. Pick a name and add appropriate permissions (e.g. 'owner)
1. Open the newly created service account, click on `keys` tab and add a new key, JSON type
1. When successful, a JSON file will be automatically downloaded on your machine
1. Store the content of this file to your GitHub secrets, e.g. SERVICE_ACCOUNT_JSON.
1. Add user to Google Play Console
1. Open https://play.google.com/console and pick your developer account
1. Open Users and permissions
1. Click invite new user and add the email of the service account created in the previous step
1. Grant permissions to the app that you want the service account to deploy in `app permissions`
## FAQ
### I get the error "Package not found"
Make sure you upload an apk or aab manually first by creating a release through the play console.
The `whatsNewDirectory` in this example supplies changelogs for English, German and Japanese
```

View File

@@ -8,6 +8,17 @@ async function expectRunInitiatesUpload(options: RunOptions) {
expect(result.commands.errors).toContainEqual("The incoming JSON object does not contain a client_email field")
}
test("correct bare minimum inputs", async () => {
const minimumOptions = RunOptions.create()
.setInputs({
serviceAccountJsonPlainText: "{}",
packageName: "com.package.name",
releaseFiles: "./__tests__/releasefiles/*.aab",
track: "production",
})
await expectRunInitiatesUpload(minimumOptions)
})
test("correct inputs for complete rollout", async () => {
// Run with the bare minimum
const minimumOptions = RunOptions.create()
@@ -34,8 +45,8 @@ test("correct inputs for complete rollout", async () => {
changesNotSentForReview: "true",
existingEditId: "123"
})
await expectRunInitiatesUpload(extraOptions)
})
await expectRunInitiatesUpload(extraOptions)
})
test("correct inputs for inProgress rollout", async () => {
// Run with the bare minimum
@@ -48,7 +59,7 @@ test("correct inputs for inProgress rollout", async () => {
userFraction: "0.99",
status: "inProgress",
})
await expectRunInitiatesUpload(minimumOptions)
await expectRunInitiatesUpload(minimumOptions)
// Test with optional extras
const extraOptions = RunOptions.create()
@@ -65,7 +76,7 @@ test("correct inputs for inProgress rollout", async () => {
changesNotSentForReview: "true",
existingEditId: "123"
})
await expectRunInitiatesUpload(extraOptions)
await expectRunInitiatesUpload(extraOptions)
})
test("correct inputs for draft rollout", async () => {
@@ -78,7 +89,7 @@ test("correct inputs for draft rollout", async () => {
track: "production",
status: "draft",
})
await expectRunInitiatesUpload(minimumOptions)
await expectRunInitiatesUpload(minimumOptions)
// Test with optional extras
const extraOptions = RunOptions.create()
@@ -94,5 +105,5 @@ test("correct inputs for draft rollout", async () => {
changesNotSentForReview: "true",
existingEditId: "123"
})
await expectRunInitiatesUpload(extraOptions)
})
await expectRunInitiatesUpload(extraOptions)
})

View File

@@ -36,7 +36,8 @@ inputs:
required: false
status:
description: "Release status. This can be set to 'draft' to complete the release at some other time."
required: false
required: true
default: "completed"
whatsNewDirectory:
description: "The directory of localized whats new files"
required: false
@@ -57,5 +58,5 @@ outputs:
internalSharingDownloadUrl:
description: "The internal app sharing download url if track was 'internalsharing'"
runs:
using: 'node16'
using: 'node20'
main: 'lib/index.js'

File diff suppressed because one or more lines are too long

3690
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.19",
"version": "1.1.3",
"private": true,
"description": "Upload an Android release (.apk or .aab) to Google Play Console",
"main": "lib/index.js",
@@ -20,21 +20,22 @@
"author": "r0adkll",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.0",
"@googleapis/androidpublisher": "^3.0.0",
"fast-glob": "^3.2.12",
"jszip": "^3.10.1"
"@actions/core": "^1.10.1",
"@googleapis/androidpublisher": "^12.1.0",
"fast-glob": "^3.3.2",
"jszip": "^3.10.1",
"p-timeout": "^6.1.2"
},
"devDependencies": {
"@types/jest": "^29.2.0",
"@types/node": "^18.11.4",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/parser": "^5.40.1",
"@vercel/ncc": "^0.34.0",
"eslint": "^8.26.0",
"github-action-ts-run-api": "^2.2.2",
"jest": "^29.2.1",
"ts-jest": "^29.0.3",
"typescript": "^4.8.4"
"@types/jest": "^29.5.12",
"@types/node": "^20.11.17",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vercel/ncc": "^0.38.1",
"eslint": "^8.56.0",
"github-action-ts-run-api": "^3.0.4",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"typescript": "^5.3.3"
}
}

View File

@@ -13,14 +13,13 @@ import Apk = androidpublisher_v3.Schema$Apk;
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 { JSONClient } from "google-auth-library/build/src/auth/googleauth"
import { GoogleAuth } from "google-auth-library/build/src/auth/googleauth"
import { readLocalizedReleaseNotes } from "./whatsnew";
const androidPublisher: AndroidPublisher = google.androidpublisher('v3');
export interface EditOptions {
auth: Compute | JSONClient;
auth: GoogleAuth;
applicationId: string;
track: string;
inAppUpdatePriority: number;
@@ -52,10 +51,8 @@ export async function runUpload(
scopes: ['https://www.googleapis.com/auth/androidpublisher']
});
const authClient = await auth.getClient();
const result = await uploadToPlayStore({
auth: authClient,
auth: auth,
applicationId: packageName,
track: track,
inAppUpdatePriority: inAppUpdatePriority || 0,
@@ -75,17 +72,16 @@ export async function runUpload(
}
async function uploadToPlayStore(options: EditOptions, releaseFiles: string[]): Promise<string | void> {
const internalSharingDownloadUrls: string[] = []
// Check the 'track' for 'internalsharing', if so switch to a non-track api
if (options.track === 'internalsharing') {
core.debug("Track is Internal app sharing, switch to special upload api")
const downloadUrls: string[] = []
for (const releaseFile of releaseFiles) {
core.debug(`Uploading ${releaseFile}`);
const url = await uploadInternalSharingRelease(options, releaseFile)
downloadUrls.push(url)
internalSharingDownloadUrls.push(url)
}
core.setOutput("internalSharingDownloadUrls", downloadUrls);
core.exportVariable("INTERNAL_SHARING_DOWNLOAD_URLS", downloadUrls);
} else {
// Create a new Edit
const appEditId = await getOrCreateEdit(options)
@@ -96,6 +92,14 @@ async function uploadToPlayStore(options: EditOptions, releaseFiles: string[]):
// Upload artifacts to Google Play, and store their version codes
const versionCodes = await uploadReleaseFiles(appEditId, options, releaseFiles)
// Infer the download URL from the version codes
for (const versionCode of versionCodes) {
const url = inferInternalSharingDownloadUrl(options.applicationId, versionCode);
core.setOutput("internalSharingDownloadUrl", url);
core.exportVariable("INTERNAL_SHARING_DOWNLOAD_URL", url);
internalSharingDownloadUrls.push(url);
}
// Add the uploaded artifacts to the Edit track
await addReleasesToTrack(appEditId, options, versionCodes);
@@ -117,6 +121,9 @@ async function uploadToPlayStore(options: EditOptions, releaseFiles: string[]):
return Promise.reject(res.status);
}
}
core.setOutput("internalSharingDownloadUrls", internalSharingDownloadUrls);
core.exportVariable("INTERNAL_SHARING_DOWNLOAD_URLS", internalSharingDownloadUrls);
}
async function uploadInternalSharingRelease(options: EditOptions, releaseFile: string): Promise<string> {
@@ -398,3 +405,10 @@ async function uploadReleaseFiles(appEditId: string, options: EditOptions, relea
return versionCodes
}
function inferInternalSharingDownloadUrl(
applicationId: string,
versionCode: number
) {
return `https://play.google.com/apps/test/${applicationId}/${versionCode}`;
}

View File

@@ -1,8 +1,9 @@
import * as core from '@actions/core';
import * as fs from "fs";
import { runUpload } from "./edits";
import * as core from '@actions/core'
import * as fs from "fs"
import { runUpload } from "./edits"
import { validateInAppUpdatePriority, validateReleaseFiles, validateStatus, validateUserFraction } from "./input-validation"
import { unlink, writeFile } from 'fs/promises';
import { unlink, writeFile } from 'fs/promises'
import pTimeout from 'p-timeout'
export async function run() {
try {
@@ -12,7 +13,7 @@ export async function run() {
const releaseFile = core.getInput('releaseFile', { required: false });
const releaseFiles = core.getInput('releaseFiles', { required: false })
?.split(',')
?.filter(x => x !== '') || [];
?.filter(x => x !== '');
const releaseName = core.getInput('releaseName', { required: false });
const track = core.getInput('track', { required: true });
const inAppUpdatePriority = core.getInput('inAppUpdatePriority', { required: false });
@@ -54,33 +55,35 @@ export async function run() {
const validatedReleaseFiles: string[] = await validateReleaseFiles(releaseFiles ?? [releaseFile])
if (whatsNewDir != undefined && whatsNewDir.length > 0 && !fs.existsSync(whatsNewDir)) {
core.setFailed(`Unable to find 'whatsnew' directory @ ${whatsNewDir}`);
return
core.warning(`Unable to find 'whatsnew' directory @ ${whatsNewDir}`);
}
if (mappingFile != undefined && mappingFile.length > 0 && !fs.existsSync(mappingFile)) {
core.setFailed(`Unable to find 'mappingFile' @ ${mappingFile}`);
return
core.warning(`Unable to find 'mappingFile' @ ${mappingFile}`);
}
if (debugSymbols != undefined && debugSymbols.length > 0 && !fs.existsSync(debugSymbols)) {
core.setFailed(`Unable to find 'debugSymbols' @ ${debugSymbols}`);
return
core.warning(`Unable to find 'debugSymbols' @ ${debugSymbols}`);
}
await runUpload(
packageName,
track,
inAppUpdatePriorityInt,
userFractionFloat,
whatsNewDir,
mappingFile,
debugSymbols,
releaseName,
changesNotSentForReview,
existingEditId,
status,
validatedReleaseFiles
await pTimeout(
runUpload(
packageName,
track,
inAppUpdatePriorityInt,
userFractionFloat,
whatsNewDir,
mappingFile,
debugSymbols,
releaseName,
changesNotSentForReview,
existingEditId,
status,
validatedReleaseFiles
),
{
milliseconds: 3.6e+6
}
)
} catch (error: unknown) {
if (error instanceof Error) {