@gpc-cli/cli 0.9.45 → 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.
- package/dist/{anomalies-NU2IN2GJ.js → anomalies-UDE4NGHJ.js} +19 -24
- package/dist/anomalies-UDE4NGHJ.js.map +1 -0
- package/dist/{apps-J2446UDA.js → apps-FKD3ZG5X.js} +31 -35
- package/dist/apps-FKD3ZG5X.js.map +1 -0
- package/dist/{audit-N2CRHWUN.js → audit-JASSHRWN.js} +47 -62
- package/dist/audit-JASSHRWN.js.map +1 -0
- package/dist/{auth-XGSTT5G5.js → auth-OTA3SV3J.js} +145 -103
- package/dist/auth-OTA3SV3J.js.map +1 -0
- package/dist/bin.js +5 -3
- package/dist/bin.js.map +1 -1
- package/dist/bundle-F7MUVC5J.js +204 -0
- package/dist/bundle-F7MUVC5J.js.map +1 -0
- package/dist/{cache-SLNFRTI2.js → cache-XKPLZYEB.js} +4 -5
- package/dist/cache-XKPLZYEB.js.map +1 -0
- package/dist/changelog-7COFZO7Q.js +48 -0
- package/dist/changelog-7COFZO7Q.js.map +1 -0
- package/dist/{chunk-4O4D5SGL.js → chunk-3SJ6OXCZ.js} +4 -5
- package/dist/chunk-3SJ6OXCZ.js.map +1 -0
- package/dist/{chunk-SEVX56VN.js → chunk-6OWN6S6X.js} +52 -48
- package/dist/{chunk-SEVX56VN.js.map → chunk-6OWN6S6X.js.map} +1 -1
- package/dist/{chunk-U6ZTQ34I.js → chunk-BCBXQC7J.js} +45 -11
- package/dist/chunk-BCBXQC7J.js.map +1 -0
- package/dist/{chunk-AA577WVQ.js → chunk-NQH4G7BI.js} +9 -3
- package/dist/chunk-NQH4G7BI.js.map +1 -0
- package/dist/chunk-SLNJEAMK.js +23 -0
- package/dist/chunk-SLNJEAMK.js.map +1 -0
- package/dist/{chunk-NV75I5VP.js → chunk-YFUBD2XB.js} +10 -8
- package/dist/chunk-YFUBD2XB.js.map +1 -0
- package/dist/{config-BUXPDN7N.js → config-2FTCYEGD.js} +8 -5
- package/dist/config-2FTCYEGD.js.map +1 -0
- package/dist/{data-safety-Q7FTCEWU.js → data-safety-AFMD6MYI.js} +12 -27
- package/dist/data-safety-AFMD6MYI.js.map +1 -0
- package/dist/{device-tiers-MIOQEXYY.js → device-tiers-AQAMUQXI.js} +23 -38
- package/dist/device-tiers-AQAMUQXI.js.map +1 -0
- package/dist/diff-6EO4ID6W.js +91 -0
- package/dist/diff-6EO4ID6W.js.map +1 -0
- package/dist/{docs-7DUXIKA3.js → docs-4D2SJ4LY.js} +4 -3
- package/dist/docs-4D2SJ4LY.js.map +1 -0
- package/dist/doctor-H4X7Q57B.js +691 -0
- package/dist/doctor-H4X7Q57B.js.map +1 -0
- package/dist/{enterprise-7THXNBTC.js → enterprise-7PWXMSUN.js} +11 -21
- package/dist/enterprise-7PWXMSUN.js.map +1 -0
- package/dist/{external-transactions-2GWIMUVM.js → external-transactions-LCZALS3V.js} +12 -28
- package/dist/external-transactions-LCZALS3V.js.map +1 -0
- package/dist/{feedback-DPTO6DUT.js → feedback-XP765TOO.js} +3 -3
- package/dist/{games-BT777WUO.js → games-ZSNGEI7A.js} +17 -32
- package/dist/games-ZSNGEI7A.js.map +1 -0
- package/dist/{generated-apks-RJWTIX7L.js → generated-apks-RX2IUWSF.js} +30 -38
- package/dist/generated-apks-RX2IUWSF.js.map +1 -0
- package/dist/{grants-TKQJ3IER.js → grants-EBPECI26.js} +22 -40
- package/dist/grants-EBPECI26.js.map +1 -0
- package/dist/{iap-ICAEQLK5.js → iap-OUI5YYN4.js} +30 -51
- package/dist/iap-OUI5YYN4.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/{init-JZ2THPMS.js → init-WSTQTJOD.js} +5 -4
- package/dist/init-WSTQTJOD.js.map +1 -0
- package/dist/{install-skills-OV4HVANW.js → install-skills-6QDUXI5F.js} +5 -6
- package/dist/{install-skills-OV4HVANW.js.map → install-skills-6QDUXI5F.js.map} +1 -1
- package/dist/{internal-sharing-3U2XFHA4.js → internal-sharing-ONNIWIAT.js} +3 -4
- package/dist/{internal-sharing-3U2XFHA4.js.map → internal-sharing-ONNIWIAT.js.map} +1 -1
- package/dist/{listings-77HZW4S5.js → listings-7SGQ4SRX.js} +118 -157
- package/dist/listings-7SGQ4SRX.js.map +1 -0
- package/dist/migrate-ZQCJGQQS.js +138 -0
- package/dist/migrate-ZQCJGQQS.js.map +1 -0
- package/dist/{one-time-products-LHZAXQES.js → one-time-products-MGZTU7OM.js} +65 -120
- package/dist/one-time-products-MGZTU7OM.js.map +1 -0
- package/dist/{preflight-H3HEBYQW.js → preflight-N7ZRG2JI.js} +58 -55
- package/dist/preflight-N7ZRG2JI.js.map +1 -0
- package/dist/{pricing-XQSDTTK5.js → pricing-JJZFICFL.js} +8 -8
- package/dist/{pricing-XQSDTTK5.js.map → pricing-JJZFICFL.js.map} +1 -1
- package/dist/{prompt-BSV22CQZ.js → prompt-GXC2JSLA.js} +2 -2
- package/dist/{publish-Q5ZKEKZ5.js → publish-JPTI4EBT.js} +34 -30
- package/dist/publish-JPTI4EBT.js.map +1 -0
- package/dist/{purchase-options-CKRN4VIW.js → purchase-options-KFWW4JW2.js} +16 -11
- package/dist/purchase-options-KFWW4JW2.js.map +1 -0
- package/dist/{purchases-43AKV6HG.js → purchases-DAWTMXP6.js} +118 -193
- package/dist/purchases-DAWTMXP6.js.map +1 -0
- package/dist/{quickstart-4HB62YEL.js → quickstart-Z5Y3FYJU.js} +5 -3
- package/dist/quickstart-Z5Y3FYJU.js.map +1 -0
- package/dist/{quota-UHIQQYOY.js → quota-MZRWYJGR.js} +5 -15
- package/dist/quota-MZRWYJGR.js.map +1 -0
- package/dist/{recovery-5EV2R476.js → recovery-YE3Z7NIN.js} +32 -61
- package/dist/recovery-YE3Z7NIN.js.map +1 -0
- package/dist/{releases-C2WC2K4E.js → releases-2I3WBULC.js} +184 -185
- package/dist/releases-2I3WBULC.js.map +1 -0
- package/dist/{reports-2YX3RDOS.js → reports-CIB2T3XT.js} +19 -21
- package/dist/reports-CIB2T3XT.js.map +1 -0
- package/dist/reviews-BCCXIQ6C.js +188 -0
- package/dist/reviews-BCCXIQ6C.js.map +1 -0
- package/dist/{status-WHGLODGV.js → status-6LH5W4FU.js} +105 -83
- package/dist/status-6LH5W4FU.js.map +1 -0
- package/dist/{subscriptions-CI3JH3VQ.js → subscriptions-DZP3Y7O7.js} +142 -232
- package/dist/subscriptions-DZP3Y7O7.js.map +1 -0
- package/dist/{testers-NZOFA3EF.js → testers-LSMBXCA2.js} +24 -44
- package/dist/testers-LSMBXCA2.js.map +1 -0
- package/dist/tracks-YHMO2A6B.js +98 -0
- package/dist/tracks-YHMO2A6B.js.map +1 -0
- package/dist/{train-CJJVLY4B.js → train-MDD2EBHS.js} +35 -55
- package/dist/train-MDD2EBHS.js.map +1 -0
- package/dist/{update-NAK6CMUX.js → update-OMALGIBR.js} +29 -14
- package/dist/update-OMALGIBR.js.map +1 -0
- package/dist/{users-2YTC4Q36.js → users-UKG7VIQH.js} +45 -67
- package/dist/users-UKG7VIQH.js.map +1 -0
- package/dist/{validate-UOVTM6L3.js → validate-QIYSA3N7.js} +8 -10
- package/dist/validate-QIYSA3N7.js.map +1 -0
- package/dist/{version-N64UBW7A.js → version-NCSNXNVN.js} +3 -3
- package/dist/{vitals-A4CS4MSS.js → vitals-C23L2Y2E.js} +153 -172
- package/dist/vitals-C23L2Y2E.js.map +1 -0
- package/package.json +6 -6
- package/dist/anomalies-NU2IN2GJ.js.map +0 -1
- package/dist/apps-J2446UDA.js.map +0 -1
- package/dist/audit-N2CRHWUN.js.map +0 -1
- package/dist/auth-XGSTT5G5.js.map +0 -1
- package/dist/bundle-F43TD2BQ.js +0 -218
- package/dist/bundle-F43TD2BQ.js.map +0 -1
- package/dist/cache-SLNFRTI2.js.map +0 -1
- package/dist/changelog-ZYD6W5IV.js +0 -53
- package/dist/changelog-ZYD6W5IV.js.map +0 -1
- package/dist/chunk-4O4D5SGL.js.map +0 -1
- package/dist/chunk-AA577WVQ.js.map +0 -1
- package/dist/chunk-FWKYRLKY.js +0 -19
- package/dist/chunk-FWKYRLKY.js.map +0 -1
- package/dist/chunk-NV75I5VP.js.map +0 -1
- package/dist/chunk-U6ZTQ34I.js.map +0 -1
- package/dist/config-BUXPDN7N.js.map +0 -1
- package/dist/data-safety-Q7FTCEWU.js.map +0 -1
- package/dist/device-tiers-MIOQEXYY.js.map +0 -1
- package/dist/diff-V77SMKAQ.js +0 -96
- package/dist/diff-V77SMKAQ.js.map +0 -1
- package/dist/docs-7DUXIKA3.js.map +0 -1
- package/dist/doctor-3Z4ARPM2.js +0 -372
- package/dist/doctor-3Z4ARPM2.js.map +0 -1
- package/dist/enterprise-7THXNBTC.js.map +0 -1
- package/dist/external-transactions-2GWIMUVM.js.map +0 -1
- package/dist/games-BT777WUO.js.map +0 -1
- package/dist/generated-apks-RJWTIX7L.js.map +0 -1
- package/dist/grants-TKQJ3IER.js.map +0 -1
- package/dist/iap-ICAEQLK5.js.map +0 -1
- package/dist/init-JZ2THPMS.js.map +0 -1
- package/dist/listings-77HZW4S5.js.map +0 -1
- package/dist/migrate-SQT6RD6T.js +0 -143
- package/dist/migrate-SQT6RD6T.js.map +0 -1
- package/dist/one-time-products-LHZAXQES.js.map +0 -1
- package/dist/preflight-H3HEBYQW.js.map +0 -1
- package/dist/publish-Q5ZKEKZ5.js.map +0 -1
- package/dist/purchase-options-CKRN4VIW.js.map +0 -1
- package/dist/purchases-43AKV6HG.js.map +0 -1
- package/dist/quickstart-4HB62YEL.js.map +0 -1
- package/dist/quota-UHIQQYOY.js.map +0 -1
- package/dist/recovery-5EV2R476.js.map +0 -1
- package/dist/releases-C2WC2K4E.js.map +0 -1
- package/dist/reports-2YX3RDOS.js.map +0 -1
- package/dist/reviews-2CWOI5CV.js +0 -213
- package/dist/reviews-2CWOI5CV.js.map +0 -1
- package/dist/status-WHGLODGV.js.map +0 -1
- package/dist/subscriptions-CI3JH3VQ.js.map +0 -1
- package/dist/testers-NZOFA3EF.js.map +0 -1
- package/dist/tracks-NERFFEDT.js +0 -107
- package/dist/tracks-NERFFEDT.js.map +0 -1
- package/dist/train-CJJVLY4B.js.map +0 -1
- package/dist/update-NAK6CMUX.js.map +0 -1
- package/dist/users-2YTC4Q36.js.map +0 -1
- package/dist/validate-UOVTM6L3.js.map +0 -1
- package/dist/vitals-A4CS4MSS.js.map +0 -1
- /package/dist/{feedback-DPTO6DUT.js.map → feedback-XP765TOO.js.map} +0 -0
- /package/dist/{prompt-BSV22CQZ.js.map → prompt-GXC2JSLA.js.map} +0 -0
- /package/dist/{version-N64UBW7A.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
|
-
|
|
38
|
-
`
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
56
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
`
|
|
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
|
-
|
|
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-
|
|
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":"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","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
|
-
|
|
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
|
-
|
|
76
|
-
`
|
|
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
|
-
|
|
83
|
-
`
|
|
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",
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
193
|
-
"
|
|
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 >
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
215
|
+
const authConfig = config.auth;
|
|
204
216
|
const makeClients = async () => {
|
|
205
|
-
const auth = await resolveAuth({
|
|
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)
|
|
214
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
267
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
354
|
+
parseSections,
|
|
355
|
+
parseThresholdOverrides,
|
|
356
|
+
registerStatusCommand,
|
|
357
|
+
resolveWatchInterval
|
|
336
358
|
};
|
|
337
|
-
//# sourceMappingURL=status-
|
|
359
|
+
//# sourceMappingURL=status-6LH5W4FU.js.map
|