@extension.dev/deploy 0.2.5 → 1.0.0
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/CHANGELOG.md +13 -0
- package/README.md +35 -2
- package/dist/module.js +407 -369
- package/dist/module.mjs +409 -371
- package/dist/src/chrome.d.ts +6 -1
- package/dist/src/chrome.d.ts.map +1 -1
- package/dist/src/cli-commands.d.ts +5 -0
- package/dist/src/cli-commands.d.ts.map +1 -0
- package/dist/src/cli-parse.d.ts +3 -0
- package/dist/src/cli-parse.d.ts.map +1 -0
- package/dist/src/cli-usage.d.ts +2 -0
- package/dist/src/cli-usage.d.ts.map +1 -0
- package/dist/src/cli.d.ts +1 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/config.d.ts +3 -2
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/deploy.d.ts.map +1 -1
- package/dist/src/edge.d.ts.map +1 -1
- package/dist/src/firefox-api.d.ts +52 -0
- package/dist/src/firefox-api.d.ts.map +1 -0
- package/dist/src/firefox.d.ts +1 -32
- package/dist/src/firefox.d.ts.map +1 -1
- package/dist/src/init-chrome.d.ts +17 -0
- package/dist/src/init-chrome.d.ts.map +1 -0
- package/dist/src/init-edge.d.ts +8 -0
- package/dist/src/init-edge.d.ts.map +1 -0
- package/dist/src/init-firefox.d.ts +8 -0
- package/dist/src/init-firefox.d.ts.map +1 -0
- package/dist/src/init-io.d.ts.map +1 -1
- package/dist/src/init-prompt.d.ts +5 -0
- package/dist/src/init-prompt.d.ts.map +1 -0
- package/dist/src/init.d.ts +3 -26
- package/dist/src/init.d.ts.map +1 -1
- package/dist/src/types.d.ts +8 -8
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/fetch.d.ts +2 -2
- package/dist/src/utils/fetch.d.ts.map +1 -1
- package/dist/src/watch.d.ts +1 -0
- package/dist/src/watch.d.ts.map +1 -1
- package/package.json +18 -8
- package/dist/__tests__/cli.test.d.ts +0 -2
- package/dist/__tests__/cli.test.d.ts.map +0 -1
- package/dist/__tests__/config.test.d.ts +0 -2
- package/dist/__tests__/config.test.d.ts.map +0 -1
- package/dist/__tests__/deploy.test.d.ts +0 -2
- package/dist/__tests__/deploy.test.d.ts.map +0 -1
- package/dist/__tests__/dry-run.test.d.ts +0 -2
- package/dist/__tests__/dry-run.test.d.ts.map +0 -1
- package/dist/__tests__/init.test.d.ts +0 -2
- package/dist/__tests__/init.test.d.ts.map +0 -1
- package/dist/__tests__/types.test.d.ts +0 -2
- package/dist/__tests__/types.test.d.ts.map +0 -1
- package/dist/__tests__/utils.test.d.ts +0 -2
- package/dist/__tests__/utils.test.d.ts.map +0 -1
- package/dist/__tests__/verify.test.d.ts +0 -2
- package/dist/__tests__/verify.test.d.ts.map +0 -1
- package/dist/__tests__/watch.test.d.ts +0 -2
- package/dist/__tests__/watch.test.d.ts.map +0 -1
package/dist/module.js
CHANGED
|
@@ -62,10 +62,7 @@ const chromeOptionsSchema = external_zod_namespaceObject.z.object({
|
|
|
62
62
|
clientId: external_zod_namespaceObject.z.string().min(1).trim(),
|
|
63
63
|
clientSecret: external_zod_namespaceObject.z.string().min(1).trim(),
|
|
64
64
|
refreshToken: external_zod_namespaceObject.z.string().min(1).trim(),
|
|
65
|
-
|
|
66
|
-
"default",
|
|
67
|
-
"trustedTesters"
|
|
68
|
-
]).default("default"),
|
|
65
|
+
publisherId: external_zod_namespaceObject.z.string().min(1).trim(),
|
|
69
66
|
deployPercentage: external_zod_namespaceObject.z.number().int().min(1).max(100).optional(),
|
|
70
67
|
reviewExemption: external_zod_namespaceObject.z.boolean().default(false),
|
|
71
68
|
skipSubmitReview: external_zod_namespaceObject.z.boolean().default(false)
|
|
@@ -115,7 +112,7 @@ function resolveConfig(flags) {
|
|
|
115
112
|
clientId: flags.chromeClientId ?? envStr("CHROME_CLIENT_ID") ?? "",
|
|
116
113
|
clientSecret: flags.chromeClientSecret ?? envStr("CHROME_CLIENT_SECRET") ?? "",
|
|
117
114
|
refreshToken: flags.chromeRefreshToken ?? envStr("CHROME_REFRESH_TOKEN") ?? "",
|
|
118
|
-
|
|
115
|
+
publisherId: flags.chromePublisherId ?? envStr("CHROME_PUBLISHER_ID") ?? "",
|
|
119
116
|
deployPercentage: flags.chromeDeployPercentage ?? envInt("CHROME_DEPLOY_PERCENTAGE"),
|
|
120
117
|
reviewExemption: flags.chromeReviewExemption ?? envBool("CHROME_REVIEW_EXEMPTION"),
|
|
121
118
|
skipSubmitReview: flags.chromeSkipSubmitReview ?? envBool("CHROME_SKIP_SUBMIT_REVIEW")
|
|
@@ -145,6 +142,7 @@ const SETUP_HINTS = {
|
|
|
145
142
|
CHROME_CLIENT_ID: "Create OAuth 2.0 credentials at https://console.cloud.google.com/apis/credentials",
|
|
146
143
|
CHROME_CLIENT_SECRET: "Generated alongside the Client ID in the Google Cloud Console.",
|
|
147
144
|
CHROME_REFRESH_TOKEN: "Obtain by completing the OAuth consent flow. See the Chrome Web Store API docs.",
|
|
145
|
+
CHROME_PUBLISHER_ID: "The publisher UUID from your Chrome Web Store dev console URL (chrome.google.com/webstore/devconsole/<UUID>).",
|
|
148
146
|
CHROME_ZIP: "Path to the .zip file built for Chrome Web Store upload.",
|
|
149
147
|
FIREFOX_EXTENSION_ID: "The addon GUID ({uuid} format) or email-style ID from your addon's AMO page.",
|
|
150
148
|
FIREFOX_JWT_ISSUER: "API key from https://addons.mozilla.org/developers/addon/api/key/",
|
|
@@ -187,16 +185,24 @@ function envInt(name) {
|
|
|
187
185
|
}
|
|
188
186
|
const promises_namespaceObject = require("node:fs/promises");
|
|
189
187
|
var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
const DEFAULT_TIMEOUT_MS = 60000;
|
|
189
|
+
function withTimeout(init, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
190
|
+
if (null == init ? void 0 : init.signal) return init;
|
|
191
|
+
return {
|
|
192
|
+
...init || {},
|
|
193
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async function fetchJson(url, init, timeoutMs) {
|
|
197
|
+
const res = await fetch(url, withTimeout(init, timeoutMs));
|
|
192
198
|
if (!res.ok) {
|
|
193
199
|
const body = await res.text().catch(()=>"");
|
|
194
200
|
throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
|
|
195
201
|
}
|
|
196
202
|
return res.json();
|
|
197
203
|
}
|
|
198
|
-
async function fetchRaw(url, init) {
|
|
199
|
-
const res = await fetch(url, init);
|
|
204
|
+
async function fetchRaw(url, init, timeoutMs) {
|
|
205
|
+
const res = await fetch(url, withTimeout(init, timeoutMs));
|
|
200
206
|
if (!res.ok) {
|
|
201
207
|
const body = await res.text().catch(()=>"");
|
|
202
208
|
throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
|
|
@@ -249,10 +255,19 @@ function logDryStep(scope, step, details) {
|
|
|
249
255
|
for (const [key, value] of Object.entries(details))if (null != value) console.log(labelLine(key, value));
|
|
250
256
|
}
|
|
251
257
|
}
|
|
258
|
+
const UPLOAD_TIMEOUT_MS = 120000;
|
|
259
|
+
const PUBLISH_TIMEOUT_MS = 60000;
|
|
252
260
|
const CWS_OAUTH_URL = "https://oauth2.googleapis.com/token";
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
|
|
261
|
+
const CWS_V2_BASE = "https://chromewebstore.googleapis.com/v2";
|
|
262
|
+
const CWS_V2_UPLOAD_BASE = "https://chromewebstore.googleapis.com/upload/v2";
|
|
263
|
+
function v2ItemName(publisherId, extensionId) {
|
|
264
|
+
return `publishers/${encodeURIComponent(publisherId)}/items/${encodeURIComponent(extensionId)}`;
|
|
265
|
+
}
|
|
266
|
+
function requirePublisherId(publisherId, op) {
|
|
267
|
+
const id = String(publisherId || "").trim();
|
|
268
|
+
if (!id) throw new Error(`Chrome ${op} needs a publisher ID (the UUID from your Chrome Web Store dev console URL).`);
|
|
269
|
+
return id;
|
|
270
|
+
}
|
|
256
271
|
function chromeStoreUrl(extensionId) {
|
|
257
272
|
return `https://chromewebstore.google.com/detail/${extensionId}`;
|
|
258
273
|
}
|
|
@@ -271,16 +286,17 @@ async function authenticate(clientId, clientSecret, refreshToken) {
|
|
|
271
286
|
}
|
|
272
287
|
});
|
|
273
288
|
}
|
|
274
|
-
async function chrome_upload(extensionId, zipPath, token) {
|
|
289
|
+
async function chrome_upload(extensionId, publisherId, zipPath, token) {
|
|
275
290
|
const body = await promises_default().readFile(zipPath);
|
|
276
|
-
const
|
|
277
|
-
|
|
291
|
+
const url = `${CWS_V2_UPLOAD_BASE}/${v2ItemName(publisherId, extensionId)}:upload`;
|
|
292
|
+
const res = await fetch(url, {
|
|
293
|
+
method: "POST",
|
|
278
294
|
body,
|
|
279
295
|
headers: {
|
|
280
296
|
Authorization: `${token.token_type} ${token.access_token}`,
|
|
281
|
-
"x-goog-api-version": "2",
|
|
282
297
|
"Content-Type": "application/zip"
|
|
283
|
-
}
|
|
298
|
+
},
|
|
299
|
+
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
|
|
284
300
|
});
|
|
285
301
|
if (!res.ok) {
|
|
286
302
|
const text = await res.text().catch(()=>"");
|
|
@@ -288,17 +304,23 @@ async function chrome_upload(extensionId, zipPath, token) {
|
|
|
288
304
|
}
|
|
289
305
|
}
|
|
290
306
|
async function publish(params) {
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (null != params.
|
|
295
|
-
|
|
307
|
+
const reqBody = {
|
|
308
|
+
publishType: "DEFAULT_PUBLISH"
|
|
309
|
+
};
|
|
310
|
+
if (null != params.deployPercentage) reqBody.deployInfos = [
|
|
311
|
+
{
|
|
312
|
+
deployPercentage: params.deployPercentage
|
|
313
|
+
}
|
|
314
|
+
];
|
|
315
|
+
if (params.reviewExemption) reqBody.skipReview = true;
|
|
316
|
+
const res = await fetch(`${CWS_V2_BASE}/${v2ItemName(params.publisherId, params.extensionId)}:publish`, {
|
|
296
317
|
method: "POST",
|
|
297
318
|
headers: {
|
|
298
319
|
Authorization: `${params.token.token_type} ${params.token.access_token}`,
|
|
299
|
-
"
|
|
300
|
-
|
|
301
|
-
|
|
320
|
+
"Content-Type": "application/json"
|
|
321
|
+
},
|
|
322
|
+
body: JSON.stringify(reqBody),
|
|
323
|
+
signal: AbortSignal.timeout(PUBLISH_TIMEOUT_MS)
|
|
302
324
|
});
|
|
303
325
|
if (!res.ok) {
|
|
304
326
|
const text = await res.text().catch(()=>"");
|
|
@@ -308,7 +330,8 @@ async function publish(params) {
|
|
|
308
330
|
async function publishChrome(options, dryRun) {
|
|
309
331
|
await assertFilePresent(options.zip);
|
|
310
332
|
const zipSize = await getFileSize(options.zip);
|
|
311
|
-
|
|
333
|
+
const publisherId = requirePublisherId(options.publisherId, "submit");
|
|
334
|
+
log("chrome", "Authenticating with Chrome Web Store (API v2)");
|
|
312
335
|
const token = await authenticate(options.clientId, options.clientSecret, options.refreshToken);
|
|
313
336
|
const outcome = {
|
|
314
337
|
storeUrl: chromeStoreUrl(options.extensionId),
|
|
@@ -321,21 +344,17 @@ async function publishChrome(options, dryRun) {
|
|
|
321
344
|
});
|
|
322
345
|
logDryStep("chrome", "Would upload ZIP", {
|
|
323
346
|
file: options.zip,
|
|
324
|
-
size: formatBytes(zipSize)
|
|
325
|
-
target: `${CWS_UPLOAD_BASE}/${options.extensionId}`,
|
|
326
|
-
method: "PUT"
|
|
347
|
+
size: formatBytes(zipSize)
|
|
327
348
|
});
|
|
328
349
|
options.skipSubmitReview ? logDryStep("chrome", "Would skip publish (skipSubmitReview=true)") : logDryStep("chrome", "Would publish for review", {
|
|
329
|
-
target: `${CWS_PUBLISH_BASE}/${options.extensionId}/publish`,
|
|
330
|
-
publishTarget: options.publishTarget ?? "default",
|
|
331
350
|
deployPercentage: options.deployPercentage,
|
|
332
351
|
reviewExemption: options.reviewExemption
|
|
333
352
|
});
|
|
334
|
-
log("chrome", "Dry run complete
|
|
353
|
+
log("chrome", "Dry run complete - no changes made");
|
|
335
354
|
return outcome;
|
|
336
355
|
}
|
|
337
356
|
log("chrome", `Uploading ZIP file (${formatBytes(zipSize)})`);
|
|
338
|
-
await chrome_upload(options.extensionId, options.zip, token);
|
|
357
|
+
await chrome_upload(options.extensionId, publisherId, options.zip, token);
|
|
339
358
|
if (options.skipSubmitReview) {
|
|
340
359
|
log("chrome", "Skipping publish step (skipSubmitReview=true)");
|
|
341
360
|
return outcome;
|
|
@@ -343,7 +362,7 @@ async function publishChrome(options, dryRun) {
|
|
|
343
362
|
log("chrome", "Publishing extension");
|
|
344
363
|
await publish({
|
|
345
364
|
extensionId: options.extensionId,
|
|
346
|
-
|
|
365
|
+
publisherId,
|
|
347
366
|
token,
|
|
348
367
|
deployPercentage: options.deployPercentage,
|
|
349
368
|
reviewExemption: options.reviewExemption
|
|
@@ -351,14 +370,22 @@ async function publishChrome(options, dryRun) {
|
|
|
351
370
|
return outcome;
|
|
352
371
|
}
|
|
353
372
|
async function getChromeItem(params) {
|
|
373
|
+
var _rev_distributionChannels_, _rev_distributionChannels;
|
|
354
374
|
const token = await authenticate(params.clientId, params.clientSecret, params.refreshToken);
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
375
|
+
const auth = {
|
|
376
|
+
Authorization: `${token.token_type} ${token.access_token}`
|
|
377
|
+
};
|
|
378
|
+
const pub = requirePublisherId(params.publisherId, "status");
|
|
379
|
+
const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
|
|
380
|
+
headers: auth
|
|
361
381
|
});
|
|
382
|
+
const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
|
|
383
|
+
return {
|
|
384
|
+
uploadState: status.lastAsyncUploadState,
|
|
385
|
+
crxVersion: null == rev ? void 0 : null == (_rev_distributionChannels = rev.distributionChannels) ? void 0 : null == (_rev_distributionChannels_ = _rev_distributionChannels[0]) ? void 0 : _rev_distributionChannels_.crxVersion,
|
|
386
|
+
publicKey: status.publicKey,
|
|
387
|
+
state: null == rev ? void 0 : rev.state
|
|
388
|
+
};
|
|
362
389
|
}
|
|
363
390
|
async function verifyChromeCredentials(params) {
|
|
364
391
|
let token;
|
|
@@ -370,17 +397,18 @@ async function verifyChromeCredentials(params) {
|
|
|
370
397
|
message: `OAuth token exchange failed. Verify your Client ID, Client Secret, and Refresh Token are correct.\n${err instanceof Error ? err.message : err}`
|
|
371
398
|
};
|
|
372
399
|
}
|
|
400
|
+
const auth = {
|
|
401
|
+
Authorization: `${token.token_type} ${token.access_token}`
|
|
402
|
+
};
|
|
373
403
|
try {
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
404
|
+
const pub = requirePublisherId(params.publisherId, "verify");
|
|
405
|
+
const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
|
|
406
|
+
headers: auth
|
|
378
407
|
});
|
|
379
|
-
const
|
|
408
|
+
const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
|
|
380
409
|
return {
|
|
381
410
|
ok: true,
|
|
382
|
-
message: `Credentials verified.
|
|
383
|
-
extensionTitle: title
|
|
411
|
+
message: `Credentials verified (Chrome Web Store API v2). Item state: ${(null == rev ? void 0 : rev.state) || "unknown"}.`
|
|
384
412
|
};
|
|
385
413
|
} catch (err) {
|
|
386
414
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -390,7 +418,7 @@ async function verifyChromeCredentials(params) {
|
|
|
390
418
|
};
|
|
391
419
|
if (msg.includes("403")) return {
|
|
392
420
|
ok: false,
|
|
393
|
-
message:
|
|
421
|
+
message: "Access denied. Check the publisher ID matches this extension's account, and that the extension ID is correct."
|
|
394
422
|
};
|
|
395
423
|
return {
|
|
396
424
|
ok: false,
|
|
@@ -398,20 +426,14 @@ async function verifyChromeCredentials(params) {
|
|
|
398
426
|
};
|
|
399
427
|
}
|
|
400
428
|
}
|
|
401
|
-
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
402
|
-
var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
|
|
403
429
|
function delay(ms) {
|
|
404
430
|
return new Promise((resolve)=>{
|
|
405
431
|
setTimeout(resolve, ms);
|
|
406
432
|
});
|
|
407
433
|
}
|
|
434
|
+
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
435
|
+
var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
|
|
408
436
|
const AMO_BASE = "https://addons.mozilla.org/api/v5/addons";
|
|
409
|
-
const AMO_PROFILE_URL = "https://addons.mozilla.org/api/v5/accounts/profile/";
|
|
410
|
-
const POLL_INTERVAL_MS = 5000;
|
|
411
|
-
const VALIDATION_TIMEOUT_MS = 600000;
|
|
412
|
-
function firefoxStoreUrl(addonIdOrSlug) {
|
|
413
|
-
return `https://addons.mozilla.org/firefox/addon/${addonIdOrSlug}`;
|
|
414
|
-
}
|
|
415
437
|
function signJwt(issuer, secret, expiresInSeconds = 30) {
|
|
416
438
|
const header = {
|
|
417
439
|
alg: "HS256",
|
|
@@ -496,6 +518,12 @@ async function createAddon(params) {
|
|
|
496
518
|
headers: authHeaders(params.jwtIssuer, params.jwtSecret)
|
|
497
519
|
});
|
|
498
520
|
}
|
|
521
|
+
const AMO_PROFILE_URL = "https://addons.mozilla.org/api/v5/accounts/profile/";
|
|
522
|
+
const POLL_INTERVAL_MS = 5000;
|
|
523
|
+
const VALIDATION_TIMEOUT_MS = 600000;
|
|
524
|
+
function firefoxStoreUrl(addonIdOrSlug) {
|
|
525
|
+
return `https://addons.mozilla.org/firefox/addon/${addonIdOrSlug}`;
|
|
526
|
+
}
|
|
499
527
|
async function publishFirefox(options, dryRun) {
|
|
500
528
|
await assertFilePresent(options.zip);
|
|
501
529
|
const zipSize = await getFileSize(options.zip);
|
|
@@ -861,8 +889,134 @@ async function deploy(config) {
|
|
|
861
889
|
success
|
|
862
890
|
};
|
|
863
891
|
}
|
|
892
|
+
function usage() {
|
|
893
|
+
return `
|
|
894
|
+
extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
|
|
895
|
+
|
|
896
|
+
USAGE
|
|
897
|
+
extension-deploy [options] submit to one or more stores
|
|
898
|
+
extension-deploy init [options] interactive credential setup wizard
|
|
899
|
+
extension-deploy watch --chrome [options] poll an in-flight submission
|
|
900
|
+
extension-deploy watch --firefox [options]
|
|
901
|
+
extension-deploy watch --edge [options]
|
|
902
|
+
|
|
903
|
+
GLOBAL
|
|
904
|
+
--dry-run Verify auth without uploading or publishing
|
|
905
|
+
--output-json <path> Write the DeployResult / WatchEvent as JSON to <path>
|
|
906
|
+
--help Show this help message
|
|
907
|
+
|
|
908
|
+
CHROME
|
|
909
|
+
--chrome-zip <path> Path to Chrome extension ZIP
|
|
910
|
+
--chrome-extension-id <id> Chrome extension ID
|
|
911
|
+
--chrome-publisher-id <uuid> Publisher UUID from the dev console (required)
|
|
912
|
+
--chrome-client-id <id> OAuth2 client ID
|
|
913
|
+
--chrome-client-secret <secret> OAuth2 client secret
|
|
914
|
+
--chrome-refresh-token <token> OAuth2 refresh token
|
|
915
|
+
--chrome-deploy-percentage <n> Staged rollout percentage (1-100)
|
|
916
|
+
--chrome-review-exemption Request expedited review
|
|
917
|
+
--chrome-skip-submit-review Upload only, skip publish
|
|
918
|
+
|
|
919
|
+
FIREFOX
|
|
920
|
+
--firefox-zip <path> Path to Firefox extension ZIP
|
|
921
|
+
--firefox-sources-zip <path> Path to sources ZIP (optional)
|
|
922
|
+
--firefox-extension-id <id> Addon GUID or email-style ID
|
|
923
|
+
--firefox-jwt-issuer <issuer> AMO JWT issuer
|
|
924
|
+
--firefox-jwt-secret <secret> AMO JWT secret
|
|
925
|
+
--firefox-channel <channel> "listed" or "unlisted"
|
|
926
|
+
|
|
927
|
+
EDGE
|
|
928
|
+
--edge-zip <path> Path to Edge extension ZIP
|
|
929
|
+
--edge-product-id <id> Partner Center product ID
|
|
930
|
+
--edge-client-id <id> Partner Center client ID
|
|
931
|
+
--edge-api-key <key> Partner Center API key (v1.1)
|
|
932
|
+
--edge-skip-submit-review Upload only, skip publish
|
|
933
|
+
|
|
934
|
+
WATCH
|
|
935
|
+
--watch-submission-id <id> Required for firefox/edge (version id / operation id)
|
|
936
|
+
--watch-interval <seconds> Poll interval (default 60)
|
|
937
|
+
--watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
|
|
938
|
+
|
|
939
|
+
INIT
|
|
940
|
+
--init-stores <list> Comma-separated store list (skip picker)
|
|
941
|
+
--init-output <path> Destination file (default .env.submit)
|
|
942
|
+
--init-skip-verify Don't run verifyCredentials after collection
|
|
943
|
+
--init-force Overwrite existing .env.submit without asking
|
|
944
|
+
|
|
945
|
+
ENVIRONMENT VARIABLES
|
|
946
|
+
All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
|
|
947
|
+
FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
|
|
948
|
+
A .env.submit file in the current directory is loaded automatically.
|
|
949
|
+
`.trim();
|
|
950
|
+
}
|
|
951
|
+
const BOOLEAN_FLAGS = new Set([
|
|
952
|
+
"help",
|
|
953
|
+
"dryRun",
|
|
954
|
+
"chromeReviewExemption",
|
|
955
|
+
"chromeSkipSubmitReview",
|
|
956
|
+
"edgeSkipSubmitReview",
|
|
957
|
+
"initSkipVerify",
|
|
958
|
+
"initForce"
|
|
959
|
+
]);
|
|
960
|
+
const INT_FLAGS = new Set([
|
|
961
|
+
"chromeDeployPercentage",
|
|
962
|
+
"watchInterval",
|
|
963
|
+
"watchTimeout"
|
|
964
|
+
]);
|
|
965
|
+
function parseArgs(argv) {
|
|
966
|
+
const flags = {};
|
|
967
|
+
let i = 0;
|
|
968
|
+
if ("watch" === argv[0]) {
|
|
969
|
+
flags.command = "watch";
|
|
970
|
+
i = 1;
|
|
971
|
+
} else if ("init" === argv[0]) {
|
|
972
|
+
flags.command = "init";
|
|
973
|
+
i = 1;
|
|
974
|
+
}
|
|
975
|
+
while(i < argv.length){
|
|
976
|
+
const arg = argv[i];
|
|
977
|
+
if ("--help" === arg || "-h" === arg) {
|
|
978
|
+
flags.help = true;
|
|
979
|
+
i++;
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if ("--chrome" === arg) {
|
|
983
|
+
flags.watchStore = "chrome";
|
|
984
|
+
i++;
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if ("--firefox" === arg) {
|
|
988
|
+
flags.watchStore = "firefox";
|
|
989
|
+
i++;
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if ("--edge" === arg) {
|
|
993
|
+
flags.watchStore = "edge";
|
|
994
|
+
i++;
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
if (!(null == arg ? void 0 : arg.startsWith("--"))) {
|
|
998
|
+
i++;
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
const key = arg.slice(2).replace(/-([a-z])/g, (_, c)=>c.toUpperCase());
|
|
1002
|
+
if (BOOLEAN_FLAGS.has(key)) {
|
|
1003
|
+
flags[key] = true;
|
|
1004
|
+
i++;
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
const next = argv[i + 1];
|
|
1008
|
+
if (null == next || next.startsWith("--")) {
|
|
1009
|
+
flags[key] = true;
|
|
1010
|
+
i++;
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
flags[key] = INT_FLAGS.has(key) ? parseInt(next, 10) : next;
|
|
1014
|
+
i += 2;
|
|
1015
|
+
}
|
|
1016
|
+
return flags;
|
|
1017
|
+
}
|
|
864
1018
|
const DEFAULT_POLL_INTERVAL_MS = 60000;
|
|
865
|
-
const
|
|
1019
|
+
const watch_DEFAULT_TIMEOUT_MS = 3600000;
|
|
866
1020
|
function watch_now() {
|
|
867
1021
|
return new Date().toISOString();
|
|
868
1022
|
}
|
|
@@ -989,7 +1143,7 @@ function watchFirefoxSubmission(input, options = {}) {
|
|
|
989
1143
|
}
|
|
990
1144
|
async function runWatchLoop(_store, probe, options) {
|
|
991
1145
|
const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
992
|
-
const timeout = options.timeoutMs ??
|
|
1146
|
+
const timeout = options.timeoutMs ?? watch_DEFAULT_TIMEOUT_MS;
|
|
993
1147
|
const deadline = Date.now() + timeout;
|
|
994
1148
|
let lastEvent;
|
|
995
1149
|
while(true){
|
|
@@ -1001,38 +1155,165 @@ async function runWatchLoop(_store, probe, options) {
|
|
|
1001
1155
|
await delay(pollInterval);
|
|
1002
1156
|
}
|
|
1003
1157
|
}
|
|
1004
|
-
|
|
1005
|
-
{
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
value: "firefox",
|
|
1011
|
-
label: "Firefox AMO (addons.mozilla.org)"
|
|
1012
|
-
},
|
|
1013
|
-
{
|
|
1014
|
-
value: "edge",
|
|
1015
|
-
label: "Edge Add-ons (Partner Center)"
|
|
1158
|
+
async function requireValue(io, label, hint, opts) {
|
|
1159
|
+
const question = hint ? `${label} ${hint}:` : `${label}:`;
|
|
1160
|
+
for(let attempt = 0; attempt < 3; attempt++){
|
|
1161
|
+
const answer = (await io.prompt(question, opts)).trim();
|
|
1162
|
+
if (answer) return answer;
|
|
1163
|
+
io.log(` ${label} is required.`);
|
|
1016
1164
|
}
|
|
1017
|
-
|
|
1165
|
+
throw new Error(`init: ${label} is required.`);
|
|
1166
|
+
}
|
|
1018
1167
|
const CHROME_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
1019
1168
|
const CHROME_OAUTH_AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
|
|
1020
1169
|
const CHROME_OOB_REDIRECT = "urn:ietf:wg:oauth:2.0:oob";
|
|
1021
1170
|
const CHROME_SCOPE = "https://www.googleapis.com/auth/chromewebstore";
|
|
1022
|
-
async function
|
|
1171
|
+
async function collectChromeCredentials(deps) {
|
|
1023
1172
|
const { io } = deps;
|
|
1173
|
+
io.log("Chrome Web Store needs OAuth credentials. You'll need:");
|
|
1174
|
+
io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
|
|
1175
|
+
io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
|
|
1176
|
+
io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
|
|
1177
|
+
io.log(" https://console.cloud.google.com/apis/credentials");
|
|
1178
|
+
io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
|
|
1179
|
+
io.log(" 4. Your publisher ID (the UUID in your dev console URL: chrome.google.com/webstore/devconsole/<UUID>).");
|
|
1024
1180
|
io.log("");
|
|
1025
|
-
io.log(
|
|
1181
|
+
io.log("This wizard will take your Client ID + Secret and walk you through");
|
|
1182
|
+
io.log("generating a refresh token automatically.");
|
|
1026
1183
|
io.log("");
|
|
1027
|
-
io
|
|
1028
|
-
|
|
1184
|
+
const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
|
|
1185
|
+
const publisherId = await requireValue(io, "Chrome publisher ID", "(UUID from your dev console URL)");
|
|
1186
|
+
const clientId = await requireValue(io, "OAuth Client ID");
|
|
1187
|
+
const clientSecret = await requireValue(io, "OAuth Client Secret", void 0, {
|
|
1188
|
+
secret: true
|
|
1189
|
+
});
|
|
1190
|
+
const refreshToken = await obtainChromeRefreshToken(deps, clientId, clientSecret);
|
|
1191
|
+
return {
|
|
1192
|
+
extensionId,
|
|
1193
|
+
publisherId,
|
|
1194
|
+
clientId,
|
|
1195
|
+
clientSecret,
|
|
1196
|
+
refreshToken
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
function buildChromeAuthUrl(clientId) {
|
|
1200
|
+
const params = new URLSearchParams({
|
|
1201
|
+
response_type: "code",
|
|
1202
|
+
client_id: clientId,
|
|
1203
|
+
redirect_uri: CHROME_OOB_REDIRECT,
|
|
1204
|
+
scope: CHROME_SCOPE,
|
|
1205
|
+
access_type: "offline",
|
|
1206
|
+
prompt: "consent"
|
|
1207
|
+
});
|
|
1208
|
+
return `${CHROME_OAUTH_AUTH_URL}?${params.toString()}`;
|
|
1209
|
+
}
|
|
1210
|
+
async function exchangeChromeAuthCode(input) {
|
|
1211
|
+
const fetchFn = input.fetchImpl ?? fetch;
|
|
1212
|
+
const body = new URLSearchParams({
|
|
1213
|
+
code: input.authCode,
|
|
1214
|
+
client_id: input.clientId,
|
|
1215
|
+
client_secret: input.clientSecret,
|
|
1216
|
+
redirect_uri: CHROME_OOB_REDIRECT,
|
|
1217
|
+
grant_type: "authorization_code"
|
|
1218
|
+
});
|
|
1219
|
+
const res = await fetchFn(CHROME_OAUTH_TOKEN_URL, {
|
|
1220
|
+
method: "POST",
|
|
1221
|
+
headers: {
|
|
1222
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1223
|
+
},
|
|
1224
|
+
body: body.toString()
|
|
1225
|
+
});
|
|
1226
|
+
if (!res.ok) {
|
|
1227
|
+
const text = await res.text().catch(()=>"");
|
|
1228
|
+
throw new Error(`Chrome token exchange failed (HTTP ${res.status}): ${text || res.statusText}`);
|
|
1229
|
+
}
|
|
1230
|
+
const json = await res.json();
|
|
1231
|
+
if (!json.refresh_token) throw new Error(`Chrome token exchange returned no refresh_token${json.error ? `: ${json.error} \u{2014} ${json.error_description ?? ""}` : ""}. This usually means the authorization code was already used or expired; try the init wizard again.`);
|
|
1232
|
+
return json.refresh_token;
|
|
1233
|
+
}
|
|
1234
|
+
async function obtainChromeRefreshToken(deps, clientId, clientSecret) {
|
|
1235
|
+
const { io } = deps;
|
|
1236
|
+
const authUrl = buildChromeAuthUrl(clientId);
|
|
1029
1237
|
io.log("");
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1238
|
+
io.log("Open the URL below in your browser, approve the scope, and paste the");
|
|
1239
|
+
io.log("authorization code shown on the page:");
|
|
1240
|
+
io.log("");
|
|
1241
|
+
io.log(` ${authUrl}`);
|
|
1242
|
+
io.log("");
|
|
1243
|
+
const authCode = await requireValue(io, "Authorization code", void 0, {
|
|
1244
|
+
secret: true
|
|
1245
|
+
});
|
|
1246
|
+
return exchangeChromeAuthCode({
|
|
1247
|
+
clientId,
|
|
1248
|
+
clientSecret,
|
|
1249
|
+
authCode,
|
|
1250
|
+
fetchImpl: deps.fetchImpl
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
async function collectFirefoxCredentials(deps) {
|
|
1254
|
+
const { io } = deps;
|
|
1255
|
+
io.log("Firefox AMO needs JWT API credentials:");
|
|
1256
|
+
io.log(" 1. Sign in at https://addons.mozilla.org");
|
|
1257
|
+
io.log(" 2. Create API credentials at https://addons.mozilla.org/developers/addon/api/key/");
|
|
1258
|
+
io.log(" 3. Copy the JWT issuer and JWT secret (the secret is only shown once).");
|
|
1259
|
+
io.log("");
|
|
1260
|
+
const addonId = await requireValue(io, "Firefox addon ID", "({uuid} GUID or email-style ID from your addon page)");
|
|
1261
|
+
const jwtIssuer = await requireValue(io, "JWT issuer");
|
|
1262
|
+
const jwtSecret = await requireValue(io, "JWT secret", void 0, {
|
|
1263
|
+
secret: true
|
|
1264
|
+
});
|
|
1265
|
+
return {
|
|
1266
|
+
addonId,
|
|
1267
|
+
jwtIssuer,
|
|
1268
|
+
jwtSecret
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
async function collectEdgeCredentials(deps) {
|
|
1272
|
+
const { io } = deps;
|
|
1273
|
+
io.log("Edge Partner Center needs the v1.1 API credentials:");
|
|
1274
|
+
io.log(" 1. Open the Partner Center dashboard: https://partner.microsoft.com/dashboard/microsoftedge/overview");
|
|
1275
|
+
io.log(" 2. Under your extension, go to Settings \u2192 API access \u2192 API v1.1 and generate credentials.");
|
|
1276
|
+
io.log(" 3. Copy the Client ID, API key, and Product ID.");
|
|
1277
|
+
io.log("");
|
|
1278
|
+
const productId = await requireValue(io, "Edge product ID");
|
|
1279
|
+
const clientId = await requireValue(io, "Edge client ID");
|
|
1280
|
+
const apiKey = await requireValue(io, "Edge API key", void 0, {
|
|
1281
|
+
secret: true
|
|
1282
|
+
});
|
|
1283
|
+
return {
|
|
1284
|
+
productId,
|
|
1285
|
+
clientId,
|
|
1286
|
+
apiKey
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
const STORE_CHOICES = [
|
|
1290
|
+
{
|
|
1291
|
+
value: "chrome",
|
|
1292
|
+
label: "Chrome Web Store"
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
value: "firefox",
|
|
1296
|
+
label: "Firefox AMO (addons.mozilla.org)"
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
value: "edge",
|
|
1300
|
+
label: "Edge Add-ons (Partner Center)"
|
|
1301
|
+
}
|
|
1302
|
+
];
|
|
1303
|
+
async function runInit(deps, options = {}) {
|
|
1304
|
+
const { io } = deps;
|
|
1305
|
+
io.log("");
|
|
1306
|
+
io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} ${colors.underline("extension-deploy \u2014 set up store credentials")}`);
|
|
1307
|
+
io.log("");
|
|
1308
|
+
io.log("This wizard walks you through creating credentials for each store and");
|
|
1309
|
+
io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
|
|
1310
|
+
io.log("");
|
|
1311
|
+
const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
|
|
1312
|
+
if (0 === stores.length) throw new Error("init: at least one store must be selected. Use --init-stores chrome,firefox,edge or make a selection.");
|
|
1313
|
+
const envLines = [
|
|
1314
|
+
"# Generated by `extension-deploy init`.",
|
|
1315
|
+
"# Do not commit secrets to source control.",
|
|
1316
|
+
""
|
|
1036
1317
|
];
|
|
1037
1318
|
const verified = {};
|
|
1038
1319
|
for (const store of stores){
|
|
@@ -1041,7 +1322,7 @@ async function runInit(deps, options = {}) {
|
|
|
1041
1322
|
io.log("");
|
|
1042
1323
|
if ("chrome" === store) {
|
|
1043
1324
|
const creds = await collectChromeCredentials(deps);
|
|
1044
|
-
envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
|
|
1325
|
+
envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_PUBLISHER_ID=${creds.publisherId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
|
|
1045
1326
|
if (!options.skipVerify) {
|
|
1046
1327
|
const verify = deps.verifyChrome ?? verifyChromeCredentials;
|
|
1047
1328
|
verified.chrome = normalizeVerify(await verify(creds));
|
|
@@ -1109,121 +1390,6 @@ async function runInit(deps, options = {}) {
|
|
|
1109
1390
|
wrote
|
|
1110
1391
|
};
|
|
1111
1392
|
}
|
|
1112
|
-
async function collectChromeCredentials(deps) {
|
|
1113
|
-
const { io } = deps;
|
|
1114
|
-
io.log("Chrome Web Store needs OAuth credentials. You'll need:");
|
|
1115
|
-
io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
|
|
1116
|
-
io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
|
|
1117
|
-
io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
|
|
1118
|
-
io.log(" https://console.cloud.google.com/apis/credentials");
|
|
1119
|
-
io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
|
|
1120
|
-
io.log("");
|
|
1121
|
-
io.log("This wizard will take your Client ID + Secret and walk you through");
|
|
1122
|
-
io.log("generating a refresh token automatically.");
|
|
1123
|
-
io.log("");
|
|
1124
|
-
const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
|
|
1125
|
-
const clientId = await requireValue(io, "OAuth Client ID");
|
|
1126
|
-
const clientSecret = await requireValue(io, "OAuth Client Secret", void 0, {
|
|
1127
|
-
secret: true
|
|
1128
|
-
});
|
|
1129
|
-
const refreshToken = await obtainChromeRefreshToken(deps, clientId, clientSecret);
|
|
1130
|
-
return {
|
|
1131
|
-
extensionId,
|
|
1132
|
-
clientId,
|
|
1133
|
-
clientSecret,
|
|
1134
|
-
refreshToken
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
|
-
function buildChromeAuthUrl(clientId) {
|
|
1138
|
-
const params = new URLSearchParams({
|
|
1139
|
-
response_type: "code",
|
|
1140
|
-
client_id: clientId,
|
|
1141
|
-
redirect_uri: CHROME_OOB_REDIRECT,
|
|
1142
|
-
scope: CHROME_SCOPE,
|
|
1143
|
-
access_type: "offline",
|
|
1144
|
-
prompt: "consent"
|
|
1145
|
-
});
|
|
1146
|
-
return `${CHROME_OAUTH_AUTH_URL}?${params.toString()}`;
|
|
1147
|
-
}
|
|
1148
|
-
async function exchangeChromeAuthCode(input) {
|
|
1149
|
-
const fetchFn = input.fetchImpl ?? fetch;
|
|
1150
|
-
const body = new URLSearchParams({
|
|
1151
|
-
code: input.authCode,
|
|
1152
|
-
client_id: input.clientId,
|
|
1153
|
-
client_secret: input.clientSecret,
|
|
1154
|
-
redirect_uri: CHROME_OOB_REDIRECT,
|
|
1155
|
-
grant_type: "authorization_code"
|
|
1156
|
-
});
|
|
1157
|
-
const res = await fetchFn(CHROME_OAUTH_TOKEN_URL, {
|
|
1158
|
-
method: "POST",
|
|
1159
|
-
headers: {
|
|
1160
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
1161
|
-
},
|
|
1162
|
-
body: body.toString()
|
|
1163
|
-
});
|
|
1164
|
-
if (!res.ok) {
|
|
1165
|
-
const text = await res.text().catch(()=>"");
|
|
1166
|
-
throw new Error(`Chrome token exchange failed (HTTP ${res.status}): ${text || res.statusText}`);
|
|
1167
|
-
}
|
|
1168
|
-
const json = await res.json();
|
|
1169
|
-
if (!json.refresh_token) throw new Error(`Chrome token exchange returned no refresh_token${json.error ? `: ${json.error} \u{2014} ${json.error_description ?? ""}` : ""}. This usually means the authorization code was already used or expired; try the init wizard again.`);
|
|
1170
|
-
return json.refresh_token;
|
|
1171
|
-
}
|
|
1172
|
-
async function obtainChromeRefreshToken(deps, clientId, clientSecret) {
|
|
1173
|
-
const { io } = deps;
|
|
1174
|
-
const authUrl = buildChromeAuthUrl(clientId);
|
|
1175
|
-
io.log("");
|
|
1176
|
-
io.log("Open the URL below in your browser, approve the scope, and paste the");
|
|
1177
|
-
io.log("authorization code shown on the page:");
|
|
1178
|
-
io.log("");
|
|
1179
|
-
io.log(` ${authUrl}`);
|
|
1180
|
-
io.log("");
|
|
1181
|
-
const authCode = await requireValue(io, "Authorization code", void 0, {
|
|
1182
|
-
secret: true
|
|
1183
|
-
});
|
|
1184
|
-
return exchangeChromeAuthCode({
|
|
1185
|
-
clientId,
|
|
1186
|
-
clientSecret,
|
|
1187
|
-
authCode,
|
|
1188
|
-
fetchImpl: deps.fetchImpl
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
async function collectFirefoxCredentials(deps) {
|
|
1192
|
-
const { io } = deps;
|
|
1193
|
-
io.log("Firefox AMO needs JWT API credentials:");
|
|
1194
|
-
io.log(" 1. Sign in at https://addons.mozilla.org");
|
|
1195
|
-
io.log(" 2. Create API credentials at https://addons.mozilla.org/developers/addon/api/key/");
|
|
1196
|
-
io.log(" 3. Copy the JWT issuer and JWT secret (the secret is only shown once).");
|
|
1197
|
-
io.log("");
|
|
1198
|
-
const addonId = await requireValue(io, "Firefox addon ID", "({uuid} GUID or email-style ID from your addon page)");
|
|
1199
|
-
const jwtIssuer = await requireValue(io, "JWT issuer");
|
|
1200
|
-
const jwtSecret = await requireValue(io, "JWT secret", void 0, {
|
|
1201
|
-
secret: true
|
|
1202
|
-
});
|
|
1203
|
-
return {
|
|
1204
|
-
addonId,
|
|
1205
|
-
jwtIssuer,
|
|
1206
|
-
jwtSecret
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
async function collectEdgeCredentials(deps) {
|
|
1210
|
-
const { io } = deps;
|
|
1211
|
-
io.log("Edge Partner Center needs the v1.1 API credentials:");
|
|
1212
|
-
io.log(" 1. Open the Partner Center dashboard: https://partner.microsoft.com/dashboard/microsoftedge/overview");
|
|
1213
|
-
io.log(" 2. Under your extension, go to Settings \u2192 API access \u2192 API v1.1 and generate credentials.");
|
|
1214
|
-
io.log(" 3. Copy the Client ID, API key, and Product ID.");
|
|
1215
|
-
io.log("");
|
|
1216
|
-
const productId = await requireValue(io, "Edge product ID");
|
|
1217
|
-
const clientId = await requireValue(io, "Edge client ID");
|
|
1218
|
-
const apiKey = await requireValue(io, "Edge API key", void 0, {
|
|
1219
|
-
secret: true
|
|
1220
|
-
});
|
|
1221
|
-
return {
|
|
1222
|
-
productId,
|
|
1223
|
-
clientId,
|
|
1224
|
-
apiKey
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
1393
|
function labelFor(store) {
|
|
1228
1394
|
var _STORE_CHOICES_find;
|
|
1229
1395
|
return (null == (_STORE_CHOICES_find = STORE_CHOICES.find((c)=>c.value === store)) ? void 0 : _STORE_CHOICES_find.label) ?? String(store);
|
|
@@ -1242,15 +1408,6 @@ function resolveEnvPath(cwd, outputPath) {
|
|
|
1242
1408
|
const target = outputPath ?? ".env.submit";
|
|
1243
1409
|
return external_node_path_default().isAbsolute(target) ? target : external_node_path_default().join(cwd, target);
|
|
1244
1410
|
}
|
|
1245
|
-
async function requireValue(io, label, hint, opts) {
|
|
1246
|
-
const question = hint ? `${label} ${hint}:` : `${label}:`;
|
|
1247
|
-
for(let attempt = 0; attempt < 3; attempt++){
|
|
1248
|
-
const answer = (await io.prompt(question, opts)).trim();
|
|
1249
|
-
if (answer) return answer;
|
|
1250
|
-
io.log(` ${label} is required.`);
|
|
1251
|
-
}
|
|
1252
|
-
throw new Error(`init: ${label} is required.`);
|
|
1253
|
-
}
|
|
1254
1411
|
const external_node_readline_namespaceObject = require("node:readline");
|
|
1255
1412
|
var external_node_readline_default = /*#__PURE__*/ __webpack_require__.n(external_node_readline_namespaceObject);
|
|
1256
1413
|
function createStdinIO() {
|
|
@@ -1306,7 +1463,13 @@ function createStdinIO() {
|
|
|
1306
1463
|
}
|
|
1307
1464
|
const stdinFs = {
|
|
1308
1465
|
async writeFile (filePath, content) {
|
|
1309
|
-
await promises_default().writeFile(filePath, content,
|
|
1466
|
+
await promises_default().writeFile(filePath, content, {
|
|
1467
|
+
encoding: "utf-8",
|
|
1468
|
+
mode: 384
|
|
1469
|
+
});
|
|
1470
|
+
try {
|
|
1471
|
+
await promises_default().chmod(filePath, 384);
|
|
1472
|
+
} catch {}
|
|
1310
1473
|
},
|
|
1311
1474
|
async fileExists (filePath) {
|
|
1312
1475
|
try {
|
|
@@ -1317,173 +1480,12 @@ const stdinFs = {
|
|
|
1317
1480
|
}
|
|
1318
1481
|
}
|
|
1319
1482
|
};
|
|
1320
|
-
function usage() {
|
|
1321
|
-
return `
|
|
1322
|
-
extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
|
|
1323
|
-
|
|
1324
|
-
USAGE
|
|
1325
|
-
extension-deploy [options] submit to one or more stores
|
|
1326
|
-
extension-deploy init [options] interactive credential setup wizard
|
|
1327
|
-
extension-deploy watch --chrome [options] poll an in-flight submission
|
|
1328
|
-
extension-deploy watch --firefox [options]
|
|
1329
|
-
extension-deploy watch --edge [options]
|
|
1330
|
-
|
|
1331
|
-
GLOBAL
|
|
1332
|
-
--dry-run Verify auth without uploading or publishing
|
|
1333
|
-
--output-json <path> Write the DeployResult / WatchEvent as JSON to <path>
|
|
1334
|
-
--help Show this help message
|
|
1335
|
-
|
|
1336
|
-
CHROME
|
|
1337
|
-
--chrome-zip <path> Path to Chrome extension ZIP
|
|
1338
|
-
--chrome-extension-id <id> Chrome extension ID
|
|
1339
|
-
--chrome-client-id <id> OAuth2 client ID
|
|
1340
|
-
--chrome-client-secret <secret> OAuth2 client secret
|
|
1341
|
-
--chrome-refresh-token <token> OAuth2 refresh token
|
|
1342
|
-
--chrome-publish-target <target> "default" or "trustedTesters"
|
|
1343
|
-
--chrome-deploy-percentage <n> Staged rollout percentage (1-100)
|
|
1344
|
-
--chrome-review-exemption Request expedited review
|
|
1345
|
-
--chrome-skip-submit-review Upload only, skip publish
|
|
1346
|
-
|
|
1347
|
-
FIREFOX
|
|
1348
|
-
--firefox-zip <path> Path to Firefox extension ZIP
|
|
1349
|
-
--firefox-sources-zip <path> Path to sources ZIP (optional)
|
|
1350
|
-
--firefox-extension-id <id> Addon GUID or email-style ID
|
|
1351
|
-
--firefox-jwt-issuer <issuer> AMO JWT issuer
|
|
1352
|
-
--firefox-jwt-secret <secret> AMO JWT secret
|
|
1353
|
-
--firefox-channel <channel> "listed" or "unlisted"
|
|
1354
|
-
|
|
1355
|
-
EDGE
|
|
1356
|
-
--edge-zip <path> Path to Edge extension ZIP
|
|
1357
|
-
--edge-product-id <id> Partner Center product ID
|
|
1358
|
-
--edge-client-id <id> Partner Center client ID
|
|
1359
|
-
--edge-api-key <key> Partner Center API key (v1.1)
|
|
1360
|
-
--edge-skip-submit-review Upload only, skip publish
|
|
1361
|
-
|
|
1362
|
-
WATCH
|
|
1363
|
-
--watch-submission-id <id> Required for firefox/edge (version id / operation id)
|
|
1364
|
-
--watch-interval <seconds> Poll interval (default 60)
|
|
1365
|
-
--watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
|
|
1366
|
-
|
|
1367
|
-
INIT
|
|
1368
|
-
--init-stores <list> Comma-separated store list (skip picker)
|
|
1369
|
-
--init-output <path> Destination file (default .env.submit)
|
|
1370
|
-
--init-skip-verify Don't run verifyCredentials after collection
|
|
1371
|
-
--init-force Overwrite existing .env.submit without asking
|
|
1372
|
-
|
|
1373
|
-
ENVIRONMENT VARIABLES
|
|
1374
|
-
All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
|
|
1375
|
-
FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
|
|
1376
|
-
A .env.submit file in the current directory is loaded automatically.
|
|
1377
|
-
`.trim();
|
|
1378
|
-
}
|
|
1379
|
-
const BOOLEAN_FLAGS = new Set([
|
|
1380
|
-
"help",
|
|
1381
|
-
"dryRun",
|
|
1382
|
-
"chromeReviewExemption",
|
|
1383
|
-
"chromeSkipSubmitReview",
|
|
1384
|
-
"edgeSkipSubmitReview",
|
|
1385
|
-
"initSkipVerify",
|
|
1386
|
-
"initForce"
|
|
1387
|
-
]);
|
|
1388
|
-
const INT_FLAGS = new Set([
|
|
1389
|
-
"chromeDeployPercentage",
|
|
1390
|
-
"watchInterval",
|
|
1391
|
-
"watchTimeout"
|
|
1392
|
-
]);
|
|
1393
|
-
function parseArgs(argv) {
|
|
1394
|
-
const flags = {};
|
|
1395
|
-
let i = 0;
|
|
1396
|
-
if ("watch" === argv[0]) {
|
|
1397
|
-
flags.command = "watch";
|
|
1398
|
-
i = 1;
|
|
1399
|
-
} else if ("init" === argv[0]) {
|
|
1400
|
-
flags.command = "init";
|
|
1401
|
-
i = 1;
|
|
1402
|
-
}
|
|
1403
|
-
while(i < argv.length){
|
|
1404
|
-
const arg = argv[i];
|
|
1405
|
-
if ("--help" === arg || "-h" === arg) {
|
|
1406
|
-
flags.help = true;
|
|
1407
|
-
i++;
|
|
1408
|
-
continue;
|
|
1409
|
-
}
|
|
1410
|
-
if ("--chrome" === arg) {
|
|
1411
|
-
flags.watchStore = "chrome";
|
|
1412
|
-
i++;
|
|
1413
|
-
continue;
|
|
1414
|
-
}
|
|
1415
|
-
if ("--firefox" === arg) {
|
|
1416
|
-
flags.watchStore = "firefox";
|
|
1417
|
-
i++;
|
|
1418
|
-
continue;
|
|
1419
|
-
}
|
|
1420
|
-
if ("--edge" === arg) {
|
|
1421
|
-
flags.watchStore = "edge";
|
|
1422
|
-
i++;
|
|
1423
|
-
continue;
|
|
1424
|
-
}
|
|
1425
|
-
if (!(null == arg ? void 0 : arg.startsWith("--"))) {
|
|
1426
|
-
i++;
|
|
1427
|
-
continue;
|
|
1428
|
-
}
|
|
1429
|
-
const key = arg.slice(2).replace(/-([a-z])/g, (_, c)=>c.toUpperCase());
|
|
1430
|
-
if (BOOLEAN_FLAGS.has(key)) {
|
|
1431
|
-
flags[key] = true;
|
|
1432
|
-
i++;
|
|
1433
|
-
continue;
|
|
1434
|
-
}
|
|
1435
|
-
const next = argv[i + 1];
|
|
1436
|
-
if (null == next || next.startsWith("--")) {
|
|
1437
|
-
flags[key] = true;
|
|
1438
|
-
i++;
|
|
1439
|
-
continue;
|
|
1440
|
-
}
|
|
1441
|
-
flags[key] = INT_FLAGS.has(key) ? parseInt(next, 10) : next;
|
|
1442
|
-
i += 2;
|
|
1443
|
-
}
|
|
1444
|
-
return flags;
|
|
1445
|
-
}
|
|
1446
|
-
function loadDotEnv() {
|
|
1447
|
-
try {
|
|
1448
|
-
const envPath = external_node_path_default().resolve(process.cwd(), ".env.submit");
|
|
1449
|
-
const content = external_node_fs_default().readFileSync(envPath, "utf-8");
|
|
1450
|
-
for (const line of content.split("\n")){
|
|
1451
|
-
const trimmed = line.trim();
|
|
1452
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1453
|
-
const eqIdx = trimmed.indexOf("=");
|
|
1454
|
-
if (-1 === eqIdx) continue;
|
|
1455
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
1456
|
-
let value = trimmed.slice(eqIdx + 1).trim();
|
|
1457
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
1458
|
-
if (!(key in process.env)) process.env[key] = value;
|
|
1459
|
-
}
|
|
1460
|
-
} catch {}
|
|
1461
|
-
}
|
|
1462
1483
|
function writeJsonOutput(filePath, payload) {
|
|
1463
1484
|
external_node_fs_default().mkdirSync(external_node_path_default().dirname(external_node_path_default().resolve(filePath)), {
|
|
1464
1485
|
recursive: true
|
|
1465
1486
|
});
|
|
1466
1487
|
external_node_fs_default().writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
|
|
1467
1488
|
}
|
|
1468
|
-
async function main() {
|
|
1469
|
-
loadDotEnv();
|
|
1470
|
-
const flags = parseArgs(process.argv.slice(2));
|
|
1471
|
-
if (flags.help) {
|
|
1472
|
-
console.log(usage());
|
|
1473
|
-
process.exit(0);
|
|
1474
|
-
}
|
|
1475
|
-
if ("watch" === flags.command) return void await runWatch(flags);
|
|
1476
|
-
if ("init" === flags.command) return void await runInitCommand(flags);
|
|
1477
|
-
const config = resolveConfig(flags);
|
|
1478
|
-
try {
|
|
1479
|
-
const result = await deploy(config);
|
|
1480
|
-
if (flags.outputJson) writeJsonOutput(flags.outputJson, result);
|
|
1481
|
-
if (!result.success) process.exit(1);
|
|
1482
|
-
} catch (err) {
|
|
1483
|
-
logError("deploy", err instanceof Error ? err.message : String(err));
|
|
1484
|
-
process.exit(1);
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
1489
|
async function runWatch(flags) {
|
|
1488
1490
|
const store = flags.watchStore;
|
|
1489
1491
|
if (!store) {
|
|
@@ -1506,7 +1508,8 @@ async function runWatch(flags) {
|
|
|
1506
1508
|
clientId: mustEnv("CHROME_CLIENT_ID", flags.chromeClientId),
|
|
1507
1509
|
clientSecret: mustEnv("CHROME_CLIENT_SECRET", flags.chromeClientSecret),
|
|
1508
1510
|
refreshToken: mustEnv("CHROME_REFRESH_TOKEN", flags.chromeRefreshToken),
|
|
1509
|
-
extensionId: mustEnv("CHROME_EXTENSION_ID", flags.chromeExtensionId)
|
|
1511
|
+
extensionId: mustEnv("CHROME_EXTENSION_ID", flags.chromeExtensionId),
|
|
1512
|
+
publisherId: mustEnv("CHROME_PUBLISHER_ID", flags.chromePublisherId)
|
|
1510
1513
|
}, {
|
|
1511
1514
|
pollIntervalMs,
|
|
1512
1515
|
timeoutMs,
|
|
@@ -1579,6 +1582,41 @@ function parseInitStores(raw) {
|
|
|
1579
1582
|
if (0 === parsed.length) throw new Error(`--init-stores: expected a comma-separated list of chrome,firefox,edge (got "${raw}")`);
|
|
1580
1583
|
return parsed;
|
|
1581
1584
|
}
|
|
1585
|
+
function loadDotEnv() {
|
|
1586
|
+
try {
|
|
1587
|
+
const envPath = external_node_path_default().resolve(process.cwd(), ".env.submit");
|
|
1588
|
+
const content = external_node_fs_default().readFileSync(envPath, "utf-8");
|
|
1589
|
+
for (const line of content.split("\n")){
|
|
1590
|
+
const trimmed = line.trim();
|
|
1591
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1592
|
+
const eqIdx = trimmed.indexOf("=");
|
|
1593
|
+
if (-1 === eqIdx) continue;
|
|
1594
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
1595
|
+
let value = trimmed.slice(eqIdx + 1).trim();
|
|
1596
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
1597
|
+
if (!(key in process.env)) process.env[key] = value;
|
|
1598
|
+
}
|
|
1599
|
+
} catch {}
|
|
1600
|
+
}
|
|
1601
|
+
async function main() {
|
|
1602
|
+
loadDotEnv();
|
|
1603
|
+
const flags = parseArgs(process.argv.slice(2));
|
|
1604
|
+
if (flags.help) {
|
|
1605
|
+
console.log(usage());
|
|
1606
|
+
process.exit(0);
|
|
1607
|
+
}
|
|
1608
|
+
if ("watch" === flags.command) return void await runWatch(flags);
|
|
1609
|
+
if ("init" === flags.command) return void await runInitCommand(flags);
|
|
1610
|
+
const config = resolveConfig(flags);
|
|
1611
|
+
try {
|
|
1612
|
+
const result = await deploy(config);
|
|
1613
|
+
if (flags.outputJson) writeJsonOutput(flags.outputJson, result);
|
|
1614
|
+
if (!result.success) process.exit(1);
|
|
1615
|
+
} catch (err) {
|
|
1616
|
+
logError("deploy", err instanceof Error ? err.message : String(err));
|
|
1617
|
+
process.exit(1);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1582
1620
|
exports.chromeOptionsSchema = __webpack_exports__.chromeOptionsSchema;
|
|
1583
1621
|
exports.deploy = __webpack_exports__.deploy;
|
|
1584
1622
|
exports.deployConfigSchema = __webpack_exports__.deployConfigSchema;
|