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