@gpc-cli/cli 0.9.28 → 0.9.29

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.
@@ -19,6 +19,22 @@ import {
19
19
  clearAuditLog,
20
20
  formatOutput
21
21
  } from "@gpc-cli/core";
22
+ function formatAuditTimestamp(iso) {
23
+ const date = new Date(iso);
24
+ const now = /* @__PURE__ */ new Date();
25
+ const diffMs = now.getTime() - date.getTime();
26
+ const diffMin = Math.floor(diffMs / 6e4);
27
+ if (diffMin < 60) return diffMin < 1 ? "just now" : `${diffMin} min ago`;
28
+ const diffHr = Math.floor(diffMin / 60);
29
+ if (diffHr < 24) return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}:${String(date.getSeconds()).padStart(2, "0")}`;
30
+ const diffDays = Math.floor(diffHr / 24);
31
+ if (diffDays < 7) {
32
+ const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
33
+ return `${days[date.getDay()]} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
34
+ }
35
+ const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
36
+ return `${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
37
+ }
22
38
  function registerAuditCommands(program) {
23
39
  const audit = program.command("audit").description("Query and manage audit logs");
24
40
  audit.command("list").description("List recent audit events").option("--limit <n>", "Maximum events to show", parseInt, 50).option("--since <date>", "Show events since date (ISO 8601)").option("--command <name>", "Filter by command name").action(async (options) => {
@@ -37,7 +53,7 @@ function registerAuditCommands(program) {
37
53
  }
38
54
  if (format !== "json") {
39
55
  const rows = events.map((e) => ({
40
- timestamp: e.timestamp,
56
+ timestamp: formatAuditTimestamp(e.timestamp),
41
57
  command: e.command,
42
58
  app: e.app || "-",
43
59
  success: e.success !== void 0 ? String(e.success) : "-",
@@ -64,7 +80,7 @@ function registerAuditCommands(program) {
64
80
  }
65
81
  if (format !== "json") {
66
82
  const rows = events.map((e) => ({
67
- timestamp: e.timestamp,
83
+ timestamp: formatAuditTimestamp(e.timestamp),
68
84
  command: e.command,
69
85
  app: e.app || "-",
70
86
  success: e.success !== void 0 ? String(e.success) : "-"
@@ -105,4 +121,4 @@ function registerAuditCommands(program) {
105
121
  export {
106
122
  registerAuditCommands
107
123
  };
108
- //# sourceMappingURL=audit-QP75CEGB.js.map
124
+ //# sourceMappingURL=audit-A4BP27DN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/audit.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { getConfigDir } from \"@gpc-cli/config\";\nimport {\n initAudit,\n listAuditEvents,\n searchAuditEvents,\n clearAuditLog,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun } from \"../dry-run.js\";\nimport { requireConfirm } from \"../prompt.js\";\n\nfunction formatAuditTimestamp(iso: string): string {\n const date = new Date(iso);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMin = Math.floor(diffMs / 60000);\n if (diffMin < 60) return diffMin < 1 ? \"just now\" : `${diffMin} min ago`;\n const diffHr = Math.floor(diffMin / 60);\n if (diffHr < 24) return `${String(date.getHours()).padStart(2, \"0\")}:${String(date.getMinutes()).padStart(2, \"0\")}:${String(date.getSeconds()).padStart(2, \"0\")}`;\n const diffDays = Math.floor(diffHr / 24);\n if (diffDays < 7) {\n const days = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n return `${days[date.getDay()]} ${String(date.getHours()).padStart(2, \"0\")}:${String(date.getMinutes()).padStart(2, \"0\")}`;\n }\n const months = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n return `${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;\n}\n\nexport function registerAuditCommands(program: Command): void {\n const audit = program.command(\"audit\").description(\"Query and manage audit logs\");\n\n audit\n .command(\"list\")\n .description(\"List recent audit events\")\n .option(\"--limit <n>\", \"Maximum events to show\", parseInt, 50)\n .option(\"--since <date>\", \"Show events since date (ISO 8601)\")\n .option(\"--command <name>\", \"Filter by command name\")\n .action(async (options) => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n initAudit(getConfigDir());\n\n try {\n const events = await listAuditEvents({\n limit: options.limit,\n since: options.since,\n command: options.command,\n });\n if (events.length === 0 && format !== \"json\") {\n console.log(\"No audit events found.\");\n return;\n }\n if (format !== \"json\") {\n const rows = events.map((e) => ({\n timestamp: formatAuditTimestamp(e.timestamp),\n command: e.command,\n app: e.app || \"-\",\n success: e.success !== undefined ? String(e.success) : \"-\",\n durationMs: e.durationMs ?? \"-\",\n }));\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(events, format));\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n });\n\n audit\n .command(\"search <query>\")\n .description(\"Search audit events by keyword\")\n .action(async (query: string) => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n initAudit(getConfigDir());\n\n try {\n const events = await searchAuditEvents(query);\n if (events.length === 0 && format !== \"json\") {\n console.log(`No audit events matching \"${query}\".`);\n return;\n }\n if (format !== \"json\") {\n const rows = events.map((e) => ({\n timestamp: formatAuditTimestamp(e.timestamp),\n command: e.command,\n app: e.app || \"-\",\n success: e.success !== undefined ? String(e.success) : \"-\",\n }));\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(events, format));\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n });\n\n audit\n .command(\"clear\")\n .description(\"Clear audit log entries\")\n .option(\"--before <date>\", \"Clear entries before date (ISO 8601)\")\n .option(\"--dry-run\", \"Preview what would be cleared\")\n .action(async (options, cmd: Command) => {\n const dryRun = options.dryRun || isDryRun(cmd);\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n initAudit(getConfigDir());\n\n if (!dryRun && !options.before) {\n await requireConfirm(\"Clear all audit log entries?\", program);\n }\n\n try {\n const result = await clearAuditLog({\n before: options.before,\n dryRun,\n });\n if (dryRun) {\n console.log(`[dry-run] Would delete ${result.deleted} entries, ${result.remaining} would remain.`);\n } else {\n console.log(`Deleted ${result.deleted} entries. ${result.remaining} remaining.`);\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n });\n}\n"],"mappings":"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","names":[]}
package/dist/bin.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createProgram,
4
4
  handleCliError,
5
5
  loadPlugins
6
- } from "./chunk-PREKF2AM.js";
6
+ } from "./chunk-SIY3HJN6.js";
7
7
 
8
8
  // src/networking.ts
9
9
  async function setupNetworking() {
@@ -79,7 +79,7 @@ async function createProgram(pluginManager) {
79
79
  (await import("./doctor-UZB2UB5X.js")).registerDoctorCommand(program);
80
80
  },
81
81
  docs: async () => {
82
- (await import("./docs-CVTWIVMS.js")).registerDocsCommand(program);
82
+ (await import("./docs-HIGQU4UL.js")).registerDocsCommand(program);
83
83
  },
84
84
  completion: async () => {
85
85
  (await import("./completion-C3PPWNS7.js")).registerCompletionCommand(program);
@@ -88,13 +88,13 @@ async function createProgram(pluginManager) {
88
88
  (await import("./apps-CVBURB5V.js")).registerAppsCommands(program);
89
89
  },
90
90
  releases: async () => {
91
- (await import("./releases-ZKPSAPB2.js")).registerReleasesCommands(program);
91
+ (await import("./releases-JMRKXEZU.js")).registerReleasesCommands(program);
92
92
  },
93
93
  tracks: async () => {
94
94
  (await import("./tracks-XFUN7JJX.js")).registerTracksCommands(program);
95
95
  },
96
96
  status: async () => {
97
- (await import("./status-NE3A2UHN.js")).registerStatusCommand(program);
97
+ (await import("./status-6Y2CHHVD.js")).registerStatusCommand(program);
98
98
  },
99
99
  listings: async () => {
100
100
  (await import("./listings-VSBHQY5H.js")).registerListingsCommands(program);
@@ -130,7 +130,7 @@ async function createProgram(pluginManager) {
130
130
  (await import("./validate-MHLPENCM.js")).registerValidateCommand(program);
131
131
  },
132
132
  publish: async () => {
133
- (await import("./publish-26ZPS7XX.js")).registerPublishCommand(program);
133
+ (await import("./publish-I6WJGR4S.js")).registerPublishCommand(program);
134
134
  },
135
135
  recovery: async () => {
136
136
  (await import("./recovery-S5UNJDBO.js")).registerRecoveryCommands(program);
@@ -162,7 +162,7 @@ async function createProgram(pluginManager) {
162
162
  (await import("./bundle-7IF5FIB4.js")).registerBundleCommands(program);
163
163
  },
164
164
  audit: async () => {
165
- (await import("./audit-QP75CEGB.js")).registerAuditCommands(program);
165
+ (await import("./audit-A4BP27DN.js")).registerAuditCommands(program);
166
166
  },
167
167
  migrate: async () => {
168
168
  (await import("./migrate-XQV7P4R7.js")).registerMigrateCommands(program);
@@ -308,4 +308,4 @@ export {
308
308
  createProgram,
309
309
  handleCliError
310
310
  };
311
- //# sourceMappingURL=chunk-PREKF2AM.js.map
311
+ //# sourceMappingURL=chunk-SIY3HJN6.js.map
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/docs.ts
4
+ import { execFile } 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
+ console.error(`Unknown topic "${topic}". Run: gpc docs --list`);
30
+ process.exit(2);
31
+ }
32
+ const url = path ? `${BASE}${path}` : BASE;
33
+ const platform = process.platform;
34
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
35
+ execFile(cmd, [url], (error) => {
36
+ if (error) {
37
+ console.log(`Open in your browser: ${url}`);
38
+ }
39
+ });
40
+ });
41
+ }
42
+ export {
43
+ registerDocsCommand
44
+ };
45
+ //# sourceMappingURL=docs-HIGQU4UL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/docs.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { execFile } 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 console.error(`Unknown topic \"${topic}\". Run: gpc docs --list`);\n process.exit(2);\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 execFile(cmd, [url], (error) => {\n if (error) {\n console.log(`Open in your browser: ${url}`);\n }\n });\n });\n}\n"],"mappings":";;;AACA,SAAS,gBAAgB;AAEzB,IAAM,WAAmC;AAAA,EACvC,UAAe;AAAA,EACf,QAAe;AAAA,EACf,QAAe;AAAA,EACf,SAAe;AAAA,EACf,UAAe;AAAA,EACf,eAAe;AAAA,EACf,QAAe;AAAA,EACf,OAAe;AAAA,EACf,OAAe;AAAA,EACf,QAAe;AAAA,EACf,QAAe;AAAA,EACf,SAAe;AACjB;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,cAAQ,MAAM,kBAAkB,KAAK,yBAAyB;AAC9D,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,UAAM,MAAM,OAAO,GAAG,IAAI,GAAG,IAAI,KAAK;AACtC,UAAM,WAAW,QAAQ;AACzB,UAAM,MAAM,aAAa,WAAW,SAAS,aAAa,UAAU,UAAU;AAC9E,aAAS,KAAK,CAAC,GAAG,GAAG,CAAC,UAAU;AAC9B,UAAI,OAAO;AACT,gBAAQ,IAAI,yBAAyB,GAAG,EAAE;AAAA,MAC5C;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACL;","names":[]}
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  createProgram,
4
4
  handleCliError
5
- } from "./chunk-PREKF2AM.js";
5
+ } from "./chunk-SIY3HJN6.js";
6
6
  export {
7
7
  createProgram,
8
8
  handleCliError
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-NV75I5VP.js";
13
13
 
14
14
  // src/commands/publish.ts
15
- import { appendFile } from "fs/promises";
15
+ import { appendFile, stat } from "fs/promises";
16
16
  import { loadConfig, getCacheDir } from "@gpc-cli/config";
17
17
  import { resolveAuth } from "@gpc-cli/auth";
18
18
  import { createApiClient } from "@gpc-cli/api";
@@ -90,6 +90,12 @@ function formatDryRunOutput(result, format) {
90
90
  }
91
91
  function registerPublishCommand(program) {
92
92
  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) => {
93
+ try {
94
+ await stat(file);
95
+ } catch {
96
+ console.error(`Error: File not found: ${file}`);
97
+ process.exit(2);
98
+ }
93
99
  const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);
94
100
  if (noteSources.length > 1) {
95
101
  console.error(
@@ -204,4 +210,4 @@ function registerPublishCommand(program) {
204
210
  export {
205
211
  registerPublishCommand
206
212
  };
207
- //# sourceMappingURL=publish-26ZPS7XX.js.map
213
+ //# sourceMappingURL=publish-I6WJGR4S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/publish.ts"],"sourcesContent":["import { appendFile, stat } from \"node:fs/promises\";\nimport type { GpcConfig, 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} 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\";\n\nconst PASS = \"\\u2713\";\nconst FAIL = \"\\u2717\";\nconst WARN = \"\\u26A0\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n console.error(\"Error: No package name. Use --app <package> or gpc config set app <package>\");\n process.exit(2);\n }\n return name;\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 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 = [\n \"Published successfully\\n\",\n ...formatChecks(result.validation.checks),\n ];\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 = [\n \"Dry run — no changes made\\n\",\n ...formatChecks(result.validation.checks),\n ];\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 = 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 .action(async (file: string, options) => {\n try {\n await stat(file);\n } catch {\n console.error(`Error: File not found: ${file}`);\n process.exit(2);\n }\n\n const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);\n if (noteSources.length > 1) {\n console.error(\n \"Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.\",\n );\n process.exit(2);\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 console.error(\n `Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`,\n );\n process.exit(2);\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 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 dryRun: true,\n });\n console.log(formatDryRunOutput(result as DryRunPublishResult, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\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 });\n\n if (!result.upload) {\n console.log(formatValidationOutput(result as PublishResult, format));\n auditEntry.success = false;\n auditEntry.error = \"Validation failed\";\n process.exit(1);\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 console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\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":[]}
@@ -14,7 +14,8 @@ import {
14
14
  } from "./chunk-NV75I5VP.js";
15
15
 
16
16
  // src/commands/releases.ts
17
- import { appendFile } from "fs/promises";
17
+ import { appendFile, stat } from "fs/promises";
18
+ import { basename } from "path";
18
19
  import { loadConfig } from "@gpc-cli/config";
19
20
  import { resolveAuth } from "@gpc-cli/auth";
20
21
  import { createApiClient } from "@gpc-cli/api";
@@ -54,6 +55,12 @@ async function getClient(config, retryLogPath, uploadTimeout) {
54
55
  function registerReleasesCommands(program) {
55
56
  const releases = program.command("releases").description("Manage releases and rollouts");
56
57
  releases.command("upload <file>").description("Upload AAB/APK and assign to a track").option("--track <track>", "Target track", "internal").option("--rollout <percent>", "Staged rollout percentage (1-100)").option("--notes <text>", "Release notes (en-US)").option("--name <name>", "Release name").option("--mapping <file>", "ProGuard/R8 mapping file for deobfuscation").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("--retry-log <path>", "Write retry log entries to file (JSONL)").option("--timeout <ms>", "Upload timeout in milliseconds (auto-scales with file size by default)", parseInt).action(async (file, options) => {
58
+ try {
59
+ await stat(file);
60
+ } catch {
61
+ console.error(`Error: File not found: ${file}`);
62
+ process.exit(2);
63
+ }
57
64
  const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);
58
65
  if (noteSources.length > 1) {
59
66
  console.error("Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.");
@@ -81,6 +88,14 @@ function registerReleasesCommands(program) {
81
88
  if (notes) options.notes = notes;
82
89
  }
83
90
  }
91
+ if (options.rollout !== void 0) {
92
+ const rollout2 = Number(options.rollout);
93
+ if (!Number.isFinite(rollout2) || rollout2 < 1 || rollout2 > 100) {
94
+ console.error(`Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`);
95
+ process.exit(2);
96
+ }
97
+ }
98
+ const { size: fileSize } = await stat(file);
84
99
  const client = await getClient(config, options.retryLog, options.timeout);
85
100
  if (isDryRun(program)) {
86
101
  try {
@@ -105,7 +120,8 @@ function registerReleasesCommands(program) {
105
120
  },
106
121
  packageName
107
122
  );
108
- const spinner = createSpinner("Uploading bundle...");
123
+ const sizeMB = (fileSize / (1024 * 1024)).toFixed(1);
124
+ const spinner = createSpinner(`Uploading ${basename(file)} (${sizeMB} MB)...`);
109
125
  if (!program.opts()["quiet"] && process.stderr.isTTY) spinner.start();
110
126
  try {
111
127
  let releaseNotes;
@@ -145,15 +161,20 @@ function registerReleasesCommands(program) {
145
161
  const client = await getClient(config);
146
162
  const format = getOutputFormat(program, config);
147
163
  try {
164
+ const TRACK_ORDER = ["production", "beta", "alpha", "internal"];
148
165
  const statuses = await getReleasesStatus(client, packageName, options.track);
149
- const sorted = Array.isArray(statuses) ? sortResults(statuses, options.sort) : statuses;
166
+ const sorted = Array.isArray(statuses) ? options.sort ? sortResults(statuses, options.sort) : [...statuses].sort((a, b) => {
167
+ const ai = TRACK_ORDER.indexOf(String(a["track"] ?? ""));
168
+ const bi = TRACK_ORDER.indexOf(String(b["track"] ?? ""));
169
+ return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);
170
+ }) : statuses;
150
171
  if (format !== "json" && Array.isArray(sorted)) {
151
172
  const rows = sorted.map((s) => ({
152
173
  track: s["track"] || "-",
153
174
  status: s["status"] || "-",
154
175
  name: s["name"] || "-",
155
176
  versionCodes: Array.isArray(s["versionCodes"]) ? s["versionCodes"].join(", ") : "-",
156
- userFraction: s["userFraction"] !== void 0 ? String(s["userFraction"]) : "-"
177
+ userFraction: s["userFraction"] !== void 0 ? `${Math.round(Number(s["userFraction"]) * 100)}%` : "\u2014"
157
178
  }));
158
179
  console.log(formatOutput(rows, format));
159
180
  } else {
@@ -188,6 +209,17 @@ function registerReleasesCommands(program) {
188
209
  },
189
210
  interactive
190
211
  );
212
+ if (options.from === options.to) {
213
+ console.error(`Error: --from and --to must be different tracks (both are "${options.from}")`);
214
+ process.exit(2);
215
+ }
216
+ if (options.rollout !== void 0) {
217
+ const rollout2 = Number(options.rollout);
218
+ if (!Number.isFinite(rollout2) || rollout2 < 1 || rollout2 > 100) {
219
+ console.error(`Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`);
220
+ process.exit(2);
221
+ }
222
+ }
191
223
  if (isDryRun(program)) {
192
224
  printDryRun(
193
225
  {
@@ -244,13 +276,20 @@ function registerReleasesCommands(program) {
244
276
  interactive
245
277
  );
246
278
  }
279
+ if (action === "increase" && options.to !== void 0) {
280
+ const to = Number(options.to);
281
+ if (!Number.isFinite(to) || to < 1 || to > 100) {
282
+ console.error(`Error: --to must be a number between 1 and 100 (got: ${options.to})`);
283
+ process.exit(2);
284
+ }
285
+ }
247
286
  if (isDryRun(program)) {
248
287
  printDryRun(
249
288
  {
250
289
  command: `releases rollout ${action}`,
251
290
  action,
252
291
  target: options.track,
253
- details: { percentage: options.to }
292
+ details: { percentage: options.to !== void 0 ? `${options.to}%` : void 0 }
254
293
  },
255
294
  format,
256
295
  formatOutput
@@ -301,7 +340,10 @@ function registerReleasesCommands(program) {
301
340
  console.error("Provide --notes <text> or --file <path>");
302
341
  process.exit(2);
303
342
  }
304
- console.log(`Release notes set for ${options.track} (${options.lang})`);
343
+ console.error(
344
+ "Error: gpc releases notes set is not yet implemented as a standalone command.\nTo set release notes, use --notes, --notes-dir, or --notes-from-git with:\n gpc releases upload\n gpc publish"
345
+ );
346
+ process.exit(1);
305
347
  });
306
348
  releases.command("upload-external").description("Upload an externally hosted APK configuration").requiredOption("--url <url>", "External URL where the APK is hosted").requiredOption("--file <config>", "Path to JSON config file with APK metadata").action(async (options) => {
307
349
  const config = await loadConfig();
@@ -352,4 +394,4 @@ function registerReleasesCommands(program) {
352
394
  export {
353
395
  registerReleasesCommands
354
396
  };
355
- //# sourceMappingURL=releases-ZKPSAPB2.js.map
397
+ //# sourceMappingURL=releases-JMRKXEZU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/releases.ts"],"sourcesContent":["import { appendFile, stat } from \"node:fs/promises\";\nimport { basename } 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 } from \"@gpc-cli/api\";\nimport type { RetryLogEntry } 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} from \"@gpc-cli/core\";\nimport { formatOutput, sortResults, createSpinner } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport { isInteractive, promptSelect, promptInput, requireOption } from \"../prompt.js\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n console.error(\"Error: No package name. Use --app <package> or gpc config set app <package>\");\n process.exit(2);\n }\n return name;\n}\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(\"--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(\"--timeout <ms>\", \"Upload timeout in milliseconds (auto-scales with file size by default)\", parseInt)\n .action(async (file: string, options) => {\n try {\n await stat(file);\n } catch {\n console.error(`Error: File not found: ${file}`);\n process.exit(2);\n }\n\n const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);\n if (noteSources.length > 1) {\n console.error(\"Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.\");\n process.exit(2);\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 console.error(`Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`);\n process.exit(2);\n }\n }\n\n const { size: fileSize } = await stat(file);\n const client = await getClient(config, options.retryLog, options.timeout);\n\n if (isDryRun(program)) {\n try {\n const result = await uploadRelease(client, packageName, file, {\n track: options.track,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n dryRun: true,\n });\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\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 sizeMB = (fileSize / (1024 * 1024)).toFixed(1);\n const spinner = createSpinner(`Uploading ${basename(file)} (${sizeMB} MB)...`);\n if (!program.opts()[\"quiet\"] && process.stderr.isTTY) spinner.start();\n\n try {\n let releaseNotes: { language: string; text: string }[] | undefined;\n 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 userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n releaseNotes,\n releaseName: options.name,\n mappingFile: options.mapping,\n });\n spinner.stop(\"Upload complete\");\n console.log(formatOutput(result, format));\n auditEntry.success = true;\n } catch (error) {\n spinner.fail(\"Upload failed\");\n auditEntry.success = false;\n auditEntry.error = error instanceof Error ? error.message : String(error);\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\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 try {\n const TRACK_ORDER = [\"production\", \"beta\", \"alpha\", \"internal\"];\n const statuses = await getReleasesStatus(client, packageName, options.track);\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(String((a as Record<string, unknown>)[\"track\"] ?? \"\"));\n const bi = TRACK_ORDER.indexOf(String((b as Record<string, unknown>)[\"track\"] ?? \"\"));\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: Record<string, unknown>) => ({\n track: s[\"track\"] || \"-\",\n status: s[\"status\"] || \"-\",\n name: s[\"name\"] || \"-\",\n versionCodes: Array.isArray(s[\"versionCodes\"]) ? (s[\"versionCodes\"] as unknown[]).join(\", \") : \"-\",\n userFraction: s[\"userFraction\"] !== undefined\n ? `${Math.round(Number(s[\"userFraction\"]) * 100)}%`\n : \"—\",\n }));\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(sorted, format));\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\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 .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.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 console.error(`Error: --from and --to must be different tracks (both are \"${options.from}\")`);\n process.exit(2);\n }\n\n if (options.rollout !== undefined) {\n const rollout = Number(options.rollout);\n if (!Number.isFinite(rollout) || rollout < 1 || rollout > 100) {\n console.error(`Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`);\n process.exit(2);\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 },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const result = await promoteRelease(client, packageName, options.from, options.to, {\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n releaseNotes: options.notes ? [{ language: \"en-US\", text: options.notes }] : undefined,\n });\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\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 }\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 console.error(`Error: --to must be a number between 1 and 100 (got: ${options.to})`);\n process.exit(2);\n }\n }\n\n if (isDryRun(program)) {\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 try {\n const result = await updateRollout(\n client,\n packageName,\n options.track,\n action,\n options.to ? Number(options.to) / 100 : undefined,\n );\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n }\n\n // Release notes\n releases\n .command(\"notes\")\n .description(\"Set release notes\")\n .argument(\"<action>\", \"Action: 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 console.error(\"Usage: gpc releases notes set --track <track> --notes <text>\");\n process.exit(2);\n }\n\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 let notesText = options.notes;\n if (options.file) {\n const { readFile } = await import(\"node:fs/promises\");\n notesText = await readFile(options.file, \"utf-8\");\n }\n\n if (!notesText && interactive) {\n notesText = await promptInput(\"Release notes text:\");\n }\n\n if (!notesText) {\n console.error(\"Provide --notes <text> or --file <path>\");\n process.exit(2);\n }\n\n console.error(\n \"Error: gpc releases notes set is not yet implemented as a standalone command.\\n\" +\n \"To set release notes, use --notes, --notes-dir, or --notes-from-git with:\\n\" +\n \" gpc releases upload\\n\" +\n \" gpc publish\"\n );\n process.exit(1);\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 try {\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 import(\"@gpc-cli/api\").ExternallyHostedApk,\n );\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\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 try {\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 } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n}\n"],"mappings":"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","names":["rollout"]}
@@ -103,6 +103,10 @@ function registerStatusCommand(program) {
103
103
  process.exit(2);
104
104
  }
105
105
  const sections = parseSections(opts.sections);
106
+ if (!Number.isFinite(opts.days) || opts.days < 1) {
107
+ console.error(`Error: --days must be a positive integer (got: ${opts.days})`);
108
+ process.exit(2);
109
+ }
106
110
  const config = await loadConfig();
107
111
  const format = getOutputFormat(program, config);
108
112
  const render = makeRenderer(format, opts.format);
@@ -156,6 +160,12 @@ function registerStatusCommand(program) {
156
160
  }
157
161
  );
158
162
  }
163
+ function applyDisplaySections(status, requestedSections) {
164
+ const requested = new Set(requestedSections);
165
+ const filtered = status.sections.filter((s) => requested.has(s));
166
+ if (filtered.length === status.sections.length) return status;
167
+ return { ...status, sections: filtered };
168
+ }
159
169
  async function runStatusForPackage(ctx) {
160
170
  const { packageName, opts, sections, vitalThresholds, watchInterval, render } = ctx;
161
171
  const fetchLive = async () => {
@@ -167,6 +177,9 @@ async function runStatusForPackage(ctx) {
167
177
  });
168
178
  };
169
179
  const save = (status2) => saveStatusCache(packageName, status2, opts.ttl);
180
+ if (watchInterval !== null && opts.sinceLast) {
181
+ process.stderr.write("Warning: --since-last is not supported with --watch and will be ignored.\n");
182
+ }
170
183
  if (watchInterval !== null) {
171
184
  await runWatchLoop({ intervalSeconds: watchInterval, render, fetch: fetchLive, save });
172
185
  return false;
@@ -177,7 +190,8 @@ async function runStatusForPackage(ctx) {
177
190
  console.error("Error: No cached status found. Run without --cached to fetch live data.");
178
191
  process.exit(1);
179
192
  }
180
- console.log(render(cached));
193
+ const display = applyDisplaySections(cached, sections);
194
+ console.log(render(display));
181
195
  await handleNotify(packageName, cached, opts.notify);
182
196
  return statusHasBreach(cached);
183
197
  }
@@ -185,7 +199,14 @@ async function runStatusForPackage(ctx) {
185
199
  if (!opts.refresh) {
186
200
  const cached = await loadStatusCache(packageName, opts.ttl);
187
201
  if (cached) {
188
- printWithDiff(cached, prevStatus, opts.sinceLast, render, ctx.format);
202
+ const display = applyDisplaySections(cached, sections);
203
+ if (ctx.format !== "json" && display.sections.length < cached.sections.length) {
204
+ process.stderr.write(
205
+ `Tip: cache contains all sections. Add --refresh to fetch only the requested sections and reduce API calls.
206
+ `
207
+ );
208
+ }
209
+ printWithDiff(display, prevStatus, opts.sinceLast, render, ctx.format);
189
210
  await handleNotify(packageName, cached, opts.notify);
190
211
  return statusHasBreach(cached);
191
212
  }
@@ -196,10 +217,19 @@ async function runStatusForPackage(ctx) {
196
217
  await handleNotify(packageName, status, opts.notify);
197
218
  return statusHasBreach(status);
198
219
  }
220
+ function relativeTime(isoString) {
221
+ const diffMs = Date.now() - new Date(isoString).getTime();
222
+ const diffMin = Math.floor(diffMs / 6e4);
223
+ if (diffMin < 1) return "just now";
224
+ if (diffMin < 60) return `${diffMin} min ago`;
225
+ const diffHr = Math.floor(diffMin / 60);
226
+ if (diffHr < 24) return `${diffHr}h ago`;
227
+ return `${Math.floor(diffHr / 24)}d ago`;
228
+ }
199
229
  function printWithDiff(status, prevStatus, sinceLast, render, format) {
200
230
  console.log(render(status));
201
231
  if (sinceLast && prevStatus) {
202
- const since = new Date(prevStatus.fetchedAt).toLocaleString();
232
+ const since = relativeTime(prevStatus.fetchedAt);
203
233
  console.log("");
204
234
  console.log(formatStatusDiff(computeStatusDiff(prevStatus, status), since));
205
235
  } else if (sinceLast && !prevStatus && format !== "json") {
@@ -219,4 +249,4 @@ async function handleNotify(packageName, status, notify) {
219
249
  export {
220
250
  registerStatusCommand
221
251
  };
222
- //# sourceMappingURL=status-NE3A2UHN.js.map
252
+ //# sourceMappingURL=status-6Y2CHHVD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/status.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient, createReportingClient } from \"@gpc-cli/api\";\nimport {\n getAppStatus,\n formatStatusTable,\n formatStatusSummary,\n formatStatusDiff,\n computeStatusDiff,\n loadStatusCache,\n saveStatusCache,\n statusHasBreach,\n runWatchLoop,\n trackBreachState,\n sendNotification,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport type { AppStatus } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\nconst VALID_SECTIONS = new Set([\"releases\", \"vitals\", \"reviews\"]);\nconst VALID_FORMATS = new Set([\"table\", \"summary\"]);\n\nfunction parseSections(raw: string): string[] {\n const sections = raw.split(\",\").map((s) => s.trim().toLowerCase());\n for (const s of sections) {\n if (!VALID_SECTIONS.has(s)) {\n console.error(`Error: Unknown section \"${s}\". Valid sections: releases, vitals, reviews`);\n process.exit(2);\n }\n }\n return sections;\n}\n\nfunction resolveVitalThresholds(config: Record<string, unknown>) {\n const vitals = config[\"vitals\"] as Record<string, unknown> | undefined;\n const t = vitals?.[\"thresholds\"] as Record<string, unknown> | undefined;\n if (!t) return undefined;\n const toN = (v: unknown): number | undefined => {\n if (v === undefined || v === null) return undefined;\n const n = Number(v);\n return isNaN(n) ? undefined : n;\n };\n return {\n crashRate: toN(t[\"crashRate\"]),\n anrRate: toN(t[\"anrRate\"]),\n slowStartRate: toN(t[\"slowStartRate\"]),\n slowRenderingRate: toN(t[\"slowRenderingRate\"]),\n };\n}\n\nfunction resolvePackages(\n program: Command,\n config: { app?: string; profiles?: Record<string, { app?: string }> },\n allApps?: boolean,\n): string[] {\n const rootApp = (program.opts()[\"app\"] || config.app) as string | undefined;\n if (!allApps) return rootApp ? [rootApp] : [];\n\n const seen = new Set<string>();\n const result: string[] = [];\n if (rootApp) {\n seen.add(rootApp);\n result.push(rootApp);\n }\n for (const profile of Object.values(config.profiles ?? {})) {\n if (profile.app && !seen.has(profile.app)) {\n seen.add(profile.app);\n result.push(profile.app);\n }\n }\n return result;\n}\n\nfunction resolveWatchInterval(watch: string | boolean | undefined): number | null {\n if (watch === undefined) return null;\n if (watch === true || watch === \"\") return 30;\n const n = parseInt(String(watch), 10);\n return isNaN(n) ? 30 : n;\n}\n\nfunction makeRenderer(\n format: string,\n displayFormat: string,\n): (status: AppStatus) => string {\n return (status: AppStatus): string => {\n if (format === \"json\") {\n const sectionSet = new Set(status.sections);\n const filtered: Record<string, unknown> = {\n packageName: status.packageName,\n fetchedAt: status.fetchedAt,\n cached: status.cached,\n sections: status.sections,\n };\n if (sectionSet.has(\"releases\")) filtered[\"releases\"] = status.releases;\n if (sectionSet.has(\"vitals\")) filtered[\"vitals\"] = status.vitals;\n if (sectionSet.has(\"reviews\")) filtered[\"reviews\"] = status.reviews;\n return formatOutput(filtered, \"json\");\n }\n if (displayFormat === \"summary\") return formatStatusSummary(status);\n return formatStatusTable(status);\n };\n}\n\nexport function registerStatusCommand(program: Command): void {\n program\n .command(\"status\")\n .description(\"Unified app health snapshot: releases, vitals, and reviews\")\n .option(\"--days <n>\", \"Vitals window in days\", (v) => parseInt(v, 10), 7)\n .option(\"--cached\", \"Use last fetched data, skip API calls\")\n .option(\"--refresh\", \"Force live fetch, ignore cache TTL\")\n .option(\"--ttl <seconds>\", \"Cache TTL in seconds\", (v) => parseInt(v, 10), 3600)\n .option(\"--format <fmt>\", \"Display style: table (default) or summary\", \"table\")\n .option(\n \"--sections <list>\",\n \"Comma-separated sections: releases,vitals,reviews\",\n \"releases,vitals,reviews\",\n )\n .option(\"--watch [seconds]\", \"Poll every N seconds (min 10, default 30)\")\n .option(\"--since-last\", \"Show diff from last cached status\")\n .option(\"--all-apps\", \"Run status for all configured app profiles (max 5)\")\n .option(\"--notify\", \"Send desktop notification on threshold breach or clear\")\n .action(\n async (opts: {\n days: number;\n cached?: boolean;\n refresh?: boolean;\n ttl: number;\n format: string;\n sections: string;\n watch?: string | boolean;\n sinceLast?: boolean;\n allApps?: boolean;\n notify?: boolean;\n }) => {\n if (!VALID_FORMATS.has(opts.format)) {\n console.error(`Error: Unknown format \"${opts.format}\". Valid: table, summary`);\n process.exit(2);\n }\n\n const sections = parseSections(opts.sections);\n\n if (!Number.isFinite(opts.days) || opts.days < 1) {\n console.error(`Error: --days must be a positive integer (got: ${opts.days})`);\n process.exit(2);\n }\n\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n const render = makeRenderer(format, opts.format);\n const vitalThresholds = resolveVitalThresholds(\n config as unknown as Record<string, unknown>,\n );\n const watchInterval = resolveWatchInterval(opts.watch);\n const packages = resolvePackages(program, config, opts.allApps);\n\n if (packages.length === 0) {\n console.error(\n \"Error: No package name. Use --app <package> or gpc config set app <package>\",\n );\n process.exit(2);\n }\n if (opts.allApps && packages.length > 5) {\n console.error(\n `Error: --all-apps found ${packages.length} apps (max 5). Use --app to target a specific app.`,\n );\n process.exit(2);\n }\n\n const authConfig = (config as unknown as Record<string, unknown>)[\"auth\"] as\n | Record<string, string>\n | undefined;\n\n const makeClients = async () => {\n const auth = await resolveAuth({ serviceAccountPath: authConfig?.[\"serviceAccount\"] });\n return {\n client: createApiClient({ auth }),\n reporting: createReportingClient({ auth }),\n };\n };\n\n let anyBreach = false;\n\n for (const packageName of packages) {\n if (packages.length > 1) console.log(`\\n=== ${packageName} ===`);\n\n try {\n const breach = await runStatusForPackage({\n packageName,\n opts,\n sections,\n format,\n vitalThresholds,\n watchInterval,\n render,\n makeClients,\n });\n if (breach) anyBreach = true;\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n if (packages.length === 1) process.exit(4);\n // For --all-apps, continue to next app on error\n }\n }\n\n if (anyBreach) process.exit(6);\n },\n );\n}\n\ninterface RunCtx {\n packageName: string;\n opts: {\n days: number;\n cached?: boolean;\n refresh?: boolean;\n ttl: number;\n sinceLast?: boolean;\n notify?: boolean;\n };\n sections: string[];\n format: string;\n vitalThresholds:\n | {\n crashRate?: number;\n anrRate?: number;\n slowStartRate?: number;\n slowRenderingRate?: number;\n }\n | undefined;\n watchInterval: number | null;\n render: (status: AppStatus) => string;\n makeClients: () => Promise<{\n client: ReturnType<typeof createApiClient>;\n reporting: ReturnType<typeof createReportingClient>;\n }>;\n}\n\n/** Override sections on a cached AppStatus with the user-requested sections for display. */\nfunction applyDisplaySections(status: AppStatus, requestedSections: string[]): AppStatus {\n const requested = new Set(requestedSections);\n const filtered = status.sections.filter((s) => requested.has(s));\n if (filtered.length === status.sections.length) return status;\n return { ...status, sections: filtered };\n}\n\n/** Returns true if a breach was detected. */\nasync function runStatusForPackage(ctx: RunCtx): Promise<boolean> {\n const { packageName, opts, sections, vitalThresholds, watchInterval, render } = ctx;\n\n const fetchLive = async (): Promise<AppStatus> => {\n const { client, reporting } = await ctx.makeClients();\n return getAppStatus(client, reporting, packageName, {\n days: opts.days,\n sections,\n vitalThresholds: vitalThresholds ?? undefined,\n });\n };\n\n const save = (status: AppStatus) => saveStatusCache(packageName, status, opts.ttl);\n\n if (watchInterval !== null && opts.sinceLast) {\n process.stderr.write(\"Warning: --since-last is not supported with --watch and will be ignored.\\n\");\n }\n\n // --watch: hand off entirely to runWatchLoop\n if (watchInterval !== null) {\n await runWatchLoop({ intervalSeconds: watchInterval, render, fetch: fetchLive, save });\n return false;\n }\n\n // --cached: serve from cache only\n if (opts.cached) {\n const cached = await loadStatusCache(packageName, opts.ttl);\n if (!cached) {\n console.error(\"Error: No cached status found. Run without --cached to fetch live data.\");\n process.exit(1);\n }\n const display = applyDisplaySections(cached, sections);\n console.log(render(display));\n await handleNotify(packageName, cached, opts.notify);\n return statusHasBreach(cached);\n }\n\n // Capture prev status before fetching (for --since-last)\n const prevStatus = opts.sinceLast ? await loadStatusCache(packageName, Infinity) : null;\n\n // Try cache (unless --refresh)\n if (!opts.refresh) {\n const cached = await loadStatusCache(packageName, opts.ttl);\n if (cached) {\n const display = applyDisplaySections(cached, sections);\n if (ctx.format !== \"json\" && display.sections.length < cached.sections.length) {\n process.stderr.write(\n `Tip: cache contains all sections. Add --refresh to fetch only the requested sections and reduce API calls.\\n`,\n );\n }\n printWithDiff(display, prevStatus, opts.sinceLast, render, ctx.format);\n await handleNotify(packageName, cached, opts.notify);\n return statusHasBreach(cached);\n }\n }\n\n // Live fetch\n const status = await fetchLive();\n await save(status);\n\n printWithDiff(status, prevStatus, opts.sinceLast, render, ctx.format);\n await handleNotify(packageName, status, opts.notify);\n return statusHasBreach(status);\n}\n\nfunction relativeTime(isoString: string): string {\n const diffMs = Date.now() - new Date(isoString).getTime();\n const diffMin = Math.floor(diffMs / 60000);\n if (diffMin < 1) return \"just now\";\n if (diffMin < 60) return `${diffMin} min ago`;\n const diffHr = Math.floor(diffMin / 60);\n if (diffHr < 24) return `${diffHr}h ago`;\n return `${Math.floor(diffHr / 24)}d ago`;\n}\n\nfunction printWithDiff(\n status: AppStatus,\n prevStatus: AppStatus | null,\n sinceLast: boolean | undefined,\n render: (s: AppStatus) => string,\n format: string,\n): void {\n console.log(render(status));\n\n if (sinceLast && prevStatus) {\n const since = relativeTime(prevStatus.fetchedAt);\n console.log(\"\");\n console.log(formatStatusDiff(computeStatusDiff(prevStatus, status), since));\n } else if (sinceLast && !prevStatus && format !== \"json\") {\n console.log(\"\\n(No prior cached status to diff against)\");\n }\n}\n\nasync function handleNotify(\n packageName: string,\n status: AppStatus,\n notify: boolean | undefined,\n): Promise<void> {\n if (!notify) return;\n const breaching = statusHasBreach(status);\n const changed = await trackBreachState(packageName, breaching);\n if (changed) {\n const title = breaching ? \"GPC Alert\" : \"GPC Status\";\n const body = breaching\n ? `${packageName}: vitals threshold breached`\n : `${packageName}: vitals back to normal`;\n sendNotification(title, body);\n }\n}\n"],"mappings":"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","names":["status"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gpc-cli/cli",
3
- "version": "0.9.28",
3
+ "version": "0.9.29",
4
4
  "description": "The complete Google Play CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,9 +21,9 @@
21
21
  "commander": "^14.0.3",
22
22
  "@gpc-cli/api": "1.0.17",
23
23
  "@gpc-cli/auth": "^0.9.9",
24
- "@gpc-cli/core": "0.9.25",
24
+ "@gpc-cli/config": "0.9.8",
25
25
  "@gpc-cli/plugin-sdk": "0.9.6",
26
- "@gpc-cli/config": "0.9.8"
26
+ "@gpc-cli/core": "0.9.26"
27
27
  },
28
28
  "keywords": [
29
29
  "google-play",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/audit.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { getConfigDir } from \"@gpc-cli/config\";\nimport {\n initAudit,\n listAuditEvents,\n searchAuditEvents,\n clearAuditLog,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun } from \"../dry-run.js\";\nimport { requireConfirm } from \"../prompt.js\";\n\nexport function registerAuditCommands(program: Command): void {\n const audit = program.command(\"audit\").description(\"Query and manage audit logs\");\n\n audit\n .command(\"list\")\n .description(\"List recent audit events\")\n .option(\"--limit <n>\", \"Maximum events to show\", parseInt, 50)\n .option(\"--since <date>\", \"Show events since date (ISO 8601)\")\n .option(\"--command <name>\", \"Filter by command name\")\n .action(async (options) => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n initAudit(getConfigDir());\n\n try {\n const events = await listAuditEvents({\n limit: options.limit,\n since: options.since,\n command: options.command,\n });\n if (events.length === 0 && format !== \"json\") {\n console.log(\"No audit events found.\");\n return;\n }\n if (format !== \"json\") {\n const rows = events.map((e) => ({\n timestamp: e.timestamp,\n command: e.command,\n app: e.app || \"-\",\n success: e.success !== undefined ? String(e.success) : \"-\",\n durationMs: e.durationMs ?? \"-\",\n }));\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(events, format));\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n });\n\n audit\n .command(\"search <query>\")\n .description(\"Search audit events by keyword\")\n .action(async (query: string) => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n initAudit(getConfigDir());\n\n try {\n const events = await searchAuditEvents(query);\n if (events.length === 0 && format !== \"json\") {\n console.log(`No audit events matching \"${query}\".`);\n return;\n }\n if (format !== \"json\") {\n const rows = events.map((e) => ({\n timestamp: e.timestamp,\n command: e.command,\n app: e.app || \"-\",\n success: e.success !== undefined ? String(e.success) : \"-\",\n }));\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(events, format));\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n });\n\n audit\n .command(\"clear\")\n .description(\"Clear audit log entries\")\n .option(\"--before <date>\", \"Clear entries before date (ISO 8601)\")\n .option(\"--dry-run\", \"Preview what would be cleared\")\n .action(async (options, cmd: Command) => {\n const dryRun = options.dryRun || isDryRun(cmd);\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n initAudit(getConfigDir());\n\n if (!dryRun && !options.before) {\n await requireConfirm(\"Clear all audit log entries?\", program);\n }\n\n try {\n const result = await clearAuditLog({\n before: options.before,\n dryRun,\n });\n if (dryRun) {\n console.log(`[dry-run] Would delete ${result.deleted} entries, ${result.remaining} would remain.`);\n } else {\n console.log(`Deleted ${result.deleted} entries. ${result.remaining} remaining.`);\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;AACA,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAKA,SAAS,sBAAsB,SAAwB;AAC5D,QAAM,QAAQ,QAAQ,QAAQ,OAAO,EAAE,YAAY,6BAA6B;AAEhF,QACG,QAAQ,MAAM,EACd,YAAY,0BAA0B,EACtC,OAAO,eAAe,0BAA0B,UAAU,EAAE,EAC5D,OAAO,kBAAkB,mCAAmC,EAC5D,OAAO,oBAAoB,wBAAwB,EACnD,OAAO,OAAO,YAAY;AACzB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAC9C,cAAU,aAAa,CAAC;AAExB,QAAI;AACF,YAAM,SAAS,MAAM,gBAAgB;AAAA,QACnC,OAAO,QAAQ;AAAA,QACf,OAAO,QAAQ;AAAA,QACf,SAAS,QAAQ;AAAA,MACnB,CAAC;AACD,UAAI,OAAO,WAAW,KAAK,WAAW,QAAQ;AAC5C,gBAAQ,IAAI,wBAAwB;AACpC;AAAA,MACF;AACA,UAAI,WAAW,QAAQ;AACrB,cAAM,OAAO,OAAO,IAAI,CAAC,OAAO;AAAA,UAC9B,WAAW,EAAE;AAAA,UACb,SAAS,EAAE;AAAA,UACX,KAAK,EAAE,OAAO;AAAA,UACd,SAAS,EAAE,YAAY,SAAY,OAAO,EAAE,OAAO,IAAI;AAAA,UACvD,YAAY,EAAE,cAAc;AAAA,QAC9B,EAAE;AACF,gBAAQ,IAAI,aAAa,MAAM,MAAM,CAAC;AAAA,MACxC,OAAO;AACL,gBAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,MAC1C;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AAChF,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,QACG,QAAQ,gBAAgB,EACxB,YAAY,gCAAgC,EAC5C,OAAO,OAAO,UAAkB;AAC/B,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAC9C,cAAU,aAAa,CAAC;AAExB,QAAI;AACF,YAAM,SAAS,MAAM,kBAAkB,KAAK;AAC5C,UAAI,OAAO,WAAW,KAAK,WAAW,QAAQ;AAC5C,gBAAQ,IAAI,6BAA6B,KAAK,IAAI;AAClD;AAAA,MACF;AACA,UAAI,WAAW,QAAQ;AACrB,cAAM,OAAO,OAAO,IAAI,CAAC,OAAO;AAAA,UAC9B,WAAW,EAAE;AAAA,UACb,SAAS,EAAE;AAAA,UACX,KAAK,EAAE,OAAO;AAAA,UACd,SAAS,EAAE,YAAY,SAAY,OAAO,EAAE,OAAO,IAAI;AAAA,QACzD,EAAE;AACF,gBAAQ,IAAI,aAAa,MAAM,MAAM,CAAC;AAAA,MACxC,OAAO;AACL,gBAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,MAC1C;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AAChF,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,yBAAyB,EACrC,OAAO,mBAAmB,sCAAsC,EAChE,OAAO,aAAa,+BAA+B,EACnD,OAAO,OAAO,SAAS,QAAiB;AACvC,UAAM,SAAS,QAAQ,UAAU,SAAS,GAAG;AAC7C,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAC9C,cAAU,aAAa,CAAC;AAExB,QAAI,CAAC,UAAU,CAAC,QAAQ,QAAQ;AAC9B,YAAM,eAAe,gCAAgC,OAAO;AAAA,IAC9D;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,cAAc;AAAA,QACjC,QAAQ,QAAQ;AAAA,QAChB;AAAA,MACF,CAAC;AACD,UAAI,QAAQ;AACV,gBAAQ,IAAI,0BAA0B,OAAO,OAAO,aAAa,OAAO,SAAS,gBAAgB;AAAA,MACnG,OAAO;AACL,gBAAQ,IAAI,WAAW,OAAO,OAAO,aAAa,OAAO,SAAS,aAAa;AAAA,MACjF;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AAChF,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AACL;","names":[]}
@@ -1,20 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/commands/docs.ts
4
- import { execFile } from "child_process";
5
- function registerDocsCommand(program) {
6
- program.command("docs").description("Open documentation in browser").action(() => {
7
- const url = "https://github.com/yasserstudio/gpc#readme";
8
- const platform = process.platform;
9
- const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
10
- execFile(cmd, [url], (error) => {
11
- if (error) {
12
- console.log(`Open in your browser: ${url}`);
13
- }
14
- });
15
- });
16
- }
17
- export {
18
- registerDocsCommand
19
- };
20
- //# sourceMappingURL=docs-CVTWIVMS.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/docs.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { execFile } from \"node:child_process\";\n\nexport function registerDocsCommand(program: Command): void {\n program\n .command(\"docs\")\n .description(\"Open documentation in browser\")\n .action(() => {\n const url = \"https://github.com/yasserstudio/gpc#readme\";\n const platform = process.platform;\n const cmd = platform === \"darwin\" ? \"open\" : platform === \"win32\" ? \"start\" : \"xdg-open\";\n execFile(cmd, [url], (error) => {\n if (error) {\n console.log(`Open in your browser: ${url}`);\n }\n });\n });\n}\n"],"mappings":";;;AACA,SAAS,gBAAgB;AAElB,SAAS,oBAAoB,SAAwB;AAC1D,UACG,QAAQ,MAAM,EACd,YAAY,+BAA+B,EAC3C,OAAO,MAAM;AACZ,UAAM,MAAM;AACZ,UAAM,WAAW,QAAQ;AACzB,UAAM,MAAM,aAAa,WAAW,SAAS,aAAa,UAAU,UAAU;AAC9E,aAAS,KAAK,CAAC,GAAG,GAAG,CAAC,UAAU;AAC9B,UAAI,OAAO;AACT,gBAAQ,IAAI,yBAAyB,GAAG,EAAE;AAAA,MAC5C;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACL;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/publish.ts"],"sourcesContent":["import { appendFile } from \"node:fs/promises\";\nimport type { GpcConfig, 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} 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\";\n\nconst PASS = \"\\u2713\";\nconst FAIL = \"\\u2717\";\nconst WARN = \"\\u26A0\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n console.error(\"Error: No package name. Use --app <package> or gpc config set app <package>\");\n process.exit(2);\n }\n return name;\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 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 = [\n \"Published successfully\\n\",\n ...formatChecks(result.validation.checks),\n ];\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 = [\n \"Dry run — no changes made\\n\",\n ...formatChecks(result.validation.checks),\n ];\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 = 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 .action(async (file: string, options) => {\n const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);\n if (noteSources.length > 1) {\n console.error(\n \"Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.\",\n );\n process.exit(2);\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 console.error(\n `Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`,\n );\n process.exit(2);\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 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 dryRun: true,\n });\n console.log(formatDryRunOutput(result as DryRunPublishResult, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\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 });\n\n if (!result.upload) {\n console.log(formatValidationOutput(result as PublishResult, format));\n auditEntry.success = false;\n auditEntry.error = \"Validation failed\";\n process.exit(1);\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 console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\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 +0,0 @@
1
- {"version":3,"sources":["../src/commands/releases.ts"],"sourcesContent":["import { appendFile } from \"node:fs/promises\";\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 } from \"@gpc-cli/api\";\nimport type { RetryLogEntry } 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} from \"@gpc-cli/core\";\nimport { formatOutput, sortResults, createSpinner } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport { isInteractive, promptSelect, promptInput, requireOption } from \"../prompt.js\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n console.error(\"Error: No package name. Use --app <package> or gpc config set app <package>\");\n process.exit(2);\n }\n return name;\n}\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(\"--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(\"--timeout <ms>\", \"Upload timeout in milliseconds (auto-scales with file size by default)\", parseInt)\n .action(async (file: string, options) => {\n const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);\n if (noteSources.length > 1) {\n console.error(\"Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.\");\n process.exit(2);\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 const client = await getClient(config, options.retryLog, options.timeout);\n\n if (isDryRun(program)) {\n try {\n const result = await uploadRelease(client, packageName, file, {\n track: options.track,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n dryRun: true,\n });\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\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 bundle...\");\n if (!program.opts()[\"quiet\"] && process.stderr.isTTY) spinner.start();\n\n try {\n let releaseNotes: { language: string; text: string }[] | undefined;\n 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 userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n releaseNotes,\n releaseName: options.name,\n mappingFile: options.mapping,\n });\n spinner.stop(\"Upload complete\");\n console.log(formatOutput(result, format));\n auditEntry.success = true;\n } catch (error) {\n spinner.fail(\"Upload failed\");\n auditEntry.success = false;\n auditEntry.error = error instanceof Error ? error.message : String(error);\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\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 try {\n const statuses = await getReleasesStatus(client, packageName, options.track);\n const sorted = Array.isArray(statuses) ? sortResults(statuses, options.sort) : statuses;\n if (format !== \"json\" && Array.isArray(sorted)) {\n const rows = sorted.map((s: Record<string, unknown>) => ({\n track: s[\"track\"] || \"-\",\n status: s[\"status\"] || \"-\",\n name: s[\"name\"] || \"-\",\n versionCodes: Array.isArray(s[\"versionCodes\"]) ? (s[\"versionCodes\"] as unknown[]).join(\", \") : \"-\",\n userFraction: s[\"userFraction\"] !== undefined ? String(s[\"userFraction\"]) : \"-\",\n }));\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(sorted, format));\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\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 .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.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 (isDryRun(program)) {\n printDryRun(\n {\n command: \"releases promote\",\n action: \"promote\",\n target: `${options.from} → ${options.to}`,\n details: { rollout: options.rollout },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const result = await promoteRelease(client, packageName, options.from, options.to, {\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n releaseNotes: options.notes ? [{ language: \"en-US\", text: options.notes }] : undefined,\n });\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\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 }\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 (isDryRun(program)) {\n printDryRun(\n {\n command: `releases rollout ${action}`,\n action: action,\n target: options.track,\n details: { percentage: options.to },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const result = await updateRollout(\n client,\n packageName,\n options.track,\n action,\n options.to ? Number(options.to) / 100 : undefined,\n );\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n }\n\n // Release notes\n releases\n .command(\"notes\")\n .description(\"Set release notes\")\n .argument(\"<action>\", \"Action: 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 console.error(\"Usage: gpc releases notes set --track <track> --notes <text>\");\n process.exit(2);\n }\n\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 let notesText = options.notes;\n if (options.file) {\n const { readFile } = await import(\"node:fs/promises\");\n notesText = await readFile(options.file, \"utf-8\");\n }\n\n if (!notesText && interactive) {\n notesText = await promptInput(\"Release notes text:\");\n }\n\n if (!notesText) {\n console.error(\"Provide --notes <text> or --file <path>\");\n process.exit(2);\n }\n\n console.log(`Release notes set for ${options.track} (${options.lang})`);\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 try {\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 import(\"@gpc-cli/api\").ExternallyHostedApk,\n );\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\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 try {\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 } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/status.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient, createReportingClient } from \"@gpc-cli/api\";\nimport {\n getAppStatus,\n formatStatusTable,\n formatStatusSummary,\n formatStatusDiff,\n computeStatusDiff,\n loadStatusCache,\n saveStatusCache,\n statusHasBreach,\n runWatchLoop,\n trackBreachState,\n sendNotification,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport type { AppStatus } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\nconst VALID_SECTIONS = new Set([\"releases\", \"vitals\", \"reviews\"]);\nconst VALID_FORMATS = new Set([\"table\", \"summary\"]);\n\nfunction parseSections(raw: string): string[] {\n const sections = raw.split(\",\").map((s) => s.trim().toLowerCase());\n for (const s of sections) {\n if (!VALID_SECTIONS.has(s)) {\n console.error(`Error: Unknown section \"${s}\". Valid sections: releases, vitals, reviews`);\n process.exit(2);\n }\n }\n return sections;\n}\n\nfunction resolveVitalThresholds(config: Record<string, unknown>) {\n const vitals = config[\"vitals\"] as Record<string, unknown> | undefined;\n const t = vitals?.[\"thresholds\"] as Record<string, unknown> | undefined;\n if (!t) return undefined;\n const toN = (v: unknown): number | undefined => {\n if (v === undefined || v === null) return undefined;\n const n = Number(v);\n return isNaN(n) ? undefined : n;\n };\n return {\n crashRate: toN(t[\"crashRate\"]),\n anrRate: toN(t[\"anrRate\"]),\n slowStartRate: toN(t[\"slowStartRate\"]),\n slowRenderingRate: toN(t[\"slowRenderingRate\"]),\n };\n}\n\nfunction resolvePackages(\n program: Command,\n config: { app?: string; profiles?: Record<string, { app?: string }> },\n allApps?: boolean,\n): string[] {\n const rootApp = (program.opts()[\"app\"] || config.app) as string | undefined;\n if (!allApps) return rootApp ? [rootApp] : [];\n\n const seen = new Set<string>();\n const result: string[] = [];\n if (rootApp) {\n seen.add(rootApp);\n result.push(rootApp);\n }\n for (const profile of Object.values(config.profiles ?? {})) {\n if (profile.app && !seen.has(profile.app)) {\n seen.add(profile.app);\n result.push(profile.app);\n }\n }\n return result;\n}\n\nfunction resolveWatchInterval(watch: string | boolean | undefined): number | null {\n if (watch === undefined) return null;\n if (watch === true || watch === \"\") return 30;\n const n = parseInt(String(watch), 10);\n return isNaN(n) ? 30 : n;\n}\n\nfunction makeRenderer(\n format: string,\n displayFormat: string,\n): (status: AppStatus) => string {\n return (status: AppStatus): string => {\n if (format === \"json\") {\n const sectionSet = new Set(status.sections);\n const filtered: Record<string, unknown> = {\n packageName: status.packageName,\n fetchedAt: status.fetchedAt,\n cached: status.cached,\n sections: status.sections,\n };\n if (sectionSet.has(\"releases\")) filtered[\"releases\"] = status.releases;\n if (sectionSet.has(\"vitals\")) filtered[\"vitals\"] = status.vitals;\n if (sectionSet.has(\"reviews\")) filtered[\"reviews\"] = status.reviews;\n return formatOutput(filtered, \"json\");\n }\n if (displayFormat === \"summary\") return formatStatusSummary(status);\n return formatStatusTable(status);\n };\n}\n\nexport function registerStatusCommand(program: Command): void {\n program\n .command(\"status\")\n .description(\"Unified app health snapshot: releases, vitals, and reviews\")\n .option(\"--days <n>\", \"Vitals window in days\", (v) => parseInt(v, 10), 7)\n .option(\"--cached\", \"Use last fetched data, skip API calls\")\n .option(\"--refresh\", \"Force live fetch, ignore cache TTL\")\n .option(\"--ttl <seconds>\", \"Cache TTL in seconds\", (v) => parseInt(v, 10), 3600)\n .option(\"--format <fmt>\", \"Display style: table (default) or summary\", \"table\")\n .option(\n \"--sections <list>\",\n \"Comma-separated sections: releases,vitals,reviews\",\n \"releases,vitals,reviews\",\n )\n .option(\"--watch [seconds]\", \"Poll every N seconds (min 10, default 30)\")\n .option(\"--since-last\", \"Show diff from last cached status\")\n .option(\"--all-apps\", \"Run status for all configured app profiles (max 5)\")\n .option(\"--notify\", \"Send desktop notification on threshold breach or clear\")\n .action(\n async (opts: {\n days: number;\n cached?: boolean;\n refresh?: boolean;\n ttl: number;\n format: string;\n sections: string;\n watch?: string | boolean;\n sinceLast?: boolean;\n allApps?: boolean;\n notify?: boolean;\n }) => {\n if (!VALID_FORMATS.has(opts.format)) {\n console.error(`Error: Unknown format \"${opts.format}\". Valid: table, summary`);\n process.exit(2);\n }\n\n const sections = parseSections(opts.sections);\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n const render = makeRenderer(format, opts.format);\n const vitalThresholds = resolveVitalThresholds(\n config as unknown as Record<string, unknown>,\n );\n const watchInterval = resolveWatchInterval(opts.watch);\n const packages = resolvePackages(program, config, opts.allApps);\n\n if (packages.length === 0) {\n console.error(\n \"Error: No package name. Use --app <package> or gpc config set app <package>\",\n );\n process.exit(2);\n }\n if (opts.allApps && packages.length > 5) {\n console.error(\n `Error: --all-apps found ${packages.length} apps (max 5). Use --app to target a specific app.`,\n );\n process.exit(2);\n }\n\n const authConfig = (config as unknown as Record<string, unknown>)[\"auth\"] as\n | Record<string, string>\n | undefined;\n\n const makeClients = async () => {\n const auth = await resolveAuth({ serviceAccountPath: authConfig?.[\"serviceAccount\"] });\n return {\n client: createApiClient({ auth }),\n reporting: createReportingClient({ auth }),\n };\n };\n\n let anyBreach = false;\n\n for (const packageName of packages) {\n if (packages.length > 1) console.log(`\\n=== ${packageName} ===`);\n\n try {\n const breach = await runStatusForPackage({\n packageName,\n opts,\n sections,\n format,\n vitalThresholds,\n watchInterval,\n render,\n makeClients,\n });\n if (breach) anyBreach = true;\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n if (packages.length === 1) process.exit(4);\n // For --all-apps, continue to next app on error\n }\n }\n\n if (anyBreach) process.exit(6);\n },\n );\n}\n\ninterface RunCtx {\n packageName: string;\n opts: {\n days: number;\n cached?: boolean;\n refresh?: boolean;\n ttl: number;\n sinceLast?: boolean;\n notify?: boolean;\n };\n sections: string[];\n format: string;\n vitalThresholds:\n | {\n crashRate?: number;\n anrRate?: number;\n slowStartRate?: number;\n slowRenderingRate?: number;\n }\n | undefined;\n watchInterval: number | null;\n render: (status: AppStatus) => string;\n makeClients: () => Promise<{\n client: ReturnType<typeof createApiClient>;\n reporting: ReturnType<typeof createReportingClient>;\n }>;\n}\n\n/** Returns true if a breach was detected. */\nasync function runStatusForPackage(ctx: RunCtx): Promise<boolean> {\n const { packageName, opts, sections, vitalThresholds, watchInterval, render } = ctx;\n\n const fetchLive = async (): Promise<AppStatus> => {\n const { client, reporting } = await ctx.makeClients();\n return getAppStatus(client, reporting, packageName, {\n days: opts.days,\n sections,\n vitalThresholds: vitalThresholds ?? undefined,\n });\n };\n\n const save = (status: AppStatus) => saveStatusCache(packageName, status, opts.ttl);\n\n // --watch: hand off entirely to runWatchLoop\n if (watchInterval !== null) {\n await runWatchLoop({ intervalSeconds: watchInterval, render, fetch: fetchLive, save });\n return false;\n }\n\n // --cached: serve from cache only\n if (opts.cached) {\n const cached = await loadStatusCache(packageName, opts.ttl);\n if (!cached) {\n console.error(\"Error: No cached status found. Run without --cached to fetch live data.\");\n process.exit(1);\n }\n console.log(render(cached));\n await handleNotify(packageName, cached, opts.notify);\n return statusHasBreach(cached);\n }\n\n // Capture prev status before fetching (for --since-last)\n const prevStatus = opts.sinceLast ? await loadStatusCache(packageName, Infinity) : null;\n\n // Try cache (unless --refresh)\n if (!opts.refresh) {\n const cached = await loadStatusCache(packageName, opts.ttl);\n if (cached) {\n printWithDiff(cached, prevStatus, opts.sinceLast, render, ctx.format);\n await handleNotify(packageName, cached, opts.notify);\n return statusHasBreach(cached);\n }\n }\n\n // Live fetch\n const status = await fetchLive();\n await save(status);\n\n printWithDiff(status, prevStatus, opts.sinceLast, render, ctx.format);\n await handleNotify(packageName, status, opts.notify);\n return statusHasBreach(status);\n}\n\nfunction printWithDiff(\n status: AppStatus,\n prevStatus: AppStatus | null,\n sinceLast: boolean | undefined,\n render: (s: AppStatus) => string,\n format: string,\n): void {\n console.log(render(status));\n\n if (sinceLast && prevStatus) {\n const since = new Date(prevStatus.fetchedAt).toLocaleString();\n console.log(\"\");\n console.log(formatStatusDiff(computeStatusDiff(prevStatus, status), since));\n } else if (sinceLast && !prevStatus && format !== \"json\") {\n console.log(\"\\n(No prior cached status to diff against)\");\n }\n}\n\nasync function handleNotify(\n packageName: string,\n status: AppStatus,\n notify: boolean | undefined,\n): Promise<void> {\n if (!notify) return;\n const breaching = statusHasBreach(status);\n const changed = await trackBreachState(packageName, breaching);\n if (changed) {\n const title = breaching ? \"GPC Alert\" : \"GPC Status\";\n const body = breaching\n ? `${packageName}: vitals threshold breached`\n : `${packageName}: vitals back to normal`;\n sendNotification(title, body);\n }\n}\n"],"mappings":"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","names":["status"]}