@gpc-cli/cli 0.9.44 → 0.9.46

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 (168) hide show
  1. package/dist/{anomalies-NU2IN2GJ.js → anomalies-UDE4NGHJ.js} +19 -24
  2. package/dist/anomalies-UDE4NGHJ.js.map +1 -0
  3. package/dist/{apps-J2446UDA.js → apps-FKD3ZG5X.js} +31 -35
  4. package/dist/apps-FKD3ZG5X.js.map +1 -0
  5. package/dist/{audit-N2CRHWUN.js → audit-JASSHRWN.js} +47 -62
  6. package/dist/audit-JASSHRWN.js.map +1 -0
  7. package/dist/{auth-XGSTT5G5.js → auth-OTA3SV3J.js} +145 -103
  8. package/dist/auth-OTA3SV3J.js.map +1 -0
  9. package/dist/bin.js +6 -4
  10. package/dist/bin.js.map +1 -1
  11. package/dist/bundle-F7MUVC5J.js +204 -0
  12. package/dist/bundle-F7MUVC5J.js.map +1 -0
  13. package/dist/{cache-SLNFRTI2.js → cache-XKPLZYEB.js} +4 -5
  14. package/dist/cache-XKPLZYEB.js.map +1 -0
  15. package/dist/changelog-7COFZO7Q.js +48 -0
  16. package/dist/changelog-7COFZO7Q.js.map +1 -0
  17. package/dist/{chunk-4O4D5SGL.js → chunk-3SJ6OXCZ.js} +4 -5
  18. package/dist/chunk-3SJ6OXCZ.js.map +1 -0
  19. package/dist/{chunk-7LURVNQV.js → chunk-6OWN6S6X.js} +53 -49
  20. package/dist/{chunk-7LURVNQV.js.map → chunk-6OWN6S6X.js.map} +1 -1
  21. package/dist/{chunk-U6ZTQ34I.js → chunk-BCBXQC7J.js} +45 -11
  22. package/dist/chunk-BCBXQC7J.js.map +1 -0
  23. package/dist/{chunk-AA577WVQ.js → chunk-NQH4G7BI.js} +9 -3
  24. package/dist/chunk-NQH4G7BI.js.map +1 -0
  25. package/dist/chunk-SLNJEAMK.js +23 -0
  26. package/dist/chunk-SLNJEAMK.js.map +1 -0
  27. package/dist/{chunk-NV75I5VP.js → chunk-YFUBD2XB.js} +10 -8
  28. package/dist/chunk-YFUBD2XB.js.map +1 -0
  29. package/dist/{config-222P3MKK.js → config-2FTCYEGD.js} +8 -5
  30. package/dist/config-2FTCYEGD.js.map +1 -0
  31. package/dist/{data-safety-Q7FTCEWU.js → data-safety-AFMD6MYI.js} +12 -27
  32. package/dist/data-safety-AFMD6MYI.js.map +1 -0
  33. package/dist/{device-tiers-MIOQEXYY.js → device-tiers-AQAMUQXI.js} +23 -38
  34. package/dist/device-tiers-AQAMUQXI.js.map +1 -0
  35. package/dist/diff-6EO4ID6W.js +91 -0
  36. package/dist/diff-6EO4ID6W.js.map +1 -0
  37. package/dist/{docs-7DUXIKA3.js → docs-4D2SJ4LY.js} +4 -3
  38. package/dist/docs-4D2SJ4LY.js.map +1 -0
  39. package/dist/doctor-H4X7Q57B.js +691 -0
  40. package/dist/doctor-H4X7Q57B.js.map +1 -0
  41. package/dist/{enterprise-7THXNBTC.js → enterprise-7PWXMSUN.js} +11 -21
  42. package/dist/enterprise-7PWXMSUN.js.map +1 -0
  43. package/dist/{external-transactions-2GWIMUVM.js → external-transactions-LCZALS3V.js} +12 -28
  44. package/dist/external-transactions-LCZALS3V.js.map +1 -0
  45. package/dist/{feedback-2W2XJGZX.js → feedback-XP765TOO.js} +4 -4
  46. package/dist/{games-BT777WUO.js → games-ZSNGEI7A.js} +17 -32
  47. package/dist/games-ZSNGEI7A.js.map +1 -0
  48. package/dist/{generated-apks-RJWTIX7L.js → generated-apks-RX2IUWSF.js} +30 -38
  49. package/dist/generated-apks-RX2IUWSF.js.map +1 -0
  50. package/dist/{grants-TKQJ3IER.js → grants-EBPECI26.js} +22 -40
  51. package/dist/grants-EBPECI26.js.map +1 -0
  52. package/dist/{iap-ICAEQLK5.js → iap-OUI5YYN4.js} +30 -51
  53. package/dist/iap-OUI5YYN4.js.map +1 -0
  54. package/dist/index.js +1 -1
  55. package/dist/{init-JZ2THPMS.js → init-WSTQTJOD.js} +5 -4
  56. package/dist/init-WSTQTJOD.js.map +1 -0
  57. package/dist/{install-skills-OV4HVANW.js → install-skills-6QDUXI5F.js} +5 -6
  58. package/dist/{install-skills-OV4HVANW.js.map → install-skills-6QDUXI5F.js.map} +1 -1
  59. package/dist/{internal-sharing-3U2XFHA4.js → internal-sharing-ONNIWIAT.js} +3 -4
  60. package/dist/{internal-sharing-3U2XFHA4.js.map → internal-sharing-ONNIWIAT.js.map} +1 -1
  61. package/dist/{listings-77HZW4S5.js → listings-7SGQ4SRX.js} +118 -157
  62. package/dist/listings-7SGQ4SRX.js.map +1 -0
  63. package/dist/migrate-ZQCJGQQS.js +138 -0
  64. package/dist/migrate-ZQCJGQQS.js.map +1 -0
  65. package/dist/{one-time-products-LHZAXQES.js → one-time-products-MGZTU7OM.js} +65 -120
  66. package/dist/one-time-products-MGZTU7OM.js.map +1 -0
  67. package/dist/{preflight-H3HEBYQW.js → preflight-N7ZRG2JI.js} +58 -55
  68. package/dist/preflight-N7ZRG2JI.js.map +1 -0
  69. package/dist/{pricing-XQSDTTK5.js → pricing-JJZFICFL.js} +8 -8
  70. package/dist/{pricing-XQSDTTK5.js.map → pricing-JJZFICFL.js.map} +1 -1
  71. package/dist/{prompt-BSV22CQZ.js → prompt-GXC2JSLA.js} +2 -2
  72. package/dist/{publish-Q5ZKEKZ5.js → publish-JPTI4EBT.js} +34 -30
  73. package/dist/publish-JPTI4EBT.js.map +1 -0
  74. package/dist/{purchase-options-CKRN4VIW.js → purchase-options-KFWW4JW2.js} +16 -11
  75. package/dist/purchase-options-KFWW4JW2.js.map +1 -0
  76. package/dist/purchases-DAWTMXP6.js +383 -0
  77. package/dist/purchases-DAWTMXP6.js.map +1 -0
  78. package/dist/{quickstart-4HB62YEL.js → quickstart-Z5Y3FYJU.js} +5 -3
  79. package/dist/quickstart-Z5Y3FYJU.js.map +1 -0
  80. package/dist/{quota-UHIQQYOY.js → quota-MZRWYJGR.js} +5 -15
  81. package/dist/quota-MZRWYJGR.js.map +1 -0
  82. package/dist/{recovery-5EV2R476.js → recovery-YE3Z7NIN.js} +32 -61
  83. package/dist/recovery-YE3Z7NIN.js.map +1 -0
  84. package/dist/{releases-C2WC2K4E.js → releases-2I3WBULC.js} +184 -185
  85. package/dist/releases-2I3WBULC.js.map +1 -0
  86. package/dist/{reports-2YX3RDOS.js → reports-CIB2T3XT.js} +19 -21
  87. package/dist/reports-CIB2T3XT.js.map +1 -0
  88. package/dist/reviews-BCCXIQ6C.js +188 -0
  89. package/dist/reviews-BCCXIQ6C.js.map +1 -0
  90. package/dist/{status-WHGLODGV.js → status-6LH5W4FU.js} +105 -83
  91. package/dist/status-6LH5W4FU.js.map +1 -0
  92. package/dist/{subscriptions-CI3JH3VQ.js → subscriptions-DZP3Y7O7.js} +142 -232
  93. package/dist/subscriptions-DZP3Y7O7.js.map +1 -0
  94. package/dist/{testers-NZOFA3EF.js → testers-LSMBXCA2.js} +24 -44
  95. package/dist/testers-LSMBXCA2.js.map +1 -0
  96. package/dist/tracks-YHMO2A6B.js +98 -0
  97. package/dist/tracks-YHMO2A6B.js.map +1 -0
  98. package/dist/{train-XKE4JN3Y.js → train-MDD2EBHS.js} +35 -55
  99. package/dist/train-MDD2EBHS.js.map +1 -0
  100. package/dist/{update-QMPRL5Y6.js → update-OMALGIBR.js} +30 -15
  101. package/dist/update-OMALGIBR.js.map +1 -0
  102. package/dist/{users-2YTC4Q36.js → users-UKG7VIQH.js} +45 -67
  103. package/dist/users-UKG7VIQH.js.map +1 -0
  104. package/dist/{validate-UOVTM6L3.js → validate-QIYSA3N7.js} +8 -10
  105. package/dist/validate-QIYSA3N7.js.map +1 -0
  106. package/dist/{version-NK5SJLHJ.js → version-NCSNXNVN.js} +4 -4
  107. package/dist/{vitals-A4CS4MSS.js → vitals-C23L2Y2E.js} +153 -172
  108. package/dist/vitals-C23L2Y2E.js.map +1 -0
  109. package/package.json +6 -6
  110. package/dist/anomalies-NU2IN2GJ.js.map +0 -1
  111. package/dist/apps-J2446UDA.js.map +0 -1
  112. package/dist/audit-N2CRHWUN.js.map +0 -1
  113. package/dist/auth-XGSTT5G5.js.map +0 -1
  114. package/dist/bundle-F43TD2BQ.js +0 -218
  115. package/dist/bundle-F43TD2BQ.js.map +0 -1
  116. package/dist/cache-SLNFRTI2.js.map +0 -1
  117. package/dist/changelog-OYUZOCOL.js +0 -53
  118. package/dist/changelog-OYUZOCOL.js.map +0 -1
  119. package/dist/chunk-4O4D5SGL.js.map +0 -1
  120. package/dist/chunk-AA577WVQ.js.map +0 -1
  121. package/dist/chunk-FWKYRLKY.js +0 -19
  122. package/dist/chunk-FWKYRLKY.js.map +0 -1
  123. package/dist/chunk-NV75I5VP.js.map +0 -1
  124. package/dist/chunk-U6ZTQ34I.js.map +0 -1
  125. package/dist/config-222P3MKK.js.map +0 -1
  126. package/dist/data-safety-Q7FTCEWU.js.map +0 -1
  127. package/dist/device-tiers-MIOQEXYY.js.map +0 -1
  128. package/dist/diff-V77SMKAQ.js +0 -96
  129. package/dist/diff-V77SMKAQ.js.map +0 -1
  130. package/dist/docs-7DUXIKA3.js.map +0 -1
  131. package/dist/doctor-3Z4ARPM2.js +0 -372
  132. package/dist/doctor-3Z4ARPM2.js.map +0 -1
  133. package/dist/enterprise-7THXNBTC.js.map +0 -1
  134. package/dist/external-transactions-2GWIMUVM.js.map +0 -1
  135. package/dist/games-BT777WUO.js.map +0 -1
  136. package/dist/generated-apks-RJWTIX7L.js.map +0 -1
  137. package/dist/grants-TKQJ3IER.js.map +0 -1
  138. package/dist/iap-ICAEQLK5.js.map +0 -1
  139. package/dist/init-JZ2THPMS.js.map +0 -1
  140. package/dist/listings-77HZW4S5.js.map +0 -1
  141. package/dist/migrate-SQT6RD6T.js +0 -143
  142. package/dist/migrate-SQT6RD6T.js.map +0 -1
  143. package/dist/one-time-products-LHZAXQES.js.map +0 -1
  144. package/dist/preflight-H3HEBYQW.js.map +0 -1
  145. package/dist/publish-Q5ZKEKZ5.js.map +0 -1
  146. package/dist/purchase-options-CKRN4VIW.js.map +0 -1
  147. package/dist/purchases-HSMCOG4A.js +0 -330
  148. package/dist/purchases-HSMCOG4A.js.map +0 -1
  149. package/dist/quickstart-4HB62YEL.js.map +0 -1
  150. package/dist/quota-UHIQQYOY.js.map +0 -1
  151. package/dist/recovery-5EV2R476.js.map +0 -1
  152. package/dist/releases-C2WC2K4E.js.map +0 -1
  153. package/dist/reports-2YX3RDOS.js.map +0 -1
  154. package/dist/reviews-2CWOI5CV.js +0 -213
  155. package/dist/reviews-2CWOI5CV.js.map +0 -1
  156. package/dist/status-WHGLODGV.js.map +0 -1
  157. package/dist/subscriptions-CI3JH3VQ.js.map +0 -1
  158. package/dist/testers-NZOFA3EF.js.map +0 -1
  159. package/dist/tracks-NERFFEDT.js +0 -107
  160. package/dist/tracks-NERFFEDT.js.map +0 -1
  161. package/dist/train-XKE4JN3Y.js.map +0 -1
  162. package/dist/update-QMPRL5Y6.js.map +0 -1
  163. package/dist/users-2YTC4Q36.js.map +0 -1
  164. package/dist/validate-UOVTM6L3.js.map +0 -1
  165. package/dist/vitals-A4CS4MSS.js.map +0 -1
  166. /package/dist/{feedback-2W2XJGZX.js.map → feedback-XP765TOO.js.map} +0 -0
  167. /package/dist/{prompt-BSV22CQZ.js.map → prompt-GXC2JSLA.js.map} +0 -0
  168. /package/dist/{version-NK5SJLHJ.js.map → version-NCSNXNVN.js.map} +0 -0
@@ -5,7 +5,8 @@ import {
5
5
  parseMonth,
6
6
  isValidReportType,
7
7
  isFinancialReportType,
8
- isStatsReportType
8
+ isStatsReportType,
9
+ GpcError
9
10
  } from "@gpc-cli/core";
10
11
  var FINANCIAL_REPORT_MESSAGE = `Financial reports (earnings, sales, estimated_sales, play_balance) are not available through the Google Play Developer API.
11
12
 
@@ -34,47 +35,44 @@ function registerReportsCommands(program) {
34
35
  const reports = program.command("reports").description("Financial and stats reports (via Google Cloud Storage)");
35
36
  reports.command("list <report-type>").description("List available reports").option("--month <YYYY-MM>", "Report month (e.g., 2026-03)").option("--limit <n>", "Maximum results to return").option("--next-page <token>", "Pagination token for next page").action(async (reportType, options) => {
36
37
  if (!isValidReportType(reportType)) {
37
- console.error(
38
- `Error: Invalid report type "${reportType}". Valid types: earnings, sales, estimated_sales, installs, crashes, ratings, reviews, store_performance, subscriptions, play_balance`
38
+ throw new GpcError(
39
+ `Invalid report type "${reportType}". Valid types: earnings, sales, estimated_sales, installs, crashes, ratings, reviews, store_performance, subscriptions, play_balance`,
40
+ "INVALID_REPORT_TYPE",
41
+ 2
39
42
  );
40
- process.exit(2);
41
43
  }
42
44
  if (options.month) {
43
45
  parseMonth(options.month);
44
46
  }
45
- if (isFinancialReportType(reportType)) {
46
- console.log(FINANCIAL_REPORT_MESSAGE);
47
- } else {
48
- console.log(STATS_REPORT_MESSAGE);
49
- }
50
- process.exit(2);
47
+ const message = isFinancialReportType(reportType) ? FINANCIAL_REPORT_MESSAGE : STATS_REPORT_MESSAGE;
48
+ throw new GpcError(message, "REPORT_NOT_AVAILABLE", 2);
51
49
  });
52
50
  const download = reports.command("download").description("Download a report");
53
51
  download.command("financial").description("Download a financial report").option("--month <YYYY-MM>", "Report month (e.g., 2026-03)").option("--type <report-type>", "Report type", "earnings").option("--output-file <path>", "Save to file instead of stdout").action(async (options) => {
54
52
  if (options.type && !isFinancialReportType(options.type)) {
55
- console.error(
56
- `Error: Invalid financial report type "${options.type}". Valid types: earnings, sales, estimated_sales, play_balance`
53
+ throw new GpcError(
54
+ `Invalid financial report type "${options.type}". Valid types: earnings, sales, estimated_sales, play_balance`,
55
+ "INVALID_REPORT_TYPE",
56
+ 2
57
57
  );
58
- process.exit(2);
59
58
  }
60
- console.log(FINANCIAL_REPORT_MESSAGE);
61
- process.exit(2);
59
+ throw new GpcError(FINANCIAL_REPORT_MESSAGE, "REPORT_NOT_AVAILABLE", 2);
62
60
  });
63
61
  download.command("stats").description("Download a stats report").option("--month <YYYY-MM>", "Report month (e.g., 2026-03)").option(
64
62
  "--type <report-type>",
65
63
  "Report type (installs, crashes, ratings, reviews, store_performance, subscriptions)"
66
64
  ).option("--output-file <path>", "Save to file instead of stdout").action(async (options) => {
67
65
  if (options.type && !isStatsReportType(options.type)) {
68
- console.error(
69
- `Error: Invalid stats report type "${options.type}". Valid types: installs, crashes, ratings, reviews, store_performance, subscriptions`
66
+ throw new GpcError(
67
+ `Invalid stats report type "${options.type}". Valid types: installs, crashes, ratings, reviews, store_performance, subscriptions`,
68
+ "INVALID_REPORT_TYPE",
69
+ 2
70
70
  );
71
- process.exit(2);
72
71
  }
73
- console.log(STATS_REPORT_MESSAGE);
74
- process.exit(2);
72
+ throw new GpcError(STATS_REPORT_MESSAGE, "REPORT_NOT_AVAILABLE", 2);
75
73
  });
76
74
  }
77
75
  export {
78
76
  registerReportsCommands
79
77
  };
80
- //# sourceMappingURL=reports-2YX3RDOS.js.map
78
+ //# sourceMappingURL=reports-CIB2T3XT.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 GpcError,\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 throw new GpcError(\n `Invalid report type \"${reportType}\". Valid types: earnings, sales, estimated_sales, installs, crashes, ratings, reviews, store_performance, subscriptions, play_balance`,\n \"INVALID_REPORT_TYPE\",\n 2,\n );\n }\n\n // Validate month format if provided\n if (options.month) {\n parseMonth(options.month);\n }\n\n const message = isFinancialReportType(reportType)\n ? FINANCIAL_REPORT_MESSAGE\n : STATS_REPORT_MESSAGE;\n throw new GpcError(message, \"REPORT_NOT_AVAILABLE\", 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 throw new GpcError(\n `Invalid financial report type \"${options.type}\". Valid types: earnings, sales, estimated_sales, play_balance`,\n \"INVALID_REPORT_TYPE\",\n 2,\n );\n }\n\n throw new GpcError(FINANCIAL_REPORT_MESSAGE, \"REPORT_NOT_AVAILABLE\", 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 throw new GpcError(\n `Invalid stats report type \"${options.type}\". Valid types: installs, crashes, ratings, reviews, store_performance, subscriptions`,\n \"INVALID_REPORT_TYPE\",\n 2,\n );\n }\n\n throw new GpcError(STATS_REPORT_MESSAGE, \"REPORT_NOT_AVAILABLE\", 2);\n });\n}\n"],"mappings":";;;AACA;AAAA,EACE;AAAA,EACA;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,YAAM,IAAI;AAAA,QACR,wBAAwB,UAAU;AAAA,QAClC;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,QAAI,QAAQ,OAAO;AACjB,iBAAW,QAAQ,KAAK;AAAA,IAC1B;AAEA,UAAM,UAAU,sBAAsB,UAAU,IAC5C,2BACA;AACJ,UAAM,IAAI,SAAS,SAAS,wBAAwB,CAAC;AAAA,EACvD,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,YAAM,IAAI;AAAA,QACR,kCAAkC,QAAQ,IAAI;AAAA,QAC9C;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,IAAI,SAAS,0BAA0B,wBAAwB,CAAC;AAAA,EACxE,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,YAAM,IAAI;AAAA,QACR,8BAA8B,QAAQ,IAAI;AAAA,QAC1C;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,IAAI,SAAS,sBAAsB,wBAAwB,CAAC;AAAA,EACpE,CAAC;AACL;","names":[]}
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getClient,
4
+ resolvePackageName
5
+ } from "./chunk-NQH4G7BI.js";
6
+ import {
7
+ isDryRun,
8
+ printDryRun
9
+ } from "./chunk-Y3QZDAKS.js";
10
+ import {
11
+ getOutputFormat
12
+ } from "./chunk-ELXAK7GI.js";
13
+ import {
14
+ isInteractive,
15
+ requireOption
16
+ } from "./chunk-YFUBD2XB.js";
17
+
18
+ // src/commands/reviews.ts
19
+ import { loadConfig } from "@gpc-cli/config";
20
+ import {
21
+ listReviews,
22
+ getReview,
23
+ replyToReview,
24
+ exportReviews,
25
+ analyzeReviews,
26
+ formatOutput,
27
+ maybePaginate,
28
+ sortResults
29
+ } from "@gpc-cli/core";
30
+ function registerReviewsCommands(program) {
31
+ const reviews = program.command("reviews").description("Manage user reviews and ratings");
32
+ reviews.command("list").description("List user reviews").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("--max <n>", "Maximum number of reviews per page", parseInt).option("--limit <n>", "Maximum total results", parseInt).option("--next-page <token>", "Resume from page token").option("--sort <field>", "Sort by field (prefix with - for descending)").action(async (options) => {
33
+ const config = await loadConfig();
34
+ const packageName = resolvePackageName(program.opts()["app"], config);
35
+ const client = await getClient(config);
36
+ const format = getOutputFormat(program, config);
37
+ const result = await listReviews(client, packageName, {
38
+ stars: options.stars,
39
+ language: options.lang,
40
+ since: options.since,
41
+ translationLanguage: options.translateTo,
42
+ maxResults: options.max,
43
+ limit: options.limit,
44
+ nextPage: options.nextPage
45
+ });
46
+ if (Array.isArray(result) && result.length === 0 && format !== "json") {
47
+ console.log("No reviews found.");
48
+ return;
49
+ }
50
+ const sorted = sortResults(result, options.sort);
51
+ if (format !== "json" && Array.isArray(sorted)) {
52
+ const rows = sorted.map((r) => {
53
+ const rv = r;
54
+ const comments = rv["comments"];
55
+ const userComment = comments?.[0]?.["userComment"];
56
+ return {
57
+ reviewId: rv["reviewId"] || "-",
58
+ author: rv["authorName"] || "-",
59
+ stars: userComment?.["starRating"] || "-",
60
+ text: String(userComment?.["text"] || "-").slice(0, 80),
61
+ lastModified: userComment?.["lastModified"] ? String(
62
+ userComment["lastModified"]?.["seconds"] || "-"
63
+ ) : "-",
64
+ thumbsUp: userComment?.["thumbsUpCount"] || 0
65
+ };
66
+ });
67
+ await maybePaginate(formatOutput(rows, format));
68
+ } else {
69
+ await maybePaginate(formatOutput(sorted, format));
70
+ }
71
+ });
72
+ reviews.command("get <review-id>").description("Get a single review").option("--translate-to <lang>", "Translate review to language").action(async (reviewId, options) => {
73
+ const config = await loadConfig();
74
+ const packageName = resolvePackageName(program.opts()["app"], config);
75
+ const client = await getClient(config);
76
+ const format = getOutputFormat(program, config);
77
+ const result = await getReview(client, packageName, reviewId, options.translateTo);
78
+ console.log(formatOutput(result, format));
79
+ });
80
+ reviews.command("reply <review-id>").description("Reply to a review").option("--text <text>", "Reply text (max 350 chars)").action(async (reviewId, options) => {
81
+ const config = await loadConfig();
82
+ const packageName = resolvePackageName(program.opts()["app"], config);
83
+ const format = getOutputFormat(program, config);
84
+ const interactive = isInteractive(program);
85
+ options.text = await requireOption(
86
+ "text",
87
+ options.text,
88
+ {
89
+ message: "Reply text (max 350 chars):"
90
+ },
91
+ interactive
92
+ );
93
+ if (isDryRun(program)) {
94
+ printDryRun(
95
+ {
96
+ command: "reviews reply",
97
+ action: "reply to",
98
+ target: reviewId,
99
+ details: { text: options.text }
100
+ },
101
+ format,
102
+ formatOutput
103
+ );
104
+ return;
105
+ }
106
+ const client = await getClient(config);
107
+ const result = await replyToReview(client, packageName, reviewId, options.text);
108
+ if (format !== "json") {
109
+ const charCount = options.text.length;
110
+ console.log(`Reply sent (${charCount}/350 chars)`);
111
+ }
112
+ console.log(formatOutput(result, format));
113
+ });
114
+ 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) => {
115
+ const config = await loadConfig();
116
+ const packageName = resolvePackageName(program.opts()["app"], config);
117
+ const client = await getClient(config);
118
+ const format = getOutputFormat(program, config);
119
+ const result = await analyzeReviews(client, packageName, {
120
+ stars: options.stars,
121
+ language: options.lang,
122
+ since: options.since,
123
+ maxResults: options.max
124
+ });
125
+ if (format === "json") {
126
+ console.log(formatOutput(result, format));
127
+ return;
128
+ }
129
+ console.log(`
130
+ Review Analysis \u2014 ${packageName}`);
131
+ console.log(`${"\u2500".repeat(50)}`);
132
+ console.log(`Total reviews: ${result.totalReviews}`);
133
+ console.log(
134
+ `Average rating: ${result.avgRating > 0 ? result.avgRating.toFixed(2) + " \u2605" : "N/A"}`
135
+ );
136
+ console.log(`
137
+ Sentiment:`);
138
+ console.log(
139
+ ` Positive: ${result.sentiment.positive} Negative: ${result.sentiment.negative} Neutral: ${result.sentiment.neutral}`
140
+ );
141
+ console.log(` Avg score: ${result.sentiment.avgScore} (range -1 to +1)`);
142
+ if (result.topics.length > 0) {
143
+ console.log(`
144
+ Top topics:`);
145
+ for (const t of result.topics.slice(0, 8)) {
146
+ const bar = t.avgScore > 0.1 ? "+" : t.avgScore < -0.1 ? "-" : "~";
147
+ console.log(` [${bar}] ${t.topic.padEnd(20)} ${t.count} reviews`);
148
+ }
149
+ }
150
+ if (result.keywords.length > 0) {
151
+ console.log(
152
+ `
153
+ Top keywords: ${result.keywords.slice(0, 10).map((k) => k.word).join(", ")}`
154
+ );
155
+ }
156
+ if (Object.keys(result.ratingDistribution).length > 0) {
157
+ console.log(`
158
+ Rating distribution:`);
159
+ for (let star = 5; star >= 1; star--) {
160
+ const count = result.ratingDistribution[star] ?? 0;
161
+ if (count > 0) console.log(` ${star}\u2605 ${count}`);
162
+ }
163
+ }
164
+ });
165
+ 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) => {
166
+ const config = await loadConfig();
167
+ const packageName = resolvePackageName(program.opts()["app"], config);
168
+ const client = await getClient(config);
169
+ const result = await exportReviews(client, packageName, {
170
+ format: options.format,
171
+ stars: options.stars,
172
+ language: options.lang,
173
+ since: options.since,
174
+ translationLanguage: options.translateTo
175
+ });
176
+ if (options.output) {
177
+ const { writeFile } = await import("fs/promises");
178
+ await writeFile(options.output, result, "utf-8");
179
+ console.log(`Reviews exported to ${options.output}`);
180
+ } else {
181
+ console.log(result);
182
+ }
183
+ });
184
+ }
185
+ export {
186
+ registerReviewsCommands
187
+ };
188
+ //# sourceMappingURL=reviews-BCCXIQ6C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/reviews.ts"],"sourcesContent":["import { resolvePackageName, getClient } from \"../resolve.js\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\n\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\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 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 });\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 const result = await getReview(client, packageName, reviewId, options.translateTo);\n console.log(formatOutput(result, format));\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 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 });\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 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 });\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 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 });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAEA,SAAS,kBAAkB;AAE3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAOA,SAAS,wBAAwB,SAAwB;AAC9D,QAAM,UAAU,QAAQ,QAAQ,SAAS,EAAE,YAAY,iCAAiC;AAExF,UACG,QAAQ,MAAM,EACd,YAAY,mBAAmB,EAC/B,OAAO,eAAe,+BAA+B,QAAQ,EAC7D,OAAO,iBAAiB,6BAA6B,EACrD,OAAO,kBAAkB,sCAAsC,EAC/D,OAAO,yBAAyB,+BAA+B,EAC/D,OAAO,aAAa,sCAAsC,QAAQ,EAClE,OAAO,eAAe,yBAAyB,QAAQ,EACvD,OAAO,uBAAuB,wBAAwB,EACtD,OAAO,kBAAkB,8CAA8C,EACvE,OAAO,OAAO,YAAY;AACzB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,MAAM,UAAU,MAAM;AACrC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,SAAS,MAAM,YAAY,QAAQ,aAAa;AAAA,MACpD,OAAO,QAAQ;AAAA,MACf,UAAU,QAAQ;AAAA,MAClB,OAAO,QAAQ;AAAA,MACf,qBAAqB,QAAQ;AAAA,MAC7B,YAAY,QAAQ;AAAA,MACpB,OAAO,QAAQ;AAAA,MACf,UAAU,QAAQ;AAAA,IACpB,CAAC;AACD,QAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW,KAAK,WAAW,QAAQ;AACrE,cAAQ,IAAI,mBAAmB;AAC/B;AAAA,IACF;AACA,UAAM,SAAS,YAAY,QAAQ,QAAQ,IAAI;AAC/C,QAAI,WAAW,UAAU,MAAM,QAAQ,MAAM,GAAG;AAC9C,YAAM,OAAO,OAAO,IAAI,CAAC,MAAe;AACtC,cAAM,KAAK;AACX,cAAM,WAAW,GAAG,UAAU;AAC9B,cAAM,cAAc,WAAW,CAAC,IAAI,aAAa;AAGjD,eAAO;AAAA,UACL,UAAU,GAAG,UAAU,KAAK;AAAA,UAC5B,QAAQ,GAAG,YAAY,KAAK;AAAA,UAC5B,OAAO,cAAc,YAAY,KAAK;AAAA,UACtC,MAAM,OAAO,cAAc,MAAM,KAAK,GAAG,EAAE,MAAM,GAAG,EAAE;AAAA,UACtD,cAAc,cAAc,cAAc,IACtC;AAAA,YACG,YAAY,cAAc,IAAgC,SAAS,KAAK;AAAA,UAC3E,IACA;AAAA,UACJ,UAAU,cAAc,eAAe,KAAK;AAAA,QAC9C;AAAA,MACF,CAAC;AACD,YAAM,cAAc,aAAa,MAAM,MAAM,CAAC;AAAA,IAChD,OAAO;AACL,YAAM,cAAc,aAAa,QAAQ,MAAM,CAAC;AAAA,IAClD;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,iBAAiB,EACzB,YAAY,qBAAqB,EACjC,OAAO,yBAAyB,8BAA8B,EAC9D,OAAO,OAAO,UAAkB,YAAY;AAC3C,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,MAAM,UAAU,MAAM;AACrC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,SAAS,MAAM,UAAU,QAAQ,aAAa,UAAU,QAAQ,WAAW;AACjF,YAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,EAC1C,CAAC;AAEH,UACG,QAAQ,mBAAmB,EAC3B,YAAY,mBAAmB,EAC/B,OAAO,iBAAiB,4BAA4B,EACpD,OAAO,OAAO,UAAkB,YAAY;AAC3C,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAC9C,UAAM,cAAc,cAAc,OAAO;AAEzC,YAAQ,OAAO,MAAM;AAAA,MACnB;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,QACE,SAAS;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAEA,QAAI,SAAS,OAAO,GAAG;AACrB;AAAA,QACE;AAAA,UACE,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,SAAS,EAAE,MAAM,QAAQ,KAAK;AAAA,QAChC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,UAAU,MAAM;AAErC,UAAM,SAAS,MAAM,cAAc,QAAQ,aAAa,UAAU,QAAQ,IAAI;AAC9E,QAAI,WAAW,QAAQ;AACrB,YAAM,YAAY,QAAQ,KAAK;AAC/B,cAAQ,IAAI,eAAe,SAAS,aAAa;AAAA,IACnD;AACA,YAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,EAC1C,CAAC;AAEH,UACG,QAAQ,SAAS,EACjB,YAAY,mEAAmE,EAC/E,OAAO,eAAe,+BAA+B,QAAQ,EAC7D,OAAO,iBAAiB,6BAA6B,EACrD,OAAO,kBAAkB,sCAAsC,EAC/D,OAAO,aAAa,wCAAwC,QAAQ,EACpE,OAAO,OAAO,YAAY;AACzB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,MAAM,UAAU,MAAM;AACrC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,SAAS,MAAM,eAAe,QAAQ,aAAa;AAAA,MACvD,OAAO,QAAQ;AAAA,MACf,UAAU,QAAQ;AAAA,MAClB,OAAO,QAAQ;AAAA,MACf,YAAY,QAAQ;AAAA,IACtB,CAAC;AAED,QAAI,WAAW,QAAQ;AACrB,cAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AACxC;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,yBAAuB,WAAW,EAAE;AAChD,YAAQ,IAAI,GAAG,SAAI,OAAO,EAAE,CAAC,EAAE;AAC/B,YAAQ,IAAI,oBAAoB,OAAO,YAAY,EAAE;AACrD,YAAQ;AAAA,MACN,oBAAoB,OAAO,YAAY,IAAI,OAAO,UAAU,QAAQ,CAAC,IAAI,YAAO,KAAK;AAAA,IACvF;AACA,YAAQ,IAAI;AAAA,WAAc;AAC1B,YAAQ;AAAA,MACN,eAAe,OAAO,UAAU,QAAQ,eAAe,OAAO,UAAU,QAAQ,cAAc,OAAO,UAAU,OAAO;AAAA,IACxH;AACA,YAAQ,IAAI,gBAAgB,OAAO,UAAU,QAAQ,mBAAmB;AAExE,QAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,cAAQ,IAAI;AAAA,YAAe;AAC3B,iBAAW,KAAK,OAAO,OAAO,MAAM,GAAG,CAAC,GAAG;AACzC,cAAM,MAAM,EAAE,WAAW,MAAM,MAAM,EAAE,WAAW,OAAO,MAAM;AAC/D,gBAAQ,IAAI,MAAM,GAAG,KAAK,EAAE,MAAM,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,UAAU;AAAA,MACnE;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,cAAQ;AAAA,QACN;AAAA,gBAAmB,OAAO,SACvB,MAAM,GAAG,EAAE,EACX,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK,IAAI,CAAC;AAAA,MACf;AAAA,IACF;AAEA,QAAI,OAAO,KAAK,OAAO,kBAAkB,EAAE,SAAS,GAAG;AACrD,cAAQ,IAAI;AAAA,qBAAwB;AACpC,eAAS,OAAO,GAAG,QAAQ,GAAG,QAAQ;AACpC,cAAM,QAAQ,OAAO,mBAAmB,IAAI,KAAK;AACjD,YAAI,QAAQ,EAAG,SAAQ,IAAI,KAAK,IAAI,WAAM,KAAK,EAAE;AAAA,MACnD;AAAA,IACF;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,QAAQ,EAChB,YAAY,+BAA+B,EAC3C,OAAO,mBAAmB,8BAA8B,MAAM,EAC9D,OAAO,eAAe,+BAA+B,QAAQ,EAC7D,OAAO,iBAAiB,6BAA6B,EACrD,OAAO,kBAAkB,sCAAsC,EAC/D,OAAO,yBAAyB,+BAA+B,EAC/D,OAAO,mBAAmB,wCAAwC,EAClE,OAAO,OAAO,YAAY;AACzB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,MAAM,UAAU,MAAM;AAErC,UAAM,SAAS,MAAM,cAAc,QAAQ,aAAa;AAAA,MACtD,QAAQ,QAAQ;AAAA,MAChB,OAAO,QAAQ;AAAA,MACf,UAAU,QAAQ;AAAA,MAClB,OAAO,QAAQ;AAAA,MACf,qBAAqB,QAAQ;AAAA,IAC/B,CAAC;AAED,QAAI,QAAQ,QAAQ;AAClB,YAAM,EAAE,UAAU,IAAI,MAAM,OAAO,aAAkB;AACrD,YAAM,UAAU,QAAQ,QAAQ,QAAQ,OAAO;AAC/C,cAAQ,IAAI,uBAAuB,QAAQ,MAAM,EAAE;AAAA,IACrD,OAAO;AACL,cAAQ,IAAI,MAAM;AAAA,IACpB;AAAA,EACF,CAAC;AACL;","names":[]}
@@ -25,37 +25,13 @@ import {
25
25
  runWatchLoop,
26
26
  trackBreachState,
27
27
  sendNotification,
28
+ relativeTime,
28
29
  formatOutput,
29
30
  createSpinner
30
31
  } from "@gpc-cli/core";
31
32
  var VALID_SECTIONS = /* @__PURE__ */ new Set(["releases", "vitals", "reviews"]);
32
33
  var VALID_FORMATS = /* @__PURE__ */ new Set(["table", "summary"]);
33
- function parseSections(raw) {
34
- const sections = raw.split(",").map((s) => s.trim().toLowerCase());
35
- for (const s of sections) {
36
- if (!VALID_SECTIONS.has(s)) {
37
- console.error(`Error: Unknown section "${s}". Valid sections: releases, vitals, reviews`);
38
- process.exit(2);
39
- }
40
- }
41
- return sections;
42
- }
43
- function resolveVitalThresholds(config) {
44
- const vitals = config["vitals"];
45
- const t = vitals?.["thresholds"];
46
- if (!t) return void 0;
47
- const toN = (v) => {
48
- if (v === void 0 || v === null) return void 0;
49
- const n = Number(v);
50
- return isNaN(n) ? void 0 : n;
51
- };
52
- return {
53
- crashRate: toN(t["crashRate"]),
54
- anrRate: toN(t["anrRate"]),
55
- slowStartRate: toN(t["slowStartRate"]),
56
- slowRenderingRate: toN(t["slowRenderingRate"])
57
- };
58
- }
34
+ var MAX_ALL_APPS = 5;
59
35
  var THRESHOLD_KEYS = {
60
36
  crashes: "crashRate",
61
37
  crash: "crashRate",
@@ -65,6 +41,25 @@ var THRESHOLD_KEYS = {
65
41
  "slow-render": "slowRenderingRate",
66
42
  "slow-rendering": "slowRenderingRate"
67
43
  };
44
+ function usageError(message, suggestion) {
45
+ throw Object.assign(new Error(message), {
46
+ code: "STATUS_USAGE_ERROR",
47
+ exitCode: 2,
48
+ suggestion
49
+ });
50
+ }
51
+ function parseSections(raw) {
52
+ const sections = raw.split(",").map((s) => s.trim().toLowerCase());
53
+ for (const s of sections) {
54
+ if (!VALID_SECTIONS.has(s)) {
55
+ usageError(
56
+ `Unknown section "${s}"`,
57
+ "Valid sections: releases, vitals, reviews"
58
+ );
59
+ }
60
+ }
61
+ return sections;
62
+ }
68
63
  function parseThresholdOverrides(raw) {
69
64
  const result = {};
70
65
  for (const pair of raw.split(",")) {
@@ -72,22 +67,46 @@ function parseThresholdOverrides(raw) {
72
67
  if (!key || !val) continue;
73
68
  const mapped = THRESHOLD_KEYS[key.toLowerCase()];
74
69
  if (!mapped) {
75
- console.error(
76
- `Error: Unknown threshold "${key}". Valid: crashes, anr, slow-starts, slow-render`
70
+ usageError(
71
+ `Unknown threshold "${key}"`,
72
+ "Valid: crashes, anr, slow-starts, slow-render"
77
73
  );
78
- process.exit(2);
79
74
  }
80
75
  const n = parseFloat(val);
81
76
  if (isNaN(n) || n < 0) {
82
- console.error(
83
- `Error: Invalid threshold value "${val}" for ${key}. Must be a positive number (percent).`
77
+ usageError(
78
+ `Invalid threshold value "${val}" for ${key}`,
79
+ "Must be a positive number (percent)"
84
80
  );
85
- process.exit(2);
86
81
  }
87
82
  result[mapped] = n / 100;
88
83
  }
89
84
  return result;
90
85
  }
86
+ function resolveWatchInterval(watch) {
87
+ if (watch === void 0) return null;
88
+ if (watch === true || watch === "") return 30;
89
+ const n = parseInt(String(watch), 10);
90
+ return isNaN(n) ? 30 : n;
91
+ }
92
+ function resolveVitalThresholds(config) {
93
+ const raw = config;
94
+ const vitals = raw["vitals"];
95
+ if (!vitals || typeof vitals !== "object") return void 0;
96
+ const t = vitals["thresholds"];
97
+ if (!t || typeof t !== "object") return void 0;
98
+ const toN = (v) => {
99
+ if (v === void 0 || v === null) return void 0;
100
+ const n = Number(v);
101
+ return isNaN(n) ? void 0 : n;
102
+ };
103
+ return {
104
+ crashRate: toN(t["crashRate"]),
105
+ anrRate: toN(t["anrRate"]),
106
+ slowStartRate: toN(t["slowStartRate"]),
107
+ slowRenderingRate: toN(t["slowRenderingRate"])
108
+ };
109
+ }
91
110
  function resolvePackages(program, config, allApps) {
92
111
  const rootApp = program.opts()["app"] || config.app;
93
112
  if (!allApps) return rootApp ? [rootApp] : [];
@@ -105,12 +124,6 @@ function resolvePackages(program, config, allApps) {
105
124
  }
106
125
  return result;
107
126
  }
108
- function resolveWatchInterval(watch) {
109
- if (watch === void 0) return null;
110
- if (watch === true || watch === "") return 30;
111
- const n = parseInt(String(watch), 10);
112
- return isNaN(n) ? 30 : n;
113
- }
114
127
  function colorizeTrackStatus(s) {
115
128
  switch (s) {
116
129
  case "inProgress":
@@ -134,7 +147,7 @@ function applyStatusColors(status) {
134
147
  }))
135
148
  };
136
149
  }
137
- function makeRenderer(format, displayFormat) {
150
+ function makeRenderer(format, displayFormat, includeDiff) {
138
151
  return (status) => {
139
152
  if (format === "json") {
140
153
  const sectionSet = new Set(status.sections);
@@ -147,6 +160,10 @@ function makeRenderer(format, displayFormat) {
147
160
  if (sectionSet.has("releases")) filtered["releases"] = status.releases;
148
161
  if (sectionSet.has("vitals")) filtered["vitals"] = status.vitals;
149
162
  if (sectionSet.has("reviews")) filtered["reviews"] = status.reviews;
163
+ if (includeDiff?.sinceLast && includeDiff.prevStatus) {
164
+ filtered["diff"] = computeStatusDiff(includeDiff.prevStatus, status);
165
+ filtered["diffSince"] = includeDiff.prevStatus.fetchedAt;
166
+ }
150
167
  return formatOutput(filtered, "json");
151
168
  }
152
169
  const colorized = applyStatusColors(status);
@@ -159,29 +176,24 @@ function registerStatusCommand(program) {
159
176
  "--sections <list>",
160
177
  "Comma-separated sections: releases,vitals,reviews",
161
178
  "releases,vitals,reviews"
162
- ).option("--watch [seconds]", "Poll every N seconds (min 10, default 30)").option("--since-last", "Show diff from last cached status").option("--all-apps", "Run status for all configured app profiles (max 5)").option("--notify", "Send desktop notification on threshold breach or clear").option("--threshold <overrides>", "Override vitals thresholds: crashes=1.5,anr=0.5 (percent)").action(
179
+ ).option("--watch [seconds]", "Poll every N seconds (min 10, default 30)").option("--since-last", "Show diff from last cached status").option("--all-apps", `Run status for all configured app profiles (max ${MAX_ALL_APPS})`).option("--notify", "Send desktop notification on threshold breach or clear").option("--threshold <overrides>", "Override vitals thresholds: crashes=1.5,anr=0.5 (percent)").action(
163
180
  async (opts) => {
164
181
  if (!VALID_FORMATS.has(opts.format)) {
165
- console.error(`Error: Unknown format "${opts.format}". Valid: table, summary`);
166
- process.exit(2);
182
+ usageError(
183
+ `Unknown format "${opts.format}"`,
184
+ "Valid: table, summary"
185
+ );
167
186
  }
168
187
  const sections = parseSections(opts.sections);
169
188
  if (!Number.isFinite(opts.days) || opts.days < 1) {
170
- console.error(`Error: --days must be a positive integer (got: ${opts.days})`);
171
- process.exit(2);
189
+ usageError(`--days must be a positive integer (got: ${opts.days})`);
172
190
  }
173
191
  if (!Number.isFinite(opts.reviewDays) || opts.reviewDays < 1) {
174
- console.error(
175
- `Error: --review-days must be a positive integer (got: ${opts.reviewDays})`
176
- );
177
- process.exit(2);
192
+ usageError(`--review-days must be a positive integer (got: ${opts.reviewDays})`);
178
193
  }
179
194
  const config = await loadConfig();
180
195
  const format = getOutputFormat(program, config);
181
- const render = makeRenderer(format, opts.format);
182
- let vitalThresholds = resolveVitalThresholds(
183
- config
184
- );
196
+ let vitalThresholds = resolveVitalThresholds(config);
185
197
  if (opts.threshold) {
186
198
  const overrides = parseThresholdOverrides(opts.threshold);
187
199
  vitalThresholds = { ...vitalThresholds, ...overrides };
@@ -189,20 +201,22 @@ function registerStatusCommand(program) {
189
201
  const watchInterval = resolveWatchInterval(opts.watch);
190
202
  const packages = resolvePackages(program, config, opts.allApps);
191
203
  if (packages.length === 0) {
192
- console.error(
193
- "Error: No package name. Use --app <package> or gpc config set app <package>"
204
+ usageError(
205
+ "No package name",
206
+ "Use --app <package> or gpc config set app <package>"
194
207
  );
195
- process.exit(2);
196
208
  }
197
- if (opts.allApps && packages.length > 5) {
198
- console.error(
199
- `Error: --all-apps found ${packages.length} apps (max 5). Use --app to target a specific app.`
209
+ if (opts.allApps && packages.length > MAX_ALL_APPS) {
210
+ usageError(
211
+ `--all-apps found ${packages.length} apps (max ${MAX_ALL_APPS})`,
212
+ "Use --app to target a specific app"
200
213
  );
201
- process.exit(2);
202
214
  }
203
- const authConfig = config["auth"];
215
+ const authConfig = config.auth;
204
216
  const makeClients = async () => {
205
- const auth = await resolveAuth({ serviceAccountPath: authConfig?.["serviceAccount"] });
217
+ const auth = await resolveAuth({
218
+ serviceAccountPath: authConfig?.serviceAccount
219
+ });
206
220
  return {
207
221
  client: createApiClient({ auth }),
208
222
  reporting: createReportingClient({ auth })
@@ -210,8 +224,12 @@ function registerStatusCommand(program) {
210
224
  };
211
225
  let anyBreach = false;
212
226
  for (const packageName of packages) {
213
- if (packages.length > 1) console.log(`
214
- === ${packageName} ===`);
227
+ if (packages.length > 1) {
228
+ const label = statusHasBreach ? `
229
+ === ${packageName} ===` : `
230
+ === ${packageName} ===`;
231
+ console.log(label);
232
+ }
215
233
  try {
216
234
  const breach = await runStatusForPackage({
217
235
  packageName,
@@ -220,16 +238,15 @@ function registerStatusCommand(program) {
220
238
  format,
221
239
  vitalThresholds,
222
240
  watchInterval,
223
- render,
224
241
  makeClients
225
242
  });
226
243
  if (breach) anyBreach = true;
227
244
  } catch (error) {
245
+ if (packages.length === 1) throw error;
228
246
  console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
229
- if (packages.length === 1) process.exit(4);
230
247
  }
231
248
  }
232
- if (anyBreach) process.exit(6);
249
+ if (anyBreach) process.exitCode = 6;
233
250
  }
234
251
  );
235
252
  }
@@ -240,7 +257,7 @@ function applyDisplaySections(status, requestedSections) {
240
257
  return { ...status, sections: filtered };
241
258
  }
242
259
  async function runStatusForPackage(ctx) {
243
- const { packageName, opts, sections, vitalThresholds, watchInterval, render } = ctx;
260
+ const { packageName, opts, sections, vitalThresholds, watchInterval } = ctx;
244
261
  const fetchLive = async () => {
245
262
  const { client, reporting } = await ctx.makeClients();
246
263
  return getAppStatus(client, reporting, packageName, {
@@ -251,6 +268,11 @@ async function runStatusForPackage(ctx) {
251
268
  });
252
269
  };
253
270
  const save = (status2) => saveStatusCache(packageName, status2, opts.ttl);
271
+ const prevStatus = opts.sinceLast ? await loadStatusCache(packageName, Infinity) : null;
272
+ const render = makeRenderer(ctx.format, opts.format, {
273
+ prevStatus,
274
+ sinceLast: opts.sinceLast
275
+ });
254
276
  if (watchInterval !== null && opts.sinceLast) {
255
277
  process.stderr.write(
256
278
  "Warning: --since-last is not supported with --watch and will be ignored.\n"
@@ -263,15 +285,20 @@ async function runStatusForPackage(ctx) {
263
285
  if (opts.cached) {
264
286
  const cached = await loadStatusCache(packageName, opts.ttl);
265
287
  if (!cached) {
266
- console.error("Error: No cached status found. Run without --cached to fetch live data.");
267
- process.exit(1);
288
+ throw Object.assign(
289
+ new Error("No cached status found"),
290
+ {
291
+ code: "STATUS_NO_CACHE",
292
+ exitCode: 2,
293
+ suggestion: "Run without --cached to fetch live data"
294
+ }
295
+ );
268
296
  }
269
297
  const display = applyDisplaySections(cached, sections);
270
- console.log(render(display));
298
+ printWithDiff(display, prevStatus, opts.sinceLast, render, ctx.format);
271
299
  await handleNotify(packageName, cached, opts.notify);
272
300
  return statusHasBreach(cached);
273
301
  }
274
- const prevStatus = opts.sinceLast ? await loadStatusCache(packageName, Infinity) : null;
275
302
  if (!opts.refresh) {
276
303
  const cached = await loadStatusCache(packageName, opts.ttl);
277
304
  if (cached) {
@@ -302,22 +329,14 @@ async function runStatusForPackage(ctx) {
302
329
  await handleNotify(packageName, status, opts.notify);
303
330
  return statusHasBreach(status);
304
331
  }
305
- function relativeTime(isoString) {
306
- const diffMs = Date.now() - new Date(isoString).getTime();
307
- const diffMin = Math.floor(diffMs / 6e4);
308
- if (diffMin < 1) return "just now";
309
- if (diffMin < 60) return `${diffMin} min ago`;
310
- const diffHr = Math.floor(diffMin / 60);
311
- if (diffHr < 24) return `${diffHr}h ago`;
312
- return `${Math.floor(diffHr / 24)}d ago`;
313
- }
314
332
  function printWithDiff(status, prevStatus, sinceLast, render, format) {
315
333
  console.log(render(status));
334
+ if (format === "json") return;
316
335
  if (sinceLast && prevStatus) {
317
336
  const since = relativeTime(prevStatus.fetchedAt);
318
337
  console.log("");
319
338
  console.log(formatStatusDiff(computeStatusDiff(prevStatus, status), since));
320
- } else if (sinceLast && !prevStatus && format !== "json") {
339
+ } else if (sinceLast && !prevStatus) {
321
340
  console.log("\n(No prior cached status to diff against)");
322
341
  }
323
342
  }
@@ -332,6 +351,9 @@ async function handleNotify(packageName, status, notify) {
332
351
  }
333
352
  }
334
353
  export {
335
- registerStatusCommand
354
+ parseSections,
355
+ parseThresholdOverrides,
356
+ registerStatusCommand,
357
+ resolveWatchInterval
336
358
  };
337
- //# sourceMappingURL=status-WHGLODGV.js.map
359
+ //# sourceMappingURL=status-6LH5W4FU.js.map