@gpc-cli/cli 0.9.34 → 0.9.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +20 -15
  2. package/dist/{anomalies-QZJGQXTZ.js → anomalies-KRRA75MJ.js} +1 -1
  3. package/dist/anomalies-KRRA75MJ.js.map +1 -0
  4. package/dist/{apps-CVBURB5V.js → apps-J2446UDA.js} +2 -2
  5. package/dist/{apps-CVBURB5V.js.map → apps-J2446UDA.js.map} +1 -1
  6. package/dist/{audit-A4BP27DN.js → audit-N2CRHWUN.js} +21 -6
  7. package/dist/audit-N2CRHWUN.js.map +1 -0
  8. package/dist/{auth-QSDK7NUO.js → auth-XGSTT5G5.js} +107 -9
  9. package/dist/auth-XGSTT5G5.js.map +1 -0
  10. package/dist/bin.js +2 -2
  11. package/dist/bin.js.map +1 -1
  12. package/dist/{bundle-7IF5FIB4.js → bundle-F43TD2BQ.js} +48 -14
  13. package/dist/bundle-F43TD2BQ.js.map +1 -0
  14. package/dist/{cache-SHWAVON6.js → cache-SLNFRTI2.js} +7 -3
  15. package/dist/cache-SLNFRTI2.js.map +1 -0
  16. package/dist/{chunk-7BXCQKJG.js → chunk-P5GF73XK.js} +1 -1
  17. package/dist/chunk-P5GF73XK.js.map +1 -0
  18. package/dist/{chunk-UH3TMIAL.js → chunk-U6ZTQ34I.js} +15 -17
  19. package/dist/chunk-U6ZTQ34I.js.map +1 -0
  20. package/dist/{chunk-5CSWPQO6.js → chunk-XHI7VKWZ.js} +113 -43
  21. package/dist/chunk-XHI7VKWZ.js.map +1 -0
  22. package/dist/{completion-C3PPWNS7.js → completion-BCHRJSAT.js} +23 -10
  23. package/dist/completion-BCHRJSAT.js.map +1 -0
  24. package/dist/{config-7EOY5HGL.js → config-F2U3KUHX.js} +4 -4
  25. package/dist/config-F2U3KUHX.js.map +1 -0
  26. package/dist/{data-safety-GDPKV5PN.js → data-safety-JR6PZ2BD.js} +11 -8
  27. package/dist/data-safety-JR6PZ2BD.js.map +1 -0
  28. package/dist/{device-tiers-GHIYJPMB.js → device-tiers-5SGJPSYG.js} +7 -8
  29. package/dist/device-tiers-5SGJPSYG.js.map +1 -0
  30. package/dist/{docs-HIGQU4UL.js → docs-7DUXIKA3.js} +3 -3
  31. package/dist/docs-7DUXIKA3.js.map +1 -0
  32. package/dist/{doctor-UKKOK55S.js → doctor-4BUPAVFT.js} +51 -7
  33. package/dist/doctor-4BUPAVFT.js.map +1 -0
  34. package/dist/enterprise-7THXNBTC.js +69 -0
  35. package/dist/enterprise-7THXNBTC.js.map +1 -0
  36. package/dist/{external-transactions-HCL7ROMN.js → external-transactions-O5P4QBIT.js} +5 -8
  37. package/dist/external-transactions-O5P4QBIT.js.map +1 -0
  38. package/dist/{feedback-W5MZMRF2.js → feedback-BZWHEADD.js} +3 -3
  39. package/dist/feedback-BZWHEADD.js.map +1 -0
  40. package/dist/games-X57AGM3E.js +80 -0
  41. package/dist/games-X57AGM3E.js.map +1 -0
  42. package/dist/{generated-apks-VX7HYZDU.js → generated-apks-KB2PLWDI.js} +2 -6
  43. package/dist/generated-apks-KB2PLWDI.js.map +1 -0
  44. package/dist/grants-TKQJ3IER.js +142 -0
  45. package/dist/grants-TKQJ3IER.js.map +1 -0
  46. package/dist/{iap-6XHJV5HX.js → iap-BNIAHBDN.js} +7 -3
  47. package/dist/iap-BNIAHBDN.js.map +1 -0
  48. package/dist/index.js +1 -1
  49. package/dist/{internal-sharing-E7SJYDW3.js → internal-sharing-M74VNIQ2.js} +7 -7
  50. package/dist/internal-sharing-M74VNIQ2.js.map +1 -0
  51. package/dist/{listings-TOYS6XBU.js → listings-IVHZJNES.js} +91 -10
  52. package/dist/listings-IVHZJNES.js.map +1 -0
  53. package/dist/{migrate-OHN2FDY6.js → migrate-SQT6RD6T.js} +2 -4
  54. package/dist/migrate-SQT6RD6T.js.map +1 -0
  55. package/dist/{one-time-products-2PK4QKWE.js → one-time-products-3WNXDKE3.js} +43 -28
  56. package/dist/one-time-products-3WNXDKE3.js.map +1 -0
  57. package/dist/{pricing-BYZSLN74.js → pricing-HMHZD44S.js} +1 -1
  58. package/dist/pricing-HMHZD44S.js.map +1 -0
  59. package/dist/{publish-I6WJGR4S.js → publish-EPZXLGKZ.js} +4 -9
  60. package/dist/publish-EPZXLGKZ.js.map +1 -0
  61. package/dist/{purchases-YRO6B7M6.js → purchases-7ZPVCN6D.js} +31 -2
  62. package/dist/purchases-7ZPVCN6D.js.map +1 -0
  63. package/dist/quickstart-EYNNOTVD.js +87 -0
  64. package/dist/quickstart-EYNNOTVD.js.map +1 -0
  65. package/dist/quota-UHIQQYOY.js +58 -0
  66. package/dist/quota-UHIQQYOY.js.map +1 -0
  67. package/dist/{recovery-S5UNJDBO.js → recovery-YGPOVUFD.js} +7 -3
  68. package/dist/recovery-YGPOVUFD.js.map +1 -0
  69. package/dist/{releases-ANC54YWF.js → releases-I7QQVKPJ.js} +62 -22
  70. package/dist/releases-I7QQVKPJ.js.map +1 -0
  71. package/dist/{reports-N5X66IUN.js → reports-2YX3RDOS.js} +1 -1
  72. package/dist/reports-2YX3RDOS.js.map +1 -0
  73. package/dist/{reviews-UHK4FGK6.js → reviews-MOVGATUI.js} +68 -7
  74. package/dist/reviews-MOVGATUI.js.map +1 -0
  75. package/dist/{status-DQYZ7A6Y.js → status-G3AMJ34G.js} +17 -5
  76. package/dist/status-G3AMJ34G.js.map +1 -0
  77. package/dist/{subscriptions-Z5ZPVUFM.js → subscriptions-LSOJID6H.js} +39 -2
  78. package/dist/subscriptions-LSOJID6H.js.map +1 -0
  79. package/dist/{testers-UWSUGGVT.js → testers-SDLVWQ2Z.js} +1 -1
  80. package/dist/testers-SDLVWQ2Z.js.map +1 -0
  81. package/dist/{tracks-XFUN7JJX.js → tracks-NERFFEDT.js} +2 -2
  82. package/dist/tracks-NERFFEDT.js.map +1 -0
  83. package/dist/train-PX5Z26PQ.js +150 -0
  84. package/dist/train-PX5Z26PQ.js.map +1 -0
  85. package/dist/{update-NIVJLUNH.js → update-ARIQO53C.js} +6 -12
  86. package/dist/update-ARIQO53C.js.map +1 -0
  87. package/dist/{users-JASXONRY.js → users-2YTC4Q36.js} +1 -1
  88. package/dist/users-2YTC4Q36.js.map +1 -0
  89. package/dist/{validate-MHLPENCM.js → validate-VNIS6OEB.js} +9 -4
  90. package/dist/validate-VNIS6OEB.js.map +1 -0
  91. package/dist/{version-7AI5IHVK.js → version-MXIJ4BUH.js} +11 -9
  92. package/dist/version-MXIJ4BUH.js.map +1 -0
  93. package/dist/{vitals-QGWOFH7E.js → vitals-H7DCI6JJ.js} +125 -7
  94. package/dist/vitals-H7DCI6JJ.js.map +1 -0
  95. package/package.json +7 -3
  96. package/dist/anomalies-QZJGQXTZ.js.map +0 -1
  97. package/dist/audit-A4BP27DN.js.map +0 -1
  98. package/dist/auth-QSDK7NUO.js.map +0 -1
  99. package/dist/bundle-7IF5FIB4.js.map +0 -1
  100. package/dist/cache-SHWAVON6.js.map +0 -1
  101. package/dist/chunk-5CSWPQO6.js.map +0 -1
  102. package/dist/chunk-7BXCQKJG.js.map +0 -1
  103. package/dist/chunk-UH3TMIAL.js.map +0 -1
  104. package/dist/completion-C3PPWNS7.js.map +0 -1
  105. package/dist/config-7EOY5HGL.js.map +0 -1
  106. package/dist/data-safety-GDPKV5PN.js.map +0 -1
  107. package/dist/device-tiers-GHIYJPMB.js.map +0 -1
  108. package/dist/docs-HIGQU4UL.js.map +0 -1
  109. package/dist/doctor-UKKOK55S.js.map +0 -1
  110. package/dist/external-transactions-HCL7ROMN.js.map +0 -1
  111. package/dist/feedback-W5MZMRF2.js.map +0 -1
  112. package/dist/generated-apks-VX7HYZDU.js.map +0 -1
  113. package/dist/iap-6XHJV5HX.js.map +0 -1
  114. package/dist/internal-sharing-E7SJYDW3.js.map +0 -1
  115. package/dist/listings-TOYS6XBU.js.map +0 -1
  116. package/dist/migrate-OHN2FDY6.js.map +0 -1
  117. package/dist/one-time-products-2PK4QKWE.js.map +0 -1
  118. package/dist/pricing-BYZSLN74.js.map +0 -1
  119. package/dist/publish-I6WJGR4S.js.map +0 -1
  120. package/dist/purchases-YRO6B7M6.js.map +0 -1
  121. package/dist/recovery-S5UNJDBO.js.map +0 -1
  122. package/dist/releases-ANC54YWF.js.map +0 -1
  123. package/dist/reports-N5X66IUN.js.map +0 -1
  124. package/dist/reviews-UHK4FGK6.js.map +0 -1
  125. package/dist/status-DQYZ7A6Y.js.map +0 -1
  126. package/dist/subscriptions-Z5ZPVUFM.js.map +0 -1
  127. package/dist/testers-UWSUGGVT.js.map +0 -1
  128. package/dist/tracks-XFUN7JJX.js.map +0 -1
  129. package/dist/update-NIVJLUNH.js.map +0 -1
  130. package/dist/users-JASXONRY.js.map +0 -1
  131. package/dist/validate-MHLPENCM.js.map +0 -1
  132. package/dist/version-7AI5IHVK.js.map +0 -1
  133. package/dist/vitals-QGWOFH7E.js.map +0 -1
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getOutputFormat
4
+ } from "./chunk-ELXAK7GI.js";
5
+
6
+ // src/commands/quota.ts
7
+ import { getQuotaUsage, formatOutput } from "@gpc-cli/core";
8
+ function printQuotaTable(usage, format) {
9
+ if (format === "json") {
10
+ console.log(formatOutput(usage, format));
11
+ return;
12
+ }
13
+ const dailyPct = (usage.dailyCallsUsed / usage.dailyCallsLimit * 100).toFixed(1);
14
+ const minPct = (usage.minuteCallsUsed / usage.minuteCallsLimit * 100).toFixed(1);
15
+ console.log(`
16
+ API Quota Usage`);
17
+ console.log(`${"\u2500".repeat(45)}`);
18
+ console.log(
19
+ `Daily: ${usage.dailyCallsUsed.toLocaleString()} / ${usage.dailyCallsLimit.toLocaleString()} (${dailyPct}%)`
20
+ );
21
+ console.log(` Remaining: ${usage.dailyCallsRemaining.toLocaleString()}`);
22
+ console.log(`Minute: ${usage.minuteCallsUsed} / ${usage.minuteCallsLimit} (${minPct}%)`);
23
+ console.log(` Remaining: ${usage.minuteCallsRemaining}`);
24
+ if (usage.topCommands.length > 0) {
25
+ console.log(`
26
+ Top commands today:`);
27
+ for (const { command, count } of usage.topCommands) {
28
+ console.log(` ${command.padEnd(30)} ${count}`);
29
+ }
30
+ }
31
+ }
32
+ function registerQuotaCommand(program) {
33
+ const quota = program.command("quota").description("View Google Play API quota usage tracked from local audit log");
34
+ quota.command("status").description("Show daily and per-minute API call counts").action(async () => {
35
+ const format = getOutputFormat(program, {});
36
+ try {
37
+ const usage = await getQuotaUsage();
38
+ printQuotaTable(usage, format);
39
+ } catch (error) {
40
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
41
+ process.exit(1);
42
+ }
43
+ });
44
+ quota.command("usage").description("Show API quota usage breakdown (alias for quota status)").action(async () => {
45
+ const format = getOutputFormat(program, {});
46
+ try {
47
+ const usage = await getQuotaUsage();
48
+ printQuotaTable(usage, format);
49
+ } catch (error) {
50
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
51
+ process.exit(1);
52
+ }
53
+ });
54
+ }
55
+ export {
56
+ registerQuotaCommand
57
+ };
58
+ //# sourceMappingURL=quota-UHIQQYOY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/quota.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { getQuotaUsage, formatOutput } from \"@gpc-cli/core\";\nimport type { QuotaUsage } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\nfunction printQuotaTable(usage: QuotaUsage, format: string): void {\n if (format === \"json\") {\n console.log(formatOutput(usage, format));\n return;\n }\n\n const dailyPct = ((usage.dailyCallsUsed / usage.dailyCallsLimit) * 100).toFixed(1);\n const minPct = ((usage.minuteCallsUsed / usage.minuteCallsLimit) * 100).toFixed(1);\n\n console.log(`\\nAPI Quota Usage`);\n console.log(`${\"─\".repeat(45)}`);\n console.log(\n `Daily: ${usage.dailyCallsUsed.toLocaleString()} / ${usage.dailyCallsLimit.toLocaleString()} (${dailyPct}%)`,\n );\n console.log(` Remaining: ${usage.dailyCallsRemaining.toLocaleString()}`);\n console.log(`Minute: ${usage.minuteCallsUsed} / ${usage.minuteCallsLimit} (${minPct}%)`);\n console.log(` Remaining: ${usage.minuteCallsRemaining}`);\n\n if (usage.topCommands.length > 0) {\n console.log(`\\nTop commands today:`);\n for (const { command, count } of usage.topCommands) {\n console.log(` ${command.padEnd(30)} ${count}`);\n }\n }\n}\n\nexport function registerQuotaCommand(program: Command): void {\n const quota = program\n .command(\"quota\")\n .description(\"View Google Play API quota usage tracked from local audit log\");\n\n quota\n .command(\"status\")\n .description(\"Show daily and per-minute API call counts\")\n .action(async () => {\n const format = getOutputFormat(program, {});\n try {\n const usage = await getQuotaUsage();\n printQuotaTable(usage, format);\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n });\n\n quota\n .command(\"usage\")\n .description(\"Show API quota usage breakdown (alias for quota status)\")\n .action(async () => {\n const format = getOutputFormat(program, {});\n try {\n const usage = await getQuotaUsage();\n printQuotaTable(usage, format);\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,eAAe,oBAAoB;AAI5C,SAAS,gBAAgB,OAAmB,QAAsB;AAChE,MAAI,WAAW,QAAQ;AACrB,YAAQ,IAAI,aAAa,OAAO,MAAM,CAAC;AACvC;AAAA,EACF;AAEA,QAAM,YAAa,MAAM,iBAAiB,MAAM,kBAAmB,KAAK,QAAQ,CAAC;AACjF,QAAM,UAAW,MAAM,kBAAkB,MAAM,mBAAoB,KAAK,QAAQ,CAAC;AAEjF,UAAQ,IAAI;AAAA,gBAAmB;AAC/B,UAAQ,IAAI,GAAG,SAAI,OAAO,EAAE,CAAC,EAAE;AAC/B,UAAQ;AAAA,IACN,YAAY,MAAM,eAAe,eAAe,CAAC,MAAM,MAAM,gBAAgB,eAAe,CAAC,KAAK,QAAQ;AAAA,EAC5G;AACA,UAAQ,IAAI,uBAAuB,MAAM,oBAAoB,eAAe,CAAC,EAAE;AAC/E,UAAQ,IAAI,YAAY,MAAM,eAAe,MAAM,MAAM,gBAAgB,KAAK,MAAM,IAAI;AACxF,UAAQ,IAAI,uBAAuB,MAAM,oBAAoB,EAAE;AAE/D,MAAI,MAAM,YAAY,SAAS,GAAG;AAChC,YAAQ,IAAI;AAAA,oBAAuB;AACnC,eAAW,EAAE,SAAS,MAAM,KAAK,MAAM,aAAa;AAClD,cAAQ,IAAI,KAAK,QAAQ,OAAO,EAAE,CAAC,IAAI,KAAK,EAAE;AAAA,IAChD;AAAA,EACF;AACF;AAEO,SAAS,qBAAqB,SAAwB;AAC3D,QAAM,QAAQ,QACX,QAAQ,OAAO,EACf,YAAY,+DAA+D;AAE9E,QACG,QAAQ,QAAQ,EAChB,YAAY,2CAA2C,EACvD,OAAO,YAAY;AAClB,UAAM,SAAS,gBAAgB,SAAS,CAAC,CAAC;AAC1C,QAAI;AACF,YAAM,QAAQ,MAAM,cAAc;AAClC,sBAAgB,OAAO,MAAM;AAAA,IAC/B,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,yDAAyD,EACrE,OAAO,YAAY;AAClB,UAAM,SAAS,gBAAgB,SAAS,CAAC,CAAC;AAC1C,QAAI;AACF,YAAM,QAAQ,MAAM,cAAc;AAClC,sBAAgB,OAAO,MAAM;AAAA,IAC/B,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":[]}
@@ -44,7 +44,9 @@ function registerRecoveryCommands(program) {
44
44
  const format = getOutputFormat(program, config);
45
45
  try {
46
46
  if (options.versionCode === void 0) {
47
- console.error("Error: --version-code is required. The API requires a version code to filter recovery actions.");
47
+ console.error(
48
+ "Error: --version-code is required. The API requires a version code to filter recovery actions."
49
+ );
48
50
  console.error("Usage: gpc recovery list --version-code <code>");
49
51
  process.exit(2);
50
52
  }
@@ -83,7 +85,9 @@ function registerRecoveryCommands(program) {
83
85
  const client = await getClient(config);
84
86
  try {
85
87
  await cancelRecoveryAction(client, packageName, id);
86
- console.log(formatOutput({ success: true, appRecoveryId: id, action: "cancelled" }, format));
88
+ console.log(
89
+ formatOutput({ success: true, appRecoveryId: id, action: "cancelled" }, format)
90
+ );
87
91
  } catch (error) {
88
92
  console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
89
93
  process.exit(4);
@@ -189,4 +193,4 @@ function registerRecoveryCommands(program) {
189
193
  export {
190
194
  registerRecoveryCommands
191
195
  };
192
- //# sourceMappingURL=recovery-S5UNJDBO.js.map
196
+ //# sourceMappingURL=recovery-YGPOVUFD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/recovery.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient } from \"@gpc-cli/api\";\nimport {\n listRecoveryActions,\n cancelRecoveryAction,\n deployRecoveryAction,\n createRecoveryAction,\n addRecoveryTargeting,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport { getOutputFormat } from \"../format.js\";\nimport { requireConfirm } from \"../prompt.js\";\nimport { readFileSync } from \"node:fs\";\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\nasync function getClient(config: GpcConfig) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createApiClient({ auth });\n}\n\nexport function registerRecoveryCommands(program: Command): void {\n const recovery = program.command(\"recovery\").description(\"Manage app recovery actions\");\n\n recovery\n .command(\"list\")\n .description(\"List app recovery actions\")\n .option(\"--version-code <code>\", \"Filter by version code\", parseInt)\n .option(\"--limit <n>\", \"Maximum results to return\")\n .option(\"--next-page <token>\", \"Pagination token for next page\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n if (options.versionCode === undefined) {\n console.error(\n \"Error: --version-code is required. The API requires a version code to filter recovery actions.\",\n );\n console.error(\"Usage: gpc recovery list --version-code <code>\");\n process.exit(2);\n }\n const result = await listRecoveryActions(client, packageName, options.versionCode);\n if (Array.isArray(result) && result.length === 0) {\n if (format === \"json\") {\n console.log(formatOutput([], format));\n } else {\n console.log(\"No recovery actions found.\");\n }\n return;\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 recovery\n .command(\"cancel <id>\")\n .description(\"Cancel a recovery action\")\n .action(async (id: string) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"recovery cancel\",\n action: \"cancel\",\n target: id,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n await requireConfirm(`Cancel recovery action ${id}?`, program);\n\n const client = await getClient(config);\n\n try {\n await cancelRecoveryAction(client, packageName, id);\n console.log(\n formatOutput({ success: true, appRecoveryId: id, action: \"cancelled\" }, format),\n );\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n recovery\n .command(\"deploy <id>\")\n .description(\"Deploy a recovery action\")\n .action(async (id: string) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"recovery deploy\",\n action: \"deploy\",\n target: id,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n await requireConfirm(`Deploy recovery action ${id}?`, program);\n\n const client = await getClient(config);\n\n try {\n await deployRecoveryAction(client, packageName, id);\n console.log(formatOutput({ success: true, appRecoveryId: id, action: \"deployed\" }, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n recovery\n .command(\"create\")\n .description(\"Create a new app recovery action\")\n .requiredOption(\"--file <path>\", \"Path to JSON file with recovery action data\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(readFileSync(options.file, \"utf-8\"));\n } catch (err) {\n console.error(\n `Error: Could not read recovery action data from ${options.file}: ${err instanceof Error ? err.message : String(err)}`,\n );\n process.exit(2);\n }\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"recovery create\",\n action: \"create recovery action\",\n target: packageName,\n details: data,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const result = await createRecoveryAction(client, packageName, data);\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 recovery\n .command(\"add-targeting <action-id>\")\n .description(\"Add targeting rules to an existing recovery action\")\n .requiredOption(\"--file <path>\", \"Path to JSON file with targeting data\")\n .action(async (actionId: string, options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n\n let targeting: Record<string, unknown>;\n try {\n targeting = JSON.parse(readFileSync(options.file, \"utf-8\"));\n } catch (err) {\n console.error(\n `Error: Could not read targeting data from ${options.file}: ${err instanceof Error ? err.message : String(err)}`,\n );\n process.exit(2);\n }\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"recovery add-targeting\",\n action: \"add targeting to recovery action\",\n target: actionId,\n details: targeting,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const result = await addRecoveryTargeting(client, packageName, actionId, targeting);\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"],"mappings":"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","names":[]}
@@ -10,6 +10,7 @@ import {
10
10
  isInteractive,
11
11
  promptInput,
12
12
  promptSelect,
13
+ requireConfirm,
13
14
  requireOption
14
15
  } from "./chunk-NV75I5VP.js";
15
16
 
@@ -56,7 +57,11 @@ async function getClient(config, retryLogPath, uploadTimeout) {
56
57
  }
57
58
  function registerReleasesCommands(program) {
58
59
  const releases = program.command("releases").description("Manage releases and rollouts");
59
- 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) => {
60
+ 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(
61
+ "--timeout <ms>",
62
+ "Upload timeout in milliseconds (auto-scales with file size by default)",
63
+ parseInt
64
+ ).action(async (file, options) => {
60
65
  try {
61
66
  await stat(file);
62
67
  } catch {
@@ -70,7 +75,9 @@ function registerReleasesCommands(program) {
70
75
  }
71
76
  const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);
72
77
  if (noteSources.length > 1) {
73
- console.error("Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.");
78
+ console.error(
79
+ "Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one."
80
+ );
74
81
  process.exit(2);
75
82
  }
76
83
  const config = await loadConfig();
@@ -98,7 +105,9 @@ function registerReleasesCommands(program) {
98
105
  if (options.rollout !== void 0) {
99
106
  const rollout2 = Number(options.rollout);
100
107
  if (!Number.isFinite(rollout2) || rollout2 < 1 || rollout2 > 100) {
101
- console.error(`Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`);
108
+ console.error(
109
+ `Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`
110
+ );
102
111
  process.exit(2);
103
112
  }
104
113
  }
@@ -114,7 +123,9 @@ function registerReleasesCommands(program) {
114
123
  const startTime = Date.now();
115
124
  progressInterval = setInterval(() => {
116
125
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(0);
117
- process.stderr.write(`\r ${FRAMES[frame % FRAMES.length]} Uploading ${basename(file)} ${sizeMB} MB (${elapsed}s) `);
126
+ process.stderr.write(
127
+ `\r ${FRAMES[frame % FRAMES.length]} Uploading ${basename(file)} ${sizeMB} MB (${elapsed}s) `
128
+ );
118
129
  frame++;
119
130
  }, 120);
120
131
  }
@@ -196,18 +207,25 @@ function registerReleasesCommands(program) {
196
207
  const rawStatuses = await getReleasesStatus(client, packageName, options.track);
197
208
  const statuses = options.track ? Array.isArray(rawStatuses) ? rawStatuses.filter((s) => s.track === options.track) : rawStatuses : rawStatuses;
198
209
  const sorted = Array.isArray(statuses) ? options.sort ? sortResults(statuses, options.sort) : [...statuses].sort((a, b) => {
199
- const ai = TRACK_ORDER.indexOf(String(a["track"] ?? ""));
200
- const bi = TRACK_ORDER.indexOf(String(b["track"] ?? ""));
210
+ const ai = TRACK_ORDER.indexOf(
211
+ String(a["track"] ?? "")
212
+ );
213
+ const bi = TRACK_ORDER.indexOf(
214
+ String(b["track"] ?? "")
215
+ );
201
216
  return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);
202
217
  }) : statuses;
203
218
  if (format !== "json" && Array.isArray(sorted)) {
204
- const rows = sorted.map((s) => ({
205
- track: s["track"] || "-",
206
- status: s["status"] || "-",
207
- name: s["name"] || "-",
208
- versionCodes: Array.isArray(s["versionCodes"]) ? s["versionCodes"].join(", ") : "-",
209
- userFraction: s["userFraction"] !== void 0 ? `${Math.round(Number(s["userFraction"]) * 100)}%` : "\u2014"
210
- }));
219
+ const rows = sorted.map((s) => {
220
+ const sr = s;
221
+ return {
222
+ track: sr["track"] || "-",
223
+ status: sr["status"] || "-",
224
+ name: sr["name"] || "-",
225
+ versionCodes: Array.isArray(sr["versionCodes"]) ? sr["versionCodes"].join(", ") : "-",
226
+ userFraction: sr["userFraction"] !== void 0 ? `${Math.round(Number(sr["userFraction"]) * 100)}%` : "\u2014"
227
+ };
228
+ });
211
229
  console.log(formatOutput(rows, format));
212
230
  } else {
213
231
  console.log(formatOutput(sorted, format));
@@ -242,13 +260,17 @@ function registerReleasesCommands(program) {
242
260
  interactive
243
261
  );
244
262
  if (options.from === options.to) {
245
- console.error(`Error: --from and --to must be different tracks (both are "${options.from}")`);
263
+ console.error(
264
+ `Error: --from and --to must be different tracks (both are "${options.from}")`
265
+ );
246
266
  process.exit(2);
247
267
  }
248
268
  if (options.rollout !== void 0) {
249
269
  const rollout2 = Number(options.rollout);
250
270
  if (!Number.isFinite(rollout2) || rollout2 < 1 || rollout2 > 100) {
251
- console.error(`Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`);
271
+ console.error(
272
+ `Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`
273
+ );
252
274
  process.exit(2);
253
275
  }
254
276
  }
@@ -316,9 +338,17 @@ function registerReleasesCommands(program) {
316
338
  process.exit(2);
317
339
  }
318
340
  }
341
+ if (action === "halt") {
342
+ await requireConfirm(
343
+ `Halt rollout on track "${options.track}" for ${packageName}?`,
344
+ program
345
+ );
346
+ }
319
347
  if (isDryRun(program)) {
320
348
  if (action === "increase" && options.vitalsGate) {
321
- console.error("Warning: --vitals-gate is ignored in --dry-run mode. Gate will run on live execution.");
349
+ console.error(
350
+ "Warning: --vitals-gate is ignored in --dry-run mode. Gate will run on live execution."
351
+ );
322
352
  }
323
353
  printDryRun(
324
354
  {
@@ -344,22 +374,32 @@ function registerReleasesCommands(program) {
344
374
  if (action === "increase" && options.vitalsGate) {
345
375
  const threshold = config.vitals?.thresholds?.crashRate;
346
376
  if (!threshold) {
347
- console.error("Warning: --vitals-gate requires vitals.thresholds.crashRate in config. Skipping gate.");
377
+ console.error(
378
+ "Warning: --vitals-gate requires vitals.thresholds.crashRate in config. Skipping gate."
379
+ );
348
380
  } else {
349
381
  try {
350
382
  const { auth: authConfig } = config;
351
- const vitalsAuth = await resolveAuth({ serviceAccountPath: authConfig?.serviceAccount });
383
+ const vitalsAuth = await resolveAuth({
384
+ serviceAccountPath: authConfig?.serviceAccount
385
+ });
352
386
  const reportingClient = createReportingClient({ auth: vitalsAuth });
353
- const vitalsResult = await getVitalsCrashes(reportingClient, packageName, { days: 1 });
387
+ const vitalsResult = await getVitalsCrashes(reportingClient, packageName, {
388
+ days: 1
389
+ });
354
390
  const latest = vitalsResult.data?.[0]?.crashRate;
355
391
  const check = checkThreshold(latest, threshold);
356
392
  if (check.breached) {
357
393
  await updateRollout(client, packageName, options.track, "halt");
358
- console.error(`Vitals gate: crash rate ${String(latest)}% > threshold ${String(threshold)}%. Rollout halted.`);
394
+ console.error(
395
+ `Vitals gate: crash rate ${String(latest)}% > threshold ${String(threshold)}%. Rollout halted.`
396
+ );
359
397
  process.exit(6);
360
398
  }
361
399
  } catch (vitalsErr) {
362
- console.error(`Warning: Vitals gate check failed: ${vitalsErr instanceof Error ? vitalsErr.message : String(vitalsErr)}`);
400
+ console.error(
401
+ `Warning: Vitals gate check failed: ${vitalsErr instanceof Error ? vitalsErr.message : String(vitalsErr)}`
402
+ );
363
403
  }
364
404
  }
365
405
  }
@@ -444,4 +484,4 @@ function registerReleasesCommands(program) {
444
484
  export {
445
485
  registerReleasesCommands
446
486
  };
447
- //# sourceMappingURL=releases-ANC54YWF.js.map
487
+ //# sourceMappingURL=releases-I7QQVKPJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/releases.ts"],"sourcesContent":["import { appendFile, stat } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient, createReportingClient } from \"@gpc-cli/api\";\nimport type { RetryLogEntry, ExternallyHostedApk } 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 getVitalsCrashes,\n checkThreshold,\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 {\n isInteractive,\n promptSelect,\n promptInput,\n requireOption,\n requireConfirm,\n} from \"../prompt.js\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n 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(\n \"--timeout <ms>\",\n \"Upload timeout in milliseconds (auto-scales with file size by default)\",\n parseInt,\n )\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 ext = extname(file).toLowerCase();\n if (ext !== \".aab\" && ext !== \".apk\") {\n console.error(`Error: Expected .aab or .apk file, got \"${ext || \"(no extension)\"}\"`);\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 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(\n `Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`,\n );\n process.exit(2);\n }\n }\n\n const { size: fileSize } = await stat(file);\n const jsonMode = format === \"json\";\n const client = await getClient(config, options.retryLog, options.timeout);\n\n // The API layer buffers the full file before sending, so byte-level streaming progress\n // isn't available. Use a time-based animation instead.\n const showProgress = !jsonMode && process.stderr.isTTY && !program.opts()[\"quiet\"];\n const sizeMB = (fileSize / (1024 * 1024)).toFixed(1);\n const FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n let progressInterval: ReturnType<typeof setInterval> | undefined;\n if (showProgress) {\n let frame = 0;\n const startTime = Date.now();\n progressInterval = setInterval(() => {\n const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);\n process.stderr.write(\n `\\r ${FRAMES[frame % FRAMES.length]} Uploading ${basename(file)} ${sizeMB} MB (${elapsed}s) `,\n );\n frame++;\n }, 120);\n }\n const onProgress = undefined;\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 ${basename(file)} (${sizeMB} MB)...`);\n if (!showProgress) 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 onProgress,\n });\n if (progressInterval) {\n clearInterval(progressInterval);\n process.stderr.write(`\\r ✓ Uploaded ${basename(file)} ${sizeMB} MB\\n`);\n }\n spinner.stop(\"Upload complete\");\n console.log(formatOutput(result, format));\n auditEntry.success = true;\n } catch (error) {\n if (progressInterval) {\n clearInterval(progressInterval);\n process.stderr.write(\"\\n\");\n }\n spinner.fail(\"Upload failed\");\n auditEntry.success = false;\n auditEntry.error = error instanceof Error ? error.message : String(error);\n 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 rawStatuses = await getReleasesStatus(client, packageName, options.track);\n const statuses = options.track\n ? Array.isArray(rawStatuses)\n ? rawStatuses.filter((s: any) => s.track === options.track)\n : rawStatuses\n : rawStatuses;\n const sorted = Array.isArray(statuses)\n ? options.sort\n ? sortResults(statuses, options.sort)\n : [...statuses].sort((a, b) => {\n const ai = TRACK_ORDER.indexOf(\n String((a as unknown as Record<string, unknown>)[\"track\"] ?? \"\"),\n );\n const bi = TRACK_ORDER.indexOf(\n String((b as unknown as Record<string, unknown>)[\"track\"] ?? \"\"),\n );\n return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);\n })\n : statuses;\n if (format !== \"json\" && Array.isArray(sorted)) {\n const rows = sorted.map((s: unknown) => {\n const sr = s as Record<string, unknown>;\n return {\n track: sr[\"track\"] || \"-\",\n status: sr[\"status\"] || \"-\",\n name: sr[\"name\"] || \"-\",\n versionCodes: Array.isArray(sr[\"versionCodes\"])\n ? (sr[\"versionCodes\"] as unknown[]).join(\", \")\n : \"-\",\n userFraction:\n sr[\"userFraction\"] !== undefined\n ? `${Math.round(Number(sr[\"userFraction\"]) * 100)}%`\n : \"—\",\n };\n });\n 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(\n `Error: --from and --to must be different tracks (both are \"${options.from}\")`,\n );\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(\n `Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`,\n );\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 cmd.option(\"--vitals-gate\", \"Halt rollout if crash rate exceeds configured threshold\");\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 // Require confirmation for destructive rollout halt\n if (action === \"halt\") {\n await requireConfirm(\n `Halt rollout on track \"${options.track}\" for ${packageName}?`,\n program,\n );\n }\n\n if (isDryRun(program)) {\n if (action === \"increase\" && options.vitalsGate) {\n console.error(\n \"Warning: --vitals-gate is ignored in --dry-run mode. Gate will run on live execution.\",\n );\n }\n printDryRun(\n {\n command: `releases rollout ${action}`,\n action: action,\n target: options.track,\n details: { percentage: options.to !== undefined ? `${options.to}%` : undefined },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n 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\n // Vitals gate: check crash rate after rollout increase\n if (action === \"increase\" && options.vitalsGate) {\n const threshold = (config as any).vitals?.thresholds?.crashRate;\n if (!threshold) {\n console.error(\n \"Warning: --vitals-gate requires vitals.thresholds.crashRate in config. Skipping gate.\",\n );\n } else {\n try {\n const { auth: authConfig } = config;\n const vitalsAuth = await resolveAuth({\n serviceAccountPath: authConfig?.serviceAccount,\n });\n const reportingClient = createReportingClient({ auth: vitalsAuth });\n const vitalsResult = await getVitalsCrashes(reportingClient, packageName, {\n days: 1,\n });\n const latest = (vitalsResult as any).data?.[0]?.crashRate;\n const check = checkThreshold(latest, threshold);\n if (check.breached) {\n await updateRollout(client, packageName, options.track, \"halt\");\n console.error(\n `Vitals gate: crash rate ${String(latest)}% > threshold ${String(threshold)}%. Rollout halted.`,\n );\n process.exit(6);\n }\n } catch (vitalsErr) {\n console.error(\n `Warning: Vitals gate check failed: ${vitalsErr instanceof Error ? vitalsErr.message : String(vitalsErr)}`,\n );\n }\n }\n }\n\n console.log(formatOutput(result, format));\n } 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(\"Get or set release notes\")\n .argument(\"<action>\", \"Action: get|set\")\n .option(\"--track <track>\", \"Track name\")\n .option(\"--lang <language>\", \"Language code\", \"en-US\")\n .option(\"--notes <text>\", \"Release notes text\")\n .option(\"--file <path>\", \"Read notes from file\")\n .action(async (action: string, options) => {\n if (action === \"set\") {\n console.error(\n \"Error: gpc releases notes set is not implemented as a standalone command.\\n\" +\n \"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 if (action === \"get\") {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n const track = options.track ?? \"internal\";\n const statuses = await getReleasesStatus(client, packageName, track);\n const notes = Array.isArray(statuses)\n ? statuses.flatMap((s: any) => s.releaseNotes ?? [])\n : ((statuses as any).releaseNotes ?? []);\n if (notes.length === 0) {\n console.log(\"No release notes found.\");\n return;\n }\n console.log(formatOutput(notes, format));\n return;\n }\n\n console.error(\"Usage: gpc releases notes <get|set> --track <track>\");\n process.exit(2);\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 unknown as 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"]}
@@ -77,4 +77,4 @@ function registerReportsCommands(program) {
77
77
  export {
78
78
  registerReportsCommands
79
79
  };
80
- //# sourceMappingURL=reports-N5X66IUN.js.map
80
+ //# sourceMappingURL=reports-2YX3RDOS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/reports.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport {\n parseMonth,\n isValidReportType,\n isFinancialReportType,\n isStatsReportType,\n} from \"@gpc-cli/core\";\n\nconst FINANCIAL_REPORT_MESSAGE = `Financial reports (earnings, sales, estimated_sales, play_balance) are not available through the Google Play Developer API.\n\nThese reports are delivered as CSV files in Google Cloud Storage buckets.\nTo access them:\n 1. Open Google Play Console → \"Download reports\" → \"Financial\"\n 2. For programmatic access, use the GCS bucket URI shown in Play Console\n with the Google Cloud Storage API or gsutil.\n\nSee: https://support.google.com/googleplay/android-developer/answer/6135870`;\n\nconst STATS_REPORT_MESSAGE = `Stats reports (installs, crashes, ratings, reviews, store_performance, subscriptions) are not available through the Google Play Developer API as downloadable CSVs.\n\nThese reports are delivered as CSV files in Google Cloud Storage buckets.\nTo access them:\n 1. Open Google Play Console → \"Download reports\" → \"Statistics\"\n 2. For programmatic access, use the GCS bucket URI shown in Play Console\n with the Google Cloud Storage API or gsutil.\n\nFor real-time crash and ANR metrics, use:\n gpc vitals crashes\n gpc vitals anr\n gpc vitals overview\n\nSee: https://support.google.com/googleplay/android-developer/answer/6135870`;\n\nexport function registerReportsCommands(program: Command): void {\n const reports = program\n .command(\"reports\")\n .description(\"Financial and stats reports (via Google Cloud Storage)\");\n\n reports\n .command(\"list <report-type>\")\n .description(\"List available reports\")\n .option(\"--month <YYYY-MM>\", \"Report month (e.g., 2026-03)\")\n .option(\"--limit <n>\", \"Maximum results to return\")\n .option(\"--next-page <token>\", \"Pagination token for next page\")\n .action(async (reportType: string, options) => {\n if (!isValidReportType(reportType)) {\n console.error(\n `Error: Invalid report type \"${reportType}\". Valid types: earnings, sales, estimated_sales, installs, crashes, ratings, reviews, store_performance, subscriptions, play_balance`,\n );\n process.exit(2);\n }\n\n // Validate month format if provided\n if (options.month) {\n parseMonth(options.month);\n }\n\n if (isFinancialReportType(reportType)) {\n console.log(FINANCIAL_REPORT_MESSAGE);\n } else {\n console.log(STATS_REPORT_MESSAGE);\n }\n process.exit(2);\n });\n\n const download = reports.command(\"download\").description(\"Download a report\");\n\n download\n .command(\"financial\")\n .description(\"Download a financial report\")\n .option(\"--month <YYYY-MM>\", \"Report month (e.g., 2026-03)\")\n .option(\"--type <report-type>\", \"Report type\", \"earnings\")\n .option(\"--output-file <path>\", \"Save to file instead of stdout\")\n .action(async (options) => {\n if (options.type && !isFinancialReportType(options.type)) {\n console.error(\n `Error: Invalid financial report type \"${options.type}\". Valid types: earnings, sales, estimated_sales, play_balance`,\n );\n process.exit(2);\n }\n\n console.log(FINANCIAL_REPORT_MESSAGE);\n process.exit(2);\n });\n\n download\n .command(\"stats\")\n .description(\"Download a stats report\")\n .option(\"--month <YYYY-MM>\", \"Report month (e.g., 2026-03)\")\n .option(\n \"--type <report-type>\",\n \"Report type (installs, crashes, ratings, reviews, store_performance, subscriptions)\",\n )\n .option(\"--output-file <path>\", \"Save to file instead of stdout\")\n .action(async (options) => {\n if (options.type && !isStatsReportType(options.type)) {\n console.error(\n `Error: Invalid stats report type \"${options.type}\". Valid types: installs, crashes, ratings, reviews, store_performance, subscriptions`,\n );\n process.exit(2);\n }\n\n console.log(STATS_REPORT_MESSAGE);\n process.exit(2);\n });\n}\n"],"mappings":";;;AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,IAAM,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUjC,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAetB,SAAS,wBAAwB,SAAwB;AAC9D,QAAM,UAAU,QACb,QAAQ,SAAS,EACjB,YAAY,wDAAwD;AAEvE,UACG,QAAQ,oBAAoB,EAC5B,YAAY,wBAAwB,EACpC,OAAO,qBAAqB,8BAA8B,EAC1D,OAAO,eAAe,2BAA2B,EACjD,OAAO,uBAAuB,gCAAgC,EAC9D,OAAO,OAAO,YAAoB,YAAY;AAC7C,QAAI,CAAC,kBAAkB,UAAU,GAAG;AAClC,cAAQ;AAAA,QACN,+BAA+B,UAAU;AAAA,MAC3C;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,QAAI,QAAQ,OAAO;AACjB,iBAAW,QAAQ,KAAK;AAAA,IAC1B;AAEA,QAAI,sBAAsB,UAAU,GAAG;AACrC,cAAQ,IAAI,wBAAwB;AAAA,IACtC,OAAO;AACL,cAAQ,IAAI,oBAAoB;AAAA,IAClC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AAEH,QAAM,WAAW,QAAQ,QAAQ,UAAU,EAAE,YAAY,mBAAmB;AAE5E,WACG,QAAQ,WAAW,EACnB,YAAY,6BAA6B,EACzC,OAAO,qBAAqB,8BAA8B,EAC1D,OAAO,wBAAwB,eAAe,UAAU,EACxD,OAAO,wBAAwB,gCAAgC,EAC/D,OAAO,OAAO,YAAY;AACzB,QAAI,QAAQ,QAAQ,CAAC,sBAAsB,QAAQ,IAAI,GAAG;AACxD,cAAQ;AAAA,QACN,yCAAyC,QAAQ,IAAI;AAAA,MACvD;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,IAAI,wBAAwB;AACpC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AAEH,WACG,QAAQ,OAAO,EACf,YAAY,yBAAyB,EACrC,OAAO,qBAAqB,8BAA8B,EAC1D;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,wBAAwB,gCAAgC,EAC/D,OAAO,OAAO,YAAY;AACzB,QAAI,QAAQ,QAAQ,CAAC,kBAAkB,QAAQ,IAAI,GAAG;AACpD,cAAQ;AAAA,QACN,qCAAqC,QAAQ,IAAI;AAAA,MACnD;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,IAAI,oBAAoB;AAChC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACL;","names":[]}
@@ -20,7 +20,9 @@ import {
20
20
  getReview,
21
21
  replyToReview,
22
22
  exportReviews,
23
+ analyzeReviews,
23
24
  formatOutput,
25
+ maybePaginate,
24
26
  sortResults
25
27
  } from "@gpc-cli/core";
26
28
  function resolvePackageName(packageArg, config) {
@@ -59,20 +61,23 @@ function registerReviewsCommands(program) {
59
61
  const sorted = sortResults(result, options.sort);
60
62
  if (format !== "json" && Array.isArray(sorted)) {
61
63
  const rows = sorted.map((r) => {
62
- const comments = r["comments"];
64
+ const rv = r;
65
+ const comments = rv["comments"];
63
66
  const userComment = comments?.[0]?.["userComment"];
64
67
  return {
65
- reviewId: r["reviewId"] || "-",
66
- author: r["authorName"] || "-",
68
+ reviewId: rv["reviewId"] || "-",
69
+ author: rv["authorName"] || "-",
67
70
  stars: userComment?.["starRating"] || "-",
68
71
  text: String(userComment?.["text"] || "-").slice(0, 80),
69
- lastModified: userComment?.["lastModified"] ? String(userComment["lastModified"]?.["seconds"] || "-") : "-",
72
+ lastModified: userComment?.["lastModified"] ? String(
73
+ userComment["lastModified"]?.["seconds"] || "-"
74
+ ) : "-",
70
75
  thumbsUp: userComment?.["thumbsUpCount"] || 0
71
76
  };
72
77
  });
73
- console.log(formatOutput(rows, format));
78
+ await maybePaginate(formatOutput(rows, format));
74
79
  } else {
75
- console.log(formatOutput(sorted, format));
80
+ await maybePaginate(formatOutput(sorted, format));
76
81
  }
77
82
  } catch (error) {
78
83
  console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
@@ -131,6 +136,62 @@ function registerReviewsCommands(program) {
131
136
  process.exit(4);
132
137
  }
133
138
  });
139
+ reviews.command("analyze").description("Analyze reviews: sentiment, topics, keywords, rating distribution").option("--stars <n>", "Filter by star rating (1-5)", parseInt).option("--lang <code>", "Filter by reviewer language").option("--since <date>", "Filter reviews after date (ISO 8601)").option("--max <n>", "Maximum number of reviews to analyze", parseInt).action(async (options) => {
140
+ const config = await loadConfig();
141
+ const packageName = resolvePackageName(program.opts()["app"], config);
142
+ const client = await getClient(config);
143
+ const format = getOutputFormat(program, config);
144
+ try {
145
+ const result = await analyzeReviews(client, packageName, {
146
+ stars: options.stars,
147
+ language: options.lang,
148
+ since: options.since,
149
+ maxResults: options.max
150
+ });
151
+ if (format === "json") {
152
+ console.log(formatOutput(result, format));
153
+ return;
154
+ }
155
+ console.log(`
156
+ Review Analysis \u2014 ${packageName}`);
157
+ console.log(`${"\u2500".repeat(50)}`);
158
+ console.log(`Total reviews: ${result.totalReviews}`);
159
+ console.log(
160
+ `Average rating: ${result.avgRating > 0 ? result.avgRating.toFixed(2) + " \u2605" : "N/A"}`
161
+ );
162
+ console.log(`
163
+ Sentiment:`);
164
+ console.log(
165
+ ` Positive: ${result.sentiment.positive} Negative: ${result.sentiment.negative} Neutral: ${result.sentiment.neutral}`
166
+ );
167
+ console.log(` Avg score: ${result.sentiment.avgScore} (range -1 to +1)`);
168
+ if (result.topics.length > 0) {
169
+ console.log(`
170
+ Top topics:`);
171
+ for (const t of result.topics.slice(0, 8)) {
172
+ const bar = t.avgScore > 0.1 ? "+" : t.avgScore < -0.1 ? "-" : "~";
173
+ console.log(` [${bar}] ${t.topic.padEnd(20)} ${t.count} reviews`);
174
+ }
175
+ }
176
+ if (result.keywords.length > 0) {
177
+ console.log(
178
+ `
179
+ Top keywords: ${result.keywords.slice(0, 10).map((k) => k.word).join(", ")}`
180
+ );
181
+ }
182
+ if (Object.keys(result.ratingDistribution).length > 0) {
183
+ console.log(`
184
+ Rating distribution:`);
185
+ for (let star = 5; star >= 1; star--) {
186
+ const count = result.ratingDistribution[star] ?? 0;
187
+ if (count > 0) console.log(` ${star}\u2605 ${count}`);
188
+ }
189
+ }
190
+ } catch (error) {
191
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
192
+ process.exit(4);
193
+ }
194
+ });
134
195
  reviews.command("export").description("Export reviews to JSON or CSV").option("--format <type>", "Output format: json or csv", "json").option("--stars <n>", "Filter by star rating (1-5)", parseInt).option("--lang <code>", "Filter by reviewer language").option("--since <date>", "Filter reviews after date (ISO 8601)").option("--translate-to <lang>", "Translate reviews to language").option("--output <file>", "Write output to file instead of stdout").action(async (options) => {
135
196
  const config = await loadConfig();
136
197
  const packageName = resolvePackageName(program.opts()["app"], config);
@@ -159,4 +220,4 @@ function registerReviewsCommands(program) {
159
220
  export {
160
221
  registerReviewsCommands
161
222
  };
162
- //# sourceMappingURL=reviews-UHK4FGK6.js.map
223
+ //# sourceMappingURL=reviews-MOVGATUI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/reviews.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient } from \"@gpc-cli/api\";\nimport {\n listReviews,\n getReview,\n replyToReview,\n exportReviews,\n analyzeReviews,\n formatOutput,\n maybePaginate,\n sortResults,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport { isInteractive, 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\nasync function getClient(config: GpcConfig) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createApiClient({ auth });\n}\n\nexport function registerReviewsCommands(program: Command): void {\n const reviews = program.command(\"reviews\").description(\"Manage user reviews and ratings\");\n\n reviews\n .command(\"list\")\n .description(\"List user reviews\")\n .option(\"--stars <n>\", \"Filter by star rating (1-5)\", parseInt)\n .option(\"--lang <code>\", \"Filter by reviewer language\")\n .option(\"--since <date>\", \"Filter reviews after date (ISO 8601)\")\n .option(\"--translate-to <lang>\", \"Translate reviews to language\")\n .option(\"--max <n>\", \"Maximum number of reviews per page\", parseInt)\n .option(\"--limit <n>\", \"Maximum total results\", parseInt)\n .option(\"--next-page <token>\", \"Resume from page token\")\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 result = await listReviews(client, packageName, {\n stars: options.stars,\n language: options.lang,\n since: options.since,\n translationLanguage: options.translateTo,\n maxResults: options.max,\n limit: options.limit,\n nextPage: options.nextPage,\n });\n if (Array.isArray(result) && result.length === 0 && format !== \"json\") {\n console.log(\"No reviews found.\");\n return;\n }\n const sorted = sortResults(result, options.sort);\n if (format !== \"json\" && Array.isArray(sorted)) {\n const rows = sorted.map((r: unknown) => {\n const rv = r as Record<string, unknown>;\n const comments = rv[\"comments\"] as Record<string, unknown>[] | undefined;\n const userComment = comments?.[0]?.[\"userComment\"] as\n | Record<string, unknown>\n | undefined;\n return {\n reviewId: rv[\"reviewId\"] || \"-\",\n author: rv[\"authorName\"] || \"-\",\n stars: userComment?.[\"starRating\"] || \"-\",\n text: String(userComment?.[\"text\"] || \"-\").slice(0, 80),\n lastModified: userComment?.[\"lastModified\"]\n ? String(\n (userComment[\"lastModified\"] as Record<string, unknown>)?.[\"seconds\"] || \"-\",\n )\n : \"-\",\n thumbsUp: userComment?.[\"thumbsUpCount\"] || 0,\n };\n });\n await maybePaginate(formatOutput(rows, format));\n } else {\n await maybePaginate(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 reviews\n .command(\"get <review-id>\")\n .description(\"Get a single review\")\n .option(\"--translate-to <lang>\", \"Translate review to language\")\n .action(async (reviewId: string, 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 getReview(client, packageName, reviewId, options.translateTo);\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 reviews\n .command(\"reply <review-id>\")\n .description(\"Reply to a review\")\n .option(\"--text <text>\", \"Reply text (max 350 chars)\")\n .action(async (reviewId: string, options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n const interactive = isInteractive(program);\n\n options.text = await requireOption(\n \"text\",\n options.text,\n {\n message: \"Reply text (max 350 chars):\",\n },\n interactive,\n );\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"reviews reply\",\n action: \"reply to\",\n target: reviewId,\n details: { text: options.text },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const result = await replyToReview(client, packageName, reviewId, options.text);\n if (format !== \"json\") {\n const charCount = options.text.length;\n console.log(`Reply sent (${charCount}/350 chars)`);\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 reviews\n .command(\"analyze\")\n .description(\"Analyze reviews: sentiment, topics, keywords, rating distribution\")\n .option(\"--stars <n>\", \"Filter by star rating (1-5)\", parseInt)\n .option(\"--lang <code>\", \"Filter by reviewer language\")\n .option(\"--since <date>\", \"Filter reviews after date (ISO 8601)\")\n .option(\"--max <n>\", \"Maximum number of reviews to analyze\", parseInt)\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 analyzeReviews(client, packageName, {\n stars: options.stars,\n language: options.lang,\n since: options.since,\n maxResults: options.max,\n });\n\n if (format === \"json\") {\n console.log(formatOutput(result, format));\n return;\n }\n\n console.log(`\\nReview Analysis — ${packageName}`);\n console.log(`${\"─\".repeat(50)}`);\n console.log(`Total reviews: ${result.totalReviews}`);\n console.log(\n `Average rating: ${result.avgRating > 0 ? result.avgRating.toFixed(2) + \" ★\" : \"N/A\"}`,\n );\n console.log(`\\nSentiment:`);\n console.log(\n ` Positive: ${result.sentiment.positive} Negative: ${result.sentiment.negative} Neutral: ${result.sentiment.neutral}`,\n );\n console.log(` Avg score: ${result.sentiment.avgScore} (range -1 to +1)`);\n\n if (result.topics.length > 0) {\n console.log(`\\nTop topics:`);\n for (const t of result.topics.slice(0, 8)) {\n const bar = t.avgScore > 0.1 ? \"+\" : t.avgScore < -0.1 ? \"-\" : \"~\";\n console.log(` [${bar}] ${t.topic.padEnd(20)} ${t.count} reviews`);\n }\n }\n\n if (result.keywords.length > 0) {\n console.log(\n `\\nTop keywords: ${result.keywords\n .slice(0, 10)\n .map((k) => k.word)\n .join(\", \")}`,\n );\n }\n\n if (Object.keys(result.ratingDistribution).length > 0) {\n console.log(`\\nRating distribution:`);\n for (let star = 5; star >= 1; star--) {\n const count = result.ratingDistribution[star] ?? 0;\n if (count > 0) console.log(` ${star}★ ${count}`);\n }\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n reviews\n .command(\"export\")\n .description(\"Export reviews to JSON or CSV\")\n .option(\"--format <type>\", \"Output format: json or csv\", \"json\")\n .option(\"--stars <n>\", \"Filter by star rating (1-5)\", parseInt)\n .option(\"--lang <code>\", \"Filter by reviewer language\")\n .option(\"--since <date>\", \"Filter reviews after date (ISO 8601)\")\n .option(\"--translate-to <lang>\", \"Translate reviews to language\")\n .option(\"--output <file>\", \"Write output to file instead of stdout\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n\n try {\n const result = await exportReviews(client, packageName, {\n format: options.format as \"json\" | \"csv\",\n stars: options.stars,\n language: options.lang,\n since: options.since,\n translationLanguage: options.translateTo,\n });\n\n if (options.output) {\n const { writeFile } = await import(\"node:fs/promises\");\n await writeFile(options.output, result, \"utf-8\");\n console.log(`Reviews exported to ${options.output}`);\n } else {\n console.log(result);\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":[]}
@@ -4,7 +4,7 @@ import {
4
4
  gray,
5
5
  green,
6
6
  red
7
- } from "./chunk-7BXCQKJG.js";
7
+ } from "./chunk-P5GF73XK.js";
8
8
  import {
9
9
  getOutputFormat
10
10
  } from "./chunk-ELXAK7GI.js";
@@ -25,7 +25,8 @@ import {
25
25
  runWatchLoop,
26
26
  trackBreachState,
27
27
  sendNotification,
28
- formatOutput
28
+ formatOutput,
29
+ createSpinner
29
30
  } from "@gpc-cli/core";
30
31
  var VALID_SECTIONS = /* @__PURE__ */ new Set(["releases", "vitals", "reviews"]);
31
32
  var VALID_FORMATS = /* @__PURE__ */ new Set(["table", "summary"]);
@@ -208,7 +209,9 @@ async function runStatusForPackage(ctx) {
208
209
  };
209
210
  const save = (status2) => saveStatusCache(packageName, status2, opts.ttl);
210
211
  if (watchInterval !== null && opts.sinceLast) {
211
- process.stderr.write("Warning: --since-last is not supported with --watch and will be ignored.\n");
212
+ process.stderr.write(
213
+ "Warning: --since-last is not supported with --watch and will be ignored.\n"
214
+ );
212
215
  }
213
216
  if (watchInterval !== null) {
214
217
  await runWatchLoop({ intervalSeconds: watchInterval, render, fetch: fetchLive, save });
@@ -241,7 +244,16 @@ async function runStatusForPackage(ctx) {
241
244
  return statusHasBreach(cached);
242
245
  }
243
246
  }
244
- const status = await fetchLive();
247
+ const spinner = createSpinner("Fetching app status...");
248
+ if (ctx.format !== "json") spinner.start();
249
+ let status;
250
+ try {
251
+ status = await fetchLive();
252
+ } catch (err) {
253
+ spinner.fail("Failed to fetch app status");
254
+ throw err;
255
+ }
256
+ spinner.stop("Done");
245
257
  await save(status);
246
258
  printWithDiff(status, prevStatus, opts.sinceLast, render, ctx.format);
247
259
  await handleNotify(packageName, status, opts.notify);
@@ -279,4 +291,4 @@ async function handleNotify(packageName, status, notify) {
279
291
  export {
280
292
  registerStatusCommand
281
293
  };
282
- //# sourceMappingURL=status-DQYZ7A6Y.js.map
294
+ //# sourceMappingURL=status-G3AMJ34G.js.map