@extension.dev/deploy 0.2.5 → 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 +436 -321
  4. package/dist/module.mjs +434 -319
  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 -32
  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 +16 -0
  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,7 +68,12 @@ 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),
@@ -118,7 +123,9 @@ function resolveConfig(flags) {
118
123
  publishTarget: flags.chromePublishTarget ?? envStr("CHROME_PUBLISH_TARGET"),
119
124
  deployPercentage: flags.chromeDeployPercentage ?? envInt("CHROME_DEPLOY_PERCENTAGE"),
120
125
  reviewExemption: flags.chromeReviewExemption ?? envBool("CHROME_REVIEW_EXEMPTION"),
121
- 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")
122
129
  },
123
130
  firefox: null == firefoxZip ? void 0 : {
124
131
  zip: firefoxZip,
@@ -145,6 +152,7 @@ const SETUP_HINTS = {
145
152
  CHROME_CLIENT_ID: "Create OAuth 2.0 credentials at https://console.cloud.google.com/apis/credentials",
146
153
  CHROME_CLIENT_SECRET: "Generated alongside the Client ID in the Google Cloud Console.",
147
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.",
148
156
  CHROME_ZIP: "Path to the .zip file built for Chrome Web Store upload.",
149
157
  FIREFOX_EXTENSION_ID: "The addon GUID ({uuid} format) or email-style ID from your addon's AMO page.",
150
158
  FIREFOX_JWT_ISSUER: "API key from https://addons.mozilla.org/developers/addon/api/key/",
@@ -187,16 +195,24 @@ function envInt(name) {
187
195
  }
188
196
  const promises_namespaceObject = require("node:fs/promises");
189
197
  var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
190
- async function fetchJson(url, init) {
191
- 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));
192
208
  if (!res.ok) {
193
209
  const body = await res.text().catch(()=>"");
194
210
  throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
195
211
  }
196
212
  return res.json();
197
213
  }
198
- async function fetchRaw(url, init) {
199
- const res = await fetch(url, init);
214
+ async function fetchRaw(url, init, timeoutMs) {
215
+ const res = await fetch(url, withTimeout(init, timeoutMs));
200
216
  if (!res.ok) {
201
217
  const body = await res.text().catch(()=>"");
202
218
  throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
@@ -249,10 +265,26 @@ function logDryStep(scope, step, details) {
249
265
  for (const [key, value] of Object.entries(details))if (null != value) console.log(labelLine(key, value));
250
266
  }
251
267
  }
268
+ const UPLOAD_TIMEOUT_MS = 120000;
269
+ const PUBLISH_TIMEOUT_MS = 60000;
252
270
  const CWS_OAUTH_URL = "https://oauth2.googleapis.com/token";
253
271
  const CWS_UPLOAD_BASE = "https://www.googleapis.com/upload/chromewebstore/v1.1/items";
254
272
  const CWS_PUBLISH_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
255
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
+ }
256
288
  function chromeStoreUrl(extensionId) {
257
289
  return `https://chromewebstore.google.com/detail/${extensionId}`;
258
290
  }
@@ -271,16 +303,28 @@ async function authenticate(clientId, clientSecret, refreshToken) {
271
303
  }
272
304
  });
273
305
  }
274
- async function chrome_upload(extensionId, zipPath, token) {
306
+ async function chrome_upload(extensionId, zipPath, token, ctx) {
275
307
  const body = await promises_default().readFile(zipPath);
276
- const res = await fetch(`${CWS_UPLOAD_BASE}/${extensionId}`, {
277
- 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,
278
325
  body,
279
- headers: {
280
- Authorization: `${token.token_type} ${token.access_token}`,
281
- "x-goog-api-version": "2",
282
- "Content-Type": "application/zip"
283
- }
326
+ headers,
327
+ signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
284
328
  });
285
329
  if (!res.ok) {
286
330
  const text = await res.text().catch(()=>"");
@@ -288,6 +332,33 @@ async function chrome_upload(extensionId, zipPath, token) {
288
332
  }
289
333
  }
290
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
+ }
291
362
  const url = new URL(`${CWS_PUBLISH_BASE}/${params.extensionId}/publish`);
292
363
  url.searchParams.set("publishTarget", params.publishTarget);
293
364
  if (null != params.deployPercentage) url.searchParams.set("deployPercentage", String(params.deployPercentage));
@@ -298,7 +369,8 @@ async function publish(params) {
298
369
  Authorization: `${params.token.token_type} ${params.token.access_token}`,
299
370
  "x-goog-api-version": "2",
300
371
  "Content-Length": "0"
301
- }
372
+ },
373
+ signal: AbortSignal.timeout(PUBLISH_TIMEOUT_MS)
302
374
  });
303
375
  if (!res.ok) {
304
376
  const text = await res.text().catch(()=>"");
@@ -308,7 +380,8 @@ async function publish(params) {
308
380
  async function publishChrome(options, dryRun) {
309
381
  await assertFilePresent(options.zip);
310
382
  const zipSize = await getFileSize(options.zip);
311
- log("chrome", "Authenticating with Chrome Web Store");
383
+ const apiVersion = resolveChromeApiVersion(options);
384
+ log("chrome", `Authenticating with Chrome Web Store (API ${apiVersion})`);
312
385
  const token = await authenticate(options.clientId, options.clientSecret, options.refreshToken);
313
386
  const outcome = {
314
387
  storeUrl: chromeStoreUrl(options.extensionId),
@@ -317,25 +390,28 @@ async function publishChrome(options, dryRun) {
317
390
  if (dryRun) {
318
391
  logDryStep("chrome", "Authentication succeeded", {
319
392
  tokenType: token.token_type,
320
- scope: "chromewebstore"
393
+ scope: "chromewebstore",
394
+ apiVersion
321
395
  });
322
396
  logDryStep("chrome", "Would upload ZIP", {
323
397
  file: options.zip,
324
398
  size: formatBytes(zipSize),
325
- target: `${CWS_UPLOAD_BASE}/${options.extensionId}`,
326
- method: "PUT"
399
+ apiVersion
327
400
  });
328
401
  options.skipSubmitReview ? logDryStep("chrome", "Would skip publish (skipSubmitReview=true)") : logDryStep("chrome", "Would publish for review", {
329
- target: `${CWS_PUBLISH_BASE}/${options.extensionId}/publish`,
402
+ apiVersion,
330
403
  publishTarget: options.publishTarget ?? "default",
331
404
  deployPercentage: options.deployPercentage,
332
405
  reviewExemption: options.reviewExemption
333
406
  });
334
- log("chrome", "Dry run complete \u2014 no changes made");
407
+ log("chrome", "Dry run complete - no changes made");
335
408
  return outcome;
336
409
  }
337
410
  log("chrome", `Uploading ZIP file (${formatBytes(zipSize)})`);
338
- await chrome_upload(options.extensionId, options.zip, token);
411
+ await chrome_upload(options.extensionId, options.zip, token, {
412
+ apiVersion,
413
+ publisherId: options.publisherId
414
+ });
339
415
  if (options.skipSubmitReview) {
340
416
  log("chrome", "Skipping publish step (skipSubmitReview=true)");
341
417
  return outcome;
@@ -346,16 +422,36 @@ async function publishChrome(options, dryRun) {
346
422
  publishTarget: options.publishTarget,
347
423
  token,
348
424
  deployPercentage: options.deployPercentage,
349
- reviewExemption: options.reviewExemption
425
+ reviewExemption: options.reviewExemption,
426
+ apiVersion,
427
+ publisherId: options.publisherId
350
428
  });
351
429
  return outcome;
352
430
  }
353
431
  async function getChromeItem(params) {
354
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
+ }
355
451
  const url = `${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=${params.projection ?? "DRAFT"}`;
356
452
  return fetchJson(url, {
357
453
  headers: {
358
- Authorization: `${token.token_type} ${token.access_token}`,
454
+ ...auth,
359
455
  "x-goog-api-version": "2"
360
456
  }
361
457
  });
@@ -370,11 +466,24 @@ async function verifyChromeCredentials(params) {
370
466
  message: `OAuth token exchange failed. Verify your Client ID, Client Secret, and Refresh Token are correct.\n${err instanceof Error ? err.message : err}`
371
467
  };
372
468
  }
469
+ const auth = {
470
+ Authorization: `${token.token_type} ${token.access_token}`
471
+ };
472
+ const version = resolveChromeApiVersion(params);
373
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
+ }
374
485
  const item = await fetchJson(`${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=DRAFT`, {
375
- headers: {
376
- Authorization: `${token.token_type} ${token.access_token}`
377
- }
486
+ headers: auth
378
487
  });
379
488
  const title = (null == item ? void 0 : item.title) || params.extensionId;
380
489
  return {
@@ -390,7 +499,7 @@ async function verifyChromeCredentials(params) {
390
499
  };
391
500
  if (msg.includes("403")) return {
392
501
  ok: false,
393
- 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.`
394
503
  };
395
504
  return {
396
505
  ok: false,
@@ -398,20 +507,14 @@ async function verifyChromeCredentials(params) {
398
507
  };
399
508
  }
400
509
  }
401
- const external_node_crypto_namespaceObject = require("node:crypto");
402
- var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
403
510
  function delay(ms) {
404
511
  return new Promise((resolve)=>{
405
512
  setTimeout(resolve, ms);
406
513
  });
407
514
  }
515
+ const external_node_crypto_namespaceObject = require("node:crypto");
516
+ var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
408
517
  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
518
  function signJwt(issuer, secret, expiresInSeconds = 30) {
416
519
  const header = {
417
520
  alg: "HS256",
@@ -496,6 +599,12 @@ async function createAddon(params) {
496
599
  headers: authHeaders(params.jwtIssuer, params.jwtSecret)
497
600
  });
498
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
+ }
499
608
  async function publishFirefox(options, dryRun) {
500
609
  await assertFilePresent(options.zip);
501
610
  const zipSize = await getFileSize(options.zip);
@@ -861,8 +970,134 @@ async function deploy(config) {
861
970
  success
862
971
  };
863
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
+ }
864
1099
  const DEFAULT_POLL_INTERVAL_MS = 60000;
865
- const DEFAULT_TIMEOUT_MS = 3600000;
1100
+ const watch_DEFAULT_TIMEOUT_MS = 3600000;
866
1101
  function watch_now() {
867
1102
  return new Date().toISOString();
868
1103
  }
@@ -989,7 +1224,7 @@ function watchFirefoxSubmission(input, options = {}) {
989
1224
  }
990
1225
  async function runWatchLoop(_store, probe, options) {
991
1226
  const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
992
- const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1227
+ const timeout = options.timeoutMs ?? watch_DEFAULT_TIMEOUT_MS;
993
1228
  const deadline = Date.now() + timeout;
994
1229
  let lastEvent;
995
1230
  while(true){
@@ -1001,125 +1236,30 @@ async function runWatchLoop(_store, probe, options) {
1001
1236
  await delay(pollInterval);
1002
1237
  }
1003
1238
  }
1004
- const STORE_CHOICES = [
1005
- {
1006
- value: "chrome",
1007
- label: "Chrome Web Store"
1008
- },
1009
- {
1010
- value: "firefox",
1011
- label: "Firefox AMO (addons.mozilla.org)"
1012
- },
1013
- {
1014
- value: "edge",
1015
- 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.`);
1016
1245
  }
1017
- ];
1246
+ throw new Error(`init: ${label} is required.`);
1247
+ }
1018
1248
  const CHROME_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
1019
1249
  const CHROME_OAUTH_AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
1020
1250
  const CHROME_OOB_REDIRECT = "urn:ietf:wg:oauth:2.0:oob";
1021
1251
  const CHROME_SCOPE = "https://www.googleapis.com/auth/chromewebstore";
1022
- async function runInit(deps, options = {}) {
1252
+ async function collectChromeCredentials(deps) {
1023
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.");
1024
1260
  io.log("");
1025
- io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} ${colors.underline("extension-deploy \u2014 set up store credentials")}`);
1026
- io.log("");
1027
- io.log("This wizard walks you through creating credentials for each store and");
1028
- io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
1029
- io.log("");
1030
- const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
1031
- 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.");
1032
- const envLines = [
1033
- "# Generated by `extension-deploy init`.",
1034
- "# Do not commit secrets to source control.",
1035
- ""
1036
- ];
1037
- const verified = {};
1038
- for (const store of stores){
1039
- io.log("");
1040
- io.log(colors.underline(colors.blue(labelFor(store))));
1041
- io.log("");
1042
- if ("chrome" === store) {
1043
- 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}`, "");
1045
- if (!options.skipVerify) {
1046
- const verify = deps.verifyChrome ?? verifyChromeCredentials;
1047
- verified.chrome = normalizeVerify(await verify(creds));
1048
- logVerify(io, "chrome", verified.chrome);
1049
- }
1050
- } else if ("firefox" === store) {
1051
- const creds = await collectFirefoxCredentials(deps);
1052
- envLines.push(`FIREFOX_EXTENSION_ID=${creds.addonId}`, `FIREFOX_JWT_ISSUER=${creds.jwtIssuer}`, `FIREFOX_JWT_SECRET=${creds.jwtSecret}`, "");
1053
- if (!options.skipVerify) {
1054
- const verify = deps.verifyFirefox ?? verifyFirefoxCredentials;
1055
- verified.firefox = normalizeVerify(await verify({
1056
- jwtIssuer: creds.jwtIssuer,
1057
- jwtSecret: creds.jwtSecret,
1058
- addonId: creds.addonId
1059
- }));
1060
- logVerify(io, "firefox", verified.firefox);
1061
- }
1062
- } else {
1063
- const creds = await collectEdgeCredentials(deps);
1064
- envLines.push(`EDGE_PRODUCT_ID=${creds.productId}`, `EDGE_CLIENT_ID=${creds.clientId}`, `EDGE_API_KEY=${creds.apiKey}`, "");
1065
- if (!options.skipVerify) {
1066
- const verify = deps.verifyEdge ?? verifyEdgeCredentials;
1067
- verified.edge = normalizeVerify(await verify({
1068
- clientId: creds.clientId,
1069
- apiKey: creds.apiKey,
1070
- productId: creds.productId
1071
- }));
1072
- logVerify(io, "edge", verified.edge);
1073
- }
1074
- }
1075
- }
1076
- const envContent = envLines.join("\n");
1077
- const envPath = resolveEnvPath(deps.cwd, options.outputPath);
1078
- const exists = await deps.fileExists(envPath);
1079
- let wrote = false;
1080
- if (exists && !options.force) {
1081
- const overwrite = await deps.io.confirm(`${envPath} already exists. Overwrite?`, false);
1082
- if (overwrite) {
1083
- await deps.writeFile(envPath, envContent);
1084
- wrote = true;
1085
- } else {
1086
- io.log("");
1087
- io.log(`Skipped writing ${envPath}. Credential block:`);
1088
- io.log("");
1089
- io.log(envContent);
1090
- }
1091
- } else {
1092
- await deps.writeFile(envPath, envContent);
1093
- wrote = true;
1094
- }
1095
- if (wrote) {
1096
- io.log("");
1097
- io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} Wrote ${colors.underline(envPath)}`);
1098
- io.log("");
1099
- io.log(colors.gray("Next steps:"));
1100
- io.log(" - Add .env.submit to .gitignore if it isn't already (it contains secrets).");
1101
- io.log(" - Run `extension-deploy --dry-run` to sanity-check the full submit flow.");
1102
- io.log("");
1103
- }
1104
- return {
1105
- stores,
1106
- envPath,
1107
- envContent,
1108
- verified,
1109
- wrote
1110
- };
1111
- }
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.");
1261
+ io.log("This wizard will take your Client ID + Secret and walk you through");
1262
+ io.log("generating a refresh token automatically.");
1123
1263
  io.log("");
1124
1264
  const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
1125
1265
  const clientId = await requireValue(io, "OAuth Client ID");
@@ -1224,6 +1364,110 @@ async function collectEdgeCredentials(deps) {
1224
1364
  apiKey
1225
1365
  };
1226
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
+ }
1227
1471
  function labelFor(store) {
1228
1472
  var _STORE_CHOICES_find;
1229
1473
  return (null == (_STORE_CHOICES_find = STORE_CHOICES.find((c)=>c.value === store)) ? void 0 : _STORE_CHOICES_find.label) ?? String(store);
@@ -1242,15 +1486,6 @@ function resolveEnvPath(cwd, outputPath) {
1242
1486
  const target = outputPath ?? ".env.submit";
1243
1487
  return external_node_path_default().isAbsolute(target) ? target : external_node_path_default().join(cwd, target);
1244
1488
  }
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
1489
  const external_node_readline_namespaceObject = require("node:readline");
1255
1490
  var external_node_readline_default = /*#__PURE__*/ __webpack_require__.n(external_node_readline_namespaceObject);
1256
1491
  function createStdinIO() {
@@ -1306,7 +1541,13 @@ function createStdinIO() {
1306
1541
  }
1307
1542
  const stdinFs = {
1308
1543
  async writeFile (filePath, content) {
1309
- 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 {}
1310
1551
  },
1311
1552
  async fileExists (filePath) {
1312
1553
  try {
@@ -1317,173 +1558,12 @@ const stdinFs = {
1317
1558
  }
1318
1559
  }
1319
1560
  };
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
1561
  function writeJsonOutput(filePath, payload) {
1463
1562
  external_node_fs_default().mkdirSync(external_node_path_default().dirname(external_node_path_default().resolve(filePath)), {
1464
1563
  recursive: true
1465
1564
  });
1466
1565
  external_node_fs_default().writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
1467
1566
  }
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
1567
  async function runWatch(flags) {
1488
1568
  const store = flags.watchStore;
1489
1569
  if (!store) {
@@ -1579,6 +1659,41 @@ function parseInitStores(raw) {
1579
1659
  if (0 === parsed.length) throw new Error(`--init-stores: expected a comma-separated list of chrome,firefox,edge (got "${raw}")`);
1580
1660
  return parsed;
1581
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
+ }
1582
1697
  exports.chromeOptionsSchema = __webpack_exports__.chromeOptionsSchema;
1583
1698
  exports.deploy = __webpack_exports__.deploy;
1584
1699
  exports.deployConfigSchema = __webpack_exports__.deployConfigSchema;