@extension.dev/deploy 0.2.4 → 0.3.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +34 -1
  3. package/dist/module.js +500 -340
  4. package/dist/module.mjs +498 -338
  5. package/dist/src/chrome.d.ts +19 -0
  6. package/dist/src/chrome.d.ts.map +1 -1
  7. package/dist/src/cli-commands.d.ts +5 -0
  8. package/dist/src/cli-commands.d.ts.map +1 -0
  9. package/dist/src/cli-parse.d.ts +3 -0
  10. package/dist/src/cli-parse.d.ts.map +1 -0
  11. package/dist/src/cli-usage.d.ts +2 -0
  12. package/dist/src/cli-usage.d.ts.map +1 -0
  13. package/dist/src/cli.d.ts +1 -2
  14. package/dist/src/cli.d.ts.map +1 -1
  15. package/dist/src/config.d.ts +6 -0
  16. package/dist/src/config.d.ts.map +1 -1
  17. package/dist/src/deploy.d.ts.map +1 -1
  18. package/dist/src/edge.d.ts.map +1 -1
  19. package/dist/src/firefox-api.d.ts +52 -0
  20. package/dist/src/firefox-api.d.ts.map +1 -0
  21. package/dist/src/firefox.d.ts +1 -24
  22. package/dist/src/firefox.d.ts.map +1 -1
  23. package/dist/src/init-chrome.d.ts +16 -0
  24. package/dist/src/init-chrome.d.ts.map +1 -0
  25. package/dist/src/init-edge.d.ts +8 -0
  26. package/dist/src/init-edge.d.ts.map +1 -0
  27. package/dist/src/init-firefox.d.ts +8 -0
  28. package/dist/src/init-firefox.d.ts.map +1 -0
  29. package/dist/src/init-io.d.ts.map +1 -1
  30. package/dist/src/init-prompt.d.ts +5 -0
  31. package/dist/src/init-prompt.d.ts.map +1 -0
  32. package/dist/src/init.d.ts +3 -26
  33. package/dist/src/init.d.ts.map +1 -1
  34. package/dist/src/types.d.ts +49 -5
  35. package/dist/src/types.d.ts.map +1 -1
  36. package/dist/src/utils/fetch.d.ts +2 -2
  37. package/dist/src/utils/fetch.d.ts.map +1 -1
  38. package/dist/src/watch.d.ts.map +1 -1
  39. package/package.json +18 -8
  40. package/dist/__tests__/cli.test.d.ts +0 -2
  41. package/dist/__tests__/cli.test.d.ts.map +0 -1
  42. package/dist/__tests__/config.test.d.ts +0 -2
  43. package/dist/__tests__/config.test.d.ts.map +0 -1
  44. package/dist/__tests__/deploy.test.d.ts +0 -2
  45. package/dist/__tests__/deploy.test.d.ts.map +0 -1
  46. package/dist/__tests__/dry-run.test.d.ts +0 -2
  47. package/dist/__tests__/dry-run.test.d.ts.map +0 -1
  48. package/dist/__tests__/init.test.d.ts +0 -2
  49. package/dist/__tests__/init.test.d.ts.map +0 -1
  50. package/dist/__tests__/types.test.d.ts +0 -2
  51. package/dist/__tests__/types.test.d.ts.map +0 -1
  52. package/dist/__tests__/utils.test.d.ts +0 -2
  53. package/dist/__tests__/utils.test.d.ts.map +0 -1
  54. package/dist/__tests__/verify.test.d.ts +0 -2
  55. package/dist/__tests__/verify.test.d.ts.map +0 -1
  56. package/dist/__tests__/watch.test.d.ts +0 -2
  57. package/dist/__tests__/watch.test.d.ts.map +0 -1
package/dist/module.js CHANGED
@@ -68,18 +68,31 @@ const chromeOptionsSchema = external_zod_namespaceObject.z.object({
68
68
  ]).default("default"),
69
69
  deployPercentage: external_zod_namespaceObject.z.number().int().min(1).max(100).optional(),
70
70
  reviewExemption: external_zod_namespaceObject.z.boolean().default(false),
71
- skipSubmitReview: external_zod_namespaceObject.z.boolean().default(false)
71
+ skipSubmitReview: external_zod_namespaceObject.z.boolean().default(false),
72
+ publisherId: external_zod_namespaceObject.z.string().trim().optional(),
73
+ apiVersion: external_zod_namespaceObject.z["enum"]([
74
+ "v1.1",
75
+ "v2"
76
+ ]).optional()
72
77
  });
73
78
  const firefoxOptionsSchema = external_zod_namespaceObject.z.object({
74
79
  zip: external_zod_namespaceObject.z.string().min(1),
75
80
  sourcesZip: external_zod_namespaceObject.z.string().min(1).optional(),
76
- extensionId: external_zod_namespaceObject.z.string().min(1).trim(),
81
+ extensionId: external_zod_namespaceObject.z.string().trim().default(""),
77
82
  jwtIssuer: external_zod_namespaceObject.z.string().min(1).trim(),
78
83
  jwtSecret: external_zod_namespaceObject.z.string().min(1).trim(),
79
84
  channel: external_zod_namespaceObject.z["enum"]([
80
85
  "listed",
81
86
  "unlisted"
82
87
  ]).default("listed")
88
+ }).superRefine((val, ctx)=>{
89
+ if ("listed" === val.channel && 0 === val.extensionId.length) ctx.addIssue({
90
+ code: external_zod_namespaceObject.z.ZodIssueCode.custom,
91
+ path: [
92
+ "extensionId"
93
+ ],
94
+ message: 'Listed Firefox submissions require an existing add-on GUID. Use channel "unlisted" for a first submission.'
95
+ });
83
96
  });
84
97
  const edgeOptionsSchema = external_zod_namespaceObject.z.object({
85
98
  zip: external_zod_namespaceObject.z.string().min(1),
@@ -110,7 +123,9 @@ function resolveConfig(flags) {
110
123
  publishTarget: flags.chromePublishTarget ?? envStr("CHROME_PUBLISH_TARGET"),
111
124
  deployPercentage: flags.chromeDeployPercentage ?? envInt("CHROME_DEPLOY_PERCENTAGE"),
112
125
  reviewExemption: flags.chromeReviewExemption ?? envBool("CHROME_REVIEW_EXEMPTION"),
113
- skipSubmitReview: flags.chromeSkipSubmitReview ?? envBool("CHROME_SKIP_SUBMIT_REVIEW")
126
+ skipSubmitReview: flags.chromeSkipSubmitReview ?? envBool("CHROME_SKIP_SUBMIT_REVIEW"),
127
+ publisherId: flags.chromePublisherId ?? envStr("CHROME_PUBLISHER_ID"),
128
+ apiVersion: flags.chromeApiVersion ?? envStr("CHROME_API_VERSION")
114
129
  },
115
130
  firefox: null == firefoxZip ? void 0 : {
116
131
  zip: firefoxZip,
@@ -137,6 +152,7 @@ const SETUP_HINTS = {
137
152
  CHROME_CLIENT_ID: "Create OAuth 2.0 credentials at https://console.cloud.google.com/apis/credentials",
138
153
  CHROME_CLIENT_SECRET: "Generated alongside the Client ID in the Google Cloud Console.",
139
154
  CHROME_REFRESH_TOKEN: "Obtain by completing the OAuth consent flow. See the Chrome Web Store API docs.",
155
+ CHROME_PUBLISHER_ID: "The publisher UUID from your Chrome Web Store dev console URL (chrome.google.com/webstore/devconsole/<UUID>). Required for the v2 API.",
140
156
  CHROME_ZIP: "Path to the .zip file built for Chrome Web Store upload.",
141
157
  FIREFOX_EXTENSION_ID: "The addon GUID ({uuid} format) or email-style ID from your addon's AMO page.",
142
158
  FIREFOX_JWT_ISSUER: "API key from https://addons.mozilla.org/developers/addon/api/key/",
@@ -179,16 +195,24 @@ function envInt(name) {
179
195
  }
180
196
  const promises_namespaceObject = require("node:fs/promises");
181
197
  var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
182
- async function fetchJson(url, init) {
183
- const res = await fetch(url, init);
198
+ const DEFAULT_TIMEOUT_MS = 60000;
199
+ function withTimeout(init, timeoutMs = DEFAULT_TIMEOUT_MS) {
200
+ if (null == init ? void 0 : init.signal) return init;
201
+ return {
202
+ ...init || {},
203
+ signal: AbortSignal.timeout(timeoutMs)
204
+ };
205
+ }
206
+ async function fetchJson(url, init, timeoutMs) {
207
+ const res = await fetch(url, withTimeout(init, timeoutMs));
184
208
  if (!res.ok) {
185
209
  const body = await res.text().catch(()=>"");
186
210
  throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
187
211
  }
188
212
  return res.json();
189
213
  }
190
- async function fetchRaw(url, init) {
191
- const res = await fetch(url, init);
214
+ async function fetchRaw(url, init, timeoutMs) {
215
+ const res = await fetch(url, withTimeout(init, timeoutMs));
192
216
  if (!res.ok) {
193
217
  const body = await res.text().catch(()=>"");
194
218
  throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
@@ -241,10 +265,26 @@ function logDryStep(scope, step, details) {
241
265
  for (const [key, value] of Object.entries(details))if (null != value) console.log(labelLine(key, value));
242
266
  }
243
267
  }
268
+ const UPLOAD_TIMEOUT_MS = 120000;
269
+ const PUBLISH_TIMEOUT_MS = 60000;
244
270
  const CWS_OAUTH_URL = "https://oauth2.googleapis.com/token";
245
271
  const CWS_UPLOAD_BASE = "https://www.googleapis.com/upload/chromewebstore/v1.1/items";
246
272
  const CWS_PUBLISH_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
247
273
  const CWS_ITEMS_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
274
+ const CWS_V2_BASE = "https://chromewebstore.googleapis.com/v2";
275
+ const CWS_V2_UPLOAD_BASE = "https://chromewebstore.googleapis.com/upload/v2";
276
+ function resolveChromeApiVersion(opts) {
277
+ if ("v1.1" === opts.apiVersion || "v2" === opts.apiVersion) return opts.apiVersion;
278
+ return String(opts.publisherId || "").trim() ? "v2" : "v1.1";
279
+ }
280
+ function v2ItemName(publisherId, extensionId) {
281
+ return `publishers/${encodeURIComponent(publisherId)}/items/${encodeURIComponent(extensionId)}`;
282
+ }
283
+ function requirePublisherId(publisherId, op) {
284
+ const id = String(publisherId || "").trim();
285
+ if (!id) throw new Error(`Chrome ${op} on the v2 API needs a publisher ID (the UUID from your Chrome Web Store dev console URL).`);
286
+ return id;
287
+ }
248
288
  function chromeStoreUrl(extensionId) {
249
289
  return `https://chromewebstore.google.com/detail/${extensionId}`;
250
290
  }
@@ -263,16 +303,28 @@ async function authenticate(clientId, clientSecret, refreshToken) {
263
303
  }
264
304
  });
265
305
  }
266
- async function chrome_upload(extensionId, zipPath, token) {
306
+ async function chrome_upload(extensionId, zipPath, token, ctx) {
267
307
  const body = await promises_default().readFile(zipPath);
268
- const res = await fetch(`${CWS_UPLOAD_BASE}/${extensionId}`, {
269
- method: "PUT",
308
+ let url;
309
+ let method;
310
+ const headers = {
311
+ Authorization: `${token.token_type} ${token.access_token}`,
312
+ "Content-Type": "application/zip"
313
+ };
314
+ if ("v2" === ctx.apiVersion) {
315
+ const pub = requirePublisherId(ctx.publisherId, "upload");
316
+ url = `${CWS_V2_UPLOAD_BASE}/${v2ItemName(pub, extensionId)}:upload`;
317
+ method = "POST";
318
+ } else {
319
+ url = `${CWS_UPLOAD_BASE}/${extensionId}`;
320
+ method = "PUT";
321
+ headers["x-goog-api-version"] = "2";
322
+ }
323
+ const res = await fetch(url, {
324
+ method,
270
325
  body,
271
- headers: {
272
- Authorization: `${token.token_type} ${token.access_token}`,
273
- "x-goog-api-version": "2",
274
- "Content-Type": "application/zip"
275
- }
326
+ headers,
327
+ signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
276
328
  });
277
329
  if (!res.ok) {
278
330
  const text = await res.text().catch(()=>"");
@@ -280,6 +332,33 @@ async function chrome_upload(extensionId, zipPath, token) {
280
332
  }
281
333
  }
282
334
  async function publish(params) {
335
+ if ("v2" === params.apiVersion) {
336
+ const pub = requirePublisherId(params.publisherId, "publish");
337
+ if ("trustedTesters" === params.publishTarget) throw new Error("The Chrome v2 API has no trusted-tester publish target. Use staged publish, or keep this project on the v1.1 API for trusted-tester releases.");
338
+ const reqBody = {
339
+ publishType: "DEFAULT_PUBLISH"
340
+ };
341
+ if (null != params.deployPercentage) reqBody.deployInfos = [
342
+ {
343
+ deployPercentage: params.deployPercentage
344
+ }
345
+ ];
346
+ if (params.reviewExemption) reqBody.skipReview = true;
347
+ const res = await fetch(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:publish`, {
348
+ method: "POST",
349
+ headers: {
350
+ Authorization: `${params.token.token_type} ${params.token.access_token}`,
351
+ "Content-Type": "application/json"
352
+ },
353
+ body: JSON.stringify(reqBody),
354
+ signal: AbortSignal.timeout(PUBLISH_TIMEOUT_MS)
355
+ });
356
+ if (!res.ok) {
357
+ const text = await res.text().catch(()=>"");
358
+ throw new Error(`Chrome publish failed: ${res.status} ${res.statusText}\n${text}`);
359
+ }
360
+ return;
361
+ }
283
362
  const url = new URL(`${CWS_PUBLISH_BASE}/${params.extensionId}/publish`);
284
363
  url.searchParams.set("publishTarget", params.publishTarget);
285
364
  if (null != params.deployPercentage) url.searchParams.set("deployPercentage", String(params.deployPercentage));
@@ -290,7 +369,8 @@ async function publish(params) {
290
369
  Authorization: `${params.token.token_type} ${params.token.access_token}`,
291
370
  "x-goog-api-version": "2",
292
371
  "Content-Length": "0"
293
- }
372
+ },
373
+ signal: AbortSignal.timeout(PUBLISH_TIMEOUT_MS)
294
374
  });
295
375
  if (!res.ok) {
296
376
  const text = await res.text().catch(()=>"");
@@ -300,7 +380,8 @@ async function publish(params) {
300
380
  async function publishChrome(options, dryRun) {
301
381
  await assertFilePresent(options.zip);
302
382
  const zipSize = await getFileSize(options.zip);
303
- log("chrome", "Authenticating with Chrome Web Store");
383
+ const apiVersion = resolveChromeApiVersion(options);
384
+ log("chrome", `Authenticating with Chrome Web Store (API ${apiVersion})`);
304
385
  const token = await authenticate(options.clientId, options.clientSecret, options.refreshToken);
305
386
  const outcome = {
306
387
  storeUrl: chromeStoreUrl(options.extensionId),
@@ -309,25 +390,28 @@ async function publishChrome(options, dryRun) {
309
390
  if (dryRun) {
310
391
  logDryStep("chrome", "Authentication succeeded", {
311
392
  tokenType: token.token_type,
312
- scope: "chromewebstore"
393
+ scope: "chromewebstore",
394
+ apiVersion
313
395
  });
314
396
  logDryStep("chrome", "Would upload ZIP", {
315
397
  file: options.zip,
316
398
  size: formatBytes(zipSize),
317
- target: `${CWS_UPLOAD_BASE}/${options.extensionId}`,
318
- method: "PUT"
399
+ apiVersion
319
400
  });
320
401
  options.skipSubmitReview ? logDryStep("chrome", "Would skip publish (skipSubmitReview=true)") : logDryStep("chrome", "Would publish for review", {
321
- target: `${CWS_PUBLISH_BASE}/${options.extensionId}/publish`,
402
+ apiVersion,
322
403
  publishTarget: options.publishTarget ?? "default",
323
404
  deployPercentage: options.deployPercentage,
324
405
  reviewExemption: options.reviewExemption
325
406
  });
326
- log("chrome", "Dry run complete \u2014 no changes made");
407
+ log("chrome", "Dry run complete - no changes made");
327
408
  return outcome;
328
409
  }
329
410
  log("chrome", `Uploading ZIP file (${formatBytes(zipSize)})`);
330
- await chrome_upload(options.extensionId, options.zip, token);
411
+ await chrome_upload(options.extensionId, options.zip, token, {
412
+ apiVersion,
413
+ publisherId: options.publisherId
414
+ });
331
415
  if (options.skipSubmitReview) {
332
416
  log("chrome", "Skipping publish step (skipSubmitReview=true)");
333
417
  return outcome;
@@ -338,16 +422,36 @@ async function publishChrome(options, dryRun) {
338
422
  publishTarget: options.publishTarget,
339
423
  token,
340
424
  deployPercentage: options.deployPercentage,
341
- reviewExemption: options.reviewExemption
425
+ reviewExemption: options.reviewExemption,
426
+ apiVersion,
427
+ publisherId: options.publisherId
342
428
  });
343
429
  return outcome;
344
430
  }
345
431
  async function getChromeItem(params) {
346
432
  const token = await authenticate(params.clientId, params.clientSecret, params.refreshToken);
433
+ const auth = {
434
+ Authorization: `${token.token_type} ${token.access_token}`
435
+ };
436
+ const version = resolveChromeApiVersion(params);
437
+ if ("v2" === version) {
438
+ var _rev_distributionChannels_, _rev_distributionChannels;
439
+ const pub = requirePublisherId(params.publisherId, "status");
440
+ const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
441
+ headers: auth
442
+ });
443
+ const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
444
+ return {
445
+ uploadState: status.lastAsyncUploadState,
446
+ crxVersion: null == rev ? void 0 : null == (_rev_distributionChannels = rev.distributionChannels) ? void 0 : null == (_rev_distributionChannels_ = _rev_distributionChannels[0]) ? void 0 : _rev_distributionChannels_.crxVersion,
447
+ publicKey: status.publicKey,
448
+ state: null == rev ? void 0 : rev.state
449
+ };
450
+ }
347
451
  const url = `${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=${params.projection ?? "DRAFT"}`;
348
452
  return fetchJson(url, {
349
453
  headers: {
350
- Authorization: `${token.token_type} ${token.access_token}`,
454
+ ...auth,
351
455
  "x-goog-api-version": "2"
352
456
  }
353
457
  });
@@ -362,11 +466,24 @@ async function verifyChromeCredentials(params) {
362
466
  message: `OAuth token exchange failed. Verify your Client ID, Client Secret, and Refresh Token are correct.\n${err instanceof Error ? err.message : err}`
363
467
  };
364
468
  }
469
+ const auth = {
470
+ Authorization: `${token.token_type} ${token.access_token}`
471
+ };
472
+ const version = resolveChromeApiVersion(params);
365
473
  try {
474
+ if ("v2" === version) {
475
+ const pub = requirePublisherId(params.publisherId, "verify");
476
+ const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
477
+ headers: auth
478
+ });
479
+ const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
480
+ return {
481
+ ok: true,
482
+ message: `Credentials verified (Chrome v2 API). Item state: ${(null == rev ? void 0 : rev.state) || "unknown"}.`
483
+ };
484
+ }
366
485
  const item = await fetchJson(`${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=DRAFT`, {
367
- headers: {
368
- Authorization: `${token.token_type} ${token.access_token}`
369
- }
486
+ headers: auth
370
487
  });
371
488
  const title = (null == item ? void 0 : item.title) || params.extensionId;
372
489
  return {
@@ -382,7 +499,7 @@ async function verifyChromeCredentials(params) {
382
499
  };
383
500
  if (msg.includes("403")) return {
384
501
  ok: false,
385
- message: `Access denied to extension ${params.extensionId}. You may not be the owner or a developer on this item.`
502
+ message: "v2" === version ? "Access denied. Check the publisher ID matches this extension's account, and that the extension ID is correct." : `Access denied to extension ${params.extensionId}. You may not be the owner or a developer on this item.`
386
503
  };
387
504
  return {
388
505
  ok: false,
@@ -390,20 +507,14 @@ async function verifyChromeCredentials(params) {
390
507
  };
391
508
  }
392
509
  }
393
- const external_node_crypto_namespaceObject = require("node:crypto");
394
- var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
395
510
  function delay(ms) {
396
511
  return new Promise((resolve)=>{
397
512
  setTimeout(resolve, ms);
398
513
  });
399
514
  }
515
+ const external_node_crypto_namespaceObject = require("node:crypto");
516
+ var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
400
517
  const AMO_BASE = "https://addons.mozilla.org/api/v5/addons";
401
- const AMO_PROFILE_URL = "https://addons.mozilla.org/api/v5/accounts/profile/";
402
- const POLL_INTERVAL_MS = 5000;
403
- const VALIDATION_TIMEOUT_MS = 600000;
404
- function firefoxStoreUrl(addonIdOrSlug) {
405
- return `https://addons.mozilla.org/firefox/addon/${addonIdOrSlug}`;
406
- }
407
518
  function signJwt(issuer, secret, expiresInSeconds = 30) {
408
519
  const header = {
409
520
  alg: "HS256",
@@ -470,6 +581,30 @@ async function createVersion(params) {
470
581
  headers: authHeaders(params.jwtIssuer, params.jwtSecret)
471
582
  });
472
583
  }
584
+ async function createAddon(params) {
585
+ const form = new FormData();
586
+ form.set("version.upload", params.uploadUuid);
587
+ if (params.sourcesZip) {
588
+ const srcBuf = await promises_default().readFile(params.sourcesZip);
589
+ const srcBlob = new Blob([
590
+ srcBuf
591
+ ], {
592
+ type: "application/zip"
593
+ });
594
+ form.set("version.source", srcBlob, "sources.zip");
595
+ }
596
+ return fetchJson(`${AMO_BASE}/addon/`, {
597
+ method: "POST",
598
+ body: form,
599
+ headers: authHeaders(params.jwtIssuer, params.jwtSecret)
600
+ });
601
+ }
602
+ const AMO_PROFILE_URL = "https://addons.mozilla.org/api/v5/accounts/profile/";
603
+ const POLL_INTERVAL_MS = 5000;
604
+ const VALIDATION_TIMEOUT_MS = 600000;
605
+ function firefoxStoreUrl(addonIdOrSlug) {
606
+ return `https://addons.mozilla.org/firefox/addon/${addonIdOrSlug}`;
607
+ }
473
608
  async function publishFirefox(options, dryRun) {
474
609
  await assertFilePresent(options.zip);
475
610
  const zipSize = await getFileSize(options.zip);
@@ -478,22 +613,28 @@ async function publishFirefox(options, dryRun) {
478
613
  await assertFilePresent(options.sourcesZip);
479
614
  sourcesSize = await getFileSize(options.sourcesZip);
480
615
  }
481
- const addonId = normalizeAddonId(options.extensionId);
616
+ const addonId = normalizeAddonId(options.extensionId ?? "");
482
617
  const { jwtIssuer, jwtSecret } = options;
483
- log("firefox", "Verifying addon exists");
484
- await getAddon(addonId, jwtIssuer, jwtSecret);
618
+ const channel = options.channel ?? "listed";
619
+ const isNewAddon = 0 === addonId.length;
620
+ if (isNewAddon && "listed" === channel) throw new Error('Listed Firefox submissions require an existing add-on GUID. Use channel "unlisted" for a first submission, or set the add-on GUID.');
621
+ if (!isNewAddon) {
622
+ log("firefox", "Verifying addon exists");
623
+ await getAddon(addonId, jwtIssuer, jwtSecret);
624
+ }
485
625
  const outcome = {
486
- storeUrl: firefoxStoreUrl(addonId),
626
+ storeUrl: isNewAddon ? firefoxStoreUrl("new") : firefoxStoreUrl(addonId),
487
627
  submissionId: addonId
488
628
  };
489
629
  if (dryRun) {
490
- logDryStep("firefox", "Addon verified", {
491
- addonId
630
+ logDryStep("firefox", isNewAddon ? "New add-on (no GUID)" : "Addon verified", {
631
+ addonId: isNewAddon ? "(assigned by AMO)" : addonId,
632
+ channel
492
633
  });
493
634
  logDryStep("firefox", "Would upload ZIP", {
494
635
  file: options.zip,
495
636
  size: formatBytes(zipSize),
496
- channel: options.channel ?? "listed",
637
+ channel,
497
638
  target: `${AMO_BASE}/upload/`,
498
639
  method: "POST"
499
640
  });
@@ -501,8 +642,8 @@ async function publishFirefox(options, dryRun) {
501
642
  interval: `${POLL_INTERVAL_MS / 1000}s`,
502
643
  timeout: `${VALIDATION_TIMEOUT_MS / 60000}min`
503
644
  });
504
- logDryStep("firefox", "Would create new version", {
505
- target: `${AMO_BASE}/addon/${addonId}/versions/`,
645
+ logDryStep("firefox", isNewAddon ? "Would create new add-on" : "Would create new version", {
646
+ target: isNewAddon ? `${AMO_BASE}/addon/` : `${AMO_BASE}/addon/${addonId}/versions/`,
506
647
  method: "POST",
507
648
  sourcesZip: options.sourcesZip ? `${options.sourcesZip} (${formatBytes(sourcesSize)})` : "(none)"
508
649
  });
@@ -511,18 +652,31 @@ async function publishFirefox(options, dryRun) {
511
652
  }
512
653
  const upload = await uploadAndAwaitProcessing(options, jwtIssuer, jwtSecret);
513
654
  outcome.submissionId = upload.uuid;
514
- log("firefox", "Creating new version");
515
- const version = await createVersion({
516
- addonId,
517
- sourcesZip: options.sourcesZip,
518
- uploadUuid: upload.uuid,
519
- jwtIssuer,
520
- jwtSecret
521
- });
522
- outcome.submissionId = String(version.id);
523
655
  const { errors, warnings, notices } = upload.validation;
524
656
  log("firefox", `Validation: ${errors} error(s), ${warnings} warning(s), ${notices} notice(s)`);
525
657
  if (!upload.valid) throw new Error("Firefox extension failed validation");
658
+ if (isNewAddon) {
659
+ var _created_version;
660
+ log("firefox", "Creating new add-on");
661
+ const created = await createAddon({
662
+ sourcesZip: options.sourcesZip,
663
+ uploadUuid: upload.uuid,
664
+ jwtIssuer,
665
+ jwtSecret
666
+ });
667
+ outcome.submissionId = String((null == (_created_version = created.version) ? void 0 : _created_version.id) ?? created.id);
668
+ outcome.storeUrl = firefoxStoreUrl(created.slug ?? created.guid);
669
+ } else {
670
+ log("firefox", "Creating new version");
671
+ const version = await createVersion({
672
+ addonId,
673
+ sourcesZip: options.sourcesZip,
674
+ uploadUuid: upload.uuid,
675
+ jwtIssuer,
676
+ jwtSecret
677
+ });
678
+ outcome.submissionId = String(version.id);
679
+ }
526
680
  return outcome;
527
681
  }
528
682
  function getFirefoxVersion(params) {
@@ -816,8 +970,134 @@ async function deploy(config) {
816
970
  success
817
971
  };
818
972
  }
973
+ function usage() {
974
+ return `
975
+ extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
976
+
977
+ USAGE
978
+ extension-deploy [options] submit to one or more stores
979
+ extension-deploy init [options] interactive credential setup wizard
980
+ extension-deploy watch --chrome [options] poll an in-flight submission
981
+ extension-deploy watch --firefox [options]
982
+ extension-deploy watch --edge [options]
983
+
984
+ GLOBAL
985
+ --dry-run Verify auth without uploading or publishing
986
+ --output-json <path> Write the DeployResult / WatchEvent as JSON to <path>
987
+ --help Show this help message
988
+
989
+ CHROME
990
+ --chrome-zip <path> Path to Chrome extension ZIP
991
+ --chrome-extension-id <id> Chrome extension ID
992
+ --chrome-client-id <id> OAuth2 client ID
993
+ --chrome-client-secret <secret> OAuth2 client secret
994
+ --chrome-refresh-token <token> OAuth2 refresh token
995
+ --chrome-publish-target <target> "default" or "trustedTesters"
996
+ --chrome-deploy-percentage <n> Staged rollout percentage (1-100)
997
+ --chrome-review-exemption Request expedited review
998
+ --chrome-skip-submit-review Upload only, skip publish
999
+
1000
+ FIREFOX
1001
+ --firefox-zip <path> Path to Firefox extension ZIP
1002
+ --firefox-sources-zip <path> Path to sources ZIP (optional)
1003
+ --firefox-extension-id <id> Addon GUID or email-style ID
1004
+ --firefox-jwt-issuer <issuer> AMO JWT issuer
1005
+ --firefox-jwt-secret <secret> AMO JWT secret
1006
+ --firefox-channel <channel> "listed" or "unlisted"
1007
+
1008
+ EDGE
1009
+ --edge-zip <path> Path to Edge extension ZIP
1010
+ --edge-product-id <id> Partner Center product ID
1011
+ --edge-client-id <id> Partner Center client ID
1012
+ --edge-api-key <key> Partner Center API key (v1.1)
1013
+ --edge-skip-submit-review Upload only, skip publish
1014
+
1015
+ WATCH
1016
+ --watch-submission-id <id> Required for firefox/edge (version id / operation id)
1017
+ --watch-interval <seconds> Poll interval (default 60)
1018
+ --watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
1019
+
1020
+ INIT
1021
+ --init-stores <list> Comma-separated store list (skip picker)
1022
+ --init-output <path> Destination file (default .env.submit)
1023
+ --init-skip-verify Don't run verifyCredentials after collection
1024
+ --init-force Overwrite existing .env.submit without asking
1025
+
1026
+ ENVIRONMENT VARIABLES
1027
+ All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
1028
+ FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
1029
+ A .env.submit file in the current directory is loaded automatically.
1030
+ `.trim();
1031
+ }
1032
+ const BOOLEAN_FLAGS = new Set([
1033
+ "help",
1034
+ "dryRun",
1035
+ "chromeReviewExemption",
1036
+ "chromeSkipSubmitReview",
1037
+ "edgeSkipSubmitReview",
1038
+ "initSkipVerify",
1039
+ "initForce"
1040
+ ]);
1041
+ const INT_FLAGS = new Set([
1042
+ "chromeDeployPercentage",
1043
+ "watchInterval",
1044
+ "watchTimeout"
1045
+ ]);
1046
+ function parseArgs(argv) {
1047
+ const flags = {};
1048
+ let i = 0;
1049
+ if ("watch" === argv[0]) {
1050
+ flags.command = "watch";
1051
+ i = 1;
1052
+ } else if ("init" === argv[0]) {
1053
+ flags.command = "init";
1054
+ i = 1;
1055
+ }
1056
+ while(i < argv.length){
1057
+ const arg = argv[i];
1058
+ if ("--help" === arg || "-h" === arg) {
1059
+ flags.help = true;
1060
+ i++;
1061
+ continue;
1062
+ }
1063
+ if ("--chrome" === arg) {
1064
+ flags.watchStore = "chrome";
1065
+ i++;
1066
+ continue;
1067
+ }
1068
+ if ("--firefox" === arg) {
1069
+ flags.watchStore = "firefox";
1070
+ i++;
1071
+ continue;
1072
+ }
1073
+ if ("--edge" === arg) {
1074
+ flags.watchStore = "edge";
1075
+ i++;
1076
+ continue;
1077
+ }
1078
+ if (!(null == arg ? void 0 : arg.startsWith("--"))) {
1079
+ i++;
1080
+ continue;
1081
+ }
1082
+ const key = arg.slice(2).replace(/-([a-z])/g, (_, c)=>c.toUpperCase());
1083
+ if (BOOLEAN_FLAGS.has(key)) {
1084
+ flags[key] = true;
1085
+ i++;
1086
+ continue;
1087
+ }
1088
+ const next = argv[i + 1];
1089
+ if (null == next || next.startsWith("--")) {
1090
+ flags[key] = true;
1091
+ i++;
1092
+ continue;
1093
+ }
1094
+ flags[key] = INT_FLAGS.has(key) ? parseInt(next, 10) : next;
1095
+ i += 2;
1096
+ }
1097
+ return flags;
1098
+ }
819
1099
  const DEFAULT_POLL_INTERVAL_MS = 60000;
820
- const DEFAULT_TIMEOUT_MS = 3600000;
1100
+ const watch_DEFAULT_TIMEOUT_MS = 3600000;
821
1101
  function watch_now() {
822
1102
  return new Date().toISOString();
823
1103
  }
@@ -944,7 +1224,7 @@ function watchFirefoxSubmission(input, options = {}) {
944
1224
  }
945
1225
  async function runWatchLoop(_store, probe, options) {
946
1226
  const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
947
- const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1227
+ const timeout = options.timeoutMs ?? watch_DEFAULT_TIMEOUT_MS;
948
1228
  const deadline = Date.now() + timeout;
949
1229
  let lastEvent;
950
1230
  while(true){
@@ -956,125 +1236,30 @@ async function runWatchLoop(_store, probe, options) {
956
1236
  await delay(pollInterval);
957
1237
  }
958
1238
  }
959
- const STORE_CHOICES = [
960
- {
961
- value: "chrome",
962
- label: "Chrome Web Store"
963
- },
964
- {
965
- value: "firefox",
966
- label: "Firefox AMO (addons.mozilla.org)"
967
- },
968
- {
969
- value: "edge",
970
- label: "Edge Add-ons (Partner Center)"
1239
+ async function requireValue(io, label, hint, opts) {
1240
+ const question = hint ? `${label} ${hint}:` : `${label}:`;
1241
+ for(let attempt = 0; attempt < 3; attempt++){
1242
+ const answer = (await io.prompt(question, opts)).trim();
1243
+ if (answer) return answer;
1244
+ io.log(` ${label} is required.`);
971
1245
  }
972
- ];
1246
+ throw new Error(`init: ${label} is required.`);
1247
+ }
973
1248
  const CHROME_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
974
1249
  const CHROME_OAUTH_AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
975
1250
  const CHROME_OOB_REDIRECT = "urn:ietf:wg:oauth:2.0:oob";
976
1251
  const CHROME_SCOPE = "https://www.googleapis.com/auth/chromewebstore";
977
- async function runInit(deps, options = {}) {
1252
+ async function collectChromeCredentials(deps) {
978
1253
  const { io } = deps;
1254
+ io.log("Chrome Web Store needs OAuth credentials. You'll need:");
1255
+ io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
1256
+ io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
1257
+ io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
1258
+ io.log(" https://console.cloud.google.com/apis/credentials");
1259
+ io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
979
1260
  io.log("");
980
- io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} ${colors.underline("extension-deploy \u2014 set up store credentials")}`);
981
- io.log("");
982
- io.log("This wizard walks you through creating credentials for each store and");
983
- io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
984
- io.log("");
985
- const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
986
- 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.");
987
- const envLines = [
988
- "# Generated by `extension-deploy init`.",
989
- "# Do not commit secrets to source control.",
990
- ""
991
- ];
992
- const verified = {};
993
- for (const store of stores){
994
- io.log("");
995
- io.log(colors.underline(colors.blue(labelFor(store))));
996
- io.log("");
997
- if ("chrome" === store) {
998
- const creds = await collectChromeCredentials(deps);
999
- envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
1000
- if (!options.skipVerify) {
1001
- const verify = deps.verifyChrome ?? verifyChromeCredentials;
1002
- verified.chrome = normalizeVerify(await verify(creds));
1003
- logVerify(io, "chrome", verified.chrome);
1004
- }
1005
- } else if ("firefox" === store) {
1006
- const creds = await collectFirefoxCredentials(deps);
1007
- envLines.push(`FIREFOX_EXTENSION_ID=${creds.addonId}`, `FIREFOX_JWT_ISSUER=${creds.jwtIssuer}`, `FIREFOX_JWT_SECRET=${creds.jwtSecret}`, "");
1008
- if (!options.skipVerify) {
1009
- const verify = deps.verifyFirefox ?? verifyFirefoxCredentials;
1010
- verified.firefox = normalizeVerify(await verify({
1011
- jwtIssuer: creds.jwtIssuer,
1012
- jwtSecret: creds.jwtSecret,
1013
- addonId: creds.addonId
1014
- }));
1015
- logVerify(io, "firefox", verified.firefox);
1016
- }
1017
- } else {
1018
- const creds = await collectEdgeCredentials(deps);
1019
- envLines.push(`EDGE_PRODUCT_ID=${creds.productId}`, `EDGE_CLIENT_ID=${creds.clientId}`, `EDGE_API_KEY=${creds.apiKey}`, "");
1020
- if (!options.skipVerify) {
1021
- const verify = deps.verifyEdge ?? verifyEdgeCredentials;
1022
- verified.edge = normalizeVerify(await verify({
1023
- clientId: creds.clientId,
1024
- apiKey: creds.apiKey,
1025
- productId: creds.productId
1026
- }));
1027
- logVerify(io, "edge", verified.edge);
1028
- }
1029
- }
1030
- }
1031
- const envContent = envLines.join("\n");
1032
- const envPath = resolveEnvPath(deps.cwd, options.outputPath);
1033
- const exists = await deps.fileExists(envPath);
1034
- let wrote = false;
1035
- if (exists && !options.force) {
1036
- const overwrite = await deps.io.confirm(`${envPath} already exists. Overwrite?`, false);
1037
- if (overwrite) {
1038
- await deps.writeFile(envPath, envContent);
1039
- wrote = true;
1040
- } else {
1041
- io.log("");
1042
- io.log(`Skipped writing ${envPath}. Credential block:`);
1043
- io.log("");
1044
- io.log(envContent);
1045
- }
1046
- } else {
1047
- await deps.writeFile(envPath, envContent);
1048
- wrote = true;
1049
- }
1050
- if (wrote) {
1051
- io.log("");
1052
- io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} Wrote ${colors.underline(envPath)}`);
1053
- io.log("");
1054
- io.log(colors.gray("Next steps:"));
1055
- io.log(" - Add .env.submit to .gitignore if it isn't already (it contains secrets).");
1056
- io.log(" - Run `extension-deploy --dry-run` to sanity-check the full submit flow.");
1057
- io.log("");
1058
- }
1059
- return {
1060
- stores,
1061
- envPath,
1062
- envContent,
1063
- verified,
1064
- wrote
1065
- };
1066
- }
1067
- async function collectChromeCredentials(deps) {
1068
- const { io } = deps;
1069
- io.log("Chrome Web Store needs OAuth credentials. You'll need:");
1070
- io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
1071
- io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
1072
- io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
1073
- io.log(" https://console.cloud.google.com/apis/credentials");
1074
- io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
1075
- io.log("");
1076
- io.log("This wizard will take your Client ID + Secret and walk you through");
1077
- io.log("generating a refresh token automatically.");
1261
+ io.log("This wizard will take your Client ID + Secret and walk you through");
1262
+ io.log("generating a refresh token automatically.");
1078
1263
  io.log("");
1079
1264
  const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
1080
1265
  const clientId = await requireValue(io, "OAuth Client ID");
@@ -1179,6 +1364,110 @@ async function collectEdgeCredentials(deps) {
1179
1364
  apiKey
1180
1365
  };
1181
1366
  }
1367
+ const STORE_CHOICES = [
1368
+ {
1369
+ value: "chrome",
1370
+ label: "Chrome Web Store"
1371
+ },
1372
+ {
1373
+ value: "firefox",
1374
+ label: "Firefox AMO (addons.mozilla.org)"
1375
+ },
1376
+ {
1377
+ value: "edge",
1378
+ label: "Edge Add-ons (Partner Center)"
1379
+ }
1380
+ ];
1381
+ async function runInit(deps, options = {}) {
1382
+ const { io } = deps;
1383
+ io.log("");
1384
+ io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} ${colors.underline("extension-deploy \u2014 set up store credentials")}`);
1385
+ io.log("");
1386
+ io.log("This wizard walks you through creating credentials for each store and");
1387
+ io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
1388
+ io.log("");
1389
+ const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
1390
+ 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.");
1391
+ const envLines = [
1392
+ "# Generated by `extension-deploy init`.",
1393
+ "# Do not commit secrets to source control.",
1394
+ ""
1395
+ ];
1396
+ const verified = {};
1397
+ for (const store of stores){
1398
+ io.log("");
1399
+ io.log(colors.underline(colors.blue(labelFor(store))));
1400
+ io.log("");
1401
+ if ("chrome" === store) {
1402
+ const creds = await collectChromeCredentials(deps);
1403
+ envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
1404
+ if (!options.skipVerify) {
1405
+ const verify = deps.verifyChrome ?? verifyChromeCredentials;
1406
+ verified.chrome = normalizeVerify(await verify(creds));
1407
+ logVerify(io, "chrome", verified.chrome);
1408
+ }
1409
+ } else if ("firefox" === store) {
1410
+ const creds = await collectFirefoxCredentials(deps);
1411
+ envLines.push(`FIREFOX_EXTENSION_ID=${creds.addonId}`, `FIREFOX_JWT_ISSUER=${creds.jwtIssuer}`, `FIREFOX_JWT_SECRET=${creds.jwtSecret}`, "");
1412
+ if (!options.skipVerify) {
1413
+ const verify = deps.verifyFirefox ?? verifyFirefoxCredentials;
1414
+ verified.firefox = normalizeVerify(await verify({
1415
+ jwtIssuer: creds.jwtIssuer,
1416
+ jwtSecret: creds.jwtSecret,
1417
+ addonId: creds.addonId
1418
+ }));
1419
+ logVerify(io, "firefox", verified.firefox);
1420
+ }
1421
+ } else {
1422
+ const creds = await collectEdgeCredentials(deps);
1423
+ envLines.push(`EDGE_PRODUCT_ID=${creds.productId}`, `EDGE_CLIENT_ID=${creds.clientId}`, `EDGE_API_KEY=${creds.apiKey}`, "");
1424
+ if (!options.skipVerify) {
1425
+ const verify = deps.verifyEdge ?? verifyEdgeCredentials;
1426
+ verified.edge = normalizeVerify(await verify({
1427
+ clientId: creds.clientId,
1428
+ apiKey: creds.apiKey,
1429
+ productId: creds.productId
1430
+ }));
1431
+ logVerify(io, "edge", verified.edge);
1432
+ }
1433
+ }
1434
+ }
1435
+ const envContent = envLines.join("\n");
1436
+ const envPath = resolveEnvPath(deps.cwd, options.outputPath);
1437
+ const exists = await deps.fileExists(envPath);
1438
+ let wrote = false;
1439
+ if (exists && !options.force) {
1440
+ const overwrite = await deps.io.confirm(`${envPath} already exists. Overwrite?`, false);
1441
+ if (overwrite) {
1442
+ await deps.writeFile(envPath, envContent);
1443
+ wrote = true;
1444
+ } else {
1445
+ io.log("");
1446
+ io.log(`Skipped writing ${envPath}. Credential block:`);
1447
+ io.log("");
1448
+ io.log(envContent);
1449
+ }
1450
+ } else {
1451
+ await deps.writeFile(envPath, envContent);
1452
+ wrote = true;
1453
+ }
1454
+ if (wrote) {
1455
+ io.log("");
1456
+ io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} Wrote ${colors.underline(envPath)}`);
1457
+ io.log("");
1458
+ io.log(colors.gray("Next steps:"));
1459
+ io.log(" - Add .env.submit to .gitignore if it isn't already (it contains secrets).");
1460
+ io.log(" - Run `extension-deploy --dry-run` to sanity-check the full submit flow.");
1461
+ io.log("");
1462
+ }
1463
+ return {
1464
+ stores,
1465
+ envPath,
1466
+ envContent,
1467
+ verified,
1468
+ wrote
1469
+ };
1470
+ }
1182
1471
  function labelFor(store) {
1183
1472
  var _STORE_CHOICES_find;
1184
1473
  return (null == (_STORE_CHOICES_find = STORE_CHOICES.find((c)=>c.value === store)) ? void 0 : _STORE_CHOICES_find.label) ?? String(store);
@@ -1197,15 +1486,6 @@ function resolveEnvPath(cwd, outputPath) {
1197
1486
  const target = outputPath ?? ".env.submit";
1198
1487
  return external_node_path_default().isAbsolute(target) ? target : external_node_path_default().join(cwd, target);
1199
1488
  }
1200
- async function requireValue(io, label, hint, opts) {
1201
- const question = hint ? `${label} ${hint}:` : `${label}:`;
1202
- for(let attempt = 0; attempt < 3; attempt++){
1203
- const answer = (await io.prompt(question, opts)).trim();
1204
- if (answer) return answer;
1205
- io.log(` ${label} is required.`);
1206
- }
1207
- throw new Error(`init: ${label} is required.`);
1208
- }
1209
1489
  const external_node_readline_namespaceObject = require("node:readline");
1210
1490
  var external_node_readline_default = /*#__PURE__*/ __webpack_require__.n(external_node_readline_namespaceObject);
1211
1491
  function createStdinIO() {
@@ -1261,7 +1541,13 @@ function createStdinIO() {
1261
1541
  }
1262
1542
  const stdinFs = {
1263
1543
  async writeFile (filePath, content) {
1264
- await promises_default().writeFile(filePath, content, "utf-8");
1544
+ await promises_default().writeFile(filePath, content, {
1545
+ encoding: "utf-8",
1546
+ mode: 384
1547
+ });
1548
+ try {
1549
+ await promises_default().chmod(filePath, 384);
1550
+ } catch {}
1265
1551
  },
1266
1552
  async fileExists (filePath) {
1267
1553
  try {
@@ -1272,173 +1558,12 @@ const stdinFs = {
1272
1558
  }
1273
1559
  }
1274
1560
  };
1275
- function usage() {
1276
- return `
1277
- extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
1278
-
1279
- USAGE
1280
- extension-deploy [options] submit to one or more stores
1281
- extension-deploy init [options] interactive credential setup wizard
1282
- extension-deploy watch --chrome [options] poll an in-flight submission
1283
- extension-deploy watch --firefox [options]
1284
- extension-deploy watch --edge [options]
1285
-
1286
- GLOBAL
1287
- --dry-run Verify auth without uploading or publishing
1288
- --output-json <path> Write the DeployResult / WatchEvent as JSON to <path>
1289
- --help Show this help message
1290
-
1291
- CHROME
1292
- --chrome-zip <path> Path to Chrome extension ZIP
1293
- --chrome-extension-id <id> Chrome extension ID
1294
- --chrome-client-id <id> OAuth2 client ID
1295
- --chrome-client-secret <secret> OAuth2 client secret
1296
- --chrome-refresh-token <token> OAuth2 refresh token
1297
- --chrome-publish-target <target> "default" or "trustedTesters"
1298
- --chrome-deploy-percentage <n> Staged rollout percentage (1-100)
1299
- --chrome-review-exemption Request expedited review
1300
- --chrome-skip-submit-review Upload only, skip publish
1301
-
1302
- FIREFOX
1303
- --firefox-zip <path> Path to Firefox extension ZIP
1304
- --firefox-sources-zip <path> Path to sources ZIP (optional)
1305
- --firefox-extension-id <id> Addon GUID or email-style ID
1306
- --firefox-jwt-issuer <issuer> AMO JWT issuer
1307
- --firefox-jwt-secret <secret> AMO JWT secret
1308
- --firefox-channel <channel> "listed" or "unlisted"
1309
-
1310
- EDGE
1311
- --edge-zip <path> Path to Edge extension ZIP
1312
- --edge-product-id <id> Partner Center product ID
1313
- --edge-client-id <id> Partner Center client ID
1314
- --edge-api-key <key> Partner Center API key (v1.1)
1315
- --edge-skip-submit-review Upload only, skip publish
1316
-
1317
- WATCH
1318
- --watch-submission-id <id> Required for firefox/edge (version id / operation id)
1319
- --watch-interval <seconds> Poll interval (default 60)
1320
- --watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
1321
-
1322
- INIT
1323
- --init-stores <list> Comma-separated store list (skip picker)
1324
- --init-output <path> Destination file (default .env.submit)
1325
- --init-skip-verify Don't run verifyCredentials after collection
1326
- --init-force Overwrite existing .env.submit without asking
1327
-
1328
- ENVIRONMENT VARIABLES
1329
- All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
1330
- FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
1331
- A .env.submit file in the current directory is loaded automatically.
1332
- `.trim();
1333
- }
1334
- const BOOLEAN_FLAGS = new Set([
1335
- "help",
1336
- "dryRun",
1337
- "chromeReviewExemption",
1338
- "chromeSkipSubmitReview",
1339
- "edgeSkipSubmitReview",
1340
- "initSkipVerify",
1341
- "initForce"
1342
- ]);
1343
- const INT_FLAGS = new Set([
1344
- "chromeDeployPercentage",
1345
- "watchInterval",
1346
- "watchTimeout"
1347
- ]);
1348
- function parseArgs(argv) {
1349
- const flags = {};
1350
- let i = 0;
1351
- if ("watch" === argv[0]) {
1352
- flags.command = "watch";
1353
- i = 1;
1354
- } else if ("init" === argv[0]) {
1355
- flags.command = "init";
1356
- i = 1;
1357
- }
1358
- while(i < argv.length){
1359
- const arg = argv[i];
1360
- if ("--help" === arg || "-h" === arg) {
1361
- flags.help = true;
1362
- i++;
1363
- continue;
1364
- }
1365
- if ("--chrome" === arg) {
1366
- flags.watchStore = "chrome";
1367
- i++;
1368
- continue;
1369
- }
1370
- if ("--firefox" === arg) {
1371
- flags.watchStore = "firefox";
1372
- i++;
1373
- continue;
1374
- }
1375
- if ("--edge" === arg) {
1376
- flags.watchStore = "edge";
1377
- i++;
1378
- continue;
1379
- }
1380
- if (!(null == arg ? void 0 : arg.startsWith("--"))) {
1381
- i++;
1382
- continue;
1383
- }
1384
- const key = arg.slice(2).replace(/-([a-z])/g, (_, c)=>c.toUpperCase());
1385
- if (BOOLEAN_FLAGS.has(key)) {
1386
- flags[key] = true;
1387
- i++;
1388
- continue;
1389
- }
1390
- const next = argv[i + 1];
1391
- if (null == next || next.startsWith("--")) {
1392
- flags[key] = true;
1393
- i++;
1394
- continue;
1395
- }
1396
- flags[key] = INT_FLAGS.has(key) ? parseInt(next, 10) : next;
1397
- i += 2;
1398
- }
1399
- return flags;
1400
- }
1401
- function loadDotEnv() {
1402
- try {
1403
- const envPath = external_node_path_default().resolve(process.cwd(), ".env.submit");
1404
- const content = external_node_fs_default().readFileSync(envPath, "utf-8");
1405
- for (const line of content.split("\n")){
1406
- const trimmed = line.trim();
1407
- if (!trimmed || trimmed.startsWith("#")) continue;
1408
- const eqIdx = trimmed.indexOf("=");
1409
- if (-1 === eqIdx) continue;
1410
- const key = trimmed.slice(0, eqIdx).trim();
1411
- let value = trimmed.slice(eqIdx + 1).trim();
1412
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1413
- if (!(key in process.env)) process.env[key] = value;
1414
- }
1415
- } catch {}
1416
- }
1417
1561
  function writeJsonOutput(filePath, payload) {
1418
1562
  external_node_fs_default().mkdirSync(external_node_path_default().dirname(external_node_path_default().resolve(filePath)), {
1419
1563
  recursive: true
1420
1564
  });
1421
1565
  external_node_fs_default().writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
1422
1566
  }
1423
- async function main() {
1424
- loadDotEnv();
1425
- const flags = parseArgs(process.argv.slice(2));
1426
- if (flags.help) {
1427
- console.log(usage());
1428
- process.exit(0);
1429
- }
1430
- if ("watch" === flags.command) return void await runWatch(flags);
1431
- if ("init" === flags.command) return void await runInitCommand(flags);
1432
- const config = resolveConfig(flags);
1433
- try {
1434
- const result = await deploy(config);
1435
- if (flags.outputJson) writeJsonOutput(flags.outputJson, result);
1436
- if (!result.success) process.exit(1);
1437
- } catch (err) {
1438
- logError("deploy", err instanceof Error ? err.message : String(err));
1439
- process.exit(1);
1440
- }
1441
- }
1442
1567
  async function runWatch(flags) {
1443
1568
  const store = flags.watchStore;
1444
1569
  if (!store) {
@@ -1534,6 +1659,41 @@ function parseInitStores(raw) {
1534
1659
  if (0 === parsed.length) throw new Error(`--init-stores: expected a comma-separated list of chrome,firefox,edge (got "${raw}")`);
1535
1660
  return parsed;
1536
1661
  }
1662
+ function loadDotEnv() {
1663
+ try {
1664
+ const envPath = external_node_path_default().resolve(process.cwd(), ".env.submit");
1665
+ const content = external_node_fs_default().readFileSync(envPath, "utf-8");
1666
+ for (const line of content.split("\n")){
1667
+ const trimmed = line.trim();
1668
+ if (!trimmed || trimmed.startsWith("#")) continue;
1669
+ const eqIdx = trimmed.indexOf("=");
1670
+ if (-1 === eqIdx) continue;
1671
+ const key = trimmed.slice(0, eqIdx).trim();
1672
+ let value = trimmed.slice(eqIdx + 1).trim();
1673
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1674
+ if (!(key in process.env)) process.env[key] = value;
1675
+ }
1676
+ } catch {}
1677
+ }
1678
+ async function main() {
1679
+ loadDotEnv();
1680
+ const flags = parseArgs(process.argv.slice(2));
1681
+ if (flags.help) {
1682
+ console.log(usage());
1683
+ process.exit(0);
1684
+ }
1685
+ if ("watch" === flags.command) return void await runWatch(flags);
1686
+ if ("init" === flags.command) return void await runInitCommand(flags);
1687
+ const config = resolveConfig(flags);
1688
+ try {
1689
+ const result = await deploy(config);
1690
+ if (flags.outputJson) writeJsonOutput(flags.outputJson, result);
1691
+ if (!result.success) process.exit(1);
1692
+ } catch (err) {
1693
+ logError("deploy", err instanceof Error ? err.message : String(err));
1694
+ process.exit(1);
1695
+ }
1696
+ }
1537
1697
  exports.chromeOptionsSchema = __webpack_exports__.chromeOptionsSchema;
1538
1698
  exports.deploy = __webpack_exports__.deploy;
1539
1699
  exports.deployConfigSchema = __webpack_exports__.deployConfigSchema;