@gpc-cli/cli 0.9.6 → 0.9.8

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 (59) hide show
  1. package/dist/{apps-TZG5GEDW.js → apps-5YQPQ3KB.js} +20 -3
  2. package/dist/apps-5YQPQ3KB.js.map +1 -0
  3. package/dist/bin.js +31 -5
  4. package/dist/bin.js.map +1 -1
  5. package/dist/{chunk-HFE2FMU4.js → chunk-JVGSSJLY.js} +62 -16
  6. package/dist/chunk-JVGSSJLY.js.map +1 -0
  7. package/dist/completion-3UYUJD6S.js +495 -0
  8. package/dist/completion-3UYUJD6S.js.map +1 -0
  9. package/dist/data-safety-M2SFKQ3U.js +91 -0
  10. package/dist/data-safety-M2SFKQ3U.js.map +1 -0
  11. package/dist/device-tiers-FUZC6IHD.js +74 -0
  12. package/dist/device-tiers-FUZC6IHD.js.map +1 -0
  13. package/dist/external-transactions-YRK5CI2D.js +131 -0
  14. package/dist/external-transactions-YRK5CI2D.js.map +1 -0
  15. package/dist/generated-apks-SUPM3NS3.js +73 -0
  16. package/dist/generated-apks-SUPM3NS3.js.map +1 -0
  17. package/dist/{iap-W4UDJMGY.js → iap-ZZS5NVFN.js} +7 -3
  18. package/dist/iap-ZZS5NVFN.js.map +1 -0
  19. package/dist/index.d.ts +14 -1
  20. package/dist/index.js +5 -3
  21. package/dist/internal-sharing-ZQUI3SDM.js +44 -0
  22. package/dist/internal-sharing-ZQUI3SDM.js.map +1 -0
  23. package/dist/{listings-42GCDAJU.js → listings-LTVQHMX3.js} +32 -2
  24. package/dist/listings-LTVQHMX3.js.map +1 -0
  25. package/dist/one-time-products-BHE6WPZI.js +266 -0
  26. package/dist/one-time-products-BHE6WPZI.js.map +1 -0
  27. package/dist/{publish-MGXRURW2.js → publish-YP7U64T2.js} +11 -6
  28. package/dist/publish-YP7U64T2.js.map +1 -0
  29. package/dist/recovery-DC66ZTGD.js +177 -0
  30. package/dist/recovery-DC66ZTGD.js.map +1 -0
  31. package/dist/{releases-DZWG2I4S.js → releases-GUMQZRFC.js} +14 -8
  32. package/dist/releases-GUMQZRFC.js.map +1 -0
  33. package/dist/{reports-OJSCABN6.js → reports-T6GBAYUK.js} +2 -2
  34. package/dist/reports-T6GBAYUK.js.map +1 -0
  35. package/dist/{reviews-SBR5FGEF.js → reviews-G3HZG3AS.js} +6 -4
  36. package/dist/reviews-G3HZG3AS.js.map +1 -0
  37. package/dist/{subscriptions-GSXDPXA4.js → subscriptions-CHQU5VXT.js} +7 -3
  38. package/dist/subscriptions-CHQU5VXT.js.map +1 -0
  39. package/dist/{testers-GSONKJAW.js → testers-NXWT2PFS.js} +8 -2
  40. package/dist/testers-NXWT2PFS.js.map +1 -0
  41. package/dist/{tracks-HMJ3F55N.js → tracks-AJH5KP7J.js} +2 -2
  42. package/dist/tracks-AJH5KP7J.js.map +1 -0
  43. package/dist/{users-L57T4MCQ.js → users-N2TK6KI7.js} +7 -3
  44. package/dist/users-N2TK6KI7.js.map +1 -0
  45. package/package.json +5 -5
  46. package/dist/apps-TZG5GEDW.js.map +0 -1
  47. package/dist/chunk-HFE2FMU4.js.map +0 -1
  48. package/dist/completion-IHVLP7OK.js +0 -145
  49. package/dist/completion-IHVLP7OK.js.map +0 -1
  50. package/dist/iap-W4UDJMGY.js.map +0 -1
  51. package/dist/listings-42GCDAJU.js.map +0 -1
  52. package/dist/publish-MGXRURW2.js.map +0 -1
  53. package/dist/releases-DZWG2I4S.js.map +0 -1
  54. package/dist/reports-OJSCABN6.js.map +0 -1
  55. package/dist/reviews-SBR5FGEF.js.map +0 -1
  56. package/dist/subscriptions-GSXDPXA4.js.map +0 -1
  57. package/dist/testers-GSONKJAW.js.map +0 -1
  58. package/dist/tracks-HMJ3F55N.js.map +0 -1
  59. package/dist/users-L57T4MCQ.js.map +0 -1
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ isDryRun,
4
+ printDryRun
5
+ } from "./chunk-Y3QZDAKS.js";
6
+ import {
7
+ requireConfirm
8
+ } from "./chunk-NV75I5VP.js";
9
+
10
+ // src/commands/one-time-products.ts
11
+ import { readFile } from "fs/promises";
12
+ import { loadConfig } from "@gpc-cli/config";
13
+ import { resolveAuth } from "@gpc-cli/auth";
14
+ import { createApiClient } from "@gpc-cli/api";
15
+ import {
16
+ listOneTimeProducts,
17
+ getOneTimeProduct,
18
+ createOneTimeProduct,
19
+ updateOneTimeProduct,
20
+ deleteOneTimeProduct,
21
+ listOneTimeOffers,
22
+ getOneTimeOffer,
23
+ createOneTimeOffer,
24
+ updateOneTimeOffer,
25
+ deleteOneTimeOffer,
26
+ detectOutputFormat,
27
+ formatOutput,
28
+ sortResults
29
+ } from "@gpc-cli/core";
30
+ function resolvePackageName(packageArg, config) {
31
+ const name = packageArg || config.app;
32
+ if (!name) {
33
+ console.error("Error: No package name. Use --app <package> or gpc config set app <package>");
34
+ process.exit(2);
35
+ }
36
+ return name;
37
+ }
38
+ async function getClient(config) {
39
+ const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });
40
+ return createApiClient({ auth });
41
+ }
42
+ function registerOneTimeProductsCommands(program) {
43
+ const otp = program.command("one-time-products").alias("otp").description("Manage one-time products and offers (modern OTP API)");
44
+ otp.command("list").description("List one-time products").option("--sort <field>", "Sort by field (prefix with - for descending)").action(async (options) => {
45
+ const config = await loadConfig();
46
+ const packageName = resolvePackageName(program.opts()["app"], config);
47
+ const client = await getClient(config);
48
+ const format = detectOutputFormat();
49
+ try {
50
+ const result = await listOneTimeProducts(client, packageName);
51
+ if (options.sort) {
52
+ result.oneTimeProducts = sortResults(result.oneTimeProducts, options.sort);
53
+ }
54
+ console.log(formatOutput(result, format));
55
+ } catch (error) {
56
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
57
+ process.exit(4);
58
+ }
59
+ });
60
+ otp.command("get <product-id>").description("Get a one-time product").action(async (productId) => {
61
+ const config = await loadConfig();
62
+ const packageName = resolvePackageName(program.opts()["app"], config);
63
+ const client = await getClient(config);
64
+ const format = detectOutputFormat();
65
+ try {
66
+ const result = await getOneTimeProduct(client, packageName, productId);
67
+ console.log(formatOutput(result, format));
68
+ } catch (error) {
69
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
70
+ process.exit(4);
71
+ }
72
+ });
73
+ otp.command("create").description("Create a one-time product from JSON file").requiredOption("--file <path>", "JSON file with product data").action(async (options) => {
74
+ const config = await loadConfig();
75
+ const packageName = resolvePackageName(program.opts()["app"], config);
76
+ const format = detectOutputFormat();
77
+ if (isDryRun(program)) {
78
+ printDryRun(
79
+ {
80
+ command: "one-time-products create",
81
+ action: "create",
82
+ target: `one-time product from ${options.file}`
83
+ },
84
+ format,
85
+ formatOutput
86
+ );
87
+ return;
88
+ }
89
+ const client = await getClient(config);
90
+ try {
91
+ const data = JSON.parse(await readFile(options.file, "utf-8"));
92
+ const result = await createOneTimeProduct(client, packageName, data);
93
+ console.log(formatOutput(result, format));
94
+ } catch (error) {
95
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
96
+ process.exit(4);
97
+ }
98
+ });
99
+ otp.command("update <product-id>").description("Update a one-time product from JSON file").requiredOption("--file <path>", "JSON file with product data").action(async (productId, options) => {
100
+ const config = await loadConfig();
101
+ const packageName = resolvePackageName(program.opts()["app"], config);
102
+ const format = detectOutputFormat();
103
+ if (isDryRun(program)) {
104
+ printDryRun(
105
+ {
106
+ command: "one-time-products update",
107
+ action: "update",
108
+ target: productId,
109
+ details: { file: options.file }
110
+ },
111
+ format,
112
+ formatOutput
113
+ );
114
+ return;
115
+ }
116
+ const client = await getClient(config);
117
+ try {
118
+ const data = JSON.parse(await readFile(options.file, "utf-8"));
119
+ const result = await updateOneTimeProduct(client, packageName, productId, data);
120
+ console.log(formatOutput(result, format));
121
+ } catch (error) {
122
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
123
+ process.exit(4);
124
+ }
125
+ });
126
+ otp.command("delete <product-id>").description("Delete a one-time product").action(async (productId) => {
127
+ const config = await loadConfig();
128
+ const packageName = resolvePackageName(program.opts()["app"], config);
129
+ await requireConfirm(`Delete one-time product "${productId}"?`, program);
130
+ if (isDryRun(program)) {
131
+ const format = detectOutputFormat();
132
+ printDryRun(
133
+ {
134
+ command: "one-time-products delete",
135
+ action: "delete",
136
+ target: productId
137
+ },
138
+ format,
139
+ formatOutput
140
+ );
141
+ return;
142
+ }
143
+ const client = await getClient(config);
144
+ try {
145
+ await deleteOneTimeProduct(client, packageName, productId);
146
+ console.log(`One-time product ${productId} deleted.`);
147
+ } catch (error) {
148
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
149
+ process.exit(4);
150
+ }
151
+ });
152
+ const offers = otp.command("offers").description("Manage one-time product offers");
153
+ offers.command("list <product-id>").description("List offers for a one-time product").option("--sort <field>", "Sort by field (prefix with - for descending)").action(async (productId, options) => {
154
+ const config = await loadConfig();
155
+ const packageName = resolvePackageName(program.opts()["app"], config);
156
+ const client = await getClient(config);
157
+ const format = detectOutputFormat();
158
+ try {
159
+ const result = await listOneTimeOffers(client, packageName, productId);
160
+ if (options.sort) {
161
+ result.oneTimeOffers = sortResults(result.oneTimeOffers, options.sort);
162
+ }
163
+ console.log(formatOutput(result, format));
164
+ } catch (error) {
165
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
166
+ process.exit(4);
167
+ }
168
+ });
169
+ offers.command("get <product-id> <offer-id>").description("Get an offer for a one-time product").action(async (productId, offerId) => {
170
+ const config = await loadConfig();
171
+ const packageName = resolvePackageName(program.opts()["app"], config);
172
+ const client = await getClient(config);
173
+ const format = detectOutputFormat();
174
+ try {
175
+ const result = await getOneTimeOffer(client, packageName, productId, offerId);
176
+ console.log(formatOutput(result, format));
177
+ } catch (error) {
178
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
179
+ process.exit(4);
180
+ }
181
+ });
182
+ offers.command("create <product-id>").description("Create an offer from JSON file").requiredOption("--file <path>", "JSON file with offer data").action(async (productId, options) => {
183
+ const config = await loadConfig();
184
+ const packageName = resolvePackageName(program.opts()["app"], config);
185
+ const format = detectOutputFormat();
186
+ if (isDryRun(program)) {
187
+ printDryRun(
188
+ {
189
+ command: "one-time-products offers create",
190
+ action: "create offer for",
191
+ target: productId,
192
+ details: { file: options.file }
193
+ },
194
+ format,
195
+ formatOutput
196
+ );
197
+ return;
198
+ }
199
+ const client = await getClient(config);
200
+ try {
201
+ const data = JSON.parse(await readFile(options.file, "utf-8"));
202
+ const result = await createOneTimeOffer(client, packageName, productId, data);
203
+ console.log(formatOutput(result, format));
204
+ } catch (error) {
205
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
206
+ process.exit(4);
207
+ }
208
+ });
209
+ offers.command("update <product-id> <offer-id>").description("Update an offer from JSON file").requiredOption("--file <path>", "JSON file with offer data").action(async (productId, offerId, options) => {
210
+ const config = await loadConfig();
211
+ const packageName = resolvePackageName(program.opts()["app"], config);
212
+ const format = detectOutputFormat();
213
+ if (isDryRun(program)) {
214
+ printDryRun(
215
+ {
216
+ command: "one-time-products offers update",
217
+ action: "update offer",
218
+ target: `${productId}/${offerId}`,
219
+ details: { file: options.file }
220
+ },
221
+ format,
222
+ formatOutput
223
+ );
224
+ return;
225
+ }
226
+ const client = await getClient(config);
227
+ try {
228
+ const data = JSON.parse(await readFile(options.file, "utf-8"));
229
+ const result = await updateOneTimeOffer(client, packageName, productId, offerId, data);
230
+ console.log(formatOutput(result, format));
231
+ } catch (error) {
232
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
233
+ process.exit(4);
234
+ }
235
+ });
236
+ offers.command("delete <product-id> <offer-id>").description("Delete an offer").action(async (productId, offerId) => {
237
+ const config = await loadConfig();
238
+ const packageName = resolvePackageName(program.opts()["app"], config);
239
+ await requireConfirm(`Delete offer "${offerId}" for product "${productId}"?`, program);
240
+ if (isDryRun(program)) {
241
+ const format = detectOutputFormat();
242
+ printDryRun(
243
+ {
244
+ command: "one-time-products offers delete",
245
+ action: "delete offer",
246
+ target: `${productId}/${offerId}`
247
+ },
248
+ format,
249
+ formatOutput
250
+ );
251
+ return;
252
+ }
253
+ const client = await getClient(config);
254
+ try {
255
+ await deleteOneTimeOffer(client, packageName, productId, offerId);
256
+ console.log(`Offer ${offerId} deleted.`);
257
+ } catch (error) {
258
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
259
+ process.exit(4);
260
+ }
261
+ });
262
+ }
263
+ export {
264
+ registerOneTimeProductsCommands
265
+ };
266
+ //# sourceMappingURL=one-time-products-BHE6WPZI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/one-time-products.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\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 } from \"@gpc-cli/api\";\nimport {\n listOneTimeProducts,\n getOneTimeProduct,\n createOneTimeProduct,\n updateOneTimeProduct,\n deleteOneTimeProduct,\n listOneTimeOffers,\n getOneTimeOffer,\n createOneTimeOffer,\n updateOneTimeOffer,\n deleteOneTimeOffer,\n detectOutputFormat,\n formatOutput,\n sortResults,\n} from \"@gpc-cli/core\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport { requireConfirm } from \"../prompt.js\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n console.error(\"Error: No package name. Use --app <package> or gpc config set app <package>\");\n process.exit(2);\n }\n return name;\n}\n\nasync function getClient(config: GpcConfig) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createApiClient({ auth });\n}\n\nexport function registerOneTimeProductsCommands(program: Command): void {\n const otp = program\n .command(\"one-time-products\")\n .alias(\"otp\")\n .description(\"Manage one-time products and offers (modern OTP API)\");\n\n otp\n .command(\"list\")\n .description(\"List one-time products\")\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 = detectOutputFormat();\n\n try {\n const result = await listOneTimeProducts(client, packageName);\n if (options.sort) {\n result.oneTimeProducts = sortResults(result.oneTimeProducts, options.sort);\n }\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n otp\n .command(\"get <product-id>\")\n .description(\"Get a one-time product\")\n .action(async (productId: string) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = detectOutputFormat();\n\n try {\n const result = await getOneTimeProduct(client, packageName, productId);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n otp\n .command(\"create\")\n .description(\"Create a one-time product from JSON file\")\n .requiredOption(\"--file <path>\", \"JSON file with product data\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = detectOutputFormat();\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"one-time-products create\",\n action: \"create\",\n target: `one-time product from ${options.file}`,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const data = JSON.parse(await readFile(options.file, \"utf-8\"));\n const result = await createOneTimeProduct(client, packageName, data);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n otp\n .command(\"update <product-id>\")\n .description(\"Update a one-time product from JSON file\")\n .requiredOption(\"--file <path>\", \"JSON file with product data\")\n .action(async (productId: string, options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = detectOutputFormat();\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"one-time-products update\",\n action: \"update\",\n target: productId,\n details: { file: options.file },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const data = JSON.parse(await readFile(options.file, \"utf-8\"));\n const result = await updateOneTimeProduct(client, packageName, productId, data);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n otp\n .command(\"delete <product-id>\")\n .description(\"Delete a one-time product\")\n .action(async (productId: string) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n\n await requireConfirm(`Delete one-time product \"${productId}\"?`, program);\n\n if (isDryRun(program)) {\n const format = detectOutputFormat();\n printDryRun(\n {\n command: \"one-time-products delete\",\n action: \"delete\",\n target: productId,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n await deleteOneTimeProduct(client, packageName, productId);\n console.log(`One-time product ${productId} deleted.`);\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n // --- Offers ---\n const offers = otp.command(\"offers\").description(\"Manage one-time product offers\");\n\n offers\n .command(\"list <product-id>\")\n .description(\"List offers for a one-time product\")\n .option(\"--sort <field>\", \"Sort by field (prefix with - for descending)\")\n .action(async (productId: string, options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = detectOutputFormat();\n\n try {\n const result = await listOneTimeOffers(client, packageName, productId);\n if (options.sort) {\n result.oneTimeOffers = sortResults(result.oneTimeOffers, options.sort);\n }\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n offers\n .command(\"get <product-id> <offer-id>\")\n .description(\"Get an offer for a one-time product\")\n .action(async (productId: string, offerId: string) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const client = await getClient(config);\n const format = detectOutputFormat();\n\n try {\n const result = await getOneTimeOffer(client, packageName, productId, offerId);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n offers\n .command(\"create <product-id>\")\n .description(\"Create an offer from JSON file\")\n .requiredOption(\"--file <path>\", \"JSON file with offer data\")\n .action(async (productId: string, options: { file: string }) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = detectOutputFormat();\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"one-time-products offers create\",\n action: \"create offer for\",\n target: productId,\n details: { file: options.file },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const data = JSON.parse(await readFile(options.file, \"utf-8\"));\n const result = await createOneTimeOffer(client, packageName, productId, data);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n offers\n .command(\"update <product-id> <offer-id>\")\n .description(\"Update an offer from JSON file\")\n .requiredOption(\"--file <path>\", \"JSON file with offer data\")\n .action(async (productId: string, offerId: string, options: { file: string }) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = detectOutputFormat();\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"one-time-products offers update\",\n action: \"update offer\",\n target: `${productId}/${offerId}`,\n details: { file: options.file },\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const data = JSON.parse(await readFile(options.file, \"utf-8\"));\n const result = await updateOneTimeOffer(client, packageName, productId, offerId, data);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n offers\n .command(\"delete <product-id> <offer-id>\")\n .description(\"Delete an offer\")\n .action(async (productId: string, offerId: string) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n\n await requireConfirm(`Delete offer \"${offerId}\" for product \"${productId}\"?`, program);\n\n if (isDryRun(program)) {\n const format = detectOutputFormat();\n printDryRun(\n {\n command: \"one-time-products offers delete\",\n action: \"delete offer\",\n target: `${productId}/${offerId}`,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n await deleteOneTimeOffer(client, packageName, productId, offerId);\n console.log(`Offer ${offerId} deleted.`);\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n}\n"],"mappings":"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","names":[]}
@@ -13,7 +13,7 @@ import { appendFile } from "fs/promises";
13
13
  import { loadConfig, getCacheDir } from "@gpc-cli/config";
14
14
  import { resolveAuth } from "@gpc-cli/auth";
15
15
  import { createApiClient } from "@gpc-cli/api";
16
- import { publish, writeAuditLog, createAuditEntry } from "@gpc-cli/core";
16
+ import { publish, generateNotesFromGit, writeAuditLog, createAuditEntry } from "@gpc-cli/core";
17
17
  import { detectOutputFormat, formatOutput } from "@gpc-cli/core";
18
18
  function resolvePackageName(packageArg, config) {
19
19
  const name = packageArg || config.app;
@@ -24,9 +24,10 @@ function resolvePackageName(packageArg, config) {
24
24
  return name;
25
25
  }
26
26
  function registerPublishCommand(program) {
27
- program.command("publish <file>").description("Validate, upload, and release in one step").option("--track <track>", "Target track", "internal").option("--rollout <percent>", "Staged rollout percentage (1-100)").option("--notes <text>", "Release notes (en-US)").option("--notes-dir <dir>", "Read release notes from directory (<dir>/<lang>.txt)").option("--name <name>", "Release name").option("--mapping <file>", "ProGuard/R8 mapping file for deobfuscation").option("--retry-log <path>", "Write retry log entries to file (JSONL)").action(async (file, options) => {
28
- if (options.notes && options.notesDir) {
29
- console.error("Error: Cannot use both --notes and --notes-dir");
27
+ program.command("publish <file>").description("Validate, upload, and release in one step").option("--track <track>", "Target track", "internal").option("--rollout <percent>", "Staged rollout percentage (1-100)").option("--notes <text>", "Release notes (en-US)").option("--notes-dir <dir>", "Read release notes from directory (<dir>/<lang>.txt)").option("--notes-from-git", "Generate release notes from git commit history").option("--since <ref>", "Git ref to start from (tag, SHA) \u2014 used with --notes-from-git").option("--name <name>", "Release name").option("--mapping <file>", "ProGuard/R8 mapping file for deobfuscation").option("--retry-log <path>", "Write retry log entries to file (JSONL)").action(async (file, options) => {
28
+ const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);
29
+ if (noteSources.length > 1) {
30
+ console.error("Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.");
30
31
  process.exit(2);
31
32
  }
32
33
  const config = await loadConfig();
@@ -46,11 +47,15 @@ function registerPublishCommand(program) {
46
47
  options.rollout = rolloutStr;
47
48
  }
48
49
  }
49
- if (!options.notes && !options.notesDir) {
50
+ if (!options.notes && !options.notesDir && !options.notesFromGit) {
50
51
  const notes = await promptInput("Release notes (en-US, blank to skip)");
51
52
  if (notes) options.notes = notes;
52
53
  }
53
54
  }
55
+ if (options.notesFromGit) {
56
+ const gitNotes = await generateNotesFromGit({ since: options.since });
57
+ options.notes = gitNotes.text;
58
+ }
54
59
  let onRetry;
55
60
  if (options.retryLog) {
56
61
  onRetry = (entry) => {
@@ -126,4 +131,4 @@ function registerPublishCommand(program) {
126
131
  export {
127
132
  registerPublishCommand
128
133
  };
129
- //# sourceMappingURL=publish-MGXRURW2.js.map
134
+ //# sourceMappingURL=publish-YP7U64T2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/publish.ts"],"sourcesContent":["import { appendFile } from \"node:fs/promises\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport type { Command } from \"commander\";\nimport { loadConfig, getCacheDir } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient } from \"@gpc-cli/api\";\nimport type { RetryLogEntry } from \"@gpc-cli/api\";\nimport { publish, generateNotesFromGit, writeAuditLog, createAuditEntry } from \"@gpc-cli/core\";\nimport { detectOutputFormat, formatOutput } from \"@gpc-cli/core\";\nimport { isDryRun } from \"../dry-run.js\";\nimport { isInteractive, promptSelect, promptInput } from \"../prompt.js\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n console.error(\"Error: No package name. Use --app <package> or gpc config set app <package>\");\n process.exit(2);\n }\n return name;\n}\n\nexport function registerPublishCommand(program: Command): void {\n program\n .command(\"publish <file>\")\n .description(\"Validate, upload, and release in one step\")\n .option(\"--track <track>\", \"Target track\", \"internal\")\n .option(\"--rollout <percent>\", \"Staged rollout percentage (1-100)\")\n .option(\"--notes <text>\", \"Release notes (en-US)\")\n .option(\"--notes-dir <dir>\", \"Read release notes from directory (<dir>/<lang>.txt)\")\n .option(\"--notes-from-git\", \"Generate release notes from git commit history\")\n .option(\"--since <ref>\", \"Git ref to start from (tag, SHA) — used with --notes-from-git\")\n .option(\"--name <name>\", \"Release name\")\n .option(\"--mapping <file>\", \"ProGuard/R8 mapping file for deobfuscation\")\n .option(\"--retry-log <path>\", \"Write retry log entries to file (JSONL)\")\n .action(async (file: string, options) => {\n const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);\n if (noteSources.length > 1) {\n console.error(\"Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.\");\n process.exit(2);\n }\n\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = detectOutputFormat();\n\n // Interactive mode: prompt for missing options\n if (isInteractive(program)) {\n if (!options.track || options.track === \"internal\") {\n const tracks = [\"internal\", \"alpha\", \"beta\", \"production\"];\n options.track = await promptSelect(\"Select track:\", tracks, \"internal\");\n }\n\n if (!options.rollout && options.track === \"production\") {\n const rolloutStr = await promptInput(\n \"Staged rollout percentage (1-100, blank for full)\",\n \"100\",\n );\n if (rolloutStr && rolloutStr !== \"100\") {\n options.rollout = rolloutStr;\n }\n }\n\n if (!options.notes && !options.notesDir && !options.notesFromGit) {\n const notes = await promptInput(\"Release notes (en-US, blank to skip)\");\n if (notes) options.notes = notes;\n }\n }\n\n // Resolve git-based release notes before calling publish\n if (options.notesFromGit) {\n const gitNotes = await generateNotesFromGit({ since: options.since });\n options.notes = gitNotes.text;\n }\n\n let onRetry: ((entry: RetryLogEntry) => void) | undefined;\n if (options.retryLog) {\n onRetry = (entry: RetryLogEntry) => {\n appendFile(options.retryLog, JSON.stringify(entry) + \"\\n\").catch(() => {});\n };\n }\n\n const auth = await resolveAuth({\n serviceAccountPath: config.auth?.serviceAccount,\n cachePath: getCacheDir(),\n });\n const client = createApiClient({ auth, onRetry });\n\n if (isDryRun(program)) {\n try {\n const result = await publish(client, packageName, file, {\n track: options.track,\n rolloutPercent: options.rollout ? Number(options.rollout) : undefined,\n notes: options.notes,\n notesDir: options.notesDir,\n releaseName: options.name,\n mappingFile: options.mapping,\n dryRun: true,\n });\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n return;\n }\n\n const auditEntry = createAuditEntry(\n \"publish\",\n {\n file,\n track: options.track,\n rollout: options.rollout,\n },\n packageName,\n );\n\n try {\n const result = await publish(client, packageName, file, {\n track: options.track,\n rolloutPercent: options.rollout ? Number(options.rollout) : undefined,\n notes: options.notes,\n notesDir: options.notesDir,\n releaseName: options.name,\n mappingFile: options.mapping,\n });\n\n if (!result.upload) {\n console.error(\"Validation failed:\");\n for (const check of result.validation.checks) {\n const icon = check.passed ? \"✓\" : \"✗\";\n console.error(` ${icon} ${check.name}: ${check.message}`);\n }\n auditEntry.success = false;\n auditEntry.error = \"Validation failed\";\n process.exit(1);\n }\n\n console.log(formatOutput(result, format));\n auditEntry.success = true;\n } catch (error) {\n auditEntry.success = false;\n auditEntry.error = error instanceof Error ? error.message : String(error);\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n } finally {\n auditEntry.durationMs = Date.now() - new Date(auditEntry.timestamp).getTime();\n writeAuditLog(auditEntry).catch(() => {});\n }\n });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ isDryRun,
4
+ printDryRun
5
+ } from "./chunk-Y3QZDAKS.js";
6
+ import {
7
+ requireConfirm
8
+ } from "./chunk-NV75I5VP.js";
9
+
10
+ // src/commands/recovery.ts
11
+ import { loadConfig } from "@gpc-cli/config";
12
+ import { resolveAuth } from "@gpc-cli/auth";
13
+ import { createApiClient } from "@gpc-cli/api";
14
+ import {
15
+ listRecoveryActions,
16
+ cancelRecoveryAction,
17
+ deployRecoveryAction,
18
+ createRecoveryAction,
19
+ addRecoveryTargeting,
20
+ detectOutputFormat,
21
+ formatOutput
22
+ } from "@gpc-cli/core";
23
+ import { readFileSync } from "fs";
24
+ function resolvePackageName(packageArg, config) {
25
+ const name = packageArg || config.app;
26
+ if (!name) {
27
+ console.error("Error: No package name. Use --app <package> or gpc config set app <package>");
28
+ process.exit(2);
29
+ }
30
+ return name;
31
+ }
32
+ async function getClient(config) {
33
+ const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });
34
+ return createApiClient({ auth });
35
+ }
36
+ function registerRecoveryCommands(program) {
37
+ const recovery = program.command("recovery").description("Manage app recovery actions");
38
+ recovery.command("list").description("List app recovery actions").option("--limit <n>", "Maximum results to return").option("--next-page <token>", "Pagination token for next page").action(async (options) => {
39
+ const config = await loadConfig();
40
+ const packageName = resolvePackageName(program.opts()["app"], config);
41
+ const client = await getClient(config);
42
+ const format = detectOutputFormat();
43
+ try {
44
+ const result = await listRecoveryActions(client, packageName);
45
+ console.log(formatOutput(result, format));
46
+ } catch (error) {
47
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
48
+ process.exit(4);
49
+ }
50
+ });
51
+ recovery.command("cancel <id>").description("Cancel a recovery action").action(async (id) => {
52
+ const config = await loadConfig();
53
+ const packageName = resolvePackageName(program.opts()["app"], config);
54
+ const format = detectOutputFormat();
55
+ if (isDryRun(program)) {
56
+ printDryRun(
57
+ {
58
+ command: "recovery cancel",
59
+ action: "cancel",
60
+ target: id
61
+ },
62
+ format,
63
+ formatOutput
64
+ );
65
+ return;
66
+ }
67
+ await requireConfirm(`Cancel recovery action ${id}?`, program);
68
+ const client = await getClient(config);
69
+ try {
70
+ await cancelRecoveryAction(client, packageName, id);
71
+ console.log(formatOutput({ success: true, appRecoveryId: id, action: "cancelled" }, format));
72
+ } catch (error) {
73
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
74
+ process.exit(4);
75
+ }
76
+ });
77
+ recovery.command("deploy <id>").description("Deploy a recovery action").action(async (id) => {
78
+ const config = await loadConfig();
79
+ const packageName = resolvePackageName(program.opts()["app"], config);
80
+ const format = detectOutputFormat();
81
+ if (isDryRun(program)) {
82
+ printDryRun(
83
+ {
84
+ command: "recovery deploy",
85
+ action: "deploy",
86
+ target: id
87
+ },
88
+ format,
89
+ formatOutput
90
+ );
91
+ return;
92
+ }
93
+ await requireConfirm(`Deploy recovery action ${id}?`, program);
94
+ const client = await getClient(config);
95
+ try {
96
+ await deployRecoveryAction(client, packageName, id);
97
+ console.log(formatOutput({ success: true, appRecoveryId: id, action: "deployed" }, format));
98
+ } catch (error) {
99
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
100
+ process.exit(4);
101
+ }
102
+ });
103
+ recovery.command("create").description("Create a new app recovery action").requiredOption("--file <path>", "Path to JSON file with recovery action data").action(async (options) => {
104
+ const config = await loadConfig();
105
+ const packageName = resolvePackageName(program.opts()["app"], config);
106
+ const format = detectOutputFormat();
107
+ let data;
108
+ try {
109
+ data = JSON.parse(readFileSync(options.file, "utf-8"));
110
+ } catch (err) {
111
+ console.error(
112
+ `Error: Could not read recovery action data from ${options.file}: ${err instanceof Error ? err.message : String(err)}`
113
+ );
114
+ process.exit(2);
115
+ }
116
+ if (isDryRun(program)) {
117
+ printDryRun(
118
+ {
119
+ command: "recovery create",
120
+ action: "create recovery action",
121
+ target: packageName,
122
+ details: data
123
+ },
124
+ format,
125
+ formatOutput
126
+ );
127
+ return;
128
+ }
129
+ const client = await getClient(config);
130
+ try {
131
+ const result = await createRecoveryAction(client, packageName, data);
132
+ console.log(formatOutput(result, format));
133
+ } catch (error) {
134
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
135
+ process.exit(4);
136
+ }
137
+ });
138
+ recovery.command("add-targeting <action-id>").description("Add targeting rules to an existing recovery action").requiredOption("--file <path>", "Path to JSON file with targeting data").action(async (actionId, options) => {
139
+ const config = await loadConfig();
140
+ const packageName = resolvePackageName(program.opts()["app"], config);
141
+ const format = detectOutputFormat();
142
+ let targeting;
143
+ try {
144
+ targeting = JSON.parse(readFileSync(options.file, "utf-8"));
145
+ } catch (err) {
146
+ console.error(
147
+ `Error: Could not read targeting data from ${options.file}: ${err instanceof Error ? err.message : String(err)}`
148
+ );
149
+ process.exit(2);
150
+ }
151
+ if (isDryRun(program)) {
152
+ printDryRun(
153
+ {
154
+ command: "recovery add-targeting",
155
+ action: "add targeting to recovery action",
156
+ target: actionId,
157
+ details: targeting
158
+ },
159
+ format,
160
+ formatOutput
161
+ );
162
+ return;
163
+ }
164
+ const client = await getClient(config);
165
+ try {
166
+ const result = await addRecoveryTargeting(client, packageName, actionId, targeting);
167
+ console.log(formatOutput(result, format));
168
+ } catch (error) {
169
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
170
+ process.exit(4);
171
+ }
172
+ });
173
+ }
174
+ export {
175
+ registerRecoveryCommands
176
+ };
177
+ //# sourceMappingURL=recovery-DC66ZTGD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/recovery.ts"],"sourcesContent":["import type { Command } from \"commander\";\nimport type { GpcConfig } from \"@gpc-cli/config\";\nimport { loadConfig } from \"@gpc-cli/config\";\nimport { resolveAuth } from \"@gpc-cli/auth\";\nimport { createApiClient } from \"@gpc-cli/api\";\nimport {\n listRecoveryActions,\n cancelRecoveryAction,\n deployRecoveryAction,\n createRecoveryAction,\n addRecoveryTargeting,\n detectOutputFormat,\n formatOutput,\n} from \"@gpc-cli/core\";\nimport { isDryRun, printDryRun } from \"../dry-run.js\";\nimport { requireConfirm } from \"../prompt.js\";\nimport { readFileSync } from \"node:fs\";\n\nfunction resolvePackageName(packageArg: string | undefined, config: GpcConfig): string {\n const name = packageArg || config.app;\n if (!name) {\n console.error(\"Error: No package name. Use --app <package> or gpc config set app <package>\");\n process.exit(2);\n }\n return name;\n}\n\nasync function getClient(config: GpcConfig) {\n const auth = await resolveAuth({ serviceAccountPath: config.auth?.serviceAccount });\n return createApiClient({ auth });\n}\n\nexport function registerRecoveryCommands(program: Command): void {\n const recovery = program.command(\"recovery\").description(\"Manage app recovery actions\");\n\n recovery\n .command(\"list\")\n .description(\"List app recovery actions\")\n .option(\"--limit <n>\", \"Maximum results to return\")\n .option(\"--next-page <token>\", \"Pagination token for next page\")\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 = detectOutputFormat();\n\n try {\n const result = await listRecoveryActions(client, packageName);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n recovery\n .command(\"cancel <id>\")\n .description(\"Cancel a recovery action\")\n .action(async (id: string) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = detectOutputFormat();\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"recovery cancel\",\n action: \"cancel\",\n target: id,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n await requireConfirm(`Cancel recovery action ${id}?`, program);\n\n const client = await getClient(config);\n\n try {\n await cancelRecoveryAction(client, packageName, id);\n console.log(formatOutput({ success: true, appRecoveryId: id, action: \"cancelled\" }, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n recovery\n .command(\"deploy <id>\")\n .description(\"Deploy a recovery action\")\n .action(async (id: string) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = detectOutputFormat();\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"recovery deploy\",\n action: \"deploy\",\n target: id,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n await requireConfirm(`Deploy recovery action ${id}?`, program);\n\n const client = await getClient(config);\n\n try {\n await deployRecoveryAction(client, packageName, id);\n console.log(formatOutput({ success: true, appRecoveryId: id, action: \"deployed\" }, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n recovery\n .command(\"create\")\n .description(\"Create a new app recovery action\")\n .requiredOption(\"--file <path>\", \"Path to JSON file with recovery action data\")\n .action(async (options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = detectOutputFormat();\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(readFileSync(options.file, \"utf-8\"));\n } catch (err) {\n console.error(\n `Error: Could not read recovery action data from ${options.file}: ${err instanceof Error ? err.message : String(err)}`,\n );\n process.exit(2);\n }\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"recovery create\",\n action: \"create recovery action\",\n target: packageName,\n details: data,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const result = await createRecoveryAction(client, packageName, data);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n\n recovery\n .command(\"add-targeting <action-id>\")\n .description(\"Add targeting rules to an existing recovery action\")\n .requiredOption(\"--file <path>\", \"Path to JSON file with targeting data\")\n .action(async (actionId: string, options) => {\n const config = await loadConfig();\n const packageName = resolvePackageName(program.opts()[\"app\"], config);\n const format = detectOutputFormat();\n\n let targeting: Record<string, unknown>;\n try {\n targeting = JSON.parse(readFileSync(options.file, \"utf-8\"));\n } catch (err) {\n console.error(\n `Error: Could not read targeting data from ${options.file}: ${err instanceof Error ? err.message : String(err)}`,\n );\n process.exit(2);\n }\n\n if (isDryRun(program)) {\n printDryRun(\n {\n command: \"recovery add-targeting\",\n action: \"add targeting to recovery action\",\n target: actionId,\n details: targeting,\n },\n format,\n formatOutput,\n );\n return;\n }\n\n const client = await getClient(config);\n\n try {\n const result = await addRecoveryTargeting(client, packageName, actionId, targeting);\n console.log(formatOutput(result, format));\n } catch (error) {\n console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(4);\n }\n });\n}\n"],"mappings":"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","names":[]}
@@ -21,10 +21,11 @@ import {
21
21
  promoteRelease,
22
22
  updateRollout,
23
23
  readReleaseNotesFromDir,
24
+ generateNotesFromGit,
24
25
  writeAuditLog,
25
26
  createAuditEntry
26
27
  } from "@gpc-cli/core";
27
- import { detectOutputFormat, formatOutput } from "@gpc-cli/core";
28
+ import { detectOutputFormat, formatOutput, sortResults } from "@gpc-cli/core";
28
29
  function resolvePackageName(packageArg, config) {
29
30
  const name = packageArg || config.app;
30
31
  if (!name) {
@@ -47,9 +48,10 @@ async function getClient(config, retryLogPath) {
47
48
  }
48
49
  function registerReleasesCommands(program) {
49
50
  const releases = program.command("releases").description("Manage releases and rollouts");
50
- releases.command("upload <file>").description("Upload AAB/APK and assign to a track").option("--track <track>", "Target track", "internal").option("--rollout <percent>", "Staged rollout percentage (1-100)").option("--notes <text>", "Release notes (en-US)").option("--name <name>", "Release name").option("--mapping <file>", "ProGuard/R8 mapping file for deobfuscation").option("--notes-dir <dir>", "Read release notes from directory (<dir>/<lang>.txt)").option("--retry-log <path>", "Write retry log entries to file (JSONL)").action(async (file, options) => {
51
- if (options.notes && options.notesDir) {
52
- console.error("Error: Cannot use both --notes and --notes-dir");
51
+ releases.command("upload <file>").description("Upload AAB/APK and assign to a track").option("--track <track>", "Target track", "internal").option("--rollout <percent>", "Staged rollout percentage (1-100)").option("--notes <text>", "Release notes (en-US)").option("--name <name>", "Release name").option("--mapping <file>", "ProGuard/R8 mapping file for deobfuscation").option("--notes-dir <dir>", "Read release notes from directory (<dir>/<lang>.txt)").option("--notes-from-git", "Generate release notes from git commit history").option("--since <ref>", "Git ref to start from (tag, SHA) \u2014 used with --notes-from-git").option("--retry-log <path>", "Write retry log entries to file (JSONL)").action(async (file, options) => {
52
+ const noteSources = [options.notes, options.notesDir, options.notesFromGit].filter(Boolean);
53
+ if (noteSources.length > 1) {
54
+ console.error("Error: Cannot combine --notes, --notes-dir, and --notes-from-git. Use only one.");
53
55
  process.exit(2);
54
56
  }
55
57
  const config = await loadConfig();
@@ -100,7 +102,10 @@ function registerReleasesCommands(program) {
100
102
  );
101
103
  try {
102
104
  let releaseNotes;
103
- if (options.notesDir) {
105
+ if (options.notesFromGit) {
106
+ const gitNotes = await generateNotesFromGit({ since: options.since });
107
+ releaseNotes = [{ language: gitNotes.language, text: gitNotes.text }];
108
+ } else if (options.notesDir) {
104
109
  releaseNotes = await readReleaseNotesFromDir(options.notesDir);
105
110
  } else if (options.notes) {
106
111
  releaseNotes = [{ language: "en-US", text: options.notes }];
@@ -125,14 +130,15 @@ function registerReleasesCommands(program) {
125
130
  });
126
131
  }
127
132
  });
128
- releases.command("status").description("Show current release status across tracks").option("--track <track>", "Filter by track").action(async (options) => {
133
+ releases.command("status").description("Show current release status across tracks").option("--track <track>", "Filter by track").option("--sort <field>", "Sort by field (prefix with - for descending)").action(async (options) => {
129
134
  const config = await loadConfig();
130
135
  const packageName = resolvePackageName(program.opts()["app"], config);
131
136
  const client = await getClient(config);
132
137
  const format = detectOutputFormat();
133
138
  try {
134
139
  const statuses = await getReleasesStatus(client, packageName, options.track);
135
- console.log(formatOutput(statuses, format));
140
+ const sorted = Array.isArray(statuses) ? sortResults(statuses, options.sort) : statuses;
141
+ console.log(formatOutput(sorted, format));
136
142
  } catch (error) {
137
143
  console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
138
144
  process.exit(4);
@@ -281,4 +287,4 @@ function registerReleasesCommands(program) {
281
287
  export {
282
288
  registerReleasesCommands
283
289
  };
284
- //# sourceMappingURL=releases-DZWG2I4S.js.map
290
+ //# sourceMappingURL=releases-GUMQZRFC.js.map