@extension.dev/deploy 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,8 +22,38 @@ Deploy browser extensions to Chrome Web Store, Firefox AMO, and Edge Partner Cen
22
22
  npm install @extension.dev/deploy
23
23
  ```
24
24
 
25
+ ## Quick start
26
+
27
+ If you're setting up a new project, run the interactive wizard. It walks you
28
+ through each store's credential acquisition (including auto-generating a
29
+ Chrome refresh token from your OAuth client), runs a verification check
30
+ against each store's API, and writes everything to a `.env.submit` file:
31
+
32
+ ```bash
33
+ npx extension-deploy init
34
+ ```
35
+
36
+ Non-interactive use (e.g. CI setup scripts):
37
+
38
+ ```bash
39
+ npx extension-deploy init \
40
+ --init-stores chrome,firefox \
41
+ --init-output config/.env.submit \
42
+ --init-skip-verify \
43
+ --init-force
44
+ ```
45
+
46
+ After `init` has written `.env.submit`, a full submission is a one-liner:
47
+
48
+ ```bash
49
+ npx extension-deploy --dry-run # validate creds and ZIPs
50
+ npx extension-deploy # actually submit
51
+ ```
52
+
25
53
  ## CLI usage
26
54
 
55
+ If you'd rather skip the wizard and pass credentials directly:
56
+
27
57
  ```bash
28
58
  npx extension-deploy \
29
59
  --chrome-zip dist/chrome.zip \
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=init.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.test.d.ts","sourceRoot":"","sources":["../../__tests__/init.test.ts"],"names":[],"mappings":""}
package/dist/module.js CHANGED
@@ -942,12 +942,329 @@ async function runWatchLoop(_store, probe, options) {
942
942
  await delay(pollInterval);
943
943
  }
944
944
  }
945
+ const STORE_CHOICES = [
946
+ {
947
+ value: "chrome",
948
+ label: "Chrome Web Store"
949
+ },
950
+ {
951
+ value: "firefox",
952
+ label: "Firefox AMO (addons.mozilla.org)"
953
+ },
954
+ {
955
+ value: "edge",
956
+ label: "Edge Add-ons (Partner Center)"
957
+ }
958
+ ];
959
+ const CHROME_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
960
+ const CHROME_OAUTH_AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
961
+ const CHROME_OOB_REDIRECT = "urn:ietf:wg:oauth:2.0:oob";
962
+ const CHROME_SCOPE = "https://www.googleapis.com/auth/chromewebstore";
963
+ async function runInit(deps, options = {}) {
964
+ const { io } = deps;
965
+ io.log("");
966
+ io.log("extension-deploy init \u2014 set up store credentials");
967
+ io.log("");
968
+ io.log("This wizard walks you through creating credentials for each store and");
969
+ io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
970
+ io.log("");
971
+ const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
972
+ 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.");
973
+ const envLines = [
974
+ "# Generated by `extension-deploy init`.",
975
+ "# Do not commit secrets to source control.",
976
+ ""
977
+ ];
978
+ const verified = {};
979
+ for (const store of stores){
980
+ io.log("");
981
+ io.log(`=== ${labelFor(store)} ===`);
982
+ io.log("");
983
+ if ("chrome" === store) {
984
+ const creds = await collectChromeCredentials(deps);
985
+ envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
986
+ if (!options.skipVerify) {
987
+ const verify = deps.verifyChrome ?? verifyChromeCredentials;
988
+ verified.chrome = normalizeVerify(await verify(creds));
989
+ logVerify(io, "chrome", verified.chrome);
990
+ }
991
+ } else if ("firefox" === store) {
992
+ const creds = await collectFirefoxCredentials(deps);
993
+ envLines.push(`FIREFOX_EXTENSION_ID=${creds.addonId}`, `FIREFOX_JWT_ISSUER=${creds.jwtIssuer}`, `FIREFOX_JWT_SECRET=${creds.jwtSecret}`, "");
994
+ if (!options.skipVerify) {
995
+ const verify = deps.verifyFirefox ?? verifyFirefoxCredentials;
996
+ verified.firefox = normalizeVerify(await verify({
997
+ jwtIssuer: creds.jwtIssuer,
998
+ jwtSecret: creds.jwtSecret,
999
+ addonId: creds.addonId
1000
+ }));
1001
+ logVerify(io, "firefox", verified.firefox);
1002
+ }
1003
+ } else {
1004
+ const creds = await collectEdgeCredentials(deps);
1005
+ envLines.push(`EDGE_PRODUCT_ID=${creds.productId}`, `EDGE_CLIENT_ID=${creds.clientId}`, `EDGE_API_KEY=${creds.apiKey}`, "");
1006
+ if (!options.skipVerify) {
1007
+ const verify = deps.verifyEdge ?? verifyEdgeCredentials;
1008
+ verified.edge = normalizeVerify(await verify({
1009
+ clientId: creds.clientId,
1010
+ apiKey: creds.apiKey,
1011
+ productId: creds.productId
1012
+ }));
1013
+ logVerify(io, "edge", verified.edge);
1014
+ }
1015
+ }
1016
+ }
1017
+ const envContent = envLines.join("\n");
1018
+ const envPath = resolveEnvPath(deps.cwd, options.outputPath);
1019
+ const exists = await deps.fileExists(envPath);
1020
+ let wrote = false;
1021
+ if (exists && !options.force) {
1022
+ const overwrite = await deps.io.confirm(`${envPath} already exists. Overwrite?`, false);
1023
+ if (overwrite) {
1024
+ await deps.writeFile(envPath, envContent);
1025
+ wrote = true;
1026
+ } else {
1027
+ io.log("");
1028
+ io.log(`Skipped writing ${envPath}. Credential block:`);
1029
+ io.log("");
1030
+ io.log(envContent);
1031
+ }
1032
+ } else {
1033
+ await deps.writeFile(envPath, envContent);
1034
+ wrote = true;
1035
+ }
1036
+ if (wrote) {
1037
+ io.log("");
1038
+ io.log(`Wrote ${envPath}`);
1039
+ io.log("");
1040
+ io.log("Next steps:");
1041
+ io.log(" - Add .env.submit to .gitignore if it isn't already (it contains secrets).");
1042
+ io.log(" - Run `extension-deploy --dry-run` to sanity-check the full submit flow.");
1043
+ io.log("");
1044
+ }
1045
+ return {
1046
+ stores,
1047
+ envPath,
1048
+ envContent,
1049
+ verified,
1050
+ wrote
1051
+ };
1052
+ }
1053
+ async function collectChromeCredentials(deps) {
1054
+ const { io } = deps;
1055
+ io.log("Chrome Web Store needs OAuth credentials. You'll need:");
1056
+ io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
1057
+ io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
1058
+ io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
1059
+ io.log(" https://console.cloud.google.com/apis/credentials");
1060
+ io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
1061
+ io.log("");
1062
+ io.log("This wizard will take your Client ID + Secret and walk you through");
1063
+ io.log("generating a refresh token automatically.");
1064
+ io.log("");
1065
+ const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
1066
+ const clientId = await requireValue(io, "OAuth Client ID");
1067
+ const clientSecret = await requireValue(io, "OAuth Client Secret", void 0, {
1068
+ secret: true
1069
+ });
1070
+ const refreshToken = await obtainChromeRefreshToken(deps, clientId, clientSecret);
1071
+ return {
1072
+ extensionId,
1073
+ clientId,
1074
+ clientSecret,
1075
+ refreshToken
1076
+ };
1077
+ }
1078
+ function buildChromeAuthUrl(clientId) {
1079
+ const params = new URLSearchParams({
1080
+ response_type: "code",
1081
+ client_id: clientId,
1082
+ redirect_uri: CHROME_OOB_REDIRECT,
1083
+ scope: CHROME_SCOPE,
1084
+ access_type: "offline",
1085
+ prompt: "consent"
1086
+ });
1087
+ return `${CHROME_OAUTH_AUTH_URL}?${params.toString()}`;
1088
+ }
1089
+ async function exchangeChromeAuthCode(input) {
1090
+ const fetchFn = input.fetchImpl ?? fetch;
1091
+ const body = new URLSearchParams({
1092
+ code: input.authCode,
1093
+ client_id: input.clientId,
1094
+ client_secret: input.clientSecret,
1095
+ redirect_uri: CHROME_OOB_REDIRECT,
1096
+ grant_type: "authorization_code"
1097
+ });
1098
+ const res = await fetchFn(CHROME_OAUTH_TOKEN_URL, {
1099
+ method: "POST",
1100
+ headers: {
1101
+ "Content-Type": "application/x-www-form-urlencoded"
1102
+ },
1103
+ body: body.toString()
1104
+ });
1105
+ if (!res.ok) {
1106
+ const text = await res.text().catch(()=>"");
1107
+ throw new Error(`Chrome token exchange failed (HTTP ${res.status}): ${text || res.statusText}`);
1108
+ }
1109
+ const json = await res.json();
1110
+ 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.`);
1111
+ return json.refresh_token;
1112
+ }
1113
+ async function obtainChromeRefreshToken(deps, clientId, clientSecret) {
1114
+ const { io } = deps;
1115
+ const authUrl = buildChromeAuthUrl(clientId);
1116
+ io.log("");
1117
+ io.log("Open the URL below in your browser, approve the scope, and paste the");
1118
+ io.log("authorization code shown on the page:");
1119
+ io.log("");
1120
+ io.log(` ${authUrl}`);
1121
+ io.log("");
1122
+ const authCode = await requireValue(io, "Authorization code", void 0, {
1123
+ secret: true
1124
+ });
1125
+ return exchangeChromeAuthCode({
1126
+ clientId,
1127
+ clientSecret,
1128
+ authCode,
1129
+ fetchImpl: deps.fetchImpl
1130
+ });
1131
+ }
1132
+ async function collectFirefoxCredentials(deps) {
1133
+ const { io } = deps;
1134
+ io.log("Firefox AMO needs JWT API credentials:");
1135
+ io.log(" 1. Sign in at https://addons.mozilla.org");
1136
+ io.log(" 2. Create API credentials at https://addons.mozilla.org/developers/addon/api/key/");
1137
+ io.log(" 3. Copy the JWT issuer and JWT secret (the secret is only shown once).");
1138
+ io.log("");
1139
+ const addonId = await requireValue(io, "Firefox addon ID", "({uuid} GUID or email-style ID from your addon page)");
1140
+ const jwtIssuer = await requireValue(io, "JWT issuer");
1141
+ const jwtSecret = await requireValue(io, "JWT secret", void 0, {
1142
+ secret: true
1143
+ });
1144
+ return {
1145
+ addonId,
1146
+ jwtIssuer,
1147
+ jwtSecret
1148
+ };
1149
+ }
1150
+ async function collectEdgeCredentials(deps) {
1151
+ const { io } = deps;
1152
+ io.log("Edge Partner Center needs the v1.1 API credentials:");
1153
+ io.log(" 1. Open the Partner Center dashboard: https://partner.microsoft.com/dashboard/microsoftedge/overview");
1154
+ io.log(" 2. Under your extension, go to Settings \u2192 API access \u2192 API v1.1 and generate credentials.");
1155
+ io.log(" 3. Copy the Client ID, API key, and Product ID.");
1156
+ io.log("");
1157
+ const productId = await requireValue(io, "Edge product ID");
1158
+ const clientId = await requireValue(io, "Edge client ID");
1159
+ const apiKey = await requireValue(io, "Edge API key", void 0, {
1160
+ secret: true
1161
+ });
1162
+ return {
1163
+ productId,
1164
+ clientId,
1165
+ apiKey
1166
+ };
1167
+ }
1168
+ function labelFor(store) {
1169
+ var _STORE_CHOICES_find;
1170
+ return (null == (_STORE_CHOICES_find = STORE_CHOICES.find((c)=>c.value === store)) ? void 0 : _STORE_CHOICES_find.label) ?? String(store);
1171
+ }
1172
+ function normalizeVerify(result) {
1173
+ return {
1174
+ ok: result.ok,
1175
+ message: result.message
1176
+ };
1177
+ }
1178
+ function logVerify(io, store, record) {
1179
+ const marker = record.ok ? "[ok]" : "[fail]";
1180
+ io.log(` ${marker} verify ${store}: ${record.message}`);
1181
+ }
1182
+ function resolveEnvPath(cwd, outputPath) {
1183
+ const target = outputPath ?? ".env.submit";
1184
+ return external_node_path_default().isAbsolute(target) ? target : external_node_path_default().join(cwd, target);
1185
+ }
1186
+ async function requireValue(io, label, hint, opts) {
1187
+ const question = hint ? `${label} ${hint}:` : `${label}:`;
1188
+ for(let attempt = 0; attempt < 3; attempt++){
1189
+ const answer = (await io.prompt(question, opts)).trim();
1190
+ if (answer) return answer;
1191
+ io.log(` ${label} is required.`);
1192
+ }
1193
+ throw new Error(`init: ${label} is required.`);
1194
+ }
1195
+ const external_node_readline_namespaceObject = require("node:readline");
1196
+ var external_node_readline_default = /*#__PURE__*/ __webpack_require__.n(external_node_readline_namespaceObject);
1197
+ function createStdinIO() {
1198
+ const rl = external_node_readline_default().createInterface({
1199
+ input: process.stdin,
1200
+ output: process.stdout,
1201
+ terminal: false
1202
+ });
1203
+ const lineIter = rl[Symbol.asyncIterator]();
1204
+ async function readLine() {
1205
+ const { value, done } = await lineIter.next();
1206
+ if (done) return "";
1207
+ return value;
1208
+ }
1209
+ function write(chunk) {
1210
+ process.stdout.write(chunk);
1211
+ }
1212
+ return {
1213
+ async prompt (question, opts) {
1214
+ const suffix = (null == opts ? void 0 : opts.default) ? ` [${opts.default}]` : "";
1215
+ const secretNote = (null == opts ? void 0 : opts.secret) ? " (input visible)" : "";
1216
+ write(`${question}${suffix}${secretNote} `);
1217
+ const answer = await readLine();
1218
+ if (0 === answer.length && (null == opts ? void 0 : opts.default)) return opts.default;
1219
+ return answer;
1220
+ },
1221
+ async confirm (question, defaultValue = false) {
1222
+ const suffix = defaultValue ? "[Y/n]" : "[y/N]";
1223
+ write(`${question} ${suffix} `);
1224
+ const raw = (await readLine()).trim().toLowerCase();
1225
+ if ("" === raw) return defaultValue;
1226
+ return "y" === raw || "yes" === raw;
1227
+ },
1228
+ async multiselect (question, choices) {
1229
+ write(`${question}\n`);
1230
+ choices.forEach((choice, idx)=>{
1231
+ write(` ${idx + 1}) ${choice.label}\n`);
1232
+ });
1233
+ write(' Enter comma-separated numbers (e.g. 1,2 or "all"): ');
1234
+ const raw = (await readLine()).trim().toLowerCase();
1235
+ if ("all" === raw) return choices.map((c)=>c.value);
1236
+ const indices = raw.split(/[,\s]+/).map((token)=>Number.parseInt(token, 10)).filter((n)=>Number.isInteger(n) && n >= 1 && n <= choices.length);
1237
+ const unique = Array.from(new Set(indices));
1238
+ return unique.map((i)=>choices[i - 1].value);
1239
+ },
1240
+ log (message) {
1241
+ write(`${message}\n`);
1242
+ },
1243
+ dispose () {
1244
+ rl.close();
1245
+ }
1246
+ };
1247
+ }
1248
+ const stdinFs = {
1249
+ async writeFile (filePath, content) {
1250
+ await promises_default().writeFile(filePath, content, "utf-8");
1251
+ },
1252
+ async fileExists (filePath) {
1253
+ try {
1254
+ await promises_default().access(filePath);
1255
+ return true;
1256
+ } catch {
1257
+ return false;
1258
+ }
1259
+ }
1260
+ };
945
1261
  function usage() {
946
1262
  return `
947
1263
  extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
948
1264
 
949
1265
  USAGE
950
1266
  extension-deploy [options] submit to one or more stores
1267
+ extension-deploy init [options] interactive credential setup wizard
951
1268
  extension-deploy watch --chrome [options] poll an in-flight submission
952
1269
  extension-deploy watch --firefox [options]
953
1270
  extension-deploy watch --edge [options]
@@ -988,6 +1305,12 @@ WATCH
988
1305
  --watch-interval <seconds> Poll interval (default 60)
989
1306
  --watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
990
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
+
991
1314
  ENVIRONMENT VARIABLES
992
1315
  All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
993
1316
  FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
@@ -999,7 +1322,9 @@ const BOOLEAN_FLAGS = new Set([
999
1322
  "dryRun",
1000
1323
  "chromeReviewExemption",
1001
1324
  "chromeSkipSubmitReview",
1002
- "edgeSkipSubmitReview"
1325
+ "edgeSkipSubmitReview",
1326
+ "initSkipVerify",
1327
+ "initForce"
1003
1328
  ]);
1004
1329
  const INT_FLAGS = new Set([
1005
1330
  "chromeDeployPercentage",
@@ -1012,6 +1337,9 @@ function parseArgs(argv) {
1012
1337
  if ("watch" === argv[0]) {
1013
1338
  flags.command = "watch";
1014
1339
  i = 1;
1340
+ } else if ("init" === argv[0]) {
1341
+ flags.command = "init";
1342
+ i = 1;
1015
1343
  }
1016
1344
  while(i < argv.length){
1017
1345
  const arg = argv[i];
@@ -1086,6 +1414,7 @@ async function main() {
1086
1414
  process.exit(0);
1087
1415
  }
1088
1416
  if ("watch" === flags.command) return void await runWatch(flags);
1417
+ if ("init" === flags.command) return void await runInitCommand(flags);
1089
1418
  const config = resolveConfig(flags);
1090
1419
  try {
1091
1420
  const result = await deploy(config);
@@ -1154,6 +1483,39 @@ function mustValue(flagName, value) {
1154
1483
  if (!value) throw new Error(`Missing required flag: ${flagName}`);
1155
1484
  return value;
1156
1485
  }
1486
+ async function runInitCommand(flags) {
1487
+ const stores = parseInitStores(flags.initStores);
1488
+ const io = createStdinIO();
1489
+ try {
1490
+ await runInit({
1491
+ io,
1492
+ cwd: process.cwd(),
1493
+ writeFile: stdinFs.writeFile,
1494
+ fileExists: stdinFs.fileExists
1495
+ }, {
1496
+ stores,
1497
+ outputPath: flags.initOutput,
1498
+ skipVerify: flags.initSkipVerify,
1499
+ force: flags.initForce
1500
+ });
1501
+ } catch (err) {
1502
+ console.error(err instanceof Error ? err.message : err);
1503
+ process.exit(1);
1504
+ } finally{
1505
+ io.dispose();
1506
+ }
1507
+ }
1508
+ function parseInitStores(raw) {
1509
+ if (!raw) return;
1510
+ const allowed = [
1511
+ "chrome",
1512
+ "firefox",
1513
+ "edge"
1514
+ ];
1515
+ const parsed = raw.split(",").map((s)=>s.trim().toLowerCase()).filter((s)=>allowed.includes(s));
1516
+ if (0 === parsed.length) throw new Error(`--init-stores: expected a comma-separated list of chrome,firefox,edge (got "${raw}")`);
1517
+ return parsed;
1518
+ }
1157
1519
  exports.chromeOptionsSchema = __webpack_exports__.chromeOptionsSchema;
1158
1520
  exports.deploy = __webpack_exports__.deploy;
1159
1521
  exports.deployConfigSchema = __webpack_exports__.deployConfigSchema;
package/dist/module.mjs CHANGED
@@ -3,6 +3,7 @@ import external_node_path_default from "node:path";
3
3
  import { z } from "zod";
4
4
  import promises_default from "node:fs/promises";
5
5
  import external_node_crypto_default from "node:crypto";
6
+ import external_node_readline_default from "node:readline";
6
7
  const chromeOptionsSchema = z.object({
7
8
  zip: z.string().min(1),
8
9
  extensionId: z.string().min(1).trim(),
@@ -885,12 +886,327 @@ async function runWatchLoop(_store, probe, options) {
885
886
  await delay(pollInterval);
886
887
  }
887
888
  }
889
+ const STORE_CHOICES = [
890
+ {
891
+ value: "chrome",
892
+ label: "Chrome Web Store"
893
+ },
894
+ {
895
+ value: "firefox",
896
+ label: "Firefox AMO (addons.mozilla.org)"
897
+ },
898
+ {
899
+ value: "edge",
900
+ label: "Edge Add-ons (Partner Center)"
901
+ }
902
+ ];
903
+ const CHROME_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
904
+ const CHROME_OAUTH_AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
905
+ const CHROME_OOB_REDIRECT = "urn:ietf:wg:oauth:2.0:oob";
906
+ const CHROME_SCOPE = "https://www.googleapis.com/auth/chromewebstore";
907
+ async function runInit(deps, options = {}) {
908
+ const { io } = deps;
909
+ io.log("");
910
+ io.log("extension-deploy init \u2014 set up store credentials");
911
+ io.log("");
912
+ io.log("This wizard walks you through creating credentials for each store and");
913
+ io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
914
+ io.log("");
915
+ const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
916
+ 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.");
917
+ const envLines = [
918
+ "# Generated by `extension-deploy init`.",
919
+ "# Do not commit secrets to source control.",
920
+ ""
921
+ ];
922
+ const verified = {};
923
+ for (const store of stores){
924
+ io.log("");
925
+ io.log(`=== ${labelFor(store)} ===`);
926
+ io.log("");
927
+ if ("chrome" === store) {
928
+ const creds = await collectChromeCredentials(deps);
929
+ envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
930
+ if (!options.skipVerify) {
931
+ const verify = deps.verifyChrome ?? verifyChromeCredentials;
932
+ verified.chrome = normalizeVerify(await verify(creds));
933
+ logVerify(io, "chrome", verified.chrome);
934
+ }
935
+ } else if ("firefox" === store) {
936
+ const creds = await collectFirefoxCredentials(deps);
937
+ envLines.push(`FIREFOX_EXTENSION_ID=${creds.addonId}`, `FIREFOX_JWT_ISSUER=${creds.jwtIssuer}`, `FIREFOX_JWT_SECRET=${creds.jwtSecret}`, "");
938
+ if (!options.skipVerify) {
939
+ const verify = deps.verifyFirefox ?? verifyFirefoxCredentials;
940
+ verified.firefox = normalizeVerify(await verify({
941
+ jwtIssuer: creds.jwtIssuer,
942
+ jwtSecret: creds.jwtSecret,
943
+ addonId: creds.addonId
944
+ }));
945
+ logVerify(io, "firefox", verified.firefox);
946
+ }
947
+ } else {
948
+ const creds = await collectEdgeCredentials(deps);
949
+ envLines.push(`EDGE_PRODUCT_ID=${creds.productId}`, `EDGE_CLIENT_ID=${creds.clientId}`, `EDGE_API_KEY=${creds.apiKey}`, "");
950
+ if (!options.skipVerify) {
951
+ const verify = deps.verifyEdge ?? verifyEdgeCredentials;
952
+ verified.edge = normalizeVerify(await verify({
953
+ clientId: creds.clientId,
954
+ apiKey: creds.apiKey,
955
+ productId: creds.productId
956
+ }));
957
+ logVerify(io, "edge", verified.edge);
958
+ }
959
+ }
960
+ }
961
+ const envContent = envLines.join("\n");
962
+ const envPath = resolveEnvPath(deps.cwd, options.outputPath);
963
+ const exists = await deps.fileExists(envPath);
964
+ let wrote = false;
965
+ if (exists && !options.force) {
966
+ const overwrite = await deps.io.confirm(`${envPath} already exists. Overwrite?`, false);
967
+ if (overwrite) {
968
+ await deps.writeFile(envPath, envContent);
969
+ wrote = true;
970
+ } else {
971
+ io.log("");
972
+ io.log(`Skipped writing ${envPath}. Credential block:`);
973
+ io.log("");
974
+ io.log(envContent);
975
+ }
976
+ } else {
977
+ await deps.writeFile(envPath, envContent);
978
+ wrote = true;
979
+ }
980
+ if (wrote) {
981
+ io.log("");
982
+ io.log(`Wrote ${envPath}`);
983
+ io.log("");
984
+ io.log("Next steps:");
985
+ io.log(" - Add .env.submit to .gitignore if it isn't already (it contains secrets).");
986
+ io.log(" - Run `extension-deploy --dry-run` to sanity-check the full submit flow.");
987
+ io.log("");
988
+ }
989
+ return {
990
+ stores,
991
+ envPath,
992
+ envContent,
993
+ verified,
994
+ wrote
995
+ };
996
+ }
997
+ async function collectChromeCredentials(deps) {
998
+ const { io } = deps;
999
+ io.log("Chrome Web Store needs OAuth credentials. You'll need:");
1000
+ io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
1001
+ io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
1002
+ io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
1003
+ io.log(" https://console.cloud.google.com/apis/credentials");
1004
+ io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
1005
+ io.log("");
1006
+ io.log("This wizard will take your Client ID + Secret and walk you through");
1007
+ io.log("generating a refresh token automatically.");
1008
+ io.log("");
1009
+ const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
1010
+ const clientId = await requireValue(io, "OAuth Client ID");
1011
+ const clientSecret = await requireValue(io, "OAuth Client Secret", void 0, {
1012
+ secret: true
1013
+ });
1014
+ const refreshToken = await obtainChromeRefreshToken(deps, clientId, clientSecret);
1015
+ return {
1016
+ extensionId,
1017
+ clientId,
1018
+ clientSecret,
1019
+ refreshToken
1020
+ };
1021
+ }
1022
+ function buildChromeAuthUrl(clientId) {
1023
+ const params = new URLSearchParams({
1024
+ response_type: "code",
1025
+ client_id: clientId,
1026
+ redirect_uri: CHROME_OOB_REDIRECT,
1027
+ scope: CHROME_SCOPE,
1028
+ access_type: "offline",
1029
+ prompt: "consent"
1030
+ });
1031
+ return `${CHROME_OAUTH_AUTH_URL}?${params.toString()}`;
1032
+ }
1033
+ async function exchangeChromeAuthCode(input) {
1034
+ const fetchFn = input.fetchImpl ?? fetch;
1035
+ const body = new URLSearchParams({
1036
+ code: input.authCode,
1037
+ client_id: input.clientId,
1038
+ client_secret: input.clientSecret,
1039
+ redirect_uri: CHROME_OOB_REDIRECT,
1040
+ grant_type: "authorization_code"
1041
+ });
1042
+ const res = await fetchFn(CHROME_OAUTH_TOKEN_URL, {
1043
+ method: "POST",
1044
+ headers: {
1045
+ "Content-Type": "application/x-www-form-urlencoded"
1046
+ },
1047
+ body: body.toString()
1048
+ });
1049
+ if (!res.ok) {
1050
+ const text = await res.text().catch(()=>"");
1051
+ throw new Error(`Chrome token exchange failed (HTTP ${res.status}): ${text || res.statusText}`);
1052
+ }
1053
+ const json = await res.json();
1054
+ 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.`);
1055
+ return json.refresh_token;
1056
+ }
1057
+ async function obtainChromeRefreshToken(deps, clientId, clientSecret) {
1058
+ const { io } = deps;
1059
+ const authUrl = buildChromeAuthUrl(clientId);
1060
+ io.log("");
1061
+ io.log("Open the URL below in your browser, approve the scope, and paste the");
1062
+ io.log("authorization code shown on the page:");
1063
+ io.log("");
1064
+ io.log(` ${authUrl}`);
1065
+ io.log("");
1066
+ const authCode = await requireValue(io, "Authorization code", void 0, {
1067
+ secret: true
1068
+ });
1069
+ return exchangeChromeAuthCode({
1070
+ clientId,
1071
+ clientSecret,
1072
+ authCode,
1073
+ fetchImpl: deps.fetchImpl
1074
+ });
1075
+ }
1076
+ async function collectFirefoxCredentials(deps) {
1077
+ const { io } = deps;
1078
+ io.log("Firefox AMO needs JWT API credentials:");
1079
+ io.log(" 1. Sign in at https://addons.mozilla.org");
1080
+ io.log(" 2. Create API credentials at https://addons.mozilla.org/developers/addon/api/key/");
1081
+ io.log(" 3. Copy the JWT issuer and JWT secret (the secret is only shown once).");
1082
+ io.log("");
1083
+ const addonId = await requireValue(io, "Firefox addon ID", "({uuid} GUID or email-style ID from your addon page)");
1084
+ const jwtIssuer = await requireValue(io, "JWT issuer");
1085
+ const jwtSecret = await requireValue(io, "JWT secret", void 0, {
1086
+ secret: true
1087
+ });
1088
+ return {
1089
+ addonId,
1090
+ jwtIssuer,
1091
+ jwtSecret
1092
+ };
1093
+ }
1094
+ async function collectEdgeCredentials(deps) {
1095
+ const { io } = deps;
1096
+ io.log("Edge Partner Center needs the v1.1 API credentials:");
1097
+ io.log(" 1. Open the Partner Center dashboard: https://partner.microsoft.com/dashboard/microsoftedge/overview");
1098
+ io.log(" 2. Under your extension, go to Settings \u2192 API access \u2192 API v1.1 and generate credentials.");
1099
+ io.log(" 3. Copy the Client ID, API key, and Product ID.");
1100
+ io.log("");
1101
+ const productId = await requireValue(io, "Edge product ID");
1102
+ const clientId = await requireValue(io, "Edge client ID");
1103
+ const apiKey = await requireValue(io, "Edge API key", void 0, {
1104
+ secret: true
1105
+ });
1106
+ return {
1107
+ productId,
1108
+ clientId,
1109
+ apiKey
1110
+ };
1111
+ }
1112
+ function labelFor(store) {
1113
+ var _STORE_CHOICES_find;
1114
+ return (null == (_STORE_CHOICES_find = STORE_CHOICES.find((c)=>c.value === store)) ? void 0 : _STORE_CHOICES_find.label) ?? String(store);
1115
+ }
1116
+ function normalizeVerify(result) {
1117
+ return {
1118
+ ok: result.ok,
1119
+ message: result.message
1120
+ };
1121
+ }
1122
+ function logVerify(io, store, record) {
1123
+ const marker = record.ok ? "[ok]" : "[fail]";
1124
+ io.log(` ${marker} verify ${store}: ${record.message}`);
1125
+ }
1126
+ function resolveEnvPath(cwd, outputPath) {
1127
+ const target = outputPath ?? ".env.submit";
1128
+ return external_node_path_default.isAbsolute(target) ? target : external_node_path_default.join(cwd, target);
1129
+ }
1130
+ async function requireValue(io, label, hint, opts) {
1131
+ const question = hint ? `${label} ${hint}:` : `${label}:`;
1132
+ for(let attempt = 0; attempt < 3; attempt++){
1133
+ const answer = (await io.prompt(question, opts)).trim();
1134
+ if (answer) return answer;
1135
+ io.log(` ${label} is required.`);
1136
+ }
1137
+ throw new Error(`init: ${label} is required.`);
1138
+ }
1139
+ function createStdinIO() {
1140
+ const rl = external_node_readline_default.createInterface({
1141
+ input: process.stdin,
1142
+ output: process.stdout,
1143
+ terminal: false
1144
+ });
1145
+ const lineIter = rl[Symbol.asyncIterator]();
1146
+ async function readLine() {
1147
+ const { value, done } = await lineIter.next();
1148
+ if (done) return "";
1149
+ return value;
1150
+ }
1151
+ function write(chunk) {
1152
+ process.stdout.write(chunk);
1153
+ }
1154
+ return {
1155
+ async prompt (question, opts) {
1156
+ const suffix = (null == opts ? void 0 : opts.default) ? ` [${opts.default}]` : "";
1157
+ const secretNote = (null == opts ? void 0 : opts.secret) ? " (input visible)" : "";
1158
+ write(`${question}${suffix}${secretNote} `);
1159
+ const answer = await readLine();
1160
+ if (0 === answer.length && (null == opts ? void 0 : opts.default)) return opts.default;
1161
+ return answer;
1162
+ },
1163
+ async confirm (question, defaultValue = false) {
1164
+ const suffix = defaultValue ? "[Y/n]" : "[y/N]";
1165
+ write(`${question} ${suffix} `);
1166
+ const raw = (await readLine()).trim().toLowerCase();
1167
+ if ("" === raw) return defaultValue;
1168
+ return "y" === raw || "yes" === raw;
1169
+ },
1170
+ async multiselect (question, choices) {
1171
+ write(`${question}\n`);
1172
+ choices.forEach((choice, idx)=>{
1173
+ write(` ${idx + 1}) ${choice.label}\n`);
1174
+ });
1175
+ write(' Enter comma-separated numbers (e.g. 1,2 or "all"): ');
1176
+ const raw = (await readLine()).trim().toLowerCase();
1177
+ if ("all" === raw) return choices.map((c)=>c.value);
1178
+ const indices = raw.split(/[,\s]+/).map((token)=>Number.parseInt(token, 10)).filter((n)=>Number.isInteger(n) && n >= 1 && n <= choices.length);
1179
+ const unique = Array.from(new Set(indices));
1180
+ return unique.map((i)=>choices[i - 1].value);
1181
+ },
1182
+ log (message) {
1183
+ write(`${message}\n`);
1184
+ },
1185
+ dispose () {
1186
+ rl.close();
1187
+ }
1188
+ };
1189
+ }
1190
+ const stdinFs = {
1191
+ async writeFile (filePath, content) {
1192
+ await promises_default.writeFile(filePath, content, "utf-8");
1193
+ },
1194
+ async fileExists (filePath) {
1195
+ try {
1196
+ await promises_default.access(filePath);
1197
+ return true;
1198
+ } catch {
1199
+ return false;
1200
+ }
1201
+ }
1202
+ };
888
1203
  function usage() {
889
1204
  return `
890
1205
  extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
891
1206
 
892
1207
  USAGE
893
1208
  extension-deploy [options] submit to one or more stores
1209
+ extension-deploy init [options] interactive credential setup wizard
894
1210
  extension-deploy watch --chrome [options] poll an in-flight submission
895
1211
  extension-deploy watch --firefox [options]
896
1212
  extension-deploy watch --edge [options]
@@ -931,6 +1247,12 @@ WATCH
931
1247
  --watch-interval <seconds> Poll interval (default 60)
932
1248
  --watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
933
1249
 
1250
+ INIT
1251
+ --init-stores <list> Comma-separated store list (skip picker)
1252
+ --init-output <path> Destination file (default .env.submit)
1253
+ --init-skip-verify Don't run verifyCredentials after collection
1254
+ --init-force Overwrite existing .env.submit without asking
1255
+
934
1256
  ENVIRONMENT VARIABLES
935
1257
  All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
936
1258
  FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
@@ -942,7 +1264,9 @@ const BOOLEAN_FLAGS = new Set([
942
1264
  "dryRun",
943
1265
  "chromeReviewExemption",
944
1266
  "chromeSkipSubmitReview",
945
- "edgeSkipSubmitReview"
1267
+ "edgeSkipSubmitReview",
1268
+ "initSkipVerify",
1269
+ "initForce"
946
1270
  ]);
947
1271
  const INT_FLAGS = new Set([
948
1272
  "chromeDeployPercentage",
@@ -955,6 +1279,9 @@ function parseArgs(argv) {
955
1279
  if ("watch" === argv[0]) {
956
1280
  flags.command = "watch";
957
1281
  i = 1;
1282
+ } else if ("init" === argv[0]) {
1283
+ flags.command = "init";
1284
+ i = 1;
958
1285
  }
959
1286
  while(i < argv.length){
960
1287
  const arg = argv[i];
@@ -1029,6 +1356,7 @@ async function main() {
1029
1356
  process.exit(0);
1030
1357
  }
1031
1358
  if ("watch" === flags.command) return void await runWatch(flags);
1359
+ if ("init" === flags.command) return void await runInitCommand(flags);
1032
1360
  const config = resolveConfig(flags);
1033
1361
  try {
1034
1362
  const result = await deploy(config);
@@ -1097,4 +1425,37 @@ function mustValue(flagName, value) {
1097
1425
  if (!value) throw new Error(`Missing required flag: ${flagName}`);
1098
1426
  return value;
1099
1427
  }
1428
+ async function runInitCommand(flags) {
1429
+ const stores = parseInitStores(flags.initStores);
1430
+ const io = createStdinIO();
1431
+ try {
1432
+ await runInit({
1433
+ io,
1434
+ cwd: process.cwd(),
1435
+ writeFile: stdinFs.writeFile,
1436
+ fileExists: stdinFs.fileExists
1437
+ }, {
1438
+ stores,
1439
+ outputPath: flags.initOutput,
1440
+ skipVerify: flags.initSkipVerify,
1441
+ force: flags.initForce
1442
+ });
1443
+ } catch (err) {
1444
+ console.error(err instanceof Error ? err.message : err);
1445
+ process.exit(1);
1446
+ } finally{
1447
+ io.dispose();
1448
+ }
1449
+ }
1450
+ function parseInitStores(raw) {
1451
+ if (!raw) return;
1452
+ const allowed = [
1453
+ "chrome",
1454
+ "firefox",
1455
+ "edge"
1456
+ ];
1457
+ const parsed = raw.split(",").map((s)=>s.trim().toLowerCase()).filter((s)=>allowed.includes(s));
1458
+ if (0 === parsed.length) throw new Error(`--init-stores: expected a comma-separated list of chrome,firefox,edge (got "${raw}")`);
1459
+ return parsed;
1460
+ }
1100
1461
  export { chromeOptionsSchema, deploy, deployConfigSchema, edgeOptionsSchema, firefoxOptionsSchema, getChromeSubmissionEvent, getEdgeSubmissionEvent, getFirefoxSubmissionEvent, main, resolveConfig, validateConfig, verifyChromeCredentials, verifyEdgeCredentials, verifyFirefoxCredentials, watchChromeSubmission, watchEdgeSubmission, watchFirefoxSubmission };
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AA2EzC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8D3D;AAiCD,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA4B1C"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAsFzC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiE3D;AAiCD,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAiC1C"}
@@ -34,8 +34,8 @@ export interface CliFlags {
34
34
  dryRun: boolean;
35
35
  /** Path to write the final DeployResult as JSON (for CI integration). */
36
36
  outputJson: string;
37
- /** Subcommand: "deploy" (default) or "watch". */
38
- command: "deploy" | "watch";
37
+ /** Subcommand: "deploy" (default), "watch", or "init". */
38
+ command: "deploy" | "watch" | "init";
39
39
  /** Store targeted by the watch subcommand. */
40
40
  watchStore: "chrome" | "firefox" | "edge";
41
41
  /** Opaque submission ID (firefox version id, edge publish operation id). */
@@ -64,5 +64,13 @@ export interface CliFlags {
64
64
  edgeClientId: string;
65
65
  edgeApiKey: string;
66
66
  edgeSkipSubmitReview: boolean;
67
+ /** Comma-separated store list for non-interactive init (e.g. "chrome,firefox"). */
68
+ initStores: string;
69
+ /** Target file path for init (default ".env.submit"). */
70
+ initOutput: string;
71
+ /** Skip running verifyCredentials after collecting values. */
72
+ initSkipVerify: boolean;
73
+ /** Overwrite an existing .env.submit without prompting. */
74
+ initForce: boolean;
67
75
  }
68
76
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAEhE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,GACvB,YAAY,CAiDd;AA2CD,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB/C;AAiBD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC5B,8CAA8C;IAC9C,UAAU,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IAC1C,4EAA4E;IAC5E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,sBAAsB,EAAE,OAAO,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,oBAAoB,EAAE,OAAO,CAAC;CAC/B"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAEhE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,GACvB,YAAY,CAiDd;AA2CD,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB/C;AAiBD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,OAAO,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IACrC,8CAA8C;IAC9C,UAAU,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IAC1C,4EAA4E;IAC5E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,sBAAsB,EAAE,OAAO,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,cAAc,EAAE,OAAO,CAAC;IACxB,2DAA2D;IAC3D,SAAS,EAAE,OAAO,CAAC;CACpB"}
@@ -0,0 +1,21 @@
1
+ import type { InitIO } from "./init";
2
+ /**
3
+ * Production stdin-based IO for the init wizard.
4
+ *
5
+ * We consume stdin via a readline async iterator instead of rl.question().
6
+ * Under Node 23, question() against a non-TTY stdin hangs on the second
7
+ * sequential call — the line iterator works consistently for both TTY and
8
+ * piped stdin, which matters for CI / test / shell-pipeline use.
9
+ *
10
+ * We intentionally don't mask echo for secret prompts. The _writeToOutput
11
+ * trick is fragile and the downside is small: users on their own terminal.
12
+ * A note is printed so users aren't surprised.
13
+ */
14
+ export declare function createStdinIO(): InitIO & {
15
+ dispose: () => void;
16
+ };
17
+ export declare const stdinFs: {
18
+ writeFile(filePath: string, content: string): Promise<void>;
19
+ fileExists(filePath: string): Promise<boolean>;
20
+ };
21
+ //# sourceMappingURL=init-io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-io.d.ts","sourceRoot":"","sources":["../../src/init-io.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,QAAQ,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CA4DhE;AAED,eAAO,MAAM,OAAO;wBACQ,MAAM,WAAW,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;yBAGtC,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAQrD,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { verifyChromeCredentials } from "./chrome";
2
+ import { verifyFirefoxCredentials } from "./firefox";
3
+ import { verifyEdgeCredentials } from "./edge";
4
+ export type InitStoreKey = "chrome" | "firefox" | "edge";
5
+ export interface InitIO {
6
+ prompt(question: string, opts?: {
7
+ secret?: boolean;
8
+ default?: string;
9
+ }): Promise<string>;
10
+ confirm(question: string, defaultValue?: boolean): Promise<boolean>;
11
+ multiselect(question: string, choices: readonly {
12
+ value: InitStoreKey;
13
+ label: string;
14
+ }[]): Promise<InitStoreKey[]>;
15
+ log(message: string): void;
16
+ }
17
+ export interface InitDeps {
18
+ io: InitIO;
19
+ cwd: string;
20
+ writeFile: (filePath: string, content: string) => Promise<void>;
21
+ fileExists: (filePath: string) => Promise<boolean>;
22
+ fetchImpl?: typeof fetch;
23
+ verifyChrome?: typeof verifyChromeCredentials;
24
+ verifyFirefox?: typeof verifyFirefoxCredentials;
25
+ verifyEdge?: typeof verifyEdgeCredentials;
26
+ }
27
+ export interface InitOptions {
28
+ /** Skip the interactive store picker. */
29
+ stores?: InitStoreKey[];
30
+ /** Skip running verifyCredentials for each store after collecting creds. */
31
+ skipVerify?: boolean;
32
+ /** Overwrite .env.submit without prompting. */
33
+ force?: boolean;
34
+ /** Destination file (default ".env.submit"). Relative to cwd if not absolute. */
35
+ outputPath?: string;
36
+ }
37
+ export interface StoreVerifyRecord {
38
+ ok: boolean;
39
+ message: string;
40
+ }
41
+ export interface InitResult {
42
+ stores: InitStoreKey[];
43
+ envPath: string;
44
+ envContent: string;
45
+ verified: Partial<Record<InitStoreKey, StoreVerifyRecord>>;
46
+ wrote: boolean;
47
+ }
48
+ export declare function runInit(deps: InitDeps, options?: InitOptions): Promise<InitResult>;
49
+ export interface ChromeCredentials {
50
+ extensionId: string;
51
+ clientId: string;
52
+ clientSecret: string;
53
+ refreshToken: string;
54
+ }
55
+ export declare function collectChromeCredentials(deps: InitDeps): Promise<ChromeCredentials>;
56
+ export declare function buildChromeAuthUrl(clientId: string): string;
57
+ export declare function exchangeChromeAuthCode(input: {
58
+ clientId: string;
59
+ clientSecret: string;
60
+ authCode: string;
61
+ fetchImpl?: typeof fetch;
62
+ }): Promise<string>;
63
+ export interface FirefoxCredentials {
64
+ addonId: string;
65
+ jwtIssuer: string;
66
+ jwtSecret: string;
67
+ }
68
+ export declare function collectFirefoxCredentials(deps: InitDeps): Promise<FirefoxCredentials>;
69
+ export interface EdgeCredentials {
70
+ productId: string;
71
+ clientId: string;
72
+ apiKey: string;
73
+ }
74
+ export declare function collectEdgeCredentials(deps: InitDeps): Promise<EdgeCredentials>;
75
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/init.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "ci",
14
14
  "automation"
15
15
  ],
16
- "version": "0.1.1",
16
+ "version": "0.2.0",
17
17
  "repository": {
18
18
  "type": "git",
19
19
  "url": "git+https://github.com/extensiondev/deploy.git"