@gpc-cli/cli 0.9.66 → 0.9.68
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 +7 -2
- package/dist/bin.js +2 -2
- package/dist/{chunk-IUG4225Q.js → chunk-PIN72MK4.js} +15 -9
- package/dist/chunk-PIN72MK4.js.map +1 -0
- package/dist/{config-DDZMNYOB.js → config-X3QX77XY.js} +3 -3
- package/dist/config-X3QX77XY.js.map +1 -0
- package/dist/{doctor-QRHXY3FC.js → doctor-IVBGTYXJ.js} +75 -3
- package/dist/doctor-IVBGTYXJ.js.map +1 -0
- package/dist/{feedback-C2BA7CK6.js → feedback-K3ORBUE5.js} +2 -2
- package/dist/generated/docs-bundle.json +1 -1
- package/dist/index.js +1 -1
- package/dist/{publish-H2IAOKP7.js → publish-B7RDH25M.js} +18 -2
- package/dist/publish-B7RDH25M.js.map +1 -0
- package/dist/{releases-RTF5NPTD.js → releases-67CU3SRK.js} +14 -2
- package/dist/releases-67CU3SRK.js.map +1 -0
- package/dist/setup-GT6IRDVG.js +267 -0
- package/dist/setup-GT6IRDVG.js.map +1 -0
- package/dist/{update-GEH352BE.js → update-6HUFGIB5.js} +2 -2
- package/dist/{version-2RUEWMPK.js → version-U44BGJY5.js} +2 -2
- package/dist/watch-4HDEY5FR.js +217 -0
- package/dist/watch-4HDEY5FR.js.map +1 -0
- package/package.json +5 -5
- package/dist/chunk-IUG4225Q.js.map +0 -1
- package/dist/config-DDZMNYOB.js.map +0 -1
- package/dist/doctor-QRHXY3FC.js.map +0 -1
- package/dist/publish-H2IAOKP7.js.map +0 -1
- package/dist/releases-RTF5NPTD.js.map +0 -1
- /package/dist/{feedback-C2BA7CK6.js.map → feedback-K3ORBUE5.js.map} +0 -0
- /package/dist/{update-GEH352BE.js.map → update-6HUFGIB5.js.map} +0 -0
- /package/dist/{version-2RUEWMPK.js.map → version-U44BGJY5.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -89,6 +89,9 @@ function registerPublishCommand(program) {
|
|
|
89
89
|
).option(
|
|
90
90
|
"--error-if-in-review",
|
|
91
91
|
"Fail if changes are already in review instead of cancelling them"
|
|
92
|
+
).option(
|
|
93
|
+
"--validate-only",
|
|
94
|
+
"Upload and validate without committing (changes are discarded after validation)"
|
|
92
95
|
).action(async (file, options) => {
|
|
93
96
|
try {
|
|
94
97
|
await stat(file);
|
|
@@ -109,6 +112,14 @@ function registerPublishCommand(program) {
|
|
|
109
112
|
"Pick one release notes source."
|
|
110
113
|
);
|
|
111
114
|
}
|
|
115
|
+
if (options.validateOnly && isDryRun(program)) {
|
|
116
|
+
throw new GpcError(
|
|
117
|
+
"Cannot combine --dry-run and --validate-only.",
|
|
118
|
+
"PUBLISH_USAGE_ERROR",
|
|
119
|
+
2,
|
|
120
|
+
"Use --dry-run to preview without any API changes, or --validate-only to upload and validate without committing."
|
|
121
|
+
);
|
|
122
|
+
}
|
|
112
123
|
const config = await loadConfig();
|
|
113
124
|
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
114
125
|
const format = getOutputFormat(program, config);
|
|
@@ -202,6 +213,7 @@ function registerPublishCommand(program) {
|
|
|
202
213
|
mappingFile: options.mapping,
|
|
203
214
|
mappingFileType: options.mappingType,
|
|
204
215
|
deviceTierConfigId: options.deviceTierConfig,
|
|
216
|
+
validateOnly: options.validateOnly,
|
|
205
217
|
commitOptions: buildCommitOptions(options)
|
|
206
218
|
});
|
|
207
219
|
if (!result.upload) {
|
|
@@ -211,7 +223,11 @@ function registerPublishCommand(program) {
|
|
|
211
223
|
process.exitCode = 1;
|
|
212
224
|
return;
|
|
213
225
|
}
|
|
214
|
-
|
|
226
|
+
if (result.upload.validateOnly) {
|
|
227
|
+
console.log(formatOutput({ ...result.upload, message: "Validation passed (not committed)" }, format));
|
|
228
|
+
} else {
|
|
229
|
+
console.log(formatPublishOutput(result, format));
|
|
230
|
+
}
|
|
215
231
|
auditEntry.success = true;
|
|
216
232
|
} catch (error) {
|
|
217
233
|
auditEntry.success = false;
|
|
@@ -227,4 +243,4 @@ function registerPublishCommand(program) {
|
|
|
227
243
|
export {
|
|
228
244
|
registerPublishCommand
|
|
229
245
|
};
|
|
230
|
-
//# sourceMappingURL=publish-
|
|
246
|
+
//# sourceMappingURL=publish-B7RDH25M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/publish.ts"],"sourcesContent":["import { resolvePackageName } from \"../resolve.js\";\nimport { appendFile, stat } from \"node:fs/promises\";\nimport type { OutputFormat } from \"@gpc-cli/config\";\nimport type { Command } from \"commander\";\nimport { loadConfig, getCacheDir } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient } from \"@gpc-cli/api\";\nimport type { RetryLogEntry } from \"@gpc-cli/api\";\nimport {\n publish,\n generateNotesFromGit,\n writeAuditLog,\n createAuditEntry,\n formatOutput,\n GpcError,\n} from \"@gpc-cli/core\";\nimport type { PublishResult, DryRunPublishResult, UploadResult } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun } from \"../dry-run.js\";\nimport { isInteractive, promptSelect, promptInput } from \"../prompt.js\";\nimport { buildCommitOptions } from \"../commit-options.js\";\n\nconst PASS = \"\\u2713\";\nconst FAIL = \"\\u2717\";\nconst WARN = \"\\u26A0\";\n\n// ---------------------------------------------------------------------------\n// Output formatters\n// ---------------------------------------------------------------------------\n\nfunction formatChecks(checks: { name: string; passed: boolean; message: string }[]): string[] {\n return checks.map((c) => ` ${c.passed ? PASS : FAIL} ${c.message}`);\n}\n\nfunction formatValidationOutput(result: PublishResult, format: OutputFormat): string {\n if (format !== \"table\") {\n return formatOutput({ success: false, validation: result.validation }, format);\n }\n const lines = [\"Validation failed:\\n\", ...formatChecks(result.validation.checks)];\n for (const w of result.validation.warnings) {\n lines.push(` ${WARN} ${w}`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction formatPublishOutput(result: PublishResult, format: OutputFormat): string {\n if (format !== \"table\") return formatOutput(result, format);\n\n const upload = result.upload;\n if (!upload) return formatOutput(result, format);\n const rollout =\n upload.status === \"inProgress\" && \"userFraction\" in upload\n ? ` (${Math.round(Number((upload as Record<string, unknown>)[\"userFraction\"]) * 100)}% rollout)`\n : \"\";\n\n const lines = [\"Published successfully\\n\", ...formatChecks(result.validation.checks)];\n for (const w of result.validation.warnings) {\n lines.push(` ${WARN} ${w}`);\n }\n lines.push(\"\");\n lines.push(` versionCode ${upload.versionCode}`);\n lines.push(` track ${upload.track}`);\n lines.push(` status ${upload.status}${rollout}`);\n return lines.join(\"\\n\");\n}\n\nfunction formatDryRunOutput(result: DryRunPublishResult, format: OutputFormat): string {\n if (format !== \"table\") return formatOutput(result, format);\n\n const lines = [\"Dry run — no changes made\\n\", ...formatChecks(result.validation.checks)];\n for (const w of result.validation.warnings) {\n lines.push(` ${WARN} ${w}`);\n }\n\n const u = result.upload;\n lines.push(\"\");\n lines.push(` Track ${u.track}`);\n\n if (u.currentReleases.length === 0) {\n lines.push(` Current (no releases on this track)`);\n } else {\n for (const r of u.currentReleases) {\n const fraction =\n r.userFraction !== undefined ? ` (${Math.round(r.userFraction * 100)}%)` : \"\";\n lines.push(` Current ${r.versionCodes.join(\", \")} · ${r.status}${fraction}`);\n }\n }\n const plannedFraction =\n u.plannedRelease.userFraction !== undefined\n ? ` (${Math.round(u.plannedRelease.userFraction * 100)}%)`\n : \"\";\n lines.push(` Would be (new bundle) · ${u.plannedRelease.status}${plannedFraction}`);\n return lines.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport function registerPublishCommand(program: Command): void {\n program\n .command(\"publish <file>\")\n .description(\"Validate, upload, and release in one step\")\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(\"--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(\"--since <ref>\", \"Git ref to start from (tag, SHA) — used with --notes-from-git\")\n .option(\"--name <name>\", \"Release name\")\n .option(\"--mapping <file>\", \"ProGuard/R8 mapping file for deobfuscation\")\n .option(\"--retry-log <path>\", \"Write retry log entries to file (JSONL)\")\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 \"PUBLISH_USAGE_ERROR\",\n 2,\n \"Check the file path and try again.\",\n );\n }\n\n const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);\n if (noteSources.length > 1) {\n throw new GpcError(\n \"Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.\",\n \"PUBLISH_USAGE_ERROR\",\n 2,\n \"Pick one release notes source.\",\n );\n }\n\n if (options.validateOnly && isDryRun(program)) {\n throw new GpcError(\n \"Cannot combine --dry-run and --validate-only.\",\n \"PUBLISH_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 && !options.notesFromGit) {\n const notes = await promptInput(\"Release notes (en-US, blank to skip)\");\n if (notes) options.notes = notes;\n }\n }\n\n // Rollout range guard\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 \"PUBLISH_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 \"PUBLISH_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 // Resolve git-based release notes before calling publish\n if (options.notesFromGit) {\n const gitNotes = await generateNotesFromGit({ since: options.since });\n options.notes = gitNotes.text;\n if (gitNotes.truncated) {\n console.error(\n `${WARN} Release notes truncated to 500 characters (${gitNotes.commitCount} commits from ${gitNotes.since}).`,\n );\n }\n }\n\n let onRetry: ((entry: RetryLogEntry) => void) | undefined;\n if (options.retryLog) {\n onRetry = (entry: RetryLogEntry) => {\n appendFile(options.retryLog, JSON.stringify(entry) + \"\\n\").catch(() => {});\n };\n }\n\n const auth = await resolveAuth({\n serviceAccountPath: config.auth?.serviceAccount,\n cachePath: getCacheDir(),\n });\n const client = createApiClient({ auth, onRetry });\n\n if (isDryRun(program)) {\n const result = await publish(client, packageName, file, {\n track: options.track,\n rolloutPercent: options.rollout ? Number(options.rollout) : undefined,\n notes: options.notes,\n notesDir: options.notesDir,\n releaseName: options.name,\n mappingFile: options.mapping,\n mappingFileType: options.mappingType,\n deviceTierConfigId: options.deviceTierConfig,\n commitOptions: buildCommitOptions(options),\n dryRun: true,\n });\n console.log(formatDryRunOutput(result as DryRunPublishResult, format));\n return;\n }\n\n const auditEntry = createAuditEntry(\n \"publish\",\n { file, track: options.track, rollout: options.rollout },\n packageName,\n );\n\n try {\n const result = await publish(client, packageName, file, {\n track: options.track,\n rolloutPercent: options.rollout ? Number(options.rollout) : undefined,\n notes: options.notes,\n notesDir: options.notesDir,\n releaseName: options.name,\n mappingFile: options.mapping,\n mappingFileType: options.mappingType,\n deviceTierConfigId: options.deviceTierConfig,\n validateOnly: options.validateOnly,\n commitOptions: buildCommitOptions(options),\n });\n\n if (!result.upload) {\n console.log(formatValidationOutput(result as PublishResult, format));\n auditEntry.success = false;\n auditEntry.error = \"Validation failed\";\n process.exitCode = 1;\n return;\n }\n\n if ((result.upload as UploadResult).validateOnly) {\n console.log(formatOutput({ ...result.upload, message: \"Validation passed (not committed)\" }, format));\n } else {\n console.log(formatPublishOutput(result as PublishResult, format));\n }\n auditEntry.success = true;\n } catch (error) {\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"],"mappings":"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","names":[]}
|
|
@@ -80,6 +80,9 @@ function registerReleasesCommands(program) {
|
|
|
80
80
|
).option(
|
|
81
81
|
"--error-if-in-review",
|
|
82
82
|
"Fail if changes are already in review instead of cancelling them"
|
|
83
|
+
).option(
|
|
84
|
+
"--validate-only",
|
|
85
|
+
"Upload and validate without committing (changes are discarded after validation)"
|
|
83
86
|
).action(async (file, options) => {
|
|
84
87
|
try {
|
|
85
88
|
await stat(file);
|
|
@@ -114,6 +117,14 @@ function registerReleasesCommands(program) {
|
|
|
114
117
|
"Pick one release notes source."
|
|
115
118
|
);
|
|
116
119
|
}
|
|
120
|
+
if (options.validateOnly && isDryRun(program)) {
|
|
121
|
+
throw new GpcError(
|
|
122
|
+
"Cannot combine --dry-run and --validate-only.",
|
|
123
|
+
"RELEASES_USAGE_ERROR",
|
|
124
|
+
2,
|
|
125
|
+
"Use --dry-run to preview without any API changes, or --validate-only to upload and validate without committing."
|
|
126
|
+
);
|
|
127
|
+
}
|
|
117
128
|
const config = await loadConfig();
|
|
118
129
|
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
119
130
|
const format = getOutputFormat(program, config);
|
|
@@ -222,6 +233,7 @@ function registerReleasesCommands(program) {
|
|
|
222
233
|
mappingFile: options.mapping,
|
|
223
234
|
mappingFileType: options.mappingType,
|
|
224
235
|
deviceTierConfigId: options.deviceTierConfig,
|
|
236
|
+
validateOnly: options.validateOnly,
|
|
225
237
|
onUploadProgress,
|
|
226
238
|
commitOptions: buildCommitOptions(options)
|
|
227
239
|
});
|
|
@@ -229,7 +241,7 @@ function registerReleasesCommands(program) {
|
|
|
229
241
|
process.stderr.write(`\r \u2713 Uploaded ${basename(file)} ${sizeMB} MB\x1B[K
|
|
230
242
|
`);
|
|
231
243
|
}
|
|
232
|
-
spinner.stop("Upload complete");
|
|
244
|
+
spinner.stop(result.validateOnly ? "Validation passed (not committed)" : "Upload complete");
|
|
233
245
|
console.log(formatOutput(result, format));
|
|
234
246
|
auditEntry.success = true;
|
|
235
247
|
} catch (error) {
|
|
@@ -593,4 +605,4 @@ function registerReleasesCommands(program) {
|
|
|
593
605
|
export {
|
|
594
606
|
registerReleasesCommands
|
|
595
607
|
};
|
|
596
|
-
//# sourceMappingURL=releases-
|
|
608
|
+
//# sourceMappingURL=releases-67CU3SRK.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 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((result as UploadResult).validateOnly ? \"Validation passed (not committed)\" : \"Upload complete\");\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"]}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/setup.ts
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
var ANDROID_PACKAGE_RE = /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
|
|
7
|
+
function step(n, total, label, status) {
|
|
8
|
+
const padded = `Step ${n}/${total} ${label}`.padEnd(50);
|
|
9
|
+
console.log(`${padded}${status}`);
|
|
10
|
+
}
|
|
11
|
+
function registerSetupCommand(program) {
|
|
12
|
+
program.command("setup").description("Guided first-time setup: auth, config, verification, and shell completion").option("--auto", "Non-interactive setup from env vars and auto-detected credentials").action(async (options) => {
|
|
13
|
+
const { isInteractive, promptInput, promptSelect, promptConfirm } = await import("./prompt-HJXNXXAR.js");
|
|
14
|
+
const { loadConfig, initConfig, setConfigValue, getUserConfigPath } = await import("@gpc-cli/config");
|
|
15
|
+
const { resolveAuth, loadServiceAccountKey } = await import("@gpc-cli/auth");
|
|
16
|
+
const interactive = !options.auto && isInteractive(program);
|
|
17
|
+
const total = 5;
|
|
18
|
+
console.log("\nGPC \u2014 Setup");
|
|
19
|
+
console.log("\u2550".repeat(50));
|
|
20
|
+
if (options.auto) {
|
|
21
|
+
console.log("Running in auto mode (non-interactive)\n");
|
|
22
|
+
}
|
|
23
|
+
let saPath;
|
|
24
|
+
let useAdc = false;
|
|
25
|
+
let authOk = false;
|
|
26
|
+
let email = "";
|
|
27
|
+
if (options.auto) {
|
|
28
|
+
try {
|
|
29
|
+
const auth = await resolveAuth();
|
|
30
|
+
email = auth.getClientEmail();
|
|
31
|
+
await auth.getAccessToken();
|
|
32
|
+
authOk = true;
|
|
33
|
+
const method = process.env["GPC_SERVICE_ACCOUNT"] || process.env["GOOGLE_APPLICATION_CREDENTIALS"] ? "env" : "adc";
|
|
34
|
+
step(1, total, "Authenticate", `\u2713 ${email} (${method})`);
|
|
35
|
+
} catch {
|
|
36
|
+
step(1, total, "Authenticate", "\u2717 No credentials found");
|
|
37
|
+
console.log("\n Set GPC_SERVICE_ACCOUNT or GOOGLE_APPLICATION_CREDENTIALS,");
|
|
38
|
+
console.log(" or run `gpc setup` interactively.\n");
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
} else if (interactive) {
|
|
43
|
+
try {
|
|
44
|
+
const existing = await loadConfig();
|
|
45
|
+
if (existing.auth?.serviceAccount) {
|
|
46
|
+
const auth = await resolveAuth({
|
|
47
|
+
serviceAccountPath: existing.auth.serviceAccount
|
|
48
|
+
});
|
|
49
|
+
email = auth.getClientEmail();
|
|
50
|
+
await auth.getAccessToken();
|
|
51
|
+
authOk = true;
|
|
52
|
+
step(1, total, "Authenticate", `\u2713 ${email} (existing config)`);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
if (!authOk) {
|
|
57
|
+
const method = await promptSelect(
|
|
58
|
+
"\nAuthentication method:",
|
|
59
|
+
["service-account", "adc"],
|
|
60
|
+
"service-account"
|
|
61
|
+
);
|
|
62
|
+
if (method === "adc") {
|
|
63
|
+
useAdc = true;
|
|
64
|
+
try {
|
|
65
|
+
const auth = await resolveAuth();
|
|
66
|
+
email = auth.getClientEmail();
|
|
67
|
+
await auth.getAccessToken();
|
|
68
|
+
authOk = true;
|
|
69
|
+
step(1, total, "Authenticate", `\u2713 ${email} (ADC)`);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
step(
|
|
72
|
+
1,
|
|
73
|
+
total,
|
|
74
|
+
"Authenticate",
|
|
75
|
+
`\u2717 ADC failed: ${err instanceof Error ? err.message : String(err)}`
|
|
76
|
+
);
|
|
77
|
+
console.log(" Run: gcloud auth application-default login");
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
const commonPaths = [
|
|
81
|
+
"~/gpc-service-account.json",
|
|
82
|
+
"./service-account.json",
|
|
83
|
+
"./gpc-service-account.json",
|
|
84
|
+
"~/.config/gpc/service-account.json"
|
|
85
|
+
].map((p) => resolve(p.replace("~", process.env["HOME"] || "")));
|
|
86
|
+
const autoDetected = commonPaths.find((p) => existsSync(p));
|
|
87
|
+
const defaultHint = autoDetected ? ` (found: ${autoDetected})` : "";
|
|
88
|
+
let attempts = 0;
|
|
89
|
+
while (!authOk && attempts < 3) {
|
|
90
|
+
const input = await promptInput(
|
|
91
|
+
`Path to service account JSON key${defaultHint}`,
|
|
92
|
+
autoDetected
|
|
93
|
+
);
|
|
94
|
+
if (!input) {
|
|
95
|
+
step(1, total, "Authenticate", "\u2014 skipped");
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
const absPath = resolve(input.replace("~", process.env["HOME"] || ""));
|
|
99
|
+
if (!existsSync(absPath)) {
|
|
100
|
+
console.log(` File not found: ${absPath}`);
|
|
101
|
+
attempts++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const key = await loadServiceAccountKey(absPath);
|
|
106
|
+
const auth = await resolveAuth({ serviceAccountPath: absPath });
|
|
107
|
+
await auth.getAccessToken();
|
|
108
|
+
saPath = absPath;
|
|
109
|
+
email = key.client_email;
|
|
110
|
+
authOk = true;
|
|
111
|
+
step(1, total, "Authenticate", `\u2713 ${email}`);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.log(
|
|
114
|
+
` Auth failed: ${err instanceof Error ? err.message : String(err)}`
|
|
115
|
+
);
|
|
116
|
+
attempts++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!authOk && attempts >= 3) {
|
|
120
|
+
step(1, total, "Authenticate", "\u2717 Failed after 3 attempts");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
step(1, total, "Authenticate", "\u2014 non-interactive, skipped");
|
|
126
|
+
}
|
|
127
|
+
let packageName;
|
|
128
|
+
if (options.auto) {
|
|
129
|
+
packageName = process.env["GPC_APP"];
|
|
130
|
+
if (packageName) {
|
|
131
|
+
step(2, total, "Default app", `\u2713 ${packageName}`);
|
|
132
|
+
} else {
|
|
133
|
+
step(2, total, "Default app", "\u2014 GPC_APP not set (optional)");
|
|
134
|
+
}
|
|
135
|
+
} else if (interactive) {
|
|
136
|
+
try {
|
|
137
|
+
const existing = await loadConfig();
|
|
138
|
+
if (existing.app) {
|
|
139
|
+
const keep = await promptConfirm(`Keep existing app: ${existing.app}?`, true);
|
|
140
|
+
if (keep) {
|
|
141
|
+
packageName = existing.app;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
if (!packageName) {
|
|
147
|
+
const input = await promptInput(
|
|
148
|
+
"Default package name (e.g. com.example.app, blank to skip)"
|
|
149
|
+
);
|
|
150
|
+
if (input) {
|
|
151
|
+
if (!ANDROID_PACKAGE_RE.test(input)) {
|
|
152
|
+
console.log(
|
|
153
|
+
` Warning: "${input}" doesn't look like a valid Android package name`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
packageName = input;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (packageName) {
|
|
160
|
+
step(2, total, "Default app", `\u2713 ${packageName}`);
|
|
161
|
+
} else {
|
|
162
|
+
step(2, total, "Default app", "\u2014 skipped (set later with: gpc config set app <pkg>)");
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
step(2, total, "Default app", "\u2014 non-interactive, skipped");
|
|
166
|
+
}
|
|
167
|
+
const configPayload = {};
|
|
168
|
+
if (saPath) configPayload["auth"] = { serviceAccount: saPath };
|
|
169
|
+
if (packageName) configPayload["app"] = packageName;
|
|
170
|
+
const configPath = getUserConfigPath();
|
|
171
|
+
const configExists = existsSync(configPath);
|
|
172
|
+
if (Object.keys(configPayload).length > 0) {
|
|
173
|
+
try {
|
|
174
|
+
if (configExists) {
|
|
175
|
+
if (saPath) await setConfigValue("auth.serviceAccount", saPath);
|
|
176
|
+
if (packageName) await setConfigValue("app", packageName);
|
|
177
|
+
step(3, total, "Save config", `\u2713 Updated ${configPath}`);
|
|
178
|
+
} else {
|
|
179
|
+
await initConfig(configPayload);
|
|
180
|
+
step(3, total, "Save config", `\u2713 Created ${configPath}`);
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
step(
|
|
184
|
+
3,
|
|
185
|
+
total,
|
|
186
|
+
"Save config",
|
|
187
|
+
`\u2717 Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
} else if (configExists) {
|
|
191
|
+
step(3, total, "Save config", `\u2713 Existing config at ${configPath}`);
|
|
192
|
+
} else if (options.auto) {
|
|
193
|
+
step(3, total, "Save config", "\u2014 using env vars only");
|
|
194
|
+
} else {
|
|
195
|
+
step(3, total, "Save config", "\u2014 nothing to save");
|
|
196
|
+
}
|
|
197
|
+
if (interactive) {
|
|
198
|
+
const installCompletion = await promptConfirm("Install shell completions?", true);
|
|
199
|
+
if (installCompletion) {
|
|
200
|
+
const shell = process.env["SHELL"] || "";
|
|
201
|
+
let completionHint = "";
|
|
202
|
+
if (shell.includes("zsh")) {
|
|
203
|
+
completionHint = "gpc completion zsh > ~/.zsh/completions/_gpc && compinit";
|
|
204
|
+
} else if (shell.includes("bash")) {
|
|
205
|
+
completionHint = "gpc completion bash >> ~/.bashrc && source ~/.bashrc";
|
|
206
|
+
} else if (shell.includes("fish")) {
|
|
207
|
+
completionHint = "gpc completion fish > ~/.config/fish/completions/gpc.fish";
|
|
208
|
+
}
|
|
209
|
+
if (completionHint) {
|
|
210
|
+
console.log(`
|
|
211
|
+
Run: ${completionHint}
|
|
212
|
+
`);
|
|
213
|
+
} else {
|
|
214
|
+
console.log("\n Run: gpc completion --help\n");
|
|
215
|
+
}
|
|
216
|
+
step(4, total, "Shell completion", "\u2713 Instructions printed");
|
|
217
|
+
} else {
|
|
218
|
+
step(4, total, "Shell completion", "\u2014 skipped");
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
step(4, total, "Shell completion", "\u2014 skipped (non-interactive)");
|
|
222
|
+
}
|
|
223
|
+
console.log("");
|
|
224
|
+
try {
|
|
225
|
+
const { spawnSync } = await import("child_process");
|
|
226
|
+
const result = spawnSync("gpc", ["doctor"], {
|
|
227
|
+
stdio: "pipe",
|
|
228
|
+
encoding: "utf-8"
|
|
229
|
+
});
|
|
230
|
+
if (result.error) {
|
|
231
|
+
step(5, total, "Verify (doctor)", "\u2014 gpc not in PATH, run: gpc doctor");
|
|
232
|
+
} else if (result.status === 0) {
|
|
233
|
+
step(5, total, "Verify (doctor)", "\u2713 All checks passed");
|
|
234
|
+
} else {
|
|
235
|
+
step(5, total, "Verify (doctor)", "\u26A0 Some checks need attention");
|
|
236
|
+
if (result.stdout) {
|
|
237
|
+
const lines = result.stdout.split("\n").filter((l) => l.includes("\u2717"));
|
|
238
|
+
for (const line of lines.slice(0, 5)) {
|
|
239
|
+
console.log(` ${line.trim()}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
console.log(" Run: gpc doctor --fix");
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
step(5, total, "Verify (doctor)", "\u2014 run: gpc doctor");
|
|
246
|
+
}
|
|
247
|
+
console.log("\n" + "\u2550".repeat(50));
|
|
248
|
+
if (authOk) {
|
|
249
|
+
console.log("\nReady! Here's what you can do next:\n");
|
|
250
|
+
console.log(" gpc status \u2192 app health snapshot");
|
|
251
|
+
console.log(" gpc releases list \u2192 current tracks and versions");
|
|
252
|
+
console.log(" gpc reviews list \u2192 recent user reviews");
|
|
253
|
+
console.log(" gpc vitals overview \u2192 crash and ANR rates");
|
|
254
|
+
console.log(" gpc publish app.aab \u2192 end-to-end upload and release");
|
|
255
|
+
console.log("\nDocs: https://yasserstudio.github.io/gpc/");
|
|
256
|
+
} else {
|
|
257
|
+
console.log("\nSetup incomplete. Fix the issues above, then run `gpc setup` again.");
|
|
258
|
+
console.log("Need help? Run `gpc doctor` for detailed diagnostics.");
|
|
259
|
+
console.log("Full guide: https://yasserstudio.github.io/gpc/guide/authentication");
|
|
260
|
+
process.exitCode = 1;
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
export {
|
|
265
|
+
registerSetupCommand
|
|
266
|
+
};
|
|
267
|
+
//# sourceMappingURL=setup-GT6IRDVG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/setup.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nconst ANDROID_PACKAGE_RE = /^[a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z][a-zA-Z0-9_]*)+$/;\n\nfunction step(n: number, total: number, label: string, status: string): void {\n const padded = `Step ${n}/${total} ${label}`.padEnd(50);\n console.log(`${padded}${status}`);\n}\n\nexport function registerSetupCommand(program: Command): void {\n program\n .command(\"setup\")\n .description(\"Guided first-time setup: auth, config, verification, and shell completion\")\n .option(\"--auto\", \"Non-interactive setup from env vars and auto-detected credentials\")\n .action(async (options: { auto?: boolean }) => {\n const { isInteractive, promptInput, promptSelect, promptConfirm } = await import(\n \"../prompt.js\"\n );\n const { loadConfig, initConfig, setConfigValue, getUserConfigPath } = await import(\n \"@gpc-cli/config\"\n );\n const { resolveAuth, loadServiceAccountKey } = await import(\"@gpc-cli/auth\");\n\n const interactive = !options.auto && isInteractive(program);\n const total = 5;\n\n console.log(\"\\nGPC — Setup\");\n console.log(\"═\".repeat(50));\n\n if (options.auto) {\n console.log(\"Running in auto mode (non-interactive)\\n\");\n }\n\n // ---------------------------------------------------------------\n // Step 1: Credentials\n // ---------------------------------------------------------------\n let saPath: string | undefined;\n let useAdc = false;\n let authOk = false;\n let email = \"\";\n\n if (options.auto) {\n // Auto mode: try env vars, existing config, then ADC\n try {\n const auth = await resolveAuth();\n email = auth.getClientEmail();\n await auth.getAccessToken();\n authOk = true;\n const method =\n process.env[\"GPC_SERVICE_ACCOUNT\"] || process.env[\"GOOGLE_APPLICATION_CREDENTIALS\"]\n ? \"env\"\n : \"adc\";\n step(1, total, \"Authenticate\", `✓ ${email} (${method})`);\n } catch {\n step(1, total, \"Authenticate\", \"✗ No credentials found\");\n console.log(\"\\n Set GPC_SERVICE_ACCOUNT or GOOGLE_APPLICATION_CREDENTIALS,\");\n console.log(\" or run `gpc setup` interactively.\\n\");\n process.exitCode = 1;\n return;\n }\n } else if (interactive) {\n // Check for existing credentials first\n try {\n const existing = await loadConfig();\n if (existing.auth?.serviceAccount) {\n const auth = await resolveAuth({\n serviceAccountPath: existing.auth.serviceAccount,\n });\n email = auth.getClientEmail();\n await auth.getAccessToken();\n authOk = true;\n step(1, total, \"Authenticate\", `✓ ${email} (existing config)`);\n }\n } catch {\n // No existing config or invalid credentials\n }\n\n if (!authOk) {\n const method = await promptSelect(\n \"\\nAuthentication method:\",\n [\"service-account\", \"adc\"],\n \"service-account\",\n );\n\n if (method === \"adc\") {\n useAdc = true;\n try {\n const auth = await resolveAuth();\n email = auth.getClientEmail();\n await auth.getAccessToken();\n authOk = true;\n step(1, total, \"Authenticate\", `✓ ${email} (ADC)`);\n } catch (err) {\n step(\n 1,\n total,\n \"Authenticate\",\n `✗ ADC failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n console.log(\" Run: gcloud auth application-default login\");\n }\n } else {\n // Service account with retry loop\n const commonPaths = [\n \"~/gpc-service-account.json\",\n \"./service-account.json\",\n \"./gpc-service-account.json\",\n \"~/.config/gpc/service-account.json\",\n ].map((p) => resolve(p.replace(\"~\", process.env[\"HOME\"] || \"\")));\n\n const autoDetected = commonPaths.find((p) => existsSync(p));\n const defaultHint = autoDetected ? ` (found: ${autoDetected})` : \"\";\n\n let attempts = 0;\n while (!authOk && attempts < 3) {\n const input = await promptInput(\n `Path to service account JSON key${defaultHint}`,\n autoDetected,\n );\n if (!input) {\n step(1, total, \"Authenticate\", \"— skipped\");\n break;\n }\n\n const absPath = resolve(input.replace(\"~\", process.env[\"HOME\"] || \"\"));\n if (!existsSync(absPath)) {\n console.log(` File not found: ${absPath}`);\n attempts++;\n continue;\n }\n\n try {\n const key = await loadServiceAccountKey(absPath);\n const auth = await resolveAuth({ serviceAccountPath: absPath });\n await auth.getAccessToken();\n saPath = absPath;\n email = key.client_email;\n authOk = true;\n step(1, total, \"Authenticate\", `✓ ${email}`);\n } catch (err) {\n console.log(\n ` Auth failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n attempts++;\n }\n }\n\n if (!authOk && attempts >= 3) {\n step(1, total, \"Authenticate\", \"✗ Failed after 3 attempts\");\n }\n }\n }\n } else {\n step(1, total, \"Authenticate\", \"— non-interactive, skipped\");\n }\n\n // ---------------------------------------------------------------\n // Step 2: Package name\n // ---------------------------------------------------------------\n let packageName: string | undefined;\n\n if (options.auto) {\n packageName = process.env[\"GPC_APP\"];\n if (packageName) {\n step(2, total, \"Default app\", `✓ ${packageName}`);\n } else {\n step(2, total, \"Default app\", \"— GPC_APP not set (optional)\");\n }\n } else if (interactive) {\n // Check existing config\n try {\n const existing = await loadConfig();\n if (existing.app) {\n const keep = await promptConfirm(`Keep existing app: ${existing.app}?`, true);\n if (keep) {\n packageName = existing.app;\n }\n }\n } catch {\n // No existing config\n }\n\n if (!packageName) {\n const input = await promptInput(\n \"Default package name (e.g. com.example.app, blank to skip)\",\n );\n if (input) {\n if (!ANDROID_PACKAGE_RE.test(input)) {\n console.log(\n ` Warning: \"${input}\" doesn't look like a valid Android package name`,\n );\n }\n packageName = input;\n }\n }\n\n if (packageName) {\n step(2, total, \"Default app\", `✓ ${packageName}`);\n } else {\n step(2, total, \"Default app\", \"— skipped (set later with: gpc config set app <pkg>)\");\n }\n } else {\n step(2, total, \"Default app\", \"— non-interactive, skipped\");\n }\n\n // ---------------------------------------------------------------\n // Step 3: Write config\n // ---------------------------------------------------------------\n const configPayload: Record<string, unknown> = {};\n if (saPath) configPayload[\"auth\"] = { serviceAccount: saPath };\n if (packageName) configPayload[\"app\"] = packageName;\n\n const configPath = getUserConfigPath();\n const configExists = existsSync(configPath);\n\n if (Object.keys(configPayload).length > 0) {\n try {\n if (configExists) {\n if (saPath) await setConfigValue(\"auth.serviceAccount\", saPath);\n if (packageName) await setConfigValue(\"app\", packageName);\n step(3, total, \"Save config\", `✓ Updated ${configPath}`);\n } else {\n await initConfig(configPayload);\n step(3, total, \"Save config\", `✓ Created ${configPath}`);\n }\n } catch (err) {\n step(\n 3,\n total,\n \"Save config\",\n `✗ Failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } else if (configExists) {\n step(3, total, \"Save config\", `✓ Existing config at ${configPath}`);\n } else if (options.auto) {\n step(3, total, \"Save config\", \"— using env vars only\");\n } else {\n step(3, total, \"Save config\", \"— nothing to save\");\n }\n\n // ---------------------------------------------------------------\n // Step 4: Shell completion\n // ---------------------------------------------------------------\n if (interactive) {\n const installCompletion = await promptConfirm(\"Install shell completions?\", true);\n if (installCompletion) {\n const shell = process.env[\"SHELL\"] || \"\";\n let completionHint = \"\";\n if (shell.includes(\"zsh\")) {\n completionHint = \"gpc completion zsh > ~/.zsh/completions/_gpc && compinit\";\n } else if (shell.includes(\"bash\")) {\n completionHint = 'gpc completion bash >> ~/.bashrc && source ~/.bashrc';\n } else if (shell.includes(\"fish\")) {\n completionHint =\n \"gpc completion fish > ~/.config/fish/completions/gpc.fish\";\n }\n\n if (completionHint) {\n console.log(`\\n Run: ${completionHint}\\n`);\n } else {\n console.log(\"\\n Run: gpc completion --help\\n\");\n }\n step(4, total, \"Shell completion\", \"✓ Instructions printed\");\n } else {\n step(4, total, \"Shell completion\", \"— skipped\");\n }\n } else {\n step(4, total, \"Shell completion\", \"— skipped (non-interactive)\");\n }\n\n // ---------------------------------------------------------------\n // Step 5: Doctor verification\n // ---------------------------------------------------------------\n console.log(\"\");\n try {\n const { spawnSync } = await import(\"node:child_process\");\n const result = spawnSync(\"gpc\", [\"doctor\"], {\n stdio: \"pipe\",\n encoding: \"utf-8\",\n });\n\n if (result.error) {\n step(5, total, \"Verify (doctor)\", \"— gpc not in PATH, run: gpc doctor\");\n } else if (result.status === 0) {\n step(5, total, \"Verify (doctor)\", \"✓ All checks passed\");\n } else {\n step(5, total, \"Verify (doctor)\", \"⚠ Some checks need attention\");\n if (result.stdout) {\n const lines = result.stdout.split(\"\\n\").filter((l: string) => l.includes(\"✗\"));\n for (const line of lines.slice(0, 5)) {\n console.log(` ${line.trim()}`);\n }\n }\n console.log(\" Run: gpc doctor --fix\");\n }\n } catch {\n step(5, total, \"Verify (doctor)\", \"— run: gpc doctor\");\n }\n\n // ---------------------------------------------------------------\n // Summary\n // ---------------------------------------------------------------\n console.log(\"\\n\" + \"═\".repeat(50));\n\n if (authOk) {\n console.log(\"\\nReady! Here's what you can do next:\\n\");\n console.log(\" gpc status → app health snapshot\");\n console.log(\" gpc releases list → current tracks and versions\");\n console.log(\" gpc reviews list → recent user reviews\");\n console.log(\" gpc vitals overview → crash and ANR rates\");\n console.log(\" gpc publish app.aab → end-to-end upload and release\");\n console.log(\"\\nDocs: https://yasserstudio.github.io/gpc/\");\n } else {\n console.log(\"\\nSetup incomplete. Fix the issues above, then run `gpc setup` again.\");\n console.log(\"Need help? Run `gpc doctor` for detailed diagnostics.\");\n console.log(\"Full guide: https://yasserstudio.github.io/gpc/guide/authentication\");\n process.exitCode = 1;\n }\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -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.68";
|
|
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-6HUFGIB5.js.map
|
|
@@ -7,7 +7,7 @@ import "./chunk-3SJ6OXCZ.js";
|
|
|
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.68";
|
|
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-U44BGJY5.js.map
|