@gpc-cli/cli 0.9.69 → 0.9.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin.js +2 -2
- package/dist/{chunk-5G2T4IJO.js → chunk-QLR4EWJG.js} +1 -1
- package/dist/chunk-QLR4EWJG.js.map +1 -0
- package/dist/{chunk-3M3SM2YH.js → chunk-RKVAMOO2.js} +8 -8
- package/dist/{config-ZOWACWRK.js → config-MPTRKWPJ.js} +2 -2
- package/dist/{doctor-EKXMIWK3.js → doctor-ECRWYCY6.js} +2 -2
- package/dist/{feedback-6GOFNCUK.js → feedback-A5B47QAO.js} +3 -3
- package/dist/generated/docs-bundle.json +1 -1
- package/dist/index.js +1 -1
- package/dist/{releases-P3ISQZ3F.js → releases-WJXNU25H.js} +32 -2
- package/dist/releases-WJXNU25H.js.map +1 -0
- package/dist/{update-4GEB7YA4.js → update-ZCZG4JJK.js} +3 -3
- package/dist/{version-2P3QCXBN.js → version-SDP2KA3J.js} +3 -3
- package/package.json +4 -4
- package/dist/chunk-5G2T4IJO.js.map +0 -1
- package/dist/releases-P3ISQZ3F.js.map +0 -1
- /package/dist/{chunk-3M3SM2YH.js.map → chunk-RKVAMOO2.js.map} +0 -0
- /package/dist/{config-ZOWACWRK.js.map → config-MPTRKWPJ.js.map} +0 -0
- /package/dist/{doctor-EKXMIWK3.js.map → doctor-ECRWYCY6.js.map} +0 -0
- /package/dist/{feedback-6GOFNCUK.js.map → feedback-A5B47QAO.js.map} +0 -0
- /package/dist/{update-4GEB7YA4.js.map → update-ZCZG4JJK.js.map} +0 -0
- /package/dist/{version-2P3QCXBN.js.map → version-SDP2KA3J.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
promoteRelease,
|
|
30
30
|
updateRollout,
|
|
31
31
|
readReleaseNotesFromDir,
|
|
32
|
+
isVersionedNotesDir,
|
|
32
33
|
generateNotesFromGit,
|
|
33
34
|
writeAuditLog,
|
|
34
35
|
createAuditEntry,
|
|
@@ -83,6 +84,25 @@ function registerReleasesCommands(program) {
|
|
|
83
84
|
).option(
|
|
84
85
|
"--validate-only",
|
|
85
86
|
"Upload and validate without committing (changes are discarded after validation)"
|
|
87
|
+
).option(
|
|
88
|
+
"--in-app-update-priority <priority>",
|
|
89
|
+
"In-app update priority (0-5, where 5 is highest)",
|
|
90
|
+
(v) => {
|
|
91
|
+
const n = parseInt(v, 10);
|
|
92
|
+
if (isNaN(n) || n < 0 || n > 5) {
|
|
93
|
+
throw new GpcError(
|
|
94
|
+
`Invalid in-app update priority: ${v}`,
|
|
95
|
+
"RELEASES_USAGE_ERROR",
|
|
96
|
+
2,
|
|
97
|
+
"Use a value from 0 (default, no priority) to 5 (highest priority)."
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return n;
|
|
101
|
+
}
|
|
102
|
+
).option(
|
|
103
|
+
"--retain-version-codes <codes>",
|
|
104
|
+
"Retain previous version codes alongside the new upload (comma-separated)",
|
|
105
|
+
(v) => v.split(",").map((s) => s.trim()).filter(Boolean)
|
|
86
106
|
).action(async (file, options) => {
|
|
87
107
|
try {
|
|
88
108
|
await stat(file);
|
|
@@ -196,6 +216,8 @@ function registerReleasesCommands(program) {
|
|
|
196
216
|
userFraction: options.rollout ? Number(options.rollout) / 100 : void 0,
|
|
197
217
|
mappingFileType: options.mappingType,
|
|
198
218
|
deviceTierConfigId: options.deviceTierConfig,
|
|
219
|
+
inAppUpdatePriority: options.inAppUpdatePriority,
|
|
220
|
+
retainVersionCodes: options.retainVersionCodes,
|
|
199
221
|
dryRun: true
|
|
200
222
|
});
|
|
201
223
|
console.log(formatOutput(result, format));
|
|
@@ -214,13 +236,18 @@ function registerReleasesCommands(program) {
|
|
|
214
236
|
if (!showProgress) spinner.start();
|
|
215
237
|
try {
|
|
216
238
|
let releaseNotes;
|
|
239
|
+
let notesDirVersioned;
|
|
217
240
|
if (options.copyNotesFrom) {
|
|
218
241
|
releaseNotes = await fetchReleaseNotes(client, packageName, options.copyNotesFrom);
|
|
219
242
|
} else if (options.notesFromGit) {
|
|
220
243
|
const gitNotes = await generateNotesFromGit({ since: options.since });
|
|
221
244
|
releaseNotes = [{ language: gitNotes.language, text: gitNotes.text }];
|
|
222
245
|
} else if (options.notesDir) {
|
|
223
|
-
|
|
246
|
+
if (await isVersionedNotesDir(options.notesDir)) {
|
|
247
|
+
notesDirVersioned = options.notesDir;
|
|
248
|
+
} else {
|
|
249
|
+
releaseNotes = await readReleaseNotesFromDir(options.notesDir);
|
|
250
|
+
}
|
|
224
251
|
} else if (options.notes) {
|
|
225
252
|
releaseNotes = [{ language: "en-US", text: options.notes }];
|
|
226
253
|
}
|
|
@@ -234,6 +261,9 @@ function registerReleasesCommands(program) {
|
|
|
234
261
|
mappingFileType: options.mappingType,
|
|
235
262
|
deviceTierConfigId: options.deviceTierConfig,
|
|
236
263
|
validateOnly: options.validateOnly,
|
|
264
|
+
inAppUpdatePriority: options.inAppUpdatePriority,
|
|
265
|
+
retainVersionCodes: options.retainVersionCodes,
|
|
266
|
+
notesDirVersioned,
|
|
237
267
|
onUploadProgress,
|
|
238
268
|
commitOptions: buildCommitOptions(options)
|
|
239
269
|
});
|
|
@@ -607,4 +637,4 @@ function registerReleasesCommands(program) {
|
|
|
607
637
|
export {
|
|
608
638
|
registerReleasesCommands
|
|
609
639
|
};
|
|
610
|
-
//# sourceMappingURL=releases-
|
|
640
|
+
//# sourceMappingURL=releases-WJXNU25H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/releases.ts"],"sourcesContent":["import { appendFile, stat } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient, createReportingClient } from \"@gpc-cli/api\";\nimport type { RetryLogEntry, ExternallyHostedApk, UploadProgressEvent } from \"@gpc-cli/api\";\nimport {\n uploadRelease,\n getReleasesStatus,\n promoteRelease,\n updateRollout,\n readReleaseNotesFromDir,\n isVersionedNotesDir,\n generateNotesFromGit,\n writeAuditLog,\n createAuditEntry,\n uploadExternallyHosted,\n diffReleases,\n fetchReleaseNotes,\n getVitalsCrashes,\n checkThreshold,\n GpcError,\n} from \"@gpc-cli/core\";\nimport { formatOutput, sortResults, createSpinner, maybePaginate } from \"@gpc-cli/core\";\nimport type { UploadResult } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport {\n isInteractive,\n promptSelect,\n promptInput,\n requireOption,\n requireConfirm,\n} from \"../prompt.js\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n throw new GpcError(\n \"No package name. Use --app <package> or gpc config set app <package>\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Set a default app: gpc config set app <package>\",\n );\n }\n return name;\n}\n\nimport { buildCommitOptions } from \"../commit-options.js\";\n\nfunction createRetryLogger(retryLogPath?: string): ((entry: RetryLogEntry) => void) | undefined {\n if (!retryLogPath) return undefined;\n return (entry: RetryLogEntry) => {\n const line = JSON.stringify(entry) + \"\\n\";\n appendFile(retryLogPath, line).catch(() => {});\n };\n}\n\nasync function getClient(config: GpcConfig, retryLogPath?: string, uploadTimeout?: number) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createApiClient({ auth, onRetry: createRetryLogger(retryLogPath), uploadTimeout });\n}\n\nexport function registerReleasesCommands(program: Command): void {\n const releases = program.command(\"releases\").description(\"Manage releases and rollouts\");\n\n // Upload\n releases\n .command(\"upload <file>\")\n .description(\"Upload AAB/APK and assign to a track\")\n .option(\"--track <track>\", \"Target track\", \"internal\")\n .option(\"--rollout <percent>\", \"Staged rollout percentage (1-100)\")\n .option(\"--notes <text>\", \"Release notes (en-US)\")\n .option(\"--name <name>\", \"Release name\")\n .option(\"--mapping <file>\", \"ProGuard/R8 mapping file for deobfuscation\")\n .option(\"--notes-dir <dir>\", \"Read release notes from directory (<dir>/<lang>.txt)\")\n .option(\"--notes-from-git\", \"Generate release notes from git commit history\")\n .option(\"--copy-notes-from <track>\", \"Copy release notes from another track\")\n .option(\"--since <ref>\", \"Git ref to start from (tag, SHA) — used with --notes-from-git\")\n .option(\"--retry-log <path>\", \"Write retry log entries to file (JSONL)\")\n .option(\n \"--timeout <ms>\",\n \"Upload timeout in milliseconds (auto-scales with file size by default)\",\n parseInt,\n )\n .option(\n \"--status <status>\",\n \"Release status: completed, inProgress, draft, halted\",\n \"completed\",\n )\n .option(\"--mapping-type <type>\", \"Deobfuscation file type: proguard or nativeCode\", \"proguard\")\n .option(\"--device-tier-config <id>\", \"Device tier config ID (or LATEST)\")\n .option(\n \"--changes-not-sent-for-review\",\n \"Commit changes without sending for review (required for rejected apps)\",\n )\n .option(\n \"--error-if-in-review\",\n \"Fail if changes are already in review instead of cancelling them\",\n )\n .option(\n \"--validate-only\",\n \"Upload and validate without committing (changes are discarded after validation)\",\n )\n .option(\n \"--in-app-update-priority <priority>\",\n \"In-app update priority (0-5, where 5 is highest)\",\n (v: string) => {\n const n = parseInt(v, 10);\n if (isNaN(n) || n < 0 || n > 5) {\n throw new GpcError(\n `Invalid in-app update priority: ${v}`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use a value from 0 (default, no priority) to 5 (highest priority).\",\n );\n }\n return n;\n },\n )\n .option(\n \"--retain-version-codes <codes>\",\n \"Retain previous version codes alongside the new upload (comma-separated)\",\n (v: string) => v.split(\",\").map((s) => s.trim()).filter(Boolean),\n )\n .action(async (file: string, options) => {\n try {\n await stat(file);\n } catch {\n throw new GpcError(\n `File not found: ${file}`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Check the file path and try again.\",\n );\n }\n\n const ext = extname(file).toLowerCase();\n if (ext !== \".aab\" && ext !== \".apk\") {\n throw new GpcError(\n `Expected .aab or .apk file, got \"${ext || \"(no extension)\"}\"`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Provide a .aab or .apk file.\",\n );\n }\n\n const noteSources = [\n options.notes,\n options.notesDir,\n options.notesFromGit,\n options.copyNotesFrom,\n ].filter(Boolean);\n if (noteSources.length > 1) {\n throw new GpcError(\n \"Cannot combine --notes, --notes-dir, --notes-from-git, and --copy-notes-from. Use only one.\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Pick one release notes source.\",\n );\n }\n if (options.validateOnly && isDryRun(program)) {\n throw new GpcError(\n \"Cannot combine --dry-run and --validate-only.\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use --dry-run to preview without any API changes, or --validate-only to upload and validate without committing.\",\n );\n }\n\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n\n // Interactive mode: prompt for missing options\n if (isInteractive(program)) {\n if (!options.track || options.track === \"internal\") {\n const tracks = [\"internal\", \"alpha\", \"beta\", \"production\"];\n options.track = await promptSelect(\"Select track:\", tracks, \"internal\");\n }\n\n if (!options.rollout && options.track === \"production\") {\n const rolloutStr = await promptInput(\n \"Staged rollout percentage (1-100, blank for full)\",\n \"100\",\n );\n if (rolloutStr && rolloutStr !== \"100\") {\n options.rollout = rolloutStr;\n }\n }\n\n if (!options.notes && !options.notesDir) {\n const notes = await promptInput(\"Release notes (en-US, blank to skip)\");\n if (notes) options.notes = notes;\n }\n }\n\n if (options.rollout !== undefined) {\n const rollout = Number(options.rollout);\n if (!Number.isFinite(rollout) || rollout < 1 || rollout > 100) {\n throw new GpcError(\n `--rollout must be a number between 1 and 100 (got: ${options.rollout})`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use a percentage between 1 and 100.\",\n );\n }\n }\n\n if (\n options.mappingType &&\n options.mappingType !== \"proguard\" &&\n options.mappingType !== \"nativeCode\"\n ) {\n throw new GpcError(\n `--mapping-type must be \"proguard\" or \"nativeCode\" (got: \"${options.mappingType}\")`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use --mapping-type proguard (default) for ProGuard/R8 maps, or --mapping-type nativeCode for native debug symbols.\",\n );\n }\n\n const { size: fileSize } = await stat(file);\n const jsonMode = format === \"json\";\n const client = await getClient(config, options.retryLog, options.timeout);\n\n const showProgress = !jsonMode && process.stderr.isTTY && !program.opts()[\"quiet\"];\n const sizeMB = (fileSize / (1024 * 1024)).toFixed(1);\n\n function formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${bytes} B`;\n }\n\n const BAR_WIDTH = 20;\n const onUploadProgress = showProgress\n ? (event: UploadProgressEvent) => {\n const filled = Math.round((event.percent / 100) * BAR_WIDTH);\n const bar = \"█\".repeat(filled) + \"░\".repeat(BAR_WIDTH - filled);\n const uploaded = formatBytes(event.bytesUploaded);\n const total = formatBytes(event.totalBytes);\n const speed =\n event.bytesPerSecond > 0 ? `${formatBytes(event.bytesPerSecond)}/s` : \"...\";\n const eta = event.etaSeconds > 0 ? `ETA ${event.etaSeconds}s` : \"\";\n process.stderr.write(\n `\\r ${bar} ${event.percent}% ${uploaded}/${total} ${speed} ${eta}\\x1b[K`,\n );\n }\n : undefined;\n\n if (isDryRun(program)) {\n const result = await uploadRelease(client, packageName, file, {\n track: options.track,\n status: options.status,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n mappingFileType: options.mappingType,\n deviceTierConfigId: options.deviceTierConfig,\n inAppUpdatePriority: options.inAppUpdatePriority,\n retainVersionCodes: options.retainVersionCodes,\n dryRun: true,\n });\n console.log(formatOutput(result, format));\n return;\n }\n\n const auditEntry = createAuditEntry(\n \"releases upload\",\n {\n file,\n track: options.track,\n rollout: options.rollout,\n },\n packageName,\n );\n\n const spinner = createSpinner(`Uploading ${basename(file)} (${sizeMB} MB)...`);\n if (!showProgress) spinner.start();\n\n try {\n let releaseNotes: { language: string; text: string }[] | undefined;\n let notesDirVersioned: string | undefined;\n if (options.copyNotesFrom) {\n releaseNotes = await fetchReleaseNotes(client, packageName, options.copyNotesFrom);\n } else if (options.notesFromGit) {\n const gitNotes = await generateNotesFromGit({ since: options.since });\n releaseNotes = [{ language: gitNotes.language, text: gitNotes.text }];\n } else if (options.notesDir) {\n if (await isVersionedNotesDir(options.notesDir)) {\n notesDirVersioned = options.notesDir;\n } else {\n releaseNotes = await readReleaseNotesFromDir(options.notesDir);\n }\n } else if (options.notes) {\n releaseNotes = [{ language: \"en-US\", text: options.notes }];\n }\n\n const result = await uploadRelease(client, packageName, file, {\n track: options.track,\n status: options.status,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n releaseNotes,\n releaseName: options.name,\n mappingFile: options.mapping,\n mappingFileType: options.mappingType,\n deviceTierConfigId: options.deviceTierConfig,\n validateOnly: options.validateOnly,\n inAppUpdatePriority: options.inAppUpdatePriority,\n retainVersionCodes: options.retainVersionCodes,\n notesDirVersioned,\n onUploadProgress,\n commitOptions: buildCommitOptions(options),\n });\n if (showProgress) {\n process.stderr.write(`\\r ✓ Uploaded ${basename(file)} ${sizeMB} MB\\x1b[K\\n`);\n }\n spinner.stop(\n (result as UploadResult).validateOnly\n ? \"Validation passed (not committed)\"\n : \"Upload complete\",\n );\n console.log(formatOutput(result, format));\n auditEntry.success = true;\n } catch (error) {\n if (showProgress) {\n process.stderr.write(\"\\n\");\n }\n spinner.fail(\"Upload failed\");\n auditEntry.success = false;\n auditEntry.error = error instanceof Error ? error.message : String(error);\n throw error;\n } finally {\n auditEntry.durationMs = Date.now() - new Date(auditEntry.timestamp).getTime();\n writeAuditLog(auditEntry).catch(() => {});\n }\n });\n\n // Status\n releases\n .command(\"status\")\n .description(\"Show current release status across tracks\")\n .option(\"--track <track>\", \"Filter by track\")\n .option(\"--sort <field>\", \"Sort by field (prefix with - for descending)\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const TRACK_ORDER = [\"production\", \"beta\", \"alpha\", \"internal\"];\n const rawStatuses = await getReleasesStatus(client, packageName, options.track);\n const statuses = options.track\n ? Array.isArray(rawStatuses)\n ? rawStatuses.filter((s: any) => s.track === options.track)\n : rawStatuses\n : rawStatuses;\n const sorted = Array.isArray(statuses)\n ? options.sort\n ? sortResults(statuses, options.sort)\n : [...statuses].sort((a, b) => {\n const ai = TRACK_ORDER.indexOf(\n String((a as unknown as Record<string, unknown>)[\"track\"] ?? \"\"),\n );\n const bi = TRACK_ORDER.indexOf(\n String((b as unknown as Record<string, unknown>)[\"track\"] ?? \"\"),\n );\n return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);\n })\n : statuses;\n if (format !== \"json\" && Array.isArray(sorted)) {\n const rows = sorted.map((s: unknown) => {\n const sr = s as Record<string, unknown>;\n return {\n track: sr[\"track\"] || \"-\",\n status: sr[\"status\"] || \"-\",\n name: sr[\"name\"] || \"-\",\n versionCodes: Array.isArray(sr[\"versionCodes\"])\n ? (sr[\"versionCodes\"] as unknown[]).join(\", \")\n : \"-\",\n userFraction:\n sr[\"userFraction\"] !== undefined\n ? `${Math.round(Number(sr[\"userFraction\"]) * 100)}%`\n : \"—\",\n };\n });\n await maybePaginate(formatOutput(rows, format));\n } else {\n await maybePaginate(formatOutput(sorted, format));\n }\n });\n\n // Promote\n releases\n .command(\"promote\")\n .description(\"Promote a release from one track to another\")\n .option(\"--from <track>\", \"Source track\")\n .option(\"--to <track>\", \"Target track\")\n .option(\"--rollout <percent>\", \"Staged rollout percentage\")\n .option(\"--notes <text>\", \"Release notes\")\n .option(\"--copy-notes-from <track>\", \"Copy release notes from another track\")\n .option(\"--status <status>\", \"Release status: completed, inProgress, draft, halted\")\n .option(\n \"--changes-not-sent-for-review\",\n \"Commit changes without sending for review (required for rejected apps)\",\n )\n .option(\n \"--error-if-in-review\",\n \"Fail if changes are already in review instead of cancelling them\",\n )\n .action(async (options) => {\n if (options.notes && options.copyNotesFrom) {\n throw new GpcError(\n \"Cannot combine --notes and --copy-notes-from. Use only one.\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Pick one release notes source.\",\n );\n }\n\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n const interactive = isInteractive(program);\n const tracks = [\"internal\", \"alpha\", \"beta\", \"production\"];\n\n options.from = await requireOption(\n \"from\",\n options.from,\n {\n message: \"Source track:\",\n choices: tracks,\n },\n interactive,\n );\n\n options.to = await requireOption(\n \"to\",\n options.to,\n {\n message: \"Target track:\",\n choices: tracks.filter((t: string) => t !== options.from),\n },\n interactive,\n );\n\n if (options.from === options.to) {\n throw new GpcError(\n `--from and --to must be different tracks (both are \"${options.from}\")`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Specify different source and target tracks.\",\n );\n }\n\n if (options.rollout !== undefined) {\n const rollout = Number(options.rollout);\n if (!Number.isFinite(rollout) || rollout < 1 || rollout > 100) {\n throw new GpcError(\n `--rollout must be a number between 1 and 100 (got: ${options.rollout})`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use a percentage between 1 and 100.\",\n );\n }\n }\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"releases promote\",\n action: \"promote\",\n target: `${options.from} → ${options.to}`,\n details: {\n rollout: options.rollout,\n ...(options.status && { status: options.status }),\n },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n let releaseNotes: { language: string; text: string }[] | undefined;\n if (options.copyNotesFrom) {\n releaseNotes = await fetchReleaseNotes(client, packageName, options.copyNotesFrom);\n } else if (options.notes) {\n releaseNotes = [{ language: \"en-US\", text: options.notes }];\n }\n\n const result = await promoteRelease(client, packageName, options.from, options.to, {\n status: options.status,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n releaseNotes,\n commitOptions: buildCommitOptions(options),\n });\n console.log(formatOutput(result, format));\n });\n\n // Rollout subcommands\n const rollout = releases.command(\"rollout\").description(\"Manage staged rollouts\");\n\n for (const action of [\"increase\", \"halt\", \"resume\", \"complete\"] as const) {\n const cmd = rollout\n .command(action)\n .description(`${action.charAt(0).toUpperCase() + action.slice(1)} a staged rollout`)\n .option(\"--track <track>\", \"Track name\");\n\n if (action === \"increase\") {\n cmd.option(\"--to <percent>\", \"New rollout percentage\");\n cmd.option(\"--vitals-gate\", \"Halt rollout if crash rate exceeds configured threshold\");\n }\n\n cmd.option(\n \"--changes-not-sent-for-review\",\n \"Commit changes without sending for review (required for rejected apps)\",\n );\n cmd.option(\n \"--error-if-in-review\",\n \"Fail if changes are already in review instead of cancelling them\",\n );\n\n cmd.action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n const interactive = isInteractive(program);\n const tracks = [\"internal\", \"alpha\", \"beta\", \"production\"];\n\n options.track = await requireOption(\n \"track\",\n options.track,\n {\n message: \"Track:\",\n choices: tracks,\n },\n interactive,\n );\n\n if (action === \"increase\") {\n options.to = await requireOption(\n \"to\",\n options.to,\n {\n message: \"New rollout percentage (1-100):\",\n },\n interactive,\n );\n }\n\n if (action === \"increase\" && options.to !== undefined) {\n const to = Number(options.to);\n if (!Number.isFinite(to) || to < 1 || to > 100) {\n throw new GpcError(\n `--to must be a number between 1 and 100 (got: ${options.to})`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use a percentage between 1 and 100.\",\n );\n }\n }\n\n // Require confirmation for destructive rollout halt\n if (action === \"halt\") {\n await requireConfirm(\n `Halt rollout on track \"${options.track}\" for ${packageName}?`,\n program,\n );\n }\n\n if (isDryRun(program)) {\n if (action === \"increase\" && options.vitalsGate) {\n console.error(\n \"Warning: --vitals-gate is ignored in --dry-run mode. Gate will run on live execution.\",\n );\n }\n printDryRun(\n {\n command: `releases rollout ${action}`,\n action: action,\n target: options.track,\n details: { percentage: options.to !== undefined ? `${options.to}%` : undefined },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n const result = await updateRollout(\n client,\n packageName,\n options.track,\n action,\n options.to ? Number(options.to) / 100 : undefined,\n buildCommitOptions(options),\n );\n\n // Vitals gate: check crash rate after rollout increase\n if (action === \"increase\" && options.vitalsGate) {\n const threshold = (config as any).vitals?.thresholds?.crashRate;\n if (!threshold) {\n console.error(\n \"Warning: --vitals-gate requires vitals.thresholds.crashRate in config. Skipping gate.\",\n );\n } else {\n try {\n const { auth: authConfig } = config;\n const vitalsAuth = await resolveAuth({\n serviceAccountPath: authConfig?.serviceAccount,\n });\n const reportingClient = createReportingClient({ auth: vitalsAuth });\n const vitalsResult = await getVitalsCrashes(reportingClient, packageName, {\n days: 1,\n });\n const latest = (vitalsResult as any).data?.[0]?.crashRate;\n const check = checkThreshold(latest, threshold);\n if (check.breached) {\n await updateRollout(client, packageName, options.track, \"halt\");\n console.error(\n `Vitals gate: crash rate ${String(latest)}% > threshold ${String(threshold)}%. Rollout halted.`,\n );\n process.exitCode = 6;\n }\n } catch (vitalsErr) {\n console.error(\n `Warning: Vitals gate check failed: ${vitalsErr instanceof Error ? vitalsErr.message : String(vitalsErr)}`,\n );\n }\n }\n }\n\n console.log(formatOutput(result, format));\n });\n }\n\n // Release notes\n releases\n .command(\"notes\")\n .description(\"Get or set release notes\")\n .argument(\"<action>\", \"Action: get|set\")\n .option(\"--track <track>\", \"Track name\")\n .option(\"--lang <language>\", \"Language code\", \"en-US\")\n .option(\"--notes <text>\", \"Release notes text\")\n .option(\"--file <path>\", \"Read notes from file\")\n .action(async (action: string, options) => {\n if (action === \"set\") {\n throw new GpcError(\n \"gpc releases notes set is not implemented as a standalone command.\",\n \"RELEASES_USAGE_ERROR\",\n 1,\n \"Use --notes, --notes-dir, or --notes-from-git with: gpc releases upload or gpc publish\",\n );\n }\n\n if (action === \"get\") {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n const track = options.track ?? \"internal\";\n\n // Try getReleasesStatus first (has all releases), then fallback to fetchReleaseNotes\n const statuses = await getReleasesStatus(client, packageName, track);\n let notes = Array.isArray(statuses)\n ? statuses.flatMap((s: any) => s.releaseNotes ?? [])\n : ((statuses as any).releaseNotes ?? []);\n\n // Fallback: fetchReleaseNotes reads the raw track data which may have notes\n // even when getReleasesStatus doesn't (e.g. completed releases)\n if (notes.length === 0) {\n try {\n notes = await fetchReleaseNotes(client, packageName, track);\n } catch {\n // No release found on track — fall through to empty message\n }\n }\n\n if (notes.length === 0) {\n console.log(`No release notes found on track \"${track}\".`);\n return;\n }\n console.log(formatOutput(notes, format));\n return;\n }\n\n throw new GpcError(\n \"Unknown action. Usage: gpc releases notes <get|set> --track <track>\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use: gpc releases notes get --track <track>\",\n );\n });\n\n // Upload externally hosted APK\n releases\n .command(\"upload-external\")\n .description(\"Upload an externally hosted APK configuration\")\n .requiredOption(\"--url <url>\", \"External URL where the APK is hosted\")\n .requiredOption(\"--file <config>\", \"Path to JSON config file with APK metadata\")\n .action(async (options: { url: string; file: string }) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n\n const { readFile } = await import(\"node:fs/promises\");\n const raw = await readFile(options.file, \"utf-8\");\n const apkConfig = JSON.parse(raw) as Record<string, unknown>;\n\n // Override with CLI-provided URL\n apkConfig[\"externallyHostedUrl\"] = options.url;\n\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n const client = createApiClient({ auth });\n const result = await uploadExternallyHosted(\n client,\n packageName,\n apkConfig as unknown as ExternallyHostedApk,\n );\n console.log(formatOutput(result, format));\n });\n\n // Diff\n releases\n .command(\"diff\")\n .description(\"Compare releases between two tracks\")\n .option(\"--from <track>\", \"Source track\", \"internal\")\n .option(\"--to <track>\", \"Target track\", \"production\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await diffReleases(client, packageName, options.from, options.to);\n if (result.diffs.length === 0) {\n console.log(`No differences between ${result.fromTrack} and ${result.toTrack}.`);\n } else {\n if (format === \"json\") {\n console.log(formatOutput(result, format));\n } else {\n console.log(`Differences: ${result.fromTrack} vs ${result.toTrack}\\n`);\n console.log(formatOutput(result.diffs, format));\n }\n }\n });\n\n // Count\n releases\n .command(\"count\")\n .description(\"Count releases per track\")\n .option(\"--track <track>\", \"Filter to a specific track\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const statuses = await getReleasesStatus(client, packageName, options.track);\n\n // Group by track\n const trackCounts = new Map<string, { total: number; statuses: Record<string, number> }>();\n for (const r of statuses) {\n const entry = trackCounts.get(r.track) ?? { total: 0, statuses: {} };\n entry.total++;\n entry.statuses[r.status] = (entry.statuses[r.status] ?? 0) + 1;\n trackCounts.set(r.track, entry);\n }\n\n if (format === \"json\") {\n const data = Object.fromEntries(\n [...trackCounts.entries()].map(([track, info]) => [track, info]),\n );\n console.log(formatOutput(data, format));\n } else {\n const rows = [...trackCounts.entries()].map(([track, info]) => ({\n track,\n releases: info.total,\n ...info.statuses,\n }));\n if (rows.length === 0) {\n console.log(\"No releases found.\");\n } else {\n console.log(formatOutput(rows, format));\n }\n }\n });\n}\n"],"mappings":"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","names":["rollout"]}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
updateBinaryInPlace,
|
|
9
9
|
updateViaBrew,
|
|
10
10
|
updateViaNpm
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-QLR4EWJG.js";
|
|
12
12
|
import "./chunk-3SJ6OXCZ.js";
|
|
13
13
|
|
|
14
14
|
// src/commands/update.ts
|
|
@@ -17,7 +17,7 @@ function registerUpdateCommand(program) {
|
|
|
17
17
|
program.command("update").description("Update gpc to the latest version").option("--check", "Check for updates without installing (exits 0 always)").option("--force", "Update even if already on the latest version").action(async (opts, cmd) => {
|
|
18
18
|
const parentOpts = cmd.parent?.opts() ?? {};
|
|
19
19
|
const jsonMode = !!(parentOpts["json"] || parentOpts["output"] === "json");
|
|
20
|
-
const currentVersion = "0.9.
|
|
20
|
+
const currentVersion = "0.9.70";
|
|
21
21
|
if (currentVersion === "0.0.0") {
|
|
22
22
|
if (jsonMode) {
|
|
23
23
|
console.log(
|
|
@@ -172,4 +172,4 @@ Run: gpc update`);
|
|
|
172
172
|
export {
|
|
173
173
|
registerUpdateCommand
|
|
174
174
|
};
|
|
175
|
-
//# sourceMappingURL=update-
|
|
175
|
+
//# sourceMappingURL=update-ZCZG4JJK.js.map
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
detectInstallMethod
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QLR4EWJG.js";
|
|
5
5
|
import "./chunk-3SJ6OXCZ.js";
|
|
6
6
|
|
|
7
7
|
// src/commands/version.ts
|
|
8
8
|
function registerVersionCommand(program) {
|
|
9
9
|
program.command("version").description("Show version information").action(() => {
|
|
10
|
-
const version = "0.9.
|
|
10
|
+
const version = "0.9.70";
|
|
11
11
|
if (program.opts()["output"] === "json") {
|
|
12
12
|
console.log(
|
|
13
13
|
JSON.stringify({
|
|
@@ -25,4 +25,4 @@ function registerVersionCommand(program) {
|
|
|
25
25
|
export {
|
|
26
26
|
registerVersionCommand
|
|
27
27
|
};
|
|
28
|
-
//# sourceMappingURL=version-
|
|
28
|
+
//# sourceMappingURL=version-SDP2KA3J.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gpc-cli/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.70",
|
|
4
4
|
"description": "GPC — Google Play Console CLI. 217 API endpoints including Managed Google Play. Upload AABs, manage releases, monitor vitals, publish private enterprise apps. Fastlane alternative for Android.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"commander": "^14.0.3",
|
|
22
|
-
"@gpc-cli/api": "1.0.
|
|
22
|
+
"@gpc-cli/api": "1.0.37",
|
|
23
|
+
"@gpc-cli/core": "0.9.60",
|
|
23
24
|
"@gpc-cli/auth": "0.9.14",
|
|
24
25
|
"@gpc-cli/plugin-sdk": "0.9.10",
|
|
25
|
-
"@gpc-cli/config": "0.9.15"
|
|
26
|
-
"@gpc-cli/core": "0.9.59"
|
|
26
|
+
"@gpc-cli/config": "0.9.15"
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|
|
29
29
|
"google-play",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/updater.ts"],"sourcesContent":["/**\n * Self-update logic for `gpc update`.\n *\n * All functions here are pure / testable — no Commander.js dependency.\n * The command layer in commands/update.ts handles output and flags.\n */\n\nimport { createWriteStream } from \"node:fs\";\nimport { rename, chmod, unlink, stat, readdir } from \"node:fs/promises\";\nimport { realpathSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\nimport { Readable, Transform } from \"node:stream\";\nimport { spawn } from \"node:child_process\";\nimport { sha256File } from \"@gpc-cli/core\";\nimport { isNewerVersion } from \"./update-check.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type InstallMethod = \"npm\" | \"homebrew\" | \"binary\" | \"unknown\";\n\nexport interface GithubAsset {\n name: string;\n browser_download_url: string;\n size: number;\n}\n\nexport interface GithubRelease {\n tag_name: string;\n html_url: string;\n assets: GithubAsset[];\n}\n\nexport interface UpdateCheckResult {\n current: string;\n latest: string;\n latestTag: string;\n updateAvailable: boolean;\n installMethod: InstallMethod;\n release: GithubRelease;\n}\n\nconst GITHUB_API_URL = \"https://api.github.com/repos/yasserstudio/gpc/releases/latest\";\nconst GITHUB_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\n// ---------------------------------------------------------------------------\n// Install method detection\n// ---------------------------------------------------------------------------\n\n/**\n * Detect how gpc was installed.\n *\n * Priority:\n * 1. __GPC_BINARY env var (injected by esbuild at compile time) → \"binary\"\n * 2. npm_config_prefix env var → \"npm\"\n * 3. realpathSync(process.argv[1]) contains \"cellar\" or \"homebrew\" → \"homebrew\"\n * 4. realpathSync(process.argv[1]) contains \"node_modules\" → \"npm\"\n * 5. fallback → \"unknown\"\n *\n * Using realpathSync(process.argv[1]) instead of shelling to `which gpc`:\n * - No child process spawn\n * - Works on Windows without `where.exe`\n * - Resolves symlinks — critical for Intel Mac Homebrew where the bin path\n * is a symlink but the real path contains \"Cellar\"\n */\nexport function detectInstallMethod(): InstallMethod {\n // 1. Compiled binary — but Homebrew also distributes as compiled binary, check execPath\n if (process.env[\"__GPC_BINARY\"] === \"1\") {\n try {\n const resolved = realpathSync(process.execPath).toLowerCase();\n if (resolved.includes(\"cellar\") || resolved.includes(\"homebrew\")) return \"homebrew\";\n } catch {\n /* ignore */\n }\n return \"binary\";\n }\n\n // 2. npm global install\n if (process.env[\"npm_config_prefix\"]) return \"npm\";\n\n // 3. Resolve symlinks and inspect path\n try {\n const resolved = realpathSync(process.argv[1] ?? \"\").toLowerCase();\n if (resolved.includes(\"cellar\") || resolved.includes(\"homebrew\")) return \"homebrew\";\n if (resolved.includes(\"node_modules\")) return \"npm\";\n } catch {\n // realpathSync can throw if the path doesn't exist — fall through to unknown\n }\n\n return \"unknown\";\n}\n\n// ---------------------------------------------------------------------------\n// Platform asset mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the GitHub release asset name for the current platform/arch,\n * matching the names produced by scripts/build-binary.ts TARGETS map.\n */\nexport function getPlatformAsset(): string | null {\n const arch = process.arch === \"arm64\" ? \"arm64\" : \"x64\";\n switch (process.platform) {\n case \"darwin\":\n return `gpc-darwin-${arch}`;\n case \"linux\":\n return `gpc-linux-${arch}`;\n case \"win32\":\n return \"gpc-windows-x64.exe\";\n default:\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Current binary path\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the path of the currently running gpc binary.\n * For compiled binaries, process.execPath IS the binary.\n * For npm/dev installs, process.argv[1] is the script entrypoint.\n */\nexport function getCurrentBinaryPath(): string {\n if (process.env[\"__GPC_BINARY\"] === \"1\") return process.execPath;\n return process.argv[1] ?? process.execPath;\n}\n\n// ---------------------------------------------------------------------------\n// GitHub Releases API\n// ---------------------------------------------------------------------------\n\nfunction getGithubToken(): string | undefined {\n return process.env[\"GPC_GITHUB_TOKEN\"] || process.env[\"GITHUB_TOKEN\"] || undefined;\n}\n\nfunction githubApiHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"User-Agent\": \"gpc-cli\",\n Accept: \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n };\n const token = getGithubToken();\n if (token) {\n headers[\"Authorization\"] = `Bearer ${token}`;\n }\n return headers;\n}\n\nfunction githubDownloadHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"User-Agent\": \"gpc-cli\",\n };\n const token = getGithubToken();\n if (token) {\n headers[\"Authorization\"] = `Bearer ${token}`;\n }\n return headers;\n}\n\nexport async function fetchLatestRelease(): Promise<GithubRelease> {\n let response: Response;\n try {\n response = await fetch(GITHUB_API_URL, {\n headers: githubApiHeaders(),\n signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw Object.assign(new Error(`Network error checking for updates: ${msg}`), {\n code: \"NETWORK_ERROR\",\n exitCode: 5,\n });\n }\n\n if (\n response.status === 429 ||\n (response.status === 403 && response.headers.get(\"x-ratelimit-remaining\") === \"0\")\n ) {\n throw Object.assign(\n new Error(\n \"GitHub API rate limit exceeded. Set GPC_GITHUB_TOKEN or GITHUB_TOKEN to increase the limit.\",\n ),\n {\n code: \"UPDATE_RATE_LIMITED\",\n exitCode: 4,\n suggestion: \"export GPC_GITHUB_TOKEN=ghp_... (a personal access token with no scopes)\",\n },\n );\n }\n\n if (!response.ok) {\n throw Object.assign(new Error(`GitHub API returned HTTP ${response.status}`), {\n code: \"UPDATE_API_ERROR\",\n exitCode: 4,\n });\n }\n\n return (await response.json()) as GithubRelease;\n}\n\n/**\n * Fetch and parse checksums.txt from the release assets.\n * Returns a Map of filename → lowercase sha256 hex.\n * Returns an empty Map if the asset is missing or the fetch fails.\n */\nexport async function fetchChecksums(release: GithubRelease): Promise<Map<string, string>> {\n const asset = release.assets.find((a) => a.name === \"checksums.txt\");\n if (!asset) return new Map();\n\n try {\n const response = await fetch(asset.browser_download_url, {\n headers: githubDownloadHeaders(),\n signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),\n });\n if (!response.ok) return new Map();\n\n const map = new Map<string, string>();\n for (const line of (await response.text()).split(\"\\n\")) {\n const parts = line.trim().split(/\\s+/);\n const hash = parts[0];\n const name = parts[1];\n if (hash && name) map.set(name, hash.toLowerCase());\n }\n return map;\n } catch {\n return new Map();\n }\n}\n\n// ---------------------------------------------------------------------------\n// High-level update check\n// ---------------------------------------------------------------------------\n\nexport async function checkForUpdate(currentVersion: string): Promise<UpdateCheckResult> {\n const release = await fetchLatestRelease();\n const latest = release.tag_name.replace(/^v/, \"\");\n return {\n current: currentVersion,\n latest,\n latestTag: release.tag_name,\n updateAvailable: isNewerVersion(currentVersion, latest),\n installMethod: detectInstallMethod(),\n release,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Update execution paths\n// ---------------------------------------------------------------------------\n\nexport async function updateViaNpm(options: { silent?: boolean } = {}): Promise<void> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"npm\", [\"install\", \"-g\", \"@gpc-cli/cli@latest\"], {\n // In silent (JSON) mode, redirect npm's stdout to stderr so it doesn't\n // pollute the machine-readable JSON that gpc writes to stdout.\n stdio: options.silent ? [\"inherit\", process.stderr, process.stderr] : \"inherit\",\n shell: false,\n });\n proc.on(\"close\", (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(\n Object.assign(new Error(`npm exited with code ${code}`), {\n code: \"UPDATE_NPM_FAILED\",\n exitCode: 1,\n suggestion: \"Run manually: npm install -g @gpc-cli/cli@latest\",\n }),\n );\n }\n });\n proc.on(\"error\", (err) => {\n reject(\n Object.assign(new Error(`Failed to run npm: ${err.message}`), {\n code: \"UPDATE_NPM_SPAWN_FAILED\",\n exitCode: 1,\n suggestion: \"Ensure npm is in your PATH\",\n }),\n );\n });\n });\n}\n\nexport async function updateViaBrew(options: { silent?: boolean } = {}): Promise<void> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"brew\", [\"upgrade\", \"yasserstudio/tap/gpc\"], {\n // In silent (JSON) mode, redirect brew's stdout to stderr so it doesn't\n // pollute the machine-readable JSON that gpc writes to stdout.\n stdio: options.silent ? [\"inherit\", process.stderr, process.stderr] : \"inherit\",\n shell: false,\n });\n proc.on(\"close\", (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(\n Object.assign(new Error(`brew exited with code ${code}`), {\n code: \"UPDATE_BREW_FAILED\",\n exitCode: 1,\n suggestion: \"Run manually: brew upgrade yasserstudio/tap/gpc\",\n }),\n );\n }\n });\n proc.on(\"error\", (err) => {\n reject(\n Object.assign(new Error(`Failed to run brew: ${err.message}`), {\n code: \"UPDATE_BREW_SPAWN_FAILED\",\n exitCode: 1,\n suggestion: \"Ensure Homebrew is installed: https://brew.sh\",\n }),\n );\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// Binary in-place replace\n// ---------------------------------------------------------------------------\n\nfunction isPermissionError(err: unknown): boolean {\n return err instanceof Error && \"code\" in err && (err.code === \"EACCES\" || err.code === \"EPERM\");\n}\n\n// sha256File imported from @gpc-cli/core\n\n/**\n * Clean up stale `.gpc-old-*` and `.gpc-update-*.tmp` files left by\n * previous update attempts (e.g. Windows EBUSY on unlink).\n * Fire-and-forget — errors are silently ignored.\n */\nexport function cleanupStaleUpdateFiles(binaryPath: string): void {\n const dir = dirname(binaryPath);\n readdir(dir)\n .then((files) => {\n for (const f of files) {\n if (f.startsWith(\".gpc-old-\") || f.startsWith(\".gpc-update-\")) {\n unlink(join(dir, f)).catch(() => {});\n }\n }\n })\n .catch(() => {});\n}\n\n/**\n * Download a new binary and atomically replace the current one.\n *\n * macOS/Linux: rename(tmp, current) — safe because open files can be replaced\n * Windows: rename(current, .old) then rename(tmp, current) — avoids EBUSY\n * because Windows locks running executables\n */\nexport async function updateBinaryInPlace(\n assetUrl: string,\n expectedSha256: string,\n currentBinaryPath: string,\n options: { onProgress?: (downloaded: number, total: number) => void } = {},\n): Promise<void> {\n const dir = dirname(currentBinaryPath);\n const tmpPath = join(dir, `.gpc-update-${process.pid}.tmp`);\n const oldPath = join(dir, `.gpc-old-${process.pid}`);\n\n try {\n // 1. Download\n let response: Response;\n try {\n response = await fetch(assetUrl, {\n headers: githubDownloadHeaders(),\n signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw Object.assign(new Error(`Download failed: ${msg}`), {\n code: \"UPDATE_DOWNLOAD_FAILED\",\n exitCode: 5,\n });\n }\n\n if (!response.ok) {\n throw Object.assign(new Error(`Download failed: HTTP ${response.status}`), {\n code: \"UPDATE_DOWNLOAD_FAILED\",\n exitCode: 4,\n });\n }\n if (!response.body) {\n throw Object.assign(new Error(\"Empty response body\"), {\n code: \"UPDATE_DOWNLOAD_FAILED\",\n exitCode: 4,\n });\n }\n\n const contentLength = response.headers.get(\"content-length\");\n const total = contentLength ? parseInt(contentLength, 10) : 0;\n let downloaded = 0;\n\n const dest = createWriteStream(tmpPath);\n const { onProgress } = options;\n\n if (onProgress) {\n const tracker = new Transform({\n transform(chunk: Buffer, _enc, cb) {\n downloaded += chunk.length;\n onProgress(downloaded, total);\n cb(null, chunk);\n },\n });\n await pipeline(\n Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]),\n tracker,\n dest,\n );\n } else {\n await pipeline(\n Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]),\n dest,\n );\n }\n\n // 2. Verify checksum (skip if no checksum available)\n if (expectedSha256) {\n const actual = await sha256File(tmpPath);\n if (actual !== expectedSha256.toLowerCase()) {\n throw Object.assign(\n new Error(`Checksum mismatch — expected ${expectedSha256}, got ${actual}`),\n {\n code: \"UPDATE_CHECKSUM_MISMATCH\",\n exitCode: 1,\n suggestion: \"The download may be corrupt. Try again.\",\n },\n );\n }\n }\n\n // 3. Set executable bit (no-op on Windows)\n if (process.platform !== \"win32\") {\n await chmod(tmpPath, 0o755);\n }\n\n // 4. Atomic replace\n if (process.platform === \"win32\") {\n // Windows locks running executables, so we rename the current binary\n // out of the way first, then move the new one into place\n await rename(currentBinaryPath, oldPath);\n try {\n await rename(tmpPath, currentBinaryPath);\n } catch (renameErr) {\n // Roll back — restore original binary\n await rename(oldPath, currentBinaryPath).catch(() => {});\n throw renameErr;\n }\n // Delete the old binary in the background (best-effort)\n unlink(oldPath).catch(() => {});\n } else {\n await rename(tmpPath, currentBinaryPath);\n }\n } catch (err) {\n // Clean up temp file on any error\n await unlink(tmpPath).catch(() => {});\n\n if (isPermissionError(err)) {\n throw Object.assign(new Error(`Permission denied replacing ${currentBinaryPath}`), {\n code: \"UPDATE_PERMISSION_DENIED\",\n exitCode: 1,\n suggestion: `Run with elevated permissions: sudo gpc update`,\n });\n }\n throw err;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/releases.ts"],"sourcesContent":["import { appendFile, stat } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient, createReportingClient } from \"@gpc-cli/api\";\nimport type { RetryLogEntry, ExternallyHostedApk, UploadProgressEvent } from \"@gpc-cli/api\";\nimport {\n uploadRelease,\n getReleasesStatus,\n promoteRelease,\n updateRollout,\n readReleaseNotesFromDir,\n generateNotesFromGit,\n writeAuditLog,\n createAuditEntry,\n uploadExternallyHosted,\n diffReleases,\n fetchReleaseNotes,\n getVitalsCrashes,\n checkThreshold,\n GpcError,\n} from \"@gpc-cli/core\";\nimport { formatOutput, sortResults, createSpinner, maybePaginate } from \"@gpc-cli/core\";\nimport type { UploadResult } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport {\n isInteractive,\n promptSelect,\n promptInput,\n requireOption,\n requireConfirm,\n} from \"../prompt.js\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n throw new GpcError(\n \"No package name. Use --app <package> or gpc config set app <package>\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Set a default app: gpc config set app <package>\",\n );\n }\n return name;\n}\n\nimport { buildCommitOptions } from \"../commit-options.js\";\n\nfunction createRetryLogger(retryLogPath?: string): ((entry: RetryLogEntry) => void) | undefined {\n if (!retryLogPath) return undefined;\n return (entry: RetryLogEntry) => {\n const line = JSON.stringify(entry) + \"\\n\";\n appendFile(retryLogPath, line).catch(() => {});\n };\n}\n\nasync function getClient(config: GpcConfig, retryLogPath?: string, uploadTimeout?: number) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createApiClient({ auth, onRetry: createRetryLogger(retryLogPath), uploadTimeout });\n}\n\nexport function registerReleasesCommands(program: Command): void {\n const releases = program.command(\"releases\").description(\"Manage releases and rollouts\");\n\n // Upload\n releases\n .command(\"upload <file>\")\n .description(\"Upload AAB/APK and assign to a track\")\n .option(\"--track <track>\", \"Target track\", \"internal\")\n .option(\"--rollout <percent>\", \"Staged rollout percentage (1-100)\")\n .option(\"--notes <text>\", \"Release notes (en-US)\")\n .option(\"--name <name>\", \"Release name\")\n .option(\"--mapping <file>\", \"ProGuard/R8 mapping file for deobfuscation\")\n .option(\"--notes-dir <dir>\", \"Read release notes from directory (<dir>/<lang>.txt)\")\n .option(\"--notes-from-git\", \"Generate release notes from git commit history\")\n .option(\"--copy-notes-from <track>\", \"Copy release notes from another track\")\n .option(\"--since <ref>\", \"Git ref to start from (tag, SHA) — used with --notes-from-git\")\n .option(\"--retry-log <path>\", \"Write retry log entries to file (JSONL)\")\n .option(\n \"--timeout <ms>\",\n \"Upload timeout in milliseconds (auto-scales with file size by default)\",\n parseInt,\n )\n .option(\n \"--status <status>\",\n \"Release status: completed, inProgress, draft, halted\",\n \"completed\",\n )\n .option(\"--mapping-type <type>\", \"Deobfuscation file type: proguard or nativeCode\", \"proguard\")\n .option(\"--device-tier-config <id>\", \"Device tier config ID (or LATEST)\")\n .option(\n \"--changes-not-sent-for-review\",\n \"Commit changes without sending for review (required for rejected apps)\",\n )\n .option(\n \"--error-if-in-review\",\n \"Fail if changes are already in review instead of cancelling them\",\n )\n .option(\n \"--validate-only\",\n \"Upload and validate without committing (changes are discarded after validation)\",\n )\n .action(async (file: string, options) => {\n try {\n await stat(file);\n } catch {\n throw new GpcError(\n `File not found: ${file}`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Check the file path and try again.\",\n );\n }\n\n const ext = extname(file).toLowerCase();\n if (ext !== \".aab\" && ext !== \".apk\") {\n throw new GpcError(\n `Expected .aab or .apk file, got \"${ext || \"(no extension)\"}\"`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Provide a .aab or .apk file.\",\n );\n }\n\n const noteSources = [\n options.notes,\n options.notesDir,\n options.notesFromGit,\n options.copyNotesFrom,\n ].filter(Boolean);\n if (noteSources.length > 1) {\n throw new GpcError(\n \"Cannot combine --notes, --notes-dir, --notes-from-git, and --copy-notes-from. Use only one.\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Pick one release notes source.\",\n );\n }\n if (options.validateOnly && isDryRun(program)) {\n throw new GpcError(\n \"Cannot combine --dry-run and --validate-only.\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use --dry-run to preview without any API changes, or --validate-only to upload and validate without committing.\",\n );\n }\n\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n\n // Interactive mode: prompt for missing options\n if (isInteractive(program)) {\n if (!options.track || options.track === \"internal\") {\n const tracks = [\"internal\", \"alpha\", \"beta\", \"production\"];\n options.track = await promptSelect(\"Select track:\", tracks, \"internal\");\n }\n\n if (!options.rollout && options.track === \"production\") {\n const rolloutStr = await promptInput(\n \"Staged rollout percentage (1-100, blank for full)\",\n \"100\",\n );\n if (rolloutStr && rolloutStr !== \"100\") {\n options.rollout = rolloutStr;\n }\n }\n\n if (!options.notes && !options.notesDir) {\n const notes = await promptInput(\"Release notes (en-US, blank to skip)\");\n if (notes) options.notes = notes;\n }\n }\n\n if (options.rollout !== undefined) {\n const rollout = Number(options.rollout);\n if (!Number.isFinite(rollout) || rollout < 1 || rollout > 100) {\n throw new GpcError(\n `--rollout must be a number between 1 and 100 (got: ${options.rollout})`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use a percentage between 1 and 100.\",\n );\n }\n }\n\n if (\n options.mappingType &&\n options.mappingType !== \"proguard\" &&\n options.mappingType !== \"nativeCode\"\n ) {\n throw new GpcError(\n `--mapping-type must be \"proguard\" or \"nativeCode\" (got: \"${options.mappingType}\")`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use --mapping-type proguard (default) for ProGuard/R8 maps, or --mapping-type nativeCode for native debug symbols.\",\n );\n }\n\n const { size: fileSize } = await stat(file);\n const jsonMode = format === \"json\";\n const client = await getClient(config, options.retryLog, options.timeout);\n\n const showProgress = !jsonMode && process.stderr.isTTY && !program.opts()[\"quiet\"];\n const sizeMB = (fileSize / (1024 * 1024)).toFixed(1);\n\n function formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${bytes} B`;\n }\n\n const BAR_WIDTH = 20;\n const onUploadProgress = showProgress\n ? (event: UploadProgressEvent) => {\n const filled = Math.round((event.percent / 100) * BAR_WIDTH);\n const bar = \"█\".repeat(filled) + \"░\".repeat(BAR_WIDTH - filled);\n const uploaded = formatBytes(event.bytesUploaded);\n const total = formatBytes(event.totalBytes);\n const speed =\n event.bytesPerSecond > 0 ? `${formatBytes(event.bytesPerSecond)}/s` : \"...\";\n const eta = event.etaSeconds > 0 ? `ETA ${event.etaSeconds}s` : \"\";\n process.stderr.write(\n `\\r ${bar} ${event.percent}% ${uploaded}/${total} ${speed} ${eta}\\x1b[K`,\n );\n }\n : undefined;\n\n if (isDryRun(program)) {\n const result = await uploadRelease(client, packageName, file, {\n track: options.track,\n status: options.status,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n mappingFileType: options.mappingType,\n deviceTierConfigId: options.deviceTierConfig,\n dryRun: true,\n });\n console.log(formatOutput(result, format));\n return;\n }\n\n const auditEntry = createAuditEntry(\n \"releases upload\",\n {\n file,\n track: options.track,\n rollout: options.rollout,\n },\n packageName,\n );\n\n const spinner = createSpinner(`Uploading ${basename(file)} (${sizeMB} MB)...`);\n if (!showProgress) spinner.start();\n\n try {\n let releaseNotes: { language: string; text: string }[] | undefined;\n if (options.copyNotesFrom) {\n releaseNotes = await fetchReleaseNotes(client, packageName, options.copyNotesFrom);\n } else if (options.notesFromGit) {\n const gitNotes = await generateNotesFromGit({ since: options.since });\n releaseNotes = [{ language: gitNotes.language, text: gitNotes.text }];\n } else if (options.notesDir) {\n releaseNotes = await readReleaseNotesFromDir(options.notesDir);\n } else if (options.notes) {\n releaseNotes = [{ language: \"en-US\", text: options.notes }];\n }\n\n const result = await uploadRelease(client, packageName, file, {\n track: options.track,\n status: options.status,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n releaseNotes,\n releaseName: options.name,\n mappingFile: options.mapping,\n mappingFileType: options.mappingType,\n deviceTierConfigId: options.deviceTierConfig,\n validateOnly: options.validateOnly,\n onUploadProgress,\n commitOptions: buildCommitOptions(options),\n });\n if (showProgress) {\n process.stderr.write(`\\r ✓ Uploaded ${basename(file)} ${sizeMB} MB\\x1b[K\\n`);\n }\n spinner.stop(\n (result as UploadResult).validateOnly\n ? \"Validation passed (not committed)\"\n : \"Upload complete\",\n );\n console.log(formatOutput(result, format));\n auditEntry.success = true;\n } catch (error) {\n if (showProgress) {\n process.stderr.write(\"\\n\");\n }\n spinner.fail(\"Upload failed\");\n auditEntry.success = false;\n auditEntry.error = error instanceof Error ? error.message : String(error);\n throw error;\n } finally {\n auditEntry.durationMs = Date.now() - new Date(auditEntry.timestamp).getTime();\n writeAuditLog(auditEntry).catch(() => {});\n }\n });\n\n // Status\n releases\n .command(\"status\")\n .description(\"Show current release status across tracks\")\n .option(\"--track <track>\", \"Filter by track\")\n .option(\"--sort <field>\", \"Sort by field (prefix with - for descending)\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const TRACK_ORDER = [\"production\", \"beta\", \"alpha\", \"internal\"];\n const rawStatuses = await getReleasesStatus(client, packageName, options.track);\n const statuses = options.track\n ? Array.isArray(rawStatuses)\n ? rawStatuses.filter((s: any) => s.track === options.track)\n : rawStatuses\n : rawStatuses;\n const sorted = Array.isArray(statuses)\n ? options.sort\n ? sortResults(statuses, options.sort)\n : [...statuses].sort((a, b) => {\n const ai = TRACK_ORDER.indexOf(\n String((a as unknown as Record<string, unknown>)[\"track\"] ?? \"\"),\n );\n const bi = TRACK_ORDER.indexOf(\n String((b as unknown as Record<string, unknown>)[\"track\"] ?? \"\"),\n );\n return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);\n })\n : statuses;\n if (format !== \"json\" && Array.isArray(sorted)) {\n const rows = sorted.map((s: unknown) => {\n const sr = s as Record<string, unknown>;\n return {\n track: sr[\"track\"] || \"-\",\n status: sr[\"status\"] || \"-\",\n name: sr[\"name\"] || \"-\",\n versionCodes: Array.isArray(sr[\"versionCodes\"])\n ? (sr[\"versionCodes\"] as unknown[]).join(\", \")\n : \"-\",\n userFraction:\n sr[\"userFraction\"] !== undefined\n ? `${Math.round(Number(sr[\"userFraction\"]) * 100)}%`\n : \"—\",\n };\n });\n await maybePaginate(formatOutput(rows, format));\n } else {\n await maybePaginate(formatOutput(sorted, format));\n }\n });\n\n // Promote\n releases\n .command(\"promote\")\n .description(\"Promote a release from one track to another\")\n .option(\"--from <track>\", \"Source track\")\n .option(\"--to <track>\", \"Target track\")\n .option(\"--rollout <percent>\", \"Staged rollout percentage\")\n .option(\"--notes <text>\", \"Release notes\")\n .option(\"--copy-notes-from <track>\", \"Copy release notes from another track\")\n .option(\"--status <status>\", \"Release status: completed, inProgress, draft, halted\")\n .option(\n \"--changes-not-sent-for-review\",\n \"Commit changes without sending for review (required for rejected apps)\",\n )\n .option(\n \"--error-if-in-review\",\n \"Fail if changes are already in review instead of cancelling them\",\n )\n .action(async (options) => {\n if (options.notes && options.copyNotesFrom) {\n throw new GpcError(\n \"Cannot combine --notes and --copy-notes-from. Use only one.\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Pick one release notes source.\",\n );\n }\n\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n const interactive = isInteractive(program);\n const tracks = [\"internal\", \"alpha\", \"beta\", \"production\"];\n\n options.from = await requireOption(\n \"from\",\n options.from,\n {\n message: \"Source track:\",\n choices: tracks,\n },\n interactive,\n );\n\n options.to = await requireOption(\n \"to\",\n options.to,\n {\n message: \"Target track:\",\n choices: tracks.filter((t: string) => t !== options.from),\n },\n interactive,\n );\n\n if (options.from === options.to) {\n throw new GpcError(\n `--from and --to must be different tracks (both are \"${options.from}\")`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Specify different source and target tracks.\",\n );\n }\n\n if (options.rollout !== undefined) {\n const rollout = Number(options.rollout);\n if (!Number.isFinite(rollout) || rollout < 1 || rollout > 100) {\n throw new GpcError(\n `--rollout must be a number between 1 and 100 (got: ${options.rollout})`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use a percentage between 1 and 100.\",\n );\n }\n }\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"releases promote\",\n action: \"promote\",\n target: `${options.from} → ${options.to}`,\n details: {\n rollout: options.rollout,\n ...(options.status && { status: options.status }),\n },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n let releaseNotes: { language: string; text: string }[] | undefined;\n if (options.copyNotesFrom) {\n releaseNotes = await fetchReleaseNotes(client, packageName, options.copyNotesFrom);\n } else if (options.notes) {\n releaseNotes = [{ language: \"en-US\", text: options.notes }];\n }\n\n const result = await promoteRelease(client, packageName, options.from, options.to, {\n status: options.status,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n releaseNotes,\n commitOptions: buildCommitOptions(options),\n });\n console.log(formatOutput(result, format));\n });\n\n // Rollout subcommands\n const rollout = releases.command(\"rollout\").description(\"Manage staged rollouts\");\n\n for (const action of [\"increase\", \"halt\", \"resume\", \"complete\"] as const) {\n const cmd = rollout\n .command(action)\n .description(`${action.charAt(0).toUpperCase() + action.slice(1)} a staged rollout`)\n .option(\"--track <track>\", \"Track name\");\n\n if (action === \"increase\") {\n cmd.option(\"--to <percent>\", \"New rollout percentage\");\n cmd.option(\"--vitals-gate\", \"Halt rollout if crash rate exceeds configured threshold\");\n }\n\n cmd.option(\n \"--changes-not-sent-for-review\",\n \"Commit changes without sending for review (required for rejected apps)\",\n );\n cmd.option(\n \"--error-if-in-review\",\n \"Fail if changes are already in review instead of cancelling them\",\n );\n\n cmd.action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n const interactive = isInteractive(program);\n const tracks = [\"internal\", \"alpha\", \"beta\", \"production\"];\n\n options.track = await requireOption(\n \"track\",\n options.track,\n {\n message: \"Track:\",\n choices: tracks,\n },\n interactive,\n );\n\n if (action === \"increase\") {\n options.to = await requireOption(\n \"to\",\n options.to,\n {\n message: \"New rollout percentage (1-100):\",\n },\n interactive,\n );\n }\n\n if (action === \"increase\" && options.to !== undefined) {\n const to = Number(options.to);\n if (!Number.isFinite(to) || to < 1 || to > 100) {\n throw new GpcError(\n `--to must be a number between 1 and 100 (got: ${options.to})`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use a percentage between 1 and 100.\",\n );\n }\n }\n\n // Require confirmation for destructive rollout halt\n if (action === \"halt\") {\n await requireConfirm(\n `Halt rollout on track \"${options.track}\" for ${packageName}?`,\n program,\n );\n }\n\n if (isDryRun(program)) {\n if (action === \"increase\" && options.vitalsGate) {\n console.error(\n \"Warning: --vitals-gate is ignored in --dry-run mode. Gate will run on live execution.\",\n );\n }\n printDryRun(\n {\n command: `releases rollout ${action}`,\n action: action,\n target: options.track,\n details: { percentage: options.to !== undefined ? `${options.to}%` : undefined },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n const result = await updateRollout(\n client,\n packageName,\n options.track,\n action,\n options.to ? Number(options.to) / 100 : undefined,\n buildCommitOptions(options),\n );\n\n // Vitals gate: check crash rate after rollout increase\n if (action === \"increase\" && options.vitalsGate) {\n const threshold = (config as any).vitals?.thresholds?.crashRate;\n if (!threshold) {\n console.error(\n \"Warning: --vitals-gate requires vitals.thresholds.crashRate in config. Skipping gate.\",\n );\n } else {\n try {\n const { auth: authConfig } = config;\n const vitalsAuth = await resolveAuth({\n serviceAccountPath: authConfig?.serviceAccount,\n });\n const reportingClient = createReportingClient({ auth: vitalsAuth });\n const vitalsResult = await getVitalsCrashes(reportingClient, packageName, {\n days: 1,\n });\n const latest = (vitalsResult as any).data?.[0]?.crashRate;\n const check = checkThreshold(latest, threshold);\n if (check.breached) {\n await updateRollout(client, packageName, options.track, \"halt\");\n console.error(\n `Vitals gate: crash rate ${String(latest)}% > threshold ${String(threshold)}%. Rollout halted.`,\n );\n process.exitCode = 6;\n }\n } catch (vitalsErr) {\n console.error(\n `Warning: Vitals gate check failed: ${vitalsErr instanceof Error ? vitalsErr.message : String(vitalsErr)}`,\n );\n }\n }\n }\n\n console.log(formatOutput(result, format));\n });\n }\n\n // Release notes\n releases\n .command(\"notes\")\n .description(\"Get or set release notes\")\n .argument(\"<action>\", \"Action: get|set\")\n .option(\"--track <track>\", \"Track name\")\n .option(\"--lang <language>\", \"Language code\", \"en-US\")\n .option(\"--notes <text>\", \"Release notes text\")\n .option(\"--file <path>\", \"Read notes from file\")\n .action(async (action: string, options) => {\n if (action === \"set\") {\n throw new GpcError(\n \"gpc releases notes set is not implemented as a standalone command.\",\n \"RELEASES_USAGE_ERROR\",\n 1,\n \"Use --notes, --notes-dir, or --notes-from-git with: gpc releases upload or gpc publish\",\n );\n }\n\n if (action === \"get\") {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n const track = options.track ?? \"internal\";\n\n // Try getReleasesStatus first (has all releases), then fallback to fetchReleaseNotes\n const statuses = await getReleasesStatus(client, packageName, track);\n let notes = Array.isArray(statuses)\n ? statuses.flatMap((s: any) => s.releaseNotes ?? [])\n : ((statuses as any).releaseNotes ?? []);\n\n // Fallback: fetchReleaseNotes reads the raw track data which may have notes\n // even when getReleasesStatus doesn't (e.g. completed releases)\n if (notes.length === 0) {\n try {\n notes = await fetchReleaseNotes(client, packageName, track);\n } catch {\n // No release found on track — fall through to empty message\n }\n }\n\n if (notes.length === 0) {\n console.log(`No release notes found on track \"${track}\".`);\n return;\n }\n console.log(formatOutput(notes, format));\n return;\n }\n\n throw new GpcError(\n \"Unknown action. Usage: gpc releases notes <get|set> --track <track>\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use: gpc releases notes get --track <track>\",\n );\n });\n\n // Upload externally hosted APK\n releases\n .command(\"upload-external\")\n .description(\"Upload an externally hosted APK configuration\")\n .requiredOption(\"--url <url>\", \"External URL where the APK is hosted\")\n .requiredOption(\"--file <config>\", \"Path to JSON config file with APK metadata\")\n .action(async (options: { url: string; file: string }) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n\n const { readFile } = await import(\"node:fs/promises\");\n const raw = await readFile(options.file, \"utf-8\");\n const apkConfig = JSON.parse(raw) as Record<string, unknown>;\n\n // Override with CLI-provided URL\n apkConfig[\"externallyHostedUrl\"] = options.url;\n\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n const client = createApiClient({ auth });\n const result = await uploadExternallyHosted(\n client,\n packageName,\n apkConfig as unknown as ExternallyHostedApk,\n );\n console.log(formatOutput(result, format));\n });\n\n // Diff\n releases\n .command(\"diff\")\n .description(\"Compare releases between two tracks\")\n .option(\"--from <track>\", \"Source track\", \"internal\")\n .option(\"--to <track>\", \"Target track\", \"production\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await diffReleases(client, packageName, options.from, options.to);\n if (result.diffs.length === 0) {\n console.log(`No differences between ${result.fromTrack} and ${result.toTrack}.`);\n } else {\n if (format === \"json\") {\n console.log(formatOutput(result, format));\n } else {\n console.log(`Differences: ${result.fromTrack} vs ${result.toTrack}\\n`);\n console.log(formatOutput(result.diffs, format));\n }\n }\n });\n\n // Count\n releases\n .command(\"count\")\n .description(\"Count releases per track\")\n .option(\"--track <track>\", \"Filter to a specific track\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const statuses = await getReleasesStatus(client, packageName, options.track);\n\n // Group by track\n const trackCounts = new Map<string, { total: number; statuses: Record<string, number> }>();\n for (const r of statuses) {\n const entry = trackCounts.get(r.track) ?? { total: 0, statuses: {} };\n entry.total++;\n entry.statuses[r.status] = (entry.statuses[r.status] ?? 0) + 1;\n trackCounts.set(r.track, entry);\n }\n\n if (format === \"json\") {\n const data = Object.fromEntries(\n [...trackCounts.entries()].map(([track, info]) => [track, info]),\n );\n console.log(formatOutput(data, format));\n } else {\n const rows = [...trackCounts.entries()].map(([track, info]) => ({\n track,\n releases: info.total,\n ...info.statuses,\n }));\n if (rows.length === 0) {\n console.log(\"No releases found.\");\n } else {\n console.log(formatOutput(rows, format));\n }\n }\n });\n}\n"],"mappings":"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","names":["rollout"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|