@gpc-cli/cli 0.9.49 → 0.9.51

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.
Files changed (40) hide show
  1. package/README.md +3 -3
  2. package/dist/{apps-FKD3ZG5X.js → apps-4GP3FD7O.js} +6 -3
  3. package/dist/apps-4GP3FD7O.js.map +1 -0
  4. package/dist/bin.js +2 -2
  5. package/dist/chunk-A7VRCCNS.js +17 -0
  6. package/dist/chunk-A7VRCCNS.js.map +1 -0
  7. package/dist/{chunk-7A4IKNZ5.js → chunk-NUH2MUGV.js} +14 -14
  8. package/dist/{config-JOYJ7TQC.js → config-NTDZA5TN.js} +2 -2
  9. package/dist/docs-GMFN6V4K.js +111 -0
  10. package/dist/docs-GMFN6V4K.js.map +1 -0
  11. package/dist/{doctor-Q4WI432G.js → doctor-M4AHIANK.js} +2 -2
  12. package/dist/{feedback-3JAMSICL.js → feedback-2HDEXLKJ.js} +2 -2
  13. package/dist/index.js +1 -1
  14. package/dist/{listings-7SGQ4SRX.js → listings-LNX6MQYN.js} +15 -11
  15. package/dist/listings-LNX6MQYN.js.map +1 -0
  16. package/dist/{publish-JPTI4EBT.js → publish-2VOCSEH2.js} +17 -3
  17. package/dist/publish-2VOCSEH2.js.map +1 -0
  18. package/dist/{releases-OUJ65774.js → releases-LUAHKIMY.js} +26 -6
  19. package/dist/releases-LUAHKIMY.js.map +1 -0
  20. package/dist/{testers-LSMBXCA2.js → testers-7BZXOJLN.js} +8 -5
  21. package/dist/testers-7BZXOJLN.js.map +1 -0
  22. package/dist/{tracks-DO7C5OSE.js → tracks-I4QZNZ3M.js} +8 -5
  23. package/dist/tracks-I4QZNZ3M.js.map +1 -0
  24. package/dist/{update-QSRQBFDJ.js → update-3UMY3JDP.js} +2 -2
  25. package/dist/{version-V2OQLSVG.js → version-ECUA4EAH.js} +2 -2
  26. package/package.json +5 -5
  27. package/dist/apps-FKD3ZG5X.js.map +0 -1
  28. package/dist/docs-4D2SJ4LY.js +0 -46
  29. package/dist/docs-4D2SJ4LY.js.map +0 -1
  30. package/dist/listings-7SGQ4SRX.js.map +0 -1
  31. package/dist/publish-JPTI4EBT.js.map +0 -1
  32. package/dist/releases-OUJ65774.js.map +0 -1
  33. package/dist/testers-LSMBXCA2.js.map +0 -1
  34. package/dist/tracks-DO7C5OSE.js.map +0 -1
  35. /package/dist/{chunk-7A4IKNZ5.js.map → chunk-NUH2MUGV.js.map} +0 -0
  36. /package/dist/{config-JOYJ7TQC.js.map → config-NTDZA5TN.js.map} +0 -0
  37. /package/dist/{doctor-Q4WI432G.js.map → doctor-M4AHIANK.js.map} +0 -0
  38. /package/dist/{feedback-3JAMSICL.js.map → feedback-2HDEXLKJ.js.map} +0 -0
  39. /package/dist/{update-QSRQBFDJ.js.map → update-3UMY3JDP.js.map} +0 -0
  40. /package/dist/{version-V2OQLSVG.js.map → version-ECUA4EAH.js.map} +0 -0
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ buildCommitOptions
4
+ } from "./chunk-A7VRCCNS.js";
2
5
  import {
3
6
  resolvePackageName
4
7
  } from "./chunk-NQH4G7BI.js";
@@ -80,7 +83,7 @@ function formatDryRunOutput(result, format) {
80
83
  return lines.join("\n");
81
84
  }
82
85
  function registerPublishCommand(program) {
83
- program.command("publish <file>").description("Validate, upload, and release in one step").option("--track <track>", "Target track", "internal").option("--rollout <percent>", "Staged rollout percentage (1-100)").option("--notes <text>", "Release notes (en-US)").option("--notes-dir <dir>", "Read release notes from directory (<dir>/<lang>.txt)").option("--notes-from-git", "Generate release notes from git commit history").option("--since <ref>", "Git ref to start from (tag, SHA) \u2014 used with --notes-from-git").option("--name <name>", "Release name").option("--mapping <file>", "ProGuard/R8 mapping file for deobfuscation").option("--retry-log <path>", "Write retry log entries to file (JSONL)").action(async (file, options) => {
86
+ program.command("publish <file>").description("Validate, upload, and release in one step").option("--track <track>", "Target track", "internal").option("--rollout <percent>", "Staged rollout percentage (1-100)").option("--notes <text>", "Release notes (en-US)").option("--notes-dir <dir>", "Read release notes from directory (<dir>/<lang>.txt)").option("--notes-from-git", "Generate release notes from git commit history").option("--since <ref>", "Git ref to start from (tag, SHA) \u2014 used with --notes-from-git").option("--name <name>", "Release name").option("--mapping <file>", "ProGuard/R8 mapping file for deobfuscation").option("--retry-log <path>", "Write retry log entries to file (JSONL)").option("--mapping-type <type>", "Deobfuscation file type: proguard or nativeCode", "proguard").option("--device-tier-config <id>", "Device tier config ID (or LATEST)").option("--changes-not-sent-for-review", "Commit changes without sending for review (required for rejected apps)").option("--error-if-in-review", "Fail if changes are already in review instead of cancelling them").action(async (file, options) => {
84
87
  try {
85
88
  await stat(file);
86
89
  } catch {
@@ -133,6 +136,14 @@ function registerPublishCommand(program) {
133
136
  );
134
137
  }
135
138
  }
139
+ if (options.mappingType && options.mappingType !== "proguard" && options.mappingType !== "nativeCode") {
140
+ throw new GpcError(
141
+ `--mapping-type must be "proguard" or "nativeCode" (got: "${options.mappingType}")`,
142
+ "PUBLISH_USAGE_ERROR",
143
+ 2,
144
+ "Use --mapping-type proguard (default) for ProGuard/R8 maps, or --mapping-type nativeCode for native debug symbols."
145
+ );
146
+ }
136
147
  if (options.notesFromGit) {
137
148
  const gitNotes = await generateNotesFromGit({ since: options.since });
138
149
  options.notes = gitNotes.text;
@@ -179,7 +190,10 @@ function registerPublishCommand(program) {
179
190
  notes: options.notes,
180
191
  notesDir: options.notesDir,
181
192
  releaseName: options.name,
182
- mappingFile: options.mapping
193
+ mappingFile: options.mapping,
194
+ mappingFileType: options.mappingType,
195
+ deviceTierConfigId: options.deviceTierConfig,
196
+ commitOptions: buildCommitOptions(options)
183
197
  });
184
198
  if (!result.upload) {
185
199
  console.log(formatValidationOutput(result, format));
@@ -204,4 +218,4 @@ function registerPublishCommand(program) {
204
218
  export {
205
219
  registerPublishCommand
206
220
  };
207
- //# sourceMappingURL=publish-JPTI4EBT.js.map
221
+ //# sourceMappingURL=publish-2VOCSEH2.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 } 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// ---------------------------------------------------------------------------\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(\"--changes-not-sent-for-review\", \"Commit changes without sending for review (required for rejected apps)\")\n .option(\"--error-if-in-review\", \"Fail if changes are already in review instead of cancelling them\")\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 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 (options.mappingType && options.mappingType !== \"proguard\" && options.mappingType !== \"nativeCode\") {\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 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 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 console.log(formatPublishOutput(result as PublishResult, format));\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":[]}
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ buildCommitOptions
4
+ } from "./chunk-A7VRCCNS.js";
2
5
  import {
3
6
  isDryRun,
4
7
  printDryRun
@@ -67,7 +70,7 @@ function registerReleasesCommands(program) {
67
70
  "--timeout <ms>",
68
71
  "Upload timeout in milliseconds (auto-scales with file size by default)",
69
72
  parseInt
70
- ).option("--status <status>", "Release status: completed, inProgress, draft, halted", "completed").action(async (file, options) => {
73
+ ).option("--status <status>", "Release status: completed, inProgress, draft, halted", "completed").option("--mapping-type <type>", "Deobfuscation file type: proguard or nativeCode", "proguard").option("--device-tier-config <id>", "Device tier config ID (or LATEST)").option("--changes-not-sent-for-review", "Commit changes without sending for review (required for rejected apps)").option("--error-if-in-review", "Fail if changes are already in review instead of cancelling them").action(async (file, options) => {
71
74
  try {
72
75
  await stat(file);
73
76
  } catch {
@@ -134,6 +137,14 @@ function registerReleasesCommands(program) {
134
137
  );
135
138
  }
136
139
  }
140
+ if (options.mappingType && options.mappingType !== "proguard" && options.mappingType !== "nativeCode") {
141
+ throw new GpcError(
142
+ `--mapping-type must be "proguard" or "nativeCode" (got: "${options.mappingType}")`,
143
+ "RELEASES_USAGE_ERROR",
144
+ 2,
145
+ "Use --mapping-type proguard (default) for ProGuard/R8 maps, or --mapping-type nativeCode for native debug symbols."
146
+ );
147
+ }
137
148
  const { size: fileSize } = await stat(file);
138
149
  const jsonMode = format === "json";
139
150
  const client = await getClient(config, options.retryLog, options.timeout);
@@ -162,6 +173,8 @@ function registerReleasesCommands(program) {
162
173
  track: options.track,
163
174
  status: options.status,
164
175
  userFraction: options.rollout ? Number(options.rollout) / 100 : void 0,
176
+ mappingFileType: options.mappingType,
177
+ deviceTierConfigId: options.deviceTierConfig,
165
178
  dryRun: true
166
179
  });
167
180
  console.log(formatOutput(result, format));
@@ -197,7 +210,10 @@ function registerReleasesCommands(program) {
197
210
  releaseNotes,
198
211
  releaseName: options.name,
199
212
  mappingFile: options.mapping,
200
- onUploadProgress
213
+ mappingFileType: options.mappingType,
214
+ deviceTierConfigId: options.deviceTierConfig,
215
+ onUploadProgress,
216
+ commitOptions: buildCommitOptions(options)
201
217
  });
202
218
  if (showProgress) {
203
219
  process.stderr.write(`\r \u2713 Uploaded ${basename(file)} ${sizeMB} MB\x1B[K
@@ -253,7 +269,7 @@ function registerReleasesCommands(program) {
253
269
  await maybePaginate(formatOutput(sorted, format));
254
270
  }
255
271
  });
256
- releases.command("promote").description("Promote a release from one track to another").option("--from <track>", "Source track").option("--to <track>", "Target track").option("--rollout <percent>", "Staged rollout percentage").option("--notes <text>", "Release notes").option("--copy-notes-from <track>", "Copy release notes from another track").option("--status <status>", "Release status: completed, inProgress, draft, halted").action(async (options) => {
272
+ releases.command("promote").description("Promote a release from one track to another").option("--from <track>", "Source track").option("--to <track>", "Target track").option("--rollout <percent>", "Staged rollout percentage").option("--notes <text>", "Release notes").option("--copy-notes-from <track>", "Copy release notes from another track").option("--status <status>", "Release status: completed, inProgress, draft, halted").option("--changes-not-sent-for-review", "Commit changes without sending for review (required for rejected apps)").option("--error-if-in-review", "Fail if changes are already in review instead of cancelling them").action(async (options) => {
257
273
  if (options.notes && options.copyNotesFrom) {
258
274
  throw new GpcError(
259
275
  "Cannot combine --notes and --copy-notes-from. Use only one.",
@@ -327,7 +343,8 @@ function registerReleasesCommands(program) {
327
343
  const result = await promoteRelease(client, packageName, options.from, options.to, {
328
344
  status: options.status,
329
345
  userFraction: options.rollout ? Number(options.rollout) / 100 : void 0,
330
- releaseNotes
346
+ releaseNotes,
347
+ commitOptions: buildCommitOptions(options)
331
348
  });
332
349
  console.log(formatOutput(result, format));
333
350
  });
@@ -338,6 +355,8 @@ function registerReleasesCommands(program) {
338
355
  cmd.option("--to <percent>", "New rollout percentage");
339
356
  cmd.option("--vitals-gate", "Halt rollout if crash rate exceeds configured threshold");
340
357
  }
358
+ cmd.option("--changes-not-sent-for-review", "Commit changes without sending for review (required for rejected apps)");
359
+ cmd.option("--error-if-in-review", "Fail if changes are already in review instead of cancelling them");
341
360
  cmd.action(async (options) => {
342
361
  const config = await loadConfig();
343
362
  const packageName = resolvePackageName(program.opts()["app"], config);
@@ -404,7 +423,8 @@ function registerReleasesCommands(program) {
404
423
  packageName,
405
424
  options.track,
406
425
  action,
407
- options.to ? Number(options.to) / 100 : void 0
426
+ options.to ? Number(options.to) / 100 : void 0,
427
+ buildCommitOptions(options)
408
428
  );
409
429
  if (action === "increase" && options.vitalsGate) {
410
430
  const threshold = config.vitals?.thresholds?.crashRate;
@@ -548,4 +568,4 @@ function registerReleasesCommands(program) {
548
568
  export {
549
569
  registerReleasesCommands
550
570
  };
551
- //# sourceMappingURL=releases-OUJ65774.js.map
571
+ //# sourceMappingURL=releases-LUAHKIMY.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 { 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(\"--status <status>\", \"Release status: completed, inProgress, draft, halted\", \"completed\")\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(\"--changes-not-sent-for-review\", \"Commit changes without sending for review (required for rejected apps)\")\n .option(\"--error-if-in-review\", \"Fail if changes are already in review instead of cancelling them\")\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 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 (options.mappingType && options.mappingType !== \"proguard\" && options.mappingType !== \"nativeCode\") {\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 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(\"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(\"--changes-not-sent-for-review\", \"Commit changes without sending for review (required for rejected apps)\")\n .option(\"--error-if-in-review\", \"Fail if changes are already in review instead of cancelling them\")\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: { rollout: options.rollout, ...(options.status && { status: options.status }) },\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(\"--changes-not-sent-for-review\", \"Commit changes without sending for review (required for rejected apps)\");\n cmd.option(\"--error-if-in-review\", \"Fail if changes are already in review instead of cancelling them\");\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"]}
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ buildCommitOptions
4
+ } from "./chunk-A7VRCCNS.js";
2
5
  import {
3
6
  getClient,
4
7
  resolvePackageName
@@ -61,7 +64,7 @@ function registerTestersCommands(program) {
61
64
  console.log(formatOutput(result, format));
62
65
  }
63
66
  });
64
- testers.command("add <emails...>").description("Add testers (Google Group emails) to a track").option("--track <track>", "Track name (e.g., internal, alpha, beta)").action(async (emails, options) => {
67
+ testers.command("add <emails...>").description("Add testers (Google Group emails) to a track").option("--track <track>", "Track name (e.g., internal, alpha, beta)").option("--changes-not-sent-for-review", "Commit changes without sending for review").option("--error-if-in-review", "Fail if changes are already in review").action(async (emails, options) => {
65
68
  const config = await loadConfig();
66
69
  const packageName = resolvePackageName(program.opts()["app"], config);
67
70
  const format = getOutputFormat(program, config);
@@ -89,10 +92,10 @@ function registerTestersCommands(program) {
89
92
  return;
90
93
  }
91
94
  const client = await getClient(config);
92
- const result = await addTesters(client, packageName, options.track, emails);
95
+ const result = await addTesters(client, packageName, options.track, emails, buildCommitOptions(options));
93
96
  console.log(formatOutput(result, format));
94
97
  });
95
- testers.command("remove <emails...>").description("Remove testers (Google Group emails) from a track").option("--track <track>", "Track name (e.g., internal, alpha, beta)").action(async (emails, options) => {
98
+ testers.command("remove <emails...>").description("Remove testers (Google Group emails) from a track").option("--track <track>", "Track name (e.g., internal, alpha, beta)").option("--changes-not-sent-for-review", "Commit changes without sending for review").option("--error-if-in-review", "Fail if changes are already in review").action(async (emails, options) => {
96
99
  const config = await loadConfig();
97
100
  const packageName = resolvePackageName(program.opts()["app"], config);
98
101
  const format = getOutputFormat(program, config);
@@ -121,7 +124,7 @@ function registerTestersCommands(program) {
121
124
  return;
122
125
  }
123
126
  const client = await getClient(config);
124
- const result = await removeTesters(client, packageName, options.track, emails);
127
+ const result = await removeTesters(client, packageName, options.track, emails, buildCommitOptions(options));
125
128
  console.log(formatOutput(result, format));
126
129
  });
127
130
  testers.command("import").description("Import testers from a CSV file").option("--track <track>", "Track name (e.g., internal, alpha, beta)").option("--file <path>", "CSV file with email addresses").action(async (options) => {
@@ -167,4 +170,4 @@ function registerTestersCommands(program) {
167
170
  export {
168
171
  registerTestersCommands
169
172
  };
170
- //# sourceMappingURL=testers-LSMBXCA2.js.map
173
+ //# sourceMappingURL=testers-7BZXOJLN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/testers.ts"],"sourcesContent":["import { resolvePackageName, getClient } from \"../resolve.js\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport {\n listTesters,\n addTesters,\n removeTesters,\n importTestersFromCsv,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport { isInteractive, requireOption, requireConfirm } from \"../prompt.js\";\nimport { buildCommitOptions } from \"../commit-options.js\";\n\n\n\nexport function registerTestersCommands(program: Command): void {\n const testers = program.command(\"testers\").description(\"Manage testers and tester groups\");\n\n testers\n .command(\"list\")\n .description(\"List testers for a track\")\n .option(\"--track <track>\", \"Track name (e.g., internal, alpha, beta)\")\n .option(\"--sort <field>\", \"Sort by field (prefix with - for descending, e.g., email or -email)\")\n .option(\"--limit <n>\", \"Maximum results to return\")\n .option(\"--next-page <token>\", \"Pagination token for next page\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const interactive = isInteractive(program);\n\n options.track = await requireOption(\n \"track\",\n options.track,\n {\n message: \"Track:\",\n choices: [\"internal\", \"alpha\", \"beta\"],\n },\n interactive,\n );\n\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await listTesters(client, packageName, options.track);\n if (options.sort && result.googleGroups) {\n const descending = options.sort.startsWith(\"-\");\n result.googleGroups = [...result.googleGroups].sort((a: string, b: string) =>\n descending ? b.localeCompare(a) : a.localeCompare(b),\n );\n }\n if (format !== \"json\") {\n const groups = (result.googleGroups || []) as string[];\n const rows = groups.map((g: string) => ({ googleGroup: g }));\n if (rows.length > 0) {\n console.log(formatOutput(rows, format));\n } else {\n console.log(\"No testers found.\");\n }\n } else {\n console.log(formatOutput(result, format));\n }\n });\n\n testers\n .command(\"add <emails...>\")\n .description(\"Add testers (Google Group emails) to a track\")\n .option(\"--track <track>\", \"Track name (e.g., internal, alpha, beta)\")\n .option(\"--changes-not-sent-for-review\", \"Commit changes without sending for review\")\n .option(\"--error-if-in-review\", \"Fail if changes are already in review\")\n .action(async (emails: string[], 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\n options.track = await requireOption(\n \"track\",\n options.track,\n {\n message: \"Track:\",\n choices: [\"internal\", \"alpha\", \"beta\"],\n },\n interactive,\n );\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"testers add\",\n action: \"add testers to\",\n target: options.track,\n details: { emails },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n const result = await addTesters(client, packageName, options.track, emails, buildCommitOptions(options));\n console.log(formatOutput(result, format));\n });\n\n testers\n .command(\"remove <emails...>\")\n .description(\"Remove testers (Google Group emails) from a track\")\n .option(\"--track <track>\", \"Track name (e.g., internal, alpha, beta)\")\n .option(\"--changes-not-sent-for-review\", \"Commit changes without sending for review\")\n .option(\"--error-if-in-review\", \"Fail if changes are already in review\")\n .action(async (emails: string[], 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\n options.track = await requireOption(\n \"track\",\n options.track,\n {\n message: \"Track:\",\n choices: [\"internal\", \"alpha\", \"beta\"],\n },\n interactive,\n );\n\n await requireConfirm(`Remove ${emails.length} tester(s) from ${options.track}?`, program);\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"testers remove\",\n action: \"remove testers from\",\n target: options.track,\n details: { emails },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n const result = await removeTesters(client, packageName, options.track, emails, buildCommitOptions(options));\n console.log(formatOutput(result, format));\n });\n\n testers\n .command(\"import\")\n .description(\"Import testers from a CSV file\")\n .option(\"--track <track>\", \"Track name (e.g., internal, alpha, beta)\")\n .option(\"--file <path>\", \"CSV file with email addresses\")\n .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\n options.track = await requireOption(\n \"track\",\n options.track,\n {\n message: \"Track:\",\n choices: [\"internal\", \"alpha\", \"beta\"],\n },\n interactive,\n );\n\n options.file = await requireOption(\n \"file\",\n options.file,\n {\n message: \"CSV file path:\",\n },\n interactive,\n );\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"testers import\",\n action: \"import testers to\",\n target: options.track,\n details: { file: options.file },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n const result = await importTestersFromCsv(client, packageName, options.track, options.file);\n console.log(formatOutput({ added: result.added, testers: result.testers }, format));\n });\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ buildCommitOptions
4
+ } from "./chunk-A7VRCCNS.js";
2
5
  import {
3
6
  isDryRun,
4
7
  printDryRun
@@ -39,7 +42,7 @@ function registerTracksCommands(program) {
39
42
  }));
40
43
  await maybePaginate(formatOutput(summary, format));
41
44
  });
42
- tracks.command("create <name>").description("Create a custom track").action(async (name) => {
45
+ tracks.command("create <name>").description("Create a custom track").option("--changes-not-sent-for-review", "Commit changes without sending for review").option("--error-if-in-review", "Fail if changes are already in review").action(async (name, options) => {
43
46
  const config = await loadConfig();
44
47
  const packageName = program.opts()["app"] || config.app;
45
48
  if (!packageName) {
@@ -61,10 +64,10 @@ function registerTracksCommands(program) {
61
64
  }
62
65
  const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });
63
66
  const client = createApiClient({ auth });
64
- const track = await createTrack(client, packageName, name);
67
+ const track = await createTrack(client, packageName, name, buildCommitOptions(options));
65
68
  console.log(formatOutput(track, format));
66
69
  });
67
- tracks.command("update <name>").description("Update track configuration from a JSON file").requiredOption("--file <path>", "Path to JSON config file").action(async (name, options) => {
70
+ tracks.command("update <name>").description("Update track configuration from a JSON file").requiredOption("--file <path>", "Path to JSON config file").option("--changes-not-sent-for-review", "Commit changes without sending for review").option("--error-if-in-review", "Fail if changes are already in review").action(async (name, options) => {
68
71
  const config = await loadConfig();
69
72
  const packageName = program.opts()["app"] || config.app;
70
73
  if (!packageName) {
@@ -88,11 +91,11 @@ function registerTracksCommands(program) {
88
91
  const trackConfig = JSON.parse(raw);
89
92
  const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });
90
93
  const client = createApiClient({ auth });
91
- const track = await updateTrackConfig(client, packageName, name, trackConfig);
94
+ const track = await updateTrackConfig(client, packageName, name, trackConfig, buildCommitOptions(options));
92
95
  console.log(formatOutput(track, format));
93
96
  });
94
97
  }
95
98
  export {
96
99
  registerTracksCommands
97
100
  };
98
- //# sourceMappingURL=tracks-DO7C5OSE.js.map
101
+ //# sourceMappingURL=tracks-I4QZNZ3M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/tracks.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient } from \"@gpc-cli/api\";\nimport { listTracks, createTrack, updateTrackConfig, GpcError } from \"@gpc-cli/core\";\nimport { formatOutput, maybePaginate } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport { buildCommitOptions } from \"../commit-options.js\";\n\nexport function registerTracksCommands(program: Command): void {\n const tracks = program.command(\"tracks\").description(\"Manage tracks\");\n\n tracks\n .command(\"list\")\n .description(\"List all tracks\")\n .option(\"--limit <n>\", \"Maximum results to return\")\n .option(\"--next-page <token>\", \"Pagination token for next page\")\n .action(async (_options) => {\n const config = await loadConfig();\n const packageName = program.opts()[\"app\"] || config.app;\n if (!packageName) {\n throw new GpcError(\n \"No package name. Use --app <package> or gpc config set app <package>\",\n \"MISSING_PACKAGE\",\n 2,\n \"gpc config set app com.example.app\",\n );\n }\n\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n const client = createApiClient({ auth });\n const trackList = await listTracks(client, packageName);\n const format = getOutputFormat(program, config);\n\n const summary = trackList.map((t) => ({\n track: t.track,\n releases: t.releases?.length || 0,\n latestStatus: t.releases?.[0]?.status || \"none\",\n latestVersion: t.releases?.[0]?.versionCodes?.[0] || \"-\",\n }));\n\n await maybePaginate(formatOutput(summary, format));\n });\n\n tracks\n .command(\"create <name>\")\n .description(\"Create a custom track\")\n .option(\"--changes-not-sent-for-review\", \"Commit changes without sending for review\")\n .option(\"--error-if-in-review\", \"Fail if changes are already in review\")\n .action(async (name: string, options: Record<string, unknown>) => {\n const config = await loadConfig();\n const packageName = program.opts()[\"app\"] || config.app;\n if (!packageName) {\n throw new GpcError(\n \"No package name. Use --app <package> or gpc config set app <package>\",\n \"MISSING_PACKAGE\",\n 2,\n \"gpc config set app com.example.app\",\n );\n }\n\n const format = getOutputFormat(program, config);\n\n if (isDryRun(program)) {\n printDryRun(\n { command: \"tracks create\", action: \"create custom track\", target: name },\n format,\n formatOutput,\n );\n return;\n }\n\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n const client = createApiClient({ auth });\n const track = await createTrack(client, packageName, name, buildCommitOptions(options));\n console.log(formatOutput(track, format));\n });\n\n tracks\n .command(\"update <name>\")\n .description(\"Update track configuration from a JSON file\")\n .requiredOption(\"--file <path>\", \"Path to JSON config file\")\n .option(\"--changes-not-sent-for-review\", \"Commit changes without sending for review\")\n .option(\"--error-if-in-review\", \"Fail if changes are already in review\")\n .action(async (name: string, options: { file: string } & Record<string, unknown>) => {\n const config = await loadConfig();\n const packageName = program.opts()[\"app\"] || config.app;\n if (!packageName) {\n throw new GpcError(\n \"No package name. Use --app <package> or gpc config set app <package>\",\n \"MISSING_PACKAGE\",\n 2,\n \"gpc config set app com.example.app\",\n );\n }\n\n const format = getOutputFormat(program, config);\n\n if (isDryRun(program)) {\n printDryRun(\n { command: \"tracks update\", action: \"update track config from\", target: options.file },\n format,\n formatOutput,\n );\n return;\n }\n\n const raw = await readFile(options.file, \"utf-8\");\n const trackConfig = JSON.parse(raw) as Record<string, unknown>;\n\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n const client = createApiClient({ auth });\n const track = await updateTrackConfig(client, packageName, name, trackConfig, buildCommitOptions(options));\n console.log(formatOutput(track, format));\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.49";
20
+ const currentVersion = "0.9.51";
21
21
  if (currentVersion === "0.0.0") {
22
22
  if (jsonMode) {
23
23
  console.log(
@@ -175,4 +175,4 @@ Run: gpc update`);
175
175
  export {
176
176
  registerUpdateCommand
177
177
  };
178
- //# sourceMappingURL=update-QSRQBFDJ.js.map
178
+ //# sourceMappingURL=update-3UMY3JDP.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.49";
10
+ const version = "0.9.51";
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-V2OQLSVG.js.map
28
+ //# sourceMappingURL=version-ECUA4EAH.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gpc-cli/cli",
3
- "version": "0.9.49",
3
+ "version": "0.9.51",
4
4
  "description": "GPC — Google Play Console CLI. 204 API endpoints, one tool.",
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.26",
22
+ "@gpc-cli/api": "1.0.27",
23
+ "@gpc-cli/auth": "0.9.11",
23
24
  "@gpc-cli/config": "0.9.11",
24
- "@gpc-cli/core": "0.9.40",
25
- "@gpc-cli/plugin-sdk": "0.9.7",
26
- "@gpc-cli/auth": "0.9.11"
25
+ "@gpc-cli/core": "0.9.42",
26
+ "@gpc-cli/plugin-sdk": "0.9.7"
27
27
  },
28
28
  "keywords": [
29
29
  "google-play",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/apps.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient } from \"@gpc-cli/api\";\nimport { getAppInfo, updateAppDetails, GpcError } from \"@gpc-cli/core\";\nimport { formatOutput } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\n\nexport function registerAppsCommands(program: Command): void {\n const apps = program.command(\"apps\").description(\"Manage applications\");\n\n apps\n .command(\"info [package]\")\n .description(\"Show app details\")\n .action(async (packageArg?: string) => {\n const config = await loadConfig();\n const packageName = packageArg || config.app;\n\n if (!packageName) {\n throw new GpcError(\n \"No package name provided. Usage: gpc apps info <package>\",\n \"MISSING_PACKAGE\",\n 2,\n \"gpc config set app com.example.app\",\n );\n }\n\n const auth = await resolveAuth({\n serviceAccountPath: config.auth?.serviceAccount,\n });\n const client = createApiClient({ auth });\n const info = await getAppInfo(client, packageName);\n const format = getOutputFormat(program, config);\n console.log(formatOutput(info, format));\n });\n\n apps\n .command(\"update\")\n .description(\"Update app details\")\n .option(\"--email <email>\", \"Contact email\")\n .option(\"--phone <phone>\", \"Contact phone\")\n .option(\"--website <url>\", \"Contact website\")\n .option(\"--default-lang <lang>\", \"Default language\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = options[\"app\"] || program.opts()[\"app\"] || config.app;\n\n if (!packageName) {\n throw new GpcError(\n \"No package name provided. Usage: gpc apps update --email user@example.com\",\n \"MISSING_PACKAGE\",\n 2,\n \"gpc config set app com.example.app\",\n );\n }\n\n const data: Record<string, string> = {};\n if (options[\"email\"]) data[\"contactEmail\"] = options[\"email\"];\n if (options[\"phone\"]) data[\"contactPhone\"] = options[\"phone\"];\n if (options[\"website\"]) data[\"contactWebsite\"] = options[\"website\"];\n if (options[\"defaultLang\"]) data[\"defaultLanguage\"] = options[\"defaultLang\"];\n\n if (Object.keys(data).length === 0) {\n throw new GpcError(\n \"Provide at least one field to update (--email, --phone, --website, --default-lang).\",\n \"MISSING_OPTION\",\n 2,\n );\n }\n\n const format = getOutputFormat(program, config);\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"apps update\",\n action: \"update app details for\",\n target: packageName,\n details: data,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const auth = await resolveAuth({\n serviceAccountPath: config.auth?.serviceAccount,\n });\n const client = createApiClient({ auth });\n const result = await updateAppDetails(client, packageName, data);\n console.log(formatOutput(result, format));\n });\n\n apps\n .command(\"list\")\n .description(\"List configured applications\")\n .option(\"--limit <n>\", \"Maximum results to return\")\n .option(\"--next-page <token>\", \"Pagination token for next page\")\n .action(async (_options) => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n\n if (config.app) {\n const apps = [{ packageName: config.app, source: \"config\" }];\n console.log(formatOutput(apps, format));\n } else {\n console.log(\"No apps configured.\");\n console.log(\"\");\n console.log(\"Set a default app:\");\n console.log(\" gpc config set app com.example.myapp\");\n console.log(\"\");\n console.log(\"Or use the --app flag:\");\n console.log(\" gpc apps info --app com.example.myapp\");\n }\n });\n}\n"],"mappings":"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","names":["apps"]}
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/commands/docs.ts
4
- import * as cp from "child_process";
5
- var PAGE_MAP = {
6
- releases: "commands/releases",
7
- status: "commands/status",
8
- vitals: "commands/vitals",
9
- reviews: "commands/reviews",
10
- listings: "commands/listings",
11
- subscriptions: "commands/subscriptions",
12
- bundle: "commands/bundle",
13
- users: "commands/users",
14
- audit: "commands/audit",
15
- config: "commands/config",
16
- doctor: "commands/doctor",
17
- publish: "commands/publish"
18
- };
19
- var BASE = "https://yasserstudio.github.io/gpc/";
20
- function registerDocsCommand(program) {
21
- program.command("docs [topic]").description("Open documentation in browser").option("--list", "List available documentation topics").action((topic, opts) => {
22
- if (opts?.list) {
23
- console.log("Available topics:");
24
- for (const key of Object.keys(PAGE_MAP)) console.log(` gpc docs ${key}`);
25
- return;
26
- }
27
- const path = topic ? PAGE_MAP[topic] : void 0;
28
- if (topic && !path) {
29
- const err = new Error(`Unknown topic "${topic}".`);
30
- Object.assign(err, { code: "USAGE_ERROR", exitCode: 2, suggestion: "Run: gpc docs --list" });
31
- throw err;
32
- }
33
- const url = path ? `${BASE}${path}` : BASE;
34
- const platform = process.platform;
35
- const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
36
- cp.execFile(cmd, [url], (error) => {
37
- if (error) {
38
- console.log(`Open in your browser: ${url}`);
39
- }
40
- });
41
- });
42
- }
43
- export {
44
- registerDocsCommand
45
- };
46
- //# sourceMappingURL=docs-4D2SJ4LY.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/docs.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport * as cp from \"node:child_process\";\n\nconst PAGE_MAP: Record<string, string> = {\n releases: \"commands/releases\",\n status: \"commands/status\",\n vitals: \"commands/vitals\",\n reviews: \"commands/reviews\",\n listings: \"commands/listings\",\n subscriptions: \"commands/subscriptions\",\n bundle: \"commands/bundle\",\n users: \"commands/users\",\n audit: \"commands/audit\",\n config: \"commands/config\",\n doctor: \"commands/doctor\",\n publish: \"commands/publish\",\n};\n\nconst BASE = \"https://yasserstudio.github.io/gpc/\";\n\nexport function registerDocsCommand(program: Command): void {\n program\n .command(\"docs [topic]\")\n .description(\"Open documentation in browser\")\n .option(\"--list\", \"List available documentation topics\")\n .action((topic?: string, opts?: { list?: boolean }) => {\n if (opts?.list) {\n console.log(\"Available topics:\");\n for (const key of Object.keys(PAGE_MAP)) console.log(` gpc docs ${key}`);\n return;\n }\n const path = topic ? PAGE_MAP[topic] : undefined;\n if (topic && !path) {\n const err = new Error(`Unknown topic \"${topic}\".`);\n Object.assign(err, { code: \"USAGE_ERROR\", exitCode: 2, suggestion: \"Run: gpc docs --list\" });\n throw err;\n }\n const url = path ? `${BASE}${path}` : BASE;\n const platform = process.platform;\n const cmd = platform === \"darwin\" ? \"open\" : platform === \"win32\" ? \"start\" : \"xdg-open\";\n cp.execFile(cmd, [url], (error) => {\n if (error) {\n console.log(`Open in your browser: ${url}`);\n }\n });\n });\n}\n"],"mappings":";;;AACA,YAAY,QAAQ;AAEpB,IAAM,WAAmC;AAAA,EACvC,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AACX;AAEA,IAAM,OAAO;AAEN,SAAS,oBAAoB,SAAwB;AAC1D,UACG,QAAQ,cAAc,EACtB,YAAY,+BAA+B,EAC3C,OAAO,UAAU,qCAAqC,EACtD,OAAO,CAAC,OAAgB,SAA8B;AACrD,QAAI,MAAM,MAAM;AACd,cAAQ,IAAI,mBAAmB;AAC/B,iBAAW,OAAO,OAAO,KAAK,QAAQ,EAAG,SAAQ,IAAI,cAAc,GAAG,EAAE;AACxE;AAAA,IACF;AACA,UAAM,OAAO,QAAQ,SAAS,KAAK,IAAI;AACvC,QAAI,SAAS,CAAC,MAAM;AAClB,YAAM,MAAM,IAAI,MAAM,kBAAkB,KAAK,IAAI;AACjD,aAAO,OAAO,KAAK,EAAE,MAAM,eAAe,UAAU,GAAG,YAAY,uBAAuB,CAAC;AAC3F,YAAM;AAAA,IACR;AACA,UAAM,MAAM,OAAO,GAAG,IAAI,GAAG,IAAI,KAAK;AACtC,UAAM,WAAW,QAAQ;AACzB,UAAM,MAAM,aAAa,WAAW,SAAS,aAAa,UAAU,UAAU;AAC9E,IAAG,YAAS,KAAK,CAAC,GAAG,GAAG,CAAC,UAAU;AACjC,UAAI,OAAO;AACT,gBAAQ,IAAI,yBAAyB,GAAG,EAAE;AAAA,MAC5C;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACL;","names":[]}