@gpc-cli/cli 0.9.24 → 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
@@ -10,7 +10,7 @@ Releases, rollouts, metadata, vitals, reviews, subscriptions, reports, and more.
10
10
  <p align="center">
11
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
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-1299_passing-00D26A" alt="Tests">
13
+ <img src="https://img.shields.io/badge/Tests-1358_passing-00D26A" alt="Tests">
14
14
  <img src="https://img.shields.io/badge/License-MIT-yellow" alt="License">
15
15
  </p>
16
16
 
@@ -33,19 +33,41 @@ curl -fsSL https://raw.githubusercontent.com/yasserstudio/gpc/main/scripts/insta
33
33
  # Authenticate
34
34
  gpc auth login --service-account path/to/key.json
35
35
 
36
+ # App health at a glance — releases, vitals, and reviews in one command
37
+ gpc status
38
+
36
39
  # Upload and release
37
40
  gpc releases upload app.aab --track internal
38
41
 
39
42
  # Promote to production
40
43
  gpc releases promote --from internal --to production --rollout 10
41
44
 
42
- # Check app health
43
- gpc vitals overview
44
-
45
45
  # Monitor reviews
46
46
  gpc reviews list --stars 1-3 --since 7d
47
47
  ```
48
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
+
49
71
  ## What You Get
50
72
 
51
73
  187 API endpoints across these command groups:
package/dist/bin.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createProgram,
4
4
  handleCliError,
5
5
  loadPlugins
6
- } from "./chunk-SZLO6HOO.js";
6
+ } from "./chunk-P6TYWHCR.js";
7
7
 
8
8
  // src/networking.ts
9
9
  async function setupNetworking() {
@@ -76,7 +76,7 @@ async function createProgram(pluginManager) {
76
76
  (await import("./config-2L7QUYWP.js")).registerConfigCommands(program);
77
77
  },
78
78
  doctor: async () => {
79
- (await import("./doctor-JMEHYOGJ.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-C222ZKHH.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,7 +127,7 @@ 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
133
  (await import("./publish-26ZPS7XX.js")).registerPublishCommand(program);
@@ -308,4 +308,4 @@ export {
308
308
  createProgram,
309
309
  handleCliError
310
310
  };
311
- //# sourceMappingURL=chunk-SZLO6HOO.js.map
311
+ //# sourceMappingURL=chunk-P6TYWHCR.js.map
@@ -56,9 +56,10 @@ function checkProxy(url) {
56
56
  }
57
57
  }
58
58
  function registerDoctorCommand(program) {
59
- 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) => {
60
60
  const results = [];
61
- const jsonMode = opts.json ?? false;
61
+ const parentOpts = cmd.parent?.opts() ?? {};
62
+ const jsonMode = !!(parentOpts["json"] || parentOpts["output"] === "json");
62
63
  results.push(checkNodeVersion(process.versions.node));
63
64
  let config;
64
65
  try {
@@ -318,4 +319,4 @@ export {
318
319
  checkProxy,
319
320
  registerDoctorCommand
320
321
  };
321
- //# sourceMappingURL=doctor-JMEHYOGJ.js.map
322
+ //# sourceMappingURL=doctor-UZB2UB5X.js.map
@@ -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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  createProgram,
4
4
  handleCliError
5
- } from "./chunk-SZLO6HOO.js";
5
+ } from "./chunk-P6TYWHCR.js";
6
6
  export {
7
7
  createProgram,
8
8
  handleCliError
@@ -16,7 +16,7 @@ import {
16
16
  formatOutput
17
17
  } from "@gpc-cli/core";
18
18
  function registerStatusCommand(program) {
19
- program.command("status").description("Unified app health snapshot: releases, vitals, and reviews").option("--days <n>", "Vitals window in days", parseInt, 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", parseInt, 3600).action(async (opts) => {
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
20
  const config = await loadConfig();
21
21
  const packageName = program.opts()["app"] || config.app;
22
22
  if (!packageName) {
@@ -93,4 +93,4 @@ function registerStatusCommand(program) {
93
93
  export {
94
94
  registerStatusCommand
95
95
  };
96
- //# sourceMappingURL=status-C222ZKHH.js.map
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
- console.log(formatOutput(result, format));
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-UYXICKBO.js.map
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-GDIQFWHV.js.map
250
+ //# sourceMappingURL=vitals-KSNAVN5F.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/vitals.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createReportingClient } from \"@gpc-cli/api\";\nimport type { ReportingDimension } from \"@gpc-cli/api\";\nimport type { VitalsMetricSet } from \"@gpc-cli/api\";\nimport {\n getVitalsOverview,\n getVitalsCrashes,\n getVitalsAnr,\n getVitalsStartup,\n getVitalsRendering,\n getVitalsBattery,\n getVitalsMemory,\n getVitalsAnomalies,\n searchVitalsErrors,\n compareVitalsTrend,\n checkThreshold,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n console.error(\"Error: No package name. Use --app <package> or gpc config set app <package>\");\n process.exit(2);\n }\n return name;\n}\n\nasync function getReportingClient(config: GpcConfig) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createReportingClient({ auth });\n}\n\nconst VALID_DIMENSIONS: ReportingDimension[] = [\n \"apiLevel\",\n \"versionCode\",\n \"deviceModel\",\n \"deviceType\",\n \"countryCode\",\n \"deviceRamBucket\",\n \"deviceSocName\",\n \"deviceCpuMakeModel\",\n \"deviceGlEsVersion\",\n \"deviceVulkanVersion\",\n \"deviceOpenGlVersion\",\n \"deviceBrand\",\n];\n\nconst THRESHOLD_CONFIG_KEYS: Record<string, string> = {\n crashes: \"crashRate\",\n anr: \"anrRate\",\n startup: \"slowStartRate\",\n rendering: \"slowRenderingRate\",\n battery: \"excessiveWakeupRate\",\n memory: \"stuckWakelockRate\",\n};\n\nfunction validateDimension(dim: string): ReportingDimension {\n if (!VALID_DIMENSIONS.includes(dim as ReportingDimension)) {\n console.error(`Error: Invalid dimension \"${dim}\".`);\n console.error(`Valid dimensions: ${VALID_DIMENSIONS.join(\", \")}`);\n process.exit(2);\n }\n return dim as ReportingDimension;\n}\n\ntype MetricFn = typeof getVitalsCrashes;\n\nfunction registerMetricCommand(\n parent: Command,\n name: string,\n description: string,\n fn: MetricFn,\n program: Command,\n): void {\n parent\n .command(name)\n .description(description)\n .option(\"--dim <dimension>\", \"Group by dimension\")\n .option(\"--days <n>\", \"Number of days to query\", parseInt)\n .option(\"--threshold <value>\", \"Threshold value for CI alerting\", parseFloat)\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await fn(reporting, packageName, {\n dimension: options.dim ? validateDimension(options.dim) : undefined,\n days: options.days,\n });\n if (format !== \"json\" && (!result.rows || result.rows.length === 0)) {\n console.log(\"No vitals data available.\");\n return;\n }\n if (format !== \"json\" && result.rows) {\n const rows = result.rows.map((row: Record<string, unknown>) => {\n const startTime = row[\"startTime\"] as Record<string, unknown> | undefined;\n const metrics = row[\"metrics\"] as Record<string, Record<string, unknown>> | undefined;\n const flat: Record<string, unknown> = {\n date: startTime ? `${startTime[\"year\"]}-${String(startTime[\"month\"]).padStart(2, \"0\")}-${String(startTime[\"day\"]).padStart(2, \"0\")}` : \"-\",\n };\n if (metrics) {\n for (const [key, val] of Object.entries(metrics)) {\n flat[key] = val?.[\"decimalValue\"]?.[\"value\"] ?? \"-\";\n }\n }\n const dims = row[\"dimensions\"] as Record<string, unknown> | undefined;\n if (dims) {\n for (const [key, val] of Object.entries(dims)) {\n flat[key] = (val as Record<string, unknown>)?.[\"stringValue\"] ?? \"-\";\n }\n }\n return flat;\n });\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(result, format));\n }\n\n // Check threshold from flag or config\n const configKey = THRESHOLD_CONFIG_KEYS[name];\n const configThreshold = configKey\n ? (config as Record<string, unknown>)[\"vitals\"]\n ? ((config as Record<string, unknown>)[\"vitals\"] as Record<string, unknown>)[\"thresholds\"]\n ? (((config as Record<string, unknown>)[\"vitals\"] as Record<string, unknown>)[\"thresholds\"] as Record<string, unknown>)[configKey]\n : undefined\n : undefined\n : undefined;\n const threshold = options.threshold ?? (configThreshold !== undefined ? Number(configThreshold) : undefined);\n if (threshold !== undefined) {\n const latestRow = result.rows?.[result.rows.length - 1];\n const metricKeys = latestRow?.metrics ? Object.keys(latestRow.metrics) : [];\n const firstMetric = metricKeys[0];\n const value = firstMetric\n ? Number(latestRow?.metrics[firstMetric]?.decimalValue?.value)\n : undefined;\n const check = checkThreshold(value, threshold);\n if (check.breached) {\n console.error(`Threshold breached: ${check.value} > ${check.threshold}`);\n process.exit(6);\n }\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n}\n\nexport function registerVitalsCommands(program: Command): void {\n const vitals = program\n .command(\"vitals\")\n .description(\"Monitor app vitals, crash rates, and performance metrics\");\n\n vitals\n .command(\"overview\")\n .description(\"Dashboard summary of all vital metrics\")\n .action(async () => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await getVitalsOverview(reporting, packageName);\n if (Object.keys(result).length === 0) {\n if (format === \"json\") {\n console.log(formatOutput({ vitals: [], message: \"No vitals data available\" }, format));\n } else {\n console.log(\"No vitals data available.\");\n }\n return;\n }\n if (format !== \"json\") {\n const overview = result as Record<string, unknown>;\n const rows = Object.entries(overview).map(([metric, data]) => {\n const metricRows = data as Record<string, unknown>[] | undefined;\n const latest = metricRows?.[metricRows.length - 1];\n const metrics = latest?.[\"metrics\"] as Record<string, Record<string, unknown>> | undefined;\n const firstKey = metrics ? Object.keys(metrics)[0] : undefined;\n const value = firstKey ? metrics?.[firstKey]?.[\"decimalValue\"]?.[\"value\"] : undefined;\n return {\n metric,\n dataPoints: metricRows?.length || 0,\n latestValue: value ?? \"-\",\n };\n });\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(result, format));\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n registerMetricCommand(vitals, \"crashes\", \"Query crash rate metrics\", getVitalsCrashes, program);\n registerMetricCommand(vitals, \"anr\", \"Query ANR rate metrics\", getVitalsAnr, program);\n registerMetricCommand(vitals, \"startup\", \"Query slow startup metrics\", getVitalsStartup, program);\n registerMetricCommand(\n vitals,\n \"rendering\",\n \"Query slow rendering metrics\",\n getVitalsRendering,\n program,\n );\n registerMetricCommand(\n vitals,\n \"battery\",\n \"Query excessive wakeup metrics\",\n getVitalsBattery,\n program,\n );\n registerMetricCommand(vitals, \"memory\", \"Query stuck wakelock metrics\", getVitalsMemory, program);\n\n vitals\n .command(\"anomalies\")\n .description(\"Detect anomalies in app vitals\")\n .action(async () => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await getVitalsAnomalies(reporting, packageName);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n const errors = vitals.command(\"errors\").description(\"Search and view error issues\");\n\n errors\n .command(\"search\")\n .description(\"Search error issues\")\n .option(\"--filter <text>\", \"Filter expression\")\n .option(\"--max <n>\", \"Maximum results\", parseInt)\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await searchVitalsErrors(reporting, packageName, {\n filter: options.filter,\n maxResults: options.max,\n });\n const issues = (result as Record<string, unknown>)[\"errorIssues\"] as unknown[] | undefined;\n if (format !== \"json\" && (!issues || issues.length === 0)) {\n console.log(\"No error issues found.\");\n return;\n }\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n const METRIC_MAP: Record<string, VitalsMetricSet> = {\n crashes: \"crashRateMetricSet\",\n anr: \"anrRateMetricSet\",\n startup: \"slowStartRateMetricSet\",\n rendering: \"slowRenderingRateMetricSet\",\n battery: \"excessiveWakeupRateMetricSet\",\n memory: \"stuckBackgroundWakelockRateMetricSet\",\n };\n\n vitals\n .command(\"compare <metric>\")\n .description(\"Compare metric trend: this period vs previous period\")\n .option(\"--days <n>\", \"Period length in days\", parseInt, 7)\n .action(async (metric: string, options) => {\n const metricSet = METRIC_MAP[metric];\n if (!metricSet) {\n console.error(\n `Error: Unknown metric \"${metric}\". Use: ${Object.keys(METRIC_MAP).join(\", \")}`,\n );\n process.exit(2);\n }\n\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await compareVitalsTrend(reporting, packageName, metricSet, options.days);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/commands/vitals.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createReportingClient } from \"@gpc-cli/api\";\nimport type { ReportingDimension } from \"@gpc-cli/api\";\nimport type { VitalsMetricSet } from \"@gpc-cli/api\";\nimport {\n getVitalsOverview,\n getVitalsCrashes,\n getVitalsAnr,\n getVitalsStartup,\n getVitalsRendering,\n getVitalsBattery,\n getVitalsMemory,\n getVitalsAnomalies,\n searchVitalsErrors,\n compareVitalsTrend,\n checkThreshold,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n console.error(\"Error: No package name. Use --app <package> or gpc config set app <package>\");\n process.exit(2);\n }\n return name;\n}\n\nasync function getReportingClient(config: GpcConfig) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createReportingClient({ auth });\n}\n\nconst VALID_DIMENSIONS: ReportingDimension[] = [\n \"apiLevel\",\n \"versionCode\",\n \"deviceModel\",\n \"deviceType\",\n \"countryCode\",\n \"deviceRamBucket\",\n \"deviceSocName\",\n \"deviceCpuMakeModel\",\n \"deviceGlEsVersion\",\n \"deviceVulkanVersion\",\n \"deviceOpenGlVersion\",\n \"deviceBrand\",\n];\n\nconst THRESHOLD_CONFIG_KEYS: Record<string, string> = {\n crashes: \"crashRate\",\n anr: \"anrRate\",\n startup: \"slowStartRate\",\n rendering: \"slowRenderingRate\",\n battery: \"excessiveWakeupRate\",\n memory: \"stuckWakelockRate\",\n};\n\nfunction validateDimension(dim: string): ReportingDimension {\n if (!VALID_DIMENSIONS.includes(dim as ReportingDimension)) {\n console.error(`Error: Invalid dimension \"${dim}\".`);\n console.error(`Valid dimensions: ${VALID_DIMENSIONS.join(\", \")}`);\n process.exit(2);\n }\n return dim as ReportingDimension;\n}\n\ntype MetricFn = typeof getVitalsCrashes;\n\nfunction registerMetricCommand(\n parent: Command,\n name: string,\n description: string,\n fn: MetricFn,\n program: Command,\n): void {\n parent\n .command(name)\n .description(description)\n .option(\"--dim <dimension>\", \"Group by dimension\")\n .option(\"--days <n>\", \"Number of days to query\", parseInt)\n .option(\"--threshold <value>\", \"Threshold value for CI alerting\", parseFloat)\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await fn(reporting, packageName, {\n dimension: options.dim ? validateDimension(options.dim) : undefined,\n days: options.days,\n });\n if (format !== \"json\" && (!result.rows || result.rows.length === 0)) {\n console.log(\"No vitals data available.\");\n return;\n }\n if (format !== \"json\" && result.rows) {\n const rows = result.rows.map((row: Record<string, unknown>) => {\n const startTime = row[\"startTime\"] as Record<string, unknown> | undefined;\n const metrics = row[\"metrics\"] as Record<string, Record<string, unknown>> | undefined;\n const flat: Record<string, unknown> = {\n date: startTime ? `${startTime[\"year\"]}-${String(startTime[\"month\"]).padStart(2, \"0\")}-${String(startTime[\"day\"]).padStart(2, \"0\")}` : \"-\",\n };\n if (metrics) {\n for (const [key, val] of Object.entries(metrics)) {\n flat[key] = val?.[\"decimalValue\"]?.[\"value\"] ?? \"-\";\n }\n }\n const dims = row[\"dimensions\"] as Record<string, unknown> | undefined;\n if (dims) {\n for (const [key, val] of Object.entries(dims)) {\n flat[key] = (val as Record<string, unknown>)?.[\"stringValue\"] ?? \"-\";\n }\n }\n return flat;\n });\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(result, format));\n }\n\n // Check threshold from flag or config\n const configKey = THRESHOLD_CONFIG_KEYS[name];\n const configThreshold = configKey\n ? (config as Record<string, unknown>)[\"vitals\"]\n ? ((config as Record<string, unknown>)[\"vitals\"] as Record<string, unknown>)[\"thresholds\"]\n ? (((config as Record<string, unknown>)[\"vitals\"] as Record<string, unknown>)[\"thresholds\"] as Record<string, unknown>)[configKey]\n : undefined\n : undefined\n : undefined;\n const threshold = options.threshold ?? (configThreshold !== undefined ? Number(configThreshold) : undefined);\n if (threshold !== undefined) {\n const latestRow = result.rows?.[result.rows.length - 1];\n const metricKeys = latestRow?.metrics ? Object.keys(latestRow.metrics) : [];\n const firstMetric = metricKeys[0];\n const value = firstMetric\n ? Number(latestRow?.metrics[firstMetric]?.decimalValue?.value)\n : undefined;\n const check = checkThreshold(value, threshold);\n if (check.breached) {\n console.error(`Threshold breached: ${check.value} > ${check.threshold}`);\n process.exit(6);\n }\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n}\n\nexport function registerVitalsCommands(program: Command): void {\n const vitals = program\n .command(\"vitals\")\n .description(\"Monitor app vitals, crash rates, and performance metrics\");\n\n vitals\n .command(\"overview\")\n .description(\"Dashboard summary of all vital metrics\")\n .action(async () => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await getVitalsOverview(reporting, packageName);\n if (Object.keys(result).length === 0) {\n if (format === \"json\") {\n console.log(formatOutput({ vitals: [], message: \"No vitals data available\" }, format));\n } else {\n console.log(\"No vitals data available.\");\n }\n return;\n }\n if (format !== \"json\") {\n const overview = result as Record<string, unknown>;\n const rows = Object.entries(overview).map(([metric, data]) => {\n const metricRows = data as Record<string, unknown>[] | undefined;\n const latest = metricRows?.[metricRows.length - 1];\n const metrics = latest?.[\"metrics\"] as Record<string, Record<string, unknown>> | undefined;\n const firstKey = metrics ? Object.keys(metrics)[0] : undefined;\n const value = firstKey ? metrics?.[firstKey]?.[\"decimalValue\"]?.[\"value\"] : undefined;\n return {\n metric,\n dataPoints: metricRows?.length || 0,\n latestValue: value ?? \"-\",\n };\n });\n console.log(formatOutput(rows, format));\n } else {\n console.log(formatOutput(result, format));\n }\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n registerMetricCommand(vitals, \"crashes\", \"Query crash rate metrics\", getVitalsCrashes, program);\n registerMetricCommand(vitals, \"anr\", \"Query ANR rate metrics\", getVitalsAnr, program);\n registerMetricCommand(vitals, \"startup\", \"Query slow startup metrics\", getVitalsStartup, program);\n registerMetricCommand(\n vitals,\n \"rendering\",\n \"Query slow rendering metrics\",\n getVitalsRendering,\n program,\n );\n registerMetricCommand(\n vitals,\n \"battery\",\n \"Query excessive wakeup metrics\",\n getVitalsBattery,\n program,\n );\n registerMetricCommand(vitals, \"memory\", \"Query stuck wakelock metrics\", getVitalsMemory, program);\n\n vitals\n .command(\"anomalies\")\n .description(\"Detect anomalies in app vitals\")\n .action(async () => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await getVitalsAnomalies(reporting, packageName);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n const errors = vitals.command(\"errors\").description(\"Search and view error issues\");\n\n errors\n .command(\"search\")\n .description(\"Search error issues\")\n .option(\"--filter <text>\", \"Filter expression\")\n .option(\"--max <n>\", \"Maximum results\", parseInt)\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await searchVitalsErrors(reporting, packageName, {\n filter: options.filter,\n maxResults: options.max,\n });\n const issues = (result as Record<string, unknown>)[\"errorIssues\"] as unknown[] | undefined;\n if (format !== \"json\" && (!issues || issues.length === 0)) {\n console.log(\"No error issues found.\");\n return;\n }\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n const METRIC_MAP: Record<string, VitalsMetricSet> = {\n crashes: \"crashRateMetricSet\",\n anr: \"anrRateMetricSet\",\n startup: \"slowStartRateMetricSet\",\n rendering: \"slowRenderingRateMetricSet\",\n battery: \"excessiveWakeupRateMetricSet\",\n memory: \"stuckBackgroundWakelockRateMetricSet\",\n };\n\n vitals\n .command(\"compare <metric>\")\n .description(\"Compare metric trend: this period vs previous period\")\n .option(\"--days <n>\", \"Period length in days\", (v) => parseInt(v, 10), 7)\n .action(async (metric: string, options) => {\n const metricSet = METRIC_MAP[metric];\n if (!metricSet) {\n console.error(\n `Error: Unknown metric \"${metric}\". Use: ${Object.keys(METRIC_MAP).join(\", \")}`,\n );\n process.exit(2);\n }\n\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const reporting = await getReportingClient(config);\n const format = getOutputFormat(program, config);\n\n try {\n const result = await compareVitalsTrend(reporting, packageName, metricSet, options.days);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gpc-cli/cli",
3
- "version": "0.9.24",
3
+ "version": "0.9.25",
4
4
  "description": "The complete Google Play CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,11 +19,11 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "commander": "^14.0.3",
22
- "@gpc-cli/config": "0.9.8",
22
+ "@gpc-cli/api": "1.0.17",
23
23
  "@gpc-cli/auth": "^0.9.9",
24
- "@gpc-cli/plugin-sdk": "0.9.6",
25
- "@gpc-cli/api": "1.0.16",
26
- "@gpc-cli/core": "0.9.22"
24
+ "@gpc-cli/config": "0.9.8",
25
+ "@gpc-cli/core": "0.9.23",
26
+ "@gpc-cli/plugin-sdk": "0.9.6"
27
27
  },
28
28
  "keywords": [
29
29
  "google-play",
@@ -1 +0,0 @@
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 .option(\"--json\", \"Output results as JSON\")\n .action(async (opts: { json?: boolean }) => {\n const results: CheckResult[] = [];\n const jsonMode = opts.json ?? false;\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":[]}
@@ -1 +0,0 @@
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\", parseInt, 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\", parseInt, 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":[]}
@@ -1 +0,0 @@
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 console.log(formatOutput(result, format));\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,YAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AACxC,YAAQ,KAAK,OAAO,QAAQ,IAAI,CAAC;AAAA,EACnC,CAAC;AACL;","names":[]}