@extension.dev/deploy 0.2.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +35 -2
  3. package/dist/module.js +407 -369
  4. package/dist/module.mjs +409 -371
  5. package/dist/src/chrome.d.ts +6 -1
  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 +3 -2
  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 +17 -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 +8 -8
  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 +1 -0
  39. package/dist/src/watch.d.ts.map +1 -1
  40. package/package.json +18 -8
  41. package/dist/__tests__/cli.test.d.ts +0 -2
  42. package/dist/__tests__/cli.test.d.ts.map +0 -1
  43. package/dist/__tests__/config.test.d.ts +0 -2
  44. package/dist/__tests__/config.test.d.ts.map +0 -1
  45. package/dist/__tests__/deploy.test.d.ts +0 -2
  46. package/dist/__tests__/deploy.test.d.ts.map +0 -1
  47. package/dist/__tests__/dry-run.test.d.ts +0 -2
  48. package/dist/__tests__/dry-run.test.d.ts.map +0 -1
  49. package/dist/__tests__/init.test.d.ts +0 -2
  50. package/dist/__tests__/init.test.d.ts.map +0 -1
  51. package/dist/__tests__/types.test.d.ts +0 -2
  52. package/dist/__tests__/types.test.d.ts.map +0 -1
  53. package/dist/__tests__/utils.test.d.ts +0 -2
  54. package/dist/__tests__/utils.test.d.ts.map +0 -1
  55. package/dist/__tests__/verify.test.d.ts +0 -2
  56. package/dist/__tests__/verify.test.d.ts.map +0 -1
  57. package/dist/__tests__/watch.test.d.ts +0 -2
  58. package/dist/__tests__/watch.test.d.ts.map +0 -1
package/dist/module.mjs CHANGED
@@ -11,10 +11,7 @@ const chromeOptionsSchema = z.object({
11
11
  clientId: z.string().min(1).trim(),
12
12
  clientSecret: z.string().min(1).trim(),
13
13
  refreshToken: z.string().min(1).trim(),
14
- publishTarget: z["enum"]([
15
- "default",
16
- "trustedTesters"
17
- ]).default("default"),
14
+ publisherId: z.string().min(1).trim(),
18
15
  deployPercentage: z.number().int().min(1).max(100).optional(),
19
16
  reviewExemption: z.boolean().default(false),
20
17
  skipSubmitReview: z.boolean().default(false)
@@ -64,7 +61,7 @@ function resolveConfig(flags) {
64
61
  clientId: flags.chromeClientId ?? envStr("CHROME_CLIENT_ID") ?? "",
65
62
  clientSecret: flags.chromeClientSecret ?? envStr("CHROME_CLIENT_SECRET") ?? "",
66
63
  refreshToken: flags.chromeRefreshToken ?? envStr("CHROME_REFRESH_TOKEN") ?? "",
67
- publishTarget: flags.chromePublishTarget ?? envStr("CHROME_PUBLISH_TARGET"),
64
+ publisherId: flags.chromePublisherId ?? envStr("CHROME_PUBLISHER_ID") ?? "",
68
65
  deployPercentage: flags.chromeDeployPercentage ?? envInt("CHROME_DEPLOY_PERCENTAGE"),
69
66
  reviewExemption: flags.chromeReviewExemption ?? envBool("CHROME_REVIEW_EXEMPTION"),
70
67
  skipSubmitReview: flags.chromeSkipSubmitReview ?? envBool("CHROME_SKIP_SUBMIT_REVIEW")
@@ -94,6 +91,7 @@ const SETUP_HINTS = {
94
91
  CHROME_CLIENT_ID: "Create OAuth 2.0 credentials at https://console.cloud.google.com/apis/credentials",
95
92
  CHROME_CLIENT_SECRET: "Generated alongside the Client ID in the Google Cloud Console.",
96
93
  CHROME_REFRESH_TOKEN: "Obtain by completing the OAuth consent flow. See the Chrome Web Store API docs.",
94
+ CHROME_PUBLISHER_ID: "The publisher UUID from your Chrome Web Store dev console URL (chrome.google.com/webstore/devconsole/<UUID>).",
97
95
  CHROME_ZIP: "Path to the .zip file built for Chrome Web Store upload.",
98
96
  FIREFOX_EXTENSION_ID: "The addon GUID ({uuid} format) or email-style ID from your addon's AMO page.",
99
97
  FIREFOX_JWT_ISSUER: "API key from https://addons.mozilla.org/developers/addon/api/key/",
@@ -134,16 +132,24 @@ function envInt(name) {
134
132
  const val = process.env[name];
135
133
  return null == val ? void 0 : parseInt(val, 10);
136
134
  }
137
- async function fetchJson(url, init) {
138
- const res = await fetch(url, init);
135
+ const DEFAULT_TIMEOUT_MS = 60000;
136
+ function withTimeout(init, timeoutMs = DEFAULT_TIMEOUT_MS) {
137
+ if (null == init ? void 0 : init.signal) return init;
138
+ return {
139
+ ...init || {},
140
+ signal: AbortSignal.timeout(timeoutMs)
141
+ };
142
+ }
143
+ async function fetchJson(url, init, timeoutMs) {
144
+ const res = await fetch(url, withTimeout(init, timeoutMs));
139
145
  if (!res.ok) {
140
146
  const body = await res.text().catch(()=>"");
141
147
  throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
142
148
  }
143
149
  return res.json();
144
150
  }
145
- async function fetchRaw(url, init) {
146
- const res = await fetch(url, init);
151
+ async function fetchRaw(url, init, timeoutMs) {
152
+ const res = await fetch(url, withTimeout(init, timeoutMs));
147
153
  if (!res.ok) {
148
154
  const body = await res.text().catch(()=>"");
149
155
  throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
@@ -194,10 +200,19 @@ function logDryStep(scope, step, details) {
194
200
  for (const [key, value] of Object.entries(details))if (null != value) console.log(labelLine(key, value));
195
201
  }
196
202
  }
203
+ const UPLOAD_TIMEOUT_MS = 120000;
204
+ const PUBLISH_TIMEOUT_MS = 60000;
197
205
  const CWS_OAUTH_URL = "https://oauth2.googleapis.com/token";
198
- const CWS_UPLOAD_BASE = "https://www.googleapis.com/upload/chromewebstore/v1.1/items";
199
- const CWS_PUBLISH_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
200
- const CWS_ITEMS_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
206
+ const CWS_V2_BASE = "https://chromewebstore.googleapis.com/v2";
207
+ const CWS_V2_UPLOAD_BASE = "https://chromewebstore.googleapis.com/upload/v2";
208
+ function v2ItemName(publisherId, extensionId) {
209
+ return `publishers/${encodeURIComponent(publisherId)}/items/${encodeURIComponent(extensionId)}`;
210
+ }
211
+ function requirePublisherId(publisherId, op) {
212
+ const id = String(publisherId || "").trim();
213
+ if (!id) throw new Error(`Chrome ${op} needs a publisher ID (the UUID from your Chrome Web Store dev console URL).`);
214
+ return id;
215
+ }
201
216
  function chromeStoreUrl(extensionId) {
202
217
  return `https://chromewebstore.google.com/detail/${extensionId}`;
203
218
  }
@@ -216,16 +231,17 @@ async function authenticate(clientId, clientSecret, refreshToken) {
216
231
  }
217
232
  });
218
233
  }
219
- async function chrome_upload(extensionId, zipPath, token) {
234
+ async function chrome_upload(extensionId, publisherId, zipPath, token) {
220
235
  const body = await promises_default.readFile(zipPath);
221
- const res = await fetch(`${CWS_UPLOAD_BASE}/${extensionId}`, {
222
- method: "PUT",
236
+ const url = `${CWS_V2_UPLOAD_BASE}/${v2ItemName(publisherId, extensionId)}:upload`;
237
+ const res = await fetch(url, {
238
+ method: "POST",
223
239
  body,
224
240
  headers: {
225
241
  Authorization: `${token.token_type} ${token.access_token}`,
226
- "x-goog-api-version": "2",
227
242
  "Content-Type": "application/zip"
228
- }
243
+ },
244
+ signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
229
245
  });
230
246
  if (!res.ok) {
231
247
  const text = await res.text().catch(()=>"");
@@ -233,17 +249,23 @@ async function chrome_upload(extensionId, zipPath, token) {
233
249
  }
234
250
  }
235
251
  async function publish(params) {
236
- const url = new URL(`${CWS_PUBLISH_BASE}/${params.extensionId}/publish`);
237
- url.searchParams.set("publishTarget", params.publishTarget);
238
- if (null != params.deployPercentage) url.searchParams.set("deployPercentage", String(params.deployPercentage));
239
- if (null != params.reviewExemption) url.searchParams.set("reviewExemption", String(params.reviewExemption));
240
- const res = await fetch(url.href, {
252
+ const reqBody = {
253
+ publishType: "DEFAULT_PUBLISH"
254
+ };
255
+ if (null != params.deployPercentage) reqBody.deployInfos = [
256
+ {
257
+ deployPercentage: params.deployPercentage
258
+ }
259
+ ];
260
+ if (params.reviewExemption) reqBody.skipReview = true;
261
+ const res = await fetch(`${CWS_V2_BASE}/${v2ItemName(params.publisherId, params.extensionId)}:publish`, {
241
262
  method: "POST",
242
263
  headers: {
243
264
  Authorization: `${params.token.token_type} ${params.token.access_token}`,
244
- "x-goog-api-version": "2",
245
- "Content-Length": "0"
246
- }
265
+ "Content-Type": "application/json"
266
+ },
267
+ body: JSON.stringify(reqBody),
268
+ signal: AbortSignal.timeout(PUBLISH_TIMEOUT_MS)
247
269
  });
248
270
  if (!res.ok) {
249
271
  const text = await res.text().catch(()=>"");
@@ -253,7 +275,8 @@ async function publish(params) {
253
275
  async function publishChrome(options, dryRun) {
254
276
  await assertFilePresent(options.zip);
255
277
  const zipSize = await getFileSize(options.zip);
256
- log("chrome", "Authenticating with Chrome Web Store");
278
+ const publisherId = requirePublisherId(options.publisherId, "submit");
279
+ log("chrome", "Authenticating with Chrome Web Store (API v2)");
257
280
  const token = await authenticate(options.clientId, options.clientSecret, options.refreshToken);
258
281
  const outcome = {
259
282
  storeUrl: chromeStoreUrl(options.extensionId),
@@ -266,21 +289,17 @@ async function publishChrome(options, dryRun) {
266
289
  });
267
290
  logDryStep("chrome", "Would upload ZIP", {
268
291
  file: options.zip,
269
- size: formatBytes(zipSize),
270
- target: `${CWS_UPLOAD_BASE}/${options.extensionId}`,
271
- method: "PUT"
292
+ size: formatBytes(zipSize)
272
293
  });
273
294
  options.skipSubmitReview ? logDryStep("chrome", "Would skip publish (skipSubmitReview=true)") : logDryStep("chrome", "Would publish for review", {
274
- target: `${CWS_PUBLISH_BASE}/${options.extensionId}/publish`,
275
- publishTarget: options.publishTarget ?? "default",
276
295
  deployPercentage: options.deployPercentage,
277
296
  reviewExemption: options.reviewExemption
278
297
  });
279
- log("chrome", "Dry run complete \u2014 no changes made");
298
+ log("chrome", "Dry run complete - no changes made");
280
299
  return outcome;
281
300
  }
282
301
  log("chrome", `Uploading ZIP file (${formatBytes(zipSize)})`);
283
- await chrome_upload(options.extensionId, options.zip, token);
302
+ await chrome_upload(options.extensionId, publisherId, options.zip, token);
284
303
  if (options.skipSubmitReview) {
285
304
  log("chrome", "Skipping publish step (skipSubmitReview=true)");
286
305
  return outcome;
@@ -288,7 +307,7 @@ async function publishChrome(options, dryRun) {
288
307
  log("chrome", "Publishing extension");
289
308
  await publish({
290
309
  extensionId: options.extensionId,
291
- publishTarget: options.publishTarget,
310
+ publisherId,
292
311
  token,
293
312
  deployPercentage: options.deployPercentage,
294
313
  reviewExemption: options.reviewExemption
@@ -296,14 +315,22 @@ async function publishChrome(options, dryRun) {
296
315
  return outcome;
297
316
  }
298
317
  async function getChromeItem(params) {
318
+ var _rev_distributionChannels_, _rev_distributionChannels;
299
319
  const token = await authenticate(params.clientId, params.clientSecret, params.refreshToken);
300
- const url = `${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=${params.projection ?? "DRAFT"}`;
301
- return fetchJson(url, {
302
- headers: {
303
- Authorization: `${token.token_type} ${token.access_token}`,
304
- "x-goog-api-version": "2"
305
- }
320
+ const auth = {
321
+ Authorization: `${token.token_type} ${token.access_token}`
322
+ };
323
+ const pub = requirePublisherId(params.publisherId, "status");
324
+ const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
325
+ headers: auth
306
326
  });
327
+ const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
328
+ return {
329
+ uploadState: status.lastAsyncUploadState,
330
+ crxVersion: null == rev ? void 0 : null == (_rev_distributionChannels = rev.distributionChannels) ? void 0 : null == (_rev_distributionChannels_ = _rev_distributionChannels[0]) ? void 0 : _rev_distributionChannels_.crxVersion,
331
+ publicKey: status.publicKey,
332
+ state: null == rev ? void 0 : rev.state
333
+ };
307
334
  }
308
335
  async function verifyChromeCredentials(params) {
309
336
  let token;
@@ -315,17 +342,18 @@ async function verifyChromeCredentials(params) {
315
342
  message: `OAuth token exchange failed. Verify your Client ID, Client Secret, and Refresh Token are correct.\n${err instanceof Error ? err.message : err}`
316
343
  };
317
344
  }
345
+ const auth = {
346
+ Authorization: `${token.token_type} ${token.access_token}`
347
+ };
318
348
  try {
319
- const item = await fetchJson(`${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=DRAFT`, {
320
- headers: {
321
- Authorization: `${token.token_type} ${token.access_token}`
322
- }
349
+ const pub = requirePublisherId(params.publisherId, "verify");
350
+ const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
351
+ headers: auth
323
352
  });
324
- const title = (null == item ? void 0 : item.title) || params.extensionId;
353
+ const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
325
354
  return {
326
355
  ok: true,
327
- message: `Credentials verified. You have access to "${title}".`,
328
- extensionTitle: title
356
+ message: `Credentials verified (Chrome Web Store API v2). Item state: ${(null == rev ? void 0 : rev.state) || "unknown"}.`
329
357
  };
330
358
  } catch (err) {
331
359
  const msg = err instanceof Error ? err.message : String(err);
@@ -335,7 +363,7 @@ async function verifyChromeCredentials(params) {
335
363
  };
336
364
  if (msg.includes("403")) return {
337
365
  ok: false,
338
- message: `Access denied to extension ${params.extensionId}. You may not be the owner or a developer on this item.`
366
+ message: "Access denied. Check the publisher ID matches this extension's account, and that the extension ID is correct."
339
367
  };
340
368
  return {
341
369
  ok: false,
@@ -349,12 +377,6 @@ function delay(ms) {
349
377
  });
350
378
  }
351
379
  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
380
  function signJwt(issuer, secret, expiresInSeconds = 30) {
359
381
  const header = {
360
382
  alg: "HS256",
@@ -439,6 +461,12 @@ async function createAddon(params) {
439
461
  headers: authHeaders(params.jwtIssuer, params.jwtSecret)
440
462
  });
441
463
  }
464
+ const AMO_PROFILE_URL = "https://addons.mozilla.org/api/v5/accounts/profile/";
465
+ const POLL_INTERVAL_MS = 5000;
466
+ const VALIDATION_TIMEOUT_MS = 600000;
467
+ function firefoxStoreUrl(addonIdOrSlug) {
468
+ return `https://addons.mozilla.org/firefox/addon/${addonIdOrSlug}`;
469
+ }
442
470
  async function publishFirefox(options, dryRun) {
443
471
  await assertFilePresent(options.zip);
444
472
  const zipSize = await getFileSize(options.zip);
@@ -804,8 +832,134 @@ async function deploy(config) {
804
832
  success
805
833
  };
806
834
  }
835
+ function usage() {
836
+ return `
837
+ extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
838
+
839
+ USAGE
840
+ extension-deploy [options] submit to one or more stores
841
+ extension-deploy init [options] interactive credential setup wizard
842
+ extension-deploy watch --chrome [options] poll an in-flight submission
843
+ extension-deploy watch --firefox [options]
844
+ extension-deploy watch --edge [options]
845
+
846
+ GLOBAL
847
+ --dry-run Verify auth without uploading or publishing
848
+ --output-json <path> Write the DeployResult / WatchEvent as JSON to <path>
849
+ --help Show this help message
850
+
851
+ CHROME
852
+ --chrome-zip <path> Path to Chrome extension ZIP
853
+ --chrome-extension-id <id> Chrome extension ID
854
+ --chrome-publisher-id <uuid> Publisher UUID from the dev console (required)
855
+ --chrome-client-id <id> OAuth2 client ID
856
+ --chrome-client-secret <secret> OAuth2 client secret
857
+ --chrome-refresh-token <token> OAuth2 refresh token
858
+ --chrome-deploy-percentage <n> Staged rollout percentage (1-100)
859
+ --chrome-review-exemption Request expedited review
860
+ --chrome-skip-submit-review Upload only, skip publish
861
+
862
+ FIREFOX
863
+ --firefox-zip <path> Path to Firefox extension ZIP
864
+ --firefox-sources-zip <path> Path to sources ZIP (optional)
865
+ --firefox-extension-id <id> Addon GUID or email-style ID
866
+ --firefox-jwt-issuer <issuer> AMO JWT issuer
867
+ --firefox-jwt-secret <secret> AMO JWT secret
868
+ --firefox-channel <channel> "listed" or "unlisted"
869
+
870
+ EDGE
871
+ --edge-zip <path> Path to Edge extension ZIP
872
+ --edge-product-id <id> Partner Center product ID
873
+ --edge-client-id <id> Partner Center client ID
874
+ --edge-api-key <key> Partner Center API key (v1.1)
875
+ --edge-skip-submit-review Upload only, skip publish
876
+
877
+ WATCH
878
+ --watch-submission-id <id> Required for firefox/edge (version id / operation id)
879
+ --watch-interval <seconds> Poll interval (default 60)
880
+ --watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
881
+
882
+ INIT
883
+ --init-stores <list> Comma-separated store list (skip picker)
884
+ --init-output <path> Destination file (default .env.submit)
885
+ --init-skip-verify Don't run verifyCredentials after collection
886
+ --init-force Overwrite existing .env.submit without asking
887
+
888
+ ENVIRONMENT VARIABLES
889
+ All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
890
+ FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
891
+ A .env.submit file in the current directory is loaded automatically.
892
+ `.trim();
893
+ }
894
+ const BOOLEAN_FLAGS = new Set([
895
+ "help",
896
+ "dryRun",
897
+ "chromeReviewExemption",
898
+ "chromeSkipSubmitReview",
899
+ "edgeSkipSubmitReview",
900
+ "initSkipVerify",
901
+ "initForce"
902
+ ]);
903
+ const INT_FLAGS = new Set([
904
+ "chromeDeployPercentage",
905
+ "watchInterval",
906
+ "watchTimeout"
907
+ ]);
908
+ function parseArgs(argv) {
909
+ const flags = {};
910
+ let i = 0;
911
+ if ("watch" === argv[0]) {
912
+ flags.command = "watch";
913
+ i = 1;
914
+ } else if ("init" === argv[0]) {
915
+ flags.command = "init";
916
+ i = 1;
917
+ }
918
+ while(i < argv.length){
919
+ const arg = argv[i];
920
+ if ("--help" === arg || "-h" === arg) {
921
+ flags.help = true;
922
+ i++;
923
+ continue;
924
+ }
925
+ if ("--chrome" === arg) {
926
+ flags.watchStore = "chrome";
927
+ i++;
928
+ continue;
929
+ }
930
+ if ("--firefox" === arg) {
931
+ flags.watchStore = "firefox";
932
+ i++;
933
+ continue;
934
+ }
935
+ if ("--edge" === arg) {
936
+ flags.watchStore = "edge";
937
+ i++;
938
+ continue;
939
+ }
940
+ if (!(null == arg ? void 0 : arg.startsWith("--"))) {
941
+ i++;
942
+ continue;
943
+ }
944
+ const key = arg.slice(2).replace(/-([a-z])/g, (_, c)=>c.toUpperCase());
945
+ if (BOOLEAN_FLAGS.has(key)) {
946
+ flags[key] = true;
947
+ i++;
948
+ continue;
949
+ }
950
+ const next = argv[i + 1];
951
+ if (null == next || next.startsWith("--")) {
952
+ flags[key] = true;
953
+ i++;
954
+ continue;
955
+ }
956
+ flags[key] = INT_FLAGS.has(key) ? parseInt(next, 10) : next;
957
+ i += 2;
958
+ }
959
+ return flags;
960
+ }
807
961
  const DEFAULT_POLL_INTERVAL_MS = 60000;
808
- const DEFAULT_TIMEOUT_MS = 3600000;
962
+ const watch_DEFAULT_TIMEOUT_MS = 3600000;
809
963
  function watch_now() {
810
964
  return new Date().toISOString();
811
965
  }
@@ -932,7 +1086,7 @@ function watchFirefoxSubmission(input, options = {}) {
932
1086
  }
933
1087
  async function runWatchLoop(_store, probe, options) {
934
1088
  const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
935
- const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1089
+ const timeout = options.timeoutMs ?? watch_DEFAULT_TIMEOUT_MS;
936
1090
  const deadline = Date.now() + timeout;
937
1091
  let lastEvent;
938
1092
  while(true){
@@ -944,47 +1098,174 @@ async function runWatchLoop(_store, probe, options) {
944
1098
  await delay(pollInterval);
945
1099
  }
946
1100
  }
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)"
1101
+ async function requireValue(io, label, hint, opts) {
1102
+ const question = hint ? `${label} ${hint}:` : `${label}:`;
1103
+ for(let attempt = 0; attempt < 3; attempt++){
1104
+ const answer = (await io.prompt(question, opts)).trim();
1105
+ if (answer) return answer;
1106
+ io.log(` ${label} is required.`);
959
1107
  }
960
- ];
1108
+ throw new Error(`init: ${label} is required.`);
1109
+ }
961
1110
  const CHROME_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
962
1111
  const CHROME_OAUTH_AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
963
1112
  const CHROME_OOB_REDIRECT = "urn:ietf:wg:oauth:2.0:oob";
964
1113
  const CHROME_SCOPE = "https://www.googleapis.com/auth/chromewebstore";
965
- async function runInit(deps, options = {}) {
1114
+ async function collectChromeCredentials(deps) {
966
1115
  const { io } = deps;
1116
+ io.log("Chrome Web Store needs OAuth credentials. You'll need:");
1117
+ io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
1118
+ io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
1119
+ io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
1120
+ io.log(" https://console.cloud.google.com/apis/credentials");
1121
+ io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
1122
+ io.log(" 4. Your publisher ID (the UUID in your dev console URL: chrome.google.com/webstore/devconsole/<UUID>).");
967
1123
  io.log("");
968
- io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} ${colors.underline("extension-deploy \u2014 set up store credentials")}`);
1124
+ io.log("This wizard will take your Client ID + Secret and walk you through");
1125
+ io.log("generating a refresh token automatically.");
969
1126
  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.");
1127
+ const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
1128
+ const publisherId = await requireValue(io, "Chrome publisher ID", "(UUID from your dev console URL)");
1129
+ const clientId = await requireValue(io, "OAuth Client ID");
1130
+ const clientSecret = await requireValue(io, "OAuth Client Secret", void 0, {
1131
+ secret: true
1132
+ });
1133
+ const refreshToken = await obtainChromeRefreshToken(deps, clientId, clientSecret);
1134
+ return {
1135
+ extensionId,
1136
+ publisherId,
1137
+ clientId,
1138
+ clientSecret,
1139
+ refreshToken
1140
+ };
1141
+ }
1142
+ function buildChromeAuthUrl(clientId) {
1143
+ const params = new URLSearchParams({
1144
+ response_type: "code",
1145
+ client_id: clientId,
1146
+ redirect_uri: CHROME_OOB_REDIRECT,
1147
+ scope: CHROME_SCOPE,
1148
+ access_type: "offline",
1149
+ prompt: "consent"
1150
+ });
1151
+ return `${CHROME_OAUTH_AUTH_URL}?${params.toString()}`;
1152
+ }
1153
+ async function exchangeChromeAuthCode(input) {
1154
+ const fetchFn = input.fetchImpl ?? fetch;
1155
+ const body = new URLSearchParams({
1156
+ code: input.authCode,
1157
+ client_id: input.clientId,
1158
+ client_secret: input.clientSecret,
1159
+ redirect_uri: CHROME_OOB_REDIRECT,
1160
+ grant_type: "authorization_code"
1161
+ });
1162
+ const res = await fetchFn(CHROME_OAUTH_TOKEN_URL, {
1163
+ method: "POST",
1164
+ headers: {
1165
+ "Content-Type": "application/x-www-form-urlencoded"
1166
+ },
1167
+ body: body.toString()
1168
+ });
1169
+ if (!res.ok) {
1170
+ const text = await res.text().catch(()=>"");
1171
+ throw new Error(`Chrome token exchange failed (HTTP ${res.status}): ${text || res.statusText}`);
1172
+ }
1173
+ const json = await res.json();
1174
+ if (!json.refresh_token) throw new Error(`Chrome token exchange returned no refresh_token${json.error ? `: ${json.error} \u{2014} ${json.error_description ?? ""}` : ""}. This usually means the authorization code was already used or expired; try the init wizard again.`);
1175
+ return json.refresh_token;
1176
+ }
1177
+ async function obtainChromeRefreshToken(deps, clientId, clientSecret) {
1178
+ const { io } = deps;
1179
+ const authUrl = buildChromeAuthUrl(clientId);
972
1180
  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("");
1181
+ io.log("Open the URL below in your browser, approve the scope, and paste the");
1182
+ io.log("authorization code shown on the page:");
1183
+ io.log("");
1184
+ io.log(` ${authUrl}`);
1185
+ io.log("");
1186
+ const authCode = await requireValue(io, "Authorization code", void 0, {
1187
+ secret: true
1188
+ });
1189
+ return exchangeChromeAuthCode({
1190
+ clientId,
1191
+ clientSecret,
1192
+ authCode,
1193
+ fetchImpl: deps.fetchImpl
1194
+ });
1195
+ }
1196
+ async function collectFirefoxCredentials(deps) {
1197
+ const { io } = deps;
1198
+ io.log("Firefox AMO needs JWT API credentials:");
1199
+ io.log(" 1. Sign in at https://addons.mozilla.org");
1200
+ io.log(" 2. Create API credentials at https://addons.mozilla.org/developers/addon/api/key/");
1201
+ io.log(" 3. Copy the JWT issuer and JWT secret (the secret is only shown once).");
1202
+ io.log("");
1203
+ const addonId = await requireValue(io, "Firefox addon ID", "({uuid} GUID or email-style ID from your addon page)");
1204
+ const jwtIssuer = await requireValue(io, "JWT issuer");
1205
+ const jwtSecret = await requireValue(io, "JWT secret", void 0, {
1206
+ secret: true
1207
+ });
1208
+ return {
1209
+ addonId,
1210
+ jwtIssuer,
1211
+ jwtSecret
1212
+ };
1213
+ }
1214
+ async function collectEdgeCredentials(deps) {
1215
+ const { io } = deps;
1216
+ io.log("Edge Partner Center needs the v1.1 API credentials:");
1217
+ io.log(" 1. Open the Partner Center dashboard: https://partner.microsoft.com/dashboard/microsoftedge/overview");
1218
+ io.log(" 2. Under your extension, go to Settings \u2192 API access \u2192 API v1.1 and generate credentials.");
1219
+ io.log(" 3. Copy the Client ID, API key, and Product ID.");
1220
+ io.log("");
1221
+ const productId = await requireValue(io, "Edge product ID");
1222
+ const clientId = await requireValue(io, "Edge client ID");
1223
+ const apiKey = await requireValue(io, "Edge API key", void 0, {
1224
+ secret: true
1225
+ });
1226
+ return {
1227
+ productId,
1228
+ clientId,
1229
+ apiKey
1230
+ };
1231
+ }
1232
+ const STORE_CHOICES = [
1233
+ {
1234
+ value: "chrome",
1235
+ label: "Chrome Web Store"
1236
+ },
1237
+ {
1238
+ value: "firefox",
1239
+ label: "Firefox AMO (addons.mozilla.org)"
1240
+ },
1241
+ {
1242
+ value: "edge",
1243
+ label: "Edge Add-ons (Partner Center)"
1244
+ }
1245
+ ];
1246
+ async function runInit(deps, options = {}) {
1247
+ const { io } = deps;
1248
+ io.log("");
1249
+ io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} ${colors.underline("extension-deploy \u2014 set up store credentials")}`);
1250
+ io.log("");
1251
+ io.log("This wizard walks you through creating credentials for each store and");
1252
+ io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
1253
+ io.log("");
1254
+ const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
1255
+ 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.");
1256
+ const envLines = [
1257
+ "# Generated by `extension-deploy init`.",
1258
+ "# Do not commit secrets to source control.",
1259
+ ""
1260
+ ];
1261
+ const verified = {};
1262
+ for (const store of stores){
1263
+ io.log("");
983
1264
  io.log(colors.underline(colors.blue(labelFor(store))));
984
1265
  io.log("");
985
1266
  if ("chrome" === store) {
986
1267
  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}`, "");
1268
+ envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_PUBLISHER_ID=${creds.publisherId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
988
1269
  if (!options.skipVerify) {
989
1270
  const verify = deps.verifyChrome ?? verifyChromeCredentials;
990
1271
  verified.chrome = normalizeVerify(await verify(creds));
@@ -1052,121 +1333,6 @@ async function runInit(deps, options = {}) {
1052
1333
  wrote
1053
1334
  };
1054
1335
  }
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.");
1066
- io.log("");
1067
- const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
1068
- const clientId = await requireValue(io, "OAuth Client ID");
1069
- const clientSecret = await requireValue(io, "OAuth Client Secret", void 0, {
1070
- secret: true
1071
- });
1072
- const refreshToken = await obtainChromeRefreshToken(deps, clientId, clientSecret);
1073
- return {
1074
- extensionId,
1075
- clientId,
1076
- clientSecret,
1077
- refreshToken
1078
- };
1079
- }
1080
- function buildChromeAuthUrl(clientId) {
1081
- const params = new URLSearchParams({
1082
- response_type: "code",
1083
- client_id: clientId,
1084
- redirect_uri: CHROME_OOB_REDIRECT,
1085
- scope: CHROME_SCOPE,
1086
- access_type: "offline",
1087
- prompt: "consent"
1088
- });
1089
- return `${CHROME_OAUTH_AUTH_URL}?${params.toString()}`;
1090
- }
1091
- async function exchangeChromeAuthCode(input) {
1092
- const fetchFn = input.fetchImpl ?? fetch;
1093
- const body = new URLSearchParams({
1094
- code: input.authCode,
1095
- client_id: input.clientId,
1096
- client_secret: input.clientSecret,
1097
- redirect_uri: CHROME_OOB_REDIRECT,
1098
- grant_type: "authorization_code"
1099
- });
1100
- const res = await fetchFn(CHROME_OAUTH_TOKEN_URL, {
1101
- method: "POST",
1102
- headers: {
1103
- "Content-Type": "application/x-www-form-urlencoded"
1104
- },
1105
- body: body.toString()
1106
- });
1107
- if (!res.ok) {
1108
- const text = await res.text().catch(()=>"");
1109
- throw new Error(`Chrome token exchange failed (HTTP ${res.status}): ${text || res.statusText}`);
1110
- }
1111
- const json = await res.json();
1112
- if (!json.refresh_token) throw new Error(`Chrome token exchange returned no refresh_token${json.error ? `: ${json.error} \u{2014} ${json.error_description ?? ""}` : ""}. This usually means the authorization code was already used or expired; try the init wizard again.`);
1113
- return json.refresh_token;
1114
- }
1115
- async function obtainChromeRefreshToken(deps, clientId, clientSecret) {
1116
- const { io } = deps;
1117
- const authUrl = buildChromeAuthUrl(clientId);
1118
- io.log("");
1119
- io.log("Open the URL below in your browser, approve the scope, and paste the");
1120
- io.log("authorization code shown on the page:");
1121
- io.log("");
1122
- io.log(` ${authUrl}`);
1123
- io.log("");
1124
- const authCode = await requireValue(io, "Authorization code", void 0, {
1125
- secret: true
1126
- });
1127
- return exchangeChromeAuthCode({
1128
- clientId,
1129
- clientSecret,
1130
- authCode,
1131
- fetchImpl: deps.fetchImpl
1132
- });
1133
- }
1134
- async function collectFirefoxCredentials(deps) {
1135
- const { io } = deps;
1136
- io.log("Firefox AMO needs JWT API credentials:");
1137
- io.log(" 1. Sign in at https://addons.mozilla.org");
1138
- io.log(" 2. Create API credentials at https://addons.mozilla.org/developers/addon/api/key/");
1139
- io.log(" 3. Copy the JWT issuer and JWT secret (the secret is only shown once).");
1140
- io.log("");
1141
- const addonId = await requireValue(io, "Firefox addon ID", "({uuid} GUID or email-style ID from your addon page)");
1142
- const jwtIssuer = await requireValue(io, "JWT issuer");
1143
- const jwtSecret = await requireValue(io, "JWT secret", void 0, {
1144
- secret: true
1145
- });
1146
- return {
1147
- addonId,
1148
- jwtIssuer,
1149
- jwtSecret
1150
- };
1151
- }
1152
- async function collectEdgeCredentials(deps) {
1153
- const { io } = deps;
1154
- io.log("Edge Partner Center needs the v1.1 API credentials:");
1155
- io.log(" 1. Open the Partner Center dashboard: https://partner.microsoft.com/dashboard/microsoftedge/overview");
1156
- io.log(" 2. Under your extension, go to Settings \u2192 API access \u2192 API v1.1 and generate credentials.");
1157
- io.log(" 3. Copy the Client ID, API key, and Product ID.");
1158
- io.log("");
1159
- const productId = await requireValue(io, "Edge product ID");
1160
- const clientId = await requireValue(io, "Edge client ID");
1161
- const apiKey = await requireValue(io, "Edge API key", void 0, {
1162
- secret: true
1163
- });
1164
- return {
1165
- productId,
1166
- clientId,
1167
- apiKey
1168
- };
1169
- }
1170
1336
  function labelFor(store) {
1171
1337
  var _STORE_CHOICES_find;
1172
1338
  return (null == (_STORE_CHOICES_find = STORE_CHOICES.find((c)=>c.value === store)) ? void 0 : _STORE_CHOICES_find.label) ?? String(store);
@@ -1185,15 +1351,6 @@ function resolveEnvPath(cwd, outputPath) {
1185
1351
  const target = outputPath ?? ".env.submit";
1186
1352
  return external_node_path_default.isAbsolute(target) ? target : external_node_path_default.join(cwd, target);
1187
1353
  }
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
1354
  function createStdinIO() {
1198
1355
  const rl = external_node_readline_default.createInterface({
1199
1356
  input: process.stdin,
@@ -1247,7 +1404,13 @@ function createStdinIO() {
1247
1404
  }
1248
1405
  const stdinFs = {
1249
1406
  async writeFile (filePath, content) {
1250
- await promises_default.writeFile(filePath, content, "utf-8");
1407
+ await promises_default.writeFile(filePath, content, {
1408
+ encoding: "utf-8",
1409
+ mode: 384
1410
+ });
1411
+ try {
1412
+ await promises_default.chmod(filePath, 384);
1413
+ } catch {}
1251
1414
  },
1252
1415
  async fileExists (filePath) {
1253
1416
  try {
@@ -1258,173 +1421,12 @@ const stdinFs = {
1258
1421
  }
1259
1422
  }
1260
1423
  };
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
1424
  function writeJsonOutput(filePath, payload) {
1404
1425
  external_node_fs_default.mkdirSync(external_node_path_default.dirname(external_node_path_default.resolve(filePath)), {
1405
1426
  recursive: true
1406
1427
  });
1407
1428
  external_node_fs_default.writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
1408
1429
  }
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
1430
  async function runWatch(flags) {
1429
1431
  const store = flags.watchStore;
1430
1432
  if (!store) {
@@ -1447,7 +1449,8 @@ async function runWatch(flags) {
1447
1449
  clientId: mustEnv("CHROME_CLIENT_ID", flags.chromeClientId),
1448
1450
  clientSecret: mustEnv("CHROME_CLIENT_SECRET", flags.chromeClientSecret),
1449
1451
  refreshToken: mustEnv("CHROME_REFRESH_TOKEN", flags.chromeRefreshToken),
1450
- extensionId: mustEnv("CHROME_EXTENSION_ID", flags.chromeExtensionId)
1452
+ extensionId: mustEnv("CHROME_EXTENSION_ID", flags.chromeExtensionId),
1453
+ publisherId: mustEnv("CHROME_PUBLISHER_ID", flags.chromePublisherId)
1451
1454
  }, {
1452
1455
  pollIntervalMs,
1453
1456
  timeoutMs,
@@ -1520,4 +1523,39 @@ function parseInitStores(raw) {
1520
1523
  if (0 === parsed.length) throw new Error(`--init-stores: expected a comma-separated list of chrome,firefox,edge (got "${raw}")`);
1521
1524
  return parsed;
1522
1525
  }
1526
+ function loadDotEnv() {
1527
+ try {
1528
+ const envPath = external_node_path_default.resolve(process.cwd(), ".env.submit");
1529
+ const content = external_node_fs_default.readFileSync(envPath, "utf-8");
1530
+ for (const line of content.split("\n")){
1531
+ const trimmed = line.trim();
1532
+ if (!trimmed || trimmed.startsWith("#")) continue;
1533
+ const eqIdx = trimmed.indexOf("=");
1534
+ if (-1 === eqIdx) continue;
1535
+ const key = trimmed.slice(0, eqIdx).trim();
1536
+ let value = trimmed.slice(eqIdx + 1).trim();
1537
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1538
+ if (!(key in process.env)) process.env[key] = value;
1539
+ }
1540
+ } catch {}
1541
+ }
1542
+ async function main() {
1543
+ loadDotEnv();
1544
+ const flags = parseArgs(process.argv.slice(2));
1545
+ if (flags.help) {
1546
+ console.log(usage());
1547
+ process.exit(0);
1548
+ }
1549
+ if ("watch" === flags.command) return void await runWatch(flags);
1550
+ if ("init" === flags.command) return void await runInitCommand(flags);
1551
+ const config = resolveConfig(flags);
1552
+ try {
1553
+ const result = await deploy(config);
1554
+ if (flags.outputJson) writeJsonOutput(flags.outputJson, result);
1555
+ if (!result.success) process.exit(1);
1556
+ } catch (err) {
1557
+ logError("deploy", err instanceof Error ? err.message : String(err));
1558
+ process.exit(1);
1559
+ }
1560
+ }
1523
1561
  export { chromeOptionsSchema, deploy, deployConfigSchema, edgeOptionsSchema, firefoxOptionsSchema, getChromeSubmissionEvent, getEdgeSubmissionEvent, getFirefoxSubmissionEvent, main, resolveConfig, validateConfig, verifyChromeCredentials, verifyEdgeCredentials, verifyFirefoxCredentials, watchChromeSubmission, watchEdgeSubmission, watchFirefoxSubmission };