@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.
- package/dist/{apps-TZG5GEDW.js → apps-5YQPQ3KB.js} +20 -3
- package/dist/apps-5YQPQ3KB.js.map +1 -0
- package/dist/bin.js +31 -5
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-HFE2FMU4.js → chunk-JVGSSJLY.js} +62 -16
- package/dist/chunk-JVGSSJLY.js.map +1 -0
- package/dist/completion-3UYUJD6S.js +495 -0
- package/dist/completion-3UYUJD6S.js.map +1 -0
- package/dist/data-safety-M2SFKQ3U.js +91 -0
- package/dist/data-safety-M2SFKQ3U.js.map +1 -0
- package/dist/device-tiers-FUZC6IHD.js +74 -0
- package/dist/device-tiers-FUZC6IHD.js.map +1 -0
- package/dist/external-transactions-YRK5CI2D.js +131 -0
- package/dist/external-transactions-YRK5CI2D.js.map +1 -0
- package/dist/generated-apks-SUPM3NS3.js +73 -0
- package/dist/generated-apks-SUPM3NS3.js.map +1 -0
- package/dist/{iap-W4UDJMGY.js → iap-ZZS5NVFN.js} +7 -3
- package/dist/iap-ZZS5NVFN.js.map +1 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +5 -3
- package/dist/internal-sharing-ZQUI3SDM.js +44 -0
- package/dist/internal-sharing-ZQUI3SDM.js.map +1 -0
- package/dist/{listings-42GCDAJU.js → listings-LTVQHMX3.js} +32 -2
- package/dist/listings-LTVQHMX3.js.map +1 -0
- package/dist/one-time-products-BHE6WPZI.js +266 -0
- package/dist/one-time-products-BHE6WPZI.js.map +1 -0
- package/dist/{publish-MGXRURW2.js → publish-YP7U64T2.js} +11 -6
- package/dist/publish-YP7U64T2.js.map +1 -0
- package/dist/recovery-DC66ZTGD.js +177 -0
- package/dist/recovery-DC66ZTGD.js.map +1 -0
- package/dist/{releases-DZWG2I4S.js → releases-GUMQZRFC.js} +14 -8
- package/dist/releases-GUMQZRFC.js.map +1 -0
- package/dist/{reports-OJSCABN6.js → reports-T6GBAYUK.js} +2 -2
- package/dist/reports-T6GBAYUK.js.map +1 -0
- package/dist/{reviews-SBR5FGEF.js → reviews-G3HZG3AS.js} +6 -4
- package/dist/reviews-G3HZG3AS.js.map +1 -0
- package/dist/{subscriptions-GSXDPXA4.js → subscriptions-CHQU5VXT.js} +7 -3
- package/dist/subscriptions-CHQU5VXT.js.map +1 -0
- package/dist/{testers-GSONKJAW.js → testers-NXWT2PFS.js} +8 -2
- package/dist/testers-NXWT2PFS.js.map +1 -0
- package/dist/{tracks-HMJ3F55N.js → tracks-AJH5KP7J.js} +2 -2
- package/dist/tracks-AJH5KP7J.js.map +1 -0
- package/dist/{users-L57T4MCQ.js → users-N2TK6KI7.js} +7 -3
- package/dist/users-N2TK6KI7.js.map +1 -0
- package/package.json +5 -5
- package/dist/apps-TZG5GEDW.js.map +0 -1
- package/dist/chunk-HFE2FMU4.js.map +0 -1
- package/dist/completion-IHVLP7OK.js +0 -145
- package/dist/completion-IHVLP7OK.js.map +0 -1
- package/dist/iap-W4UDJMGY.js.map +0 -1
- package/dist/listings-42GCDAJU.js.map +0 -1
- package/dist/publish-MGXRURW2.js.map +0 -1
- package/dist/releases-DZWG2I4S.js.map +0 -1
- package/dist/reports-OJSCABN6.js.map +0 -1
- package/dist/reviews-SBR5FGEF.js.map +0 -1
- package/dist/subscriptions-GSXDPXA4.js.map +0 -1
- package/dist/testers-GSONKJAW.js.map +0 -1
- package/dist/tracks-HMJ3F55N.js.map +0 -1
- 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
|
-
|
|
29
|
-
|
|
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-
|
|
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":";;;;;;;;;;;AAAA,SAAS,kBAAkB;AAG3B,SAAS,YAAY,mBAAmB;AACxC,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAEhC,SAAS,SAAS,sBAAsB,eAAe,wBAAwB;AAC/E,SAAS,oBAAoB,oBAAoB;AAIjD,SAAS,mBAAmB,YAAgC,QAA2B;AACrF,QAAM,OAAO,cAAc,OAAO;AAClC,MAAI,CAAC,MAAM;AACT,YAAQ,MAAM,6EAA6E;AAC3F,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAEO,SAAS,uBAAuB,SAAwB;AAC7D,UACG,QAAQ,gBAAgB,EACxB,YAAY,2CAA2C,EACvD,OAAO,mBAAmB,gBAAgB,UAAU,EACpD,OAAO,uBAAuB,mCAAmC,EACjE,OAAO,kBAAkB,uBAAuB,EAChD,OAAO,qBAAqB,sDAAsD,EAClF,OAAO,oBAAoB,gDAAgD,EAC3E,OAAO,iBAAiB,oEAA+D,EACvF,OAAO,iBAAiB,cAAc,EACtC,OAAO,oBAAoB,4CAA4C,EACvE,OAAO,sBAAsB,yCAAyC,EACtE,OAAO,OAAO,MAAc,YAAY;AACvC,UAAM,cAAc,CAAC,QAAQ,OAAO,QAAQ,UAAU,QAAQ,YAAY,EAAE,OAAO,OAAO;AAC1F,QAAI,YAAY,SAAS,GAAG;AAC1B,cAAQ,MAAM,iFAAiF;AAC/F,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,cAAc,mBAAmB,QAAQ,KAAK,EAAE,KAAK,GAAG,MAAM;AACpE,UAAM,SAAS,mBAAmB;AAGlC,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,YAAY,CAAC,QAAQ,cAAc;AAChE,cAAM,QAAQ,MAAM,YAAY,sCAAsC;AACtE,YAAI,MAAO,SAAQ,QAAQ;AAAA,MAC7B;AAAA,IACF;AAGA,QAAI,QAAQ,cAAc;AACxB,YAAM,WAAW,MAAM,qBAAqB,EAAE,OAAO,QAAQ,MAAM,CAAC;AACpE,cAAQ,QAAQ,SAAS;AAAA,IAC3B;AAEA,QAAI;AACJ,QAAI,QAAQ,UAAU;AACpB,gBAAU,CAAC,UAAyB;AAClC,mBAAW,QAAQ,UAAU,KAAK,UAAU,KAAK,IAAI,IAAI,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC3E;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,YAAY;AAAA,MAC7B,oBAAoB,OAAO,MAAM;AAAA,MACjC,WAAW,YAAY;AAAA,IACzB,CAAC;AACD,UAAM,SAAS,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAEhD,QAAI,SAAS,OAAO,GAAG;AACrB,UAAI;AACF,cAAM,SAAS,MAAM,QAAQ,QAAQ,aAAa,MAAM;AAAA,UACtD,OAAO,QAAQ;AAAA,UACf,gBAAgB,QAAQ,UAAU,OAAO,QAAQ,OAAO,IAAI;AAAA,UAC5D,OAAO,QAAQ;AAAA,UACf,UAAU,QAAQ;AAAA,UAClB,aAAa,QAAQ;AAAA,UACrB,aAAa,QAAQ;AAAA,UACrB,QAAQ;AAAA,QACV,CAAC;AACD,gBAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AAAA,MAC1C,SAAS,OAAO;AACd,gBAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AAChF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA;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,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,QAAQ,aAAa,MAAM;AAAA,QACtD,OAAO,QAAQ;AAAA,QACf,gBAAgB,QAAQ,UAAU,OAAO,QAAQ,OAAO,IAAI;AAAA,QAC5D,OAAO,QAAQ;AAAA,QACf,UAAU,QAAQ;AAAA,QAClB,aAAa,QAAQ;AAAA,QACrB,aAAa,QAAQ;AAAA,MACvB,CAAC;AAED,UAAI,CAAC,OAAO,QAAQ;AAClB,gBAAQ,MAAM,oBAAoB;AAClC,mBAAW,SAAS,OAAO,WAAW,QAAQ;AAC5C,gBAAM,OAAO,MAAM,SAAS,WAAM;AAClC,kBAAQ,MAAM,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,MAAM,OAAO,EAAE;AAAA,QAC3D;AACA,mBAAW,UAAU;AACrB,mBAAW,QAAQ;AACnB,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,cAAQ,IAAI,aAAa,QAAQ,MAAM,CAAC;AACxC,iBAAW,UAAU;AAAA,IACvB,SAAS,OAAO;AACd,iBAAW,UAAU;AACrB,iBAAW,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACxE,cAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AAChF,cAAQ,KAAK,CAAC;AAAA,IAChB,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;AACL;","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
|
-
|
|
52
|
-
|
|
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.
|
|
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
|
-
|
|
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-
|
|
290
|
+
//# sourceMappingURL=releases-GUMQZRFC.js.map
|