@extension.dev/deploy 0.1.0 → 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/dist/module.mjs CHANGED
@@ -1,6 +1,9 @@
1
+ import external_node_fs_default from "node:fs";
2
+ import external_node_path_default from "node:path";
1
3
  import { z } from "zod";
2
4
  import promises_default from "node:fs/promises";
3
5
  import external_node_crypto_default from "node:crypto";
6
+ import external_node_readline_default from "node:readline";
4
7
  const chromeOptionsSchema = z.object({
5
8
  zip: z.string().min(1),
6
9
  extensionId: z.string().min(1).trim(),
@@ -883,4 +886,576 @@ async function runWatchLoop(_store, probe, options) {
883
886
  await delay(pollInterval);
884
887
  }
885
888
  }
886
- export { chromeOptionsSchema, deploy, deployConfigSchema, edgeOptionsSchema, firefoxOptionsSchema, getChromeSubmissionEvent, getEdgeSubmissionEvent, getFirefoxSubmissionEvent, resolveConfig, validateConfig, verifyChromeCredentials, verifyEdgeCredentials, verifyFirefoxCredentials, watchChromeSubmission, watchEdgeSubmission, watchFirefoxSubmission };
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
+ };
1203
+ function usage() {
1204
+ return `
1205
+ extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
1206
+
1207
+ USAGE
1208
+ extension-deploy [options] submit to one or more stores
1209
+ extension-deploy init [options] interactive credential setup wizard
1210
+ extension-deploy watch --chrome [options] poll an in-flight submission
1211
+ extension-deploy watch --firefox [options]
1212
+ extension-deploy watch --edge [options]
1213
+
1214
+ GLOBAL
1215
+ --dry-run Verify auth without uploading or publishing
1216
+ --output-json <path> Write the DeployResult / WatchEvent as JSON to <path>
1217
+ --help Show this help message
1218
+
1219
+ CHROME
1220
+ --chrome-zip <path> Path to Chrome extension ZIP
1221
+ --chrome-extension-id <id> Chrome extension ID
1222
+ --chrome-client-id <id> OAuth2 client ID
1223
+ --chrome-client-secret <secret> OAuth2 client secret
1224
+ --chrome-refresh-token <token> OAuth2 refresh token
1225
+ --chrome-publish-target <target> "default" or "trustedTesters"
1226
+ --chrome-deploy-percentage <n> Staged rollout percentage (1-100)
1227
+ --chrome-review-exemption Request expedited review
1228
+ --chrome-skip-submit-review Upload only, skip publish
1229
+
1230
+ FIREFOX
1231
+ --firefox-zip <path> Path to Firefox extension ZIP
1232
+ --firefox-sources-zip <path> Path to sources ZIP (optional)
1233
+ --firefox-extension-id <id> Addon GUID or email-style ID
1234
+ --firefox-jwt-issuer <issuer> AMO JWT issuer
1235
+ --firefox-jwt-secret <secret> AMO JWT secret
1236
+ --firefox-channel <channel> "listed" or "unlisted"
1237
+
1238
+ EDGE
1239
+ --edge-zip <path> Path to Edge extension ZIP
1240
+ --edge-product-id <id> Partner Center product ID
1241
+ --edge-client-id <id> Partner Center client ID
1242
+ --edge-api-key <key> Partner Center API key (v1.1)
1243
+ --edge-skip-submit-review Upload only, skip publish
1244
+
1245
+ WATCH
1246
+ --watch-submission-id <id> Required for firefox/edge (version id / operation id)
1247
+ --watch-interval <seconds> Poll interval (default 60)
1248
+ --watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
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
+
1256
+ ENVIRONMENT VARIABLES
1257
+ All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
1258
+ FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
1259
+ A .env.submit file in the current directory is loaded automatically.
1260
+ `.trim();
1261
+ }
1262
+ const BOOLEAN_FLAGS = new Set([
1263
+ "help",
1264
+ "dryRun",
1265
+ "chromeReviewExemption",
1266
+ "chromeSkipSubmitReview",
1267
+ "edgeSkipSubmitReview",
1268
+ "initSkipVerify",
1269
+ "initForce"
1270
+ ]);
1271
+ const INT_FLAGS = new Set([
1272
+ "chromeDeployPercentage",
1273
+ "watchInterval",
1274
+ "watchTimeout"
1275
+ ]);
1276
+ function parseArgs(argv) {
1277
+ const flags = {};
1278
+ let i = 0;
1279
+ if ("watch" === argv[0]) {
1280
+ flags.command = "watch";
1281
+ i = 1;
1282
+ } else if ("init" === argv[0]) {
1283
+ flags.command = "init";
1284
+ i = 1;
1285
+ }
1286
+ while(i < argv.length){
1287
+ const arg = argv[i];
1288
+ if ("--help" === arg || "-h" === arg) {
1289
+ flags.help = true;
1290
+ i++;
1291
+ continue;
1292
+ }
1293
+ if ("--chrome" === arg) {
1294
+ flags.watchStore = "chrome";
1295
+ i++;
1296
+ continue;
1297
+ }
1298
+ if ("--firefox" === arg) {
1299
+ flags.watchStore = "firefox";
1300
+ i++;
1301
+ continue;
1302
+ }
1303
+ if ("--edge" === arg) {
1304
+ flags.watchStore = "edge";
1305
+ i++;
1306
+ continue;
1307
+ }
1308
+ if (!(null == arg ? void 0 : arg.startsWith("--"))) {
1309
+ i++;
1310
+ continue;
1311
+ }
1312
+ const key = arg.slice(2).replace(/-([a-z])/g, (_, c)=>c.toUpperCase());
1313
+ if (BOOLEAN_FLAGS.has(key)) {
1314
+ flags[key] = true;
1315
+ i++;
1316
+ continue;
1317
+ }
1318
+ const next = argv[i + 1];
1319
+ if (null == next || next.startsWith("--")) {
1320
+ flags[key] = true;
1321
+ i++;
1322
+ continue;
1323
+ }
1324
+ flags[key] = INT_FLAGS.has(key) ? parseInt(next, 10) : next;
1325
+ i += 2;
1326
+ }
1327
+ return flags;
1328
+ }
1329
+ function loadDotEnv() {
1330
+ try {
1331
+ const envPath = external_node_path_default.resolve(process.cwd(), ".env.submit");
1332
+ const content = external_node_fs_default.readFileSync(envPath, "utf-8");
1333
+ for (const line of content.split("\n")){
1334
+ const trimmed = line.trim();
1335
+ if (!trimmed || trimmed.startsWith("#")) continue;
1336
+ const eqIdx = trimmed.indexOf("=");
1337
+ if (-1 === eqIdx) continue;
1338
+ const key = trimmed.slice(0, eqIdx).trim();
1339
+ let value = trimmed.slice(eqIdx + 1).trim();
1340
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1341
+ if (!(key in process.env)) process.env[key] = value;
1342
+ }
1343
+ } catch {}
1344
+ }
1345
+ function writeJsonOutput(filePath, payload) {
1346
+ external_node_fs_default.mkdirSync(external_node_path_default.dirname(external_node_path_default.resolve(filePath)), {
1347
+ recursive: true
1348
+ });
1349
+ external_node_fs_default.writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
1350
+ }
1351
+ async function main() {
1352
+ loadDotEnv();
1353
+ const flags = parseArgs(process.argv.slice(2));
1354
+ if (flags.help) {
1355
+ console.log(usage());
1356
+ process.exit(0);
1357
+ }
1358
+ if ("watch" === flags.command) return void await runWatch(flags);
1359
+ if ("init" === flags.command) return void await runInitCommand(flags);
1360
+ const config = resolveConfig(flags);
1361
+ try {
1362
+ const result = await deploy(config);
1363
+ if (flags.outputJson) writeJsonOutput(flags.outputJson, result);
1364
+ if (!result.success) process.exit(1);
1365
+ } catch (err) {
1366
+ console.error(err instanceof Error ? err.message : err);
1367
+ process.exit(1);
1368
+ }
1369
+ }
1370
+ async function runWatch(flags) {
1371
+ const store = flags.watchStore;
1372
+ if (!store) {
1373
+ console.error("watch: one of --chrome, --firefox, or --edge must be provided.");
1374
+ process.exit(2);
1375
+ }
1376
+ const onEvent = (event)=>{
1377
+ const line = `[watch:${event.store}] ${event.status}${event.nativeStatus ? ` (${event.nativeStatus})` : ""}${event.message ? ` \u{2014} ${event.message}` : ""}`;
1378
+ console.log(line);
1379
+ };
1380
+ const pollIntervalMs = (flags.watchInterval ?? 60) * 1000;
1381
+ const timeoutMs = (flags.watchTimeout ?? 3600) * 1000;
1382
+ let terminal;
1383
+ try {
1384
+ terminal = "chrome" === store ? await watchChromeSubmission({
1385
+ clientId: mustEnv("CHROME_CLIENT_ID", flags.chromeClientId),
1386
+ clientSecret: mustEnv("CHROME_CLIENT_SECRET", flags.chromeClientSecret),
1387
+ refreshToken: mustEnv("CHROME_REFRESH_TOKEN", flags.chromeRefreshToken),
1388
+ extensionId: mustEnv("CHROME_EXTENSION_ID", flags.chromeExtensionId)
1389
+ }, {
1390
+ pollIntervalMs,
1391
+ timeoutMs,
1392
+ onEvent
1393
+ }) : "firefox" === store ? await watchFirefoxSubmission({
1394
+ jwtIssuer: mustEnv("FIREFOX_JWT_ISSUER", flags.firefoxJwtIssuer),
1395
+ jwtSecret: mustEnv("FIREFOX_JWT_SECRET", flags.firefoxJwtSecret),
1396
+ extensionId: mustEnv("FIREFOX_EXTENSION_ID", flags.firefoxExtensionId),
1397
+ versionId: mustValue("--watch-submission-id", flags.watchSubmissionId)
1398
+ }, {
1399
+ pollIntervalMs,
1400
+ timeoutMs,
1401
+ onEvent
1402
+ }) : await watchEdgeSubmission({
1403
+ clientId: mustEnv("EDGE_CLIENT_ID", flags.edgeClientId),
1404
+ apiKey: mustEnv("EDGE_API_KEY", flags.edgeApiKey),
1405
+ productId: mustEnv("EDGE_PRODUCT_ID", flags.edgeProductId),
1406
+ operationId: mustValue("--watch-submission-id", flags.watchSubmissionId)
1407
+ }, {
1408
+ pollIntervalMs,
1409
+ timeoutMs,
1410
+ onEvent
1411
+ });
1412
+ } catch (err) {
1413
+ console.error(err instanceof Error ? err.message : err);
1414
+ process.exit(1);
1415
+ }
1416
+ if (flags.outputJson) writeJsonOutput(flags.outputJson, terminal);
1417
+ if ("failed" === terminal.status || "rejected" === terminal.status) process.exit(1);
1418
+ }
1419
+ function mustEnv(envName, flagValue) {
1420
+ const v = flagValue ?? process.env[envName];
1421
+ if (!v) throw new Error(`Missing required credential: ${envName}. Set the env var or pass the matching flag.`);
1422
+ return v;
1423
+ }
1424
+ function mustValue(flagName, value) {
1425
+ if (!value) throw new Error(`Missing required flag: ${flagName}`);
1426
+ return value;
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
+ }
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"}
@@ -1,3 +1,4 @@
1
+ export { main } from "./cli";
1
2
  export { deploy } from "./deploy";
2
3
  export { resolveConfig, validateConfig } from "./config";
3
4
  export type { CliFlags } from "./config";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACzD,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,YAAY,EACV,QAAQ,EACR,aAAa,EACb,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AACnD,YAAY,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,SAAS,CAAC;AACjB,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,GAClB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACzD,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,YAAY,EACV,QAAQ,EACR,aAAa,EACb,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AACnD,YAAY,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,SAAS,CAAC;AACjB,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,GAClB,MAAM,SAAS,CAAC"}
@@ -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"}