@gpc-cli/cli 0.9.23 → 0.9.25
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 +57 -30
- package/dist/bin.js +1 -1
- package/dist/{bundle-U7RF6HDE.js → bundle-7IF5FIB4.js} +63 -1
- package/dist/bundle-7IF5FIB4.js.map +1 -0
- package/dist/{chunk-CE2HXEJX.js → chunk-P6TYWHCR.js} +9 -9
- package/dist/config-2L7QUYWP.js +97 -0
- package/dist/config-2L7QUYWP.js.map +1 -0
- package/dist/{doctor-TEIKODLP.js → doctor-UZB2UB5X.js} +79 -65
- package/dist/doctor-UZB2UB5X.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/migrate-XQV7P4R7.js +135 -0
- package/dist/migrate-XQV7P4R7.js.map +1 -0
- package/dist/{publish-PZRWX3TH.js → publish-26ZPS7XX.js} +86 -16
- package/dist/publish-26ZPS7XX.js.map +1 -0
- package/dist/status-5TOOZAGT.js +96 -0
- package/dist/status-5TOOZAGT.js.map +1 -0
- package/dist/{validate-UYXICKBO.js → validate-MHLPENCM.js} +19 -2
- package/dist/validate-MHLPENCM.js.map +1 -0
- package/dist/{vitals-GDIQFWHV.js → vitals-KSNAVN5F.js} +2 -2
- package/dist/{vitals-GDIQFWHV.js.map → vitals-KSNAVN5F.js.map} +1 -1
- package/package.json +3 -3
- package/dist/bundle-U7RF6HDE.js.map +0 -1
- package/dist/config-R5U7GV56.js +0 -51
- package/dist/config-R5U7GV56.js.map +0 -1
- package/dist/doctor-TEIKODLP.js.map +0 -1
- package/dist/migrate-V6G5YUVH.js +0 -80
- package/dist/migrate-V6G5YUVH.js.map +0 -1
- package/dist/publish-PZRWX3TH.js.map +0 -1
- package/dist/status-S3FAEXNH.js +0 -37
- package/dist/status-S3FAEXNH.js.map +0 -1
- package/dist/validate-UYXICKBO.js.map +0 -1
- /package/dist/{chunk-CE2HXEJX.js.map → chunk-P6TYWHCR.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/doctor.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig, getCacheDir, getConfigDir } from \"@gpc-cli/config\";\nimport { resolveAuth, AuthError } from \"@gpc-cli/auth\";\nimport { existsSync, accessSync, statSync, constants } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { lookup } from \"node:dns/promises\";\n\nexport interface CheckResult {\n name: string;\n status: \"pass\" | \"fail\" | \"warn\" | \"info\";\n message: string;\n suggestion?: string;\n}\n\nconst PASS = \"\\u2713\";\nconst FAIL = \"\\u2717\";\nconst WARN = \"\\u26A0\";\nconst INFO = \"-\";\n\nfunction icon(status: CheckResult[\"status\"]): string {\n switch (status) {\n case \"pass\":\n return PASS;\n case \"fail\":\n return FAIL;\n case \"warn\":\n return WARN;\n case \"info\":\n return INFO;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Pure, testable check helpers\n// ---------------------------------------------------------------------------\n\nconst ANDROID_PACKAGE_RE = /^[a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z][a-zA-Z0-9_]*)+$/;\n\nexport function checkNodeVersion(nodeVersion: string): CheckResult {\n const major = parseInt(nodeVersion.split(\".\")[0] ?? \"0\", 10);\n return major >= 20\n ? { name: \"node\", status: \"pass\", message: `Node.js ${nodeVersion}` }\n : {\n name: \"node\",\n status: \"fail\",\n message: `Node.js ${nodeVersion} (requires >=20)`,\n suggestion: \"Upgrade Node.js to v20 or later: https://nodejs.org\",\n };\n}\n\nexport function checkPackageName(app: string | undefined): CheckResult | null {\n if (!app) return null;\n return ANDROID_PACKAGE_RE.test(app)\n ? { name: \"package-name\", status: \"pass\", message: `Package name format OK: ${app}` }\n : {\n name: \"package-name\",\n status: \"warn\",\n message: `Package name may be invalid: ${app}`,\n suggestion:\n \"Android package names must have 2+ dot-separated segments, each starting with a letter (e.g. com.example.app)\",\n };\n}\n\nexport function checkProxy(url: string | undefined): CheckResult | null {\n if (!url) return null;\n try {\n new URL(url);\n return { name: \"proxy\", status: \"pass\", message: `Proxy configured: ${url}` };\n } catch {\n return {\n name: \"proxy\",\n status: \"warn\",\n message: `Invalid proxy URL: ${url}`,\n suggestion: \"Set HTTPS_PROXY to a valid URL (e.g. http://proxy.example.com:8080)\",\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command registration\n// ---------------------------------------------------------------------------\n\nexport function registerDoctorCommand(program: Command): void {\n program\n .command(\"doctor\")\n .description(\"Verify setup and connectivity\")\n .action(async (_opts, cmd) => {\n const results: CheckResult[] = [];\n const parentOpts = cmd.parent?.opts() ?? {};\n const jsonMode = !!(parentOpts[\"json\"] || parentOpts[\"output\"] === \"json\");\n\n // 1. Node.js version\n results.push(checkNodeVersion(process.versions.node));\n\n // 2. Config file\n let config;\n try {\n config = await loadConfig();\n results.push({ name: \"config\", status: \"pass\", message: \"Configuration loaded\" });\n if (config.app) {\n results.push({\n name: \"default-app\",\n status: \"pass\",\n message: `Default app: ${config.app}`,\n });\n // 2b. Package name format\n const pkgCheck = checkPackageName(config.app);\n if (pkgCheck) results.push(pkgCheck);\n } else {\n results.push({\n name: \"default-app\",\n status: \"info\",\n message: \"No default app configured\",\n suggestion: \"Use --app flag or run: gpc config set app <package>\",\n });\n }\n } catch {\n results.push({\n name: \"config\",\n status: \"fail\",\n message: \"Configuration could not be loaded\",\n suggestion: \"Run gpc config init to create a config file, or check .gpcrc.json for syntax errors\",\n });\n }\n\n // 3. Config directory permissions\n const configDir = getConfigDir();\n try {\n if (existsSync(configDir)) {\n accessSync(configDir, constants.R_OK | constants.W_OK);\n results.push({\n name: \"config-dir\",\n status: \"pass\",\n message: `Config directory: ${configDir}`,\n });\n } else {\n results.push({\n name: \"config-dir\",\n status: \"info\",\n message: `Config directory does not exist yet: ${configDir}`,\n });\n }\n } catch {\n results.push({\n name: \"config-dir\",\n status: \"warn\",\n message: `Config directory not writable: ${configDir}`,\n suggestion: `Fix permissions: chmod 755 ${configDir}`,\n });\n }\n\n // 4. Cache directory permissions\n const cacheDir = getCacheDir();\n try {\n if (existsSync(cacheDir)) {\n accessSync(cacheDir, constants.R_OK | constants.W_OK);\n results.push({\n name: \"cache-dir\",\n status: \"pass\",\n message: `Cache directory: ${cacheDir}`,\n });\n } else {\n results.push({\n name: \"cache-dir\",\n status: \"info\",\n message: `Cache directory does not exist yet: ${cacheDir}`,\n });\n }\n } catch {\n results.push({\n name: \"cache-dir\",\n status: \"warn\",\n message: `Cache directory not writable: ${cacheDir}`,\n suggestion: `Fix permissions: chmod 700 ${cacheDir}`,\n });\n }\n\n // 5. Service account file existence + permissions\n if (config?.auth?.serviceAccount) {\n const saValue = config.auth.serviceAccount;\n const looksLikePath = !saValue.trim().startsWith(\"{\");\n if (looksLikePath) {\n const saPath = resolve(saValue);\n if (existsSync(saPath)) {\n try {\n accessSync(saPath, constants.R_OK);\n results.push({\n name: \"service-account-file\",\n status: \"pass\",\n message: `Service account file: ${saPath}`,\n });\n } catch {\n results.push({\n name: \"service-account-file\",\n status: \"fail\",\n message: `Service account file not readable: ${saPath}`,\n suggestion: `Fix permissions: chmod 600 ${saPath}`,\n });\n }\n\n // 5b. SA key file permissions (Unix only)\n if (process.platform !== \"win32\") {\n try {\n const mode = statSync(saPath).mode;\n const groupRead = (mode & 0o040) !== 0;\n const worldRead = (mode & 0o004) !== 0;\n if (groupRead || worldRead) {\n results.push({\n name: \"service-account-permissions\",\n status: \"warn\",\n message: `Service account file is group/world-readable (mode: ${(mode & 0o777).toString(8)})`,\n suggestion: `Restrict permissions: chmod 600 ${saPath}`,\n });\n } else {\n results.push({\n name: \"service-account-permissions\",\n status: \"pass\",\n message: `Service account file permissions OK (mode: ${(mode & 0o777).toString(8)})`,\n });\n }\n } catch {\n // stat failed — skip permission check\n }\n }\n } else {\n results.push({\n name: \"service-account-file\",\n status: \"fail\",\n message: `Service account file not found: ${saPath}`,\n suggestion: \"Check the path in your config or GPC_SERVICE_ACCOUNT env var\",\n });\n }\n }\n }\n\n // 6. Profile validation\n const gpcProfile = process.env[\"GPC_PROFILE\"];\n if (gpcProfile && config) {\n if (config.profiles && gpcProfile in config.profiles) {\n results.push({\n name: \"profile\",\n status: \"pass\",\n message: `Profile \"${gpcProfile}\" found`,\n });\n } else {\n const available = config.profiles ? Object.keys(config.profiles).join(\", \") : \"\";\n results.push({\n name: \"profile\",\n status: \"fail\",\n message: `Profile \"${gpcProfile}\" not found`,\n suggestion: available\n ? `Available profiles: ${available}. Create with: gpc auth login --profile ${gpcProfile}`\n : `No profiles defined. Create one with: gpc auth login --profile ${gpcProfile}`,\n });\n }\n }\n\n // 7. Proxy configuration\n const proxyUrl =\n process.env[\"HTTPS_PROXY\"] ||\n process.env[\"https_proxy\"] ||\n process.env[\"HTTP_PROXY\"] ||\n process.env[\"http_proxy\"];\n const proxyCheck = checkProxy(proxyUrl);\n if (proxyCheck) results.push(proxyCheck);\n\n // 8. CA certificate\n const caCert = process.env[\"GPC_CA_CERT\"] || process.env[\"NODE_EXTRA_CA_CERTS\"];\n if (caCert) {\n if (existsSync(caCert)) {\n results.push({\n name: \"ca-cert\",\n status: \"pass\",\n message: `CA certificate: ${caCert}`,\n });\n } else {\n results.push({\n name: \"ca-cert\",\n status: \"warn\",\n message: `CA certificate file not found: ${caCert}`,\n suggestion: \"Check that GPC_CA_CERT points to an existing PEM file\",\n });\n }\n }\n\n // 9. DNS resolution — both API endpoints\n const dnsHosts = [\n \"androidpublisher.googleapis.com\",\n \"playdeveloperreporting.googleapis.com\",\n ];\n for (const host of dnsHosts) {\n try {\n await lookup(host);\n results.push({\n name: \"dns\",\n status: \"pass\",\n message: `DNS: ${host}`,\n });\n } catch {\n results.push({\n name: \"dns\",\n status: \"fail\",\n message: `Cannot resolve ${host}`,\n suggestion: \"Check your DNS settings and network connection\",\n });\n }\n }\n\n // 10. Authentication + API connectivity\n try {\n const authConfig = config ?? (await loadConfig());\n const client = await resolveAuth({\n serviceAccountPath: authConfig.auth?.serviceAccount,\n });\n results.push({\n name: \"auth\",\n status: \"pass\",\n message: `Authenticated as ${client.getClientEmail()}`,\n });\n\n await client.getAccessToken();\n results.push({\n name: \"api-connectivity\",\n status: \"pass\",\n message: \"API connectivity verified\",\n });\n } catch (error) {\n if (error instanceof AuthError) {\n results.push({\n name: \"auth\",\n status: \"fail\",\n message: `Authentication: ${error.message}`,\n suggestion: error.suggestion,\n });\n } else {\n results.push({\n name: \"api-connectivity\",\n status: \"fail\",\n message: \"API connectivity failed\",\n suggestion: \"Check your network connection and credentials\",\n });\n }\n }\n\n // ---------------------------------------------------------------------------\n // Output\n // ---------------------------------------------------------------------------\n\n const errors = results.filter((r) => r.status === \"fail\").length;\n const warnings = results.filter((r) => r.status === \"warn\").length;\n const passed = results.filter((r) => r.status === \"pass\").length;\n\n if (jsonMode) {\n console.log(\n JSON.stringify({ success: errors === 0, errors, warnings, checks: results }, null, 2),\n );\n if (errors > 0) process.exit(1);\n return;\n }\n\n console.log(\"GPC Doctor\\n\");\n for (const r of results) {\n console.log(` ${icon(r.status)} ${r.message}`);\n if (r.suggestion && r.status !== \"pass\") {\n console.log(` ${r.suggestion}`);\n }\n }\n\n console.log(\n `\\n ${PASS} ${passed} passed ${WARN} ${warnings} warning${warnings !== 1 ? \"s\" : \"\"} ${FAIL} ${errors} failed`,\n );\n\n if (errors > 0) {\n console.log(\"\\nSome checks failed. Fix the issues above and run again.\");\n process.exit(1);\n } else if (warnings > 0) {\n console.log(\"\\nAll checks passed with warnings.\");\n } else {\n console.log(\"\\nAll checks passed!\");\n }\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getOutputFormat
|
|
4
|
+
} from "./chunk-ELXAK7GI.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/migrate.ts
|
|
7
|
+
import { access } from "fs/promises";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { loadConfig } from "@gpc-cli/config";
|
|
10
|
+
import {
|
|
11
|
+
detectFastlane,
|
|
12
|
+
generateMigrationPlan,
|
|
13
|
+
writeMigrationOutput,
|
|
14
|
+
formatOutput
|
|
15
|
+
} from "@gpc-cli/core";
|
|
16
|
+
var WARN = "\u26A0";
|
|
17
|
+
async function fileExists(path) {
|
|
18
|
+
try {
|
|
19
|
+
await access(path);
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function registerMigrateCommands(program) {
|
|
26
|
+
const migrate = program.command("migrate").description("Migrate from other tools to GPC");
|
|
27
|
+
migrate.command("fastlane").description("Migrate from Fastlane to GPC").option("--dir <path>", "Directory containing Fastlane files", ".").option("--output <path>", "Output directory for migration files", ".").option("--dry-run", "Preview migration plan without writing any files").action(async (options) => {
|
|
28
|
+
const config = await loadConfig();
|
|
29
|
+
const format = getOutputFormat(program, config);
|
|
30
|
+
const dryRun = options.dryRun ?? false;
|
|
31
|
+
try {
|
|
32
|
+
const detection = await detectFastlane(options.dir);
|
|
33
|
+
if (format === "json") {
|
|
34
|
+
if (!detection.hasFastfile && !detection.hasAppfile && !detection.hasMetadata) {
|
|
35
|
+
console.log(formatOutput({ detection, plan: null, files: [] }, format));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const plan2 = generateMigrationPlan(detection);
|
|
39
|
+
if (!dryRun) {
|
|
40
|
+
const files2 = await writeMigrationOutput(plan2, options.output);
|
|
41
|
+
console.log(formatOutput({ detection, plan: plan2, files: files2 }, format));
|
|
42
|
+
} else {
|
|
43
|
+
console.log(formatOutput({ detection, plan: plan2, files: [] }, format));
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
console.log("Fastlane Detection Results:\n");
|
|
48
|
+
console.log(` Fastfile: ${detection.hasFastfile ? "found" : "not found"}`);
|
|
49
|
+
console.log(` Appfile: ${detection.hasAppfile ? "found" : "not found"}`);
|
|
50
|
+
console.log(` Metadata: ${detection.hasMetadata ? "found" : "not found"}`);
|
|
51
|
+
console.log(` Gemfile: ${detection.hasGemfile ? "found" : "not found"}`);
|
|
52
|
+
if (detection.packageName) {
|
|
53
|
+
console.log(` Package: ${detection.packageName}`);
|
|
54
|
+
}
|
|
55
|
+
if (detection.lanes.length > 0) {
|
|
56
|
+
console.log(`
|
|
57
|
+
Lanes (${detection.lanes.length}):`);
|
|
58
|
+
for (const lane of detection.lanes) {
|
|
59
|
+
const equiv = lane.gpcEquivalent ? ` \u2192 ${lane.gpcEquivalent}` : " (no equivalent)";
|
|
60
|
+
console.log(` ${lane.name}${equiv}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (detection.metadataLanguages.length > 0) {
|
|
64
|
+
console.log(`
|
|
65
|
+
Metadata languages: ${detection.metadataLanguages.join(", ")}`);
|
|
66
|
+
}
|
|
67
|
+
if (detection.parseWarnings.length > 0) {
|
|
68
|
+
console.log("");
|
|
69
|
+
for (const w of detection.parseWarnings) {
|
|
70
|
+
console.log(` ${WARN} ${w}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!detection.hasFastfile && !detection.hasAppfile && !detection.hasMetadata) {
|
|
74
|
+
console.log("\nNo Fastlane files detected in this directory. Nothing to migrate.");
|
|
75
|
+
console.log(" Try: gpc migrate fastlane --dir <path-to-your-android-project>");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const plan = generateMigrationPlan(detection);
|
|
79
|
+
if (dryRun) {
|
|
80
|
+
console.log("\nMigration Plan (dry run \u2014 nothing written):\n");
|
|
81
|
+
if (plan.warnings.length > 0) {
|
|
82
|
+
console.log("Warnings:");
|
|
83
|
+
for (const w of plan.warnings) {
|
|
84
|
+
console.log(` ${WARN} ${w}`);
|
|
85
|
+
}
|
|
86
|
+
console.log("");
|
|
87
|
+
}
|
|
88
|
+
console.log("Checklist:");
|
|
89
|
+
for (const item of plan.checklist) {
|
|
90
|
+
console.log(` [ ] ${item}`);
|
|
91
|
+
}
|
|
92
|
+
if (Object.keys(plan.config).length > 0) {
|
|
93
|
+
console.log("\n.gpcrc.json (would be written):");
|
|
94
|
+
console.log(JSON.stringify(plan.config, null, 2).replace(/^/gm, " "));
|
|
95
|
+
}
|
|
96
|
+
console.log(
|
|
97
|
+
"\nRun without --dry-run to write MIGRATION.md" + (Object.keys(plan.config).length > 0 ? " and .gpcrc.json" : "") + "."
|
|
98
|
+
);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (Object.keys(plan.config).length > 0 && await fileExists(join(options.output, ".gpcrc.json"))) {
|
|
102
|
+
console.log(
|
|
103
|
+
`
|
|
104
|
+
${WARN} .gpcrc.json already exists in ${options.output} \u2014 it will be overwritten.`
|
|
105
|
+
);
|
|
106
|
+
console.log(" Use --dry-run to preview the new config first.");
|
|
107
|
+
}
|
|
108
|
+
const files = await writeMigrationOutput(plan, options.output);
|
|
109
|
+
console.log("\nMigration files written:");
|
|
110
|
+
for (const file of files) {
|
|
111
|
+
console.log(` ${file}`);
|
|
112
|
+
}
|
|
113
|
+
if (plan.warnings.length > 0) {
|
|
114
|
+
console.log("\nWarnings:");
|
|
115
|
+
for (const w of plan.warnings) {
|
|
116
|
+
console.log(` ${WARN} ${w}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
console.log("\nMigration Checklist:");
|
|
120
|
+
for (const item of plan.checklist) {
|
|
121
|
+
console.log(` [ ] ${item}`);
|
|
122
|
+
}
|
|
123
|
+
console.log(
|
|
124
|
+
"\nNext step: open MIGRATION.md and work through the checklist. Run `gpc doctor` when done."
|
|
125
|
+
);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
registerMigrateCommands
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=migrate-XQV7P4R7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/migrate.ts"],"sourcesContent":["import { access } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport {\n detectFastlane,\n generateMigrationPlan,\n writeMigrationOutput,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\nconst WARN = \"\\u26A0\";\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function registerMigrateCommands(program: Command): void {\n const migrate = program.command(\"migrate\").description(\"Migrate from other tools to GPC\");\n\n migrate\n .command(\"fastlane\")\n .description(\"Migrate from Fastlane to GPC\")\n .option(\"--dir <path>\", \"Directory containing Fastlane files\", \".\")\n .option(\"--output <path>\", \"Output directory for migration files\", \".\")\n .option(\"--dry-run\", \"Preview migration plan without writing any files\")\n .action(async (options: { dir: string; output: string; dryRun?: boolean }) => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n const dryRun = options.dryRun ?? false;\n\n try {\n const detection = await detectFastlane(options.dir);\n\n if (format === \"json\") {\n if (\n !detection.hasFastfile &&\n !detection.hasAppfile &&\n !detection.hasMetadata\n ) {\n console.log(formatOutput({ detection, plan: null, files: [] }, format));\n return;\n }\n const plan = generateMigrationPlan(detection);\n if (!dryRun) {\n const files = await writeMigrationOutput(plan, options.output);\n console.log(formatOutput({ detection, plan, files }, format));\n } else {\n console.log(formatOutput({ detection, plan, files: [] }, format));\n }\n return;\n }\n\n // Human-readable output\n console.log(\"Fastlane Detection Results:\\n\");\n console.log(` Fastfile: ${detection.hasFastfile ? \"found\" : \"not found\"}`);\n console.log(` Appfile: ${detection.hasAppfile ? \"found\" : \"not found\"}`);\n console.log(` Metadata: ${detection.hasMetadata ? \"found\" : \"not found\"}`);\n console.log(` Gemfile: ${detection.hasGemfile ? \"found\" : \"not found\"}`);\n\n if (detection.packageName) {\n console.log(` Package: ${detection.packageName}`);\n }\n\n if (detection.lanes.length > 0) {\n console.log(`\\n Lanes (${detection.lanes.length}):`);\n for (const lane of detection.lanes) {\n const equiv = lane.gpcEquivalent ? ` → ${lane.gpcEquivalent}` : \" (no equivalent)\";\n console.log(` ${lane.name}${equiv}`);\n }\n }\n\n if (detection.metadataLanguages.length > 0) {\n console.log(`\\n Metadata languages: ${detection.metadataLanguages.join(\", \")}`);\n }\n\n if (detection.parseWarnings.length > 0) {\n console.log(\"\");\n for (const w of detection.parseWarnings) {\n console.log(` ${WARN} ${w}`);\n }\n }\n\n if (\n !detection.hasFastfile &&\n !detection.hasAppfile &&\n !detection.hasMetadata\n ) {\n console.log(\"\\nNo Fastlane files detected in this directory. Nothing to migrate.\");\n console.log(\" Try: gpc migrate fastlane --dir <path-to-your-android-project>\");\n return;\n }\n\n const plan = generateMigrationPlan(detection);\n\n if (dryRun) {\n console.log(\"\\nMigration Plan (dry run — nothing written):\\n\");\n\n if (plan.warnings.length > 0) {\n console.log(\"Warnings:\");\n for (const w of plan.warnings) {\n console.log(` ${WARN} ${w}`);\n }\n console.log(\"\");\n }\n\n console.log(\"Checklist:\");\n for (const item of plan.checklist) {\n console.log(` [ ] ${item}`);\n }\n\n if (Object.keys(plan.config).length > 0) {\n console.log(\"\\n.gpcrc.json (would be written):\");\n console.log(JSON.stringify(plan.config, null, 2).replace(/^/gm, \" \"));\n }\n\n console.log(\n \"\\nRun without --dry-run to write MIGRATION.md\" +\n (Object.keys(plan.config).length > 0 ? \" and .gpcrc.json\" : \"\") +\n \".\",\n );\n return;\n }\n\n // Conflict check — warn before clobbering existing .gpcrc.json\n if (\n Object.keys(plan.config).length > 0 &&\n (await fileExists(join(options.output, \".gpcrc.json\")))\n ) {\n console.log(\n `\\n${WARN} .gpcrc.json already exists in ${options.output} — it will be overwritten.`,\n );\n console.log(\" Use --dry-run to preview the new config first.\");\n }\n\n const files = await writeMigrationOutput(plan, options.output);\n\n console.log(\"\\nMigration files written:\");\n for (const file of files) {\n console.log(` ${file}`);\n }\n\n if (plan.warnings.length > 0) {\n console.log(\"\\nWarnings:\");\n for (const w of plan.warnings) {\n console.log(` ${WARN} ${w}`);\n }\n }\n\n console.log(\"\\nMigration Checklist:\");\n for (const item of plan.checklist) {\n console.log(` [ ] ${item}`);\n }\n\n console.log(\n \"\\nNext step: open MIGRATION.md and work through the checklist. Run `gpc doctor` when done.\",\n );\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n });\n}\n"],"mappings":"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","names":["plan","files"]}
|
|
@@ -16,8 +16,16 @@ import { appendFile } from "fs/promises";
|
|
|
16
16
|
import { loadConfig, getCacheDir } from "@gpc-cli/config";
|
|
17
17
|
import { resolveAuth } from "@gpc-cli/auth";
|
|
18
18
|
import { createApiClient } from "@gpc-cli/api";
|
|
19
|
-
import {
|
|
20
|
-
|
|
19
|
+
import {
|
|
20
|
+
publish,
|
|
21
|
+
generateNotesFromGit,
|
|
22
|
+
writeAuditLog,
|
|
23
|
+
createAuditEntry,
|
|
24
|
+
formatOutput
|
|
25
|
+
} from "@gpc-cli/core";
|
|
26
|
+
var PASS = "\u2713";
|
|
27
|
+
var FAIL = "\u2717";
|
|
28
|
+
var WARN = "\u26A0";
|
|
21
29
|
function resolvePackageName(packageArg, config) {
|
|
22
30
|
const name = packageArg || config.app;
|
|
23
31
|
if (!name) {
|
|
@@ -26,11 +34,67 @@ function resolvePackageName(packageArg, config) {
|
|
|
26
34
|
}
|
|
27
35
|
return name;
|
|
28
36
|
}
|
|
37
|
+
function formatChecks(checks) {
|
|
38
|
+
return checks.map((c) => ` ${c.passed ? PASS : FAIL} ${c.message}`);
|
|
39
|
+
}
|
|
40
|
+
function formatValidationOutput(result, format) {
|
|
41
|
+
if (format !== "table") {
|
|
42
|
+
return formatOutput({ success: false, validation: result.validation }, format);
|
|
43
|
+
}
|
|
44
|
+
const lines = ["Validation failed:\n", ...formatChecks(result.validation.checks)];
|
|
45
|
+
for (const w of result.validation.warnings) {
|
|
46
|
+
lines.push(` ${WARN} ${w}`);
|
|
47
|
+
}
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
function formatPublishOutput(result, format) {
|
|
51
|
+
if (format !== "table") return formatOutput(result, format);
|
|
52
|
+
const upload = result.upload;
|
|
53
|
+
const rollout = upload.status === "inProgress" && "userFraction" in upload ? ` (${Math.round(Number(upload["userFraction"]) * 100)}% rollout)` : "";
|
|
54
|
+
const lines = [
|
|
55
|
+
"Published successfully\n",
|
|
56
|
+
...formatChecks(result.validation.checks)
|
|
57
|
+
];
|
|
58
|
+
for (const w of result.validation.warnings) {
|
|
59
|
+
lines.push(` ${WARN} ${w}`);
|
|
60
|
+
}
|
|
61
|
+
lines.push("");
|
|
62
|
+
lines.push(` versionCode ${upload.versionCode}`);
|
|
63
|
+
lines.push(` track ${upload.track}`);
|
|
64
|
+
lines.push(` status ${upload.status}${rollout}`);
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
function formatDryRunOutput(result, format) {
|
|
68
|
+
if (format !== "table") return formatOutput(result, format);
|
|
69
|
+
const lines = [
|
|
70
|
+
"Dry run \u2014 no changes made\n",
|
|
71
|
+
...formatChecks(result.validation.checks)
|
|
72
|
+
];
|
|
73
|
+
for (const w of result.validation.warnings) {
|
|
74
|
+
lines.push(` ${WARN} ${w}`);
|
|
75
|
+
}
|
|
76
|
+
const u = result.upload;
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push(` Track ${u.track}`);
|
|
79
|
+
if (u.currentReleases.length === 0) {
|
|
80
|
+
lines.push(` Current (no releases on this track)`);
|
|
81
|
+
} else {
|
|
82
|
+
for (const r of u.currentReleases) {
|
|
83
|
+
const fraction = r.userFraction !== void 0 ? ` (${Math.round(r.userFraction * 100)}%)` : "";
|
|
84
|
+
lines.push(` Current ${r.versionCodes.join(", ")} \xB7 ${r.status}${fraction}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const plannedFraction = u.plannedRelease.userFraction !== void 0 ? ` (${Math.round(u.plannedRelease.userFraction * 100)}%)` : "";
|
|
88
|
+
lines.push(` Would be (new bundle) \xB7 ${u.plannedRelease.status}${plannedFraction}`);
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
|
29
91
|
function registerPublishCommand(program) {
|
|
30
92
|
program.command("publish <file>").description("Validate, upload, and release in one step").option("--track <track>", "Target track", "internal").option("--rollout <percent>", "Staged rollout percentage (1-100)").option("--notes <text>", "Release notes (en-US)").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("--name <name>", "Release name").option("--mapping <file>", "ProGuard/R8 mapping file for deobfuscation").option("--retry-log <path>", "Write retry log entries to file (JSONL)").action(async (file, options) => {
|
|
31
93
|
const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);
|
|
32
94
|
if (noteSources.length > 1) {
|
|
33
|
-
console.error(
|
|
95
|
+
console.error(
|
|
96
|
+
"Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one."
|
|
97
|
+
);
|
|
34
98
|
process.exit(2);
|
|
35
99
|
}
|
|
36
100
|
const config = await loadConfig();
|
|
@@ -55,9 +119,23 @@ function registerPublishCommand(program) {
|
|
|
55
119
|
if (notes) options.notes = notes;
|
|
56
120
|
}
|
|
57
121
|
}
|
|
122
|
+
if (options.rollout !== void 0) {
|
|
123
|
+
const rollout = Number(options.rollout);
|
|
124
|
+
if (!Number.isFinite(rollout) || rollout < 1 || rollout > 100) {
|
|
125
|
+
console.error(
|
|
126
|
+
`Error: --rollout must be a number between 1 and 100 (got: ${options.rollout})`
|
|
127
|
+
);
|
|
128
|
+
process.exit(2);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
58
131
|
if (options.notesFromGit) {
|
|
59
132
|
const gitNotes = await generateNotesFromGit({ since: options.since });
|
|
60
133
|
options.notes = gitNotes.text;
|
|
134
|
+
if (gitNotes.truncated) {
|
|
135
|
+
console.error(
|
|
136
|
+
`${WARN} Release notes truncated to 500 characters (${gitNotes.commitCount} commits from ${gitNotes.since}).`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
61
139
|
}
|
|
62
140
|
let onRetry;
|
|
63
141
|
if (options.retryLog) {
|
|
@@ -82,7 +160,7 @@ function registerPublishCommand(program) {
|
|
|
82
160
|
mappingFile: options.mapping,
|
|
83
161
|
dryRun: true
|
|
84
162
|
});
|
|
85
|
-
console.log(
|
|
163
|
+
console.log(formatDryRunOutput(result, format));
|
|
86
164
|
} catch (error) {
|
|
87
165
|
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
88
166
|
process.exit(4);
|
|
@@ -91,11 +169,7 @@ function registerPublishCommand(program) {
|
|
|
91
169
|
}
|
|
92
170
|
const auditEntry = createAuditEntry(
|
|
93
171
|
"publish",
|
|
94
|
-
{
|
|
95
|
-
file,
|
|
96
|
-
track: options.track,
|
|
97
|
-
rollout: options.rollout
|
|
98
|
-
},
|
|
172
|
+
{ file, track: options.track, rollout: options.rollout },
|
|
99
173
|
packageName
|
|
100
174
|
);
|
|
101
175
|
try {
|
|
@@ -108,16 +182,12 @@ function registerPublishCommand(program) {
|
|
|
108
182
|
mappingFile: options.mapping
|
|
109
183
|
});
|
|
110
184
|
if (!result.upload) {
|
|
111
|
-
console.
|
|
112
|
-
for (const check of result.validation.checks) {
|
|
113
|
-
const icon = check.passed ? "\u2713" : "\u2717";
|
|
114
|
-
console.error(` ${icon} ${check.name}: ${check.message}`);
|
|
115
|
-
}
|
|
185
|
+
console.log(formatValidationOutput(result, format));
|
|
116
186
|
auditEntry.success = false;
|
|
117
187
|
auditEntry.error = "Validation failed";
|
|
118
188
|
process.exit(1);
|
|
119
189
|
}
|
|
120
|
-
console.log(
|
|
190
|
+
console.log(formatPublishOutput(result, format));
|
|
121
191
|
auditEntry.success = true;
|
|
122
192
|
} catch (error) {
|
|
123
193
|
auditEntry.success = false;
|
|
@@ -134,4 +204,4 @@ function registerPublishCommand(program) {
|
|
|
134
204
|
export {
|
|
135
205
|
registerPublishCommand
|
|
136
206
|
};
|
|
137
|
-
//# sourceMappingURL=publish-
|
|
207
|
+
//# sourceMappingURL=publish-26ZPS7XX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/publish.ts"],"sourcesContent":["import { appendFile } from \"node:fs/promises\";\nimport type { GpcConfig, OutputFormat } from \"@gpc-cli/config\";\nimport type { Command } from \"commander\";\nimport { loadConfig, getCacheDir } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient } from \"@gpc-cli/api\";\nimport type { RetryLogEntry } from \"@gpc-cli/api\";\nimport {\n publish,\n generateNotesFromGit,\n writeAuditLog,\n createAuditEntry,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport type { PublishResult, DryRunPublishResult } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun } from \"../dry-run.js\";\nimport { isInteractive, promptSelect, promptInput } from \"../prompt.js\";\n\nconst PASS = \"\\u2713\";\nconst FAIL = \"\\u2717\";\nconst WARN = \"\\u26A0\";\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\n// ---------------------------------------------------------------------------\n// Output formatters\n// ---------------------------------------------------------------------------\n\nfunction formatChecks(checks: { name: string; passed: boolean; message: string }[]): string[] {\n return checks.map((c) => ` ${c.passed ? PASS : FAIL} ${c.message}`);\n}\n\nfunction formatValidationOutput(result: PublishResult, format: OutputFormat): string {\n if (format !== \"table\") {\n return formatOutput({ success: false, validation: result.validation }, format);\n }\n const lines = [\"Validation failed:\\n\", ...formatChecks(result.validation.checks)];\n for (const w of result.validation.warnings) {\n lines.push(` ${WARN} ${w}`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction formatPublishOutput(result: PublishResult, format: OutputFormat): string {\n if (format !== \"table\") return formatOutput(result, format);\n\n const upload = result.upload!;\n const rollout =\n upload.status === \"inProgress\" && \"userFraction\" in upload\n ? ` (${Math.round(Number((upload as Record<string, unknown>)[\"userFraction\"]) * 100)}% rollout)`\n : \"\";\n\n const lines = [\n \"Published successfully\\n\",\n ...formatChecks(result.validation.checks),\n ];\n for (const w of result.validation.warnings) {\n lines.push(` ${WARN} ${w}`);\n }\n lines.push(\"\");\n lines.push(` versionCode ${upload.versionCode}`);\n lines.push(` track ${upload.track}`);\n lines.push(` status ${upload.status}${rollout}`);\n return lines.join(\"\\n\");\n}\n\nfunction formatDryRunOutput(result: DryRunPublishResult, format: OutputFormat): string {\n if (format !== \"table\") return formatOutput(result, format);\n\n const lines = [\n \"Dry run — no changes made\\n\",\n ...formatChecks(result.validation.checks),\n ];\n for (const w of result.validation.warnings) {\n lines.push(` ${WARN} ${w}`);\n }\n\n const u = result.upload;\n lines.push(\"\");\n lines.push(` Track ${u.track}`);\n\n if (u.currentReleases.length === 0) {\n lines.push(` Current (no releases on this track)`);\n } else {\n for (const r of u.currentReleases) {\n const fraction = r.userFraction !== undefined ? ` (${Math.round(r.userFraction * 100)}%)` : \"\";\n lines.push(` Current ${r.versionCodes.join(\", \")} · ${r.status}${fraction}`);\n }\n }\n const plannedFraction =\n u.plannedRelease.userFraction !== undefined\n ? ` (${Math.round(u.plannedRelease.userFraction * 100)}%)`\n : \"\";\n lines.push(` Would be (new bundle) · ${u.plannedRelease.status}${plannedFraction}`);\n return lines.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Command\n// ---------------------------------------------------------------------------\n\nexport function registerPublishCommand(program: Command): void {\n program\n .command(\"publish <file>\")\n .description(\"Validate, upload, and release in one step\")\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(\"--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(\"--name <name>\", \"Release name\")\n .option(\"--mapping <file>\", \"ProGuard/R8 mapping file for deobfuscation\")\n .option(\"--retry-log <path>\", \"Write retry log entries to file (JSONL)\")\n .action(async (file: string, options) => {\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\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 && !options.notesFromGit) {\n const notes = await promptInput(\"Release notes (en-US, blank to skip)\");\n if (notes) options.notes = notes;\n }\n }\n\n // Rollout range guard\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 // Resolve git-based release notes before calling publish\n if (options.notesFromGit) {\n const gitNotes = await generateNotesFromGit({ since: options.since });\n options.notes = gitNotes.text;\n if (gitNotes.truncated) {\n console.error(\n `${WARN} Release notes truncated to 500 characters (${gitNotes.commitCount} commits from ${gitNotes.since}).`,\n );\n }\n }\n\n let onRetry: ((entry: RetryLogEntry) => void) | undefined;\n if (options.retryLog) {\n onRetry = (entry: RetryLogEntry) => {\n appendFile(options.retryLog, JSON.stringify(entry) + \"\\n\").catch(() => {});\n };\n }\n\n const auth = await resolveAuth({\n serviceAccountPath: config.auth?.serviceAccount,\n cachePath: getCacheDir(),\n });\n const client = createApiClient({ auth, onRetry });\n\n if (isDryRun(program)) {\n try {\n const result = await publish(client, packageName, file, {\n track: options.track,\n rolloutPercent: options.rollout ? Number(options.rollout) : undefined,\n notes: options.notes,\n notesDir: options.notesDir,\n releaseName: options.name,\n mappingFile: options.mapping,\n dryRun: true,\n });\n console.log(formatDryRunOutput(result as DryRunPublishResult, 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 \"publish\",\n { file, track: options.track, rollout: options.rollout },\n packageName,\n );\n\n try {\n const result = await publish(client, packageName, file, {\n track: options.track,\n rolloutPercent: options.rollout ? Number(options.rollout) : undefined,\n notes: options.notes,\n notesDir: options.notesDir,\n releaseName: options.name,\n mappingFile: options.mapping,\n });\n\n if (!result.upload) {\n console.log(formatValidationOutput(result as PublishResult, format));\n auditEntry.success = false;\n auditEntry.error = \"Validation failed\";\n process.exit(1);\n }\n\n console.log(formatPublishOutput(result as PublishResult, format));\n auditEntry.success = true;\n } catch (error) {\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"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getOutputFormat
|
|
4
|
+
} from "./chunk-ELXAK7GI.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/status.ts
|
|
7
|
+
import { loadConfig } from "@gpc-cli/config";
|
|
8
|
+
import { resolveAuth } from "@gpc-cli/auth";
|
|
9
|
+
import { createApiClient, createReportingClient } from "@gpc-cli/api";
|
|
10
|
+
import {
|
|
11
|
+
getAppStatus,
|
|
12
|
+
formatStatusTable,
|
|
13
|
+
loadStatusCache,
|
|
14
|
+
saveStatusCache,
|
|
15
|
+
statusHasBreach,
|
|
16
|
+
formatOutput
|
|
17
|
+
} from "@gpc-cli/core";
|
|
18
|
+
function registerStatusCommand(program) {
|
|
19
|
+
program.command("status").description("Unified app health snapshot: releases, vitals, and reviews").option("--days <n>", "Vitals window in days", (v) => parseInt(v, 10), 7).option("--cached", "Use last fetched data, skip API calls").option("--refresh", "Force live fetch, ignore cache TTL").option("--ttl <seconds>", "Cache TTL in seconds", (v) => parseInt(v, 10), 3600).action(async (opts) => {
|
|
20
|
+
const config = await loadConfig();
|
|
21
|
+
const packageName = program.opts()["app"] || config.app;
|
|
22
|
+
if (!packageName) {
|
|
23
|
+
console.error(
|
|
24
|
+
"Error: No package name. Use --app <package> or gpc config set app <package>"
|
|
25
|
+
);
|
|
26
|
+
process.exit(2);
|
|
27
|
+
}
|
|
28
|
+
const format = getOutputFormat(program, config);
|
|
29
|
+
try {
|
|
30
|
+
if (opts.cached) {
|
|
31
|
+
const cached = await loadStatusCache(packageName, opts.ttl);
|
|
32
|
+
if (!cached) {
|
|
33
|
+
console.error(
|
|
34
|
+
"Error: No cached status found. Run without --cached to fetch live data."
|
|
35
|
+
);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
if (format === "json") {
|
|
39
|
+
console.log(formatOutput(cached, format));
|
|
40
|
+
} else {
|
|
41
|
+
console.log(formatStatusTable(cached));
|
|
42
|
+
}
|
|
43
|
+
if (statusHasBreach(cached)) process.exit(6);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!opts.refresh) {
|
|
47
|
+
const cached = await loadStatusCache(packageName, opts.ttl);
|
|
48
|
+
if (cached) {
|
|
49
|
+
if (format === "json") {
|
|
50
|
+
console.log(formatOutput(cached, format));
|
|
51
|
+
} else {
|
|
52
|
+
console.log(formatStatusTable(cached));
|
|
53
|
+
}
|
|
54
|
+
if (statusHasBreach(cached)) process.exit(6);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });
|
|
59
|
+
const client = createApiClient({ auth });
|
|
60
|
+
const reporting = createReportingClient({ auth });
|
|
61
|
+
const vitalThresholds = config["vitals"] ? (() => {
|
|
62
|
+
const vitals = config["vitals"];
|
|
63
|
+
const t = vitals["thresholds"];
|
|
64
|
+
const toThreshold = (v) => {
|
|
65
|
+
if (v === void 0 || v === null) return void 0;
|
|
66
|
+
const n = Number(v);
|
|
67
|
+
return isNaN(n) ? void 0 : n;
|
|
68
|
+
};
|
|
69
|
+
return t ? {
|
|
70
|
+
crashRate: toThreshold(t["crashRate"]),
|
|
71
|
+
anrRate: toThreshold(t["anrRate"]),
|
|
72
|
+
slowStartRate: toThreshold(t["slowStartRate"]),
|
|
73
|
+
slowRenderingRate: toThreshold(t["slowRenderingRate"])
|
|
74
|
+
} : void 0;
|
|
75
|
+
})() : void 0;
|
|
76
|
+
const status = await getAppStatus(client, reporting, packageName, {
|
|
77
|
+
days: opts.days,
|
|
78
|
+
vitalThresholds
|
|
79
|
+
});
|
|
80
|
+
await saveStatusCache(packageName, status, opts.ttl);
|
|
81
|
+
if (format === "json") {
|
|
82
|
+
console.log(formatOutput(status, format));
|
|
83
|
+
} else {
|
|
84
|
+
console.log(formatStatusTable(status));
|
|
85
|
+
}
|
|
86
|
+
if (statusHasBreach(status)) process.exit(6);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
89
|
+
process.exit(4);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
registerStatusCommand
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=status-5TOOZAGT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/status.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient, createReportingClient } from \"@gpc-cli/api\";\nimport {\n getAppStatus,\n formatStatusTable,\n loadStatusCache,\n saveStatusCache,\n statusHasBreach,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\nexport function registerStatusCommand(program: Command): void {\n program\n .command(\"status\")\n .description(\"Unified app health snapshot: releases, vitals, and reviews\")\n .option(\"--days <n>\", \"Vitals window in days\", (v) => parseInt(v, 10), 7)\n .option(\"--cached\", \"Use last fetched data, skip API calls\")\n .option(\"--refresh\", \"Force live fetch, ignore cache TTL\")\n .option(\"--ttl <seconds>\", \"Cache TTL in seconds\", (v) => parseInt(v, 10), 3600)\n .action(async (opts: { days: number; cached?: boolean; refresh?: boolean; ttl: number }) => {\n const config = await loadConfig();\n const packageName = program.opts()[\"app\"] || config.app;\n if (!packageName) {\n console.error(\n \"Error: No package name. Use --app <package> or gpc config set app <package>\",\n );\n process.exit(2);\n }\n\n const format = getOutputFormat(program, config);\n\n try {\n // --cached: read from cache, skip API\n if (opts.cached) {\n const cached = await loadStatusCache(packageName, opts.ttl);\n if (!cached) {\n console.error(\n \"Error: No cached status found. Run without --cached to fetch live data.\",\n );\n process.exit(1);\n }\n if (format === \"json\") {\n console.log(formatOutput(cached, format));\n } else {\n console.log(formatStatusTable(cached));\n }\n if (statusHasBreach(cached)) process.exit(6);\n return;\n }\n\n // Check cache unless --refresh\n if (!opts.refresh) {\n const cached = await loadStatusCache(packageName, opts.ttl);\n if (cached) {\n if (format === \"json\") {\n console.log(formatOutput(cached, format));\n } else {\n console.log(formatStatusTable(cached));\n }\n if (statusHasBreach(cached)) process.exit(6);\n return;\n }\n }\n\n // Live fetch\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n const client = createApiClient({ auth });\n const reporting = createReportingClient({ auth });\n\n const vitalThresholds = (config as Record<string, unknown>)[\"vitals\"]\n ? (() => {\n const vitals = (config as Record<string, unknown>)[\"vitals\"] as Record<\n string,\n unknown\n >;\n const t = vitals[\"thresholds\"] as Record<string, unknown> | undefined;\n const toThreshold = (v: unknown): number | undefined => {\n if (v === undefined || v === null) return undefined;\n const n = Number(v);\n return isNaN(n) ? undefined : n;\n };\n return t\n ? {\n crashRate: toThreshold(t[\"crashRate\"]),\n anrRate: toThreshold(t[\"anrRate\"]),\n slowStartRate: toThreshold(t[\"slowStartRate\"]),\n slowRenderingRate: toThreshold(t[\"slowRenderingRate\"]),\n }\n : undefined;\n })()\n : undefined;\n\n const status = await getAppStatus(client, reporting, packageName, {\n days: opts.days,\n vitalThresholds,\n });\n\n await saveStatusCache(packageName, status, opts.ttl);\n\n if (format === \"json\") {\n console.log(formatOutput(status, format));\n } else {\n console.log(formatStatusTable(status));\n }\n\n if (statusHasBreach(status)) process.exit(6);\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":[]}
|
|
@@ -32,11 +32,28 @@ function registerValidateCommand(program) {
|
|
|
32
32
|
track: options.track,
|
|
33
33
|
notes
|
|
34
34
|
});
|
|
35
|
-
|
|
35
|
+
if (format === "json") {
|
|
36
|
+
console.log(formatOutput(result, format));
|
|
37
|
+
} else {
|
|
38
|
+
const checkRows = result.checks.map((c) => ({
|
|
39
|
+
check: c.name,
|
|
40
|
+
passed: c.passed ? "pass" : "FAIL",
|
|
41
|
+
message: c.message
|
|
42
|
+
}));
|
|
43
|
+
console.log(formatOutput(checkRows, format));
|
|
44
|
+
if (result.warnings.length > 0) {
|
|
45
|
+
console.log("\nWarnings:");
|
|
46
|
+
for (const w of result.warnings) {
|
|
47
|
+
console.log(` ${w}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
console.log(`
|
|
51
|
+
${result.valid ? "Valid" : "Invalid"}`);
|
|
52
|
+
}
|
|
36
53
|
process.exit(result.valid ? 0 : 1);
|
|
37
54
|
});
|
|
38
55
|
}
|
|
39
56
|
export {
|
|
40
57
|
registerValidateCommand
|
|
41
58
|
};
|
|
42
|
-
//# sourceMappingURL=validate-
|
|
59
|
+
//# sourceMappingURL=validate-MHLPENCM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/validate.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { validatePreSubmission, readReleaseNotesFromDir } from \"@gpc-cli/core\";\nimport { formatOutput } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\nexport function registerValidateCommand(program: Command): void {\n program\n .command(\"validate <file>\")\n .description(\"Pre-submission validation checks\")\n .option(\"--track <track>\", \"Target track to validate\")\n .option(\"--mapping <file>\", \"ProGuard/R8 mapping file\")\n .option(\"--notes <text>\", \"Release notes (en-US)\")\n .option(\"--notes-dir <dir>\", \"Read release notes from directory (<dir>/<lang>.txt)\")\n .action(async (file: string, options) => {\n if (options.notes && options.notesDir) {\n console.error(\"Error: Cannot use both --notes and --notes-dir\");\n process.exit(2);\n }\n\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n\n let notes: { language: string; text: string }[] | undefined;\n if (options.notesDir) {\n try {\n notes = await readReleaseNotesFromDir(options.notesDir);\n } catch (err) {\n console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n } else if (options.notes) {\n notes = [{ language: \"en-US\", text: options.notes }];\n }\n\n const result = await validatePreSubmission({\n filePath: file,\n mappingFile: options.mapping,\n track: options.track,\n notes,\n });\n\n if (format === \"json\") {\n console.log(formatOutput(result, format));\n } else {\n const checkRows = result.checks.map((c) => ({\n check: c.name,\n passed: c.passed ? \"pass\" : \"FAIL\",\n message: c.message,\n }));\n console.log(formatOutput(checkRows, format));\n if (result.warnings.length > 0) {\n console.log(\"\\nWarnings:\");\n for (const w of result.warnings) {\n console.log(` ${w}`);\n }\n }\n console.log(`\\n${result.valid ? \"Valid\" : \"Invalid\"}`);\n }\n process.exit(result.valid ? 0 : 1);\n });\n}\n"],"mappings":";;;;;;AACA,SAAS,kBAAkB;AAC3B,SAAS,uBAAuB,+BAA+B;AAC/D,SAAS,oBAAoB;AAGtB,SAAS,wBAAwB,SAAwB;AAC9D,UACG,QAAQ,iBAAiB,EACzB,YAAY,kCAAkC,EAC9C,OAAO,mBAAmB,0BAA0B,EACpD,OAAO,oBAAoB,0BAA0B,EACrD,OAAO,kBAAkB,uBAAuB,EAChD,OAAO,qBAAqB,sDAAsD,EAClF,OAAO,OAAO,MAAc,YAAY;AACvC,QAAI,QAAQ,SAAS,QAAQ,UAAU;AACrC,cAAQ,MAAM,gDAAgD;AAC9D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,QAAI;AACJ,QAAI,QAAQ,UAAU;AACpB,UAAI;AACF,gBAAQ,MAAM,wBAAwB,QAAQ,QAAQ;AAAA,MACxD,SAAS,KAAK;AACZ,gBAAQ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAC1E,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,WAAW,QAAQ,OAAO;AACxB,cAAQ,CAAC,EAAE,UAAU,SAAS,MAAM,QAAQ,MAAM,CAAC;AAAA,IACrD;AAEA,UAAM,SAAS,MAAM,sBAAsB;AAAA,MACzC,UAAU;AAAA,MACV,aAAa,QAAQ;AAAA,MACrB,OAAO,QAAQ;AAAA,MACf;AAAA,IACF,CAAC;AAED,QAAI,WAAW,QAAQ;AACrB,cAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,IAC1C,OAAO;AACL,YAAM,YAAY,OAAO,OAAO,IAAI,CAAC,OAAO;AAAA,QAC1C,OAAO,EAAE;AAAA,QACT,QAAQ,EAAE,SAAS,SAAS;AAAA,QAC5B,SAAS,EAAE;AAAA,MACb,EAAE;AACF,cAAQ,IAAI,aAAa,WAAW,MAAM,CAAC;AAC3C,UAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,gBAAQ,IAAI,aAAa;AACzB,mBAAW,KAAK,OAAO,UAAU;AAC/B,kBAAQ,IAAI,KAAK,CAAC,EAAE;AAAA,QACtB;AAAA,MACF;AACA,cAAQ,IAAI;AAAA,EAAK,OAAO,QAAQ,UAAU,SAAS,EAAE;AAAA,IACvD;AACA,YAAQ,KAAK,OAAO,QAAQ,IAAI,CAAC;AAAA,EACnC,CAAC;AACL;","names":[]}
|
|
@@ -223,7 +223,7 @@ function registerVitalsCommands(program) {
|
|
|
223
223
|
battery: "excessiveWakeupRateMetricSet",
|
|
224
224
|
memory: "stuckBackgroundWakelockRateMetricSet"
|
|
225
225
|
};
|
|
226
|
-
vitals.command("compare <metric>").description("Compare metric trend: this period vs previous period").option("--days <n>", "Period length in days", parseInt, 7).action(async (metric, options) => {
|
|
226
|
+
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) => {
|
|
227
227
|
const metricSet = METRIC_MAP[metric];
|
|
228
228
|
if (!metricSet) {
|
|
229
229
|
console.error(
|
|
@@ -247,4 +247,4 @@ function registerVitalsCommands(program) {
|
|
|
247
247
|
export {
|
|
248
248
|
registerVitalsCommands
|
|
249
249
|
};
|
|
250
|
-
//# sourceMappingURL=vitals-
|
|
250
|
+
//# sourceMappingURL=vitals-KSNAVN5F.js.map
|