@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.mjs CHANGED
@@ -17,7 +17,12 @@ const chromeOptionsSchema = z.object({
17
17
  ]).default("default"),
18
18
  deployPercentage: z.number().int().min(1).max(100).optional(),
19
19
  reviewExemption: z.boolean().default(false),
20
- skipSubmitReview: z.boolean().default(false)
20
+ skipSubmitReview: z.boolean().default(false),
21
+ publisherId: z.string().trim().optional(),
22
+ apiVersion: z["enum"]([
23
+ "v1.1",
24
+ "v2"
25
+ ]).optional()
21
26
  });
22
27
  const firefoxOptionsSchema = z.object({
23
28
  zip: z.string().min(1),
@@ -67,7 +72,9 @@ function resolveConfig(flags) {
67
72
  publishTarget: flags.chromePublishTarget ?? envStr("CHROME_PUBLISH_TARGET"),
68
73
  deployPercentage: flags.chromeDeployPercentage ?? envInt("CHROME_DEPLOY_PERCENTAGE"),
69
74
  reviewExemption: flags.chromeReviewExemption ?? envBool("CHROME_REVIEW_EXEMPTION"),
70
- skipSubmitReview: flags.chromeSkipSubmitReview ?? envBool("CHROME_SKIP_SUBMIT_REVIEW")
75
+ skipSubmitReview: flags.chromeSkipSubmitReview ?? envBool("CHROME_SKIP_SUBMIT_REVIEW"),
76
+ publisherId: flags.chromePublisherId ?? envStr("CHROME_PUBLISHER_ID"),
77
+ apiVersion: flags.chromeApiVersion ?? envStr("CHROME_API_VERSION")
71
78
  },
72
79
  firefox: null == firefoxZip ? void 0 : {
73
80
  zip: firefoxZip,
@@ -94,6 +101,7 @@ const SETUP_HINTS = {
94
101
  CHROME_CLIENT_ID: "Create OAuth 2.0 credentials at https://console.cloud.google.com/apis/credentials",
95
102
  CHROME_CLIENT_SECRET: "Generated alongside the Client ID in the Google Cloud Console.",
96
103
  CHROME_REFRESH_TOKEN: "Obtain by completing the OAuth consent flow. See the Chrome Web Store API docs.",
104
+ 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.",
97
105
  CHROME_ZIP: "Path to the .zip file built for Chrome Web Store upload.",
98
106
  FIREFOX_EXTENSION_ID: "The addon GUID ({uuid} format) or email-style ID from your addon's AMO page.",
99
107
  FIREFOX_JWT_ISSUER: "API key from https://addons.mozilla.org/developers/addon/api/key/",
@@ -134,16 +142,24 @@ function envInt(name) {
134
142
  const val = process.env[name];
135
143
  return null == val ? void 0 : parseInt(val, 10);
136
144
  }
137
- async function fetchJson(url, init) {
138
- const res = await fetch(url, init);
145
+ const DEFAULT_TIMEOUT_MS = 60000;
146
+ function withTimeout(init, timeoutMs = DEFAULT_TIMEOUT_MS) {
147
+ if (null == init ? void 0 : init.signal) return init;
148
+ return {
149
+ ...init || {},
150
+ signal: AbortSignal.timeout(timeoutMs)
151
+ };
152
+ }
153
+ async function fetchJson(url, init, timeoutMs) {
154
+ const res = await fetch(url, withTimeout(init, timeoutMs));
139
155
  if (!res.ok) {
140
156
  const body = await res.text().catch(()=>"");
141
157
  throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
142
158
  }
143
159
  return res.json();
144
160
  }
145
- async function fetchRaw(url, init) {
146
- const res = await fetch(url, init);
161
+ async function fetchRaw(url, init, timeoutMs) {
162
+ const res = await fetch(url, withTimeout(init, timeoutMs));
147
163
  if (!res.ok) {
148
164
  const body = await res.text().catch(()=>"");
149
165
  throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
@@ -194,10 +210,26 @@ function logDryStep(scope, step, details) {
194
210
  for (const [key, value] of Object.entries(details))if (null != value) console.log(labelLine(key, value));
195
211
  }
196
212
  }
213
+ const UPLOAD_TIMEOUT_MS = 120000;
214
+ const PUBLISH_TIMEOUT_MS = 60000;
197
215
  const CWS_OAUTH_URL = "https://oauth2.googleapis.com/token";
198
216
  const CWS_UPLOAD_BASE = "https://www.googleapis.com/upload/chromewebstore/v1.1/items";
199
217
  const CWS_PUBLISH_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
200
218
  const CWS_ITEMS_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
219
+ const CWS_V2_BASE = "https://chromewebstore.googleapis.com/v2";
220
+ const CWS_V2_UPLOAD_BASE = "https://chromewebstore.googleapis.com/upload/v2";
221
+ function resolveChromeApiVersion(opts) {
222
+ if ("v1.1" === opts.apiVersion || "v2" === opts.apiVersion) return opts.apiVersion;
223
+ return String(opts.publisherId || "").trim() ? "v2" : "v1.1";
224
+ }
225
+ function v2ItemName(publisherId, extensionId) {
226
+ return `publishers/${encodeURIComponent(publisherId)}/items/${encodeURIComponent(extensionId)}`;
227
+ }
228
+ function requirePublisherId(publisherId, op) {
229
+ const id = String(publisherId || "").trim();
230
+ 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).`);
231
+ return id;
232
+ }
201
233
  function chromeStoreUrl(extensionId) {
202
234
  return `https://chromewebstore.google.com/detail/${extensionId}`;
203
235
  }
@@ -216,16 +248,28 @@ async function authenticate(clientId, clientSecret, refreshToken) {
216
248
  }
217
249
  });
218
250
  }
219
- async function chrome_upload(extensionId, zipPath, token) {
251
+ async function chrome_upload(extensionId, zipPath, token, ctx) {
220
252
  const body = await promises_default.readFile(zipPath);
221
- const res = await fetch(`${CWS_UPLOAD_BASE}/${extensionId}`, {
222
- method: "PUT",
253
+ let url;
254
+ let method;
255
+ const headers = {
256
+ Authorization: `${token.token_type} ${token.access_token}`,
257
+ "Content-Type": "application/zip"
258
+ };
259
+ if ("v2" === ctx.apiVersion) {
260
+ const pub = requirePublisherId(ctx.publisherId, "upload");
261
+ url = `${CWS_V2_UPLOAD_BASE}/${v2ItemName(pub, extensionId)}:upload`;
262
+ method = "POST";
263
+ } else {
264
+ url = `${CWS_UPLOAD_BASE}/${extensionId}`;
265
+ method = "PUT";
266
+ headers["x-goog-api-version"] = "2";
267
+ }
268
+ const res = await fetch(url, {
269
+ method,
223
270
  body,
224
- headers: {
225
- Authorization: `${token.token_type} ${token.access_token}`,
226
- "x-goog-api-version": "2",
227
- "Content-Type": "application/zip"
228
- }
271
+ headers,
272
+ signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
229
273
  });
230
274
  if (!res.ok) {
231
275
  const text = await res.text().catch(()=>"");
@@ -233,6 +277,33 @@ async function chrome_upload(extensionId, zipPath, token) {
233
277
  }
234
278
  }
235
279
  async function publish(params) {
280
+ if ("v2" === params.apiVersion) {
281
+ const pub = requirePublisherId(params.publisherId, "publish");
282
+ 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.");
283
+ const reqBody = {
284
+ publishType: "DEFAULT_PUBLISH"
285
+ };
286
+ if (null != params.deployPercentage) reqBody.deployInfos = [
287
+ {
288
+ deployPercentage: params.deployPercentage
289
+ }
290
+ ];
291
+ if (params.reviewExemption) reqBody.skipReview = true;
292
+ const res = await fetch(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:publish`, {
293
+ method: "POST",
294
+ headers: {
295
+ Authorization: `${params.token.token_type} ${params.token.access_token}`,
296
+ "Content-Type": "application/json"
297
+ },
298
+ body: JSON.stringify(reqBody),
299
+ signal: AbortSignal.timeout(PUBLISH_TIMEOUT_MS)
300
+ });
301
+ if (!res.ok) {
302
+ const text = await res.text().catch(()=>"");
303
+ throw new Error(`Chrome publish failed: ${res.status} ${res.statusText}\n${text}`);
304
+ }
305
+ return;
306
+ }
236
307
  const url = new URL(`${CWS_PUBLISH_BASE}/${params.extensionId}/publish`);
237
308
  url.searchParams.set("publishTarget", params.publishTarget);
238
309
  if (null != params.deployPercentage) url.searchParams.set("deployPercentage", String(params.deployPercentage));
@@ -243,7 +314,8 @@ async function publish(params) {
243
314
  Authorization: `${params.token.token_type} ${params.token.access_token}`,
244
315
  "x-goog-api-version": "2",
245
316
  "Content-Length": "0"
246
- }
317
+ },
318
+ signal: AbortSignal.timeout(PUBLISH_TIMEOUT_MS)
247
319
  });
248
320
  if (!res.ok) {
249
321
  const text = await res.text().catch(()=>"");
@@ -253,7 +325,8 @@ async function publish(params) {
253
325
  async function publishChrome(options, dryRun) {
254
326
  await assertFilePresent(options.zip);
255
327
  const zipSize = await getFileSize(options.zip);
256
- log("chrome", "Authenticating with Chrome Web Store");
328
+ const apiVersion = resolveChromeApiVersion(options);
329
+ log("chrome", `Authenticating with Chrome Web Store (API ${apiVersion})`);
257
330
  const token = await authenticate(options.clientId, options.clientSecret, options.refreshToken);
258
331
  const outcome = {
259
332
  storeUrl: chromeStoreUrl(options.extensionId),
@@ -262,25 +335,28 @@ async function publishChrome(options, dryRun) {
262
335
  if (dryRun) {
263
336
  logDryStep("chrome", "Authentication succeeded", {
264
337
  tokenType: token.token_type,
265
- scope: "chromewebstore"
338
+ scope: "chromewebstore",
339
+ apiVersion
266
340
  });
267
341
  logDryStep("chrome", "Would upload ZIP", {
268
342
  file: options.zip,
269
343
  size: formatBytes(zipSize),
270
- target: `${CWS_UPLOAD_BASE}/${options.extensionId}`,
271
- method: "PUT"
344
+ apiVersion
272
345
  });
273
346
  options.skipSubmitReview ? logDryStep("chrome", "Would skip publish (skipSubmitReview=true)") : logDryStep("chrome", "Would publish for review", {
274
- target: `${CWS_PUBLISH_BASE}/${options.extensionId}/publish`,
347
+ apiVersion,
275
348
  publishTarget: options.publishTarget ?? "default",
276
349
  deployPercentage: options.deployPercentage,
277
350
  reviewExemption: options.reviewExemption
278
351
  });
279
- log("chrome", "Dry run complete \u2014 no changes made");
352
+ log("chrome", "Dry run complete - no changes made");
280
353
  return outcome;
281
354
  }
282
355
  log("chrome", `Uploading ZIP file (${formatBytes(zipSize)})`);
283
- await chrome_upload(options.extensionId, options.zip, token);
356
+ await chrome_upload(options.extensionId, options.zip, token, {
357
+ apiVersion,
358
+ publisherId: options.publisherId
359
+ });
284
360
  if (options.skipSubmitReview) {
285
361
  log("chrome", "Skipping publish step (skipSubmitReview=true)");
286
362
  return outcome;
@@ -291,16 +367,36 @@ async function publishChrome(options, dryRun) {
291
367
  publishTarget: options.publishTarget,
292
368
  token,
293
369
  deployPercentage: options.deployPercentage,
294
- reviewExemption: options.reviewExemption
370
+ reviewExemption: options.reviewExemption,
371
+ apiVersion,
372
+ publisherId: options.publisherId
295
373
  });
296
374
  return outcome;
297
375
  }
298
376
  async function getChromeItem(params) {
299
377
  const token = await authenticate(params.clientId, params.clientSecret, params.refreshToken);
378
+ const auth = {
379
+ Authorization: `${token.token_type} ${token.access_token}`
380
+ };
381
+ const version = resolveChromeApiVersion(params);
382
+ if ("v2" === version) {
383
+ var _rev_distributionChannels_, _rev_distributionChannels;
384
+ const pub = requirePublisherId(params.publisherId, "status");
385
+ const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
386
+ headers: auth
387
+ });
388
+ const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
389
+ return {
390
+ uploadState: status.lastAsyncUploadState,
391
+ crxVersion: null == rev ? void 0 : null == (_rev_distributionChannels = rev.distributionChannels) ? void 0 : null == (_rev_distributionChannels_ = _rev_distributionChannels[0]) ? void 0 : _rev_distributionChannels_.crxVersion,
392
+ publicKey: status.publicKey,
393
+ state: null == rev ? void 0 : rev.state
394
+ };
395
+ }
300
396
  const url = `${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=${params.projection ?? "DRAFT"}`;
301
397
  return fetchJson(url, {
302
398
  headers: {
303
- Authorization: `${token.token_type} ${token.access_token}`,
399
+ ...auth,
304
400
  "x-goog-api-version": "2"
305
401
  }
306
402
  });
@@ -315,11 +411,24 @@ async function verifyChromeCredentials(params) {
315
411
  message: `OAuth token exchange failed. Verify your Client ID, Client Secret, and Refresh Token are correct.\n${err instanceof Error ? err.message : err}`
316
412
  };
317
413
  }
414
+ const auth = {
415
+ Authorization: `${token.token_type} ${token.access_token}`
416
+ };
417
+ const version = resolveChromeApiVersion(params);
318
418
  try {
419
+ if ("v2" === version) {
420
+ const pub = requirePublisherId(params.publisherId, "verify");
421
+ const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
422
+ headers: auth
423
+ });
424
+ const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
425
+ return {
426
+ ok: true,
427
+ message: `Credentials verified (Chrome v2 API). Item state: ${(null == rev ? void 0 : rev.state) || "unknown"}.`
428
+ };
429
+ }
319
430
  const item = await fetchJson(`${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=DRAFT`, {
320
- headers: {
321
- Authorization: `${token.token_type} ${token.access_token}`
322
- }
431
+ headers: auth
323
432
  });
324
433
  const title = (null == item ? void 0 : item.title) || params.extensionId;
325
434
  return {
@@ -335,7 +444,7 @@ async function verifyChromeCredentials(params) {
335
444
  };
336
445
  if (msg.includes("403")) return {
337
446
  ok: false,
338
- message: `Access denied to extension ${params.extensionId}. You may not be the owner or a developer on this item.`
447
+ 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.`
339
448
  };
340
449
  return {
341
450
  ok: false,
@@ -349,12 +458,6 @@ function delay(ms) {
349
458
  });
350
459
  }
351
460
  const AMO_BASE = "https://addons.mozilla.org/api/v5/addons";
352
- const AMO_PROFILE_URL = "https://addons.mozilla.org/api/v5/accounts/profile/";
353
- const POLL_INTERVAL_MS = 5000;
354
- const VALIDATION_TIMEOUT_MS = 600000;
355
- function firefoxStoreUrl(addonIdOrSlug) {
356
- return `https://addons.mozilla.org/firefox/addon/${addonIdOrSlug}`;
357
- }
358
461
  function signJwt(issuer, secret, expiresInSeconds = 30) {
359
462
  const header = {
360
463
  alg: "HS256",
@@ -439,6 +542,12 @@ async function createAddon(params) {
439
542
  headers: authHeaders(params.jwtIssuer, params.jwtSecret)
440
543
  });
441
544
  }
545
+ const AMO_PROFILE_URL = "https://addons.mozilla.org/api/v5/accounts/profile/";
546
+ const POLL_INTERVAL_MS = 5000;
547
+ const VALIDATION_TIMEOUT_MS = 600000;
548
+ function firefoxStoreUrl(addonIdOrSlug) {
549
+ return `https://addons.mozilla.org/firefox/addon/${addonIdOrSlug}`;
550
+ }
442
551
  async function publishFirefox(options, dryRun) {
443
552
  await assertFilePresent(options.zip);
444
553
  const zipSize = await getFileSize(options.zip);
@@ -804,8 +913,134 @@ async function deploy(config) {
804
913
  success
805
914
  };
806
915
  }
916
+ function usage() {
917
+ return `
918
+ extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
919
+
920
+ USAGE
921
+ extension-deploy [options] submit to one or more stores
922
+ extension-deploy init [options] interactive credential setup wizard
923
+ extension-deploy watch --chrome [options] poll an in-flight submission
924
+ extension-deploy watch --firefox [options]
925
+ extension-deploy watch --edge [options]
926
+
927
+ GLOBAL
928
+ --dry-run Verify auth without uploading or publishing
929
+ --output-json <path> Write the DeployResult / WatchEvent as JSON to <path>
930
+ --help Show this help message
931
+
932
+ CHROME
933
+ --chrome-zip <path> Path to Chrome extension ZIP
934
+ --chrome-extension-id <id> Chrome extension ID
935
+ --chrome-client-id <id> OAuth2 client ID
936
+ --chrome-client-secret <secret> OAuth2 client secret
937
+ --chrome-refresh-token <token> OAuth2 refresh token
938
+ --chrome-publish-target <target> "default" or "trustedTesters"
939
+ --chrome-deploy-percentage <n> Staged rollout percentage (1-100)
940
+ --chrome-review-exemption Request expedited review
941
+ --chrome-skip-submit-review Upload only, skip publish
942
+
943
+ FIREFOX
944
+ --firefox-zip <path> Path to Firefox extension ZIP
945
+ --firefox-sources-zip <path> Path to sources ZIP (optional)
946
+ --firefox-extension-id <id> Addon GUID or email-style ID
947
+ --firefox-jwt-issuer <issuer> AMO JWT issuer
948
+ --firefox-jwt-secret <secret> AMO JWT secret
949
+ --firefox-channel <channel> "listed" or "unlisted"
950
+
951
+ EDGE
952
+ --edge-zip <path> Path to Edge extension ZIP
953
+ --edge-product-id <id> Partner Center product ID
954
+ --edge-client-id <id> Partner Center client ID
955
+ --edge-api-key <key> Partner Center API key (v1.1)
956
+ --edge-skip-submit-review Upload only, skip publish
957
+
958
+ WATCH
959
+ --watch-submission-id <id> Required for firefox/edge (version id / operation id)
960
+ --watch-interval <seconds> Poll interval (default 60)
961
+ --watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
962
+
963
+ INIT
964
+ --init-stores <list> Comma-separated store list (skip picker)
965
+ --init-output <path> Destination file (default .env.submit)
966
+ --init-skip-verify Don't run verifyCredentials after collection
967
+ --init-force Overwrite existing .env.submit without asking
968
+
969
+ ENVIRONMENT VARIABLES
970
+ All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
971
+ FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
972
+ A .env.submit file in the current directory is loaded automatically.
973
+ `.trim();
974
+ }
975
+ const BOOLEAN_FLAGS = new Set([
976
+ "help",
977
+ "dryRun",
978
+ "chromeReviewExemption",
979
+ "chromeSkipSubmitReview",
980
+ "edgeSkipSubmitReview",
981
+ "initSkipVerify",
982
+ "initForce"
983
+ ]);
984
+ const INT_FLAGS = new Set([
985
+ "chromeDeployPercentage",
986
+ "watchInterval",
987
+ "watchTimeout"
988
+ ]);
989
+ function parseArgs(argv) {
990
+ const flags = {};
991
+ let i = 0;
992
+ if ("watch" === argv[0]) {
993
+ flags.command = "watch";
994
+ i = 1;
995
+ } else if ("init" === argv[0]) {
996
+ flags.command = "init";
997
+ i = 1;
998
+ }
999
+ while(i < argv.length){
1000
+ const arg = argv[i];
1001
+ if ("--help" === arg || "-h" === arg) {
1002
+ flags.help = true;
1003
+ i++;
1004
+ continue;
1005
+ }
1006
+ if ("--chrome" === arg) {
1007
+ flags.watchStore = "chrome";
1008
+ i++;
1009
+ continue;
1010
+ }
1011
+ if ("--firefox" === arg) {
1012
+ flags.watchStore = "firefox";
1013
+ i++;
1014
+ continue;
1015
+ }
1016
+ if ("--edge" === arg) {
1017
+ flags.watchStore = "edge";
1018
+ i++;
1019
+ continue;
1020
+ }
1021
+ if (!(null == arg ? void 0 : arg.startsWith("--"))) {
1022
+ i++;
1023
+ continue;
1024
+ }
1025
+ const key = arg.slice(2).replace(/-([a-z])/g, (_, c)=>c.toUpperCase());
1026
+ if (BOOLEAN_FLAGS.has(key)) {
1027
+ flags[key] = true;
1028
+ i++;
1029
+ continue;
1030
+ }
1031
+ const next = argv[i + 1];
1032
+ if (null == next || next.startsWith("--")) {
1033
+ flags[key] = true;
1034
+ i++;
1035
+ continue;
1036
+ }
1037
+ flags[key] = INT_FLAGS.has(key) ? parseInt(next, 10) : next;
1038
+ i += 2;
1039
+ }
1040
+ return flags;
1041
+ }
807
1042
  const DEFAULT_POLL_INTERVAL_MS = 60000;
808
- const DEFAULT_TIMEOUT_MS = 3600000;
1043
+ const watch_DEFAULT_TIMEOUT_MS = 3600000;
809
1044
  function watch_now() {
810
1045
  return new Date().toISOString();
811
1046
  }
@@ -932,7 +1167,7 @@ function watchFirefoxSubmission(input, options = {}) {
932
1167
  }
933
1168
  async function runWatchLoop(_store, probe, options) {
934
1169
  const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
935
- const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1170
+ const timeout = options.timeoutMs ?? watch_DEFAULT_TIMEOUT_MS;
936
1171
  const deadline = Date.now() + timeout;
937
1172
  let lastEvent;
938
1173
  while(true){
@@ -944,125 +1179,30 @@ async function runWatchLoop(_store, probe, options) {
944
1179
  await delay(pollInterval);
945
1180
  }
946
1181
  }
947
- const STORE_CHOICES = [
948
- {
949
- value: "chrome",
950
- label: "Chrome Web Store"
951
- },
952
- {
953
- value: "firefox",
954
- label: "Firefox AMO (addons.mozilla.org)"
955
- },
956
- {
957
- value: "edge",
958
- label: "Edge Add-ons (Partner Center)"
1182
+ async function requireValue(io, label, hint, opts) {
1183
+ const question = hint ? `${label} ${hint}:` : `${label}:`;
1184
+ for(let attempt = 0; attempt < 3; attempt++){
1185
+ const answer = (await io.prompt(question, opts)).trim();
1186
+ if (answer) return answer;
1187
+ io.log(` ${label} is required.`);
959
1188
  }
960
- ];
1189
+ throw new Error(`init: ${label} is required.`);
1190
+ }
961
1191
  const CHROME_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
962
1192
  const CHROME_OAUTH_AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
963
1193
  const CHROME_OOB_REDIRECT = "urn:ietf:wg:oauth:2.0:oob";
964
1194
  const CHROME_SCOPE = "https://www.googleapis.com/auth/chromewebstore";
965
- async function runInit(deps, options = {}) {
1195
+ async function collectChromeCredentials(deps) {
966
1196
  const { io } = deps;
1197
+ io.log("Chrome Web Store needs OAuth credentials. You'll need:");
1198
+ io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
1199
+ io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
1200
+ io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
1201
+ io.log(" https://console.cloud.google.com/apis/credentials");
1202
+ io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
967
1203
  io.log("");
968
- io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} ${colors.underline("extension-deploy \u2014 set up store credentials")}`);
969
- io.log("");
970
- io.log("This wizard walks you through creating credentials for each store and");
971
- io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
972
- io.log("");
973
- const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
974
- 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.");
975
- const envLines = [
976
- "# Generated by `extension-deploy init`.",
977
- "# Do not commit secrets to source control.",
978
- ""
979
- ];
980
- const verified = {};
981
- for (const store of stores){
982
- io.log("");
983
- io.log(colors.underline(colors.blue(labelFor(store))));
984
- io.log("");
985
- if ("chrome" === store) {
986
- const creds = await collectChromeCredentials(deps);
987
- envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
988
- if (!options.skipVerify) {
989
- const verify = deps.verifyChrome ?? verifyChromeCredentials;
990
- verified.chrome = normalizeVerify(await verify(creds));
991
- logVerify(io, "chrome", verified.chrome);
992
- }
993
- } else if ("firefox" === store) {
994
- const creds = await collectFirefoxCredentials(deps);
995
- envLines.push(`FIREFOX_EXTENSION_ID=${creds.addonId}`, `FIREFOX_JWT_ISSUER=${creds.jwtIssuer}`, `FIREFOX_JWT_SECRET=${creds.jwtSecret}`, "");
996
- if (!options.skipVerify) {
997
- const verify = deps.verifyFirefox ?? verifyFirefoxCredentials;
998
- verified.firefox = normalizeVerify(await verify({
999
- jwtIssuer: creds.jwtIssuer,
1000
- jwtSecret: creds.jwtSecret,
1001
- addonId: creds.addonId
1002
- }));
1003
- logVerify(io, "firefox", verified.firefox);
1004
- }
1005
- } else {
1006
- const creds = await collectEdgeCredentials(deps);
1007
- envLines.push(`EDGE_PRODUCT_ID=${creds.productId}`, `EDGE_CLIENT_ID=${creds.clientId}`, `EDGE_API_KEY=${creds.apiKey}`, "");
1008
- if (!options.skipVerify) {
1009
- const verify = deps.verifyEdge ?? verifyEdgeCredentials;
1010
- verified.edge = normalizeVerify(await verify({
1011
- clientId: creds.clientId,
1012
- apiKey: creds.apiKey,
1013
- productId: creds.productId
1014
- }));
1015
- logVerify(io, "edge", verified.edge);
1016
- }
1017
- }
1018
- }
1019
- const envContent = envLines.join("\n");
1020
- const envPath = resolveEnvPath(deps.cwd, options.outputPath);
1021
- const exists = await deps.fileExists(envPath);
1022
- let wrote = false;
1023
- if (exists && !options.force) {
1024
- const overwrite = await deps.io.confirm(`${envPath} already exists. Overwrite?`, false);
1025
- if (overwrite) {
1026
- await deps.writeFile(envPath, envContent);
1027
- wrote = true;
1028
- } else {
1029
- io.log("");
1030
- io.log(`Skipped writing ${envPath}. Credential block:`);
1031
- io.log("");
1032
- io.log(envContent);
1033
- }
1034
- } else {
1035
- await deps.writeFile(envPath, envContent);
1036
- wrote = true;
1037
- }
1038
- if (wrote) {
1039
- io.log("");
1040
- io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} Wrote ${colors.underline(envPath)}`);
1041
- io.log("");
1042
- io.log(colors.gray("Next steps:"));
1043
- io.log(" - Add .env.submit to .gitignore if it isn't already (it contains secrets).");
1044
- io.log(" - Run `extension-deploy --dry-run` to sanity-check the full submit flow.");
1045
- io.log("");
1046
- }
1047
- return {
1048
- stores,
1049
- envPath,
1050
- envContent,
1051
- verified,
1052
- wrote
1053
- };
1054
- }
1055
- async function collectChromeCredentials(deps) {
1056
- const { io } = deps;
1057
- io.log("Chrome Web Store needs OAuth credentials. You'll need:");
1058
- io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
1059
- io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
1060
- io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
1061
- io.log(" https://console.cloud.google.com/apis/credentials");
1062
- io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
1063
- io.log("");
1064
- io.log("This wizard will take your Client ID + Secret and walk you through");
1065
- io.log("generating a refresh token automatically.");
1204
+ io.log("This wizard will take your Client ID + Secret and walk you through");
1205
+ io.log("generating a refresh token automatically.");
1066
1206
  io.log("");
1067
1207
  const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
1068
1208
  const clientId = await requireValue(io, "OAuth Client ID");
@@ -1167,6 +1307,110 @@ async function collectEdgeCredentials(deps) {
1167
1307
  apiKey
1168
1308
  };
1169
1309
  }
1310
+ const STORE_CHOICES = [
1311
+ {
1312
+ value: "chrome",
1313
+ label: "Chrome Web Store"
1314
+ },
1315
+ {
1316
+ value: "firefox",
1317
+ label: "Firefox AMO (addons.mozilla.org)"
1318
+ },
1319
+ {
1320
+ value: "edge",
1321
+ label: "Edge Add-ons (Partner Center)"
1322
+ }
1323
+ ];
1324
+ async function runInit(deps, options = {}) {
1325
+ const { io } = deps;
1326
+ io.log("");
1327
+ io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} ${colors.underline("extension-deploy \u2014 set up store credentials")}`);
1328
+ io.log("");
1329
+ io.log("This wizard walks you through creating credentials for each store and");
1330
+ io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
1331
+ io.log("");
1332
+ const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
1333
+ 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.");
1334
+ const envLines = [
1335
+ "# Generated by `extension-deploy init`.",
1336
+ "# Do not commit secrets to source control.",
1337
+ ""
1338
+ ];
1339
+ const verified = {};
1340
+ for (const store of stores){
1341
+ io.log("");
1342
+ io.log(colors.underline(colors.blue(labelFor(store))));
1343
+ io.log("");
1344
+ if ("chrome" === store) {
1345
+ const creds = await collectChromeCredentials(deps);
1346
+ envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
1347
+ if (!options.skipVerify) {
1348
+ const verify = deps.verifyChrome ?? verifyChromeCredentials;
1349
+ verified.chrome = normalizeVerify(await verify(creds));
1350
+ logVerify(io, "chrome", verified.chrome);
1351
+ }
1352
+ } else if ("firefox" === store) {
1353
+ const creds = await collectFirefoxCredentials(deps);
1354
+ envLines.push(`FIREFOX_EXTENSION_ID=${creds.addonId}`, `FIREFOX_JWT_ISSUER=${creds.jwtIssuer}`, `FIREFOX_JWT_SECRET=${creds.jwtSecret}`, "");
1355
+ if (!options.skipVerify) {
1356
+ const verify = deps.verifyFirefox ?? verifyFirefoxCredentials;
1357
+ verified.firefox = normalizeVerify(await verify({
1358
+ jwtIssuer: creds.jwtIssuer,
1359
+ jwtSecret: creds.jwtSecret,
1360
+ addonId: creds.addonId
1361
+ }));
1362
+ logVerify(io, "firefox", verified.firefox);
1363
+ }
1364
+ } else {
1365
+ const creds = await collectEdgeCredentials(deps);
1366
+ envLines.push(`EDGE_PRODUCT_ID=${creds.productId}`, `EDGE_CLIENT_ID=${creds.clientId}`, `EDGE_API_KEY=${creds.apiKey}`, "");
1367
+ if (!options.skipVerify) {
1368
+ const verify = deps.verifyEdge ?? verifyEdgeCredentials;
1369
+ verified.edge = normalizeVerify(await verify({
1370
+ clientId: creds.clientId,
1371
+ apiKey: creds.apiKey,
1372
+ productId: creds.productId
1373
+ }));
1374
+ logVerify(io, "edge", verified.edge);
1375
+ }
1376
+ }
1377
+ }
1378
+ const envContent = envLines.join("\n");
1379
+ const envPath = resolveEnvPath(deps.cwd, options.outputPath);
1380
+ const exists = await deps.fileExists(envPath);
1381
+ let wrote = false;
1382
+ if (exists && !options.force) {
1383
+ const overwrite = await deps.io.confirm(`${envPath} already exists. Overwrite?`, false);
1384
+ if (overwrite) {
1385
+ await deps.writeFile(envPath, envContent);
1386
+ wrote = true;
1387
+ } else {
1388
+ io.log("");
1389
+ io.log(`Skipped writing ${envPath}. Credential block:`);
1390
+ io.log("");
1391
+ io.log(envContent);
1392
+ }
1393
+ } else {
1394
+ await deps.writeFile(envPath, envContent);
1395
+ wrote = true;
1396
+ }
1397
+ if (wrote) {
1398
+ io.log("");
1399
+ io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} Wrote ${colors.underline(envPath)}`);
1400
+ io.log("");
1401
+ io.log(colors.gray("Next steps:"));
1402
+ io.log(" - Add .env.submit to .gitignore if it isn't already (it contains secrets).");
1403
+ io.log(" - Run `extension-deploy --dry-run` to sanity-check the full submit flow.");
1404
+ io.log("");
1405
+ }
1406
+ return {
1407
+ stores,
1408
+ envPath,
1409
+ envContent,
1410
+ verified,
1411
+ wrote
1412
+ };
1413
+ }
1170
1414
  function labelFor(store) {
1171
1415
  var _STORE_CHOICES_find;
1172
1416
  return (null == (_STORE_CHOICES_find = STORE_CHOICES.find((c)=>c.value === store)) ? void 0 : _STORE_CHOICES_find.label) ?? String(store);
@@ -1185,15 +1429,6 @@ function resolveEnvPath(cwd, outputPath) {
1185
1429
  const target = outputPath ?? ".env.submit";
1186
1430
  return external_node_path_default.isAbsolute(target) ? target : external_node_path_default.join(cwd, target);
1187
1431
  }
1188
- async function requireValue(io, label, hint, opts) {
1189
- const question = hint ? `${label} ${hint}:` : `${label}:`;
1190
- for(let attempt = 0; attempt < 3; attempt++){
1191
- const answer = (await io.prompt(question, opts)).trim();
1192
- if (answer) return answer;
1193
- io.log(` ${label} is required.`);
1194
- }
1195
- throw new Error(`init: ${label} is required.`);
1196
- }
1197
1432
  function createStdinIO() {
1198
1433
  const rl = external_node_readline_default.createInterface({
1199
1434
  input: process.stdin,
@@ -1247,7 +1482,13 @@ function createStdinIO() {
1247
1482
  }
1248
1483
  const stdinFs = {
1249
1484
  async writeFile (filePath, content) {
1250
- await promises_default.writeFile(filePath, content, "utf-8");
1485
+ await promises_default.writeFile(filePath, content, {
1486
+ encoding: "utf-8",
1487
+ mode: 384
1488
+ });
1489
+ try {
1490
+ await promises_default.chmod(filePath, 384);
1491
+ } catch {}
1251
1492
  },
1252
1493
  async fileExists (filePath) {
1253
1494
  try {
@@ -1258,173 +1499,12 @@ const stdinFs = {
1258
1499
  }
1259
1500
  }
1260
1501
  };
1261
- function usage() {
1262
- return `
1263
- extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
1264
-
1265
- USAGE
1266
- extension-deploy [options] submit to one or more stores
1267
- extension-deploy init [options] interactive credential setup wizard
1268
- extension-deploy watch --chrome [options] poll an in-flight submission
1269
- extension-deploy watch --firefox [options]
1270
- extension-deploy watch --edge [options]
1271
-
1272
- GLOBAL
1273
- --dry-run Verify auth without uploading or publishing
1274
- --output-json <path> Write the DeployResult / WatchEvent as JSON to <path>
1275
- --help Show this help message
1276
-
1277
- CHROME
1278
- --chrome-zip <path> Path to Chrome extension ZIP
1279
- --chrome-extension-id <id> Chrome extension ID
1280
- --chrome-client-id <id> OAuth2 client ID
1281
- --chrome-client-secret <secret> OAuth2 client secret
1282
- --chrome-refresh-token <token> OAuth2 refresh token
1283
- --chrome-publish-target <target> "default" or "trustedTesters"
1284
- --chrome-deploy-percentage <n> Staged rollout percentage (1-100)
1285
- --chrome-review-exemption Request expedited review
1286
- --chrome-skip-submit-review Upload only, skip publish
1287
-
1288
- FIREFOX
1289
- --firefox-zip <path> Path to Firefox extension ZIP
1290
- --firefox-sources-zip <path> Path to sources ZIP (optional)
1291
- --firefox-extension-id <id> Addon GUID or email-style ID
1292
- --firefox-jwt-issuer <issuer> AMO JWT issuer
1293
- --firefox-jwt-secret <secret> AMO JWT secret
1294
- --firefox-channel <channel> "listed" or "unlisted"
1295
-
1296
- EDGE
1297
- --edge-zip <path> Path to Edge extension ZIP
1298
- --edge-product-id <id> Partner Center product ID
1299
- --edge-client-id <id> Partner Center client ID
1300
- --edge-api-key <key> Partner Center API key (v1.1)
1301
- --edge-skip-submit-review Upload only, skip publish
1302
-
1303
- WATCH
1304
- --watch-submission-id <id> Required for firefox/edge (version id / operation id)
1305
- --watch-interval <seconds> Poll interval (default 60)
1306
- --watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
1307
-
1308
- INIT
1309
- --init-stores <list> Comma-separated store list (skip picker)
1310
- --init-output <path> Destination file (default .env.submit)
1311
- --init-skip-verify Don't run verifyCredentials after collection
1312
- --init-force Overwrite existing .env.submit without asking
1313
-
1314
- ENVIRONMENT VARIABLES
1315
- All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
1316
- FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
1317
- A .env.submit file in the current directory is loaded automatically.
1318
- `.trim();
1319
- }
1320
- const BOOLEAN_FLAGS = new Set([
1321
- "help",
1322
- "dryRun",
1323
- "chromeReviewExemption",
1324
- "chromeSkipSubmitReview",
1325
- "edgeSkipSubmitReview",
1326
- "initSkipVerify",
1327
- "initForce"
1328
- ]);
1329
- const INT_FLAGS = new Set([
1330
- "chromeDeployPercentage",
1331
- "watchInterval",
1332
- "watchTimeout"
1333
- ]);
1334
- function parseArgs(argv) {
1335
- const flags = {};
1336
- let i = 0;
1337
- if ("watch" === argv[0]) {
1338
- flags.command = "watch";
1339
- i = 1;
1340
- } else if ("init" === argv[0]) {
1341
- flags.command = "init";
1342
- i = 1;
1343
- }
1344
- while(i < argv.length){
1345
- const arg = argv[i];
1346
- if ("--help" === arg || "-h" === arg) {
1347
- flags.help = true;
1348
- i++;
1349
- continue;
1350
- }
1351
- if ("--chrome" === arg) {
1352
- flags.watchStore = "chrome";
1353
- i++;
1354
- continue;
1355
- }
1356
- if ("--firefox" === arg) {
1357
- flags.watchStore = "firefox";
1358
- i++;
1359
- continue;
1360
- }
1361
- if ("--edge" === arg) {
1362
- flags.watchStore = "edge";
1363
- i++;
1364
- continue;
1365
- }
1366
- if (!(null == arg ? void 0 : arg.startsWith("--"))) {
1367
- i++;
1368
- continue;
1369
- }
1370
- const key = arg.slice(2).replace(/-([a-z])/g, (_, c)=>c.toUpperCase());
1371
- if (BOOLEAN_FLAGS.has(key)) {
1372
- flags[key] = true;
1373
- i++;
1374
- continue;
1375
- }
1376
- const next = argv[i + 1];
1377
- if (null == next || next.startsWith("--")) {
1378
- flags[key] = true;
1379
- i++;
1380
- continue;
1381
- }
1382
- flags[key] = INT_FLAGS.has(key) ? parseInt(next, 10) : next;
1383
- i += 2;
1384
- }
1385
- return flags;
1386
- }
1387
- function loadDotEnv() {
1388
- try {
1389
- const envPath = external_node_path_default.resolve(process.cwd(), ".env.submit");
1390
- const content = external_node_fs_default.readFileSync(envPath, "utf-8");
1391
- for (const line of content.split("\n")){
1392
- const trimmed = line.trim();
1393
- if (!trimmed || trimmed.startsWith("#")) continue;
1394
- const eqIdx = trimmed.indexOf("=");
1395
- if (-1 === eqIdx) continue;
1396
- const key = trimmed.slice(0, eqIdx).trim();
1397
- let value = trimmed.slice(eqIdx + 1).trim();
1398
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1399
- if (!(key in process.env)) process.env[key] = value;
1400
- }
1401
- } catch {}
1402
- }
1403
1502
  function writeJsonOutput(filePath, payload) {
1404
1503
  external_node_fs_default.mkdirSync(external_node_path_default.dirname(external_node_path_default.resolve(filePath)), {
1405
1504
  recursive: true
1406
1505
  });
1407
1506
  external_node_fs_default.writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
1408
1507
  }
1409
- async function main() {
1410
- loadDotEnv();
1411
- const flags = parseArgs(process.argv.slice(2));
1412
- if (flags.help) {
1413
- console.log(usage());
1414
- process.exit(0);
1415
- }
1416
- if ("watch" === flags.command) return void await runWatch(flags);
1417
- if ("init" === flags.command) return void await runInitCommand(flags);
1418
- const config = resolveConfig(flags);
1419
- try {
1420
- const result = await deploy(config);
1421
- if (flags.outputJson) writeJsonOutput(flags.outputJson, result);
1422
- if (!result.success) process.exit(1);
1423
- } catch (err) {
1424
- logError("deploy", err instanceof Error ? err.message : String(err));
1425
- process.exit(1);
1426
- }
1427
- }
1428
1508
  async function runWatch(flags) {
1429
1509
  const store = flags.watchStore;
1430
1510
  if (!store) {
@@ -1520,4 +1600,39 @@ function parseInitStores(raw) {
1520
1600
  if (0 === parsed.length) throw new Error(`--init-stores: expected a comma-separated list of chrome,firefox,edge (got "${raw}")`);
1521
1601
  return parsed;
1522
1602
  }
1603
+ function loadDotEnv() {
1604
+ try {
1605
+ const envPath = external_node_path_default.resolve(process.cwd(), ".env.submit");
1606
+ const content = external_node_fs_default.readFileSync(envPath, "utf-8");
1607
+ for (const line of content.split("\n")){
1608
+ const trimmed = line.trim();
1609
+ if (!trimmed || trimmed.startsWith("#")) continue;
1610
+ const eqIdx = trimmed.indexOf("=");
1611
+ if (-1 === eqIdx) continue;
1612
+ const key = trimmed.slice(0, eqIdx).trim();
1613
+ let value = trimmed.slice(eqIdx + 1).trim();
1614
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1615
+ if (!(key in process.env)) process.env[key] = value;
1616
+ }
1617
+ } catch {}
1618
+ }
1619
+ async function main() {
1620
+ loadDotEnv();
1621
+ const flags = parseArgs(process.argv.slice(2));
1622
+ if (flags.help) {
1623
+ console.log(usage());
1624
+ process.exit(0);
1625
+ }
1626
+ if ("watch" === flags.command) return void await runWatch(flags);
1627
+ if ("init" === flags.command) return void await runInitCommand(flags);
1628
+ const config = resolveConfig(flags);
1629
+ try {
1630
+ const result = await deploy(config);
1631
+ if (flags.outputJson) writeJsonOutput(flags.outputJson, result);
1632
+ if (!result.success) process.exit(1);
1633
+ } catch (err) {
1634
+ logError("deploy", err instanceof Error ? err.message : String(err));
1635
+ process.exit(1);
1636
+ }
1637
+ }
1523
1638
  export { chromeOptionsSchema, deploy, deployConfigSchema, edgeOptionsSchema, firefoxOptionsSchema, getChromeSubmissionEvent, getEdgeSubmissionEvent, getFirefoxSubmissionEvent, main, resolveConfig, validateConfig, verifyChromeCredentials, verifyEdgeCredentials, verifyFirefoxCredentials, watchChromeSubmission, watchEdgeSubmission, watchFirefoxSubmission };