@gpc-cli/cli 0.9.52 → 0.9.54

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 (160) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -19
  3. package/dist/{anomalies-V3AFS4LD.js → anomalies-I5P6QQAZ.js} +14 -5
  4. package/dist/anomalies-I5P6QQAZ.js.map +1 -0
  5. package/dist/{apps-4GP3FD7O.js → apps-WPUQOQLL.js} +2 -2
  6. package/dist/{audit-VTWXTXC6.js → audit-M7GYID74.js} +2 -2
  7. package/dist/{auth-BA4FE2PO.js → auth-4DRT7ZH2.js} +10 -4
  8. package/dist/auth-4DRT7ZH2.js.map +1 -0
  9. package/dist/bin.js +2 -2
  10. package/dist/{bundle-F7MUVC5J.js → bundle-PFTE7XMU.js} +4 -2
  11. package/dist/bundle-PFTE7XMU.js.map +1 -0
  12. package/dist/{cache-XKPLZYEB.js → cache-FGNP7Y37.js} +6 -2
  13. package/dist/cache-FGNP7Y37.js.map +1 -0
  14. package/dist/changelog-QLDFG5TV.js +0 -0
  15. package/dist/{chunk-SLNJEAMK.js → chunk-22XCOLZX.js} +4 -2
  16. package/dist/chunk-22XCOLZX.js.map +1 -0
  17. package/dist/chunk-3SJ6OXCZ.js +0 -0
  18. package/dist/{chunk-FXOWADQD.js → chunk-6HIY4IGM.js} +48 -45
  19. package/dist/chunk-6HIY4IGM.js.map +1 -0
  20. package/dist/{chunk-BCBXQC7J.js → chunk-E7SVZ7RF.js} +1 -1
  21. package/dist/chunk-E7SVZ7RF.js.map +1 -0
  22. package/dist/chunk-ELXAK7GI.js +0 -0
  23. package/dist/chunk-FAN4ZITI.js +0 -0
  24. package/dist/{chunk-NQH4G7BI.js → chunk-JDRY7HK5.js} +6 -9
  25. package/dist/chunk-JDRY7HK5.js.map +1 -0
  26. package/dist/{chunk-YFUBD2XB.js → chunk-RZQSEDKI.js} +6 -9
  27. package/dist/chunk-RZQSEDKI.js.map +1 -0
  28. package/dist/{chunk-A7VRCCNS.js → chunk-WSLFHX5X.js} +3 -3
  29. package/dist/chunk-WSLFHX5X.js.map +1 -0
  30. package/dist/chunk-Y3QZDAKS.js +0 -0
  31. package/dist/completion-BCHRJSAT.js +0 -0
  32. package/dist/{config-BLMJ35J2.js → config-PUINDZON.js} +3 -3
  33. package/dist/{data-safety-AFMD6MYI.js → data-safety-46VY64OO.js} +12 -4
  34. package/dist/data-safety-46VY64OO.js.map +1 -0
  35. package/dist/{device-tiers-AQAMUQXI.js → device-tiers-MNZYMG3Y.js} +2 -2
  36. package/dist/device-tiers-MNZYMG3Y.js.map +1 -0
  37. package/dist/{diff-6EO4ID6W.js → diff-OBSHUSTL.js} +2 -2
  38. package/dist/diff-OBSHUSTL.js.map +1 -0
  39. package/dist/{docs-GMFN6V4K.js → docs-GP6AEX4N.js} +8 -4
  40. package/dist/docs-GP6AEX4N.js.map +1 -0
  41. package/dist/{doctor-7LQWPY5P.js → doctor-T3QFYBRV.js} +5 -5
  42. package/dist/doctor-T3QFYBRV.js.map +1 -0
  43. package/dist/enterprise-7PWXMSUN.js +0 -0
  44. package/dist/{external-transactions-LCZALS3V.js → external-transactions-JL3G4IG5.js} +11 -5
  45. package/dist/external-transactions-JL3G4IG5.js.map +1 -0
  46. package/dist/{feedback-7ADYSGRD.js → feedback-AULXQLJ7.js} +3 -3
  47. package/dist/feedback-AULXQLJ7.js.map +1 -0
  48. package/dist/{games-ZSNGEI7A.js → games-SVFN2YIS.js} +2 -2
  49. package/dist/games-SVFN2YIS.js.map +1 -0
  50. package/dist/{generated-apks-RX2IUWSF.js → generated-apks-TC33S2YN.js} +2 -2
  51. package/dist/generated-apks-TC33S2YN.js.map +1 -0
  52. package/dist/{grants-EBPECI26.js → grants-UHNBPIFD.js} +10 -3
  53. package/dist/grants-UHNBPIFD.js.map +1 -0
  54. package/dist/{iap-OUI5YYN4.js → iap-ETOL7OAC.js} +4 -4
  55. package/dist/iap-ETOL7OAC.js.map +1 -0
  56. package/dist/index.js +1 -1
  57. package/dist/{init-WSTQTJOD.js → init-6CCSVJC2.js} +7 -3
  58. package/dist/init-6CCSVJC2.js.map +1 -0
  59. package/dist/{install-skills-JKPYZHYS.js → install-skills-S342NJ7T.js} +7 -3
  60. package/dist/{install-skills-JKPYZHYS.js.map → install-skills-S342NJ7T.js.map} +1 -1
  61. package/dist/{internal-sharing-ONNIWIAT.js → internal-sharing-KW6YENDG.js} +2 -2
  62. package/dist/internal-sharing-KW6YENDG.js.map +1 -0
  63. package/dist/{listings-LNX6MQYN.js → listings-MOHHHNE6.js} +27 -7
  64. package/dist/listings-MOHHHNE6.js.map +1 -0
  65. package/dist/migrate-ZQCJGQQS.js +0 -0
  66. package/dist/one-time-products-Y5RNIPV2.js +472 -0
  67. package/dist/one-time-products-Y5RNIPV2.js.map +1 -0
  68. package/dist/{preflight-W3JAJ4GO.js → preflight-KIWZPFTX.js} +4 -13
  69. package/dist/preflight-KIWZPFTX.js.map +1 -0
  70. package/dist/{pricing-JJZFICFL.js → pricing-HYQRXKNO.js} +3 -3
  71. package/dist/pricing-HYQRXKNO.js.map +1 -0
  72. package/dist/{prompt-GXC2JSLA.js → prompt-HJXNXXAR.js} +2 -2
  73. package/dist/{publish-P5KIGSLI.js → publish-26SSZ2W3.js} +11 -5
  74. package/dist/publish-26SSZ2W3.js.map +1 -0
  75. package/dist/purchase-options-KFWW4JW2.js +0 -0
  76. package/dist/{purchases-UBFLNYZC.js → purchases-AHWGLW6V.js} +35 -32
  77. package/dist/purchases-AHWGLW6V.js.map +1 -0
  78. package/dist/{quickstart-Z5Y3FYJU.js → quickstart-FOWM3OKT.js} +7 -2
  79. package/dist/quickstart-FOWM3OKT.js.map +1 -0
  80. package/dist/quota-MZRWYJGR.js +0 -0
  81. package/dist/{recovery-YE3Z7NIN.js → recovery-B7DZQ6XG.js} +28 -12
  82. package/dist/recovery-B7DZQ6XG.js.map +1 -0
  83. package/dist/{releases-LUAHKIMY.js → releases-XY57V22L.js} +33 -8
  84. package/dist/releases-XY57V22L.js.map +1 -0
  85. package/dist/reports-CIB2T3XT.js +0 -0
  86. package/dist/{reviews-YCBBM656.js → reviews-P4M6BEJI.js} +4 -6
  87. package/dist/reviews-P4M6BEJI.js.map +1 -0
  88. package/dist/{rtdn-LID2B7XZ.js → rtdn-YII4H6SD.js} +29 -21
  89. package/dist/rtdn-YII4H6SD.js.map +1 -0
  90. package/dist/{status-3HXBBXG6.js → status-32AJ7A6O.js} +13 -32
  91. package/dist/status-32AJ7A6O.js.map +1 -0
  92. package/dist/{subscriptions-LURZFPGJ.js → subscriptions-GTVJB4HX.js} +49 -12
  93. package/dist/subscriptions-GTVJB4HX.js.map +1 -0
  94. package/dist/system-apks-L6M7QYB3.js +111 -0
  95. package/dist/system-apks-L6M7QYB3.js.map +1 -0
  96. package/dist/{testers-6CQL4KQV.js → testers-QUWZHO6M.js} +25 -7
  97. package/dist/testers-QUWZHO6M.js.map +1 -0
  98. package/dist/{tracks-I4QZNZ3M.js → tracks-RH3RKVFB.js} +9 -3
  99. package/dist/{tracks-I4QZNZ3M.js.map → tracks-RH3RKVFB.js.map} +1 -1
  100. package/dist/{train-MDD2EBHS.js → train-D2NKUW3M.js} +3 -3
  101. package/dist/train-D2NKUW3M.js.map +1 -0
  102. package/dist/{update-FZ3MNLOH.js → update-73YOR4GP.js} +8 -11
  103. package/dist/{update-FZ3MNLOH.js.map → update-73YOR4GP.js.map} +1 -1
  104. package/dist/{users-UKG7VIQH.js → users-FOMAT7DY.js} +2 -2
  105. package/dist/{validate-QIYSA3N7.js → validate-RHWEZYD2.js} +6 -2
  106. package/dist/validate-RHWEZYD2.js.map +1 -0
  107. package/dist/{verify-UUQNQMPG.js → verify-H4ZUVHMZ.js} +5 -13
  108. package/dist/verify-H4ZUVHMZ.js.map +1 -0
  109. package/dist/{version-VHQBXU2I.js → version-RXLEX62V.js} +3 -3
  110. package/dist/{vitals-PJEQUUAK.js → vitals-PRBPNMVC.js} +26 -8
  111. package/dist/vitals-PRBPNMVC.js.map +1 -0
  112. package/package.json +18 -18
  113. package/dist/anomalies-V3AFS4LD.js.map +0 -1
  114. package/dist/auth-BA4FE2PO.js.map +0 -1
  115. package/dist/bundle-F7MUVC5J.js.map +0 -1
  116. package/dist/cache-XKPLZYEB.js.map +0 -1
  117. package/dist/chunk-A7VRCCNS.js.map +0 -1
  118. package/dist/chunk-BCBXQC7J.js.map +0 -1
  119. package/dist/chunk-FXOWADQD.js.map +0 -1
  120. package/dist/chunk-NQH4G7BI.js.map +0 -1
  121. package/dist/chunk-SLNJEAMK.js.map +0 -1
  122. package/dist/chunk-YFUBD2XB.js.map +0 -1
  123. package/dist/data-safety-AFMD6MYI.js.map +0 -1
  124. package/dist/device-tiers-AQAMUQXI.js.map +0 -1
  125. package/dist/diff-6EO4ID6W.js.map +0 -1
  126. package/dist/docs-GMFN6V4K.js.map +0 -1
  127. package/dist/doctor-7LQWPY5P.js.map +0 -1
  128. package/dist/external-transactions-LCZALS3V.js.map +0 -1
  129. package/dist/feedback-7ADYSGRD.js.map +0 -1
  130. package/dist/games-ZSNGEI7A.js.map +0 -1
  131. package/dist/generated-apks-RX2IUWSF.js.map +0 -1
  132. package/dist/grants-EBPECI26.js.map +0 -1
  133. package/dist/iap-OUI5YYN4.js.map +0 -1
  134. package/dist/init-WSTQTJOD.js.map +0 -1
  135. package/dist/internal-sharing-ONNIWIAT.js.map +0 -1
  136. package/dist/listings-LNX6MQYN.js.map +0 -1
  137. package/dist/one-time-products-MGZTU7OM.js +0 -254
  138. package/dist/one-time-products-MGZTU7OM.js.map +0 -1
  139. package/dist/preflight-W3JAJ4GO.js.map +0 -1
  140. package/dist/pricing-JJZFICFL.js.map +0 -1
  141. package/dist/publish-P5KIGSLI.js.map +0 -1
  142. package/dist/purchases-UBFLNYZC.js.map +0 -1
  143. package/dist/quickstart-Z5Y3FYJU.js.map +0 -1
  144. package/dist/recovery-YE3Z7NIN.js.map +0 -1
  145. package/dist/releases-LUAHKIMY.js.map +0 -1
  146. package/dist/reviews-YCBBM656.js.map +0 -1
  147. package/dist/rtdn-LID2B7XZ.js.map +0 -1
  148. package/dist/status-3HXBBXG6.js.map +0 -1
  149. package/dist/subscriptions-LURZFPGJ.js.map +0 -1
  150. package/dist/testers-6CQL4KQV.js.map +0 -1
  151. package/dist/train-MDD2EBHS.js.map +0 -1
  152. package/dist/validate-QIYSA3N7.js.map +0 -1
  153. package/dist/verify-UUQNQMPG.js.map +0 -1
  154. package/dist/vitals-PJEQUUAK.js.map +0 -1
  155. /package/dist/{apps-4GP3FD7O.js.map → apps-WPUQOQLL.js.map} +0 -0
  156. /package/dist/{audit-VTWXTXC6.js.map → audit-M7GYID74.js.map} +0 -0
  157. /package/dist/{config-BLMJ35J2.js.map → config-PUINDZON.js.map} +0 -0
  158. /package/dist/{prompt-GXC2JSLA.js.map → prompt-HJXNXXAR.js.map} +0 -0
  159. /package/dist/{users-UKG7VIQH.js.map → users-FOMAT7DY.js.map} +0 -0
  160. /package/dist/{version-VHQBXU2I.js.map → version-RXLEX62V.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/releases.ts"],"sourcesContent":["import { appendFile, stat } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient, createReportingClient } from \"@gpc-cli/api\";\nimport type { RetryLogEntry, ExternallyHostedApk, UploadProgressEvent } from \"@gpc-cli/api\";\nimport {\n uploadRelease,\n getReleasesStatus,\n promoteRelease,\n updateRollout,\n readReleaseNotesFromDir,\n generateNotesFromGit,\n writeAuditLog,\n createAuditEntry,\n uploadExternallyHosted,\n diffReleases,\n fetchReleaseNotes,\n getVitalsCrashes,\n checkThreshold,\n GpcError,\n} from \"@gpc-cli/core\";\nimport { formatOutput, sortResults, createSpinner, maybePaginate } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport {\n isInteractive,\n promptSelect,\n promptInput,\n requireOption,\n requireConfirm,\n} from \"../prompt.js\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n throw new GpcError(\n \"No package name. Use --app <package> or gpc config set app <package>\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Set a default app: gpc config set app <package>\",\n );\n }\n return name;\n}\n\nimport { buildCommitOptions } from \"../commit-options.js\";\n\nfunction createRetryLogger(retryLogPath?: string): ((entry: RetryLogEntry) => void) | undefined {\n if (!retryLogPath) return undefined;\n return (entry: RetryLogEntry) => {\n const line = JSON.stringify(entry) + \"\\n\";\n appendFile(retryLogPath, line).catch(() => {});\n };\n}\n\nasync function getClient(config: GpcConfig, retryLogPath?: string, uploadTimeout?: number) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createApiClient({ auth, onRetry: createRetryLogger(retryLogPath), uploadTimeout });\n}\n\nexport function registerReleasesCommands(program: Command): void {\n const releases = program.command(\"releases\").description(\"Manage releases and rollouts\");\n\n // Upload\n releases\n .command(\"upload <file>\")\n .description(\"Upload AAB/APK and assign to a track\")\n .option(\"--track <track>\", \"Target track\", \"internal\")\n .option(\"--rollout <percent>\", \"Staged rollout percentage (1-100)\")\n .option(\"--notes <text>\", \"Release notes (en-US)\")\n .option(\"--name <name>\", \"Release name\")\n .option(\"--mapping <file>\", \"ProGuard/R8 mapping file for deobfuscation\")\n .option(\"--notes-dir <dir>\", \"Read release notes from directory (<dir>/<lang>.txt)\")\n .option(\"--notes-from-git\", \"Generate release notes from git commit history\")\n .option(\"--copy-notes-from <track>\", \"Copy release notes from another track\")\n .option(\"--since <ref>\", \"Git ref to start from (tag, SHA) — used with --notes-from-git\")\n .option(\"--retry-log <path>\", \"Write retry log entries to file (JSONL)\")\n .option(\n \"--timeout <ms>\",\n \"Upload timeout in milliseconds (auto-scales with file size by default)\",\n parseInt,\n )\n .option(\n \"--status <status>\",\n \"Release status: completed, inProgress, draft, halted\",\n \"completed\",\n )\n .option(\"--mapping-type <type>\", \"Deobfuscation file type: proguard or nativeCode\", \"proguard\")\n .option(\"--device-tier-config <id>\", \"Device tier config ID (or LATEST)\")\n .option(\n \"--changes-not-sent-for-review\",\n \"Commit changes without sending for review (required for rejected apps)\",\n )\n .option(\n \"--error-if-in-review\",\n \"Fail if changes are already in review instead of cancelling them\",\n )\n .action(async (file: string, options) => {\n try {\n await stat(file);\n } catch {\n throw new GpcError(\n `File not found: ${file}`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Check the file path and try again.\",\n );\n }\n\n const ext = extname(file).toLowerCase();\n if (ext !== \".aab\" && ext !== \".apk\") {\n throw new GpcError(\n `Expected .aab or .apk file, got \"${ext || \"(no extension)\"}\"`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Provide a .aab or .apk file.\",\n );\n }\n\n const noteSources = [\n options.notes,\n options.notesDir,\n options.notesFromGit,\n options.copyNotesFrom,\n ].filter(Boolean);\n if (noteSources.length > 1) {\n throw new GpcError(\n \"Cannot combine --notes, --notes-dir, --notes-from-git, and --copy-notes-from. Use only one.\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Pick one release notes source.\",\n );\n }\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n\n // Interactive mode: prompt for missing options\n if (isInteractive(program)) {\n if (!options.track || options.track === \"internal\") {\n const tracks = [\"internal\", \"alpha\", \"beta\", \"production\"];\n options.track = await promptSelect(\"Select track:\", tracks, \"internal\");\n }\n\n if (!options.rollout && options.track === \"production\") {\n const rolloutStr = await promptInput(\n \"Staged rollout percentage (1-100, blank for full)\",\n \"100\",\n );\n if (rolloutStr && rolloutStr !== \"100\") {\n options.rollout = rolloutStr;\n }\n }\n\n if (!options.notes && !options.notesDir) {\n const notes = await promptInput(\"Release notes (en-US, blank to skip)\");\n if (notes) options.notes = notes;\n }\n }\n\n if (options.rollout !== undefined) {\n const rollout = Number(options.rollout);\n if (!Number.isFinite(rollout) || rollout < 1 || rollout > 100) {\n throw new GpcError(\n `--rollout must be a number between 1 and 100 (got: ${options.rollout})`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use a percentage between 1 and 100.\",\n );\n }\n }\n\n if (\n options.mappingType &&\n options.mappingType !== \"proguard\" &&\n options.mappingType !== \"nativeCode\"\n ) {\n throw new GpcError(\n `--mapping-type must be \"proguard\" or \"nativeCode\" (got: \"${options.mappingType}\")`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use --mapping-type proguard (default) for ProGuard/R8 maps, or --mapping-type nativeCode for native debug symbols.\",\n );\n }\n\n const { size: fileSize } = await stat(file);\n const jsonMode = format === \"json\";\n const client = await getClient(config, options.retryLog, options.timeout);\n\n const showProgress = !jsonMode && process.stderr.isTTY && !program.opts()[\"quiet\"];\n const sizeMB = (fileSize / (1024 * 1024)).toFixed(1);\n\n function formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${bytes} B`;\n }\n\n const BAR_WIDTH = 20;\n const onUploadProgress = showProgress\n ? (event: UploadProgressEvent) => {\n const filled = Math.round((event.percent / 100) * BAR_WIDTH);\n const bar = \"█\".repeat(filled) + \"░\".repeat(BAR_WIDTH - filled);\n const uploaded = formatBytes(event.bytesUploaded);\n const total = formatBytes(event.totalBytes);\n const speed =\n event.bytesPerSecond > 0 ? `${formatBytes(event.bytesPerSecond)}/s` : \"...\";\n const eta = event.etaSeconds > 0 ? `ETA ${event.etaSeconds}s` : \"\";\n process.stderr.write(\n `\\r ${bar} ${event.percent}% ${uploaded}/${total} ${speed} ${eta}\\x1b[K`,\n );\n }\n : undefined;\n\n if (isDryRun(program)) {\n const result = await uploadRelease(client, packageName, file, {\n track: options.track,\n status: options.status,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n mappingFileType: options.mappingType,\n deviceTierConfigId: options.deviceTierConfig,\n dryRun: true,\n });\n console.log(formatOutput(result, format));\n return;\n }\n\n const auditEntry = createAuditEntry(\n \"releases upload\",\n {\n file,\n track: options.track,\n rollout: options.rollout,\n },\n packageName,\n );\n\n const spinner = createSpinner(`Uploading ${basename(file)} (${sizeMB} MB)...`);\n if (!showProgress) spinner.start();\n\n try {\n let releaseNotes: { language: string; text: string }[] | undefined;\n if (options.copyNotesFrom) {\n releaseNotes = await fetchReleaseNotes(client, packageName, options.copyNotesFrom);\n } else if (options.notesFromGit) {\n const gitNotes = await generateNotesFromGit({ since: options.since });\n releaseNotes = [{ language: gitNotes.language, text: gitNotes.text }];\n } else if (options.notesDir) {\n releaseNotes = await readReleaseNotesFromDir(options.notesDir);\n } else if (options.notes) {\n releaseNotes = [{ language: \"en-US\", text: options.notes }];\n }\n\n const result = await uploadRelease(client, packageName, file, {\n track: options.track,\n status: options.status,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n releaseNotes,\n releaseName: options.name,\n mappingFile: options.mapping,\n mappingFileType: options.mappingType,\n deviceTierConfigId: options.deviceTierConfig,\n onUploadProgress,\n commitOptions: buildCommitOptions(options),\n });\n if (showProgress) {\n process.stderr.write(`\\r ✓ Uploaded ${basename(file)} ${sizeMB} MB\\x1b[K\\n`);\n }\n spinner.stop(\"Upload complete\");\n console.log(formatOutput(result, format));\n auditEntry.success = true;\n } catch (error) {\n if (showProgress) {\n process.stderr.write(\"\\n\");\n }\n spinner.fail(\"Upload failed\");\n auditEntry.success = false;\n auditEntry.error = error instanceof Error ? error.message : String(error);\n throw error;\n } finally {\n auditEntry.durationMs = Date.now() - new Date(auditEntry.timestamp).getTime();\n writeAuditLog(auditEntry).catch(() => {});\n }\n });\n\n // Status\n releases\n .command(\"status\")\n .description(\"Show current release status across tracks\")\n .option(\"--track <track>\", \"Filter by track\")\n .option(\"--sort <field>\", \"Sort by field (prefix with - for descending)\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const TRACK_ORDER = [\"production\", \"beta\", \"alpha\", \"internal\"];\n const rawStatuses = await getReleasesStatus(client, packageName, options.track);\n const statuses = options.track\n ? Array.isArray(rawStatuses)\n ? rawStatuses.filter((s: any) => s.track === options.track)\n : rawStatuses\n : rawStatuses;\n const sorted = Array.isArray(statuses)\n ? options.sort\n ? sortResults(statuses, options.sort)\n : [...statuses].sort((a, b) => {\n const ai = TRACK_ORDER.indexOf(\n String((a as unknown as Record<string, unknown>)[\"track\"] ?? \"\"),\n );\n const bi = TRACK_ORDER.indexOf(\n String((b as unknown as Record<string, unknown>)[\"track\"] ?? \"\"),\n );\n return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);\n })\n : statuses;\n if (format !== \"json\" && Array.isArray(sorted)) {\n const rows = sorted.map((s: unknown) => {\n const sr = s as Record<string, unknown>;\n return {\n track: sr[\"track\"] || \"-\",\n status: sr[\"status\"] || \"-\",\n name: sr[\"name\"] || \"-\",\n versionCodes: Array.isArray(sr[\"versionCodes\"])\n ? (sr[\"versionCodes\"] as unknown[]).join(\", \")\n : \"-\",\n userFraction:\n sr[\"userFraction\"] !== undefined\n ? `${Math.round(Number(sr[\"userFraction\"]) * 100)}%`\n : \"—\",\n };\n });\n await maybePaginate(formatOutput(rows, format));\n } else {\n await maybePaginate(formatOutput(sorted, format));\n }\n });\n\n // Promote\n releases\n .command(\"promote\")\n .description(\"Promote a release from one track to another\")\n .option(\"--from <track>\", \"Source track\")\n .option(\"--to <track>\", \"Target track\")\n .option(\"--rollout <percent>\", \"Staged rollout percentage\")\n .option(\"--notes <text>\", \"Release notes\")\n .option(\"--copy-notes-from <track>\", \"Copy release notes from another track\")\n .option(\"--status <status>\", \"Release status: completed, inProgress, draft, halted\")\n .option(\n \"--changes-not-sent-for-review\",\n \"Commit changes without sending for review (required for rejected apps)\",\n )\n .option(\n \"--error-if-in-review\",\n \"Fail if changes are already in review instead of cancelling them\",\n )\n .action(async (options) => {\n if (options.notes && options.copyNotesFrom) {\n throw new GpcError(\n \"Cannot combine --notes and --copy-notes-from. Use only one.\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Pick one release notes source.\",\n );\n }\n\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n const interactive = isInteractive(program);\n const tracks = [\"internal\", \"alpha\", \"beta\", \"production\"];\n\n options.from = await requireOption(\n \"from\",\n options.from,\n {\n message: \"Source track:\",\n choices: tracks,\n },\n interactive,\n );\n\n options.to = await requireOption(\n \"to\",\n options.to,\n {\n message: \"Target track:\",\n choices: tracks.filter((t: string) => t !== options.from),\n },\n interactive,\n );\n\n if (options.from === options.to) {\n throw new GpcError(\n `--from and --to must be different tracks (both are \"${options.from}\")`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Specify different source and target tracks.\",\n );\n }\n\n if (options.rollout !== undefined) {\n const rollout = Number(options.rollout);\n if (!Number.isFinite(rollout) || rollout < 1 || rollout > 100) {\n throw new GpcError(\n `--rollout must be a number between 1 and 100 (got: ${options.rollout})`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use a percentage between 1 and 100.\",\n );\n }\n }\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"releases promote\",\n action: \"promote\",\n target: `${options.from} → ${options.to}`,\n details: {\n rollout: options.rollout,\n ...(options.status && { status: options.status }),\n },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n let releaseNotes: { language: string; text: string }[] | undefined;\n if (options.copyNotesFrom) {\n releaseNotes = await fetchReleaseNotes(client, packageName, options.copyNotesFrom);\n } else if (options.notes) {\n releaseNotes = [{ language: \"en-US\", text: options.notes }];\n }\n\n const result = await promoteRelease(client, packageName, options.from, options.to, {\n status: options.status,\n userFraction: options.rollout ? Number(options.rollout) / 100 : undefined,\n releaseNotes,\n commitOptions: buildCommitOptions(options),\n });\n console.log(formatOutput(result, format));\n });\n\n // Rollout subcommands\n const rollout = releases.command(\"rollout\").description(\"Manage staged rollouts\");\n\n for (const action of [\"increase\", \"halt\", \"resume\", \"complete\"] as const) {\n const cmd = rollout\n .command(action)\n .description(`${action.charAt(0).toUpperCase() + action.slice(1)} a staged rollout`)\n .option(\"--track <track>\", \"Track name\");\n\n if (action === \"increase\") {\n cmd.option(\"--to <percent>\", \"New rollout percentage\");\n cmd.option(\"--vitals-gate\", \"Halt rollout if crash rate exceeds configured threshold\");\n }\n\n cmd.option(\n \"--changes-not-sent-for-review\",\n \"Commit changes without sending for review (required for rejected apps)\",\n );\n cmd.option(\n \"--error-if-in-review\",\n \"Fail if changes are already in review instead of cancelling them\",\n );\n\n cmd.action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n const interactive = isInteractive(program);\n const tracks = [\"internal\", \"alpha\", \"beta\", \"production\"];\n\n options.track = await requireOption(\n \"track\",\n options.track,\n {\n message: \"Track:\",\n choices: tracks,\n },\n interactive,\n );\n\n if (action === \"increase\") {\n options.to = await requireOption(\n \"to\",\n options.to,\n {\n message: \"New rollout percentage (1-100):\",\n },\n interactive,\n );\n }\n\n if (action === \"increase\" && options.to !== undefined) {\n const to = Number(options.to);\n if (!Number.isFinite(to) || to < 1 || to > 100) {\n throw new GpcError(\n `--to must be a number between 1 and 100 (got: ${options.to})`,\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use a percentage between 1 and 100.\",\n );\n }\n }\n\n // Require confirmation for destructive rollout halt\n if (action === \"halt\") {\n await requireConfirm(\n `Halt rollout on track \"${options.track}\" for ${packageName}?`,\n program,\n );\n }\n\n if (isDryRun(program)) {\n if (action === \"increase\" && options.vitalsGate) {\n console.error(\n \"Warning: --vitals-gate is ignored in --dry-run mode. Gate will run on live execution.\",\n );\n }\n printDryRun(\n {\n command: `releases rollout ${action}`,\n action: action,\n target: options.track,\n details: { percentage: options.to !== undefined ? `${options.to}%` : undefined },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n const result = await updateRollout(\n client,\n packageName,\n options.track,\n action,\n options.to ? Number(options.to) / 100 : undefined,\n buildCommitOptions(options),\n );\n\n // Vitals gate: check crash rate after rollout increase\n if (action === \"increase\" && options.vitalsGate) {\n const threshold = (config as any).vitals?.thresholds?.crashRate;\n if (!threshold) {\n console.error(\n \"Warning: --vitals-gate requires vitals.thresholds.crashRate in config. Skipping gate.\",\n );\n } else {\n try {\n const { auth: authConfig } = config;\n const vitalsAuth = await resolveAuth({\n serviceAccountPath: authConfig?.serviceAccount,\n });\n const reportingClient = createReportingClient({ auth: vitalsAuth });\n const vitalsResult = await getVitalsCrashes(reportingClient, packageName, {\n days: 1,\n });\n const latest = (vitalsResult as any).data?.[0]?.crashRate;\n const check = checkThreshold(latest, threshold);\n if (check.breached) {\n await updateRollout(client, packageName, options.track, \"halt\");\n console.error(\n `Vitals gate: crash rate ${String(latest)}% > threshold ${String(threshold)}%. Rollout halted.`,\n );\n process.exitCode = 6;\n }\n } catch (vitalsErr) {\n console.error(\n `Warning: Vitals gate check failed: ${vitalsErr instanceof Error ? vitalsErr.message : String(vitalsErr)}`,\n );\n }\n }\n }\n\n console.log(formatOutput(result, format));\n });\n }\n\n // Release notes\n releases\n .command(\"notes\")\n .description(\"Get or set release notes\")\n .argument(\"<action>\", \"Action: get|set\")\n .option(\"--track <track>\", \"Track name\")\n .option(\"--lang <language>\", \"Language code\", \"en-US\")\n .option(\"--notes <text>\", \"Release notes text\")\n .option(\"--file <path>\", \"Read notes from file\")\n .action(async (action: string, options) => {\n if (action === \"set\") {\n throw new GpcError(\n \"gpc releases notes set is not implemented as a standalone command.\",\n \"RELEASES_USAGE_ERROR\",\n 1,\n \"Use --notes, --notes-dir, or --notes-from-git with: gpc releases upload or gpc publish\",\n );\n }\n\n if (action === \"get\") {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n const track = options.track ?? \"internal\";\n\n // Try getReleasesStatus first (has all releases), then fallback to fetchReleaseNotes\n const statuses = await getReleasesStatus(client, packageName, track);\n let notes = Array.isArray(statuses)\n ? statuses.flatMap((s: any) => s.releaseNotes ?? [])\n : ((statuses as any).releaseNotes ?? []);\n\n // Fallback: fetchReleaseNotes reads the raw track data which may have notes\n // even when getReleasesStatus doesn't (e.g. completed releases)\n if (notes.length === 0) {\n try {\n notes = await fetchReleaseNotes(client, packageName, track);\n } catch {\n // No release found on track — fall through to empty message\n }\n }\n\n if (notes.length === 0) {\n console.log(`No release notes found on track \"${track}\".`);\n return;\n }\n console.log(formatOutput(notes, format));\n return;\n }\n\n throw new GpcError(\n \"Unknown action. Usage: gpc releases notes <get|set> --track <track>\",\n \"RELEASES_USAGE_ERROR\",\n 2,\n \"Use: gpc releases notes get --track <track>\",\n );\n });\n\n // Upload externally hosted APK\n releases\n .command(\"upload-external\")\n .description(\"Upload an externally hosted APK configuration\")\n .requiredOption(\"--url <url>\", \"External URL where the APK is hosted\")\n .requiredOption(\"--file <config>\", \"Path to JSON config file with APK metadata\")\n .action(async (options: { url: string; file: string }) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n\n const { readFile } = await import(\"node:fs/promises\");\n const raw = await readFile(options.file, \"utf-8\");\n const apkConfig = JSON.parse(raw) as Record<string, unknown>;\n\n // Override with CLI-provided URL\n apkConfig[\"externallyHostedUrl\"] = options.url;\n\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n const client = createApiClient({ auth });\n const result = await uploadExternallyHosted(\n client,\n packageName,\n apkConfig as unknown as ExternallyHostedApk,\n );\n console.log(formatOutput(result, format));\n });\n\n // Diff\n releases\n .command(\"diff\")\n .description(\"Compare releases between two tracks\")\n .option(\"--from <track>\", \"Source track\", \"internal\")\n .option(\"--to <track>\", \"Target track\", \"production\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await diffReleases(client, packageName, options.from, options.to);\n if (result.diffs.length === 0) {\n console.log(`No differences between ${result.fromTrack} and ${result.toTrack}.`);\n } else {\n if (format === \"json\") {\n console.log(formatOutput(result, format));\n } else {\n console.log(`Differences: ${result.fromTrack} vs ${result.toTrack}\\n`);\n console.log(formatOutput(result.diffs, format));\n }\n }\n });\n\n // Count\n releases\n .command(\"count\")\n .description(\"Count releases per track\")\n .option(\"--track <track>\", \"Filter to a specific track\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const statuses = await getReleasesStatus(client, packageName, options.track);\n\n // Group by track\n const trackCounts = new Map<string, { total: number; statuses: Record<string, number> }>();\n for (const r of statuses) {\n const entry = trackCounts.get(r.track) ?? { total: 0, statuses: {} };\n entry.total++;\n entry.statuses[r.status] = (entry.statuses[r.status] ?? 0) + 1;\n trackCounts.set(r.track, entry);\n }\n\n if (format === \"json\") {\n const data = Object.fromEntries(\n [...trackCounts.entries()].map(([track, info]) => [track, info]),\n );\n console.log(formatOutput(data, format));\n } else {\n const rows = [...trackCounts.entries()].map(([track, info]) => ({\n track,\n releases: info.total,\n ...info.statuses,\n }));\n if (rows.length === 0) {\n console.log(\"No releases found.\");\n } else {\n console.log(formatOutput(rows, format));\n }\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY,YAAY;AACjC,SAAS,UAAU,eAAe;AAGlC,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB,6BAA6B;AAEvD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc,aAAa,eAAe,qBAAqB;AAWxE,SAAS,mBAAmB,YAAgC,QAA2B;AACrF,QAAM,OAAO,cAAc,OAAO;AAClC,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAIA,SAAS,kBAAkB,cAAqE;AAC9F,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,CAAC,UAAyB;AAC/B,UAAM,OAAO,KAAK,UAAU,KAAK,IAAI;AACrC,eAAW,cAAc,IAAI,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC/C;AACF;AAEA,eAAe,UAAU,QAAmB,cAAuB,eAAwB;AACzF,QAAM,OAAO,MAAM,YAAY,EAAE,oBAAoB,OAAO,MAAM,eAAe,CAAC;AAClF,SAAO,gBAAgB,EAAE,MAAM,SAAS,kBAAkB,YAAY,GAAG,cAAc,CAAC;AAC1F;AAEO,SAAS,yBAAyB,SAAwB;AAC/D,QAAM,WAAW,QAAQ,QAAQ,UAAU,EAAE,YAAY,8BAA8B;AAGvF,WACG,QAAQ,eAAe,EACvB,YAAY,sCAAsC,EAClD,OAAO,mBAAmB,gBAAgB,UAAU,EACpD,OAAO,uBAAuB,mCAAmC,EACjE,OAAO,kBAAkB,uBAAuB,EAChD,OAAO,iBAAiB,cAAc,EACtC,OAAO,oBAAoB,4CAA4C,EACvE,OAAO,qBAAqB,sDAAsD,EAClF,OAAO,oBAAoB,gDAAgD,EAC3E,OAAO,6BAA6B,uCAAuC,EAC3E,OAAO,iBAAiB,oEAA+D,EACvF,OAAO,sBAAsB,yCAAyC,EACtE;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC,OAAO,yBAAyB,mDAAmD,UAAU,EAC7F,OAAO,6BAA6B,mCAAmC,EACvE;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,OAAO,MAAc,YAAY;AACvC,QAAI;AACF,YAAM,KAAK,IAAI;AAAA,IACjB,QAAQ;AACN,YAAM,IAAI;AAAA,QACR,mBAAmB,IAAI;AAAA,QACvB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM,QAAQ,IAAI,EAAE,YAAY;AACtC,QAAI,QAAQ,UAAU,QAAQ,QAAQ;AACpC,YAAM,IAAI;AAAA,QACR,oCAAoC,OAAO,gBAAgB;AAAA,QAC3D;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAAc;AAAA,MAClB,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV,EAAE,OAAO,OAAO;AAChB,QAAI,YAAY,SAAS,GAAG;AAC1B,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAG9C,QAAI,cAAc,OAAO,GAAG;AAC1B,UAAI,CAAC,QAAQ,SAAS,QAAQ,UAAU,YAAY;AAClD,cAAM,SAAS,CAAC,YAAY,SAAS,QAAQ,YAAY;AACzD,gBAAQ,QAAQ,MAAM,aAAa,iBAAiB,QAAQ,UAAU;AAAA,MACxE;AAEA,UAAI,CAAC,QAAQ,WAAW,QAAQ,UAAU,cAAc;AACtD,cAAM,aAAa,MAAM;AAAA,UACvB;AAAA,UACA;AAAA,QACF;AACA,YAAI,cAAc,eAAe,OAAO;AACtC,kBAAQ,UAAU;AAAA,QACpB;AAAA,MACF;AAEA,UAAI,CAAC,QAAQ,SAAS,CAAC,QAAQ,UAAU;AACvC,cAAM,QAAQ,MAAM,YAAY,sCAAsC;AACtE,YAAI,MAAO,SAAQ,QAAQ;AAAA,MAC7B;AAAA,IACF;AAEA,QAAI,QAAQ,YAAY,QAAW;AACjC,YAAMA,WAAU,OAAO,QAAQ,OAAO;AACtC,UAAI,CAAC,OAAO,SAASA,QAAO,KAAKA,WAAU,KAAKA,WAAU,KAAK;AAC7D,cAAM,IAAI;AAAA,UACR,sDAAsD,QAAQ,OAAO;AAAA,UACrE;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QACE,QAAQ,eACR,QAAQ,gBAAgB,cACxB,QAAQ,gBAAgB,cACxB;AACA,YAAM,IAAI;AAAA,QACR,4DAA4D,QAAQ,WAAW;AAAA,QAC/E;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,EAAE,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI;AAC1C,UAAM,WAAW,WAAW;AAC5B,UAAM,SAAS,MAAM,UAAU,QAAQ,QAAQ,UAAU,QAAQ,OAAO;AAExE,UAAM,eAAe,CAAC,YAAY,QAAQ,OAAO,SAAS,CAAC,QAAQ,KAAK,EAAE,OAAO;AACjF,UAAM,UAAU,YAAY,OAAO,OAAO,QAAQ,CAAC;AAEnD,aAAS,YAAY,OAAuB;AAC1C,UAAI,SAAS,OAAO,OAAO,KAAM,QAAO,IAAI,SAAS,OAAO,OAAO,OAAO,QAAQ,CAAC,CAAC;AACpF,UAAI,SAAS,OAAO,KAAM,QAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,CAAC,CAAC;AACtE,UAAI,SAAS,KAAM,QAAO,IAAI,QAAQ,MAAM,QAAQ,CAAC,CAAC;AACtD,aAAO,GAAG,KAAK;AAAA,IACjB;AAEA,UAAM,YAAY;AAClB,UAAM,mBAAmB,eACrB,CAAC,UAA+B;AAC9B,YAAM,SAAS,KAAK,MAAO,MAAM,UAAU,MAAO,SAAS;AAC3D,YAAM,MAAM,SAAI,OAAO,MAAM,IAAI,SAAI,OAAO,YAAY,MAAM;AAC9D,YAAM,WAAW,YAAY,MAAM,aAAa;AAChD,YAAM,QAAQ,YAAY,MAAM,UAAU;AAC1C,YAAM,QACJ,MAAM,iBAAiB,IAAI,GAAG,YAAY,MAAM,cAAc,CAAC,OAAO;AACxE,YAAM,MAAM,MAAM,aAAa,IAAI,OAAO,MAAM,UAAU,MAAM;AAChE,cAAQ,OAAO;AAAA,QACb,OAAO,GAAG,KAAK,MAAM,OAAO,MAAM,QAAQ,IAAI,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,MACvE;AAAA,IACF,IACA;AAEJ,QAAI,SAAS,OAAO,GAAG;AACrB,YAAM,SAAS,MAAM,cAAc,QAAQ,aAAa,MAAM;AAAA,QAC5D,OAAO,QAAQ;AAAA,QACf,QAAQ,QAAQ;AAAA,QAChB,cAAc,QAAQ,UAAU,OAAO,QAAQ,OAAO,IAAI,MAAM;AAAA,QAChE,iBAAiB,QAAQ;AAAA,QACzB,oBAAoB,QAAQ;AAAA,QAC5B,QAAQ;AAAA,MACV,CAAC;AACD,cAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AACxC;AAAA,IACF;AAEA,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,QACE;AAAA,QACA,OAAO,QAAQ;AAAA,QACf,SAAS,QAAQ;AAAA,MACnB;AAAA,MACA;AAAA,IACF;AAEA,UAAM,UAAU,cAAc,aAAa,SAAS,IAAI,CAAC,KAAK,MAAM,SAAS;AAC7E,QAAI,CAAC,aAAc,SAAQ,MAAM;AAEjC,QAAI;AACF,UAAI;AACJ,UAAI,QAAQ,eAAe;AACzB,uBAAe,MAAM,kBAAkB,QAAQ,aAAa,QAAQ,aAAa;AAAA,MACnF,WAAW,QAAQ,cAAc;AAC/B,cAAM,WAAW,MAAM,qBAAqB,EAAE,OAAO,QAAQ,MAAM,CAAC;AACpE,uBAAe,CAAC,EAAE,UAAU,SAAS,UAAU,MAAM,SAAS,KAAK,CAAC;AAAA,MACtE,WAAW,QAAQ,UAAU;AAC3B,uBAAe,MAAM,wBAAwB,QAAQ,QAAQ;AAAA,MAC/D,WAAW,QAAQ,OAAO;AACxB,uBAAe,CAAC,EAAE,UAAU,SAAS,MAAM,QAAQ,MAAM,CAAC;AAAA,MAC5D;AAEA,YAAM,SAAS,MAAM,cAAc,QAAQ,aAAa,MAAM;AAAA,QAC5D,OAAO,QAAQ;AAAA,QACf,QAAQ,QAAQ;AAAA,QAChB,cAAc,QAAQ,UAAU,OAAO,QAAQ,OAAO,IAAI,MAAM;AAAA,QAChE;AAAA,QACA,aAAa,QAAQ;AAAA,QACrB,aAAa,QAAQ;AAAA,QACrB,iBAAiB,QAAQ;AAAA,QACzB,oBAAoB,QAAQ;AAAA,QAC5B;AAAA,QACA,eAAe,mBAAmB,OAAO;AAAA,MAC3C,CAAC;AACD,UAAI,cAAc;AAChB,gBAAQ,OAAO,MAAM,uBAAkB,SAAS,IAAI,CAAC,KAAK,MAAM;AAAA,CAAa;AAAA,MAC/E;AACA,cAAQ,KAAK,iBAAiB;AAC9B,cAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AACxC,iBAAW,UAAU;AAAA,IACvB,SAAS,OAAO;AACd,UAAI,cAAc;AAChB,gBAAQ,OAAO,MAAM,IAAI;AAAA,MAC3B;AACA,cAAQ,KAAK,eAAe;AAC5B,iBAAW,UAAU;AACrB,iBAAW,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACxE,YAAM;AAAA,IACR,UAAE;AACA,iBAAW,aAAa,KAAK,IAAI,IAAI,IAAI,KAAK,WAAW,SAAS,EAAE,QAAQ;AAC5E,oBAAc,UAAU,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC1C;AAAA,EACF,CAAC;AAGH,WACG,QAAQ,QAAQ,EAChB,YAAY,2CAA2C,EACvD,OAAO,mBAAmB,iBAAiB,EAC3C,OAAO,kBAAkB,8CAA8C,EACvE,OAAO,OAAO,YAAY;AACzB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,MAAM,UAAU,MAAM;AACrC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,cAAc,CAAC,cAAc,QAAQ,SAAS,UAAU;AAC9D,UAAM,cAAc,MAAM,kBAAkB,QAAQ,aAAa,QAAQ,KAAK;AAC9E,UAAM,WAAW,QAAQ,QACrB,MAAM,QAAQ,WAAW,IACvB,YAAY,OAAO,CAAC,MAAW,EAAE,UAAU,QAAQ,KAAK,IACxD,cACF;AACJ,UAAM,SAAS,MAAM,QAAQ,QAAQ,IACjC,QAAQ,OACN,YAAY,UAAU,QAAQ,IAAI,IAClC,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM;AAC3B,YAAM,KAAK,YAAY;AAAA,QACrB,OAAQ,EAAyC,OAAO,KAAK,EAAE;AAAA,MACjE;AACA,YAAM,KAAK,YAAY;AAAA,QACrB,OAAQ,EAAyC,OAAO,KAAK,EAAE;AAAA,MACjE;AACA,cAAQ,OAAO,KAAK,KAAK,OAAO,OAAO,KAAK,KAAK;AAAA,IACnD,CAAC,IACH;AACJ,QAAI,WAAW,UAAU,MAAM,QAAQ,MAAM,GAAG;AAC9C,YAAM,OAAO,OAAO,IAAI,CAAC,MAAe;AACtC,cAAM,KAAK;AACX,eAAO;AAAA,UACL,OAAO,GAAG,OAAO,KAAK;AAAA,UACtB,QAAQ,GAAG,QAAQ,KAAK;AAAA,UACxB,MAAM,GAAG,MAAM,KAAK;AAAA,UACpB,cAAc,MAAM,QAAQ,GAAG,cAAc,CAAC,IACzC,GAAG,cAAc,EAAgB,KAAK,IAAI,IAC3C;AAAA,UACJ,cACE,GAAG,cAAc,MAAM,SACnB,GAAG,KAAK,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,GAAG,CAAC,MAC/C;AAAA,QACR;AAAA,MACF,CAAC;AACD,YAAM,cAAc,aAAa,MAAM,MAAM,CAAC;AAAA,IAChD,OAAO;AACL,YAAM,cAAc,aAAa,QAAQ,MAAM,CAAC;AAAA,IAClD;AAAA,EACF,CAAC;AAGH,WACG,QAAQ,SAAS,EACjB,YAAY,6CAA6C,EACzD,OAAO,kBAAkB,cAAc,EACvC,OAAO,gBAAgB,cAAc,EACrC,OAAO,uBAAuB,2BAA2B,EACzD,OAAO,kBAAkB,eAAe,EACxC,OAAO,6BAA6B,uCAAuC,EAC3E,OAAO,qBAAqB,sDAAsD,EAClF;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,OAAO,YAAY;AACzB,QAAI,QAAQ,SAAS,QAAQ,eAAe;AAC1C,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAC9C,UAAM,cAAc,cAAc,OAAO;AACzC,UAAM,SAAS,CAAC,YAAY,SAAS,QAAQ,YAAY;AAEzD,YAAQ,OAAO,MAAM;AAAA,MACnB;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,QACE,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAEA,YAAQ,KAAK,MAAM;AAAA,MACjB;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,QACE,SAAS;AAAA,QACT,SAAS,OAAO,OAAO,CAAC,MAAc,MAAM,QAAQ,IAAI;AAAA,MAC1D;AAAA,MACA;AAAA,IACF;AAEA,QAAI,QAAQ,SAAS,QAAQ,IAAI;AAC/B,YAAM,IAAI;AAAA,QACR,uDAAuD,QAAQ,IAAI;AAAA,QACnE;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,QAAQ,YAAY,QAAW;AACjC,YAAMA,WAAU,OAAO,QAAQ,OAAO;AACtC,UAAI,CAAC,OAAO,SAASA,QAAO,KAAKA,WAAU,KAAKA,WAAU,KAAK;AAC7D,cAAM,IAAI;AAAA,UACR,sDAAsD,QAAQ,OAAO;AAAA,UACrE;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,SAAS,OAAO,GAAG;AACrB;AAAA,QACE;AAAA,UACE,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,QAAQ,GAAG,QAAQ,IAAI,WAAM,QAAQ,EAAE;AAAA,UACvC,SAAS;AAAA,YACP,SAAS,QAAQ;AAAA,YACjB,GAAI,QAAQ,UAAU,EAAE,QAAQ,QAAQ,OAAO;AAAA,UACjD;AAAA,QACF;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,UAAU,MAAM;AAErC,QAAI;AACJ,QAAI,QAAQ,eAAe;AACzB,qBAAe,MAAM,kBAAkB,QAAQ,aAAa,QAAQ,aAAa;AAAA,IACnF,WAAW,QAAQ,OAAO;AACxB,qBAAe,CAAC,EAAE,UAAU,SAAS,MAAM,QAAQ,MAAM,CAAC;AAAA,IAC5D;AAEA,UAAM,SAAS,MAAM,eAAe,QAAQ,aAAa,QAAQ,MAAM,QAAQ,IAAI;AAAA,MACjF,QAAQ,QAAQ;AAAA,MAChB,cAAc,QAAQ,UAAU,OAAO,QAAQ,OAAO,IAAI,MAAM;AAAA,MAChE;AAAA,MACA,eAAe,mBAAmB,OAAO;AAAA,IAC3C,CAAC;AACD,YAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,EAC1C,CAAC;AAGH,QAAM,UAAU,SAAS,QAAQ,SAAS,EAAE,YAAY,wBAAwB;AAEhF,aAAW,UAAU,CAAC,YAAY,QAAQ,UAAU,UAAU,GAAY;AACxE,UAAM,MAAM,QACT,QAAQ,MAAM,EACd,YAAY,GAAG,OAAO,OAAO,CAAC,EAAE,YAAY,IAAI,OAAO,MAAM,CAAC,CAAC,mBAAmB,EAClF,OAAO,mBAAmB,YAAY;AAEzC,QAAI,WAAW,YAAY;AACzB,UAAI,OAAO,kBAAkB,wBAAwB;AACrD,UAAI,OAAO,iBAAiB,yDAAyD;AAAA,IACvF;AAEA,QAAI;AAAA,MACF;AAAA,MACA;AAAA,IACF;AACA,QAAI;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAEA,QAAI,OAAO,OAAO,YAAY;AAC5B,YAAM,SAAS,MAAM,WAAW;AAChC,YAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,YAAM,SAAS,gBAAgB,SAAS,MAAM;AAC9C,YAAM,cAAc,cAAc,OAAO;AACzC,YAAM,SAAS,CAAC,YAAY,SAAS,QAAQ,YAAY;AAEzD,cAAQ,QAAQ,MAAM;AAAA,QACpB;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,UACE,SAAS;AAAA,UACT,SAAS;AAAA,QACX;AAAA,QACA;AAAA,MACF;AAEA,UAAI,WAAW,YAAY;AACzB,gBAAQ,KAAK,MAAM;AAAA,UACjB;AAAA,UACA,QAAQ;AAAA,UACR;AAAA,YACE,SAAS;AAAA,UACX;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAEA,UAAI,WAAW,cAAc,QAAQ,OAAO,QAAW;AACrD,cAAM,KAAK,OAAO,QAAQ,EAAE;AAC5B,YAAI,CAAC,OAAO,SAAS,EAAE,KAAK,KAAK,KAAK,KAAK,KAAK;AAC9C,gBAAM,IAAI;AAAA,YACR,iDAAiD,QAAQ,EAAE;AAAA,YAC3D;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,WAAW,QAAQ;AACrB,cAAM;AAAA,UACJ,0BAA0B,QAAQ,KAAK,SAAS,WAAW;AAAA,UAC3D;AAAA,QACF;AAAA,MACF;AAEA,UAAI,SAAS,OAAO,GAAG;AACrB,YAAI,WAAW,cAAc,QAAQ,YAAY;AAC/C,kBAAQ;AAAA,YACN;AAAA,UACF;AAAA,QACF;AACA;AAAA,UACE;AAAA,YACE,SAAS,oBAAoB,MAAM;AAAA,YACnC;AAAA,YACA,QAAQ,QAAQ;AAAA,YAChB,SAAS,EAAE,YAAY,QAAQ,OAAO,SAAY,GAAG,QAAQ,EAAE,MAAM,OAAU;AAAA,UACjF;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,UAAU,MAAM;AAErC,YAAM,SAAS,MAAM;AAAA,QACnB;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,QACA,QAAQ,KAAK,OAAO,QAAQ,EAAE,IAAI,MAAM;AAAA,QACxC,mBAAmB,OAAO;AAAA,MAC5B;AAGA,UAAI,WAAW,cAAc,QAAQ,YAAY;AAC/C,cAAM,YAAa,OAAe,QAAQ,YAAY;AACtD,YAAI,CAAC,WAAW;AACd,kBAAQ;AAAA,YACN;AAAA,UACF;AAAA,QACF,OAAO;AACL,cAAI;AACF,kBAAM,EAAE,MAAM,WAAW,IAAI;AAC7B,kBAAM,aAAa,MAAM,YAAY;AAAA,cACnC,oBAAoB,YAAY;AAAA,YAClC,CAAC;AACD,kBAAM,kBAAkB,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAClE,kBAAM,eAAe,MAAM,iBAAiB,iBAAiB,aAAa;AAAA,cACxE,MAAM;AAAA,YACR,CAAC;AACD,kBAAM,SAAU,aAAqB,OAAO,CAAC,GAAG;AAChD,kBAAM,QAAQ,eAAe,QAAQ,SAAS;AAC9C,gBAAI,MAAM,UAAU;AAClB,oBAAM,cAAc,QAAQ,aAAa,QAAQ,OAAO,MAAM;AAC9D,sBAAQ;AAAA,gBACN,2BAA2B,OAAO,MAAM,CAAC,iBAAiB,OAAO,SAAS,CAAC;AAAA,cAC7E;AACA,sBAAQ,WAAW;AAAA,YACrB;AAAA,UACF,SAAS,WAAW;AAClB,oBAAQ;AAAA,cACN,sCAAsC,qBAAqB,QAAQ,UAAU,UAAU,OAAO,SAAS,CAAC;AAAA,YAC1G;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,cAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,IAC1C,CAAC;AAAA,EACH;AAGA,WACG,QAAQ,OAAO,EACf,YAAY,0BAA0B,EACtC,SAAS,YAAY,iBAAiB,EACtC,OAAO,mBAAmB,YAAY,EACtC,OAAO,qBAAqB,iBAAiB,OAAO,EACpD,OAAO,kBAAkB,oBAAoB,EAC7C,OAAO,iBAAiB,sBAAsB,EAC9C,OAAO,OAAO,QAAgB,YAAY;AACzC,QAAI,WAAW,OAAO;AACpB,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,WAAW,OAAO;AACpB,YAAM,SAAS,MAAM,WAAW;AAChC,YAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,YAAM,SAAS,MAAM,UAAU,MAAM;AACrC,YAAM,SAAS,gBAAgB,SAAS,MAAM;AAC9C,YAAM,QAAQ,QAAQ,SAAS;AAG/B,YAAM,WAAW,MAAM,kBAAkB,QAAQ,aAAa,KAAK;AACnE,UAAI,QAAQ,MAAM,QAAQ,QAAQ,IAC9B,SAAS,QAAQ,CAAC,MAAW,EAAE,gBAAgB,CAAC,CAAC,IAC/C,SAAiB,gBAAgB,CAAC;AAIxC,UAAI,MAAM,WAAW,GAAG;AACtB,YAAI;AACF,kBAAQ,MAAM,kBAAkB,QAAQ,aAAa,KAAK;AAAA,QAC5D,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,UAAI,MAAM,WAAW,GAAG;AACtB,gBAAQ,IAAI,oCAAoC,KAAK,IAAI;AACzD;AAAA,MACF;AACA,cAAQ,IAAI,aAAa,OAAO,MAAM,CAAC;AACvC;AAAA,IACF;AAEA,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AAGH,WACG,QAAQ,iBAAiB,EACzB,YAAY,+CAA+C,EAC3D,eAAe,eAAe,sCAAsC,EACpE,eAAe,mBAAmB,4CAA4C,EAC9E,OAAO,OAAO,YAA2C;AACxD,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,EAAE,SAAS,IAAI,MAAM,OAAO,aAAkB;AACpD,UAAM,MAAM,MAAM,SAAS,QAAQ,MAAM,OAAO;AAChD,UAAM,YAAY,KAAK,MAAM,GAAG;AAGhC,cAAU,qBAAqB,IAAI,QAAQ;AAE3C,UAAM,OAAO,MAAM,YAAY,EAAE,oBAAoB,OAAO,MAAM,eAAe,CAAC;AAClF,UAAM,SAAS,gBAAgB,EAAE,KAAK,CAAC;AACvC,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,YAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,EAC1C,CAAC;AAGH,WACG,QAAQ,MAAM,EACd,YAAY,qCAAqC,EACjD,OAAO,kBAAkB,gBAAgB,UAAU,EACnD,OAAO,gBAAgB,gBAAgB,YAAY,EACnD,OAAO,OAAO,YAAY;AACzB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,MAAM,UAAU,MAAM;AACrC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,SAAS,MAAM,aAAa,QAAQ,aAAa,QAAQ,MAAM,QAAQ,EAAE;AAC/E,QAAI,OAAO,MAAM,WAAW,GAAG;AAC7B,cAAQ,IAAI,0BAA0B,OAAO,SAAS,QAAQ,OAAO,OAAO,GAAG;AAAA,IACjF,OAAO;AACL,UAAI,WAAW,QAAQ;AACrB,gBAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,MAC1C,OAAO;AACL,gBAAQ,IAAI,gBAAgB,OAAO,SAAS,OAAO,OAAO,OAAO;AAAA,CAAI;AACrE,gBAAQ,IAAI,aAAa,OAAO,OAAO,MAAM,CAAC;AAAA,MAChD;AAAA,IACF;AAAA,EACF,CAAC;AAGH,WACG,QAAQ,OAAO,EACf,YAAY,0BAA0B,EACtC,OAAO,mBAAmB,4BAA4B,EACtD,OAAO,OAAO,YAAY;AACzB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,MAAM,UAAU,MAAM;AACrC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,WAAW,MAAM,kBAAkB,QAAQ,aAAa,QAAQ,KAAK;AAG3E,UAAM,cAAc,oBAAI,IAAiE;AACzF,eAAW,KAAK,UAAU;AACxB,YAAM,QAAQ,YAAY,IAAI,EAAE,KAAK,KAAK,EAAE,OAAO,GAAG,UAAU,CAAC,EAAE;AACnE,YAAM;AACN,YAAM,SAAS,EAAE,MAAM,KAAK,MAAM,SAAS,EAAE,MAAM,KAAK,KAAK;AAC7D,kBAAY,IAAI,EAAE,OAAO,KAAK;AAAA,IAChC;AAEA,QAAI,WAAW,QAAQ;AACrB,YAAM,OAAO,OAAO;AAAA,QAClB,CAAC,GAAG,YAAY,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,IAAI,CAAC;AAAA,MACjE;AACA,cAAQ,IAAI,aAAa,MAAM,MAAM,CAAC;AAAA,IACxC,OAAO;AACL,YAAM,OAAO,CAAC,GAAG,YAAY,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,IAAI,OAAO;AAAA,QAC9D;AAAA,QACA,UAAU,KAAK;AAAA,QACf,GAAG,KAAK;AAAA,MACV,EAAE;AACF,UAAI,KAAK,WAAW,GAAG;AACrB,gBAAQ,IAAI,oBAAoB;AAAA,MAClC,OAAO;AACL,gBAAQ,IAAI,aAAa,MAAM,MAAM,CAAC;AAAA,MACxC;AAAA,IACF;AAAA,EACF,CAAC;AACL;","names":["rollout"]}
File without changes
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getClient,
4
4
  resolvePackageName
5
- } from "./chunk-NQH4G7BI.js";
5
+ } from "./chunk-JDRY7HK5.js";
6
6
  import {
7
7
  isDryRun,
8
8
  printDryRun
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  isInteractive,
15
15
  requireOption
16
- } from "./chunk-YFUBD2XB.js";
16
+ } from "./chunk-RZQSEDKI.js";
17
17
 
18
18
  // src/commands/reviews.ts
19
19
  import { loadConfig } from "@gpc-cli/config";
@@ -61,9 +61,7 @@ function registerReviewsCommands(program) {
61
61
  author: rv["authorName"] || "-",
62
62
  stars: userComment?.["starRating"] || "-",
63
63
  text: String(userComment?.["text"] || "-").slice(0, 80),
64
- lastModified: userComment?.["lastModified"] ? String(
65
- userComment["lastModified"]?.["seconds"] || "-"
66
- ) : "-",
64
+ lastModified: userComment?.["lastModified"] ? String(userComment["lastModified"]?.["seconds"] || "-") : "-",
67
65
  thumbsUp: userComment?.["thumbsUpCount"] || 0
68
66
  };
69
67
  });
@@ -196,4 +194,4 @@ Rating distribution:`);
196
194
  export {
197
195
  registerReviewsCommands
198
196
  };
199
- //# sourceMappingURL=reviews-YCBBM656.js.map
197
+ //# sourceMappingURL=reviews-P4M6BEJI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/reviews.ts"],"sourcesContent":["import { resolvePackageName, getClient } from \"../resolve.js\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\n\nimport {\n listReviews,\n getReview,\n replyToReview,\n exportReviews,\n analyzeReviews,\n formatOutput,\n maybePaginate,\n sortResults,\n GpcError,\n} from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport { isInteractive, requireOption } from \"../prompt.js\";\n\nconst MAX_REPLY_CHARS = 350;\n\nexport function registerReviewsCommands(program: Command): void {\n const reviews = program.command(\"reviews\").description(\"Manage user reviews and ratings\");\n\n reviews\n .command(\"list\")\n .description(\"List user reviews\")\n .option(\"--stars <n>\", \"Filter by star rating (1-5)\", parseInt)\n .option(\"--lang <code>\", \"Filter by reviewer language\")\n .option(\"--since <date>\", \"Filter reviews after date (ISO 8601)\")\n .option(\"--translate-to <lang>\", \"Translate reviews to language\")\n .option(\"--max <n>\", \"Maximum number of reviews per page\", parseInt)\n .option(\"--limit <n>\", \"Maximum total results\", parseInt)\n .option(\"--next-page <token>\", \"Resume from page token\")\n .option(\"--all\", \"Auto-paginate to fetch all reviews (API returns last 7 days only)\")\n .option(\"--sort <field>\", \"Sort by field (prefix with - for descending)\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await listReviews(client, packageName, {\n stars: options.stars,\n language: options.lang,\n since: options.since,\n translationLanguage: options.translateTo,\n maxResults: options.max,\n limit: options.limit,\n nextPage: options.nextPage,\n all: options.all,\n });\n if (Array.isArray(result) && result.length === 0 && format !== \"json\") {\n console.log(\"No reviews found.\");\n return;\n }\n const sorted = sortResults(result, options.sort);\n if (format !== \"json\" && Array.isArray(sorted)) {\n const rows = sorted.map((r: unknown) => {\n const rv = r as Record<string, unknown>;\n const comments = rv[\"comments\"] as Record<string, unknown>[] | undefined;\n const userComment = comments?.[0]?.[\"userComment\"] as Record<string, unknown> | undefined;\n return {\n reviewId: rv[\"reviewId\"] || \"-\",\n author: rv[\"authorName\"] || \"-\",\n stars: userComment?.[\"starRating\"] || \"-\",\n text: String(userComment?.[\"text\"] || \"-\").slice(0, 80),\n lastModified: userComment?.[\"lastModified\"]\n ? String((userComment[\"lastModified\"] as Record<string, unknown>)?.[\"seconds\"] || \"-\")\n : \"-\",\n thumbsUp: userComment?.[\"thumbsUpCount\"] || 0,\n };\n });\n await maybePaginate(formatOutput(rows, format));\n } else {\n await maybePaginate(formatOutput(sorted, format));\n }\n });\n\n reviews\n .command(\"get <review-id>\")\n .description(\"Get a single review\")\n .option(\"--translate-to <lang>\", \"Translate review to language\")\n .action(async (reviewId: string, options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await getReview(client, packageName, reviewId, options.translateTo);\n console.log(formatOutput(result, format));\n });\n\n reviews\n .command(\"reply <review-id>\")\n .description(\"Reply to a review\")\n .option(\"--text <text>\", \"Reply text (max 350 chars)\")\n .action(async (reviewId: string, options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = getOutputFormat(program, config);\n const interactive = isInteractive(program);\n\n options.text = await requireOption(\n \"text\",\n options.text,\n {\n message: \"Reply text (max 350 chars):\",\n },\n interactive,\n );\n\n if (options.text.length > MAX_REPLY_CHARS) {\n throw new GpcError(\n `Reply text exceeds ${MAX_REPLY_CHARS} characters (${options.text.length} chars). Google Play will reject this reply.`,\n \"REVIEWS_USAGE_ERROR\",\n 2,\n `Shorten your reply to ${MAX_REPLY_CHARS} characters or fewer.`,\n );\n }\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"reviews reply\",\n action: \"reply to\",\n target: reviewId,\n details: { text: options.text },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n const result = await replyToReview(client, packageName, reviewId, options.text);\n if (format !== \"json\") {\n const charCount = options.text.length;\n console.log(`Reply sent (${charCount}/350 chars)`);\n }\n console.log(formatOutput(result, format));\n });\n\n reviews\n .command(\"analyze\")\n .description(\"Analyze reviews: sentiment, topics, keywords, rating distribution\")\n .option(\"--stars <n>\", \"Filter by star rating (1-5)\", parseInt)\n .option(\"--lang <code>\", \"Filter by reviewer language\")\n .option(\"--since <date>\", \"Filter reviews after date (ISO 8601)\")\n .option(\"--max <n>\", \"Maximum number of reviews to analyze\", parseInt)\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const result = await analyzeReviews(client, packageName, {\n stars: options.stars,\n language: options.lang,\n since: options.since,\n maxResults: options.max,\n });\n\n if (format === \"json\") {\n console.log(formatOutput(result, format));\n return;\n }\n\n console.log(`\\nReview Analysis — ${packageName}`);\n console.log(`${\"─\".repeat(50)}`);\n console.log(`Total reviews: ${result.totalReviews}`);\n console.log(\n `Average rating: ${result.avgRating > 0 ? result.avgRating.toFixed(2) + \" ★\" : \"N/A\"}`,\n );\n console.log(`\\nSentiment:`);\n console.log(\n ` Positive: ${result.sentiment.positive} Negative: ${result.sentiment.negative} Neutral: ${result.sentiment.neutral}`,\n );\n console.log(` Avg score: ${result.sentiment.avgScore} (range -1 to +1)`);\n\n if (result.topics.length > 0) {\n console.log(`\\nTop topics:`);\n for (const t of result.topics.slice(0, 8)) {\n const bar = t.avgScore > 0.1 ? \"+\" : t.avgScore < -0.1 ? \"-\" : \"~\";\n console.log(` [${bar}] ${t.topic.padEnd(20)} ${t.count} reviews`);\n }\n }\n\n if (result.keywords.length > 0) {\n console.log(\n `\\nTop keywords: ${result.keywords\n .slice(0, 10)\n .map((k) => k.word)\n .join(\", \")}`,\n );\n }\n\n if (Object.keys(result.ratingDistribution).length > 0) {\n console.log(`\\nRating distribution:`);\n for (let star = 5; star >= 1; star--) {\n const count = result.ratingDistribution[star] ?? 0;\n if (count > 0) console.log(` ${star}★ ${count}`);\n }\n }\n });\n\n reviews\n .command(\"export\")\n .description(\"Export reviews to JSON or CSV\")\n .option(\"--format <type>\", \"Output format: json or csv\", \"json\")\n .option(\"--stars <n>\", \"Filter by star rating (1-5)\", parseInt)\n .option(\"--lang <code>\", \"Filter by reviewer language\")\n .option(\"--since <date>\", \"Filter reviews after date (ISO 8601)\")\n .option(\"--translate-to <lang>\", \"Translate reviews to language\")\n .option(\"--output <file>\", \"Write output to file instead of stdout\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n\n const result = await exportReviews(client, packageName, {\n format: options.format as \"json\" | \"csv\",\n stars: options.stars,\n language: options.lang,\n since: options.since,\n translationLanguage: options.translateTo,\n });\n\n if (options.output) {\n const { writeFile } = await import(\"node:fs/promises\");\n await writeFile(options.output, result, \"utf-8\");\n console.log(`Reviews exported to ${options.output}`);\n } else {\n console.log(result);\n }\n });\n}\n"],"mappings":"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","names":[]}
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getClient,
4
4
  resolvePackageName
5
- } from "./chunk-NQH4G7BI.js";
5
+ } from "./chunk-JDRY7HK5.js";
6
6
  import {
7
7
  yellow
8
8
  } from "./chunk-FAN4ZITI.js";
@@ -12,12 +12,7 @@ import {
12
12
 
13
13
  // src/commands/rtdn.ts
14
14
  import { loadConfig } from "@gpc-cli/config";
15
- import {
16
- getRtdnStatus,
17
- decodeNotification,
18
- formatNotification,
19
- formatOutput
20
- } from "@gpc-cli/core";
15
+ import { getRtdnStatus, decodeNotification, formatNotification, formatOutput } from "@gpc-cli/core";
21
16
  function registerRtdnCommands(program) {
22
17
  const rtdn = program.command("rtdn").description("Real-Time Developer Notifications (Pub/Sub)");
23
18
  rtdn.command("status").description("Check RTDN notification topic configuration").action(async () => {
@@ -41,8 +36,12 @@ RTDN Status \u2014 ${packageName}`);
41
36
  console.log(`
42
37
  To set up RTDN:`);
43
38
  console.log(` 1. Create a Pub/Sub topic in your GCP project`);
44
- console.log(` 2. Grant google-play-developer-notifications@system.gserviceaccount.com the Pub/Sub Publisher role`);
45
- console.log(` 3. Set the topic in Play Console \u2192 Monetization setup \u2192 Real-time developer notifications`);
39
+ console.log(
40
+ ` 2. Grant google-play-developer-notifications@system.gserviceaccount.com the Pub/Sub Publisher role`
41
+ );
42
+ console.log(
43
+ ` 3. Set the topic in Play Console \u2192 Monetization setup \u2192 Real-time developer notifications`
44
+ );
46
45
  console.log(` 4. Or use: gpc rtdn setup --topic projects/<PROJECT>/topics/<TOPIC>`);
47
46
  }
48
47
  });
@@ -61,27 +60,36 @@ To set up RTDN:`);
61
60
  const config = await loadConfig();
62
61
  const format = getOutputFormat(program, config);
63
62
  if (format !== "json") {
64
- console.log(`${yellow("\u26A0")} Test notifications can only be triggered from the Play Console.`);
63
+ console.log(
64
+ `${yellow("\u26A0")} Test notifications can only be triggered from the Play Console.`
65
+ );
65
66
  console.log(`
66
67
  To test RTDN:`);
67
- console.log(` 1. Open Play Console \u2192 Monetization setup \u2192 Real-time developer notifications`);
68
+ console.log(
69
+ ` 1. Open Play Console \u2192 Monetization setup \u2192 Real-time developer notifications`
70
+ );
68
71
  console.log(` 2. Click "Send test notification"`);
69
72
  console.log(` 3. Check your Pub/Sub subscription for the test message`);
70
73
  console.log(` 4. Decode it with: gpc rtdn decode <base64-payload>`);
71
74
  } else {
72
- console.log(formatOutput({
73
- message: "Test notifications can only be triggered from the Play Console",
74
- steps: [
75
- "Open Play Console \u2192 Monetization setup \u2192 RTDN",
76
- "Click 'Send test notification'",
77
- "Check your Pub/Sub subscription",
78
- "Decode with: gpc rtdn decode <payload>"
79
- ]
80
- }, format));
75
+ console.log(
76
+ formatOutput(
77
+ {
78
+ message: "Test notifications can only be triggered from the Play Console",
79
+ steps: [
80
+ "Open Play Console \u2192 Monetization setup \u2192 RTDN",
81
+ "Click 'Send test notification'",
82
+ "Check your Pub/Sub subscription",
83
+ "Decode with: gpc rtdn decode <payload>"
84
+ ]
85
+ },
86
+ format
87
+ )
88
+ );
81
89
  }
82
90
  });
83
91
  }
84
92
  export {
85
93
  registerRtdnCommands
86
94
  };
87
- //# sourceMappingURL=rtdn-LID2B7XZ.js.map
95
+ //# sourceMappingURL=rtdn-YII4H6SD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/rtdn.ts"],"sourcesContent":["import { resolvePackageName, getClient } from \"../resolve.js\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { getRtdnStatus, decodeNotification, formatNotification, formatOutput } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { yellow } from \"../colors.js\";\n\nexport function registerRtdnCommands(program: Command): void {\n const rtdn = program.command(\"rtdn\").description(\"Real-Time Developer Notifications (Pub/Sub)\");\n\n rtdn\n .command(\"status\")\n .description(\"Check RTDN notification topic configuration\")\n .action(async () => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = getOutputFormat(program, config);\n\n const status = await getRtdnStatus(client, packageName);\n\n if (format === \"json\") {\n console.log(formatOutput({ packageName, ...status }, format));\n return;\n }\n\n console.log(`\\nRTDN Status — ${packageName}`);\n console.log(`${\"─\".repeat(50)}`);\n if (status.topicName) {\n console.log(`Topic: ${status.topicName}`);\n console.log(`Enabled: ${status.enabled ? \"yes\" : \"no\"}`);\n } else {\n console.log(`${yellow(\"⚠\")} No RTDN topic configured.`);\n console.log(`\\nTo set up RTDN:`);\n console.log(` 1. Create a Pub/Sub topic in your GCP project`);\n console.log(\n ` 2. Grant google-play-developer-notifications@system.gserviceaccount.com the Pub/Sub Publisher role`,\n );\n console.log(\n ` 3. Set the topic in Play Console → Monetization setup → Real-time developer notifications`,\n );\n console.log(` 4. Or use: gpc rtdn setup --topic projects/<PROJECT>/topics/<TOPIC>`);\n }\n });\n\n rtdn\n .command(\"decode <payload>\")\n .description(\"Decode a base64-encoded Pub/Sub notification payload\")\n .action(async (payload: string) => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n\n const notification = decodeNotification(payload);\n const formatted = formatNotification(notification);\n\n if (format === \"json\") {\n console.log(formatOutput(notification, format));\n } else {\n console.log(formatOutput(formatted, format));\n }\n });\n\n rtdn\n .command(\"test\")\n .description(\"Send a test notification to verify RTDN setup\")\n .action(async () => {\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n\n if (format !== \"json\") {\n console.log(\n `${yellow(\"⚠\")} Test notifications can only be triggered from the Play Console.`,\n );\n console.log(`\\nTo test RTDN:`);\n console.log(\n ` 1. Open Play Console → Monetization setup → Real-time developer notifications`,\n );\n console.log(` 2. Click \"Send test notification\"`);\n console.log(` 3. Check your Pub/Sub subscription for the test message`);\n console.log(` 4. Decode it with: gpc rtdn decode <base64-payload>`);\n } else {\n console.log(\n formatOutput(\n {\n message: \"Test notifications can only be triggered from the Play Console\",\n steps: [\n \"Open Play Console → Monetization setup → RTDN\",\n \"Click 'Send test notification'\",\n \"Check your Pub/Sub subscription\",\n \"Decode with: gpc rtdn decode <payload>\",\n ],\n },\n format,\n ),\n );\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;AAEA,SAAS,kBAAkB;AAC3B,SAAS,eAAe,oBAAoB,oBAAoB,oBAAoB;AAI7E,SAAS,qBAAqB,SAAwB;AAC3D,QAAM,OAAO,QAAQ,QAAQ,MAAM,EAAE,YAAY,6CAA6C;AAE9F,OACG,QAAQ,QAAQ,EAChB,YAAY,6CAA6C,EACzD,OAAO,YAAY;AAClB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,MAAM,UAAU,MAAM;AACrC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,SAAS,MAAM,cAAc,QAAQ,WAAW;AAEtD,QAAI,WAAW,QAAQ;AACrB,cAAQ,IAAI,aAAa,EAAE,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAC5D;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,qBAAmB,WAAW,EAAE;AAC5C,YAAQ,IAAI,GAAG,SAAI,OAAO,EAAE,CAAC,EAAE;AAC/B,QAAI,OAAO,WAAW;AACpB,cAAQ,IAAI,YAAY,OAAO,SAAS,EAAE;AAC1C,cAAQ,IAAI,YAAY,OAAO,UAAU,QAAQ,IAAI,EAAE;AAAA,IACzD,OAAO;AACL,cAAQ,IAAI,GAAG,OAAO,QAAG,CAAC,4BAA4B;AACtD,cAAQ,IAAI;AAAA,gBAAmB;AAC/B,cAAQ,IAAI,iDAAiD;AAC7D,cAAQ;AAAA,QACN;AAAA,MACF;AACA,cAAQ;AAAA,QACN;AAAA,MACF;AACA,cAAQ,IAAI,uEAAuE;AAAA,IACrF;AAAA,EACF,CAAC;AAEH,OACG,QAAQ,kBAAkB,EAC1B,YAAY,sDAAsD,EAClE,OAAO,OAAO,YAAoB;AACjC,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,UAAM,eAAe,mBAAmB,OAAO;AAC/C,UAAM,YAAY,mBAAmB,YAAY;AAEjD,QAAI,WAAW,QAAQ;AACrB,cAAQ,IAAI,aAAa,cAAc,MAAM,CAAC;AAAA,IAChD,OAAO;AACL,cAAQ,IAAI,aAAa,WAAW,MAAM,CAAC;AAAA,IAC7C;AAAA,EACF,CAAC;AAEH,OACG,QAAQ,MAAM,EACd,YAAY,+CAA+C,EAC3D,OAAO,YAAY;AAClB,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAE9C,QAAI,WAAW,QAAQ;AACrB,cAAQ;AAAA,QACN,GAAG,OAAO,QAAG,CAAC;AAAA,MAChB;AACA,cAAQ,IAAI;AAAA,cAAiB;AAC7B,cAAQ;AAAA,QACN;AAAA,MACF;AACA,cAAQ,IAAI,qCAAqC;AACjD,cAAQ,IAAI,2DAA2D;AACvE,cAAQ,IAAI,uDAAuD;AAAA,IACrE,OAAO;AACL,cAAQ;AAAA,QACN;AAAA,UACE;AAAA,YACE,SAAS;AAAA,YACT,OAAO;AAAA,cACL;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YACF;AAAA,UACF;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACL;","names":[]}
@@ -52,10 +52,7 @@ function parseSections(raw) {
52
52
  const sections = raw.split(",").map((s) => s.trim().toLowerCase());
53
53
  for (const s of sections) {
54
54
  if (!VALID_SECTIONS.has(s)) {
55
- usageError(
56
- `Unknown section "${s}"`,
57
- "Valid sections: releases, vitals, reviews"
58
- );
55
+ usageError(`Unknown section "${s}"`, "Valid sections: releases, vitals, reviews");
59
56
  }
60
57
  }
61
58
  return sections;
@@ -67,10 +64,7 @@ function parseThresholdOverrides(raw) {
67
64
  if (!key || !val) continue;
68
65
  const mapped = THRESHOLD_KEYS[key.toLowerCase()];
69
66
  if (!mapped) {
70
- usageError(
71
- `Unknown threshold "${key}"`,
72
- "Valid: crashes, anr, slow-starts, slow-render"
73
- );
67
+ usageError(`Unknown threshold "${key}"`, "Valid: crashes, anr, slow-starts, slow-render");
74
68
  }
75
69
  const n = parseFloat(val);
76
70
  if (isNaN(n) || n < 0) {
@@ -179,10 +173,7 @@ function registerStatusCommand(program) {
179
173
  ).option("--watch [seconds]", "Poll every N seconds (min 10, default 30)").option("--since-last", "Show diff from last cached status").option("--all-apps", `Run status for all configured app profiles (max ${MAX_ALL_APPS})`).option("--notify", "Send desktop notification on threshold breach or clear").option("--threshold <overrides>", "Override vitals thresholds: crashes=1.5,anr=0.5 (percent)").action(
180
174
  async (opts) => {
181
175
  if (!VALID_FORMATS.has(opts.format)) {
182
- usageError(
183
- `Unknown format "${opts.format}"`,
184
- "Valid: table, summary"
185
- );
176
+ usageError(`Unknown format "${opts.format}"`, "Valid: table, summary");
186
177
  }
187
178
  const sections = parseSections(opts.sections);
188
179
  if (!Number.isFinite(opts.days) || opts.days < 1) {
@@ -201,10 +192,7 @@ function registerStatusCommand(program) {
201
192
  const watchInterval = resolveWatchInterval(opts.watch);
202
193
  const packages = resolvePackages(program, config, opts.allApps);
203
194
  if (packages.length === 0) {
204
- usageError(
205
- "No package name",
206
- "Use --app <package> or gpc config set app <package>"
207
- );
195
+ usageError("No package name", "Use --app <package> or gpc config set app <package>");
208
196
  }
209
197
  if (opts.allApps && packages.length > MAX_ALL_APPS) {
210
198
  usageError(
@@ -225,10 +213,8 @@ function registerStatusCommand(program) {
225
213
  let anyBreach = false;
226
214
  for (const packageName of packages) {
227
215
  if (packages.length > 1) {
228
- const label = statusHasBreach ? `
229
- === ${packageName} ===` : `
230
- === ${packageName} ===`;
231
- console.log(label);
216
+ console.log(`
217
+ === ${packageName} ===`);
232
218
  }
233
219
  try {
234
220
  const breach = await runStatusForPackage({
@@ -285,14 +271,11 @@ async function runStatusForPackage(ctx) {
285
271
  if (opts.cached) {
286
272
  const cached = await loadStatusCache(packageName, opts.ttl);
287
273
  if (!cached) {
288
- throw Object.assign(
289
- new Error("No cached status found"),
290
- {
291
- code: "STATUS_NO_CACHE",
292
- exitCode: 2,
293
- suggestion: "Run without --cached to fetch live data"
294
- }
295
- );
274
+ throw Object.assign(new Error("No cached status found"), {
275
+ code: "STATUS_NO_CACHE",
276
+ exitCode: 2,
277
+ suggestion: "Run without --cached to fetch live data"
278
+ });
296
279
  }
297
280
  const display = applyDisplaySections(cached, sections);
298
281
  printWithDiff(display, prevStatus, opts.sinceLast, render, ctx.format);
@@ -341,9 +324,7 @@ function printWithDiff(status, prevStatus, sinceLast, render, format) {
341
324
  }
342
325
  if (Date.now() < (/* @__PURE__ */ new Date("2026-09-01")).getTime()) {
343
326
  console.log("");
344
- console.log(
345
- dim("Verification: enforcement begins Sep 2026 (BR, ID, SG, TH) \xB7 gpc verify")
346
- );
327
+ console.log(dim("Verification: enforcement begins Sep 2026 (BR, ID, SG, TH) \xB7 gpc verify"));
347
328
  }
348
329
  }
349
330
  async function handleNotify(packageName, status, notify) {
@@ -362,4 +343,4 @@ export {
362
343
  registerStatusCommand,
363
344
  resolveWatchInterval
364
345
  };
365
- //# sourceMappingURL=status-3HXBBXG6.js.map
346
+ //# sourceMappingURL=status-32AJ7A6O.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 type { ResolvedConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient, createReportingClient } from \"@gpc-cli/api\";\nimport {\n getAppStatus,\n formatStatusTable,\n formatStatusSummary,\n formatStatusDiff,\n computeStatusDiff,\n loadStatusCache,\n saveStatusCache,\n statusHasBreach,\n runWatchLoop,\n trackBreachState,\n sendNotification,\n relativeTime,\n formatOutput,\n createSpinner,\n} from \"@gpc-cli/core\";\nimport type { AppStatus } from \"@gpc-cli/core\";\nimport { getOutputFormat } from \"../format.js\";\nimport { green, red, dim, gray } from \"../colors.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst VALID_SECTIONS = new Set([\"releases\", \"vitals\", \"reviews\"]);\nconst VALID_FORMATS = new Set([\"table\", \"summary\"]);\nconst MAX_ALL_APPS = 5;\n\nconst THRESHOLD_KEYS: Record<string, string> = {\n crashes: \"crashRate\",\n crash: \"crashRate\",\n anr: \"anrRate\",\n \"slow-starts\": \"slowStartRate\",\n \"slow-start\": \"slowStartRate\",\n \"slow-render\": \"slowRenderingRate\",\n \"slow-rendering\": \"slowRenderingRate\",\n};\n\n// ---------------------------------------------------------------------------\n// Validation error helper\n// ---------------------------------------------------------------------------\n\nfunction usageError(message: string, suggestion?: string): never {\n throw Object.assign(new Error(message), {\n code: \"STATUS_USAGE_ERROR\",\n exitCode: 2,\n suggestion,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Pure, testable helpers\n// ---------------------------------------------------------------------------\n\nexport function parseSections(raw: string): string[] {\n const sections = raw.split(\",\").map((s) => s.trim().toLowerCase());\n for (const s of sections) {\n if (!VALID_SECTIONS.has(s)) {\n usageError(`Unknown section \"${s}\"`, \"Valid sections: releases, vitals, reviews\");\n }\n }\n return sections;\n}\n\nexport function parseThresholdOverrides(raw: string): Record<string, number> {\n const result: Record<string, number> = {};\n for (const pair of raw.split(\",\")) {\n const [key, val] = pair.split(\"=\").map((s) => s.trim());\n if (!key || !val) continue;\n const mapped = THRESHOLD_KEYS[key.toLowerCase()];\n if (!mapped) {\n usageError(`Unknown threshold \"${key}\"`, \"Valid: crashes, anr, slow-starts, slow-render\");\n }\n const n = parseFloat(val);\n if (isNaN(n) || n < 0) {\n usageError(\n `Invalid threshold value \"${val}\" for ${key}`,\n \"Must be a positive number (percent)\",\n );\n }\n result[mapped] = n / 100; // Convert percent to decimal\n }\n return result;\n}\n\nexport function resolveWatchInterval(watch: string | boolean | undefined): number | null {\n if (watch === undefined) return null;\n if (watch === true || watch === \"\") return 30;\n const n = parseInt(String(watch), 10);\n return isNaN(n) ? 30 : n;\n}\n\nfunction resolveVitalThresholds(config: ResolvedConfig) {\n const raw = config as unknown as Record<string, unknown>;\n const vitals = raw[\"vitals\"] as Record<string, unknown> | undefined;\n if (!vitals || typeof vitals !== \"object\") return undefined;\n const t = vitals[\"thresholds\"] as Record<string, unknown> | undefined;\n if (!t || typeof t !== \"object\") return undefined;\n const toN = (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 {\n crashRate: toN(t[\"crashRate\"]),\n anrRate: toN(t[\"anrRate\"]),\n slowStartRate: toN(t[\"slowStartRate\"]),\n slowRenderingRate: toN(t[\"slowRenderingRate\"]),\n };\n}\n\nfunction resolvePackages(program: Command, config: ResolvedConfig, allApps?: boolean): string[] {\n const rootApp = (program.opts()[\"app\"] || config.app) as string | undefined;\n if (!allApps) return rootApp ? [rootApp] : [];\n\n const seen = new Set<string>();\n const result: string[] = [];\n if (rootApp) {\n seen.add(rootApp);\n result.push(rootApp);\n }\n for (const profile of Object.values(config.profiles ?? {})) {\n if (profile.app && !seen.has(profile.app)) {\n seen.add(profile.app);\n result.push(profile.app);\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Display helpers\n// ---------------------------------------------------------------------------\n\nfunction colorizeTrackStatus(s: string): string {\n switch (s) {\n case \"inProgress\":\n case \"completed\":\n return green(s);\n case \"halted\":\n return red(s);\n case \"draft\":\n return dim(s);\n default:\n return gray(s);\n }\n}\n\nfunction applyStatusColors(status: AppStatus): AppStatus {\n if (!status.releases || status.releases.length === 0) return status;\n return {\n ...status,\n releases: status.releases.map((r) => ({\n ...r,\n status: colorizeTrackStatus(r.status),\n })),\n };\n}\n\nfunction makeRenderer(\n format: string,\n displayFormat: string,\n includeDiff?: { prevStatus: AppStatus | null; sinceLast?: boolean },\n): (status: AppStatus) => string {\n return (status: AppStatus): string => {\n if (format === \"json\") {\n const sectionSet = new Set(status.sections);\n const filtered: Record<string, unknown> = {\n packageName: status.packageName,\n fetchedAt: status.fetchedAt,\n cached: status.cached,\n sections: status.sections,\n };\n if (sectionSet.has(\"releases\")) filtered[\"releases\"] = status.releases;\n if (sectionSet.has(\"vitals\")) filtered[\"vitals\"] = status.vitals;\n if (sectionSet.has(\"reviews\")) filtered[\"reviews\"] = status.reviews;\n\n // Embed diff in JSON output when --since-last is used\n if (includeDiff?.sinceLast && includeDiff.prevStatus) {\n filtered[\"diff\"] = computeStatusDiff(includeDiff.prevStatus, status);\n filtered[\"diffSince\"] = includeDiff.prevStatus.fetchedAt;\n }\n\n return formatOutput(filtered, \"json\");\n }\n const colorized = applyStatusColors(status);\n if (displayFormat === \"summary\") return formatStatusSummary(colorized);\n return formatStatusTable(colorized);\n };\n}\n\n// ---------------------------------------------------------------------------\n// Command registration\n// ---------------------------------------------------------------------------\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(\"--review-days <n>\", \"Reviews window in days\", (v) => parseInt(v, 10), 30)\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 .option(\"--format <fmt>\", \"Display style: table (default) or summary\", \"table\")\n .option(\n \"--sections <list>\",\n \"Comma-separated sections: releases,vitals,reviews\",\n \"releases,vitals,reviews\",\n )\n .option(\"--watch [seconds]\", \"Poll every N seconds (min 10, default 30)\")\n .option(\"--since-last\", \"Show diff from last cached status\")\n .option(\"--all-apps\", `Run status for all configured app profiles (max ${MAX_ALL_APPS})`)\n .option(\"--notify\", \"Send desktop notification on threshold breach or clear\")\n .option(\"--threshold <overrides>\", \"Override vitals thresholds: crashes=1.5,anr=0.5 (percent)\")\n .action(\n async (opts: {\n days: number;\n reviewDays: number;\n cached?: boolean;\n refresh?: boolean;\n ttl: number;\n format: string;\n sections: string;\n watch?: string | boolean;\n sinceLast?: boolean;\n allApps?: boolean;\n threshold?: string;\n notify?: boolean;\n }) => {\n if (!VALID_FORMATS.has(opts.format)) {\n usageError(`Unknown format \"${opts.format}\"`, \"Valid: table, summary\");\n }\n\n const sections = parseSections(opts.sections);\n\n if (!Number.isFinite(opts.days) || opts.days < 1) {\n usageError(`--days must be a positive integer (got: ${opts.days})`);\n }\n\n if (!Number.isFinite(opts.reviewDays) || opts.reviewDays < 1) {\n usageError(`--review-days must be a positive integer (got: ${opts.reviewDays})`);\n }\n\n const config = await loadConfig();\n const format = getOutputFormat(program, config);\n let vitalThresholds: RunCtx[\"vitalThresholds\"] = resolveVitalThresholds(config);\n if (opts.threshold) {\n const overrides = parseThresholdOverrides(opts.threshold);\n vitalThresholds = { ...vitalThresholds, ...overrides } as RunCtx[\"vitalThresholds\"];\n }\n const watchInterval = resolveWatchInterval(opts.watch);\n const packages = resolvePackages(program, config, opts.allApps);\n\n if (packages.length === 0) {\n usageError(\"No package name\", \"Use --app <package> or gpc config set app <package>\");\n }\n if (opts.allApps && packages.length > MAX_ALL_APPS) {\n usageError(\n `--all-apps found ${packages.length} apps (max ${MAX_ALL_APPS})`,\n \"Use --app to target a specific app\",\n );\n }\n\n const authConfig = config.auth;\n\n const makeClients = async () => {\n const auth = await resolveAuth({\n serviceAccountPath: authConfig?.serviceAccount,\n });\n return {\n client: createApiClient({ auth }),\n reporting: createReportingClient({ auth }),\n };\n };\n\n let anyBreach = false;\n\n for (const packageName of packages) {\n if (packages.length > 1) {\n console.log(`\\n=== ${packageName} ===`);\n }\n\n try {\n const breach = await runStatusForPackage({\n packageName,\n opts,\n sections,\n format,\n vitalThresholds,\n watchInterval,\n makeClients,\n });\n if (breach) anyBreach = true;\n } catch (error) {\n if (packages.length === 1) throw error;\n // For --all-apps, print error and continue to next app\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n if (anyBreach) process.exitCode = 6;\n },\n );\n}\n\n// ---------------------------------------------------------------------------\n// Per-package status runner\n// ---------------------------------------------------------------------------\n\ninterface RunCtx {\n packageName: string;\n opts: {\n days: number;\n reviewDays: number;\n cached?: boolean;\n refresh?: boolean;\n ttl: number;\n format: string;\n sinceLast?: boolean;\n notify?: boolean;\n };\n sections: string[];\n format: string;\n vitalThresholds:\n | {\n crashRate?: number;\n anrRate?: number;\n slowStartRate?: number;\n slowRenderingRate?: number;\n }\n | undefined;\n watchInterval: number | null;\n makeClients: () => Promise<{\n client: ReturnType<typeof createApiClient>;\n reporting: ReturnType<typeof createReportingClient>;\n }>;\n}\n\n/** Override sections on a cached AppStatus with the user-requested sections for display. */\nfunction applyDisplaySections(status: AppStatus, requestedSections: string[]): AppStatus {\n const requested = new Set(requestedSections);\n const filtered = status.sections.filter((s) => requested.has(s));\n if (filtered.length === status.sections.length) return status;\n return { ...status, sections: filtered };\n}\n\n/** Returns true if a breach was detected. */\nasync function runStatusForPackage(ctx: RunCtx): Promise<boolean> {\n const { packageName, opts, sections, vitalThresholds, watchInterval } = ctx;\n\n const fetchLive = async (): Promise<AppStatus> => {\n const { client, reporting } = await ctx.makeClients();\n return getAppStatus(client, reporting, packageName, {\n days: opts.days,\n reviewDays: opts.reviewDays,\n sections,\n vitalThresholds: vitalThresholds ?? undefined,\n });\n };\n\n const save = (status: AppStatus) => saveStatusCache(packageName, status, opts.ttl);\n\n // Capture prev status early (for --since-last)\n const prevStatus = opts.sinceLast ? await loadStatusCache(packageName, Infinity) : null;\n\n // Build the renderer — for JSON mode with --since-last, diff is embedded\n const render = makeRenderer(ctx.format, opts.format, {\n prevStatus,\n sinceLast: opts.sinceLast,\n });\n\n if (watchInterval !== null && opts.sinceLast) {\n process.stderr.write(\n \"Warning: --since-last is not supported with --watch and will be ignored.\\n\",\n );\n }\n\n // --watch: hand off entirely to runWatchLoop\n if (watchInterval !== null) {\n await runWatchLoop({ intervalSeconds: watchInterval, render, fetch: fetchLive, save });\n return false;\n }\n\n // --cached: serve from cache only\n if (opts.cached) {\n const cached = await loadStatusCache(packageName, opts.ttl);\n if (!cached) {\n throw Object.assign(new Error(\"No cached status found\"), {\n code: \"STATUS_NO_CACHE\",\n exitCode: 2,\n suggestion: \"Run without --cached to fetch live data\",\n });\n }\n const display = applyDisplaySections(cached, sections);\n printWithDiff(display, prevStatus, opts.sinceLast, render, ctx.format);\n await handleNotify(packageName, cached, opts.notify);\n return statusHasBreach(cached);\n }\n\n // Try cache (unless --refresh)\n if (!opts.refresh) {\n const cached = await loadStatusCache(packageName, opts.ttl);\n if (cached) {\n const display = applyDisplaySections(cached, sections);\n if (ctx.format !== \"json\" && display.sections.length < cached.sections.length) {\n process.stderr.write(\n `Tip: cache contains all sections. Add --refresh to fetch only the requested sections and reduce API calls.\\n`,\n );\n }\n printWithDiff(display, prevStatus, opts.sinceLast, render, ctx.format);\n await handleNotify(packageName, cached, opts.notify);\n return statusHasBreach(cached);\n }\n }\n\n // Live fetch (with spinner in TTY mode)\n const spinner = createSpinner(\"Fetching app status...\");\n if (ctx.format !== \"json\") spinner.start();\n let status: AppStatus;\n try {\n status = await fetchLive();\n } catch (err) {\n spinner.fail(\"Failed to fetch app status\");\n throw err;\n }\n spinner.stop(\"Done\");\n await save(status);\n\n printWithDiff(status, prevStatus, opts.sinceLast, render, ctx.format);\n await handleNotify(packageName, status, opts.notify);\n return statusHasBreach(status);\n}\n\n// ---------------------------------------------------------------------------\n// Output helpers\n// ---------------------------------------------------------------------------\n\nfunction printWithDiff(\n status: AppStatus,\n prevStatus: AppStatus | null,\n sinceLast: boolean | undefined,\n render: (s: AppStatus) => string,\n format: string,\n): void {\n console.log(render(status));\n\n // In JSON mode, diff is embedded by makeRenderer — no extra text output\n if (format === \"json\") return;\n\n if (sinceLast && prevStatus) {\n const since = relativeTime(prevStatus.fetchedAt);\n console.log(\"\");\n console.log(formatStatusDiff(computeStatusDiff(prevStatus, status), since));\n } else if (sinceLast && !prevStatus) {\n console.log(\"\\n(No prior cached status to diff against)\");\n }\n\n // Verification deadline awareness (auto-expires Sep 2026)\n if (Date.now() < new Date(\"2026-09-01\").getTime()) {\n console.log(\"\");\n console.log(dim(\"Verification: enforcement begins Sep 2026 (BR, ID, SG, TH) · gpc verify\"));\n }\n}\n\nasync function handleNotify(\n packageName: string,\n status: AppStatus,\n notify: boolean | undefined,\n): Promise<void> {\n if (!notify) return;\n const breaching = statusHasBreach(status);\n const changed = await trackBreachState(packageName, breaching);\n if (changed) {\n const title = breaching ? \"GPC Alert\" : \"GPC Status\";\n const body = breaching\n ? `${packageName}: vitals threshold breached`\n : `${packageName}: vitals back to normal`;\n sendNotification(title, body);\n }\n}\n"],"mappings":";;;;;;;;;;;;AACA,SAAS,kBAAkB;AAE3B,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB,6BAA6B;AACvD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP,IAAM,iBAAiB,oBAAI,IAAI,CAAC,YAAY,UAAU,SAAS,CAAC;AAChE,IAAM,gBAAgB,oBAAI,IAAI,CAAC,SAAS,SAAS,CAAC;AAClD,IAAM,eAAe;AAErB,IAAM,iBAAyC;AAAA,EAC7C,SAAS;AAAA,EACT,OAAO;AAAA,EACP,KAAK;AAAA,EACL,eAAe;AAAA,EACf,cAAc;AAAA,EACd,eAAe;AAAA,EACf,kBAAkB;AACpB;AAMA,SAAS,WAAW,SAAiB,YAA4B;AAC/D,QAAM,OAAO,OAAO,IAAI,MAAM,OAAO,GAAG;AAAA,IACtC,MAAM;AAAA,IACN,UAAU;AAAA,IACV;AAAA,EACF,CAAC;AACH;AAMO,SAAS,cAAc,KAAuB;AACnD,QAAM,WAAW,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC;AACjE,aAAW,KAAK,UAAU;AACxB,QAAI,CAAC,eAAe,IAAI,CAAC,GAAG;AAC1B,iBAAW,oBAAoB,CAAC,KAAK,2CAA2C;AAAA,IAClF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,wBAAwB,KAAqC;AAC3E,QAAM,SAAiC,CAAC;AACxC,aAAW,QAAQ,IAAI,MAAM,GAAG,GAAG;AACjC,UAAM,CAAC,KAAK,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACtD,QAAI,CAAC,OAAO,CAAC,IAAK;AAClB,UAAM,SAAS,eAAe,IAAI,YAAY,CAAC;AAC/C,QAAI,CAAC,QAAQ;AACX,iBAAW,sBAAsB,GAAG,KAAK,+CAA+C;AAAA,IAC1F;AACA,UAAM,IAAI,WAAW,GAAG;AACxB,QAAI,MAAM,CAAC,KAAK,IAAI,GAAG;AACrB;AAAA,QACE,4BAA4B,GAAG,SAAS,GAAG;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AACA,WAAO,MAAM,IAAI,IAAI;AAAA,EACvB;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,OAAoD;AACvF,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,UAAU,QAAQ,UAAU,GAAI,QAAO;AAC3C,QAAM,IAAI,SAAS,OAAO,KAAK,GAAG,EAAE;AACpC,SAAO,MAAM,CAAC,IAAI,KAAK;AACzB;AAEA,SAAS,uBAAuB,QAAwB;AACtD,QAAM,MAAM;AACZ,QAAM,SAAS,IAAI,QAAQ;AAC3B,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,IAAI,OAAO,YAAY;AAC7B,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,QAAM,MAAM,CAAC,MAAmC;AAC9C,QAAI,MAAM,UAAa,MAAM,KAAM,QAAO;AAC1C,UAAM,IAAI,OAAO,CAAC;AAClB,WAAO,MAAM,CAAC,IAAI,SAAY;AAAA,EAChC;AACA,SAAO;AAAA,IACL,WAAW,IAAI,EAAE,WAAW,CAAC;AAAA,IAC7B,SAAS,IAAI,EAAE,SAAS,CAAC;AAAA,IACzB,eAAe,IAAI,EAAE,eAAe,CAAC;AAAA,IACrC,mBAAmB,IAAI,EAAE,mBAAmB,CAAC;AAAA,EAC/C;AACF;AAEA,SAAS,gBAAgB,SAAkB,QAAwB,SAA6B;AAC9F,QAAM,UAAW,QAAQ,KAAK,EAAE,KAAK,KAAK,OAAO;AACjD,MAAI,CAAC,QAAS,QAAO,UAAU,CAAC,OAAO,IAAI,CAAC;AAE5C,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAAmB,CAAC;AAC1B,MAAI,SAAS;AACX,SAAK,IAAI,OAAO;AAChB,WAAO,KAAK,OAAO;AAAA,EACrB;AACA,aAAW,WAAW,OAAO,OAAO,OAAO,YAAY,CAAC,CAAC,GAAG;AAC1D,QAAI,QAAQ,OAAO,CAAC,KAAK,IAAI,QAAQ,GAAG,GAAG;AACzC,WAAK,IAAI,QAAQ,GAAG;AACpB,aAAO,KAAK,QAAQ,GAAG;AAAA,IACzB;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,oBAAoB,GAAmB;AAC9C,UAAQ,GAAG;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO,MAAM,CAAC;AAAA,IAChB,KAAK;AACH,aAAO,IAAI,CAAC;AAAA,IACd,KAAK;AACH,aAAO,IAAI,CAAC;AAAA,IACd;AACE,aAAO,KAAK,CAAC;AAAA,EACjB;AACF;AAEA,SAAS,kBAAkB,QAA8B;AACvD,MAAI,CAAC,OAAO,YAAY,OAAO,SAAS,WAAW,EAAG,QAAO;AAC7D,SAAO;AAAA,IACL,GAAG;AAAA,IACH,UAAU,OAAO,SAAS,IAAI,CAAC,OAAO;AAAA,MACpC,GAAG;AAAA,MACH,QAAQ,oBAAoB,EAAE,MAAM;AAAA,IACtC,EAAE;AAAA,EACJ;AACF;AAEA,SAAS,aACP,QACA,eACA,aAC+B;AAC/B,SAAO,CAAC,WAA8B;AACpC,QAAI,WAAW,QAAQ;AACrB,YAAM,aAAa,IAAI,IAAI,OAAO,QAAQ;AAC1C,YAAM,WAAoC;AAAA,QACxC,aAAa,OAAO;AAAA,QACpB,WAAW,OAAO;AAAA,QAClB,QAAQ,OAAO;AAAA,QACf,UAAU,OAAO;AAAA,MACnB;AACA,UAAI,WAAW,IAAI,UAAU,EAAG,UAAS,UAAU,IAAI,OAAO;AAC9D,UAAI,WAAW,IAAI,QAAQ,EAAG,UAAS,QAAQ,IAAI,OAAO;AAC1D,UAAI,WAAW,IAAI,SAAS,EAAG,UAAS,SAAS,IAAI,OAAO;AAG5D,UAAI,aAAa,aAAa,YAAY,YAAY;AACpD,iBAAS,MAAM,IAAI,kBAAkB,YAAY,YAAY,MAAM;AACnE,iBAAS,WAAW,IAAI,YAAY,WAAW;AAAA,MACjD;AAEA,aAAO,aAAa,UAAU,MAAM;AAAA,IACtC;AACA,UAAM,YAAY,kBAAkB,MAAM;AAC1C,QAAI,kBAAkB,UAAW,QAAO,oBAAoB,SAAS;AACrE,WAAO,kBAAkB,SAAS;AAAA,EACpC;AACF;AAMO,SAAS,sBAAsB,SAAwB;AAC5D,UACG,QAAQ,QAAQ,EAChB,YAAY,4DAA4D,EACxE,OAAO,cAAc,yBAAyB,CAAC,MAAM,SAAS,GAAG,EAAE,GAAG,CAAC,EACvE,OAAO,qBAAqB,0BAA0B,CAAC,MAAM,SAAS,GAAG,EAAE,GAAG,EAAE,EAChF,OAAO,YAAY,uCAAuC,EAC1D,OAAO,aAAa,oCAAoC,EACxD,OAAO,mBAAmB,wBAAwB,CAAC,MAAM,SAAS,GAAG,EAAE,GAAG,IAAI,EAC9E,OAAO,kBAAkB,6CAA6C,OAAO,EAC7E;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC,OAAO,qBAAqB,2CAA2C,EACvE,OAAO,gBAAgB,mCAAmC,EAC1D,OAAO,cAAc,mDAAmD,YAAY,GAAG,EACvF,OAAO,YAAY,wDAAwD,EAC3E,OAAO,2BAA2B,2DAA2D,EAC7F;AAAA,IACC,OAAO,SAaD;AACJ,UAAI,CAAC,cAAc,IAAI,KAAK,MAAM,GAAG;AACnC,mBAAW,mBAAmB,KAAK,MAAM,KAAK,uBAAuB;AAAA,MACvE;AAEA,YAAM,WAAW,cAAc,KAAK,QAAQ;AAE5C,UAAI,CAAC,OAAO,SAAS,KAAK,IAAI,KAAK,KAAK,OAAO,GAAG;AAChD,mBAAW,2CAA2C,KAAK,IAAI,GAAG;AAAA,MACpE;AAEA,UAAI,CAAC,OAAO,SAAS,KAAK,UAAU,KAAK,KAAK,aAAa,GAAG;AAC5D,mBAAW,kDAAkD,KAAK,UAAU,GAAG;AAAA,MACjF;AAEA,YAAM,SAAS,MAAM,WAAW;AAChC,YAAM,SAAS,gBAAgB,SAAS,MAAM;AAC9C,UAAI,kBAA6C,uBAAuB,MAAM;AAC9E,UAAI,KAAK,WAAW;AAClB,cAAM,YAAY,wBAAwB,KAAK,SAAS;AACxD,0BAAkB,EAAE,GAAG,iBAAiB,GAAG,UAAU;AAAA,MACvD;AACA,YAAM,gBAAgB,qBAAqB,KAAK,KAAK;AACrD,YAAM,WAAW,gBAAgB,SAAS,QAAQ,KAAK,OAAO;AAE9D,UAAI,SAAS,WAAW,GAAG;AACzB,mBAAW,mBAAmB,qDAAqD;AAAA,MACrF;AACA,UAAI,KAAK,WAAW,SAAS,SAAS,cAAc;AAClD;AAAA,UACE,oBAAoB,SAAS,MAAM,cAAc,YAAY;AAAA,UAC7D;AAAA,QACF;AAAA,MACF;AAEA,YAAM,aAAa,OAAO;AAE1B,YAAM,cAAc,YAAY;AAC9B,cAAM,OAAO,MAAM,YAAY;AAAA,UAC7B,oBAAoB,YAAY;AAAA,QAClC,CAAC;AACD,eAAO;AAAA,UACL,QAAQ,gBAAgB,EAAE,KAAK,CAAC;AAAA,UAChC,WAAW,sBAAsB,EAAE,KAAK,CAAC;AAAA,QAC3C;AAAA,MACF;AAEA,UAAI,YAAY;AAEhB,iBAAW,eAAe,UAAU;AAClC,YAAI,SAAS,SAAS,GAAG;AACvB,kBAAQ,IAAI;AAAA,MAAS,WAAW,MAAM;AAAA,QACxC;AAEA,YAAI;AACF,gBAAM,SAAS,MAAM,oBAAoB;AAAA,YACvC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AACD,cAAI,OAAQ,aAAY;AAAA,QAC1B,SAAS,OAAO;AACd,cAAI,SAAS,WAAW,EAAG,OAAM;AAEjC,kBAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AAAA,QAClF;AAAA,MACF;AAEA,UAAI,UAAW,SAAQ,WAAW;AAAA,IACpC;AAAA,EACF;AACJ;AAoCA,SAAS,qBAAqB,QAAmB,mBAAwC;AACvF,QAAM,YAAY,IAAI,IAAI,iBAAiB;AAC3C,QAAM,WAAW,OAAO,SAAS,OAAO,CAAC,MAAM,UAAU,IAAI,CAAC,CAAC;AAC/D,MAAI,SAAS,WAAW,OAAO,SAAS,OAAQ,QAAO;AACvD,SAAO,EAAE,GAAG,QAAQ,UAAU,SAAS;AACzC;AAGA,eAAe,oBAAoB,KAA+B;AAChE,QAAM,EAAE,aAAa,MAAM,UAAU,iBAAiB,cAAc,IAAI;AAExE,QAAM,YAAY,YAAgC;AAChD,UAAM,EAAE,QAAQ,UAAU,IAAI,MAAM,IAAI,YAAY;AACpD,WAAO,aAAa,QAAQ,WAAW,aAAa;AAAA,MAClD,MAAM,KAAK;AAAA,MACX,YAAY,KAAK;AAAA,MACjB;AAAA,MACA,iBAAiB,mBAAmB;AAAA,IACtC,CAAC;AAAA,EACH;AAEA,QAAM,OAAO,CAACA,YAAsB,gBAAgB,aAAaA,SAAQ,KAAK,GAAG;AAGjF,QAAM,aAAa,KAAK,YAAY,MAAM,gBAAgB,aAAa,QAAQ,IAAI;AAGnF,QAAM,SAAS,aAAa,IAAI,QAAQ,KAAK,QAAQ;AAAA,IACnD;AAAA,IACA,WAAW,KAAK;AAAA,EAClB,CAAC;AAED,MAAI,kBAAkB,QAAQ,KAAK,WAAW;AAC5C,YAAQ,OAAO;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAGA,MAAI,kBAAkB,MAAM;AAC1B,UAAM,aAAa,EAAE,iBAAiB,eAAe,QAAQ,OAAO,WAAW,KAAK,CAAC;AACrF,WAAO;AAAA,EACT;AAGA,MAAI,KAAK,QAAQ;AACf,UAAM,SAAS,MAAM,gBAAgB,aAAa,KAAK,GAAG;AAC1D,QAAI,CAAC,QAAQ;AACX,YAAM,OAAO,OAAO,IAAI,MAAM,wBAAwB,GAAG;AAAA,QACvD,MAAM;AAAA,QACN,UAAU;AAAA,QACV,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AACA,UAAM,UAAU,qBAAqB,QAAQ,QAAQ;AACrD,kBAAc,SAAS,YAAY,KAAK,WAAW,QAAQ,IAAI,MAAM;AACrE,UAAM,aAAa,aAAa,QAAQ,KAAK,MAAM;AACnD,WAAO,gBAAgB,MAAM;AAAA,EAC/B;AAGA,MAAI,CAAC,KAAK,SAAS;AACjB,UAAM,SAAS,MAAM,gBAAgB,aAAa,KAAK,GAAG;AAC1D,QAAI,QAAQ;AACV,YAAM,UAAU,qBAAqB,QAAQ,QAAQ;AACrD,UAAI,IAAI,WAAW,UAAU,QAAQ,SAAS,SAAS,OAAO,SAAS,QAAQ;AAC7E,gBAAQ,OAAO;AAAA,UACb;AAAA;AAAA,QACF;AAAA,MACF;AACA,oBAAc,SAAS,YAAY,KAAK,WAAW,QAAQ,IAAI,MAAM;AACrE,YAAM,aAAa,aAAa,QAAQ,KAAK,MAAM;AACnD,aAAO,gBAAgB,MAAM;AAAA,IAC/B;AAAA,EACF;AAGA,QAAM,UAAU,cAAc,wBAAwB;AACtD,MAAI,IAAI,WAAW,OAAQ,SAAQ,MAAM;AACzC,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,UAAU;AAAA,EAC3B,SAAS,KAAK;AACZ,YAAQ,KAAK,4BAA4B;AACzC,UAAM;AAAA,EACR;AACA,UAAQ,KAAK,MAAM;AACnB,QAAM,KAAK,MAAM;AAEjB,gBAAc,QAAQ,YAAY,KAAK,WAAW,QAAQ,IAAI,MAAM;AACpE,QAAM,aAAa,aAAa,QAAQ,KAAK,MAAM;AACnD,SAAO,gBAAgB,MAAM;AAC/B;AAMA,SAAS,cACP,QACA,YACA,WACA,QACA,QACM;AACN,UAAQ,IAAI,OAAO,MAAM,CAAC;AAG1B,MAAI,WAAW,OAAQ;AAEvB,MAAI,aAAa,YAAY;AAC3B,UAAM,QAAQ,aAAa,WAAW,SAAS;AAC/C,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,iBAAiB,kBAAkB,YAAY,MAAM,GAAG,KAAK,CAAC;AAAA,EAC5E,WAAW,aAAa,CAAC,YAAY;AACnC,YAAQ,IAAI,4CAA4C;AAAA,EAC1D;AAGA,MAAI,KAAK,IAAI,KAAI,oBAAI,KAAK,YAAY,GAAE,QAAQ,GAAG;AACjD,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,IAAI,4EAAyE,CAAC;AAAA,EAC5F;AACF;AAEA,eAAe,aACb,aACA,QACA,QACe;AACf,MAAI,CAAC,OAAQ;AACb,QAAM,YAAY,gBAAgB,MAAM;AACxC,QAAM,UAAU,MAAM,iBAAiB,aAAa,SAAS;AAC7D,MAAI,SAAS;AACX,UAAM,QAAQ,YAAY,cAAc;AACxC,UAAM,OAAO,YACT,GAAG,WAAW,gCACd,GAAG,WAAW;AAClB,qBAAiB,OAAO,IAAI;AAAA,EAC9B;AACF;","names":["status"]}
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readJsonFile
4
- } from "./chunk-SLNJEAMK.js";
4
+ } from "./chunk-22XCOLZX.js";
5
5
  import {
6
6
  getClient,
7
7
  resolvePackageName
8
- } from "./chunk-NQH4G7BI.js";
8
+ } from "./chunk-JDRY7HK5.js";
9
9
  import {
10
10
  isDryRun,
11
11
  printDryRun
@@ -15,7 +15,7 @@ import {
15
15
  } from "./chunk-ELXAK7GI.js";
16
16
  import {
17
17
  requireConfirm
18
- } from "./chunk-YFUBD2XB.js";
18
+ } from "./chunk-RZQSEDKI.js";
19
19
 
20
20
  // src/commands/subscriptions.ts
21
21
  import { Option } from "commander";
@@ -73,7 +73,9 @@ function registerSubscriptionsCommands(program) {
73
73
  }));
74
74
  await maybePaginate(formatOutput(summary, format));
75
75
  } else {
76
- await maybePaginate(formatOutput(subs2.length === 0 ? { subscriptions: [] } : result, format));
76
+ await maybePaginate(
77
+ formatOutput(subs2.length === 0 ? { subscriptions: [] } : result, format)
78
+ );
77
79
  }
78
80
  });
79
81
  subs.command("get <product-id>").description("Get a subscription").action(async (productId) => {
@@ -268,13 +270,7 @@ function registerSubscriptionsCommands(program) {
268
270
  }
269
271
  const client = await getClient(config);
270
272
  const data = await readJsonFile(options.file);
271
- const result = await migratePrices(
272
- client,
273
- packageName,
274
- productId,
275
- basePlanId,
276
- data
277
- );
273
+ const result = await migratePrices(client, packageName, productId, basePlanId, data);
278
274
  console.log(formatOutput(result, format));
279
275
  }
280
276
  );
@@ -428,6 +424,47 @@ function registerSubscriptionsCommands(program) {
428
424
  const result = await deactivateOffer(client, packageName, productId, basePlanId, offerId);
429
425
  console.log(formatOutput(result, format));
430
426
  });
427
+ offers.command("batch-get <product-id> <base-plan-id>").description("Batch get multiple offers (max 100)").requiredOption("--ids <offer-ids>", "Comma-separated offer IDs").action(async (productId, basePlanId, options) => {
428
+ const config = await loadConfig();
429
+ const packageName = resolvePackageName(program.opts()["app"], config);
430
+ const client = await getClient(config);
431
+ const format = getOutputFormat(program, config);
432
+ const offerIds = options.ids.split(",").map((id) => id.trim());
433
+ const result = await client.subscriptions.batchGetOffers(
434
+ packageName,
435
+ productId,
436
+ basePlanId,
437
+ offerIds
438
+ );
439
+ console.log(formatOutput(result, format));
440
+ });
441
+ offers.command("batch-update-states <product-id> <base-plan-id>").description("Batch activate or deactivate multiple offers (max 100)").requiredOption("--file <path>", "JSON file with state transition requests").action(async (productId, basePlanId, options) => {
442
+ const config = await loadConfig();
443
+ const packageName = resolvePackageName(program.opts()["app"], config);
444
+ const format = getOutputFormat(program, config);
445
+ if (isDryRun(program)) {
446
+ printDryRun(
447
+ {
448
+ command: "subscriptions offers batch-update-states",
449
+ action: "batch update offer states",
450
+ target: `${productId}/${basePlanId}`,
451
+ details: { file: options.file }
452
+ },
453
+ format,
454
+ formatOutput
455
+ );
456
+ return;
457
+ }
458
+ const client = await getClient(config);
459
+ const requests = await readJsonFile(options.file);
460
+ const result = await client.subscriptions.batchUpdateOfferStates(
461
+ packageName,
462
+ productId,
463
+ basePlanId,
464
+ requests
465
+ );
466
+ console.log(formatOutput(result, format));
467
+ });
431
468
  subs.command("diff <product-id>").description("Compare local JSON file against remote subscription").requiredOption("--file <path>", "Local JSON file to compare against remote").action(async (productId, options) => {
432
469
  const config = await loadConfig();
433
470
  const packageName = resolvePackageName(program.opts()["app"], config);
@@ -470,4 +507,4 @@ By product:`);
470
507
  export {
471
508
  registerSubscriptionsCommands
472
509
  };
473
- //# sourceMappingURL=subscriptions-LURZFPGJ.js.map
510
+ //# sourceMappingURL=subscriptions-GTVJB4HX.js.map