@gpc-cli/cli 0.9.31 → 0.9.33

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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/dist/apps-CVBURB5V.js +0 -0
  3. package/dist/audit-A4BP27DN.js +0 -0
  4. package/dist/{auth-5XAQMZRV.js → auth-QSDK7NUO.js} +18 -1
  5. package/dist/{auth-5XAQMZRV.js.map → auth-QSDK7NUO.js.map} +1 -1
  6. package/dist/bin.js +5 -1
  7. package/dist/bin.js.map +1 -1
  8. package/dist/bundle-7IF5FIB4.js +0 -0
  9. package/dist/cache-SHWAVON6.js +114 -0
  10. package/dist/cache-SHWAVON6.js.map +1 -0
  11. package/dist/chunk-4O4D5SGL.js +0 -0
  12. package/dist/{chunk-5VVYPCDE.js → chunk-DMMFPBYF.js} +38 -5
  13. package/dist/chunk-DMMFPBYF.js.map +1 -0
  14. package/dist/chunk-ELXAK7GI.js +0 -0
  15. package/dist/chunk-FWKYRLKY.js +0 -0
  16. package/dist/chunk-NV75I5VP.js +0 -0
  17. package/dist/{update-GC2A2WVP.js → chunk-UH3TMIAL.js} +17 -154
  18. package/dist/chunk-UH3TMIAL.js.map +1 -0
  19. package/dist/chunk-Y3QZDAKS.js +0 -0
  20. package/dist/completion-C3PPWNS7.js +0 -0
  21. package/dist/config-2L7QUYWP.js +0 -0
  22. package/dist/data-safety-GDPKV5PN.js +0 -0
  23. package/dist/device-tiers-GHIYJPMB.js +0 -0
  24. package/dist/docs-HIGQU4UL.js +0 -0
  25. package/dist/doctor-UZB2UB5X.js +0 -0
  26. package/dist/external-transactions-HCL7ROMN.js +0 -0
  27. package/dist/feedback-W5MZMRF2.js +46 -0
  28. package/dist/feedback-W5MZMRF2.js.map +1 -0
  29. package/dist/generated-apks-VX7HYZDU.js +0 -0
  30. package/dist/iap-BBHF7BLZ.js +0 -0
  31. package/dist/index.js +1 -1
  32. package/dist/install-skills-OV4HVANW.js +0 -0
  33. package/dist/internal-sharing-E7SJYDW3.js +0 -0
  34. package/dist/listings-VSBHQY5H.js +0 -0
  35. package/dist/migrate-XQV7P4R7.js +0 -0
  36. package/dist/one-time-products-2PK4QKWE.js +0 -0
  37. package/dist/pricing-BYZSLN74.js +0 -0
  38. package/dist/prompt-BSV22CQZ.js +0 -0
  39. package/dist/publish-I6WJGR4S.js +0 -0
  40. package/dist/purchase-options-CKRN4VIW.js +0 -0
  41. package/dist/purchases-YRO6B7M6.js +0 -0
  42. package/dist/recovery-S5UNJDBO.js +0 -0
  43. package/dist/{releases-JMRKXEZU.js → releases-ANC54YWF.js} +88 -38
  44. package/dist/releases-ANC54YWF.js.map +1 -0
  45. package/dist/reports-N5X66IUN.js +0 -0
  46. package/dist/reviews-GJAQ5OVC.js +0 -0
  47. package/dist/status-6Y2CHHVD.js +0 -0
  48. package/dist/subscriptions-Z5ZPVUFM.js +0 -0
  49. package/dist/testers-UWSUGGVT.js +0 -0
  50. package/dist/tracks-XFUN7JJX.js +0 -0
  51. package/dist/update-NIVJLUNH.js +169 -0
  52. package/dist/update-NIVJLUNH.js.map +1 -0
  53. package/dist/users-JASXONRY.js +0 -0
  54. package/dist/validate-MHLPENCM.js +0 -0
  55. package/dist/version-7AI5IHVK.js +26 -0
  56. package/dist/version-7AI5IHVK.js.map +1 -0
  57. package/dist/vitals-KSNAVN5F.js +0 -0
  58. package/package.json +25 -19
  59. package/dist/chunk-5VVYPCDE.js.map +0 -1
  60. package/dist/releases-JMRKXEZU.js.map +0 -1
  61. package/dist/update-GC2A2WVP.js.map +0 -1
@@ -16,7 +16,14 @@ var GITHUB_API_URL = "https://api.github.com/repos/yasserstudio/gpc/releases/lat
16
16
  var GITHUB_TIMEOUT_MS = 1e4;
17
17
  var DOWNLOAD_TIMEOUT_MS = 12e4;
18
18
  function detectInstallMethod() {
19
- if (process.env["__GPC_BINARY"] === "1") return "binary";
19
+ if (process.env["__GPC_BINARY"] === "1") {
20
+ try {
21
+ const resolved = realpathSync(process.execPath).toLowerCase();
22
+ if (resolved.includes("cellar") || resolved.includes("homebrew")) return "homebrew";
23
+ } catch {
24
+ }
25
+ return "binary";
26
+ }
20
27
  if (process.env["npm_config_prefix"]) return "npm";
21
28
  try {
22
29
  const resolved = realpathSync(process.argv[1] ?? "").toLowerCase();
@@ -294,158 +301,14 @@ async function updateBinaryInPlace(assetUrl, expectedSha256, currentBinaryPath,
294
301
  }
295
302
  }
296
303
 
297
- // src/commands/update.ts
298
- import { createSpinner } from "@gpc-cli/core";
299
- function registerUpdateCommand(program) {
300
- program.command("update").description("Update gpc to the latest version").option("--check", "Check for updates without installing (exits 0 always)").option("--force", "Update even if already on the latest version").action(async (opts, cmd) => {
301
- const parentOpts = cmd.parent?.opts() ?? {};
302
- const jsonMode = !!(parentOpts["json"] || parentOpts["output"] === "json");
303
- const currentVersion = process.env["__GPC_VERSION"] ?? "0.0.0";
304
- if (currentVersion === "0.0.0") {
305
- if (jsonMode) {
306
- console.log(
307
- JSON.stringify({
308
- success: false,
309
- reason: "Cannot update a development build",
310
- current: currentVersion
311
- })
312
- );
313
- } else {
314
- console.log("Cannot update a development build (version: 0.0.0).");
315
- }
316
- return;
317
- }
318
- const spinner = createSpinner("Checking for updates...");
319
- spinner.start();
320
- let result;
321
- try {
322
- result = await checkForUpdate(currentVersion);
323
- } catch (err) {
324
- spinner.fail("Failed to check for updates");
325
- throw err;
326
- }
327
- if (opts.check) {
328
- spinner.stop();
329
- if (jsonMode) {
330
- console.log(
331
- JSON.stringify({
332
- current: result.current,
333
- latest: result.latest,
334
- updateAvailable: result.updateAvailable,
335
- installMethod: result.installMethod,
336
- releaseUrl: result.release.html_url
337
- })
338
- );
339
- } else if (result.updateAvailable) {
340
- console.log(`Update available: ${result.current} \u2192 ${result.latest}`);
341
- console.log(`Install method: ${result.installMethod}`);
342
- console.log(`Release: ${result.release.html_url}`);
343
- console.log(`
344
- Run: gpc update`);
345
- } else {
346
- console.log(`Already on latest version: v${result.current}`);
347
- }
348
- return;
349
- }
350
- if (!result.updateAvailable && !opts.force) {
351
- spinner.stop(`Already on latest version: v${result.current}`);
352
- if (jsonMode) {
353
- console.log(
354
- JSON.stringify({
355
- success: true,
356
- current: result.current,
357
- latest: result.latest,
358
- updated: false
359
- })
360
- );
361
- }
362
- return;
363
- }
364
- spinner.update(
365
- `Updating v${result.current} \u2192 v${result.latest} (${result.installMethod})...`
366
- );
367
- switch (result.installMethod) {
368
- case "npm":
369
- spinner.stop();
370
- await updateViaNpm({ silent: jsonMode });
371
- break;
372
- case "homebrew":
373
- spinner.stop();
374
- await updateViaBrew({ silent: jsonMode });
375
- break;
376
- case "binary": {
377
- const assetName = getPlatformAsset();
378
- if (!assetName) {
379
- spinner.fail();
380
- console.error(
381
- `Error: Unsupported platform: ${process.platform}/${process.arch}`
382
- );
383
- console.error(
384
- `Download manually: ${result.release.html_url}`
385
- );
386
- process.exit(1);
387
- }
388
- const assetObj = result.release.assets.find((a) => a.name === assetName);
389
- if (!assetObj) {
390
- spinner.fail();
391
- console.error(
392
- `Error: No binary found for ${assetName} in release ${result.latestTag}`
393
- );
394
- console.error(`Check: ${result.release.html_url}`);
395
- process.exit(1);
396
- }
397
- const sizeMB = (assetObj.size / (1024 * 1024)).toFixed(1);
398
- spinner.stop();
399
- const checksums = await fetchChecksums(result.release);
400
- const expectedHash = checksums.get(assetName) ?? "";
401
- const binaryPath = getCurrentBinaryPath();
402
- if (jsonMode) {
403
- await updateBinaryInPlace(assetObj.browser_download_url, expectedHash, binaryPath);
404
- } else {
405
- const label = `Downloading ${assetName} (${sizeMB} MB)`;
406
- process.stdout.write(`${label}...
407
- `);
408
- let lastPct = -1;
409
- await updateBinaryInPlace(assetObj.browser_download_url, expectedHash, binaryPath, {
410
- onProgress(downloaded, total) {
411
- if (total <= 0) return;
412
- const pct = Math.min(100, Math.round(downloaded / total * 100));
413
- if (pct === lastPct) return;
414
- lastPct = pct;
415
- const dlMB = (downloaded / (1024 * 1024)).toFixed(1);
416
- process.stdout.write(
417
- `\r${label} ${dlMB} / ${sizeMB} MB (${String(pct).padStart(3)}%) `
418
- );
419
- }
420
- });
421
- process.stdout.write("\n");
422
- }
423
- break;
424
- }
425
- case "unknown":
426
- spinner.fail();
427
- console.error("Error: Could not detect install method. Update manually:");
428
- console.error(` npm: npm install -g @gpc-cli/cli@latest`);
429
- console.error(` Homebrew: brew upgrade yasserstudio/tap/gpc`);
430
- console.error(` Binary: https://github.com/yasserstudio/gpc/releases/latest`);
431
- process.exit(1);
432
- }
433
- if (jsonMode) {
434
- console.log(
435
- JSON.stringify({
436
- success: true,
437
- previous: result.current,
438
- current: result.latest,
439
- method: result.installMethod
440
- })
441
- );
442
- } else {
443
- console.log(`
444
- \u2714 Updated to v${result.latest}`);
445
- }
446
- });
447
- }
448
304
  export {
449
- registerUpdateCommand
305
+ detectInstallMethod,
306
+ getPlatformAsset,
307
+ getCurrentBinaryPath,
308
+ fetchChecksums,
309
+ checkForUpdate,
310
+ updateViaNpm,
311
+ updateViaBrew,
312
+ updateBinaryInPlace
450
313
  };
451
- //# sourceMappingURL=update-GC2A2WVP.js.map
314
+ //# sourceMappingURL=chunk-UH3TMIAL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/updater.ts"],"sourcesContent":["/**\n * Self-update logic for `gpc update`.\n *\n * All functions here are pure / testable — no Commander.js dependency.\n * The command layer in commands/update.ts handles output and flags.\n */\n\nimport { createWriteStream } from \"node:fs\";\nimport { rename, chmod, unlink, stat } from \"node:fs/promises\";\nimport { realpathSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { pipeline } from \"node:stream/promises\";\nimport { Readable, Transform } from \"node:stream\";\nimport { spawn } from \"node:child_process\";\nimport { isNewerVersion } from \"./update-check.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type InstallMethod = \"npm\" | \"homebrew\" | \"binary\" | \"unknown\";\n\nexport interface GithubAsset {\n name: string;\n browser_download_url: string;\n size: number;\n}\n\nexport interface GithubRelease {\n tag_name: string;\n html_url: string;\n assets: GithubAsset[];\n}\n\nexport interface UpdateCheckResult {\n current: string;\n latest: string;\n latestTag: string;\n updateAvailable: boolean;\n installMethod: InstallMethod;\n release: GithubRelease;\n}\n\nconst GITHUB_API_URL = \"https://api.github.com/repos/yasserstudio/gpc/releases/latest\";\nconst GITHUB_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\n// ---------------------------------------------------------------------------\n// Install method detection\n// ---------------------------------------------------------------------------\n\n/**\n * Detect how gpc was installed.\n *\n * Priority:\n * 1. __GPC_BINARY env var (injected by esbuild at compile time) → \"binary\"\n * 2. npm_config_prefix env var → \"npm\"\n * 3. realpathSync(process.argv[1]) contains \"cellar\" or \"homebrew\" → \"homebrew\"\n * 4. realpathSync(process.argv[1]) contains \"node_modules\" → \"npm\"\n * 5. fallback → \"unknown\"\n *\n * Using realpathSync(process.argv[1]) instead of shelling to `which gpc`:\n * - No child process spawn\n * - Works on Windows without `where.exe`\n * - Resolves symlinks — critical for Intel Mac Homebrew where the bin path\n * is a symlink but the real path contains \"Cellar\"\n */\nexport function detectInstallMethod(): InstallMethod {\n // 1. Compiled binary — but Homebrew also distributes as compiled binary, check execPath\n if (process.env[\"__GPC_BINARY\"] === \"1\") {\n try {\n const resolved = realpathSync(process.execPath).toLowerCase();\n if (resolved.includes(\"cellar\") || resolved.includes(\"homebrew\")) return \"homebrew\";\n } catch { /* ignore */ }\n return \"binary\";\n }\n\n // 2. npm global install\n if (process.env[\"npm_config_prefix\"]) return \"npm\";\n\n // 3. Resolve symlinks and inspect path\n try {\n const resolved = realpathSync(process.argv[1] ?? \"\").toLowerCase();\n if (resolved.includes(\"cellar\") || resolved.includes(\"homebrew\")) return \"homebrew\";\n if (resolved.includes(\"node_modules\")) return \"npm\";\n } catch {\n // realpathSync can throw if the path doesn't exist — fall through to unknown\n }\n\n return \"unknown\";\n}\n\n// ---------------------------------------------------------------------------\n// Platform asset mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the GitHub release asset name for the current platform/arch,\n * matching the names produced by scripts/build-binary.ts TARGETS map.\n */\nexport function getPlatformAsset(): string | null {\n const arch = process.arch === \"arm64\" ? \"arm64\" : \"x64\";\n switch (process.platform) {\n case \"darwin\":\n return `gpc-darwin-${arch}`;\n case \"linux\":\n return `gpc-linux-${arch}`;\n case \"win32\":\n return \"gpc-windows-x64.exe\";\n default:\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Current binary path\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the path of the currently running gpc binary.\n * For compiled binaries, process.execPath IS the binary.\n * For npm/dev installs, process.argv[1] is the script entrypoint.\n */\nexport function getCurrentBinaryPath(): string {\n if (process.env[\"__GPC_BINARY\"] === \"1\") return process.execPath;\n return process.argv[1] ?? process.execPath;\n}\n\n// ---------------------------------------------------------------------------\n// GitHub Releases API\n// ---------------------------------------------------------------------------\n\nfunction githubHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"User-Agent\": \"gpc-cli\",\n Accept: \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n };\n // Support GPC_GITHUB_TOKEN for authenticated requests (avoids 60 req/hr limit\n // on shared CI runner IPs)\n if (process.env[\"GPC_GITHUB_TOKEN\"]) {\n headers[\"Authorization\"] = `Bearer ${process.env[\"GPC_GITHUB_TOKEN\"]}`;\n }\n return headers;\n}\n\nexport async function fetchLatestRelease(): Promise<GithubRelease> {\n let response: Response;\n try {\n response = await fetch(GITHUB_API_URL, {\n headers: githubHeaders(),\n signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw Object.assign(new Error(`Network error checking for updates: ${msg}`), {\n code: \"NETWORK_ERROR\",\n exitCode: 5,\n });\n }\n\n if (response.status === 429) {\n throw Object.assign(\n new Error(\n \"GitHub API rate limit exceeded. Set GPC_GITHUB_TOKEN to increase the limit.\",\n ),\n { code: \"UPDATE_RATE_LIMITED\", exitCode: 4 },\n );\n }\n\n if (!response.ok) {\n throw Object.assign(new Error(`GitHub API returned HTTP ${response.status}`), {\n code: \"UPDATE_API_ERROR\",\n exitCode: 4,\n });\n }\n\n return (await response.json()) as GithubRelease;\n}\n\n/**\n * Fetch and parse checksums.txt from the release assets.\n * Returns a Map of filename → lowercase sha256 hex.\n * Returns an empty Map if the asset is missing or the fetch fails.\n */\nexport async function fetchChecksums(release: GithubRelease): Promise<Map<string, string>> {\n const asset = release.assets.find((a) => a.name === \"checksums.txt\");\n if (!asset) return new Map();\n\n try {\n const response = await fetch(asset.browser_download_url, {\n headers: githubHeaders(),\n signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),\n });\n if (!response.ok) return new Map();\n\n const map = new Map<string, string>();\n for (const line of (await response.text()).split(\"\\n\")) {\n const parts = line.trim().split(/\\s+/);\n const hash = parts[0];\n const name = parts[1];\n if (hash && name) map.set(name, hash.toLowerCase());\n }\n return map;\n } catch {\n return new Map();\n }\n}\n\n// ---------------------------------------------------------------------------\n// High-level update check\n// ---------------------------------------------------------------------------\n\nexport async function checkForUpdate(currentVersion: string): Promise<UpdateCheckResult> {\n const release = await fetchLatestRelease();\n const latest = release.tag_name.replace(/^v/, \"\");\n return {\n current: currentVersion,\n latest,\n latestTag: release.tag_name,\n updateAvailable: isNewerVersion(currentVersion, latest),\n installMethod: detectInstallMethod(),\n release,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Update execution paths\n// ---------------------------------------------------------------------------\n\nexport async function updateViaNpm(options: { silent?: boolean } = {}): Promise<void> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"npm\", [\"install\", \"-g\", \"@gpc-cli/cli@latest\"], {\n // In silent (JSON) mode, redirect npm's stdout to stderr so it doesn't\n // pollute the machine-readable JSON that gpc writes to stdout.\n stdio: options.silent ? [\"inherit\", process.stderr, process.stderr] : \"inherit\",\n shell: false,\n });\n proc.on(\"close\", (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(\n Object.assign(new Error(`npm exited with code ${code}`), {\n code: \"UPDATE_NPM_FAILED\",\n exitCode: 1,\n suggestion: \"Run manually: npm install -g @gpc-cli/cli@latest\",\n }),\n );\n }\n });\n proc.on(\"error\", (err) => {\n reject(\n Object.assign(new Error(`Failed to run npm: ${err.message}`), {\n code: \"UPDATE_NPM_SPAWN_FAILED\",\n exitCode: 1,\n suggestion: \"Ensure npm is in your PATH\",\n }),\n );\n });\n });\n}\n\nexport async function updateViaBrew(options: { silent?: boolean } = {}): Promise<void> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"brew\", [\"upgrade\", \"yasserstudio/tap/gpc\"], {\n // In silent (JSON) mode, redirect brew's stdout to stderr so it doesn't\n // pollute the machine-readable JSON that gpc writes to stdout.\n stdio: options.silent ? [\"inherit\", process.stderr, process.stderr] : \"inherit\",\n shell: false,\n });\n proc.on(\"close\", (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(\n Object.assign(new Error(`brew exited with code ${code}`), {\n code: \"UPDATE_BREW_FAILED\",\n exitCode: 1,\n suggestion: \"Run manually: brew upgrade yasserstudio/tap/gpc\",\n }),\n );\n }\n });\n proc.on(\"error\", (err) => {\n reject(\n Object.assign(new Error(`Failed to run brew: ${err.message}`), {\n code: \"UPDATE_BREW_SPAWN_FAILED\",\n exitCode: 1,\n suggestion: \"Ensure Homebrew is installed: https://brew.sh\",\n }),\n );\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// Binary in-place replace\n// ---------------------------------------------------------------------------\n\nfunction isPermissionError(err: unknown): boolean {\n return (\n err instanceof Error &&\n \"code\" in err &&\n (err.code === \"EACCES\" || err.code === \"EPERM\")\n );\n}\n\nasync function sha256File(filePath: string): Promise<string> {\n const hash = createHash(\"sha256\");\n const { size } = await stat(filePath);\n if (size === 0) return hash.digest(\"hex\");\n\n // Read in 64 KB chunks\n const { createReadStream } = await import(\"node:fs\");\n const stream = createReadStream(filePath);\n await pipeline(stream, async function* (source) {\n for await (const chunk of source) {\n hash.update(chunk as Buffer);\n }\n });\n return hash.digest(\"hex\");\n}\n\n/**\n * Download a new binary and atomically replace the current one.\n *\n * macOS/Linux: rename(tmp, current) — safe because open files can be replaced\n * Windows: rename(current, .old) then rename(tmp, current) — avoids EBUSY\n * because Windows locks running executables\n */\nexport async function updateBinaryInPlace(\n assetUrl: string,\n expectedSha256: string,\n currentBinaryPath: string,\n options: { onProgress?: (downloaded: number, total: number) => void } = {},\n): Promise<void> {\n const dir = dirname(currentBinaryPath);\n const tmpPath = join(dir, `.gpc-update-${process.pid}.tmp`);\n const oldPath = join(dir, `.gpc-old-${process.pid}`);\n\n try {\n // 1. Download\n let response: Response;\n try {\n response = await fetch(assetUrl, {\n signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw Object.assign(new Error(`Download failed: ${msg}`), {\n code: \"UPDATE_DOWNLOAD_FAILED\",\n exitCode: 5,\n });\n }\n\n if (!response.ok) {\n throw Object.assign(new Error(`Download failed: HTTP ${response.status}`), {\n code: \"UPDATE_DOWNLOAD_FAILED\",\n exitCode: 4,\n });\n }\n if (!response.body) {\n throw Object.assign(new Error(\"Empty response body\"), {\n code: \"UPDATE_DOWNLOAD_FAILED\",\n exitCode: 4,\n });\n }\n\n const contentLength = response.headers.get(\"content-length\");\n const total = contentLength ? parseInt(contentLength, 10) : 0;\n let downloaded = 0;\n\n const dest = createWriteStream(tmpPath);\n const { onProgress } = options;\n\n if (onProgress) {\n const tracker = new Transform({\n transform(chunk: Buffer, _enc, cb) {\n downloaded += chunk.length;\n onProgress(downloaded, total);\n cb(null, chunk);\n },\n });\n await pipeline(\n Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]),\n tracker,\n dest,\n );\n } else {\n await pipeline(Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]), dest);\n }\n\n // 2. Verify checksum (skip if no checksum available)\n if (expectedSha256) {\n const actual = await sha256File(tmpPath);\n if (actual !== expectedSha256.toLowerCase()) {\n throw Object.assign(\n new Error(`Checksum mismatch — expected ${expectedSha256}, got ${actual}`),\n {\n code: \"UPDATE_CHECKSUM_MISMATCH\",\n exitCode: 1,\n suggestion: \"The download may be corrupt. Try again.\",\n },\n );\n }\n }\n\n // 3. Set executable bit (no-op on Windows)\n if (process.platform !== \"win32\") {\n await chmod(tmpPath, 0o755);\n }\n\n // 4. Atomic replace\n if (process.platform === \"win32\") {\n // Windows locks running executables, so we rename the current binary\n // out of the way first, then move the new one into place\n await rename(currentBinaryPath, oldPath);\n try {\n await rename(tmpPath, currentBinaryPath);\n } catch (renameErr) {\n // Roll back — restore original binary\n await rename(oldPath, currentBinaryPath).catch(() => {});\n throw renameErr;\n }\n // Delete the old binary in the background (best-effort)\n unlink(oldPath).catch(() => {});\n } else {\n await rename(tmpPath, currentBinaryPath);\n }\n } catch (err) {\n // Clean up temp file on any error\n await unlink(tmpPath).catch(() => {});\n\n if (isPermissionError(err)) {\n throw Object.assign(\n new Error(`Permission denied replacing ${currentBinaryPath}`),\n {\n code: \"UPDATE_PERMISSION_DENIED\",\n exitCode: 1,\n suggestion: `Run with elevated permissions: sudo gpc update`,\n },\n );\n }\n throw err;\n }\n}\n"],"mappings":"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","names":[]}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ detectInstallMethod
4
+ } from "./chunk-UH3TMIAL.js";
5
+ import "./chunk-4O4D5SGL.js";
6
+
7
+ // src/commands/feedback.ts
8
+ import { execFile } from "child_process";
9
+ function registerFeedbackCommand(program) {
10
+ program.command("feedback").description("Open a pre-filled GitHub issue with system diagnostics").option("--title <title>", "Issue title").action(async (opts) => {
11
+ const version = process.env["__GPC_VERSION"] || "0.0.0";
12
+ const body = [
13
+ "**GPC version:** " + version,
14
+ "**Node:** " + process.version,
15
+ "**Platform:** " + process.platform + "/" + process.arch,
16
+ "**Install method:** " + detectInstallMethod(),
17
+ "",
18
+ "**Describe the issue:**",
19
+ "<!-- Replace this with your bug description -->"
20
+ ].join("\n");
21
+ const params = new URLSearchParams({
22
+ title: opts.title ?? "Bug report",
23
+ body,
24
+ labels: "bug"
25
+ });
26
+ const url = `https://github.com/yasserstudio/gpc/issues/new?${params}`;
27
+ if (process.platform === "win32") {
28
+ execFile("cmd", ["/c", "start", "", url], (err) => {
29
+ if (err) console.log(`Open in your browser:
30
+ ${url}`);
31
+ else console.log("Opened GitHub issue form in your browser.");
32
+ });
33
+ } else {
34
+ const cmd = process.platform === "darwin" ? "open" : "xdg-open";
35
+ execFile(cmd, [url], (err) => {
36
+ if (err) console.log(`Open in your browser:
37
+ ${url}`);
38
+ else console.log("Opened GitHub issue form in your browser.");
39
+ });
40
+ }
41
+ });
42
+ }
43
+ export {
44
+ registerFeedbackCommand
45
+ };
46
+ //# sourceMappingURL=feedback-W5MZMRF2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/feedback.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { execFile } from \"node:child_process\";\nimport { detectInstallMethod } from \"../updater.js\";\n\nexport function registerFeedbackCommand(program: Command): void {\n program\n .command(\"feedback\")\n .description(\"Open a pre-filled GitHub issue with system diagnostics\")\n .option(\"--title <title>\", \"Issue title\")\n .action(async (opts) => {\n const version = process.env[\"__GPC_VERSION\"] || \"0.0.0\";\n const body = [\n \"**GPC version:** \" + version,\n \"**Node:** \" + process.version,\n \"**Platform:** \" + process.platform + \"/\" + process.arch,\n \"**Install method:** \" + detectInstallMethod(),\n \"\",\n \"**Describe the issue:**\",\n \"<!-- Replace this with your bug description -->\",\n ].join(\"\\n\");\n const params = new URLSearchParams({\n title: opts.title ?? \"Bug report\",\n body,\n labels: \"bug\",\n });\n const url = `https://github.com/yasserstudio/gpc/issues/new?${params}`;\n if (process.platform === \"win32\") {\n execFile(\"cmd\", [\"/c\", \"start\", \"\", url], (err) => {\n if (err) console.log(`Open in your browser:\\n${url}`);\n else console.log(\"Opened GitHub issue form in your browser.\");\n });\n } else {\n const cmd = process.platform === \"darwin\" ? \"open\" : \"xdg-open\";\n execFile(cmd, [url], (err) => {\n if (err) console.log(`Open in your browser:\\n${url}`);\n else console.log(\"Opened GitHub issue form in your browser.\");\n });\n }\n });\n}\n"],"mappings":";;;;;;;AACA,SAAS,gBAAgB;AAGlB,SAAS,wBAAwB,SAAwB;AAC9D,UACG,QAAQ,UAAU,EAClB,YAAY,wDAAwD,EACpE,OAAO,mBAAmB,aAAa,EACvC,OAAO,OAAO,SAAS;AACtB,UAAM,UAAU,QAAQ,IAAI,eAAe,KAAK;AAChD,UAAM,OAAO;AAAA,MACX,sBAAsB;AAAA,MACtB,eAAe,QAAQ;AAAA,MACvB,mBAAmB,QAAQ,WAAW,MAAM,QAAQ;AAAA,MACpD,yBAAyB,oBAAoB;AAAA,MAC7C;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AACX,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,OAAO,KAAK,SAAS;AAAA,MACrB;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AACD,UAAM,MAAM,kDAAkD,MAAM;AACpE,QAAI,QAAQ,aAAa,SAAS;AAChC,eAAS,OAAO,CAAC,MAAM,SAAS,IAAI,GAAG,GAAG,CAAC,QAAQ;AACjD,YAAI,IAAK,SAAQ,IAAI;AAAA,EAA0B,GAAG,EAAE;AAAA,YAC/C,SAAQ,IAAI,2CAA2C;AAAA,MAC9D,CAAC;AAAA,IACH,OAAO;AACL,YAAM,MAAM,QAAQ,aAAa,WAAW,SAAS;AACrD,eAAS,KAAK,CAAC,GAAG,GAAG,CAAC,QAAQ;AAC5B,YAAI,IAAK,SAAQ,IAAI;AAAA,EAA0B,GAAG,EAAE;AAAA,YAC/C,SAAQ,IAAI,2CAA2C;AAAA,MAC9D,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACL;","names":[]}
File without changes
File without changes
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  createProgram,
4
4
  handleCliError
5
- } from "./chunk-5VVYPCDE.js";
5
+ } from "./chunk-DMMFPBYF.js";
6
6
  export {
7
7
  createProgram,
8
8
  handleCliError
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -15,10 +15,10 @@ import {
15
15
 
16
16
  // src/commands/releases.ts
17
17
  import { appendFile, stat } from "fs/promises";
18
- import { basename } from "path";
18
+ import { basename, extname } from "path";
19
19
  import { loadConfig } from "@gpc-cli/config";
20
20
  import { resolveAuth } from "@gpc-cli/auth";
21
- import { createApiClient } from "@gpc-cli/api";
21
+ import { createApiClient, createReportingClient } from "@gpc-cli/api";
22
22
  import {
23
23
  uploadRelease,
24
24
  getReleasesStatus,
@@ -29,7 +29,9 @@ import {
29
29
  writeAuditLog,
30
30
  createAuditEntry,
31
31
  uploadExternallyHosted,
32
- diffReleases
32
+ diffReleases,
33
+ getVitalsCrashes,
34
+ checkThreshold
33
35
  } from "@gpc-cli/core";
34
36
  import { formatOutput, sortResults, createSpinner } from "@gpc-cli/core";
35
37
  function resolvePackageName(packageArg, config) {
@@ -61,6 +63,11 @@ function registerReleasesCommands(program) {
61
63
  console.error(`Error: File not found: ${file}`);
62
64
  process.exit(2);
63
65
  }
66
+ const ext = extname(file).toLowerCase();
67
+ if (ext !== ".aab" && ext !== ".apk") {
68
+ console.error(`Error: Expected .aab or .apk file, got "${ext || "(no extension)"}"`);
69
+ process.exit(2);
70
+ }
64
71
  const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);
65
72
  if (noteSources.length > 1) {
66
73
  console.error("Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.");
@@ -96,7 +103,22 @@ function registerReleasesCommands(program) {
96
103
  }
97
104
  }
98
105
  const { size: fileSize } = await stat(file);
106
+ const jsonMode = format === "json";
99
107
  const client = await getClient(config, options.retryLog, options.timeout);
108
+ const showProgress = !jsonMode && process.stderr.isTTY && !program.opts()["quiet"];
109
+ const sizeMB = (fileSize / (1024 * 1024)).toFixed(1);
110
+ const FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
111
+ let progressInterval;
112
+ if (showProgress) {
113
+ let frame = 0;
114
+ const startTime = Date.now();
115
+ progressInterval = setInterval(() => {
116
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(0);
117
+ process.stderr.write(`\r ${FRAMES[frame % FRAMES.length]} Uploading ${basename(file)} ${sizeMB} MB (${elapsed}s) `);
118
+ frame++;
119
+ }, 120);
120
+ }
121
+ const onProgress = void 0;
100
122
  if (isDryRun(program)) {
101
123
  try {
102
124
  const result = await uploadRelease(client, packageName, file, {
@@ -120,9 +142,8 @@ function registerReleasesCommands(program) {
120
142
  },
121
143
  packageName
122
144
  );
123
- const sizeMB = (fileSize / (1024 * 1024)).toFixed(1);
124
145
  const spinner = createSpinner(`Uploading ${basename(file)} (${sizeMB} MB)...`);
125
- if (!program.opts()["quiet"] && process.stderr.isTTY) spinner.start();
146
+ if (!showProgress) spinner.start();
126
147
  try {
127
148
  let releaseNotes;
128
149
  if (options.notesFromGit) {
@@ -138,12 +159,22 @@ function registerReleasesCommands(program) {
138
159
  userFraction: options.rollout ? Number(options.rollout) / 100 : void 0,
139
160
  releaseNotes,
140
161
  releaseName: options.name,
141
- mappingFile: options.mapping
162
+ mappingFile: options.mapping,
163
+ onProgress
142
164
  });
165
+ if (progressInterval) {
166
+ clearInterval(progressInterval);
167
+ process.stderr.write(`\r \u2713 Uploaded ${basename(file)} ${sizeMB} MB
168
+ `);
169
+ }
143
170
  spinner.stop("Upload complete");
144
171
  console.log(formatOutput(result, format));
145
172
  auditEntry.success = true;
146
173
  } catch (error) {
174
+ if (progressInterval) {
175
+ clearInterval(progressInterval);
176
+ process.stderr.write("\n");
177
+ }
147
178
  spinner.fail("Upload failed");
148
179
  auditEntry.success = false;
149
180
  auditEntry.error = error instanceof Error ? error.message : String(error);
@@ -162,7 +193,8 @@ function registerReleasesCommands(program) {
162
193
  const format = getOutputFormat(program, config);
163
194
  try {
164
195
  const TRACK_ORDER = ["production", "beta", "alpha", "internal"];
165
- const statuses = await getReleasesStatus(client, packageName, options.track);
196
+ const rawStatuses = await getReleasesStatus(client, packageName, options.track);
197
+ const statuses = options.track ? Array.isArray(rawStatuses) ? rawStatuses.filter((s) => s.track === options.track) : rawStatuses : rawStatuses;
166
198
  const sorted = Array.isArray(statuses) ? options.sort ? sortResults(statuses, options.sort) : [...statuses].sort((a, b) => {
167
199
  const ai = TRACK_ORDER.indexOf(String(a["track"] ?? ""));
168
200
  const bi = TRACK_ORDER.indexOf(String(b["track"] ?? ""));
@@ -250,6 +282,7 @@ function registerReleasesCommands(program) {
250
282
  const cmd = rollout.command(action).description(`${action.charAt(0).toUpperCase() + action.slice(1)} a staged rollout`).option("--track <track>", "Track name");
251
283
  if (action === "increase") {
252
284
  cmd.option("--to <percent>", "New rollout percentage");
285
+ cmd.option("--vitals-gate", "Halt rollout if crash rate exceeds configured threshold");
253
286
  }
254
287
  cmd.action(async (options) => {
255
288
  const config = await loadConfig();
@@ -284,6 +317,9 @@ function registerReleasesCommands(program) {
284
317
  }
285
318
  }
286
319
  if (isDryRun(program)) {
320
+ if (action === "increase" && options.vitalsGate) {
321
+ console.error("Warning: --vitals-gate is ignored in --dry-run mode. Gate will run on live execution.");
322
+ }
287
323
  printDryRun(
288
324
  {
289
325
  command: `releases rollout ${action}`,
@@ -305,6 +341,28 @@ function registerReleasesCommands(program) {
305
341
  action,
306
342
  options.to ? Number(options.to) / 100 : void 0
307
343
  );
344
+ if (action === "increase" && options.vitalsGate) {
345
+ const threshold = config.vitals?.thresholds?.crashRate;
346
+ if (!threshold) {
347
+ console.error("Warning: --vitals-gate requires vitals.thresholds.crashRate in config. Skipping gate.");
348
+ } else {
349
+ try {
350
+ const { auth: authConfig } = config;
351
+ const vitalsAuth = await resolveAuth({ serviceAccountPath: authConfig?.serviceAccount });
352
+ const reportingClient = createReportingClient({ auth: vitalsAuth });
353
+ const vitalsResult = await getVitalsCrashes(reportingClient, packageName, { days: 1 });
354
+ const latest = vitalsResult.data?.[0]?.crashRate;
355
+ const check = checkThreshold(latest, threshold);
356
+ if (check.breached) {
357
+ await updateRollout(client, packageName, options.track, "halt");
358
+ console.error(`Vitals gate: crash rate ${String(latest)}% > threshold ${String(threshold)}%. Rollout halted.`);
359
+ process.exit(6);
360
+ }
361
+ } catch (vitalsErr) {
362
+ console.error(`Warning: Vitals gate check failed: ${vitalsErr instanceof Error ? vitalsErr.message : String(vitalsErr)}`);
363
+ }
364
+ }
365
+ }
308
366
  console.log(formatOutput(result, format));
309
367
  } catch (error) {
310
368
  console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
@@ -312,38 +370,30 @@ function registerReleasesCommands(program) {
312
370
  }
313
371
  });
314
372
  }
315
- releases.command("notes").description("Set release notes").argument("<action>", "Action: set").option("--track <track>", "Track name").option("--lang <language>", "Language code", "en-US").option("--notes <text>", "Release notes text").option("--file <path>", "Read notes from file").action(async (action, options) => {
316
- if (action !== "set") {
317
- console.error("Usage: gpc releases notes set --track <track> --notes <text>");
318
- process.exit(2);
319
- }
320
- const interactive = isInteractive(program);
321
- const tracks = ["internal", "alpha", "beta", "production"];
322
- options.track = await requireOption(
323
- "track",
324
- options.track,
325
- {
326
- message: "Track:",
327
- choices: tracks
328
- },
329
- interactive
330
- );
331
- let notesText = options.notes;
332
- if (options.file) {
333
- const { readFile } = await import("fs/promises");
334
- notesText = await readFile(options.file, "utf-8");
335
- }
336
- if (!notesText && interactive) {
337
- notesText = await promptInput("Release notes text:");
373
+ releases.command("notes").description("Get or set release notes").argument("<action>", "Action: get|set").option("--track <track>", "Track name").option("--lang <language>", "Language code", "en-US").option("--notes <text>", "Release notes text").option("--file <path>", "Read notes from file").action(async (action, options) => {
374
+ if (action === "set") {
375
+ console.error(
376
+ "Error: gpc releases notes set is not implemented as a standalone command.\nUse --notes, --notes-dir, or --notes-from-git with:\n gpc releases upload\n gpc publish"
377
+ );
378
+ process.exit(1);
338
379
  }
339
- if (!notesText) {
340
- console.error("Provide --notes <text> or --file <path>");
341
- process.exit(2);
380
+ if (action === "get") {
381
+ const config = await loadConfig();
382
+ const packageName = resolvePackageName(program.opts()["app"], config);
383
+ const client = await getClient(config);
384
+ const format = getOutputFormat(program, config);
385
+ const track = options.track ?? "internal";
386
+ const statuses = await getReleasesStatus(client, packageName, track);
387
+ const notes = Array.isArray(statuses) ? statuses.flatMap((s) => s.releaseNotes ?? []) : statuses.releaseNotes ?? [];
388
+ if (notes.length === 0) {
389
+ console.log("No release notes found.");
390
+ return;
391
+ }
392
+ console.log(formatOutput(notes, format));
393
+ return;
342
394
  }
343
- console.error(
344
- "Error: gpc releases notes set is not yet implemented as a standalone command.\nTo set release notes, use --notes, --notes-dir, or --notes-from-git with:\n gpc releases upload\n gpc publish"
345
- );
346
- process.exit(1);
395
+ console.error("Usage: gpc releases notes <get|set> --track <track>");
396
+ process.exit(2);
347
397
  });
348
398
  releases.command("upload-external").description("Upload an externally hosted APK configuration").requiredOption("--url <url>", "External URL where the APK is hosted").requiredOption("--file <config>", "Path to JSON config file with APK metadata").action(async (options) => {
349
399
  const config = await loadConfig();
@@ -394,4 +444,4 @@ function registerReleasesCommands(program) {
394
444
  export {
395
445
  registerReleasesCommands
396
446
  };
397
- //# sourceMappingURL=releases-JMRKXEZU.js.map
447
+ //# sourceMappingURL=releases-ANC54YWF.js.map