@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
resolvePackageName
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-NQH4G7BI.js";
|
|
5
5
|
import {
|
|
6
6
|
green,
|
|
7
7
|
red,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
// src/commands/vitals.ts
|
|
15
15
|
import { loadConfig } from "@gpc-cli/config";
|
|
16
16
|
import { resolveAuth } from "@gpc-cli/auth";
|
|
17
|
-
import { createReportingClient } from "@gpc-cli/api";
|
|
17
|
+
import { createReportingClient, PlayApiError } from "@gpc-cli/api";
|
|
18
18
|
import {
|
|
19
19
|
getVitalsOverview,
|
|
20
20
|
getVitalsCrashes,
|
|
@@ -30,7 +30,8 @@ import {
|
|
|
30
30
|
compareVersionVitals,
|
|
31
31
|
watchVitalsWithAutoHalt,
|
|
32
32
|
checkThreshold,
|
|
33
|
-
formatOutput
|
|
33
|
+
formatOutput,
|
|
34
|
+
GpcError
|
|
34
35
|
} from "@gpc-cli/core";
|
|
35
36
|
async function getReportingClient(config) {
|
|
36
37
|
const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });
|
|
@@ -64,9 +65,12 @@ var THRESHOLD_CONFIG_KEYS = {
|
|
|
64
65
|
};
|
|
65
66
|
function validateDimension(dim) {
|
|
66
67
|
if (!VALID_DIMENSIONS.includes(dim)) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
throw new GpcError(
|
|
69
|
+
`Invalid dimension "${dim}". Valid dimensions: ${VALID_DIMENSIONS.join(", ")}`,
|
|
70
|
+
"VITALS_USAGE_ERROR",
|
|
71
|
+
2,
|
|
72
|
+
`Use one of: ${VALID_DIMENSIONS.join(", ")}`
|
|
73
|
+
);
|
|
70
74
|
}
|
|
71
75
|
return dim;
|
|
72
76
|
}
|
|
@@ -76,70 +80,79 @@ function registerMetricCommand(parent, name, description, fn, program) {
|
|
|
76
80
|
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
77
81
|
const reporting = await getReportingClient(config);
|
|
78
82
|
const format = getOutputFormat(program, config);
|
|
83
|
+
let result;
|
|
79
84
|
try {
|
|
80
|
-
|
|
85
|
+
result = await fn(reporting, packageName, {
|
|
81
86
|
dimension: options.dim ? validateDimension(options.dim) : void 0,
|
|
82
87
|
days: options.days
|
|
83
88
|
});
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (err instanceof PlayApiError && err.statusCode === 403) {
|
|
91
|
+
if (format === "json") {
|
|
92
|
+
console.log(formatOutput({ rows: [], message: "Reporting API not enabled or insufficient permissions" }, format));
|
|
93
|
+
} else {
|
|
94
|
+
console.log(`${yellow("\u26A0")} No vitals data available. The Reporting API may not be enabled for this project.`);
|
|
95
|
+
console.log(` Enable it at: https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com`);
|
|
96
|
+
}
|
|
86
97
|
return;
|
|
87
98
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
flat[key] = val?.["decimalValue"] !== void 0 ? val?.["decimalValue"]?.["value"] ?? "-" : "-";
|
|
106
|
-
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
if (format !== "json" && (!result.rows || result.rows.length === 0)) {
|
|
102
|
+
console.log(`${yellow("\u26A0")} No vitals data available.`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (format !== "json" && result.rows) {
|
|
106
|
+
const rows = result.rows.map((row) => {
|
|
107
|
+
const rowR = row;
|
|
108
|
+
const startTime = rowR["startTime"];
|
|
109
|
+
const metrics = rowR["metrics"];
|
|
110
|
+
const flat = {
|
|
111
|
+
date: startTime ? `${startTime["year"]}-${String(startTime["month"]).padStart(2, "0")}-${String(startTime["day"]).padStart(2, "0")}` : "-"
|
|
112
|
+
};
|
|
113
|
+
if (metrics && !Array.isArray(metrics)) {
|
|
114
|
+
for (const [key, val] of Object.entries(metrics)) {
|
|
115
|
+
flat[key] = val?.["decimalValue"] !== void 0 ? val?.["decimalValue"]?.["value"] ?? "-" : "-";
|
|
107
116
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
for (let i = 0; i < dims.length; i++) {
|
|
115
|
-
const val = dims[i];
|
|
116
|
-
const dimName = val?.["dimension"];
|
|
117
|
-
flat[dimName ?? `dim${i}`] = val?.["stringValue"] ?? val?.["int64Value"] ?? "-";
|
|
118
|
-
}
|
|
117
|
+
} else if (Array.isArray(metrics)) {
|
|
118
|
+
for (let i = 0; i < metrics.length; i++) {
|
|
119
|
+
const val = metrics[i];
|
|
120
|
+
const metricName = val?.["metric"];
|
|
121
|
+
const key = metricName ?? `metric${i}`;
|
|
122
|
+
flat[key] = val?.["decimalValue"] !== void 0 ? val?.["decimalValue"]?.["value"] ?? "-" : "-";
|
|
119
123
|
}
|
|
120
|
-
return flat;
|
|
121
|
-
});
|
|
122
|
-
console.log(formatOutput(rows, format));
|
|
123
|
-
} else {
|
|
124
|
-
console.log(formatOutput(result, format));
|
|
125
|
-
}
|
|
126
|
-
const configKey = THRESHOLD_CONFIG_KEYS[name];
|
|
127
|
-
const configThreshold = configKey ? config["vitals"] ? config["vitals"]["thresholds"] ? config["vitals"]["thresholds"][configKey] : void 0 : void 0 : void 0;
|
|
128
|
-
const threshold = options.threshold ?? (configThreshold !== void 0 ? Number(configThreshold) : void 0);
|
|
129
|
-
if (threshold !== void 0) {
|
|
130
|
-
const latestRow = result.rows?.[result.rows.length - 1];
|
|
131
|
-
const metricKeys = latestRow?.metrics ? Object.keys(latestRow.metrics) : [];
|
|
132
|
-
const firstMetric = metricKeys[0];
|
|
133
|
-
const value = firstMetric ? Number(latestRow?.metrics[firstMetric]?.decimalValue?.value) : void 0;
|
|
134
|
-
const check = checkThreshold(value, threshold);
|
|
135
|
-
if (check.breached) {
|
|
136
|
-
console.error(`${red("\u2717")} Threshold breached: ${check.value} > ${check.threshold}`);
|
|
137
|
-
process.exit(6);
|
|
138
124
|
}
|
|
125
|
+
const dims = rowR["dimensions"];
|
|
126
|
+
if (dims && !Array.isArray(dims)) {
|
|
127
|
+
for (const [key, val] of Object.entries(dims)) {
|
|
128
|
+
flat[key] = val?.["stringValue"] ?? "-";
|
|
129
|
+
}
|
|
130
|
+
} else if (Array.isArray(dims)) {
|
|
131
|
+
for (let i = 0; i < dims.length; i++) {
|
|
132
|
+
const val = dims[i];
|
|
133
|
+
const dimName = val?.["dimension"];
|
|
134
|
+
flat[dimName ?? `dim${i}`] = val?.["stringValue"] ?? val?.["int64Value"] ?? "-";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return flat;
|
|
138
|
+
});
|
|
139
|
+
console.log(formatOutput(rows, format));
|
|
140
|
+
} else {
|
|
141
|
+
console.log(formatOutput(result, format));
|
|
142
|
+
}
|
|
143
|
+
const configKey = THRESHOLD_CONFIG_KEYS[name];
|
|
144
|
+
const configThreshold = configKey ? config["vitals"] ? config["vitals"]["thresholds"] ? config["vitals"]["thresholds"][configKey] : void 0 : void 0 : void 0;
|
|
145
|
+
const threshold = options.threshold ?? (configThreshold !== void 0 ? Number(configThreshold) : void 0);
|
|
146
|
+
if (threshold !== void 0) {
|
|
147
|
+
const latestRow = result.rows?.[result.rows.length - 1];
|
|
148
|
+
const metricKeys = latestRow?.metrics ? Object.keys(latestRow.metrics) : [];
|
|
149
|
+
const firstMetric = metricKeys[0];
|
|
150
|
+
const value = firstMetric ? Number(latestRow?.metrics[firstMetric]?.decimalValue?.value) : void 0;
|
|
151
|
+
const check = checkThreshold(value, threshold);
|
|
152
|
+
if (check.breached) {
|
|
153
|
+
console.error(`${red("\u2717")} Threshold breached: ${check.value} > ${check.threshold}`);
|
|
154
|
+
process.exitCode = 6;
|
|
139
155
|
}
|
|
140
|
-
} catch (error) {
|
|
141
|
-
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
142
|
-
process.exit(4);
|
|
143
156
|
}
|
|
144
157
|
});
|
|
145
158
|
}
|
|
@@ -150,37 +163,32 @@ function registerVitalsCommands(program) {
|
|
|
150
163
|
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
151
164
|
const reporting = await getReportingClient(config);
|
|
152
165
|
const format = getOutputFormat(program, config);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
console.log(formatOutput({ vitals: [], message: "No vitals data available" }, format));
|
|
158
|
-
} else {
|
|
159
|
-
console.log("No vitals data available.");
|
|
160
|
-
}
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
if (format !== "json") {
|
|
164
|
-
const overview = result;
|
|
165
|
-
const rows = Object.entries(overview).map(([metric, data]) => {
|
|
166
|
-
const metricRows = data;
|
|
167
|
-
const latest = metricRows?.[metricRows.length - 1];
|
|
168
|
-
const metrics = latest?.["metrics"];
|
|
169
|
-
const firstKey = metrics ? Object.keys(metrics)[0] : void 0;
|
|
170
|
-
const value = firstKey ? metrics?.[firstKey]?.["decimalValue"]?.["value"] : void 0;
|
|
171
|
-
return {
|
|
172
|
-
metric,
|
|
173
|
-
dataPoints: metricRows?.length || 0,
|
|
174
|
-
latestValue: value ?? "-"
|
|
175
|
-
};
|
|
176
|
-
});
|
|
177
|
-
console.log(formatOutput(rows, format));
|
|
166
|
+
const result = await getVitalsOverview(reporting, packageName);
|
|
167
|
+
if (Object.keys(result).length === 0) {
|
|
168
|
+
if (format === "json") {
|
|
169
|
+
console.log(formatOutput({ vitals: [], message: "No vitals data available" }, format));
|
|
178
170
|
} else {
|
|
179
|
-
console.log(
|
|
171
|
+
console.log("No vitals data available.");
|
|
180
172
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (format !== "json") {
|
|
176
|
+
const overview = result;
|
|
177
|
+
const rows = Object.entries(overview).map(([metric, data]) => {
|
|
178
|
+
const metricRows = data;
|
|
179
|
+
const latest = metricRows?.[metricRows.length - 1];
|
|
180
|
+
const metrics = latest?.["metrics"];
|
|
181
|
+
const firstKey = metrics ? Object.keys(metrics)[0] : void 0;
|
|
182
|
+
const value = firstKey ? metrics?.[firstKey]?.["decimalValue"]?.["value"] : void 0;
|
|
183
|
+
return {
|
|
184
|
+
metric,
|
|
185
|
+
dataPoints: metricRows?.length || 0,
|
|
186
|
+
latestValue: value ?? "-"
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
console.log(formatOutput(rows, format));
|
|
190
|
+
} else {
|
|
191
|
+
console.log(formatOutput(result, format));
|
|
184
192
|
}
|
|
185
193
|
});
|
|
186
194
|
registerMetricCommand(vitals, "crashes", "Query crash rate metrics", getVitalsCrashes, program);
|
|
@@ -220,13 +228,22 @@ function registerVitalsCommands(program) {
|
|
|
220
228
|
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
221
229
|
const reporting = await getReportingClient(config);
|
|
222
230
|
const format = getOutputFormat(program, config);
|
|
231
|
+
let result;
|
|
223
232
|
try {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
233
|
+
result = await getVitalsAnomalies(reporting, packageName);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
if (err instanceof PlayApiError && err.statusCode === 403) {
|
|
236
|
+
if (format === "json") {
|
|
237
|
+
console.log(formatOutput({ anomalies: [], message: "Reporting API not enabled or insufficient permissions" }, format));
|
|
238
|
+
} else {
|
|
239
|
+
console.log(`${yellow("\u26A0")} No anomaly data available. The Reporting API may not be enabled for this project.`);
|
|
240
|
+
console.log(` Enable it at: https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com`);
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
throw err;
|
|
229
245
|
}
|
|
246
|
+
console.log(formatOutput(result, format));
|
|
230
247
|
});
|
|
231
248
|
const errors = vitals.command("errors").description("Search and view error issues");
|
|
232
249
|
errors.command("search").description("Search error issues").option("--filter <text>", "Filter expression").option("--max <n>", "Maximum results", parseInt).action(async (options) => {
|
|
@@ -234,21 +251,16 @@ function registerVitalsCommands(program) {
|
|
|
234
251
|
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
235
252
|
const reporting = await getReportingClient(config);
|
|
236
253
|
const format = getOutputFormat(program, config);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
console.log(formatOutput(result, format));
|
|
248
|
-
} catch (error) {
|
|
249
|
-
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
250
|
-
process.exit(4);
|
|
254
|
+
const result = await searchVitalsErrors(reporting, packageName, {
|
|
255
|
+
filter: options.filter,
|
|
256
|
+
maxResults: options.max
|
|
257
|
+
});
|
|
258
|
+
const issues = result["errorIssues"];
|
|
259
|
+
if (format !== "json" && (!issues || issues.length === 0)) {
|
|
260
|
+
console.log("No error issues found.");
|
|
261
|
+
return;
|
|
251
262
|
}
|
|
263
|
+
console.log(formatOutput(result, format));
|
|
252
264
|
});
|
|
253
265
|
const METRIC_MAP = {
|
|
254
266
|
crashes: "crashRateMetricSet",
|
|
@@ -261,70 +273,62 @@ function registerVitalsCommands(program) {
|
|
|
261
273
|
vitals.command("compare <metric>").description("Compare metric trend: this period vs previous period").option("--days <n>", "Period length in days", (v) => parseInt(v, 10), 7).action(async (metric, options) => {
|
|
262
274
|
const metricSet = METRIC_MAP[metric];
|
|
263
275
|
if (!metricSet) {
|
|
264
|
-
|
|
265
|
-
`
|
|
276
|
+
throw new GpcError(
|
|
277
|
+
`Unknown metric "${metric}". Use: ${Object.keys(METRIC_MAP).join(", ")}`,
|
|
278
|
+
"VITALS_USAGE_ERROR",
|
|
279
|
+
2,
|
|
280
|
+
`Valid metrics: ${Object.keys(METRIC_MAP).join(", ")}`
|
|
266
281
|
);
|
|
267
|
-
process.exit(2);
|
|
268
282
|
}
|
|
269
283
|
const config = await loadConfig();
|
|
270
284
|
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
271
285
|
const reporting = await getReportingClient(config);
|
|
272
286
|
const format = getOutputFormat(program, config);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
console.log(formatOutput(result, format));
|
|
276
|
-
} catch (error) {
|
|
277
|
-
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
278
|
-
process.exit(4);
|
|
279
|
-
}
|
|
287
|
+
const result = await compareVitalsTrend(reporting, packageName, metricSet, options.days);
|
|
288
|
+
console.log(formatOutput(result, format));
|
|
280
289
|
});
|
|
281
290
|
vitals.command("compare-versions <v1> <v2>").description("Compare vitals side-by-side for two version codes").option("--days <n>", "Number of days to query", (v) => parseInt(v, 10), 30).action(async (v1, v2, options) => {
|
|
282
291
|
const config = await loadConfig();
|
|
283
292
|
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
284
293
|
const reporting = await getReportingClient(config);
|
|
285
294
|
const format = getOutputFormat(program, config);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
console.log(`
|
|
295
|
+
const result = await compareVersionVitals(reporting, packageName, v1, v2, {
|
|
296
|
+
days: options.days
|
|
297
|
+
});
|
|
298
|
+
if (format === "json") {
|
|
299
|
+
console.log(formatOutput(result, format));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const metrics = [
|
|
303
|
+
["crashRate", "Crash Rate"],
|
|
304
|
+
["anrRate", "ANR Rate"],
|
|
305
|
+
["slowStartRate", "Slow Start Rate"],
|
|
306
|
+
["slowRenderingRate", "Slow Rendering Rate"]
|
|
307
|
+
];
|
|
308
|
+
console.log(`
|
|
301
309
|
Version Comparison \u2014 ${packageName}`);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
310
|
+
console.log(`${"\u2500".repeat(60)}`);
|
|
311
|
+
console.log(`${"Metric".padEnd(22)} ${"v" + v1.padEnd(14)} ${"v" + v2.padEnd(14)} Change`);
|
|
312
|
+
console.log(`${"\u2500".repeat(60)}`);
|
|
313
|
+
for (const [key, label] of metrics) {
|
|
314
|
+
const val1 = result.v1[key];
|
|
315
|
+
const val2 = result.v2[key];
|
|
316
|
+
const s1 = val1 !== void 0 ? (val1 * 100).toFixed(3) + "%" : "N/A";
|
|
317
|
+
const s2 = val2 !== void 0 ? (val2 * 100).toFixed(3) + "%" : "N/A";
|
|
318
|
+
const isRegression = result.regressions.includes(key);
|
|
319
|
+
const change = val1 !== void 0 && val2 !== void 0 ? (val2 - val1) / val1 * 100 : void 0;
|
|
320
|
+
const changeStr = change !== void 0 ? (change > 0 ? "+" : "") + change.toFixed(1) + "%" : "N/A";
|
|
321
|
+
const colorFn = isRegression ? red : change !== void 0 && change < -1 ? green : (s) => s;
|
|
322
|
+
console.log(
|
|
323
|
+
`${label.padEnd(22)} ${s1.padEnd(15)} ${colorFn(s2.padEnd(15))} ${colorFn(changeStr)}`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
if (result.regressions.length > 0) {
|
|
327
|
+
console.log(`
|
|
320
328
|
${red("\u2717")} Regressions detected: ${result.regressions.join(", ")}`);
|
|
321
|
-
|
|
322
|
-
|
|
329
|
+
} else {
|
|
330
|
+
console.log(`
|
|
323
331
|
${green("\u2713")} No regressions detected.`);
|
|
324
|
-
}
|
|
325
|
-
} catch (error) {
|
|
326
|
-
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
327
|
-
process.exit(4);
|
|
328
332
|
}
|
|
329
333
|
});
|
|
330
334
|
vitals.command("watch").description("Monitor vitals continuously and optionally auto-halt rollout on breach").requiredOption("--threshold <value>", "Breach threshold value", parseFloat).option(
|
|
@@ -334,14 +338,20 @@ ${green("\u2713")} No regressions detected.`);
|
|
|
334
338
|
).option("--interval <seconds>", "Polling interval in seconds", (v) => parseInt(v, 10), 300).option("--auto-halt-rollout", "Automatically halt rollout if threshold is breached").option("--track <name>", "Track to halt rollout on (required with --auto-halt-rollout)").action(async (options) => {
|
|
335
339
|
const metricSet = METRIC_MAP[options.metric];
|
|
336
340
|
if (!metricSet) {
|
|
337
|
-
|
|
338
|
-
`
|
|
341
|
+
throw new GpcError(
|
|
342
|
+
`Unknown metric "${options.metric}". Use: ${Object.keys(METRIC_MAP).join(", ")}`,
|
|
343
|
+
"VITALS_USAGE_ERROR",
|
|
344
|
+
2,
|
|
345
|
+
`Valid metrics: ${Object.keys(METRIC_MAP).join(", ")}`
|
|
339
346
|
);
|
|
340
|
-
process.exit(2);
|
|
341
347
|
}
|
|
342
348
|
if (options.autoHaltRollout && !options.track) {
|
|
343
|
-
|
|
344
|
-
|
|
349
|
+
throw new GpcError(
|
|
350
|
+
"--track <name> is required when using --auto-halt-rollout",
|
|
351
|
+
"VITALS_USAGE_ERROR",
|
|
352
|
+
2,
|
|
353
|
+
"Add --track <name> to specify which track to halt on breach."
|
|
354
|
+
);
|
|
345
355
|
}
|
|
346
356
|
const config = await loadConfig();
|
|
347
357
|
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
@@ -382,17 +392,16 @@ ${red("\u2717")} Threshold breached (${(value * 100).toFixed(3)}% > ${options.th
|
|
|
382
392
|
);
|
|
383
393
|
}
|
|
384
394
|
stop();
|
|
385
|
-
process.
|
|
395
|
+
process.exitCode = 6;
|
|
386
396
|
} : void 0
|
|
387
397
|
});
|
|
388
398
|
process.on("SIGINT", () => {
|
|
389
399
|
stop();
|
|
390
400
|
console.log("\nWatch stopped.");
|
|
391
|
-
process.exit(0);
|
|
392
401
|
});
|
|
393
402
|
});
|
|
394
403
|
}
|
|
395
404
|
export {
|
|
396
405
|
registerVitalsCommands
|
|
397
406
|
};
|
|
398
|
-
//# sourceMappingURL=vitals-
|
|
407
|
+
//# sourceMappingURL=vitals-PJEQUUAK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/vitals.ts"],"sourcesContent":["import { resolvePackageName } from \"../resolve.js\";\nimport type { Command } from \"commander\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createReportingClient, PlayApiError } from \"@gpc-cli/api\";\nimport type { ReportingDimension } from \"@gpc-cli/api\";\nimport type { VitalsMetricSet } from \"@gpc-cli/api\";\nimport {\n getVitalsOverview,\n getVitalsCrashes,\n getVitalsAnr,\n getVitalsStartup,\n getVitalsRendering,\n getVitalsBattery,\n getVitalsMemory,\n getVitalsLmk,\n getVitalsAnomalies,\n searchVitalsErrors,\n compareVitalsTrend,\n compareVersionVitals,\n watchVitalsWithAutoHalt,\n checkThreshold,\n formatOutput,\n GpcError,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { red, yellow, green } from \"../colors.js\";\n\n\nasync function getReportingClient(config: GpcConfig) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createReportingClient({ auth });\n}\n\nconst VALID_DIMENSIONS: ReportingDimension[] = [\n \"apiLevel\",\n \"versionCode\",\n \"deviceModel\",\n \"deviceType\",\n \"countryCode\",\n \"deviceRamBucket\",\n \"deviceSocName\",\n \"deviceCpuMakeModel\",\n \"deviceGlEsVersion\",\n \"deviceVulkanVersion\",\n \"deviceOpenGlVersion\",\n \"deviceBrand\",\n \"startType\", // Required dimension for slowStartRateMetricSet\n];\n\nconst THRESHOLD_CONFIG_KEYS: Record<string, string> = {\n crashes: \"crashRate\",\n anr: \"anrRate\",\n startup: \"slowStartRate\",\n rendering: \"slowRenderingRate\",\n battery: \"excessiveWakeupRate\",\n memory: \"stuckWakelockRate\",\n wakeup: \"excessiveWakeupRate\",\n lmk: \"stuckWakelockRate\",\n};\n\nfunction validateDimension(dim: string): ReportingDimension {\n if (!(VALID_DIMENSIONS as readonly string[]).includes(dim)) {\n throw new GpcError(\n `Invalid dimension \"${dim}\". Valid dimensions: ${VALID_DIMENSIONS.join(\", \")}`,\n \"VITALS_USAGE_ERROR\",\n 2,\n `Use one of: ${VALID_DIMENSIONS.join(\", \")}`,\n );\n }\n return dim as ReportingDimension;\n}\n\ntype MetricFn = typeof getVitalsCrashes;\n\nfunction registerMetricCommand(\n parent: Command,\n name: string,\n description: string,\n fn: MetricFn,\n program: Command,\n): void {\n parent\n .command(name)\n .description(description)\n .option(\"--dim <dimension>\", \"Group by dimension\")\n .option(\"--days <n>\", \"Number of days to query\", parseInt)\n .option(\"--threshold <value>\", \"Threshold value for CI alerting\", parseFloat)\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n let result;\n try {\n result = await fn(reporting, packageName, {\n dimension: options.dim ? validateDimension(options.dim) : undefined,\n days: options.days,\n });\n } catch (err) {\n if (err instanceof PlayApiError && err.statusCode === 403) {\n if (format === \"json\") {\n console.log(formatOutput({ rows: [], message: \"Reporting API not enabled or insufficient permissions\" }, format));\n } else {\n console.log(`${yellow(\"⚠\")} No vitals data available. The Reporting API may not be enabled for this project.`);\n console.log(` Enable it at: https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com`);\n }\n return;\n }\n throw err;\n }\n if (format !== \"json\" && (!result.rows || result.rows.length === 0)) {\n console.log(`${yellow(\"⚠\")} No vitals data available.`);\n return;\n }\n if (format !== \"json\" && result.rows) {\n const rows = result.rows.map((row: unknown) => {\n const rowR = row as Record<string, unknown>;\n const startTime = rowR[\"startTime\"] as Record<string, unknown> | undefined;\n const metrics = rowR[\"metrics\"] as\n | Record<string, Record<string, unknown>>\n | unknown[]\n | undefined;\n const flat: Record<string, unknown> = {\n date: startTime\n ? `${startTime[\"year\"]}-${String(startTime[\"month\"]).padStart(2, \"0\")}-${String(startTime[\"day\"]).padStart(2, \"0\")}`\n : \"-\",\n };\n if (metrics && !Array.isArray(metrics)) {\n for (const [key, val] of Object.entries(metrics)) {\n flat[key] =\n (val as Record<string, unknown>)?.[\"decimalValue\"] !== undefined\n ? ((val as Record<string, Record<string, unknown>>)?.[\"decimalValue\"]?.[\n \"value\"\n ] ?? \"-\")\n : \"-\";\n }\n } else if (Array.isArray(metrics)) {\n // Fallback: if API returns metrics as array, use metric names from config\n for (let i = 0; i < metrics.length; i++) {\n const val = metrics[i] as Record<string, unknown> | undefined;\n const metricName = val?.[\"metric\"] as string | undefined;\n const key = metricName ?? `metric${i}`;\n flat[key] =\n (val as Record<string, unknown>)?.[\"decimalValue\"] !== undefined\n ? ((val as Record<string, Record<string, unknown>>)?.[\"decimalValue\"]?.[\n \"value\"\n ] ?? \"-\")\n : \"-\";\n }\n }\n const dims = rowR[\"dimensions\"] as Record<string, unknown> | unknown[] | undefined;\n if (dims && !Array.isArray(dims)) {\n for (const [key, val] of Object.entries(dims)) {\n flat[key] = (val as Record<string, unknown>)?.[\"stringValue\"] ?? \"-\";\n }\n } else if (Array.isArray(dims)) {\n // Fallback: if API returns dimensions as array\n for (let i = 0; i < dims.length; i++) {\n const val = dims[i] as Record<string, unknown> | undefined;\n const dimName = val?.[\"dimension\"] as string | undefined;\n flat[dimName ?? `dim${i}`] = val?.[\"stringValue\"] ?? val?.[\"int64Value\"] ?? \"-\";\n }\n }\n return flat;\n });\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(result, format));\n }\n\n // Check threshold from flag or config\n const configKey = THRESHOLD_CONFIG_KEYS[name];\n const configThreshold = configKey\n ? (config as unknown as Record<string, unknown>)[\"vitals\"]\n ? ((config as unknown as Record<string, unknown>)[\"vitals\"] as Record<string, unknown>)[\n \"thresholds\"\n ]\n ? (\n (\n (config as unknown as Record<string, unknown>)[\"vitals\"] as Record<\n string,\n unknown\n >\n )[\"thresholds\"] as Record<string, unknown>\n )[configKey]\n : undefined\n : undefined\n : undefined;\n const threshold =\n options.threshold ??\n (configThreshold !== undefined ? Number(configThreshold) : undefined);\n if (threshold !== undefined) {\n const latestRow = result.rows?.[result.rows.length - 1];\n const metricKeys = latestRow?.metrics ? Object.keys(latestRow.metrics) : [];\n const firstMetric = metricKeys[0];\n const value = firstMetric\n ? Number(latestRow?.metrics[firstMetric]?.decimalValue?.value)\n : undefined;\n const check = checkThreshold(value, threshold);\n if (check.breached) {\n console.error(`${red(\"✗\")} Threshold breached: ${check.value} > ${check.threshold}`);\n process.exitCode = 6;\n }\n }\n });\n}\n\nexport function registerVitalsCommands(program: Command): void {\n const vitals = program\n .command(\"vitals\")\n .description(\"Monitor app vitals, crash rates, and performance metrics\");\n\n vitals\n .command(\"overview\")\n .description(\"Dashboard summary of all vital metrics\")\n .action(async () => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await getVitalsOverview(reporting, packageName);\n if (Object.keys(result).length === 0) {\n if (format === \"json\") {\n console.log(formatOutput({ vitals: [], message: \"No vitals data available\" }, format));\n } else {\n console.log(\"No vitals data available.\");\n }\n return;\n }\n if (format !== \"json\") {\n const overview = result as Record<string, unknown>;\n const rows = Object.entries(overview).map(([metric, data]) => {\n const metricRows = data as Record<string, unknown>[] | undefined;\n const latest = metricRows?.[metricRows.length - 1];\n const metrics = latest?.[\"metrics\"] as\n | Record<string, Record<string, unknown>>\n | undefined;\n const firstKey = metrics ? Object.keys(metrics)[0] : undefined;\n const value = firstKey\n ? (metrics?.[firstKey]?.[\"decimalValue\"] as Record<string, unknown> | undefined)?.[\n \"value\"\n ]\n : undefined;\n return {\n metric,\n dataPoints: metricRows?.length || 0,\n latestValue: value ?? \"-\",\n };\n });\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(result, format));\n }\n });\n\n registerMetricCommand(vitals, \"crashes\", \"Query crash rate metrics\", getVitalsCrashes, program);\n registerMetricCommand(vitals, \"anr\", \"Query ANR rate metrics\", getVitalsAnr, program);\n registerMetricCommand(vitals, \"startup\", \"Query slow startup metrics\", getVitalsStartup, program);\n registerMetricCommand(\n vitals,\n \"rendering\",\n \"Query slow rendering metrics\",\n getVitalsRendering,\n program,\n );\n registerMetricCommand(\n vitals,\n \"battery\",\n \"Query excessive wakeup metrics\",\n getVitalsBattery,\n program,\n );\n registerMetricCommand(vitals, \"memory\", \"Query stuck wakelock metrics\", getVitalsMemory, program);\n registerMetricCommand(\n vitals,\n \"wakeup\",\n \"Query excessive wakeup rate metrics\",\n getVitalsBattery,\n program,\n );\n registerMetricCommand(\n vitals,\n \"lmk\",\n \"Query low-memory kill (stuck wakelock) metrics\",\n getVitalsLmk,\n program,\n );\n\n vitals\n .command(\"anomalies\")\n .description(\"Detect anomalies in app vitals\")\n .action(async () => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n let result;\n try {\n result = await getVitalsAnomalies(reporting, packageName);\n } catch (err) {\n if (err instanceof PlayApiError && err.statusCode === 403) {\n if (format === \"json\") {\n console.log(formatOutput({ anomalies: [], message: \"Reporting API not enabled or insufficient permissions\" }, format));\n } else {\n console.log(`${yellow(\"⚠\")} No anomaly data available. The Reporting API may not be enabled for this project.`);\n console.log(` Enable it at: https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com`);\n }\n return;\n }\n throw err;\n }\n console.log(formatOutput(result, format));\n });\n\n const errors = vitals.command(\"errors\").description(\"Search and view error issues\");\n\n errors\n .command(\"search\")\n .description(\"Search error issues\")\n .option(\"--filter <text>\", \"Filter expression\")\n .option(\"--max <n>\", \"Maximum results\", parseInt)\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await searchVitalsErrors(reporting, packageName, {\n filter: options.filter,\n maxResults: options.max,\n });\n const issues = (result as unknown as Record<string, unknown>)[\"errorIssues\"] as\n | unknown[]\n | undefined;\n if (format !== \"json\" && (!issues || issues.length === 0)) {\n console.log(\"No error issues found.\");\n return;\n }\n console.log(formatOutput(result, format));\n });\n\n const METRIC_MAP: Record<string, VitalsMetricSet> = {\n crashes: \"crashRateMetricSet\",\n anr: \"anrRateMetricSet\",\n startup: \"slowStartRateMetricSet\",\n rendering: \"slowRenderingRateMetricSet\",\n battery: \"excessiveWakeupRateMetricSet\",\n memory: \"stuckBackgroundWakelockRateMetricSet\",\n };\n\n vitals\n .command(\"compare <metric>\")\n .description(\"Compare metric trend: this period vs previous period\")\n .option(\"--days <n>\", \"Period length in days\", (v) => parseInt(v, 10), 7)\n .action(async (metric: string, options) => {\n const metricSet = METRIC_MAP[metric];\n if (!metricSet) {\n throw new GpcError(\n `Unknown metric \"${metric}\". Use: ${Object.keys(METRIC_MAP).join(\", \")}`,\n \"VITALS_USAGE_ERROR\",\n 2,\n `Valid metrics: ${Object.keys(METRIC_MAP).join(\", \")}`,\n );\n }\n\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await compareVitalsTrend(reporting, packageName, metricSet, options.days);\n console.log(formatOutput(result, format));\n });\n\n vitals\n .command(\"compare-versions <v1> <v2>\")\n .description(\"Compare vitals side-by-side for two version codes\")\n .option(\"--days <n>\", \"Number of days to query\", (v) => parseInt(v, 10), 30)\n .action(async (v1: string, v2: string, options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await compareVersionVitals(reporting, packageName, v1, v2, {\n days: options.days,\n });\n\n if (format === \"json\") {\n console.log(formatOutput(result, format));\n return;\n }\n\n const metrics: [keyof typeof result.v1, string][] = [\n [\"crashRate\", \"Crash Rate\"],\n [\"anrRate\", \"ANR Rate\"],\n [\"slowStartRate\", \"Slow Start Rate\"],\n [\"slowRenderingRate\", \"Slow Rendering Rate\"],\n ];\n\n console.log(`\\nVersion Comparison — ${packageName}`);\n console.log(`${\"─\".repeat(60)}`);\n console.log(`${\"Metric\".padEnd(22)} ${\"v\" + v1.padEnd(14)} ${\"v\" + v2.padEnd(14)} Change`);\n console.log(`${\"─\".repeat(60)}`);\n\n for (const [key, label] of metrics) {\n const val1 = result.v1[key] as number | undefined;\n const val2 = result.v2[key] as number | undefined;\n const s1 = val1 !== undefined ? (val1 * 100).toFixed(3) + \"%\" : \"N/A\";\n const s2 = val2 !== undefined ? (val2 * 100).toFixed(3) + \"%\" : \"N/A\";\n const isRegression = result.regressions.includes(key as string);\n const change =\n val1 !== undefined && val2 !== undefined ? ((val2 - val1) / val1) * 100 : undefined;\n const changeStr =\n change !== undefined ? (change > 0 ? \"+\" : \"\") + change.toFixed(1) + \"%\" : \"N/A\";\n const colorFn = isRegression\n ? red\n : change !== undefined && change < -1\n ? green\n : (s: string) => s;\n console.log(\n `${label.padEnd(22)} ${s1.padEnd(15)} ${colorFn(s2.padEnd(15))} ${colorFn(changeStr)}`,\n );\n }\n\n if (result.regressions.length > 0) {\n console.log(`\\n${red(\"✗\")} Regressions detected: ${result.regressions.join(\", \")}`);\n } else {\n console.log(`\\n${green(\"✓\")} No regressions detected.`);\n }\n });\n\n vitals\n .command(\"watch\")\n .description(\"Monitor vitals continuously and optionally auto-halt rollout on breach\")\n .requiredOption(\"--threshold <value>\", \"Breach threshold value\", parseFloat)\n .option(\n \"--metric <name>\",\n \"Metric to monitor (crashes, anr, startup, rendering, battery, memory)\",\n \"crashes\",\n )\n .option(\"--interval <seconds>\", \"Polling interval in seconds\", (v) => parseInt(v, 10), 300)\n .option(\"--auto-halt-rollout\", \"Automatically halt rollout if threshold is breached\")\n .option(\"--track <name>\", \"Track to halt rollout on (required with --auto-halt-rollout)\")\n .action(async (options) => {\n const metricSet = METRIC_MAP[options.metric as string];\n if (!metricSet) {\n throw new GpcError(\n `Unknown metric \"${options.metric}\". Use: ${Object.keys(METRIC_MAP).join(\", \")}`,\n \"VITALS_USAGE_ERROR\",\n 2,\n `Valid metrics: ${Object.keys(METRIC_MAP).join(\", \")}`,\n );\n }\n\n if (options.autoHaltRollout && !options.track) {\n throw new GpcError(\n \"--track <name> is required when using --auto-halt-rollout\",\n \"VITALS_USAGE_ERROR\",\n 2,\n \"Add --track <name> to specify which track to halt on breach.\",\n );\n }\n\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const intervalMs = (options.interval as number) * 1000;\n\n console.log(`Watching ${options.metric} for ${packageName}`);\n console.log(`Threshold: ${options.threshold} Interval: ${options.interval}s`);\n if (options.autoHaltRollout) {\n console.log(`Auto-halt enabled on track: ${options.track}`);\n }\n console.log(\"Press Ctrl+C to stop.\\n\");\n\n const stop = watchVitalsWithAutoHalt(reporting, packageName, {\n intervalMs,\n threshold: options.threshold as number,\n metricSet,\n onPoll: (value, breached) => {\n const ts = new Date().toISOString();\n const valStr = value !== undefined ? (value * 100).toFixed(3) + \"%\" : \"N/A\";\n const indicator = breached ? red(\"✗ BREACH\") : green(\"✓ OK\");\n console.log(`[${ts}] ${options.metric}: ${valStr} — ${indicator}`);\n },\n onHalt: options.autoHaltRollout\n ? async (value: number) => {\n console.error(\n `\\n${red(\"✗\")} Threshold breached (${(value * 100).toFixed(3)}% > ${options.threshold}%). Halting rollout on track \"${options.track}\"...`,\n );\n try {\n const { resolveAuth } = await import(\"@gpc-cli/auth\");\n const { createApiClient } = await import(\"@gpc-cli/api\");\n const { updateRollout } = await import(\"@gpc-cli/core\");\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n const apiClient = createApiClient({ auth });\n await updateRollout(apiClient, packageName, options.track as string, \"halt\");\n console.error(`${red(\"⚠\")} Rollout halted on track \"${options.track}\".`);\n } catch (err) {\n console.error(\n `Failed to halt rollout: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n stop();\n process.exitCode = 6;\n }\n : undefined,\n });\n\n process.on(\"SIGINT\", () => {\n stop();\n console.log(\"\\nWatch stopped.\");\n });\n });\n}\n"],"mappings":"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","names":["resolveAuth"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gpc-cli/cli",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "GPC — Google Play Console CLI.
|
|
3
|
+
"version": "0.9.47",
|
|
4
|
+
"description": "GPC — Google Play Console CLI. 204 API endpoints, one tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"gpc": "./dist/bin.js"
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"commander": "^14.0.3",
|
|
22
|
-
"@gpc-cli/api": "1.0.
|
|
23
|
-
"@gpc-cli/auth": "0.9.
|
|
24
|
-
"@gpc-cli/config": "0.9.
|
|
25
|
-
"@gpc-cli/core": "0.9.
|
|
22
|
+
"@gpc-cli/api": "1.0.26",
|
|
23
|
+
"@gpc-cli/auth": "0.9.11",
|
|
24
|
+
"@gpc-cli/config": "0.9.11",
|
|
25
|
+
"@gpc-cli/core": "0.9.40",
|
|
26
26
|
"@gpc-cli/plugin-sdk": "0.9.7"
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
resolvePackageName
|
|
4
|
-
} from "./chunk-AA577WVQ.js";
|
|
5
|
-
import {
|
|
6
|
-
getOutputFormat
|
|
7
|
-
} from "./chunk-ELXAK7GI.js";
|
|
8
|
-
|
|
9
|
-
// src/commands/anomalies.ts
|
|
10
|
-
import { loadConfig } from "@gpc-cli/config";
|
|
11
|
-
import { resolveAuth } from "@gpc-cli/auth";
|
|
12
|
-
import { createReportingClient } from "@gpc-cli/api";
|
|
13
|
-
import { getVitalsAnomalies, formatOutput } from "@gpc-cli/core";
|
|
14
|
-
async function getReportingClient(config) {
|
|
15
|
-
const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });
|
|
16
|
-
return createReportingClient({ auth });
|
|
17
|
-
}
|
|
18
|
-
function registerAnomaliesCommands(program) {
|
|
19
|
-
const anomalies = program.command("anomalies").description("Detect and list vitals anomalies");
|
|
20
|
-
anomalies.command("list").description("List detected vitals anomalies").action(async () => {
|
|
21
|
-
const config = await loadConfig();
|
|
22
|
-
const packageName = resolvePackageName(program.opts()["app"], config);
|
|
23
|
-
const reporting = await getReportingClient(config);
|
|
24
|
-
const format = getOutputFormat(program, config);
|
|
25
|
-
try {
|
|
26
|
-
const result = await getVitalsAnomalies(reporting, packageName);
|
|
27
|
-
const items = result["anomalies"];
|
|
28
|
-
if (format !== "json") {
|
|
29
|
-
if (!items || items.length === 0) {
|
|
30
|
-
console.log("No anomalies detected.");
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const rows = items.map((item) => {
|
|
34
|
-
const a = item;
|
|
35
|
-
return {
|
|
36
|
-
name: String(a["name"] ?? "-"),
|
|
37
|
-
metricSet: String(a["metricSet"] ?? "-"),
|
|
38
|
-
aggregationPeriod: String(a["aggregationPeriod"] ?? "-")
|
|
39
|
-
};
|
|
40
|
-
});
|
|
41
|
-
console.log(formatOutput(rows, format));
|
|
42
|
-
} else {
|
|
43
|
-
console.log(formatOutput(result, format));
|
|
44
|
-
}
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
47
|
-
process.exit(4);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
export {
|
|
52
|
-
registerAnomaliesCommands
|
|
53
|
-
};
|
|
54
|
-
//# sourceMappingURL=anomalies-NU2IN2GJ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/anomalies.ts"],"sourcesContent":["import { resolvePackageName } from \"../resolve.js\";\nimport type { Command } from \"commander\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createReportingClient } from \"@gpc-cli/api\";\nimport { getVitalsAnomalies, formatOutput } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\n\nasync function getReportingClient(config: GpcConfig) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createReportingClient({ auth });\n}\n\nexport function registerAnomaliesCommands(program: Command): void {\n const anomalies = program.command(\"anomalies\").description(\"Detect and list vitals anomalies\");\n\n anomalies\n .command(\"list\")\n .description(\"List detected vitals anomalies\")\n .action(async () => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await getVitalsAnomalies(reporting, packageName);\n const items = (result as unknown as Record<string, unknown>)[\"anomalies\"] as\n | unknown[]\n | undefined;\n\n if (format !== \"json\") {\n if (!items || items.length === 0) {\n console.log(\"No anomalies detected.\");\n return;\n }\n const rows = items.map((item) => {\n const a = item as Record<string, unknown>;\n return {\n name: String(a[\"name\"] ?? \"-\"),\n metricSet: String(a[\"metricSet\"] ?? \"-\"),\n aggregationPeriod: String(a[\"aggregationPeriod\"] ?? \"-\"),\n };\n });\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(result, format));\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n}\n"],"mappings":";;;;;;;;;AAGA,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,6BAA6B;AACtC,SAAS,oBAAoB,oBAAoB;AAIjD,eAAe,mBAAmB,QAAmB;AACnD,QAAM,OAAO,MAAM,YAAY,EAAE,oBAAoB,OAAO,MAAM,eAAe,CAAC;AAClF,SAAO,sBAAsB,EAAE,KAAK,CAAC;AACvC;AAEO,SAAS,0BAA0B,SAAwB;AAChE,QAAM,YAAY,QAAQ,QAAQ,WAAW,EAAE,YAAY,kCAAkC;AAE7F,YACG,QAAQ,MAAM,EACd,YAAY,gCAAgC,EAC5C,OAAO,YAAY;AAClB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,YAAY,MAAM,mBAAmB,MAAM;AACjD,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,QAAI;AACF,YAAM,SAAS,MAAM,mBAAmB,WAAW,WAAW;AAC9D,YAAM,QAAS,OAA8C,WAAW;AAIxE,UAAI,WAAW,QAAQ;AACrB,YAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,kBAAQ,IAAI,wBAAwB;AACpC;AAAA,QACF;AACA,cAAM,OAAO,MAAM,IAAI,CAAC,SAAS;AAC/B,gBAAM,IAAI;AACV,iBAAO;AAAA,YACL,MAAM,OAAO,EAAE,MAAM,KAAK,GAAG;AAAA,YAC7B,WAAW,OAAO,EAAE,WAAW,KAAK,GAAG;AAAA,YACvC,mBAAmB,OAAO,EAAE,mBAAmB,KAAK,GAAG;AAAA,UACzD;AAAA,QACF,CAAC;AACD,gBAAQ,IAAI,aAAa,MAAM,MAAM,CAAC;AAAA,MACxC,OAAO;AACL,gBAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,MAC1C;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":[]}
|