Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cf8fd30c9 | ||
|
|
101c2cddf9 | ||
|
|
d17e3fc745 | ||
|
|
70c7397e86 | ||
|
|
853233422d | ||
|
|
9a5e7fcdd5 | ||
|
|
02c72d76fe | ||
|
|
cd131e853c | ||
|
|
8a353d6e46 | ||
|
|
d5cf6e9445 | ||
|
|
b9dc4464a6 | ||
|
|
974d2d7566 | ||
|
|
536f822b60 | ||
|
|
d7e6e42414 |
24
.github/workflows/release.yml
vendored
24
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
12
README.md
12
README.md
@@ -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,10 @@ with:
|
||||
debugSymbols: app/intermediates/merged_native_libs/release/out/lib
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
16
lib/index.js
16
lib/index.js
File diff suppressed because one or more lines are too long
3267
package-lock.json
generated
3267
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "upload-google-play",
|
||||
"version": "1.0.19",
|
||||
"version": "1.1.2",
|
||||
"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": "^8.4.0",
|
||||
"fast-glob": "^3.3.1",
|
||||
"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.5",
|
||||
"@types/node": "^20.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||
"@typescript-eslint/parser": "^6.7.0",
|
||||
"@vercel/ncc": "^0.38.0",
|
||||
"eslint": "^8.49.0",
|
||||
"github-action-ts-run-api": "^3.0.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
34
src/edits.ts
34
src/edits.ts
@@ -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}`;
|
||||
}
|
||||
51
src/main.ts
51
src/main.ts
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user