@gpc-cli/cli 0.9.45 → 0.9.47
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/README.md +36 -15
- package/dist/anomalies-V3AFS4LD.js +66 -0
- package/dist/anomalies-V3AFS4LD.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 +6 -4
- 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-QLDFG5TV.js +48 -0
- package/dist/changelog-QLDFG5TV.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-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-SEVX56VN.js → chunk-WWVURXVO.js} +56 -49
- package/dist/chunk-WWVURXVO.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-NY3TZGVS.js} +8 -5
- package/dist/config-NY3TZGVS.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-QCCWG6Y3.js +708 -0
- package/dist/doctor-QCCWG6Y3.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-CET2X67K.js} +4 -4
- 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-Z3QBM3UO.js} +121 -194
- package/dist/purchases-Z3QBM3UO.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-276W3BR7.js} +188 -187
- package/dist/releases-276W3BR7.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-YCBBM656.js +199 -0
- package/dist/reviews-YCBBM656.js.map +1 -0
- package/dist/rtdn-LID2B7XZ.js +87 -0
- package/dist/rtdn-LID2B7XZ.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-XAO5EZHC.js} +30 -15
- package/dist/update-XAO5EZHC.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-R3P4NHCF.js} +4 -4
- package/dist/{vitals-A4CS4MSS.js → vitals-PJEQUUAK.js} +174 -165
- package/dist/vitals-PJEQUUAK.js.map +1 -0
- package/package.json +6 -6
- package/dist/anomalies-NU2IN2GJ.js +0 -54
- 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-SEVX56VN.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-CET2X67K.js.map} +0 -0
- /package/dist/{prompt-BSV22CQZ.js.map → prompt-GXC2JSLA.js.map} +0 -0
- /package/dist/{version-N64UBW7A.js.map → version-R3P4NHCF.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,199 @@
|
|
|
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
|
+
GpcError
|
|
30
|
+
} from "@gpc-cli/core";
|
|
31
|
+
var MAX_REPLY_CHARS = 350;
|
|
32
|
+
function registerReviewsCommands(program) {
|
|
33
|
+
const reviews = program.command("reviews").description("Manage user reviews and ratings");
|
|
34
|
+
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("--all", "Auto-paginate to fetch all reviews (API returns last 7 days only)").option("--sort <field>", "Sort by field (prefix with - for descending)").action(async (options) => {
|
|
35
|
+
const config = await loadConfig();
|
|
36
|
+
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
37
|
+
const client = await getClient(config);
|
|
38
|
+
const format = getOutputFormat(program, config);
|
|
39
|
+
const result = await listReviews(client, packageName, {
|
|
40
|
+
stars: options.stars,
|
|
41
|
+
language: options.lang,
|
|
42
|
+
since: options.since,
|
|
43
|
+
translationLanguage: options.translateTo,
|
|
44
|
+
maxResults: options.max,
|
|
45
|
+
limit: options.limit,
|
|
46
|
+
nextPage: options.nextPage,
|
|
47
|
+
all: options.all
|
|
48
|
+
});
|
|
49
|
+
if (Array.isArray(result) && result.length === 0 && format !== "json") {
|
|
50
|
+
console.log("No reviews found.");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const sorted = sortResults(result, options.sort);
|
|
54
|
+
if (format !== "json" && Array.isArray(sorted)) {
|
|
55
|
+
const rows = sorted.map((r) => {
|
|
56
|
+
const rv = r;
|
|
57
|
+
const comments = rv["comments"];
|
|
58
|
+
const userComment = comments?.[0]?.["userComment"];
|
|
59
|
+
return {
|
|
60
|
+
reviewId: rv["reviewId"] || "-",
|
|
61
|
+
author: rv["authorName"] || "-",
|
|
62
|
+
stars: userComment?.["starRating"] || "-",
|
|
63
|
+
text: String(userComment?.["text"] || "-").slice(0, 80),
|
|
64
|
+
lastModified: userComment?.["lastModified"] ? String(
|
|
65
|
+
userComment["lastModified"]?.["seconds"] || "-"
|
|
66
|
+
) : "-",
|
|
67
|
+
thumbsUp: userComment?.["thumbsUpCount"] || 0
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
await maybePaginate(formatOutput(rows, format));
|
|
71
|
+
} else {
|
|
72
|
+
await maybePaginate(formatOutput(sorted, format));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
reviews.command("get <review-id>").description("Get a single review").option("--translate-to <lang>", "Translate review to language").action(async (reviewId, options) => {
|
|
76
|
+
const config = await loadConfig();
|
|
77
|
+
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
78
|
+
const client = await getClient(config);
|
|
79
|
+
const format = getOutputFormat(program, config);
|
|
80
|
+
const result = await getReview(client, packageName, reviewId, options.translateTo);
|
|
81
|
+
console.log(formatOutput(result, format));
|
|
82
|
+
});
|
|
83
|
+
reviews.command("reply <review-id>").description("Reply to a review").option("--text <text>", "Reply text (max 350 chars)").action(async (reviewId, options) => {
|
|
84
|
+
const config = await loadConfig();
|
|
85
|
+
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
86
|
+
const format = getOutputFormat(program, config);
|
|
87
|
+
const interactive = isInteractive(program);
|
|
88
|
+
options.text = await requireOption(
|
|
89
|
+
"text",
|
|
90
|
+
options.text,
|
|
91
|
+
{
|
|
92
|
+
message: "Reply text (max 350 chars):"
|
|
93
|
+
},
|
|
94
|
+
interactive
|
|
95
|
+
);
|
|
96
|
+
if (options.text.length > MAX_REPLY_CHARS) {
|
|
97
|
+
throw new GpcError(
|
|
98
|
+
`Reply text exceeds ${MAX_REPLY_CHARS} characters (${options.text.length} chars). Google Play will reject this reply.`,
|
|
99
|
+
"REVIEWS_USAGE_ERROR",
|
|
100
|
+
2,
|
|
101
|
+
`Shorten your reply to ${MAX_REPLY_CHARS} characters or fewer.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (isDryRun(program)) {
|
|
105
|
+
printDryRun(
|
|
106
|
+
{
|
|
107
|
+
command: "reviews reply",
|
|
108
|
+
action: "reply to",
|
|
109
|
+
target: reviewId,
|
|
110
|
+
details: { text: options.text }
|
|
111
|
+
},
|
|
112
|
+
format,
|
|
113
|
+
formatOutput
|
|
114
|
+
);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const client = await getClient(config);
|
|
118
|
+
const result = await replyToReview(client, packageName, reviewId, options.text);
|
|
119
|
+
if (format !== "json") {
|
|
120
|
+
const charCount = options.text.length;
|
|
121
|
+
console.log(`Reply sent (${charCount}/350 chars)`);
|
|
122
|
+
}
|
|
123
|
+
console.log(formatOutput(result, format));
|
|
124
|
+
});
|
|
125
|
+
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) => {
|
|
126
|
+
const config = await loadConfig();
|
|
127
|
+
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
128
|
+
const client = await getClient(config);
|
|
129
|
+
const format = getOutputFormat(program, config);
|
|
130
|
+
const result = await analyzeReviews(client, packageName, {
|
|
131
|
+
stars: options.stars,
|
|
132
|
+
language: options.lang,
|
|
133
|
+
since: options.since,
|
|
134
|
+
maxResults: options.max
|
|
135
|
+
});
|
|
136
|
+
if (format === "json") {
|
|
137
|
+
console.log(formatOutput(result, format));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
console.log(`
|
|
141
|
+
Review Analysis \u2014 ${packageName}`);
|
|
142
|
+
console.log(`${"\u2500".repeat(50)}`);
|
|
143
|
+
console.log(`Total reviews: ${result.totalReviews}`);
|
|
144
|
+
console.log(
|
|
145
|
+
`Average rating: ${result.avgRating > 0 ? result.avgRating.toFixed(2) + " \u2605" : "N/A"}`
|
|
146
|
+
);
|
|
147
|
+
console.log(`
|
|
148
|
+
Sentiment:`);
|
|
149
|
+
console.log(
|
|
150
|
+
` Positive: ${result.sentiment.positive} Negative: ${result.sentiment.negative} Neutral: ${result.sentiment.neutral}`
|
|
151
|
+
);
|
|
152
|
+
console.log(` Avg score: ${result.sentiment.avgScore} (range -1 to +1)`);
|
|
153
|
+
if (result.topics.length > 0) {
|
|
154
|
+
console.log(`
|
|
155
|
+
Top topics:`);
|
|
156
|
+
for (const t of result.topics.slice(0, 8)) {
|
|
157
|
+
const bar = t.avgScore > 0.1 ? "+" : t.avgScore < -0.1 ? "-" : "~";
|
|
158
|
+
console.log(` [${bar}] ${t.topic.padEnd(20)} ${t.count} reviews`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (result.keywords.length > 0) {
|
|
162
|
+
console.log(
|
|
163
|
+
`
|
|
164
|
+
Top keywords: ${result.keywords.slice(0, 10).map((k) => k.word).join(", ")}`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (Object.keys(result.ratingDistribution).length > 0) {
|
|
168
|
+
console.log(`
|
|
169
|
+
Rating distribution:`);
|
|
170
|
+
for (let star = 5; star >= 1; star--) {
|
|
171
|
+
const count = result.ratingDistribution[star] ?? 0;
|
|
172
|
+
if (count > 0) console.log(` ${star}\u2605 ${count}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
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) => {
|
|
177
|
+
const config = await loadConfig();
|
|
178
|
+
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
179
|
+
const client = await getClient(config);
|
|
180
|
+
const result = await exportReviews(client, packageName, {
|
|
181
|
+
format: options.format,
|
|
182
|
+
stars: options.stars,
|
|
183
|
+
language: options.lang,
|
|
184
|
+
since: options.since,
|
|
185
|
+
translationLanguage: options.translateTo
|
|
186
|
+
});
|
|
187
|
+
if (options.output) {
|
|
188
|
+
const { writeFile } = await import("fs/promises");
|
|
189
|
+
await writeFile(options.output, result, "utf-8");
|
|
190
|
+
console.log(`Reviews exported to ${options.output}`);
|
|
191
|
+
} else {
|
|
192
|
+
console.log(result);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
export {
|
|
197
|
+
registerReviewsCommands
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=reviews-YCBBM656.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 GpcError,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport { isInteractive, requireOption } from \"../prompt.js\";\n\nconst MAX_REPLY_CHARS = 350;\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(\"--all\", \"Auto-paginate to fetch all reviews (API returns last 7 days only)\")\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 all: options.all,\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 (options.text.length > MAX_REPLY_CHARS) {\n throw new GpcError(\n `Reply text exceeds ${MAX_REPLY_CHARS} characters (${options.text.length} chars). Google Play will reject this reply.`,\n \"REVIEWS_USAGE_ERROR\",\n 2,\n `Shorten your reply to ${MAX_REPLY_CHARS} characters or fewer.`,\n );\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":[]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getClient,
|
|
4
|
+
resolvePackageName
|
|
5
|
+
} from "./chunk-NQH4G7BI.js";
|
|
6
|
+
import {
|
|
7
|
+
yellow
|
|
8
|
+
} from "./chunk-FAN4ZITI.js";
|
|
9
|
+
import {
|
|
10
|
+
getOutputFormat
|
|
11
|
+
} from "./chunk-ELXAK7GI.js";
|
|
12
|
+
|
|
13
|
+
// src/commands/rtdn.ts
|
|
14
|
+
import { loadConfig } from "@gpc-cli/config";
|
|
15
|
+
import {
|
|
16
|
+
getRtdnStatus,
|
|
17
|
+
decodeNotification,
|
|
18
|
+
formatNotification,
|
|
19
|
+
formatOutput
|
|
20
|
+
} from "@gpc-cli/core";
|
|
21
|
+
function registerRtdnCommands(program) {
|
|
22
|
+
const rtdn = program.command("rtdn").description("Real-Time Developer Notifications (Pub/Sub)");
|
|
23
|
+
rtdn.command("status").description("Check RTDN notification topic configuration").action(async () => {
|
|
24
|
+
const config = await loadConfig();
|
|
25
|
+
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
26
|
+
const client = await getClient(config);
|
|
27
|
+
const format = getOutputFormat(program, config);
|
|
28
|
+
const status = await getRtdnStatus(client, packageName);
|
|
29
|
+
if (format === "json") {
|
|
30
|
+
console.log(formatOutput({ packageName, ...status }, format));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log(`
|
|
34
|
+
RTDN Status \u2014 ${packageName}`);
|
|
35
|
+
console.log(`${"\u2500".repeat(50)}`);
|
|
36
|
+
if (status.topicName) {
|
|
37
|
+
console.log(`Topic: ${status.topicName}`);
|
|
38
|
+
console.log(`Enabled: ${status.enabled ? "yes" : "no"}`);
|
|
39
|
+
} else {
|
|
40
|
+
console.log(`${yellow("\u26A0")} No RTDN topic configured.`);
|
|
41
|
+
console.log(`
|
|
42
|
+
To set up RTDN:`);
|
|
43
|
+
console.log(` 1. Create a Pub/Sub topic in your GCP project`);
|
|
44
|
+
console.log(` 2. Grant google-play-developer-notifications@system.gserviceaccount.com the Pub/Sub Publisher role`);
|
|
45
|
+
console.log(` 3. Set the topic in Play Console \u2192 Monetization setup \u2192 Real-time developer notifications`);
|
|
46
|
+
console.log(` 4. Or use: gpc rtdn setup --topic projects/<PROJECT>/topics/<TOPIC>`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
rtdn.command("decode <payload>").description("Decode a base64-encoded Pub/Sub notification payload").action(async (payload) => {
|
|
50
|
+
const config = await loadConfig();
|
|
51
|
+
const format = getOutputFormat(program, config);
|
|
52
|
+
const notification = decodeNotification(payload);
|
|
53
|
+
const formatted = formatNotification(notification);
|
|
54
|
+
if (format === "json") {
|
|
55
|
+
console.log(formatOutput(notification, format));
|
|
56
|
+
} else {
|
|
57
|
+
console.log(formatOutput(formatted, format));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
rtdn.command("test").description("Send a test notification to verify RTDN setup").action(async () => {
|
|
61
|
+
const config = await loadConfig();
|
|
62
|
+
const format = getOutputFormat(program, config);
|
|
63
|
+
if (format !== "json") {
|
|
64
|
+
console.log(`${yellow("\u26A0")} Test notifications can only be triggered from the Play Console.`);
|
|
65
|
+
console.log(`
|
|
66
|
+
To test RTDN:`);
|
|
67
|
+
console.log(` 1. Open Play Console \u2192 Monetization setup \u2192 Real-time developer notifications`);
|
|
68
|
+
console.log(` 2. Click "Send test notification"`);
|
|
69
|
+
console.log(` 3. Check your Pub/Sub subscription for the test message`);
|
|
70
|
+
console.log(` 4. Decode it with: gpc rtdn decode <base64-payload>`);
|
|
71
|
+
} else {
|
|
72
|
+
console.log(formatOutput({
|
|
73
|
+
message: "Test notifications can only be triggered from the Play Console",
|
|
74
|
+
steps: [
|
|
75
|
+
"Open Play Console \u2192 Monetization setup \u2192 RTDN",
|
|
76
|
+
"Click 'Send test notification'",
|
|
77
|
+
"Check your Pub/Sub subscription",
|
|
78
|
+
"Decode with: gpc rtdn decode <payload>"
|
|
79
|
+
]
|
|
80
|
+
}, format));
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
registerRtdnCommands
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=rtdn-LID2B7XZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/rtdn.ts"],"sourcesContent":["import { resolvePackageName, getClient } from \"../resolve.js\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport {\n getRtdnStatus,\n decodeNotification,\n formatNotification,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { yellow } from \"../colors.js\";\n\nexport function registerRtdnCommands(program: Command): void {\n const rtdn = program\n .command(\"rtdn\")\n .description(\"Real-Time Developer Notifications (Pub/Sub)\");\n\n rtdn\n .command(\"status\")\n .description(\"Check RTDN notification topic configuration\")\n .action(async () => {\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 status = await getRtdnStatus(client, packageName);\n\n if (format === \"json\") {\n console.log(formatOutput({ packageName, ...status }, format));\n return;\n }\n\n console.log(`\\nRTDN Status — ${packageName}`);\n console.log(`${\"─\".repeat(50)}`);\n if (status.topicName) {\n console.log(`Topic: ${status.topicName}`);\n console.log(`Enabled: ${status.enabled ? \"yes\" : \"no\"}`);\n } else {\n console.log(`${yellow(\"⚠\")} No RTDN topic configured.`);\n console.log(`\\nTo set up RTDN:`);\n console.log(` 1. Create a Pub/Sub topic in your GCP project`);\n console.log(` 2. Grant google-play-developer-notifications@system.gserviceaccount.com the Pub/Sub Publisher role`);\n console.log(` 3. Set the topic in Play Console → Monetization setup → Real-time developer notifications`);\n console.log(` 4. Or use: gpc rtdn setup --topic projects/<PROJECT>/topics/<TOPIC>`);\n }\n });\n\n rtdn\n .command(\"decode <payload>\")\n .description(\"Decode a base64-encoded Pub/Sub notification payload\")\n .action(async (payload: string) => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n\n const notification = decodeNotification(payload);\n const formatted = formatNotification(notification);\n\n if (format === \"json\") {\n console.log(formatOutput(notification, format));\n } else {\n console.log(formatOutput(formatted, format));\n }\n });\n\n rtdn\n .command(\"test\")\n .description(\"Send a test notification to verify RTDN setup\")\n .action(async () => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n\n if (format !== \"json\") {\n console.log(`${yellow(\"⚠\")} Test notifications can only be triggered from the Play Console.`);\n console.log(`\\nTo test RTDN:`);\n console.log(` 1. Open Play Console → Monetization setup → Real-time developer notifications`);\n console.log(` 2. Click \"Send test notification\"`);\n console.log(` 3. Check your Pub/Sub subscription for the test message`);\n console.log(` 4. Decode it with: gpc rtdn decode <base64-payload>`);\n } else {\n console.log(formatOutput({\n message: \"Test notifications can only be triggered from the Play Console\",\n steps: [\n \"Open Play Console → Monetization setup → RTDN\",\n \"Click 'Send test notification'\",\n \"Check your Pub/Sub subscription\",\n \"Decode with: gpc rtdn decode <payload>\",\n ],\n }, format));\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;AAEA,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIA,SAAS,qBAAqB,SAAwB;AAC3D,QAAM,OAAO,QACV,QAAQ,MAAM,EACd,YAAY,6CAA6C;AAE5D,OACG,QAAQ,QAAQ,EAChB,YAAY,6CAA6C,EACzD,OAAO,YAAY;AAClB,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,cAAc,QAAQ,WAAW;AAEtD,QAAI,WAAW,QAAQ;AACrB,cAAQ,IAAI,aAAa,EAAE,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAC5D;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,qBAAmB,WAAW,EAAE;AAC5C,YAAQ,IAAI,GAAG,SAAI,OAAO,EAAE,CAAC,EAAE;AAC/B,QAAI,OAAO,WAAW;AACpB,cAAQ,IAAI,YAAY,OAAO,SAAS,EAAE;AAC1C,cAAQ,IAAI,YAAY,OAAO,UAAU,QAAQ,IAAI,EAAE;AAAA,IACzD,OAAO;AACL,cAAQ,IAAI,GAAG,OAAO,QAAG,CAAC,4BAA4B;AACtD,cAAQ,IAAI;AAAA,gBAAmB;AAC/B,cAAQ,IAAI,iDAAiD;AAC7D,cAAQ,IAAI,sGAAsG;AAClH,cAAQ,IAAI,uGAA6F;AACzG,cAAQ,IAAI,uEAAuE;AAAA,IACrF;AAAA,EACF,CAAC;AAEH,OACG,QAAQ,kBAAkB,EAC1B,YAAY,sDAAsD,EAClE,OAAO,OAAO,YAAoB;AACjC,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,eAAe,mBAAmB,OAAO;AAC/C,UAAM,YAAY,mBAAmB,YAAY;AAEjD,QAAI,WAAW,QAAQ;AACrB,cAAQ,IAAI,aAAa,cAAc,MAAM,CAAC;AAAA,IAChD,OAAO;AACL,cAAQ,IAAI,aAAa,WAAW,MAAM,CAAC;AAAA,IAC7C;AAAA,EACF,CAAC;AAEH,OACG,QAAQ,MAAM,EACd,YAAY,+CAA+C,EAC3D,OAAO,YAAY;AAClB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,QAAI,WAAW,QAAQ;AACrB,cAAQ,IAAI,GAAG,OAAO,QAAG,CAAC,kEAAkE;AAC5F,cAAQ,IAAI;AAAA,cAAiB;AAC7B,cAAQ,IAAI,2FAAiF;AAC7F,cAAQ,IAAI,qCAAqC;AACjD,cAAQ,IAAI,2DAA2D;AACvE,cAAQ,IAAI,uDAAuD;AAAA,IACrE,OAAO;AACL,cAAQ,IAAI,aAAa;AAAA,QACvB,SAAS;AAAA,QACT,OAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF,GAAG,MAAM,CAAC;AAAA,IACZ;AAAA,EACF,CAAC;AACL;","names":[]}
|