@gpc-cli/cli 0.9.45 → 0.9.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{anomalies-NU2IN2GJ.js → anomalies-UDE4NGHJ.js} +19 -24
- package/dist/anomalies-UDE4NGHJ.js.map +1 -0
- package/dist/{apps-J2446UDA.js → apps-FKD3ZG5X.js} +31 -35
- package/dist/apps-FKD3ZG5X.js.map +1 -0
- package/dist/{audit-N2CRHWUN.js → audit-JASSHRWN.js} +47 -62
- package/dist/audit-JASSHRWN.js.map +1 -0
- package/dist/{auth-XGSTT5G5.js → auth-OTA3SV3J.js} +145 -103
- package/dist/auth-OTA3SV3J.js.map +1 -0
- package/dist/bin.js +5 -3
- package/dist/bin.js.map +1 -1
- package/dist/bundle-F7MUVC5J.js +204 -0
- package/dist/bundle-F7MUVC5J.js.map +1 -0
- package/dist/{cache-SLNFRTI2.js → cache-XKPLZYEB.js} +4 -5
- package/dist/cache-XKPLZYEB.js.map +1 -0
- package/dist/changelog-7COFZO7Q.js +48 -0
- package/dist/changelog-7COFZO7Q.js.map +1 -0
- package/dist/{chunk-4O4D5SGL.js → chunk-3SJ6OXCZ.js} +4 -5
- package/dist/chunk-3SJ6OXCZ.js.map +1 -0
- package/dist/{chunk-SEVX56VN.js → chunk-6OWN6S6X.js} +52 -48
- package/dist/{chunk-SEVX56VN.js.map → chunk-6OWN6S6X.js.map} +1 -1
- package/dist/{chunk-U6ZTQ34I.js → chunk-BCBXQC7J.js} +45 -11
- package/dist/chunk-BCBXQC7J.js.map +1 -0
- package/dist/{chunk-AA577WVQ.js → chunk-NQH4G7BI.js} +9 -3
- package/dist/chunk-NQH4G7BI.js.map +1 -0
- package/dist/chunk-SLNJEAMK.js +23 -0
- package/dist/chunk-SLNJEAMK.js.map +1 -0
- package/dist/{chunk-NV75I5VP.js → chunk-YFUBD2XB.js} +10 -8
- package/dist/chunk-YFUBD2XB.js.map +1 -0
- package/dist/{config-BUXPDN7N.js → config-2FTCYEGD.js} +8 -5
- package/dist/config-2FTCYEGD.js.map +1 -0
- package/dist/{data-safety-Q7FTCEWU.js → data-safety-AFMD6MYI.js} +12 -27
- package/dist/data-safety-AFMD6MYI.js.map +1 -0
- package/dist/{device-tiers-MIOQEXYY.js → device-tiers-AQAMUQXI.js} +23 -38
- package/dist/device-tiers-AQAMUQXI.js.map +1 -0
- package/dist/diff-6EO4ID6W.js +91 -0
- package/dist/diff-6EO4ID6W.js.map +1 -0
- package/dist/{docs-7DUXIKA3.js → docs-4D2SJ4LY.js} +4 -3
- package/dist/docs-4D2SJ4LY.js.map +1 -0
- package/dist/doctor-H4X7Q57B.js +691 -0
- package/dist/doctor-H4X7Q57B.js.map +1 -0
- package/dist/{enterprise-7THXNBTC.js → enterprise-7PWXMSUN.js} +11 -21
- package/dist/enterprise-7PWXMSUN.js.map +1 -0
- package/dist/{external-transactions-2GWIMUVM.js → external-transactions-LCZALS3V.js} +12 -28
- package/dist/external-transactions-LCZALS3V.js.map +1 -0
- package/dist/{feedback-DPTO6DUT.js → feedback-XP765TOO.js} +3 -3
- package/dist/{games-BT777WUO.js → games-ZSNGEI7A.js} +17 -32
- package/dist/games-ZSNGEI7A.js.map +1 -0
- package/dist/{generated-apks-RJWTIX7L.js → generated-apks-RX2IUWSF.js} +30 -38
- package/dist/generated-apks-RX2IUWSF.js.map +1 -0
- package/dist/{grants-TKQJ3IER.js → grants-EBPECI26.js} +22 -40
- package/dist/grants-EBPECI26.js.map +1 -0
- package/dist/{iap-ICAEQLK5.js → iap-OUI5YYN4.js} +30 -51
- package/dist/iap-OUI5YYN4.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/{init-JZ2THPMS.js → init-WSTQTJOD.js} +5 -4
- package/dist/init-WSTQTJOD.js.map +1 -0
- package/dist/{install-skills-OV4HVANW.js → install-skills-6QDUXI5F.js} +5 -6
- package/dist/{install-skills-OV4HVANW.js.map → install-skills-6QDUXI5F.js.map} +1 -1
- package/dist/{internal-sharing-3U2XFHA4.js → internal-sharing-ONNIWIAT.js} +3 -4
- package/dist/{internal-sharing-3U2XFHA4.js.map → internal-sharing-ONNIWIAT.js.map} +1 -1
- package/dist/{listings-77HZW4S5.js → listings-7SGQ4SRX.js} +118 -157
- package/dist/listings-7SGQ4SRX.js.map +1 -0
- package/dist/migrate-ZQCJGQQS.js +138 -0
- package/dist/migrate-ZQCJGQQS.js.map +1 -0
- package/dist/{one-time-products-LHZAXQES.js → one-time-products-MGZTU7OM.js} +65 -120
- package/dist/one-time-products-MGZTU7OM.js.map +1 -0
- package/dist/{preflight-H3HEBYQW.js → preflight-N7ZRG2JI.js} +58 -55
- package/dist/preflight-N7ZRG2JI.js.map +1 -0
- package/dist/{pricing-XQSDTTK5.js → pricing-JJZFICFL.js} +8 -8
- package/dist/{pricing-XQSDTTK5.js.map → pricing-JJZFICFL.js.map} +1 -1
- package/dist/{prompt-BSV22CQZ.js → prompt-GXC2JSLA.js} +2 -2
- package/dist/{publish-Q5ZKEKZ5.js → publish-JPTI4EBT.js} +34 -30
- package/dist/publish-JPTI4EBT.js.map +1 -0
- package/dist/{purchase-options-CKRN4VIW.js → purchase-options-KFWW4JW2.js} +16 -11
- package/dist/purchase-options-KFWW4JW2.js.map +1 -0
- package/dist/{purchases-43AKV6HG.js → purchases-DAWTMXP6.js} +118 -193
- package/dist/purchases-DAWTMXP6.js.map +1 -0
- package/dist/{quickstart-4HB62YEL.js → quickstart-Z5Y3FYJU.js} +5 -3
- package/dist/quickstart-Z5Y3FYJU.js.map +1 -0
- package/dist/{quota-UHIQQYOY.js → quota-MZRWYJGR.js} +5 -15
- package/dist/quota-MZRWYJGR.js.map +1 -0
- package/dist/{recovery-5EV2R476.js → recovery-YE3Z7NIN.js} +32 -61
- package/dist/recovery-YE3Z7NIN.js.map +1 -0
- package/dist/{releases-C2WC2K4E.js → releases-2I3WBULC.js} +184 -185
- package/dist/releases-2I3WBULC.js.map +1 -0
- package/dist/{reports-2YX3RDOS.js → reports-CIB2T3XT.js} +19 -21
- package/dist/reports-CIB2T3XT.js.map +1 -0
- package/dist/reviews-BCCXIQ6C.js +188 -0
- package/dist/reviews-BCCXIQ6C.js.map +1 -0
- package/dist/{status-WHGLODGV.js → status-6LH5W4FU.js} +105 -83
- package/dist/status-6LH5W4FU.js.map +1 -0
- package/dist/{subscriptions-CI3JH3VQ.js → subscriptions-DZP3Y7O7.js} +142 -232
- package/dist/subscriptions-DZP3Y7O7.js.map +1 -0
- package/dist/{testers-NZOFA3EF.js → testers-LSMBXCA2.js} +24 -44
- package/dist/testers-LSMBXCA2.js.map +1 -0
- package/dist/tracks-YHMO2A6B.js +98 -0
- package/dist/tracks-YHMO2A6B.js.map +1 -0
- package/dist/{train-CJJVLY4B.js → train-MDD2EBHS.js} +35 -55
- package/dist/train-MDD2EBHS.js.map +1 -0
- package/dist/{update-NAK6CMUX.js → update-OMALGIBR.js} +29 -14
- package/dist/update-OMALGIBR.js.map +1 -0
- package/dist/{users-2YTC4Q36.js → users-UKG7VIQH.js} +45 -67
- package/dist/users-UKG7VIQH.js.map +1 -0
- package/dist/{validate-UOVTM6L3.js → validate-QIYSA3N7.js} +8 -10
- package/dist/validate-QIYSA3N7.js.map +1 -0
- package/dist/{version-N64UBW7A.js → version-NCSNXNVN.js} +3 -3
- package/dist/{vitals-A4CS4MSS.js → vitals-C23L2Y2E.js} +153 -172
- package/dist/vitals-C23L2Y2E.js.map +1 -0
- package/package.json +6 -6
- package/dist/anomalies-NU2IN2GJ.js.map +0 -1
- package/dist/apps-J2446UDA.js.map +0 -1
- package/dist/audit-N2CRHWUN.js.map +0 -1
- package/dist/auth-XGSTT5G5.js.map +0 -1
- package/dist/bundle-F43TD2BQ.js +0 -218
- package/dist/bundle-F43TD2BQ.js.map +0 -1
- package/dist/cache-SLNFRTI2.js.map +0 -1
- package/dist/changelog-ZYD6W5IV.js +0 -53
- package/dist/changelog-ZYD6W5IV.js.map +0 -1
- package/dist/chunk-4O4D5SGL.js.map +0 -1
- package/dist/chunk-AA577WVQ.js.map +0 -1
- package/dist/chunk-FWKYRLKY.js +0 -19
- package/dist/chunk-FWKYRLKY.js.map +0 -1
- package/dist/chunk-NV75I5VP.js.map +0 -1
- package/dist/chunk-U6ZTQ34I.js.map +0 -1
- package/dist/config-BUXPDN7N.js.map +0 -1
- package/dist/data-safety-Q7FTCEWU.js.map +0 -1
- package/dist/device-tiers-MIOQEXYY.js.map +0 -1
- package/dist/diff-V77SMKAQ.js +0 -96
- package/dist/diff-V77SMKAQ.js.map +0 -1
- package/dist/docs-7DUXIKA3.js.map +0 -1
- package/dist/doctor-3Z4ARPM2.js +0 -372
- package/dist/doctor-3Z4ARPM2.js.map +0 -1
- package/dist/enterprise-7THXNBTC.js.map +0 -1
- package/dist/external-transactions-2GWIMUVM.js.map +0 -1
- package/dist/games-BT777WUO.js.map +0 -1
- package/dist/generated-apks-RJWTIX7L.js.map +0 -1
- package/dist/grants-TKQJ3IER.js.map +0 -1
- package/dist/iap-ICAEQLK5.js.map +0 -1
- package/dist/init-JZ2THPMS.js.map +0 -1
- package/dist/listings-77HZW4S5.js.map +0 -1
- package/dist/migrate-SQT6RD6T.js +0 -143
- package/dist/migrate-SQT6RD6T.js.map +0 -1
- package/dist/one-time-products-LHZAXQES.js.map +0 -1
- package/dist/preflight-H3HEBYQW.js.map +0 -1
- package/dist/publish-Q5ZKEKZ5.js.map +0 -1
- package/dist/purchase-options-CKRN4VIW.js.map +0 -1
- package/dist/purchases-43AKV6HG.js.map +0 -1
- package/dist/quickstart-4HB62YEL.js.map +0 -1
- package/dist/quota-UHIQQYOY.js.map +0 -1
- package/dist/recovery-5EV2R476.js.map +0 -1
- package/dist/releases-C2WC2K4E.js.map +0 -1
- package/dist/reports-2YX3RDOS.js.map +0 -1
- package/dist/reviews-2CWOI5CV.js +0 -213
- package/dist/reviews-2CWOI5CV.js.map +0 -1
- package/dist/status-WHGLODGV.js.map +0 -1
- package/dist/subscriptions-CI3JH3VQ.js.map +0 -1
- package/dist/testers-NZOFA3EF.js.map +0 -1
- package/dist/tracks-NERFFEDT.js +0 -107
- package/dist/tracks-NERFFEDT.js.map +0 -1
- package/dist/train-CJJVLY4B.js.map +0 -1
- package/dist/update-NAK6CMUX.js.map +0 -1
- package/dist/users-2YTC4Q36.js.map +0 -1
- package/dist/validate-UOVTM6L3.js.map +0 -1
- package/dist/vitals-A4CS4MSS.js.map +0 -1
- /package/dist/{feedback-DPTO6DUT.js.map → feedback-XP765TOO.js.map} +0 -0
- /package/dist/{prompt-BSV22CQZ.js.map → prompt-GXC2JSLA.js.map} +0 -0
- /package/dist/{version-N64UBW7A.js.map → version-NCSNXNVN.js.map} +0 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
green,
|
|
4
|
+
red,
|
|
5
|
+
yellow
|
|
6
|
+
} from "./chunk-FAN4ZITI.js";
|
|
7
|
+
import {
|
|
8
|
+
isNewerVersion
|
|
9
|
+
} from "./chunk-3SJ6OXCZ.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/doctor.ts
|
|
12
|
+
import { loadConfig, getCacheDir, getConfigDir } from "@gpc-cli/config";
|
|
13
|
+
import { resolveAuth, AuthError } from "@gpc-cli/auth";
|
|
14
|
+
import { existsSync, accessSync, statSync, constants } from "fs";
|
|
15
|
+
import { readFile, stat, statfs } from "fs/promises";
|
|
16
|
+
import { resolve, join } from "path";
|
|
17
|
+
import { lookup } from "dns/promises";
|
|
18
|
+
var PASS = "\u2713";
|
|
19
|
+
var FAIL = "\u2717";
|
|
20
|
+
var WARN = "\u26A0";
|
|
21
|
+
var INFO = "-";
|
|
22
|
+
var ANDROID_PACKAGE_RE = /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
|
|
23
|
+
var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
24
|
+
"app",
|
|
25
|
+
"output",
|
|
26
|
+
"profile",
|
|
27
|
+
"auth",
|
|
28
|
+
"developerId",
|
|
29
|
+
"plugins",
|
|
30
|
+
"profiles",
|
|
31
|
+
"approvedPlugins",
|
|
32
|
+
"webhooks",
|
|
33
|
+
"debug",
|
|
34
|
+
"train"
|
|
35
|
+
]);
|
|
36
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@gpc-cli/cli/latest";
|
|
37
|
+
var API_HOST = "androidpublisher.googleapis.com";
|
|
38
|
+
var REPORTING_HOST = "playdeveloperreporting.googleapis.com";
|
|
39
|
+
var SA_KEY_ROTATION_DAYS = 90;
|
|
40
|
+
function icon(status) {
|
|
41
|
+
switch (status) {
|
|
42
|
+
case "pass":
|
|
43
|
+
return green(PASS);
|
|
44
|
+
case "fail":
|
|
45
|
+
return red(FAIL);
|
|
46
|
+
case "warn":
|
|
47
|
+
return yellow(WARN);
|
|
48
|
+
case "info":
|
|
49
|
+
return INFO;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function checkNodeVersion(nodeVersion) {
|
|
53
|
+
const major = parseInt(nodeVersion.split(".")[0] ?? "0", 10);
|
|
54
|
+
return major >= 20 ? { name: "node", status: "pass", message: `Node.js ${nodeVersion}` } : {
|
|
55
|
+
name: "node",
|
|
56
|
+
status: "fail",
|
|
57
|
+
message: `Node.js ${nodeVersion} (requires >=20)`,
|
|
58
|
+
suggestion: "Upgrade Node.js to v20 or later: https://nodejs.org"
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function checkPackageName(app) {
|
|
62
|
+
if (!app) return null;
|
|
63
|
+
return ANDROID_PACKAGE_RE.test(app) ? { name: "package-name", status: "pass", message: `Package name format OK: ${app}` } : {
|
|
64
|
+
name: "package-name",
|
|
65
|
+
status: "warn",
|
|
66
|
+
message: `Package name may be invalid: ${app}`,
|
|
67
|
+
suggestion: "Android package names must have 2+ dot-separated segments, each starting with a letter (e.g. com.example.app)"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function checkProxy(url) {
|
|
71
|
+
if (!url) return null;
|
|
72
|
+
try {
|
|
73
|
+
new URL(url);
|
|
74
|
+
return { name: "proxy", status: "pass", message: `Proxy configured: ${url}` };
|
|
75
|
+
} catch {
|
|
76
|
+
return {
|
|
77
|
+
name: "proxy",
|
|
78
|
+
status: "warn",
|
|
79
|
+
message: `Invalid proxy URL: ${url}`,
|
|
80
|
+
suggestion: "Set HTTPS_PROXY to a valid URL (e.g. http://proxy.example.com:8080)"
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function checkDeveloperId(id) {
|
|
85
|
+
if (!id) return null;
|
|
86
|
+
if (/^\d+$/.test(id)) {
|
|
87
|
+
return { name: "developer-id", status: "pass", message: `Developer ID: ${id}` };
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
name: "developer-id",
|
|
91
|
+
status: "warn",
|
|
92
|
+
message: `Developer ID may be invalid: ${id}`,
|
|
93
|
+
suggestion: "Developer IDs are numeric. Find yours at: Play Console \u2192 Settings \u2192 Developer account \u2192 Developer ID"
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function checkConflictingCredentials(configSaPath) {
|
|
97
|
+
const sources = [];
|
|
98
|
+
if (configSaPath) sources.push("config file (auth.serviceAccount)");
|
|
99
|
+
if (process.env["GPC_SERVICE_ACCOUNT"]) sources.push("GPC_SERVICE_ACCOUNT env var");
|
|
100
|
+
if (process.env["GOOGLE_APPLICATION_CREDENTIALS"])
|
|
101
|
+
sources.push("GOOGLE_APPLICATION_CREDENTIALS env var");
|
|
102
|
+
if (sources.length <= 1) return null;
|
|
103
|
+
return {
|
|
104
|
+
name: "credentials-conflict",
|
|
105
|
+
status: "warn",
|
|
106
|
+
message: `Multiple credential sources: ${sources.join(", ")}`,
|
|
107
|
+
suggestion: "GPC uses the first match: config file \u2192 GPC_SERVICE_ACCOUNT \u2192 GOOGLE_APPLICATION_CREDENTIALS \u2192 ADC. Remove unused sources to avoid confusion."
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function checkConfigKeys(config) {
|
|
111
|
+
const unknown = Object.keys(config).filter((k) => !KNOWN_CONFIG_KEYS.has(k));
|
|
112
|
+
if (unknown.length === 0) return null;
|
|
113
|
+
return {
|
|
114
|
+
name: "config-keys",
|
|
115
|
+
status: "warn",
|
|
116
|
+
message: `Unknown config key${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}`,
|
|
117
|
+
suggestion: `Valid keys: ${[...KNOWN_CONFIG_KEYS].sort().join(", ")}`
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function checkCiEnvironment() {
|
|
121
|
+
if (!process.env["CI"]) return null;
|
|
122
|
+
let platform = "Unknown CI";
|
|
123
|
+
if (process.env["GITHUB_ACTIONS"]) platform = "GitHub Actions";
|
|
124
|
+
else if (process.env["GITLAB_CI"]) platform = "GitLab CI";
|
|
125
|
+
else if (process.env["BITBUCKET_PIPELINE_UUID"]) platform = "Bitbucket Pipelines";
|
|
126
|
+
else if (process.env["CIRCLECI"]) platform = "CircleCI";
|
|
127
|
+
else if (process.env["JENKINS_URL"]) platform = "Jenkins";
|
|
128
|
+
else if (process.env["TRAVIS"]) platform = "Travis CI";
|
|
129
|
+
else if (process.env["CODEBUILD_BUILD_ID"]) platform = "AWS CodeBuild";
|
|
130
|
+
else if (process.env["BUILD_BUILDID"]) platform = "Azure Pipelines";
|
|
131
|
+
const tips = [];
|
|
132
|
+
if (!process.env["GPC_NO_COLOR"]) tips.push("Set GPC_NO_COLOR=1 for clean logs");
|
|
133
|
+
if (!process.env["GPC_NO_UPDATE_CHECK"])
|
|
134
|
+
tips.push("Set GPC_NO_UPDATE_CHECK=1 to skip update checks");
|
|
135
|
+
return {
|
|
136
|
+
name: "ci",
|
|
137
|
+
status: "info",
|
|
138
|
+
message: `CI detected: ${platform}`,
|
|
139
|
+
suggestion: tips.length > 0 ? tips.join(". ") : void 0
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function checkGpcVersion() {
|
|
143
|
+
const currentVersion = "0.9.45";
|
|
144
|
+
if (currentVersion === "0.0.0") {
|
|
145
|
+
return { name: "version", status: "info", message: "GPC development build" };
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const resp = await fetch(NPM_REGISTRY_URL, {
|
|
149
|
+
signal: AbortSignal.timeout(3e3)
|
|
150
|
+
});
|
|
151
|
+
if (!resp.ok) return { name: "version", status: "pass", message: `GPC v${currentVersion}` };
|
|
152
|
+
const body = await resp.json();
|
|
153
|
+
if (!body.version)
|
|
154
|
+
return { name: "version", status: "pass", message: `GPC v${currentVersion}` };
|
|
155
|
+
if (isNewerVersion(currentVersion, body.version)) {
|
|
156
|
+
return {
|
|
157
|
+
name: "version",
|
|
158
|
+
status: "warn",
|
|
159
|
+
message: `GPC v${currentVersion} (v${body.version} available)`,
|
|
160
|
+
suggestion: "Run: gpc update"
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
name: "version",
|
|
165
|
+
status: "pass",
|
|
166
|
+
message: `GPC v${currentVersion} (up to date)`
|
|
167
|
+
};
|
|
168
|
+
} catch {
|
|
169
|
+
return { name: "version", status: "pass", message: `GPC v${currentVersion}` };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async function checkDiskSpace(dir) {
|
|
173
|
+
try {
|
|
174
|
+
const stats = await statfs(dir);
|
|
175
|
+
const availableGB = Number(stats.bavail) * Number(stats.bsize) / 1024 ** 3;
|
|
176
|
+
if (availableGB < 0.1) {
|
|
177
|
+
return {
|
|
178
|
+
name: "disk-space",
|
|
179
|
+
status: "warn",
|
|
180
|
+
message: `Low disk space: ${availableGB.toFixed(1)} GB available`,
|
|
181
|
+
suggestion: "Free up disk space \u2014 AAB uploads can be up to 2 GB"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
name: "disk-space",
|
|
186
|
+
status: "pass",
|
|
187
|
+
message: `Disk space: ${availableGB.toFixed(1)} GB available`
|
|
188
|
+
};
|
|
189
|
+
} catch {
|
|
190
|
+
return { name: "disk-space", status: "info", message: "Could not check disk space" };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function checkSaKeyAge(saPath) {
|
|
194
|
+
try {
|
|
195
|
+
const stats = await stat(saPath);
|
|
196
|
+
const ageDays = Math.floor((Date.now() - stats.mtimeMs) / (1e3 * 60 * 60 * 24));
|
|
197
|
+
if (ageDays > SA_KEY_ROTATION_DAYS) {
|
|
198
|
+
return {
|
|
199
|
+
name: "service-account-age",
|
|
200
|
+
status: "warn",
|
|
201
|
+
message: `Service account key is ${ageDays} days old`,
|
|
202
|
+
suggestion: `Google recommends rotating service account keys every ${SA_KEY_ROTATION_DAYS} days`
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function checkTokenCache(cacheDir) {
|
|
211
|
+
const cachePath = join(cacheDir, "token-cache.json");
|
|
212
|
+
try {
|
|
213
|
+
if (!existsSync(cachePath)) return null;
|
|
214
|
+
const content = await readFile(cachePath, "utf-8");
|
|
215
|
+
JSON.parse(content);
|
|
216
|
+
return { name: "token-cache", status: "pass", message: "Token cache OK" };
|
|
217
|
+
} catch {
|
|
218
|
+
return {
|
|
219
|
+
name: "token-cache",
|
|
220
|
+
status: "warn",
|
|
221
|
+
message: "Token cache is corrupt",
|
|
222
|
+
suggestion: "Clear with: gpc cache clear",
|
|
223
|
+
fixData: { path: cachePath }
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function probeHttps(host, timeoutMs = 5e3) {
|
|
228
|
+
const { connect } = await import("tls");
|
|
229
|
+
return new Promise((resolve2) => {
|
|
230
|
+
const start = performance.now();
|
|
231
|
+
const socket = connect({ host, port: 443 }, () => {
|
|
232
|
+
const latencyMs = Math.round(performance.now() - start);
|
|
233
|
+
socket.end();
|
|
234
|
+
resolve2({ ok: true, latencyMs });
|
|
235
|
+
});
|
|
236
|
+
socket.setTimeout(timeoutMs);
|
|
237
|
+
socket.on("error", (err) => {
|
|
238
|
+
socket.destroy();
|
|
239
|
+
resolve2({ ok: false, latencyMs: 0, error: err.message });
|
|
240
|
+
});
|
|
241
|
+
socket.on("timeout", () => {
|
|
242
|
+
socket.destroy();
|
|
243
|
+
resolve2({ ok: false, latencyMs: 0, error: "Connection timed out" });
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
async function checkAppAccess(packageName, accessToken) {
|
|
248
|
+
try {
|
|
249
|
+
const insertResp = await fetch(
|
|
250
|
+
`https://${API_HOST}/androidpublisher/v3/applications/${encodeURIComponent(packageName)}/edits`,
|
|
251
|
+
{
|
|
252
|
+
method: "POST",
|
|
253
|
+
headers: {
|
|
254
|
+
Authorization: `Bearer ${accessToken}`,
|
|
255
|
+
"Content-Type": "application/json"
|
|
256
|
+
},
|
|
257
|
+
body: JSON.stringify({}),
|
|
258
|
+
signal: AbortSignal.timeout(1e4)
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
if (!insertResp.ok) {
|
|
262
|
+
const body = await insertResp.json().catch(() => ({}));
|
|
263
|
+
if (body.error?.status === "PERMISSION_DENIED" && body.error?.message?.includes("has not been used")) {
|
|
264
|
+
return {
|
|
265
|
+
name: "app-access",
|
|
266
|
+
status: "fail",
|
|
267
|
+
message: "Google Play Android Developer API is not enabled",
|
|
268
|
+
suggestion: "Enable it: https://console.cloud.google.com/apis/api/androidpublisher.googleapis.com"
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (insertResp.status === 403) {
|
|
272
|
+
return {
|
|
273
|
+
name: "app-access",
|
|
274
|
+
status: "fail",
|
|
275
|
+
message: `No access to ${packageName}`,
|
|
276
|
+
suggestion: "Grant the service account access in Google Play Console \u2192 Users and permissions"
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (insertResp.status === 404) {
|
|
280
|
+
return {
|
|
281
|
+
name: "app-access",
|
|
282
|
+
status: "fail",
|
|
283
|
+
message: `App not found: ${packageName}`,
|
|
284
|
+
suggestion: "Check the package name with: gpc config get app"
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
name: "app-access",
|
|
289
|
+
status: "fail",
|
|
290
|
+
message: `App access check failed: HTTP ${insertResp.status}`,
|
|
291
|
+
suggestion: body.error?.message ?? "Check your credentials and app configuration"
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const edit = await insertResp.json();
|
|
295
|
+
if (edit.id) {
|
|
296
|
+
await fetch(
|
|
297
|
+
`https://${API_HOST}/androidpublisher/v3/applications/${encodeURIComponent(packageName)}/edits/${edit.id}`,
|
|
298
|
+
{
|
|
299
|
+
method: "DELETE",
|
|
300
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
301
|
+
signal: AbortSignal.timeout(5e3)
|
|
302
|
+
}
|
|
303
|
+
).catch(() => {
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
name: "app-access",
|
|
308
|
+
status: "pass",
|
|
309
|
+
message: `App access verified: ${packageName}`
|
|
310
|
+
};
|
|
311
|
+
} catch (err) {
|
|
312
|
+
return {
|
|
313
|
+
name: "app-access",
|
|
314
|
+
status: "warn",
|
|
315
|
+
message: `Could not verify app access: ${err instanceof Error ? err.message : String(err)}`
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function applyFix(check) {
|
|
320
|
+
switch (check.name) {
|
|
321
|
+
case "config-dir":
|
|
322
|
+
case "cache-dir": {
|
|
323
|
+
const dirMatch = check.message.match(/: (.+)$/);
|
|
324
|
+
if (!dirMatch?.[1]) return null;
|
|
325
|
+
const { mkdir } = await import("fs/promises");
|
|
326
|
+
await mkdir(dirMatch[1], {
|
|
327
|
+
recursive: true,
|
|
328
|
+
mode: check.name === "cache-dir" ? 448 : 493
|
|
329
|
+
});
|
|
330
|
+
return `Created ${dirMatch[1]}`;
|
|
331
|
+
}
|
|
332
|
+
case "service-account-permissions": {
|
|
333
|
+
const saPath = check.fixData?.path;
|
|
334
|
+
if (!saPath) return null;
|
|
335
|
+
const { chmod } = await import("fs/promises");
|
|
336
|
+
await chmod(saPath, 384);
|
|
337
|
+
return `Fixed permissions on ${saPath}`;
|
|
338
|
+
}
|
|
339
|
+
case "token-cache": {
|
|
340
|
+
const cachePath = check.fixData?.path;
|
|
341
|
+
if (!cachePath) return null;
|
|
342
|
+
const { unlink } = await import("fs/promises");
|
|
343
|
+
await unlink(cachePath);
|
|
344
|
+
return `Cleared corrupt token cache`;
|
|
345
|
+
}
|
|
346
|
+
case "config": {
|
|
347
|
+
const { initConfig } = await import("@gpc-cli/config");
|
|
348
|
+
await initConfig({});
|
|
349
|
+
return "Initialized config file";
|
|
350
|
+
}
|
|
351
|
+
default:
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function registerDoctorCommand(program) {
|
|
356
|
+
program.command("doctor").description("Verify setup and connectivity").option("--fix", "Attempt to auto-fix failing checks").action(async (opts, cmd) => {
|
|
357
|
+
const results = [];
|
|
358
|
+
const parentOpts = cmd.parent?.opts() ?? {};
|
|
359
|
+
const jsonMode = !!(parentOpts["json"] || parentOpts["output"] === "json");
|
|
360
|
+
results.push(checkNodeVersion(process.versions.node));
|
|
361
|
+
results.push(await checkGpcVersion());
|
|
362
|
+
const ciResult = checkCiEnvironment();
|
|
363
|
+
if (ciResult) results.push(ciResult);
|
|
364
|
+
let config;
|
|
365
|
+
try {
|
|
366
|
+
config = await loadConfig();
|
|
367
|
+
results.push({ name: "config", status: "pass", message: "Configuration loaded" });
|
|
368
|
+
const keysCheck = checkConfigKeys(config);
|
|
369
|
+
if (keysCheck) results.push(keysCheck);
|
|
370
|
+
if (config.app) {
|
|
371
|
+
results.push({
|
|
372
|
+
name: "default-app",
|
|
373
|
+
status: "pass",
|
|
374
|
+
message: `Default app: ${config.app}`
|
|
375
|
+
});
|
|
376
|
+
const pkgCheck = checkPackageName(config.app);
|
|
377
|
+
if (pkgCheck) results.push(pkgCheck);
|
|
378
|
+
} else {
|
|
379
|
+
results.push({
|
|
380
|
+
name: "default-app",
|
|
381
|
+
status: "info",
|
|
382
|
+
message: "No default app configured",
|
|
383
|
+
suggestion: "Use --app flag or run: gpc config set app <package>"
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
const devIdCheck = checkDeveloperId(config.developerId);
|
|
387
|
+
if (devIdCheck) results.push(devIdCheck);
|
|
388
|
+
} catch {
|
|
389
|
+
results.push({
|
|
390
|
+
name: "config",
|
|
391
|
+
status: "fail",
|
|
392
|
+
message: "Configuration could not be loaded",
|
|
393
|
+
suggestion: "Run gpc config init to create a config file, or check .gpcrc.json for syntax errors"
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
const configDir = getConfigDir();
|
|
397
|
+
try {
|
|
398
|
+
if (existsSync(configDir)) {
|
|
399
|
+
accessSync(configDir, constants.R_OK | constants.W_OK);
|
|
400
|
+
results.push({
|
|
401
|
+
name: "config-dir",
|
|
402
|
+
status: "pass",
|
|
403
|
+
message: `Config directory: ${configDir}`
|
|
404
|
+
});
|
|
405
|
+
} else {
|
|
406
|
+
results.push({
|
|
407
|
+
name: "config-dir",
|
|
408
|
+
status: "info",
|
|
409
|
+
message: `Config directory does not exist yet: ${configDir}`
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
} catch {
|
|
413
|
+
results.push({
|
|
414
|
+
name: "config-dir",
|
|
415
|
+
status: "warn",
|
|
416
|
+
message: `Config directory not writable: ${configDir}`,
|
|
417
|
+
suggestion: `Fix permissions: chmod 755 ${configDir}`
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
const cacheDir = getCacheDir();
|
|
421
|
+
try {
|
|
422
|
+
if (existsSync(cacheDir)) {
|
|
423
|
+
accessSync(cacheDir, constants.R_OK | constants.W_OK);
|
|
424
|
+
results.push({
|
|
425
|
+
name: "cache-dir",
|
|
426
|
+
status: "pass",
|
|
427
|
+
message: `Cache directory: ${cacheDir}`
|
|
428
|
+
});
|
|
429
|
+
} else {
|
|
430
|
+
results.push({
|
|
431
|
+
name: "cache-dir",
|
|
432
|
+
status: "info",
|
|
433
|
+
message: `Cache directory does not exist yet: ${cacheDir}`
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
results.push({
|
|
438
|
+
name: "cache-dir",
|
|
439
|
+
status: "warn",
|
|
440
|
+
message: `Cache directory not writable: ${cacheDir}`,
|
|
441
|
+
suggestion: `Fix permissions: chmod 700 ${cacheDir}`
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
const spaceDir = existsSync(cacheDir) ? cacheDir : configDir;
|
|
445
|
+
if (existsSync(spaceDir)) {
|
|
446
|
+
results.push(await checkDiskSpace(spaceDir));
|
|
447
|
+
}
|
|
448
|
+
let saFilePath;
|
|
449
|
+
if (config?.auth?.serviceAccount) {
|
|
450
|
+
const saValue = config.auth.serviceAccount;
|
|
451
|
+
const looksLikePath = !saValue.trim().startsWith("{");
|
|
452
|
+
if (looksLikePath) {
|
|
453
|
+
saFilePath = resolve(saValue);
|
|
454
|
+
if (existsSync(saFilePath)) {
|
|
455
|
+
try {
|
|
456
|
+
accessSync(saFilePath, constants.R_OK);
|
|
457
|
+
results.push({
|
|
458
|
+
name: "service-account-file",
|
|
459
|
+
status: "pass",
|
|
460
|
+
message: `Service account file: ${saFilePath}`
|
|
461
|
+
});
|
|
462
|
+
} catch {
|
|
463
|
+
results.push({
|
|
464
|
+
name: "service-account-file",
|
|
465
|
+
status: "fail",
|
|
466
|
+
message: `Service account file not readable: ${saFilePath}`,
|
|
467
|
+
suggestion: `Fix permissions: chmod 600 ${saFilePath}`
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (process.platform !== "win32") {
|
|
471
|
+
try {
|
|
472
|
+
const mode = statSync(saFilePath).mode;
|
|
473
|
+
const groupRead = (mode & 32) !== 0;
|
|
474
|
+
const worldRead = (mode & 4) !== 0;
|
|
475
|
+
if (groupRead || worldRead) {
|
|
476
|
+
results.push({
|
|
477
|
+
name: "service-account-permissions",
|
|
478
|
+
status: "warn",
|
|
479
|
+
message: `Service account file is group/world-readable (mode: ${(mode & 511).toString(8)})`,
|
|
480
|
+
suggestion: `Restrict permissions: chmod 600 ${saFilePath}`,
|
|
481
|
+
fixData: { path: saFilePath }
|
|
482
|
+
});
|
|
483
|
+
} else {
|
|
484
|
+
results.push({
|
|
485
|
+
name: "service-account-permissions",
|
|
486
|
+
status: "pass",
|
|
487
|
+
message: `Service account file permissions OK (mode: ${(mode & 511).toString(8)})`
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
} catch {
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const ageResult = await checkSaKeyAge(saFilePath);
|
|
494
|
+
if (ageResult) results.push(ageResult);
|
|
495
|
+
} else {
|
|
496
|
+
results.push({
|
|
497
|
+
name: "service-account-file",
|
|
498
|
+
status: "fail",
|
|
499
|
+
message: `Service account file not found: ${saFilePath}`,
|
|
500
|
+
suggestion: "Check the path in your config or GPC_SERVICE_ACCOUNT env var"
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const conflictCheck = checkConflictingCredentials(saFilePath);
|
|
506
|
+
if (conflictCheck) results.push(conflictCheck);
|
|
507
|
+
const cacheResult = await checkTokenCache(cacheDir);
|
|
508
|
+
if (cacheResult) results.push(cacheResult);
|
|
509
|
+
const gpcProfile = process.env["GPC_PROFILE"];
|
|
510
|
+
if (gpcProfile && config) {
|
|
511
|
+
if (config.profiles && gpcProfile in config.profiles) {
|
|
512
|
+
results.push({
|
|
513
|
+
name: "profile",
|
|
514
|
+
status: "pass",
|
|
515
|
+
message: `Profile "${gpcProfile}" found`
|
|
516
|
+
});
|
|
517
|
+
} else {
|
|
518
|
+
const available = config.profiles ? Object.keys(config.profiles).join(", ") : "";
|
|
519
|
+
results.push({
|
|
520
|
+
name: "profile",
|
|
521
|
+
status: "fail",
|
|
522
|
+
message: `Profile "${gpcProfile}" not found`,
|
|
523
|
+
suggestion: available ? `Available profiles: ${available}. Create with: gpc auth login --profile ${gpcProfile}` : `No profiles defined. Create one with: gpc auth login --profile ${gpcProfile}`
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const proxyUrl = process.env["HTTPS_PROXY"] || process.env["https_proxy"] || process.env["HTTP_PROXY"] || process.env["http_proxy"];
|
|
528
|
+
const proxyCheck = checkProxy(proxyUrl);
|
|
529
|
+
if (proxyCheck) results.push(proxyCheck);
|
|
530
|
+
const caCert = process.env["GPC_CA_CERT"] || process.env["NODE_EXTRA_CA_CERTS"];
|
|
531
|
+
if (caCert) {
|
|
532
|
+
if (existsSync(caCert)) {
|
|
533
|
+
results.push({
|
|
534
|
+
name: "ca-cert",
|
|
535
|
+
status: "pass",
|
|
536
|
+
message: `CA certificate: ${caCert}`
|
|
537
|
+
});
|
|
538
|
+
} else {
|
|
539
|
+
results.push({
|
|
540
|
+
name: "ca-cert",
|
|
541
|
+
status: "warn",
|
|
542
|
+
message: `CA certificate file not found: ${caCert}`,
|
|
543
|
+
suggestion: "Check that GPC_CA_CERT points to an existing PEM file"
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const dnsHosts = [API_HOST, REPORTING_HOST];
|
|
548
|
+
const dnsPassedHosts = [];
|
|
549
|
+
for (const host of dnsHosts) {
|
|
550
|
+
const label = host.split(".")[0];
|
|
551
|
+
try {
|
|
552
|
+
const start = performance.now();
|
|
553
|
+
await lookup(host);
|
|
554
|
+
const ms = Math.round(performance.now() - start);
|
|
555
|
+
results.push({
|
|
556
|
+
name: `dns-${label}`,
|
|
557
|
+
status: "pass",
|
|
558
|
+
message: `DNS: ${host} (${ms}ms)`
|
|
559
|
+
});
|
|
560
|
+
dnsPassedHosts.push(host);
|
|
561
|
+
} catch {
|
|
562
|
+
results.push({
|
|
563
|
+
name: `dns-${label}`,
|
|
564
|
+
status: "fail",
|
|
565
|
+
message: `Cannot resolve ${host}`,
|
|
566
|
+
suggestion: "Check your DNS settings and network connection"
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
for (const host of dnsPassedHosts) {
|
|
571
|
+
const label = host.split(".")[0];
|
|
572
|
+
const probe = await probeHttps(host);
|
|
573
|
+
if (probe.ok) {
|
|
574
|
+
results.push({
|
|
575
|
+
name: `https-${label}`,
|
|
576
|
+
status: "pass",
|
|
577
|
+
message: `HTTPS: ${host} (${probe.latencyMs}ms)`
|
|
578
|
+
});
|
|
579
|
+
} else {
|
|
580
|
+
results.push({
|
|
581
|
+
name: `https-${label}`,
|
|
582
|
+
status: "fail",
|
|
583
|
+
message: `HTTPS connection failed: ${host}`,
|
|
584
|
+
suggestion: probe.error ? `Error: ${probe.error}. Check firewall rules and proxy settings.` : "Check firewall rules and proxy settings"
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
let accessToken;
|
|
589
|
+
try {
|
|
590
|
+
const authConfig = config ?? await loadConfig();
|
|
591
|
+
const client = await resolveAuth({
|
|
592
|
+
serviceAccountPath: authConfig.auth?.serviceAccount
|
|
593
|
+
});
|
|
594
|
+
results.push({
|
|
595
|
+
name: "auth",
|
|
596
|
+
status: "pass",
|
|
597
|
+
message: `Authenticated as ${client.getClientEmail()}`
|
|
598
|
+
});
|
|
599
|
+
accessToken = await client.getAccessToken();
|
|
600
|
+
results.push({
|
|
601
|
+
name: "api-connectivity",
|
|
602
|
+
status: "pass",
|
|
603
|
+
message: "API connectivity verified"
|
|
604
|
+
});
|
|
605
|
+
} catch (error) {
|
|
606
|
+
if (error instanceof AuthError) {
|
|
607
|
+
results.push({
|
|
608
|
+
name: "auth",
|
|
609
|
+
status: "fail",
|
|
610
|
+
message: `Authentication: ${error.message}`,
|
|
611
|
+
suggestion: error.suggestion
|
|
612
|
+
});
|
|
613
|
+
} else {
|
|
614
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
615
|
+
results.push({
|
|
616
|
+
name: "api-connectivity",
|
|
617
|
+
status: "fail",
|
|
618
|
+
message: `API connectivity failed: ${detail}`,
|
|
619
|
+
suggestion: "Check your network connection and credentials"
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (accessToken && config?.app) {
|
|
624
|
+
results.push(await checkAppAccess(config.app, accessToken));
|
|
625
|
+
}
|
|
626
|
+
if (opts["fix"]) {
|
|
627
|
+
for (const r of results) {
|
|
628
|
+
if (r.status === "fail" || r.status === "warn") {
|
|
629
|
+
try {
|
|
630
|
+
const fixMsg = await applyFix(r);
|
|
631
|
+
if (fixMsg) {
|
|
632
|
+
console.log(` ${green("\u2192")} Fixed: ${fixMsg}`);
|
|
633
|
+
r.status = "pass";
|
|
634
|
+
r.message += " (fixed)";
|
|
635
|
+
}
|
|
636
|
+
} catch (err) {
|
|
637
|
+
console.error(
|
|
638
|
+
` ${red("\u2717")} Could not fix "${r.name}": ${err instanceof Error ? err.message : String(err)}`
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const errors = results.filter((r) => r.status === "fail").length;
|
|
645
|
+
const warnings = results.filter((r) => r.status === "warn").length;
|
|
646
|
+
const passed = results.filter((r) => r.status === "pass").length;
|
|
647
|
+
if (jsonMode) {
|
|
648
|
+
const cleanResults = results.map(({ fixData: _, ...rest }) => rest);
|
|
649
|
+
console.log(
|
|
650
|
+
JSON.stringify(
|
|
651
|
+
{ success: errors === 0, errors, warnings, checks: cleanResults },
|
|
652
|
+
null,
|
|
653
|
+
2
|
|
654
|
+
)
|
|
655
|
+
);
|
|
656
|
+
process.exitCode = errors > 0 ? 1 : 0;
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
console.log("GPC Doctor\n");
|
|
660
|
+
for (const r of results) {
|
|
661
|
+
console.log(` ${icon(r.status)} ${r.message}`);
|
|
662
|
+
if (r.suggestion && r.status !== "pass") {
|
|
663
|
+
console.log(` ${r.suggestion}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
console.log(
|
|
667
|
+
`
|
|
668
|
+
${green(PASS)} ${passed} passed ${yellow(WARN)} ${warnings} warning${warnings !== 1 ? "s" : ""} ${red(FAIL)} ${errors} failed`
|
|
669
|
+
);
|
|
670
|
+
if (errors > 0) {
|
|
671
|
+
console.log("\nSome checks failed. Fix the issues above and run again.");
|
|
672
|
+
process.exitCode = 1;
|
|
673
|
+
} else if (warnings > 0) {
|
|
674
|
+
console.log("\nAll checks passed with warnings.");
|
|
675
|
+
} else {
|
|
676
|
+
console.log(`
|
|
677
|
+
${green("\u2713")} Ready. Try: gpc status`);
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
export {
|
|
682
|
+
checkCiEnvironment,
|
|
683
|
+
checkConfigKeys,
|
|
684
|
+
checkConflictingCredentials,
|
|
685
|
+
checkDeveloperId,
|
|
686
|
+
checkNodeVersion,
|
|
687
|
+
checkPackageName,
|
|
688
|
+
checkProxy,
|
|
689
|
+
registerDoctorCommand
|
|
690
|
+
};
|
|
691
|
+
//# sourceMappingURL=doctor-H4X7Q57B.js.map
|