@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 CHANGED
@@ -1,17 +1,29 @@
1
1
  # @gpc-cli/cli
2
2
 
3
- Ship Android apps from your terminal. The complete CLI for Google Play Developer API v3.
3
+ <p align="center"><strong>Ship Android apps from your terminal.</strong></p>
4
+
5
+ <p align="center">
6
+ The complete CLI for Google Play — 187 API endpoints, one tool.<br>
7
+ Releases, rollouts, metadata, vitals, reviews, subscriptions, reports, and more.
8
+ </p>
9
+
10
+ <p align="center">
11
+ <a href="https://www.npmjs.com/package/@gpc-cli/cli"><img src="https://img.shields.io/npm/v/@gpc-cli/cli?color=00D26A" alt="npm version"></a>
12
+ <a href="https://github.com/yasserstudio/gpc"><img src="https://img.shields.io/github/stars/yasserstudio/gpc" alt="GitHub Stars"></a>
13
+ <img src="https://img.shields.io/badge/Tests-1358_passing-00D26A" alt="Tests">
14
+ <img src="https://img.shields.io/badge/License-MIT-yellow" alt="License">
15
+ </p>
4
16
 
5
17
  ## Install
6
18
 
7
19
  ```bash
8
- # npm
20
+ # npm (includes plugin support)
9
21
  npm install -g @gpc-cli/cli
10
22
 
11
- # Homebrew
23
+ # Homebrew (macOS/Linux)
12
24
  brew install yasserstudio/tap/gpc
13
25
 
14
- # Standalone binary
26
+ # Standalone binary (no Node.js required)
15
27
  curl -fsSL https://raw.githubusercontent.com/yasserstudio/gpc/main/scripts/install.sh | sh
16
28
  ```
17
29
 
@@ -21,63 +33,74 @@ curl -fsSL https://raw.githubusercontent.com/yasserstudio/gpc/main/scripts/insta
21
33
  # Authenticate
22
34
  gpc auth login --service-account path/to/key.json
23
35
 
36
+ # App health at a glance — releases, vitals, and reviews in one command
37
+ gpc status
38
+
24
39
  # Upload and release
25
40
  gpc releases upload app.aab --track internal
26
41
 
27
42
  # Promote to production
28
43
  gpc releases promote --from internal --to production --rollout 10
29
44
 
30
- # Check app health
31
- gpc vitals overview
32
-
33
45
  # Monitor reviews
34
46
  gpc reviews list --stars 1-3 --since 7d
35
47
  ```
36
48
 
49
+ ## App Health at a Glance
50
+
51
+ ```
52
+ $ gpc status
53
+
54
+ App: com.example.myapp · My App (fetched 10:42:01 AM)
55
+
56
+ RELEASES
57
+ production v1.4.2 completed —
58
+ beta v1.5.0 inProgress 10%
59
+ internal v1.5.1 draft —
60
+
61
+ VITALS (last 7 days)
62
+ crashes 0.80% ✓ anr 0.20% ✓
63
+ slow starts 2.10% ✓ slow render 4.30% ⚠
64
+
65
+ REVIEWS (last 30 days)
66
+ ★ 4.6 142 new 89% positive ↑ from 4.4
67
+ ```
68
+
69
+ 6 parallel API calls, results in under 3 seconds. Results cached — `--cached` skips the network entirely.
70
+
37
71
  ## What You Get
38
72
 
39
- 187 API endpoints across 10 command groups:
73
+ 187 API endpoints across these command groups:
40
74
 
41
- | Command Group | Examples |
75
+ | Group | Examples |
42
76
  | ----------------- | -------------------------------------------------------------- |
43
77
  | **Releases** | `upload`, `promote`, `rollout increase/halt/resume`, `publish` |
44
78
  | **Listings** | `pull`, `push`, `images upload/delete`, Fastlane format |
45
79
  | **Reviews** | `list`, `reply`, `export --format csv` |
46
80
  | **Vitals** | `crashes`, `anr`, `startup`, `rendering`, `battery`, `memory` |
47
- | **Subscriptions** | `list`, `create`, `base-plans`, `offers` |
48
- | **IAP** | `list`, `create`, `sync --dir products/` |
49
- | **Purchases** | `get`, `acknowledge`, `cancel`, `refund` |
81
+ | **Bundle** | `analyze` (size breakdown), `compare` (size diff) |
82
+ | **Subscriptions** | `list`, `create`, `update`, `base-plans`, `offers` |
83
+ | **IAP** | `list`, `create`, `sync --dir products/`, `batch-get/update` |
84
+ | **Purchases** | `get`, `acknowledge`, `cancel`, `refund`, `voided list` |
50
85
  | **Reports** | `download financial`, `download stats` |
51
86
  | **Testers** | `add`, `remove`, `import --file testers.csv` |
52
87
  | **Users** | `invite`, `update`, `remove`, per-app grants |
53
88
 
54
- ## CI/CD
89
+ ## CI/CD Ready
55
90
 
56
- JSON output, semantic exit codes (0-6), env var config no wrapper scripts needed.
91
+ JSON output when piped. Formatted tables in your terminal. Semantic exit codes (0-6) your CI can react to.
57
92
 
58
93
  ```yaml
59
- - name: Install GPC
60
- run: npm install -g @gpc-cli/cli
61
-
62
94
  - name: Upload
63
95
  env:
64
96
  GPC_SERVICE_ACCOUNT: ${{ secrets.GPC_SERVICE_ACCOUNT }}
65
97
  GPC_APP: com.example.myapp
66
- run: gpc releases upload app.aab --track internal
98
+ run: |
99
+ npm install -g @gpc-cli/cli
100
+ gpc releases upload app.aab --track internal
67
101
  ```
68
102
 
69
- ## Output Formats
70
-
71
- GPC auto-detects your environment:
72
-
73
- - **Terminal:** formatted tables
74
- - **Piped/CI:** structured JSON
75
-
76
- Override with `--output json|yaml|markdown|table`.
77
-
78
- ## Documentation
79
-
80
- Full docs at **[yasserstudio.github.io/gpc](https://yasserstudio.github.io/gpc/)**
103
+ Every write operation supports `--dry-run`.
81
104
 
82
105
  ## Part of the GPC Monorepo
83
106
 
@@ -91,6 +114,10 @@ Full docs at **[yasserstudio.github.io/gpc](https://yasserstudio.github.io/gpc/)
91
114
  | [@gpc-cli/plugin-sdk](https://www.npmjs.com/package/@gpc-cli/plugin-sdk) | Plugin interface and lifecycle hooks |
92
115
  | [@gpc-cli/plugin-ci](https://www.npmjs.com/package/@gpc-cli/plugin-ci) | CI/CD helpers |
93
116
 
117
+ ## Documentation
118
+
119
+ Full docs at **[yasserstudio.github.io/gpc](https://yasserstudio.github.io/gpc/)**
120
+
94
121
  ## License
95
122
 
96
123
  MIT
package/dist/bin.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createProgram,
4
4
  handleCliError,
5
5
  loadPlugins
6
- } from "./chunk-CE2HXEJX.js";
6
+ } from "./chunk-P6TYWHCR.js";
7
7
 
8
8
  // src/networking.ts
9
9
  async function setupNetworking() {
@@ -28,6 +28,35 @@ function registerBundleCommands(program) {
28
28
  const analysis = await analyzeBundle(file);
29
29
  if (format === "json") {
30
30
  console.log(formatOutput(analysis, format));
31
+ } else if (format === "markdown") {
32
+ const moduleRows = analysis.modules.map((m) => ({
33
+ module: m.name,
34
+ compressed: formatSize(m.compressedSize),
35
+ uncompressed: formatSize(m.uncompressedSize),
36
+ entries: m.entries
37
+ }));
38
+ const categoryRows = analysis.categories.map((c) => ({
39
+ category: c.name,
40
+ compressed: formatSize(c.compressedSize),
41
+ uncompressed: formatSize(c.uncompressedSize),
42
+ entries: c.entries
43
+ }));
44
+ console.log(`## Bundle Analysis: \`${analysis.filePath}\``);
45
+ console.log();
46
+ console.log(`| Property | Value |`);
47
+ console.log(`| --- | --- |`);
48
+ console.log(`| Type | ${analysis.fileType.toUpperCase()} |`);
49
+ console.log(`| Total compressed | ${formatSize(analysis.totalCompressed)} |`);
50
+ console.log(`| Total uncompressed | ${formatSize(analysis.totalUncompressed)} |`);
51
+ console.log(`| Entries | ${analysis.entryCount} |`);
52
+ console.log();
53
+ console.log(`### Modules`);
54
+ console.log();
55
+ console.log(formatOutput(moduleRows, "markdown"));
56
+ console.log();
57
+ console.log(`### Categories`);
58
+ console.log();
59
+ console.log(formatOutput(categoryRows, "markdown"));
31
60
  } else {
32
61
  console.log(`
33
62
  File: ${analysis.filePath}`);
@@ -78,6 +107,39 @@ Threshold breached: ${formatSize(analysis.totalCompressed)} > ${opts.threshold}
78
107
  const comparison = compareBundles(before, after);
79
108
  if (format === "json") {
80
109
  console.log(formatOutput(comparison, format));
110
+ } else if (format === "markdown") {
111
+ const sign = comparison.sizeDelta >= 0 ? "+" : "";
112
+ const moduleRows = comparison.moduleDeltas.filter((m) => m.delta !== 0).map((m) => ({
113
+ module: m.module,
114
+ before: formatSize(m.before),
115
+ after: formatSize(m.after),
116
+ delta: formatDelta(m.delta)
117
+ }));
118
+ const categoryRows = comparison.categoryDeltas.filter((c) => c.delta !== 0).map((c) => ({
119
+ category: c.category,
120
+ before: formatSize(c.before),
121
+ after: formatSize(c.after),
122
+ delta: formatDelta(c.delta)
123
+ }));
124
+ console.log(`## Bundle Comparison`);
125
+ console.log();
126
+ console.log(`| | Path | Size |`);
127
+ console.log(`| --- | --- | --- |`);
128
+ console.log(`| Before | \`${comparison.before.path}\` | ${formatSize(comparison.before.totalCompressed)} |`);
129
+ console.log(`| After | \`${comparison.after.path}\` | ${formatSize(comparison.after.totalCompressed)} |`);
130
+ console.log(`| **Delta** | | **${sign}${formatSize(comparison.sizeDelta)} (${sign}${comparison.sizeDeltaPercent}%)** |`);
131
+ if (moduleRows.length > 0) {
132
+ console.log();
133
+ console.log(`### Module Changes`);
134
+ console.log();
135
+ console.log(formatOutput(moduleRows, "markdown"));
136
+ }
137
+ if (categoryRows.length > 0) {
138
+ console.log();
139
+ console.log(`### Category Changes`);
140
+ console.log();
141
+ console.log(formatOutput(categoryRows, "markdown"));
142
+ }
81
143
  } else {
82
144
  const sign = comparison.sizeDelta >= 0 ? "+" : "";
83
145
  console.log(`
@@ -119,4 +181,4 @@ async function getConfig() {
119
181
  export {
120
182
  registerBundleCommands
121
183
  };
122
- //# sourceMappingURL=bundle-U7RF6HDE.js.map
184
+ //# sourceMappingURL=bundle-7IF5FIB4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/bundle.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport {\n analyzeBundle,\n compareBundles,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\nfunction formatSize(bytes: number): string {\n const abs = Math.abs(bytes);\n const sign = bytes < 0 ? \"-\" : \"\";\n if (abs < 1024) return `${sign}${abs} B`;\n if (abs < 1024 * 1024) return `${sign}${(abs / 1024).toFixed(1)} KB`;\n return `${sign}${(abs / (1024 * 1024)).toFixed(2)} MB`;\n}\n\nfunction formatDelta(delta: number): string {\n const prefix = delta > 0 ? \"+\" : \"\";\n return `${prefix}${formatSize(delta)}`;\n}\n\nexport function registerBundleCommands(program: Command): void {\n const bundle = program.command(\"bundle\").description(\"Analyze app bundles and APKs\");\n\n bundle\n .command(\"analyze <file>\")\n .description(\"Analyze size breakdown of an AAB or APK\")\n .option(\"--threshold <mb>\", \"Fail if compressed size exceeds threshold (MB)\", parseFloat)\n .action(async (file: string, opts: { threshold?: number }) => {\n const format = getOutputFormat(program, await getConfig());\n\n try {\n const analysis = await analyzeBundle(file);\n\n if (format === \"json\") {\n console.log(formatOutput(analysis, format));\n } else if (format === \"markdown\") {\n const moduleRows = analysis.modules.map((m) => ({\n module: m.name,\n compressed: formatSize(m.compressedSize),\n uncompressed: formatSize(m.uncompressedSize),\n entries: m.entries,\n }));\n const categoryRows = analysis.categories.map((c) => ({\n category: c.name,\n compressed: formatSize(c.compressedSize),\n uncompressed: formatSize(c.uncompressedSize),\n entries: c.entries,\n }));\n console.log(`## Bundle Analysis: \\`${analysis.filePath}\\``);\n console.log();\n console.log(`| Property | Value |`);\n console.log(`| --- | --- |`);\n console.log(`| Type | ${analysis.fileType.toUpperCase()} |`);\n console.log(`| Total compressed | ${formatSize(analysis.totalCompressed)} |`);\n console.log(`| Total uncompressed | ${formatSize(analysis.totalUncompressed)} |`);\n console.log(`| Entries | ${analysis.entryCount} |`);\n console.log();\n console.log(`### Modules`);\n console.log();\n console.log(formatOutput(moduleRows, \"markdown\"));\n console.log();\n console.log(`### Categories`);\n console.log();\n console.log(formatOutput(categoryRows, \"markdown\"));\n } else {\n console.log(`\\nFile: ${analysis.filePath}`);\n console.log(`Type: ${analysis.fileType.toUpperCase()}`);\n console.log(`Total compressed: ${formatSize(analysis.totalCompressed)}`);\n console.log(`Total uncompressed: ${formatSize(analysis.totalUncompressed)}`);\n console.log(`Entries: ${analysis.entryCount}\\n`);\n\n // Modules table\n const moduleRows = analysis.modules.map((m) => ({\n module: m.name,\n compressed: formatSize(m.compressedSize),\n uncompressed: formatSize(m.uncompressedSize),\n entries: m.entries,\n }));\n console.log(\"Modules:\");\n console.log(formatOutput(moduleRows, \"table\"));\n\n // Categories table\n const categoryRows = analysis.categories.map((c) => ({\n category: c.name,\n compressed: formatSize(c.compressedSize),\n uncompressed: formatSize(c.uncompressedSize),\n entries: c.entries,\n }));\n console.log(\"\\nCategories:\");\n console.log(formatOutput(categoryRows, \"table\"));\n }\n\n // Threshold check\n if (opts.threshold !== undefined) {\n const thresholdBytes = opts.threshold * 1024 * 1024;\n if (analysis.totalCompressed > thresholdBytes) {\n console.error(\n `\\nThreshold breached: ${formatSize(analysis.totalCompressed)} > ${opts.threshold} MB`,\n );\n process.exit(6);\n }\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n });\n\n bundle\n .command(\"compare <file1> <file2>\")\n .description(\"Compare size differences between two bundles or APKs\")\n .action(async (file1: string, file2: string) => {\n const format = getOutputFormat(program, await getConfig());\n\n try {\n const [before, after] = await Promise.all([\n analyzeBundle(file1),\n analyzeBundle(file2),\n ]);\n const comparison = compareBundles(before, after);\n\n if (format === \"json\") {\n console.log(formatOutput(comparison, format));\n } else if (format === \"markdown\") {\n const sign = comparison.sizeDelta >= 0 ? \"+\" : \"\";\n const moduleRows = comparison.moduleDeltas\n .filter((m) => m.delta !== 0)\n .map((m) => ({\n module: m.module,\n before: formatSize(m.before),\n after: formatSize(m.after),\n delta: formatDelta(m.delta),\n }));\n const categoryRows = comparison.categoryDeltas\n .filter((c) => c.delta !== 0)\n .map((c) => ({\n category: c.category,\n before: formatSize(c.before),\n after: formatSize(c.after),\n delta: formatDelta(c.delta),\n }));\n console.log(`## Bundle Comparison`);\n console.log();\n console.log(`| | Path | Size |`);\n console.log(`| --- | --- | --- |`);\n console.log(`| Before | \\`${comparison.before.path}\\` | ${formatSize(comparison.before.totalCompressed)} |`);\n console.log(`| After | \\`${comparison.after.path}\\` | ${formatSize(comparison.after.totalCompressed)} |`);\n console.log(`| **Delta** | | **${sign}${formatSize(comparison.sizeDelta)} (${sign}${comparison.sizeDeltaPercent}%)** |`);\n if (moduleRows.length > 0) {\n console.log();\n console.log(`### Module Changes`);\n console.log();\n console.log(formatOutput(moduleRows, \"markdown\"));\n }\n if (categoryRows.length > 0) {\n console.log();\n console.log(`### Category Changes`);\n console.log();\n console.log(formatOutput(categoryRows, \"markdown\"));\n }\n } else {\n const sign = comparison.sizeDelta >= 0 ? \"+\" : \"\";\n console.log(`\\nBefore: ${comparison.before.path} (${formatSize(comparison.before.totalCompressed)})`);\n console.log(`After: ${comparison.after.path} (${formatSize(comparison.after.totalCompressed)})`);\n console.log(`Delta: ${sign}${formatSize(comparison.sizeDelta)} (${sign}${comparison.sizeDeltaPercent}%)\\n`);\n\n // Module deltas\n const moduleRows = comparison.moduleDeltas\n .filter((m) => m.delta !== 0)\n .map((m) => ({\n module: m.module,\n before: formatSize(m.before),\n after: formatSize(m.after),\n delta: formatDelta(m.delta),\n }));\n if (moduleRows.length > 0) {\n console.log(\"Module changes:\");\n console.log(formatOutput(moduleRows, \"table\"));\n }\n\n // Category deltas\n const categoryRows = comparison.categoryDeltas\n .filter((c) => c.delta !== 0)\n .map((c) => ({\n category: c.category,\n before: formatSize(c.before),\n after: formatSize(c.after),\n delta: formatDelta(c.delta),\n }));\n if (categoryRows.length > 0) {\n console.log(\"\\nCategory changes:\");\n console.log(formatOutput(categoryRows, \"table\"));\n }\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n });\n}\n\nasync function getConfig() {\n const { loadConfig } = await import(\"@gpc-cli/config\");\n return loadConfig();\n}\n"],"mappings":"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","names":[]}
@@ -73,10 +73,10 @@ async function createProgram(pluginManager) {
73
73
  (await import("./auth-5XAQMZRV.js")).registerAuthCommands(program);
74
74
  },
75
75
  config: async () => {
76
- (await import("./config-R5U7GV56.js")).registerConfigCommands(program);
76
+ (await import("./config-2L7QUYWP.js")).registerConfigCommands(program);
77
77
  },
78
78
  doctor: async () => {
79
- (await import("./doctor-TEIKODLP.js")).registerDoctorCommand(program);
79
+ (await import("./doctor-UZB2UB5X.js")).registerDoctorCommand(program);
80
80
  },
81
81
  docs: async () => {
82
82
  (await import("./docs-CVTWIVMS.js")).registerDocsCommand(program);
@@ -94,7 +94,7 @@ async function createProgram(pluginManager) {
94
94
  (await import("./tracks-XFUN7JJX.js")).registerTracksCommands(program);
95
95
  },
96
96
  status: async () => {
97
- (await import("./status-S3FAEXNH.js")).registerStatusCommand(program);
97
+ (await import("./status-5TOOZAGT.js")).registerStatusCommand(program);
98
98
  },
99
99
  listings: async () => {
100
100
  (await import("./listings-VSBHQY5H.js")).registerListingsCommands(program);
@@ -103,7 +103,7 @@ async function createProgram(pluginManager) {
103
103
  (await import("./reviews-GJAQ5OVC.js")).registerReviewsCommands(program);
104
104
  },
105
105
  vitals: async () => {
106
- (await import("./vitals-GDIQFWHV.js")).registerVitalsCommands(program);
106
+ (await import("./vitals-KSNAVN5F.js")).registerVitalsCommands(program);
107
107
  },
108
108
  subscriptions: async () => {
109
109
  (await import("./subscriptions-Z5ZPVUFM.js")).registerSubscriptionsCommands(program);
@@ -127,10 +127,10 @@ async function createProgram(pluginManager) {
127
127
  (await import("./testers-UWSUGGVT.js")).registerTestersCommands(program);
128
128
  },
129
129
  validate: async () => {
130
- (await import("./validate-UYXICKBO.js")).registerValidateCommand(program);
130
+ (await import("./validate-MHLPENCM.js")).registerValidateCommand(program);
131
131
  },
132
132
  publish: async () => {
133
- (await import("./publish-PZRWX3TH.js")).registerPublishCommand(program);
133
+ (await import("./publish-26ZPS7XX.js")).registerPublishCommand(program);
134
134
  },
135
135
  recovery: async () => {
136
136
  (await import("./recovery-S5UNJDBO.js")).registerRecoveryCommands(program);
@@ -159,13 +159,13 @@ async function createProgram(pluginManager) {
159
159
  (await import("./purchase-options-CKRN4VIW.js")).registerPurchaseOptionsCommands(program);
160
160
  },
161
161
  bundle: async () => {
162
- (await import("./bundle-U7RF6HDE.js")).registerBundleCommands(program);
162
+ (await import("./bundle-7IF5FIB4.js")).registerBundleCommands(program);
163
163
  },
164
164
  audit: async () => {
165
165
  (await import("./audit-DSTCSU4V.js")).registerAuditCommands(program);
166
166
  },
167
167
  migrate: async () => {
168
- (await import("./migrate-V6G5YUVH.js")).registerMigrateCommands(program);
168
+ (await import("./migrate-XQV7P4R7.js")).registerMigrateCommands(program);
169
169
  },
170
170
  "install-skills": async () => {
171
171
  (await import("./install-skills-OV4HVANW.js")).registerInstallSkillsCommand(program);
@@ -308,4 +308,4 @@ export {
308
308
  createProgram,
309
309
  handleCliError
310
310
  };
311
- //# sourceMappingURL=chunk-CE2HXEJX.js.map
311
+ //# sourceMappingURL=chunk-P6TYWHCR.js.map
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getOutputFormat
4
+ } from "./chunk-ELXAK7GI.js";
5
+ import {
6
+ isInteractive,
7
+ promptConfirm,
8
+ promptInput,
9
+ promptSelect
10
+ } from "./chunk-NV75I5VP.js";
11
+
12
+ // src/commands/config.ts
13
+ import { loadConfig, setConfigValue, getUserConfigPath, initConfig } from "@gpc-cli/config";
14
+ import { formatOutput, writeAuditLog, createAuditEntry } from "@gpc-cli/core";
15
+ import { existsSync } from "fs";
16
+ import { resolve } from "path";
17
+ var ANDROID_PACKAGE_RE = /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
18
+ function registerConfigCommands(program) {
19
+ const config = program.command("config").description("Manage configuration");
20
+ config.command("init").description("Create a configuration file").option("--global", "Create in user config directory (~/.config/gpc/)").action(async (_options) => {
21
+ const initialConfig = {};
22
+ if (isInteractive(program)) {
23
+ console.log("\nGPC Setup Wizard\n");
24
+ let app = await promptInput("Default package name (e.g. com.example.app, blank to skip)");
25
+ if (app) {
26
+ if (!ANDROID_PACKAGE_RE.test(app)) {
27
+ console.error(
28
+ ` Warning: "${app}" doesn't look like a valid Android package name \u2014 continuing anyway`
29
+ );
30
+ }
31
+ initialConfig["app"] = app;
32
+ }
33
+ const authMethod = await promptSelect(
34
+ "Authentication method:",
35
+ ["service-account", "adc", "skip"],
36
+ "service-account"
37
+ );
38
+ if (authMethod === "service-account") {
39
+ let saPath = "";
40
+ while (true) {
41
+ saPath = await promptInput("Path to service account JSON key file");
42
+ if (!saPath) {
43
+ console.log(" Skipping service account setup.");
44
+ break;
45
+ }
46
+ const resolved = resolve(saPath);
47
+ if (existsSync(resolved)) {
48
+ initialConfig["auth"] = { serviceAccount: saPath };
49
+ break;
50
+ }
51
+ console.error(` File not found: ${resolved}`);
52
+ const retry = await promptConfirm("Try a different path?");
53
+ if (!retry) break;
54
+ }
55
+ } else if (authMethod === "adc") {
56
+ console.log(
57
+ " Using Application Default Credentials \u2014 run `gcloud auth application-default login` if not already set up."
58
+ );
59
+ }
60
+ const output = await promptSelect(
61
+ "Default output format:",
62
+ ["table", "json", "yaml", "markdown"],
63
+ "table"
64
+ );
65
+ if (output !== "table") initialConfig["output"] = output;
66
+ }
67
+ const path = await initConfig(initialConfig);
68
+ const configured = [];
69
+ if (initialConfig["app"]) configured.push(`app: ${initialConfig["app"]}`);
70
+ if (initialConfig["auth"]) configured.push("auth: service account");
71
+ if (initialConfig["output"]) configured.push(`output: ${initialConfig["output"]}`);
72
+ console.log(`
73
+ Configuration file created: ${path}`);
74
+ if (configured.length > 0) {
75
+ console.log(` ${configured.join(" \xB7 ")}`);
76
+ }
77
+ console.log("\nRun `gpc doctor` to verify your setup.");
78
+ writeAuditLog(createAuditEntry("config init", { path })).catch(() => {
79
+ });
80
+ });
81
+ config.command("show").description("Display resolved configuration").action(async () => {
82
+ const resolved = await loadConfig();
83
+ const format = getOutputFormat(program, resolved);
84
+ console.log(formatOutput(resolved, format));
85
+ });
86
+ config.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
87
+ await setConfigValue(key, value);
88
+ console.log(`Set ${key} = ${value}`);
89
+ });
90
+ config.command("path").description("Show configuration file path").action(() => {
91
+ console.log(getUserConfigPath());
92
+ });
93
+ }
94
+ export {
95
+ registerConfigCommands
96
+ };
97
+ //# sourceMappingURL=config-2L7QUYWP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/config.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig, setConfigValue, getUserConfigPath, initConfig } from \"@gpc-cli/config\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport { formatOutput, writeAuditLog, createAuditEntry } from \"@gpc-cli/core\";\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isInteractive, promptInput, promptSelect, promptConfirm } from \"../prompt.js\";\n\nconst ANDROID_PACKAGE_RE = /^[a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z][a-zA-Z0-9_]*)+$/;\n\nexport function registerConfigCommands(program: Command): void {\n const config = program.command(\"config\").description(\"Manage configuration\");\n\n config\n .command(\"init\")\n .description(\"Create a configuration file\")\n .option(\"--global\", \"Create in user config directory (~/.config/gpc/)\")\n .action(async (_options: { global?: boolean }) => {\n const initialConfig: Record<string, unknown> = {};\n\n if (isInteractive(program)) {\n console.log(\"\\nGPC Setup Wizard\\n\");\n\n // Package name\n let app = await promptInput(\"Default package name (e.g. com.example.app, blank to skip)\");\n if (app) {\n if (!ANDROID_PACKAGE_RE.test(app)) {\n console.error(\n ` Warning: \"${app}\" doesn't look like a valid Android package name — continuing anyway`,\n );\n }\n initialConfig[\"app\"] = app;\n }\n\n // Auth method\n const authMethod = await promptSelect(\n \"Authentication method:\",\n [\"service-account\", \"adc\", \"skip\"],\n \"service-account\",\n );\n\n if (authMethod === \"service-account\") {\n let saPath = \"\";\n while (true) {\n saPath = await promptInput(\"Path to service account JSON key file\");\n if (!saPath) {\n console.log(\" Skipping service account setup.\");\n break;\n }\n const resolved = resolve(saPath);\n if (existsSync(resolved)) {\n initialConfig[\"auth\"] = { serviceAccount: saPath };\n break;\n }\n console.error(` File not found: ${resolved}`);\n const retry = await promptConfirm(\"Try a different path?\");\n if (!retry) break;\n }\n } else if (authMethod === \"adc\") {\n console.log(\n \" Using Application Default Credentials — run `gcloud auth application-default login` if not already set up.\",\n );\n }\n\n // Output format\n const output = await promptSelect(\n \"Default output format:\",\n [\"table\", \"json\", \"yaml\", \"markdown\"],\n \"table\",\n );\n if (output !== \"table\") initialConfig[\"output\"] = output;\n }\n\n const path = await initConfig(initialConfig as GpcConfig);\n\n // Summary\n const configured: string[] = [];\n if (initialConfig[\"app\"]) configured.push(`app: ${initialConfig[\"app\"]}`);\n if (initialConfig[\"auth\"]) configured.push(\"auth: service account\");\n if (initialConfig[\"output\"]) configured.push(`output: ${initialConfig[\"output\"]}`);\n\n console.log(`\\nConfiguration file created: ${path}`);\n if (configured.length > 0) {\n console.log(` ${configured.join(\" · \")}`);\n }\n console.log(\"\\nRun `gpc doctor` to verify your setup.\");\n\n writeAuditLog(createAuditEntry(\"config init\", { path })).catch(() => {});\n });\n\n config\n .command(\"show\")\n .description(\"Display resolved configuration\")\n .action(async () => {\n const resolved = await loadConfig();\n const format = getOutputFormat(program, resolved);\n console.log(formatOutput(resolved, format));\n });\n\n config\n .command(\"set <key> <value>\")\n .description(\"Set a configuration value\")\n .action(async (key: string, value: string) => {\n await setConfigValue(key, value);\n console.log(`Set ${key} = ${value}`);\n });\n\n config\n .command(\"path\")\n .description(\"Show configuration file path\")\n .action(() => {\n console.log(getUserConfigPath());\n });\n}\n"],"mappings":"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","names":[]}
@@ -22,20 +22,45 @@ function icon(status) {
22
22
  return INFO;
23
23
  }
24
24
  }
25
+ var ANDROID_PACKAGE_RE = /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
26
+ function checkNodeVersion(nodeVersion) {
27
+ const major = parseInt(nodeVersion.split(".")[0] ?? "0", 10);
28
+ return major >= 20 ? { name: "node", status: "pass", message: `Node.js ${nodeVersion}` } : {
29
+ name: "node",
30
+ status: "fail",
31
+ message: `Node.js ${nodeVersion} (requires >=20)`,
32
+ suggestion: "Upgrade Node.js to v20 or later: https://nodejs.org"
33
+ };
34
+ }
35
+ function checkPackageName(app) {
36
+ if (!app) return null;
37
+ return ANDROID_PACKAGE_RE.test(app) ? { name: "package-name", status: "pass", message: `Package name format OK: ${app}` } : {
38
+ name: "package-name",
39
+ status: "warn",
40
+ message: `Package name may be invalid: ${app}`,
41
+ suggestion: "Android package names must have 2+ dot-separated segments, each starting with a letter (e.g. com.example.app)"
42
+ };
43
+ }
44
+ function checkProxy(url) {
45
+ if (!url) return null;
46
+ try {
47
+ new URL(url);
48
+ return { name: "proxy", status: "pass", message: `Proxy configured: ${url}` };
49
+ } catch {
50
+ return {
51
+ name: "proxy",
52
+ status: "warn",
53
+ message: `Invalid proxy URL: ${url}`,
54
+ suggestion: "Set HTTPS_PROXY to a valid URL (e.g. http://proxy.example.com:8080)"
55
+ };
56
+ }
57
+ }
25
58
  function registerDoctorCommand(program) {
26
- program.command("doctor").description("Verify setup and connectivity").option("--json", "Output results as JSON").action(async (opts) => {
59
+ program.command("doctor").description("Verify setup and connectivity").action(async (_opts, cmd) => {
27
60
  const results = [];
28
- const jsonMode = opts.json ?? false;
29
- const nodeVersion = process.versions.node;
30
- const major = parseInt(nodeVersion.split(".")[0] ?? "0", 10);
31
- results.push(
32
- major >= 20 ? { name: "node", status: "pass", message: `Node.js ${nodeVersion}` } : {
33
- name: "node",
34
- status: "fail",
35
- message: `Node.js ${nodeVersion} (requires >=20)`,
36
- suggestion: "Upgrade Node.js to v20 or later: https://nodejs.org"
37
- }
38
- );
61
+ const parentOpts = cmd.parent?.opts() ?? {};
62
+ const jsonMode = !!(parentOpts["json"] || parentOpts["output"] === "json");
63
+ results.push(checkNodeVersion(process.versions.node));
39
64
  let config;
40
65
  try {
41
66
  config = await loadConfig();
@@ -46,6 +71,8 @@ function registerDoctorCommand(program) {
46
71
  status: "pass",
47
72
  message: `Default app: ${config.app}`
48
73
  });
74
+ const pkgCheck = checkPackageName(config.app);
75
+ if (pkgCheck) results.push(pkgCheck);
49
76
  } else {
50
77
  results.push({
51
78
  name: "default-app",
@@ -58,8 +85,8 @@ function registerDoctorCommand(program) {
58
85
  results.push({
59
86
  name: "config",
60
87
  status: "fail",
61
- message: "Configuration error",
62
- suggestion: "Check your .gpcrc.json or config file for syntax errors"
88
+ message: "Configuration could not be loaded",
89
+ suggestion: "Run gpc config init to create a config file, or check .gpcrc.json for syntax errors"
63
90
  });
64
91
  }
65
92
  const configDir = getConfigDir();
@@ -182,23 +209,8 @@ function registerDoctorCommand(program) {
182
209
  }
183
210
  }
184
211
  const proxyUrl = process.env["HTTPS_PROXY"] || process.env["https_proxy"] || process.env["HTTP_PROXY"] || process.env["http_proxy"];
185
- if (proxyUrl) {
186
- try {
187
- new URL(proxyUrl);
188
- results.push({
189
- name: "proxy",
190
- status: "pass",
191
- message: `Proxy configured: ${proxyUrl}`
192
- });
193
- } catch {
194
- results.push({
195
- name: "proxy",
196
- status: "warn",
197
- message: `Invalid proxy URL: ${proxyUrl}`,
198
- suggestion: "Set HTTPS_PROXY to a valid URL (e.g., http://proxy.example.com:8080)"
199
- });
200
- }
201
- }
212
+ const proxyCheck = checkProxy(proxyUrl);
213
+ if (proxyCheck) results.push(proxyCheck);
202
214
  const caCert = process.env["GPC_CA_CERT"] || process.env["NODE_EXTRA_CA_CERTS"];
203
215
  if (caCert) {
204
216
  if (existsSync(caCert)) {
@@ -216,20 +228,26 @@ function registerDoctorCommand(program) {
216
228
  });
217
229
  }
218
230
  }
219
- try {
220
- await lookup("androidpublisher.googleapis.com");
221
- results.push({
222
- name: "dns",
223
- status: "pass",
224
- message: "DNS resolution: androidpublisher.googleapis.com"
225
- });
226
- } catch {
227
- results.push({
228
- name: "dns",
229
- status: "fail",
230
- message: "Cannot resolve androidpublisher.googleapis.com",
231
- suggestion: "Check your DNS settings and network connection"
232
- });
231
+ const dnsHosts = [
232
+ "androidpublisher.googleapis.com",
233
+ "playdeveloperreporting.googleapis.com"
234
+ ];
235
+ for (const host of dnsHosts) {
236
+ try {
237
+ await lookup(host);
238
+ results.push({
239
+ name: "dns",
240
+ status: "pass",
241
+ message: `DNS: ${host}`
242
+ });
243
+ } catch {
244
+ results.push({
245
+ name: "dns",
246
+ status: "fail",
247
+ message: `Cannot resolve ${host}`,
248
+ suggestion: "Check your DNS settings and network connection"
249
+ });
250
+ }
233
251
  }
234
252
  try {
235
253
  const authConfig = config ?? await loadConfig();
@@ -264,22 +282,14 @@ function registerDoctorCommand(program) {
264
282
  });
265
283
  }
266
284
  }
285
+ const errors = results.filter((r) => r.status === "fail").length;
286
+ const warnings = results.filter((r) => r.status === "warn").length;
287
+ const passed = results.filter((r) => r.status === "pass").length;
267
288
  if (jsonMode) {
268
- const errors2 = results.filter((r) => r.status === "fail").length;
269
- const warnings2 = results.filter((r) => r.status === "warn").length;
270
289
  console.log(
271
- JSON.stringify(
272
- {
273
- success: errors2 === 0,
274
- errors: errors2,
275
- warnings: warnings2,
276
- checks: results
277
- },
278
- null,
279
- 2
280
- )
290
+ JSON.stringify({ success: errors === 0, errors, warnings, checks: results }, null, 2)
281
291
  );
282
- if (errors2 > 0) process.exit(1);
292
+ if (errors > 0) process.exit(1);
283
293
  return;
284
294
  }
285
295
  console.log("GPC Doctor\n");
@@ -289,20 +299,24 @@ function registerDoctorCommand(program) {
289
299
  console.log(` ${r.suggestion}`);
290
300
  }
291
301
  }
292
- const errors = results.filter((r) => r.status === "fail").length;
293
- const warnings = results.filter((r) => r.status === "warn").length;
294
- console.log("");
302
+ console.log(
303
+ `
304
+ ${PASS} ${passed} passed ${WARN} ${warnings} warning${warnings !== 1 ? "s" : ""} ${FAIL} ${errors} failed`
305
+ );
295
306
  if (errors > 0) {
296
- console.log("Some checks failed. Fix the issues above and run again.");
307
+ console.log("\nSome checks failed. Fix the issues above and run again.");
297
308
  process.exit(1);
298
309
  } else if (warnings > 0) {
299
- console.log("All checks passed with warnings.");
310
+ console.log("\nAll checks passed with warnings.");
300
311
  } else {
301
- console.log("All checks passed!");
312
+ console.log("\nAll checks passed!");
302
313
  }
303
314
  });
304
315
  }
305
316
  export {
317
+ checkNodeVersion,
318
+ checkPackageName,
319
+ checkProxy,
306
320
  registerDoctorCommand
307
321
  };
308
- //# sourceMappingURL=doctor-TEIKODLP.js.map
322
+ //# sourceMappingURL=doctor-UZB2UB5X.js.map