@extension.dev/deploy 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +34 -1
- package/dist/module.js +500 -340
- package/dist/module.mjs +498 -338
- package/dist/src/chrome.d.ts +19 -0
- package/dist/src/chrome.d.ts.map +1 -1
- package/dist/src/cli-commands.d.ts +5 -0
- package/dist/src/cli-commands.d.ts.map +1 -0
- package/dist/src/cli-parse.d.ts +3 -0
- package/dist/src/cli-parse.d.ts.map +1 -0
- package/dist/src/cli-usage.d.ts +2 -0
- package/dist/src/cli-usage.d.ts.map +1 -0
- package/dist/src/cli.d.ts +1 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/config.d.ts +6 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/deploy.d.ts.map +1 -1
- package/dist/src/edge.d.ts.map +1 -1
- package/dist/src/firefox-api.d.ts +52 -0
- package/dist/src/firefox-api.d.ts.map +1 -0
- package/dist/src/firefox.d.ts +1 -24
- package/dist/src/firefox.d.ts.map +1 -1
- package/dist/src/init-chrome.d.ts +16 -0
- package/dist/src/init-chrome.d.ts.map +1 -0
- package/dist/src/init-edge.d.ts +8 -0
- package/dist/src/init-edge.d.ts.map +1 -0
- package/dist/src/init-firefox.d.ts +8 -0
- package/dist/src/init-firefox.d.ts.map +1 -0
- package/dist/src/init-io.d.ts.map +1 -1
- package/dist/src/init-prompt.d.ts +5 -0
- package/dist/src/init-prompt.d.ts.map +1 -0
- package/dist/src/init.d.ts +3 -26
- package/dist/src/init.d.ts.map +1 -1
- package/dist/src/types.d.ts +49 -5
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/fetch.d.ts +2 -2
- package/dist/src/utils/fetch.d.ts.map +1 -1
- package/dist/src/watch.d.ts.map +1 -1
- package/package.json +18 -8
- package/dist/__tests__/cli.test.d.ts +0 -2
- package/dist/__tests__/cli.test.d.ts.map +0 -1
- package/dist/__tests__/config.test.d.ts +0 -2
- package/dist/__tests__/config.test.d.ts.map +0 -1
- package/dist/__tests__/deploy.test.d.ts +0 -2
- package/dist/__tests__/deploy.test.d.ts.map +0 -1
- package/dist/__tests__/dry-run.test.d.ts +0 -2
- package/dist/__tests__/dry-run.test.d.ts.map +0 -1
- package/dist/__tests__/init.test.d.ts +0 -2
- package/dist/__tests__/init.test.d.ts.map +0 -1
- package/dist/__tests__/types.test.d.ts +0 -2
- package/dist/__tests__/types.test.d.ts.map +0 -1
- package/dist/__tests__/utils.test.d.ts +0 -2
- package/dist/__tests__/utils.test.d.ts.map +0 -1
- package/dist/__tests__/verify.test.d.ts +0 -2
- package/dist/__tests__/verify.test.d.ts.map +0 -1
- package/dist/__tests__/watch.test.d.ts +0 -2
- package/dist/__tests__/watch.test.d.ts.map +0 -1
package/dist/module.js
CHANGED
|
@@ -68,18 +68,31 @@ const chromeOptionsSchema = external_zod_namespaceObject.z.object({
|
|
|
68
68
|
]).default("default"),
|
|
69
69
|
deployPercentage: external_zod_namespaceObject.z.number().int().min(1).max(100).optional(),
|
|
70
70
|
reviewExemption: external_zod_namespaceObject.z.boolean().default(false),
|
|
71
|
-
skipSubmitReview: external_zod_namespaceObject.z.boolean().default(false)
|
|
71
|
+
skipSubmitReview: external_zod_namespaceObject.z.boolean().default(false),
|
|
72
|
+
publisherId: external_zod_namespaceObject.z.string().trim().optional(),
|
|
73
|
+
apiVersion: external_zod_namespaceObject.z["enum"]([
|
|
74
|
+
"v1.1",
|
|
75
|
+
"v2"
|
|
76
|
+
]).optional()
|
|
72
77
|
});
|
|
73
78
|
const firefoxOptionsSchema = external_zod_namespaceObject.z.object({
|
|
74
79
|
zip: external_zod_namespaceObject.z.string().min(1),
|
|
75
80
|
sourcesZip: external_zod_namespaceObject.z.string().min(1).optional(),
|
|
76
|
-
extensionId: external_zod_namespaceObject.z.string().
|
|
81
|
+
extensionId: external_zod_namespaceObject.z.string().trim().default(""),
|
|
77
82
|
jwtIssuer: external_zod_namespaceObject.z.string().min(1).trim(),
|
|
78
83
|
jwtSecret: external_zod_namespaceObject.z.string().min(1).trim(),
|
|
79
84
|
channel: external_zod_namespaceObject.z["enum"]([
|
|
80
85
|
"listed",
|
|
81
86
|
"unlisted"
|
|
82
87
|
]).default("listed")
|
|
88
|
+
}).superRefine((val, ctx)=>{
|
|
89
|
+
if ("listed" === val.channel && 0 === val.extensionId.length) ctx.addIssue({
|
|
90
|
+
code: external_zod_namespaceObject.z.ZodIssueCode.custom,
|
|
91
|
+
path: [
|
|
92
|
+
"extensionId"
|
|
93
|
+
],
|
|
94
|
+
message: 'Listed Firefox submissions require an existing add-on GUID. Use channel "unlisted" for a first submission.'
|
|
95
|
+
});
|
|
83
96
|
});
|
|
84
97
|
const edgeOptionsSchema = external_zod_namespaceObject.z.object({
|
|
85
98
|
zip: external_zod_namespaceObject.z.string().min(1),
|
|
@@ -110,7 +123,9 @@ function resolveConfig(flags) {
|
|
|
110
123
|
publishTarget: flags.chromePublishTarget ?? envStr("CHROME_PUBLISH_TARGET"),
|
|
111
124
|
deployPercentage: flags.chromeDeployPercentage ?? envInt("CHROME_DEPLOY_PERCENTAGE"),
|
|
112
125
|
reviewExemption: flags.chromeReviewExemption ?? envBool("CHROME_REVIEW_EXEMPTION"),
|
|
113
|
-
skipSubmitReview: flags.chromeSkipSubmitReview ?? envBool("CHROME_SKIP_SUBMIT_REVIEW")
|
|
126
|
+
skipSubmitReview: flags.chromeSkipSubmitReview ?? envBool("CHROME_SKIP_SUBMIT_REVIEW"),
|
|
127
|
+
publisherId: flags.chromePublisherId ?? envStr("CHROME_PUBLISHER_ID"),
|
|
128
|
+
apiVersion: flags.chromeApiVersion ?? envStr("CHROME_API_VERSION")
|
|
114
129
|
},
|
|
115
130
|
firefox: null == firefoxZip ? void 0 : {
|
|
116
131
|
zip: firefoxZip,
|
|
@@ -137,6 +152,7 @@ const SETUP_HINTS = {
|
|
|
137
152
|
CHROME_CLIENT_ID: "Create OAuth 2.0 credentials at https://console.cloud.google.com/apis/credentials",
|
|
138
153
|
CHROME_CLIENT_SECRET: "Generated alongside the Client ID in the Google Cloud Console.",
|
|
139
154
|
CHROME_REFRESH_TOKEN: "Obtain by completing the OAuth consent flow. See the Chrome Web Store API docs.",
|
|
155
|
+
CHROME_PUBLISHER_ID: "The publisher UUID from your Chrome Web Store dev console URL (chrome.google.com/webstore/devconsole/<UUID>). Required for the v2 API.",
|
|
140
156
|
CHROME_ZIP: "Path to the .zip file built for Chrome Web Store upload.",
|
|
141
157
|
FIREFOX_EXTENSION_ID: "The addon GUID ({uuid} format) or email-style ID from your addon's AMO page.",
|
|
142
158
|
FIREFOX_JWT_ISSUER: "API key from https://addons.mozilla.org/developers/addon/api/key/",
|
|
@@ -179,16 +195,24 @@ function envInt(name) {
|
|
|
179
195
|
}
|
|
180
196
|
const promises_namespaceObject = require("node:fs/promises");
|
|
181
197
|
var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
|
|
182
|
-
|
|
183
|
-
|
|
198
|
+
const DEFAULT_TIMEOUT_MS = 60000;
|
|
199
|
+
function withTimeout(init, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
200
|
+
if (null == init ? void 0 : init.signal) return init;
|
|
201
|
+
return {
|
|
202
|
+
...init || {},
|
|
203
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
async function fetchJson(url, init, timeoutMs) {
|
|
207
|
+
const res = await fetch(url, withTimeout(init, timeoutMs));
|
|
184
208
|
if (!res.ok) {
|
|
185
209
|
const body = await res.text().catch(()=>"");
|
|
186
210
|
throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
|
|
187
211
|
}
|
|
188
212
|
return res.json();
|
|
189
213
|
}
|
|
190
|
-
async function fetchRaw(url, init) {
|
|
191
|
-
const res = await fetch(url, init);
|
|
214
|
+
async function fetchRaw(url, init, timeoutMs) {
|
|
215
|
+
const res = await fetch(url, withTimeout(init, timeoutMs));
|
|
192
216
|
if (!res.ok) {
|
|
193
217
|
const body = await res.text().catch(()=>"");
|
|
194
218
|
throw new Error(`HTTP ${res.status} ${res.statusText} \u{2014} ${(null == init ? void 0 : init.method) ?? "GET"} ${url}\n${body}`);
|
|
@@ -241,10 +265,26 @@ function logDryStep(scope, step, details) {
|
|
|
241
265
|
for (const [key, value] of Object.entries(details))if (null != value) console.log(labelLine(key, value));
|
|
242
266
|
}
|
|
243
267
|
}
|
|
268
|
+
const UPLOAD_TIMEOUT_MS = 120000;
|
|
269
|
+
const PUBLISH_TIMEOUT_MS = 60000;
|
|
244
270
|
const CWS_OAUTH_URL = "https://oauth2.googleapis.com/token";
|
|
245
271
|
const CWS_UPLOAD_BASE = "https://www.googleapis.com/upload/chromewebstore/v1.1/items";
|
|
246
272
|
const CWS_PUBLISH_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
|
|
247
273
|
const CWS_ITEMS_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
|
|
274
|
+
const CWS_V2_BASE = "https://chromewebstore.googleapis.com/v2";
|
|
275
|
+
const CWS_V2_UPLOAD_BASE = "https://chromewebstore.googleapis.com/upload/v2";
|
|
276
|
+
function resolveChromeApiVersion(opts) {
|
|
277
|
+
if ("v1.1" === opts.apiVersion || "v2" === opts.apiVersion) return opts.apiVersion;
|
|
278
|
+
return String(opts.publisherId || "").trim() ? "v2" : "v1.1";
|
|
279
|
+
}
|
|
280
|
+
function v2ItemName(publisherId, extensionId) {
|
|
281
|
+
return `publishers/${encodeURIComponent(publisherId)}/items/${encodeURIComponent(extensionId)}`;
|
|
282
|
+
}
|
|
283
|
+
function requirePublisherId(publisherId, op) {
|
|
284
|
+
const id = String(publisherId || "").trim();
|
|
285
|
+
if (!id) throw new Error(`Chrome ${op} on the v2 API needs a publisher ID (the UUID from your Chrome Web Store dev console URL).`);
|
|
286
|
+
return id;
|
|
287
|
+
}
|
|
248
288
|
function chromeStoreUrl(extensionId) {
|
|
249
289
|
return `https://chromewebstore.google.com/detail/${extensionId}`;
|
|
250
290
|
}
|
|
@@ -263,16 +303,28 @@ async function authenticate(clientId, clientSecret, refreshToken) {
|
|
|
263
303
|
}
|
|
264
304
|
});
|
|
265
305
|
}
|
|
266
|
-
async function chrome_upload(extensionId, zipPath, token) {
|
|
306
|
+
async function chrome_upload(extensionId, zipPath, token, ctx) {
|
|
267
307
|
const body = await promises_default().readFile(zipPath);
|
|
268
|
-
|
|
269
|
-
|
|
308
|
+
let url;
|
|
309
|
+
let method;
|
|
310
|
+
const headers = {
|
|
311
|
+
Authorization: `${token.token_type} ${token.access_token}`,
|
|
312
|
+
"Content-Type": "application/zip"
|
|
313
|
+
};
|
|
314
|
+
if ("v2" === ctx.apiVersion) {
|
|
315
|
+
const pub = requirePublisherId(ctx.publisherId, "upload");
|
|
316
|
+
url = `${CWS_V2_UPLOAD_BASE}/${v2ItemName(pub, extensionId)}:upload`;
|
|
317
|
+
method = "POST";
|
|
318
|
+
} else {
|
|
319
|
+
url = `${CWS_UPLOAD_BASE}/${extensionId}`;
|
|
320
|
+
method = "PUT";
|
|
321
|
+
headers["x-goog-api-version"] = "2";
|
|
322
|
+
}
|
|
323
|
+
const res = await fetch(url, {
|
|
324
|
+
method,
|
|
270
325
|
body,
|
|
271
|
-
headers
|
|
272
|
-
|
|
273
|
-
"x-goog-api-version": "2",
|
|
274
|
-
"Content-Type": "application/zip"
|
|
275
|
-
}
|
|
326
|
+
headers,
|
|
327
|
+
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
|
|
276
328
|
});
|
|
277
329
|
if (!res.ok) {
|
|
278
330
|
const text = await res.text().catch(()=>"");
|
|
@@ -280,6 +332,33 @@ async function chrome_upload(extensionId, zipPath, token) {
|
|
|
280
332
|
}
|
|
281
333
|
}
|
|
282
334
|
async function publish(params) {
|
|
335
|
+
if ("v2" === params.apiVersion) {
|
|
336
|
+
const pub = requirePublisherId(params.publisherId, "publish");
|
|
337
|
+
if ("trustedTesters" === params.publishTarget) throw new Error("The Chrome v2 API has no trusted-tester publish target. Use staged publish, or keep this project on the v1.1 API for trusted-tester releases.");
|
|
338
|
+
const reqBody = {
|
|
339
|
+
publishType: "DEFAULT_PUBLISH"
|
|
340
|
+
};
|
|
341
|
+
if (null != params.deployPercentage) reqBody.deployInfos = [
|
|
342
|
+
{
|
|
343
|
+
deployPercentage: params.deployPercentage
|
|
344
|
+
}
|
|
345
|
+
];
|
|
346
|
+
if (params.reviewExemption) reqBody.skipReview = true;
|
|
347
|
+
const res = await fetch(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:publish`, {
|
|
348
|
+
method: "POST",
|
|
349
|
+
headers: {
|
|
350
|
+
Authorization: `${params.token.token_type} ${params.token.access_token}`,
|
|
351
|
+
"Content-Type": "application/json"
|
|
352
|
+
},
|
|
353
|
+
body: JSON.stringify(reqBody),
|
|
354
|
+
signal: AbortSignal.timeout(PUBLISH_TIMEOUT_MS)
|
|
355
|
+
});
|
|
356
|
+
if (!res.ok) {
|
|
357
|
+
const text = await res.text().catch(()=>"");
|
|
358
|
+
throw new Error(`Chrome publish failed: ${res.status} ${res.statusText}\n${text}`);
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
283
362
|
const url = new URL(`${CWS_PUBLISH_BASE}/${params.extensionId}/publish`);
|
|
284
363
|
url.searchParams.set("publishTarget", params.publishTarget);
|
|
285
364
|
if (null != params.deployPercentage) url.searchParams.set("deployPercentage", String(params.deployPercentage));
|
|
@@ -290,7 +369,8 @@ async function publish(params) {
|
|
|
290
369
|
Authorization: `${params.token.token_type} ${params.token.access_token}`,
|
|
291
370
|
"x-goog-api-version": "2",
|
|
292
371
|
"Content-Length": "0"
|
|
293
|
-
}
|
|
372
|
+
},
|
|
373
|
+
signal: AbortSignal.timeout(PUBLISH_TIMEOUT_MS)
|
|
294
374
|
});
|
|
295
375
|
if (!res.ok) {
|
|
296
376
|
const text = await res.text().catch(()=>"");
|
|
@@ -300,7 +380,8 @@ async function publish(params) {
|
|
|
300
380
|
async function publishChrome(options, dryRun) {
|
|
301
381
|
await assertFilePresent(options.zip);
|
|
302
382
|
const zipSize = await getFileSize(options.zip);
|
|
303
|
-
|
|
383
|
+
const apiVersion = resolveChromeApiVersion(options);
|
|
384
|
+
log("chrome", `Authenticating with Chrome Web Store (API ${apiVersion})`);
|
|
304
385
|
const token = await authenticate(options.clientId, options.clientSecret, options.refreshToken);
|
|
305
386
|
const outcome = {
|
|
306
387
|
storeUrl: chromeStoreUrl(options.extensionId),
|
|
@@ -309,25 +390,28 @@ async function publishChrome(options, dryRun) {
|
|
|
309
390
|
if (dryRun) {
|
|
310
391
|
logDryStep("chrome", "Authentication succeeded", {
|
|
311
392
|
tokenType: token.token_type,
|
|
312
|
-
scope: "chromewebstore"
|
|
393
|
+
scope: "chromewebstore",
|
|
394
|
+
apiVersion
|
|
313
395
|
});
|
|
314
396
|
logDryStep("chrome", "Would upload ZIP", {
|
|
315
397
|
file: options.zip,
|
|
316
398
|
size: formatBytes(zipSize),
|
|
317
|
-
|
|
318
|
-
method: "PUT"
|
|
399
|
+
apiVersion
|
|
319
400
|
});
|
|
320
401
|
options.skipSubmitReview ? logDryStep("chrome", "Would skip publish (skipSubmitReview=true)") : logDryStep("chrome", "Would publish for review", {
|
|
321
|
-
|
|
402
|
+
apiVersion,
|
|
322
403
|
publishTarget: options.publishTarget ?? "default",
|
|
323
404
|
deployPercentage: options.deployPercentage,
|
|
324
405
|
reviewExemption: options.reviewExemption
|
|
325
406
|
});
|
|
326
|
-
log("chrome", "Dry run complete
|
|
407
|
+
log("chrome", "Dry run complete - no changes made");
|
|
327
408
|
return outcome;
|
|
328
409
|
}
|
|
329
410
|
log("chrome", `Uploading ZIP file (${formatBytes(zipSize)})`);
|
|
330
|
-
await chrome_upload(options.extensionId, options.zip, token
|
|
411
|
+
await chrome_upload(options.extensionId, options.zip, token, {
|
|
412
|
+
apiVersion,
|
|
413
|
+
publisherId: options.publisherId
|
|
414
|
+
});
|
|
331
415
|
if (options.skipSubmitReview) {
|
|
332
416
|
log("chrome", "Skipping publish step (skipSubmitReview=true)");
|
|
333
417
|
return outcome;
|
|
@@ -338,16 +422,36 @@ async function publishChrome(options, dryRun) {
|
|
|
338
422
|
publishTarget: options.publishTarget,
|
|
339
423
|
token,
|
|
340
424
|
deployPercentage: options.deployPercentage,
|
|
341
|
-
reviewExemption: options.reviewExemption
|
|
425
|
+
reviewExemption: options.reviewExemption,
|
|
426
|
+
apiVersion,
|
|
427
|
+
publisherId: options.publisherId
|
|
342
428
|
});
|
|
343
429
|
return outcome;
|
|
344
430
|
}
|
|
345
431
|
async function getChromeItem(params) {
|
|
346
432
|
const token = await authenticate(params.clientId, params.clientSecret, params.refreshToken);
|
|
433
|
+
const auth = {
|
|
434
|
+
Authorization: `${token.token_type} ${token.access_token}`
|
|
435
|
+
};
|
|
436
|
+
const version = resolveChromeApiVersion(params);
|
|
437
|
+
if ("v2" === version) {
|
|
438
|
+
var _rev_distributionChannels_, _rev_distributionChannels;
|
|
439
|
+
const pub = requirePublisherId(params.publisherId, "status");
|
|
440
|
+
const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
|
|
441
|
+
headers: auth
|
|
442
|
+
});
|
|
443
|
+
const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
|
|
444
|
+
return {
|
|
445
|
+
uploadState: status.lastAsyncUploadState,
|
|
446
|
+
crxVersion: null == rev ? void 0 : null == (_rev_distributionChannels = rev.distributionChannels) ? void 0 : null == (_rev_distributionChannels_ = _rev_distributionChannels[0]) ? void 0 : _rev_distributionChannels_.crxVersion,
|
|
447
|
+
publicKey: status.publicKey,
|
|
448
|
+
state: null == rev ? void 0 : rev.state
|
|
449
|
+
};
|
|
450
|
+
}
|
|
347
451
|
const url = `${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=${params.projection ?? "DRAFT"}`;
|
|
348
452
|
return fetchJson(url, {
|
|
349
453
|
headers: {
|
|
350
|
-
|
|
454
|
+
...auth,
|
|
351
455
|
"x-goog-api-version": "2"
|
|
352
456
|
}
|
|
353
457
|
});
|
|
@@ -362,11 +466,24 @@ async function verifyChromeCredentials(params) {
|
|
|
362
466
|
message: `OAuth token exchange failed. Verify your Client ID, Client Secret, and Refresh Token are correct.\n${err instanceof Error ? err.message : err}`
|
|
363
467
|
};
|
|
364
468
|
}
|
|
469
|
+
const auth = {
|
|
470
|
+
Authorization: `${token.token_type} ${token.access_token}`
|
|
471
|
+
};
|
|
472
|
+
const version = resolveChromeApiVersion(params);
|
|
365
473
|
try {
|
|
474
|
+
if ("v2" === version) {
|
|
475
|
+
const pub = requirePublisherId(params.publisherId, "verify");
|
|
476
|
+
const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
|
|
477
|
+
headers: auth
|
|
478
|
+
});
|
|
479
|
+
const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
|
|
480
|
+
return {
|
|
481
|
+
ok: true,
|
|
482
|
+
message: `Credentials verified (Chrome v2 API). Item state: ${(null == rev ? void 0 : rev.state) || "unknown"}.`
|
|
483
|
+
};
|
|
484
|
+
}
|
|
366
485
|
const item = await fetchJson(`${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=DRAFT`, {
|
|
367
|
-
headers:
|
|
368
|
-
Authorization: `${token.token_type} ${token.access_token}`
|
|
369
|
-
}
|
|
486
|
+
headers: auth
|
|
370
487
|
});
|
|
371
488
|
const title = (null == item ? void 0 : item.title) || params.extensionId;
|
|
372
489
|
return {
|
|
@@ -382,7 +499,7 @@ async function verifyChromeCredentials(params) {
|
|
|
382
499
|
};
|
|
383
500
|
if (msg.includes("403")) return {
|
|
384
501
|
ok: false,
|
|
385
|
-
message: `Access denied to extension ${params.extensionId}. You may not be the owner or a developer on this item.`
|
|
502
|
+
message: "v2" === version ? "Access denied. Check the publisher ID matches this extension's account, and that the extension ID is correct." : `Access denied to extension ${params.extensionId}. You may not be the owner or a developer on this item.`
|
|
386
503
|
};
|
|
387
504
|
return {
|
|
388
505
|
ok: false,
|
|
@@ -390,20 +507,14 @@ async function verifyChromeCredentials(params) {
|
|
|
390
507
|
};
|
|
391
508
|
}
|
|
392
509
|
}
|
|
393
|
-
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
394
|
-
var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
|
|
395
510
|
function delay(ms) {
|
|
396
511
|
return new Promise((resolve)=>{
|
|
397
512
|
setTimeout(resolve, ms);
|
|
398
513
|
});
|
|
399
514
|
}
|
|
515
|
+
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
516
|
+
var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
|
|
400
517
|
const AMO_BASE = "https://addons.mozilla.org/api/v5/addons";
|
|
401
|
-
const AMO_PROFILE_URL = "https://addons.mozilla.org/api/v5/accounts/profile/";
|
|
402
|
-
const POLL_INTERVAL_MS = 5000;
|
|
403
|
-
const VALIDATION_TIMEOUT_MS = 600000;
|
|
404
|
-
function firefoxStoreUrl(addonIdOrSlug) {
|
|
405
|
-
return `https://addons.mozilla.org/firefox/addon/${addonIdOrSlug}`;
|
|
406
|
-
}
|
|
407
518
|
function signJwt(issuer, secret, expiresInSeconds = 30) {
|
|
408
519
|
const header = {
|
|
409
520
|
alg: "HS256",
|
|
@@ -470,6 +581,30 @@ async function createVersion(params) {
|
|
|
470
581
|
headers: authHeaders(params.jwtIssuer, params.jwtSecret)
|
|
471
582
|
});
|
|
472
583
|
}
|
|
584
|
+
async function createAddon(params) {
|
|
585
|
+
const form = new FormData();
|
|
586
|
+
form.set("version.upload", params.uploadUuid);
|
|
587
|
+
if (params.sourcesZip) {
|
|
588
|
+
const srcBuf = await promises_default().readFile(params.sourcesZip);
|
|
589
|
+
const srcBlob = new Blob([
|
|
590
|
+
srcBuf
|
|
591
|
+
], {
|
|
592
|
+
type: "application/zip"
|
|
593
|
+
});
|
|
594
|
+
form.set("version.source", srcBlob, "sources.zip");
|
|
595
|
+
}
|
|
596
|
+
return fetchJson(`${AMO_BASE}/addon/`, {
|
|
597
|
+
method: "POST",
|
|
598
|
+
body: form,
|
|
599
|
+
headers: authHeaders(params.jwtIssuer, params.jwtSecret)
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
const AMO_PROFILE_URL = "https://addons.mozilla.org/api/v5/accounts/profile/";
|
|
603
|
+
const POLL_INTERVAL_MS = 5000;
|
|
604
|
+
const VALIDATION_TIMEOUT_MS = 600000;
|
|
605
|
+
function firefoxStoreUrl(addonIdOrSlug) {
|
|
606
|
+
return `https://addons.mozilla.org/firefox/addon/${addonIdOrSlug}`;
|
|
607
|
+
}
|
|
473
608
|
async function publishFirefox(options, dryRun) {
|
|
474
609
|
await assertFilePresent(options.zip);
|
|
475
610
|
const zipSize = await getFileSize(options.zip);
|
|
@@ -478,22 +613,28 @@ async function publishFirefox(options, dryRun) {
|
|
|
478
613
|
await assertFilePresent(options.sourcesZip);
|
|
479
614
|
sourcesSize = await getFileSize(options.sourcesZip);
|
|
480
615
|
}
|
|
481
|
-
const addonId = normalizeAddonId(options.extensionId);
|
|
616
|
+
const addonId = normalizeAddonId(options.extensionId ?? "");
|
|
482
617
|
const { jwtIssuer, jwtSecret } = options;
|
|
483
|
-
|
|
484
|
-
|
|
618
|
+
const channel = options.channel ?? "listed";
|
|
619
|
+
const isNewAddon = 0 === addonId.length;
|
|
620
|
+
if (isNewAddon && "listed" === channel) throw new Error('Listed Firefox submissions require an existing add-on GUID. Use channel "unlisted" for a first submission, or set the add-on GUID.');
|
|
621
|
+
if (!isNewAddon) {
|
|
622
|
+
log("firefox", "Verifying addon exists");
|
|
623
|
+
await getAddon(addonId, jwtIssuer, jwtSecret);
|
|
624
|
+
}
|
|
485
625
|
const outcome = {
|
|
486
|
-
storeUrl: firefoxStoreUrl(addonId),
|
|
626
|
+
storeUrl: isNewAddon ? firefoxStoreUrl("new") : firefoxStoreUrl(addonId),
|
|
487
627
|
submissionId: addonId
|
|
488
628
|
};
|
|
489
629
|
if (dryRun) {
|
|
490
|
-
logDryStep("firefox", "Addon verified", {
|
|
491
|
-
addonId
|
|
630
|
+
logDryStep("firefox", isNewAddon ? "New add-on (no GUID)" : "Addon verified", {
|
|
631
|
+
addonId: isNewAddon ? "(assigned by AMO)" : addonId,
|
|
632
|
+
channel
|
|
492
633
|
});
|
|
493
634
|
logDryStep("firefox", "Would upload ZIP", {
|
|
494
635
|
file: options.zip,
|
|
495
636
|
size: formatBytes(zipSize),
|
|
496
|
-
channel
|
|
637
|
+
channel,
|
|
497
638
|
target: `${AMO_BASE}/upload/`,
|
|
498
639
|
method: "POST"
|
|
499
640
|
});
|
|
@@ -501,8 +642,8 @@ async function publishFirefox(options, dryRun) {
|
|
|
501
642
|
interval: `${POLL_INTERVAL_MS / 1000}s`,
|
|
502
643
|
timeout: `${VALIDATION_TIMEOUT_MS / 60000}min`
|
|
503
644
|
});
|
|
504
|
-
logDryStep("firefox", "Would create new version", {
|
|
505
|
-
target: `${AMO_BASE}/addon/${addonId}/versions/`,
|
|
645
|
+
logDryStep("firefox", isNewAddon ? "Would create new add-on" : "Would create new version", {
|
|
646
|
+
target: isNewAddon ? `${AMO_BASE}/addon/` : `${AMO_BASE}/addon/${addonId}/versions/`,
|
|
506
647
|
method: "POST",
|
|
507
648
|
sourcesZip: options.sourcesZip ? `${options.sourcesZip} (${formatBytes(sourcesSize)})` : "(none)"
|
|
508
649
|
});
|
|
@@ -511,18 +652,31 @@ async function publishFirefox(options, dryRun) {
|
|
|
511
652
|
}
|
|
512
653
|
const upload = await uploadAndAwaitProcessing(options, jwtIssuer, jwtSecret);
|
|
513
654
|
outcome.submissionId = upload.uuid;
|
|
514
|
-
log("firefox", "Creating new version");
|
|
515
|
-
const version = await createVersion({
|
|
516
|
-
addonId,
|
|
517
|
-
sourcesZip: options.sourcesZip,
|
|
518
|
-
uploadUuid: upload.uuid,
|
|
519
|
-
jwtIssuer,
|
|
520
|
-
jwtSecret
|
|
521
|
-
});
|
|
522
|
-
outcome.submissionId = String(version.id);
|
|
523
655
|
const { errors, warnings, notices } = upload.validation;
|
|
524
656
|
log("firefox", `Validation: ${errors} error(s), ${warnings} warning(s), ${notices} notice(s)`);
|
|
525
657
|
if (!upload.valid) throw new Error("Firefox extension failed validation");
|
|
658
|
+
if (isNewAddon) {
|
|
659
|
+
var _created_version;
|
|
660
|
+
log("firefox", "Creating new add-on");
|
|
661
|
+
const created = await createAddon({
|
|
662
|
+
sourcesZip: options.sourcesZip,
|
|
663
|
+
uploadUuid: upload.uuid,
|
|
664
|
+
jwtIssuer,
|
|
665
|
+
jwtSecret
|
|
666
|
+
});
|
|
667
|
+
outcome.submissionId = String((null == (_created_version = created.version) ? void 0 : _created_version.id) ?? created.id);
|
|
668
|
+
outcome.storeUrl = firefoxStoreUrl(created.slug ?? created.guid);
|
|
669
|
+
} else {
|
|
670
|
+
log("firefox", "Creating new version");
|
|
671
|
+
const version = await createVersion({
|
|
672
|
+
addonId,
|
|
673
|
+
sourcesZip: options.sourcesZip,
|
|
674
|
+
uploadUuid: upload.uuid,
|
|
675
|
+
jwtIssuer,
|
|
676
|
+
jwtSecret
|
|
677
|
+
});
|
|
678
|
+
outcome.submissionId = String(version.id);
|
|
679
|
+
}
|
|
526
680
|
return outcome;
|
|
527
681
|
}
|
|
528
682
|
function getFirefoxVersion(params) {
|
|
@@ -816,8 +970,134 @@ async function deploy(config) {
|
|
|
816
970
|
success
|
|
817
971
|
};
|
|
818
972
|
}
|
|
973
|
+
function usage() {
|
|
974
|
+
return `
|
|
975
|
+
extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
|
|
976
|
+
|
|
977
|
+
USAGE
|
|
978
|
+
extension-deploy [options] submit to one or more stores
|
|
979
|
+
extension-deploy init [options] interactive credential setup wizard
|
|
980
|
+
extension-deploy watch --chrome [options] poll an in-flight submission
|
|
981
|
+
extension-deploy watch --firefox [options]
|
|
982
|
+
extension-deploy watch --edge [options]
|
|
983
|
+
|
|
984
|
+
GLOBAL
|
|
985
|
+
--dry-run Verify auth without uploading or publishing
|
|
986
|
+
--output-json <path> Write the DeployResult / WatchEvent as JSON to <path>
|
|
987
|
+
--help Show this help message
|
|
988
|
+
|
|
989
|
+
CHROME
|
|
990
|
+
--chrome-zip <path> Path to Chrome extension ZIP
|
|
991
|
+
--chrome-extension-id <id> Chrome extension ID
|
|
992
|
+
--chrome-client-id <id> OAuth2 client ID
|
|
993
|
+
--chrome-client-secret <secret> OAuth2 client secret
|
|
994
|
+
--chrome-refresh-token <token> OAuth2 refresh token
|
|
995
|
+
--chrome-publish-target <target> "default" or "trustedTesters"
|
|
996
|
+
--chrome-deploy-percentage <n> Staged rollout percentage (1-100)
|
|
997
|
+
--chrome-review-exemption Request expedited review
|
|
998
|
+
--chrome-skip-submit-review Upload only, skip publish
|
|
999
|
+
|
|
1000
|
+
FIREFOX
|
|
1001
|
+
--firefox-zip <path> Path to Firefox extension ZIP
|
|
1002
|
+
--firefox-sources-zip <path> Path to sources ZIP (optional)
|
|
1003
|
+
--firefox-extension-id <id> Addon GUID or email-style ID
|
|
1004
|
+
--firefox-jwt-issuer <issuer> AMO JWT issuer
|
|
1005
|
+
--firefox-jwt-secret <secret> AMO JWT secret
|
|
1006
|
+
--firefox-channel <channel> "listed" or "unlisted"
|
|
1007
|
+
|
|
1008
|
+
EDGE
|
|
1009
|
+
--edge-zip <path> Path to Edge extension ZIP
|
|
1010
|
+
--edge-product-id <id> Partner Center product ID
|
|
1011
|
+
--edge-client-id <id> Partner Center client ID
|
|
1012
|
+
--edge-api-key <key> Partner Center API key (v1.1)
|
|
1013
|
+
--edge-skip-submit-review Upload only, skip publish
|
|
1014
|
+
|
|
1015
|
+
WATCH
|
|
1016
|
+
--watch-submission-id <id> Required for firefox/edge (version id / operation id)
|
|
1017
|
+
--watch-interval <seconds> Poll interval (default 60)
|
|
1018
|
+
--watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
|
|
1019
|
+
|
|
1020
|
+
INIT
|
|
1021
|
+
--init-stores <list> Comma-separated store list (skip picker)
|
|
1022
|
+
--init-output <path> Destination file (default .env.submit)
|
|
1023
|
+
--init-skip-verify Don't run verifyCredentials after collection
|
|
1024
|
+
--init-force Overwrite existing .env.submit without asking
|
|
1025
|
+
|
|
1026
|
+
ENVIRONMENT VARIABLES
|
|
1027
|
+
All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
|
|
1028
|
+
FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
|
|
1029
|
+
A .env.submit file in the current directory is loaded automatically.
|
|
1030
|
+
`.trim();
|
|
1031
|
+
}
|
|
1032
|
+
const BOOLEAN_FLAGS = new Set([
|
|
1033
|
+
"help",
|
|
1034
|
+
"dryRun",
|
|
1035
|
+
"chromeReviewExemption",
|
|
1036
|
+
"chromeSkipSubmitReview",
|
|
1037
|
+
"edgeSkipSubmitReview",
|
|
1038
|
+
"initSkipVerify",
|
|
1039
|
+
"initForce"
|
|
1040
|
+
]);
|
|
1041
|
+
const INT_FLAGS = new Set([
|
|
1042
|
+
"chromeDeployPercentage",
|
|
1043
|
+
"watchInterval",
|
|
1044
|
+
"watchTimeout"
|
|
1045
|
+
]);
|
|
1046
|
+
function parseArgs(argv) {
|
|
1047
|
+
const flags = {};
|
|
1048
|
+
let i = 0;
|
|
1049
|
+
if ("watch" === argv[0]) {
|
|
1050
|
+
flags.command = "watch";
|
|
1051
|
+
i = 1;
|
|
1052
|
+
} else if ("init" === argv[0]) {
|
|
1053
|
+
flags.command = "init";
|
|
1054
|
+
i = 1;
|
|
1055
|
+
}
|
|
1056
|
+
while(i < argv.length){
|
|
1057
|
+
const arg = argv[i];
|
|
1058
|
+
if ("--help" === arg || "-h" === arg) {
|
|
1059
|
+
flags.help = true;
|
|
1060
|
+
i++;
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
if ("--chrome" === arg) {
|
|
1064
|
+
flags.watchStore = "chrome";
|
|
1065
|
+
i++;
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
if ("--firefox" === arg) {
|
|
1069
|
+
flags.watchStore = "firefox";
|
|
1070
|
+
i++;
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
if ("--edge" === arg) {
|
|
1074
|
+
flags.watchStore = "edge";
|
|
1075
|
+
i++;
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
if (!(null == arg ? void 0 : arg.startsWith("--"))) {
|
|
1079
|
+
i++;
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
const key = arg.slice(2).replace(/-([a-z])/g, (_, c)=>c.toUpperCase());
|
|
1083
|
+
if (BOOLEAN_FLAGS.has(key)) {
|
|
1084
|
+
flags[key] = true;
|
|
1085
|
+
i++;
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
const next = argv[i + 1];
|
|
1089
|
+
if (null == next || next.startsWith("--")) {
|
|
1090
|
+
flags[key] = true;
|
|
1091
|
+
i++;
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
flags[key] = INT_FLAGS.has(key) ? parseInt(next, 10) : next;
|
|
1095
|
+
i += 2;
|
|
1096
|
+
}
|
|
1097
|
+
return flags;
|
|
1098
|
+
}
|
|
819
1099
|
const DEFAULT_POLL_INTERVAL_MS = 60000;
|
|
820
|
-
const
|
|
1100
|
+
const watch_DEFAULT_TIMEOUT_MS = 3600000;
|
|
821
1101
|
function watch_now() {
|
|
822
1102
|
return new Date().toISOString();
|
|
823
1103
|
}
|
|
@@ -944,7 +1224,7 @@ function watchFirefoxSubmission(input, options = {}) {
|
|
|
944
1224
|
}
|
|
945
1225
|
async function runWatchLoop(_store, probe, options) {
|
|
946
1226
|
const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
947
|
-
const timeout = options.timeoutMs ??
|
|
1227
|
+
const timeout = options.timeoutMs ?? watch_DEFAULT_TIMEOUT_MS;
|
|
948
1228
|
const deadline = Date.now() + timeout;
|
|
949
1229
|
let lastEvent;
|
|
950
1230
|
while(true){
|
|
@@ -956,125 +1236,30 @@ async function runWatchLoop(_store, probe, options) {
|
|
|
956
1236
|
await delay(pollInterval);
|
|
957
1237
|
}
|
|
958
1238
|
}
|
|
959
|
-
|
|
960
|
-
{
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
value: "firefox",
|
|
966
|
-
label: "Firefox AMO (addons.mozilla.org)"
|
|
967
|
-
},
|
|
968
|
-
{
|
|
969
|
-
value: "edge",
|
|
970
|
-
label: "Edge Add-ons (Partner Center)"
|
|
1239
|
+
async function requireValue(io, label, hint, opts) {
|
|
1240
|
+
const question = hint ? `${label} ${hint}:` : `${label}:`;
|
|
1241
|
+
for(let attempt = 0; attempt < 3; attempt++){
|
|
1242
|
+
const answer = (await io.prompt(question, opts)).trim();
|
|
1243
|
+
if (answer) return answer;
|
|
1244
|
+
io.log(` ${label} is required.`);
|
|
971
1245
|
}
|
|
972
|
-
|
|
1246
|
+
throw new Error(`init: ${label} is required.`);
|
|
1247
|
+
}
|
|
973
1248
|
const CHROME_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
974
1249
|
const CHROME_OAUTH_AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
|
|
975
1250
|
const CHROME_OOB_REDIRECT = "urn:ietf:wg:oauth:2.0:oob";
|
|
976
1251
|
const CHROME_SCOPE = "https://www.googleapis.com/auth/chromewebstore";
|
|
977
|
-
async function
|
|
1252
|
+
async function collectChromeCredentials(deps) {
|
|
978
1253
|
const { io } = deps;
|
|
1254
|
+
io.log("Chrome Web Store needs OAuth credentials. You'll need:");
|
|
1255
|
+
io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
|
|
1256
|
+
io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
|
|
1257
|
+
io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
|
|
1258
|
+
io.log(" https://console.cloud.google.com/apis/credentials");
|
|
1259
|
+
io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
|
|
979
1260
|
io.log("");
|
|
980
|
-
io.log(
|
|
981
|
-
io.log("");
|
|
982
|
-
io.log("This wizard walks you through creating credentials for each store and");
|
|
983
|
-
io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
|
|
984
|
-
io.log("");
|
|
985
|
-
const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
|
|
986
|
-
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.");
|
|
987
|
-
const envLines = [
|
|
988
|
-
"# Generated by `extension-deploy init`.",
|
|
989
|
-
"# Do not commit secrets to source control.",
|
|
990
|
-
""
|
|
991
|
-
];
|
|
992
|
-
const verified = {};
|
|
993
|
-
for (const store of stores){
|
|
994
|
-
io.log("");
|
|
995
|
-
io.log(colors.underline(colors.blue(labelFor(store))));
|
|
996
|
-
io.log("");
|
|
997
|
-
if ("chrome" === store) {
|
|
998
|
-
const creds = await collectChromeCredentials(deps);
|
|
999
|
-
envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
|
|
1000
|
-
if (!options.skipVerify) {
|
|
1001
|
-
const verify = deps.verifyChrome ?? verifyChromeCredentials;
|
|
1002
|
-
verified.chrome = normalizeVerify(await verify(creds));
|
|
1003
|
-
logVerify(io, "chrome", verified.chrome);
|
|
1004
|
-
}
|
|
1005
|
-
} else if ("firefox" === store) {
|
|
1006
|
-
const creds = await collectFirefoxCredentials(deps);
|
|
1007
|
-
envLines.push(`FIREFOX_EXTENSION_ID=${creds.addonId}`, `FIREFOX_JWT_ISSUER=${creds.jwtIssuer}`, `FIREFOX_JWT_SECRET=${creds.jwtSecret}`, "");
|
|
1008
|
-
if (!options.skipVerify) {
|
|
1009
|
-
const verify = deps.verifyFirefox ?? verifyFirefoxCredentials;
|
|
1010
|
-
verified.firefox = normalizeVerify(await verify({
|
|
1011
|
-
jwtIssuer: creds.jwtIssuer,
|
|
1012
|
-
jwtSecret: creds.jwtSecret,
|
|
1013
|
-
addonId: creds.addonId
|
|
1014
|
-
}));
|
|
1015
|
-
logVerify(io, "firefox", verified.firefox);
|
|
1016
|
-
}
|
|
1017
|
-
} else {
|
|
1018
|
-
const creds = await collectEdgeCredentials(deps);
|
|
1019
|
-
envLines.push(`EDGE_PRODUCT_ID=${creds.productId}`, `EDGE_CLIENT_ID=${creds.clientId}`, `EDGE_API_KEY=${creds.apiKey}`, "");
|
|
1020
|
-
if (!options.skipVerify) {
|
|
1021
|
-
const verify = deps.verifyEdge ?? verifyEdgeCredentials;
|
|
1022
|
-
verified.edge = normalizeVerify(await verify({
|
|
1023
|
-
clientId: creds.clientId,
|
|
1024
|
-
apiKey: creds.apiKey,
|
|
1025
|
-
productId: creds.productId
|
|
1026
|
-
}));
|
|
1027
|
-
logVerify(io, "edge", verified.edge);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
const envContent = envLines.join("\n");
|
|
1032
|
-
const envPath = resolveEnvPath(deps.cwd, options.outputPath);
|
|
1033
|
-
const exists = await deps.fileExists(envPath);
|
|
1034
|
-
let wrote = false;
|
|
1035
|
-
if (exists && !options.force) {
|
|
1036
|
-
const overwrite = await deps.io.confirm(`${envPath} already exists. Overwrite?`, false);
|
|
1037
|
-
if (overwrite) {
|
|
1038
|
-
await deps.writeFile(envPath, envContent);
|
|
1039
|
-
wrote = true;
|
|
1040
|
-
} else {
|
|
1041
|
-
io.log("");
|
|
1042
|
-
io.log(`Skipped writing ${envPath}. Credential block:`);
|
|
1043
|
-
io.log("");
|
|
1044
|
-
io.log(envContent);
|
|
1045
|
-
}
|
|
1046
|
-
} else {
|
|
1047
|
-
await deps.writeFile(envPath, envContent);
|
|
1048
|
-
wrote = true;
|
|
1049
|
-
}
|
|
1050
|
-
if (wrote) {
|
|
1051
|
-
io.log("");
|
|
1052
|
-
io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} Wrote ${colors.underline(envPath)}`);
|
|
1053
|
-
io.log("");
|
|
1054
|
-
io.log(colors.gray("Next steps:"));
|
|
1055
|
-
io.log(" - Add .env.submit to .gitignore if it isn't already (it contains secrets).");
|
|
1056
|
-
io.log(" - Run `extension-deploy --dry-run` to sanity-check the full submit flow.");
|
|
1057
|
-
io.log("");
|
|
1058
|
-
}
|
|
1059
|
-
return {
|
|
1060
|
-
stores,
|
|
1061
|
-
envPath,
|
|
1062
|
-
envContent,
|
|
1063
|
-
verified,
|
|
1064
|
-
wrote
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
async function collectChromeCredentials(deps) {
|
|
1068
|
-
const { io } = deps;
|
|
1069
|
-
io.log("Chrome Web Store needs OAuth credentials. You'll need:");
|
|
1070
|
-
io.log(" 1. A Google Cloud project with the Chrome Web Store API enabled:");
|
|
1071
|
-
io.log(" https://console.cloud.google.com/apis/library/chromewebstore.googleapis.com");
|
|
1072
|
-
io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
|
|
1073
|
-
io.log(" https://console.cloud.google.com/apis/credentials");
|
|
1074
|
-
io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
|
|
1075
|
-
io.log("");
|
|
1076
|
-
io.log("This wizard will take your Client ID + Secret and walk you through");
|
|
1077
|
-
io.log("generating a refresh token automatically.");
|
|
1261
|
+
io.log("This wizard will take your Client ID + Secret and walk you through");
|
|
1262
|
+
io.log("generating a refresh token automatically.");
|
|
1078
1263
|
io.log("");
|
|
1079
1264
|
const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
|
|
1080
1265
|
const clientId = await requireValue(io, "OAuth Client ID");
|
|
@@ -1179,6 +1364,110 @@ async function collectEdgeCredentials(deps) {
|
|
|
1179
1364
|
apiKey
|
|
1180
1365
|
};
|
|
1181
1366
|
}
|
|
1367
|
+
const STORE_CHOICES = [
|
|
1368
|
+
{
|
|
1369
|
+
value: "chrome",
|
|
1370
|
+
label: "Chrome Web Store"
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
value: "firefox",
|
|
1374
|
+
label: "Firefox AMO (addons.mozilla.org)"
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
value: "edge",
|
|
1378
|
+
label: "Edge Add-ons (Partner Center)"
|
|
1379
|
+
}
|
|
1380
|
+
];
|
|
1381
|
+
async function runInit(deps, options = {}) {
|
|
1382
|
+
const { io } = deps;
|
|
1383
|
+
io.log("");
|
|
1384
|
+
io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} ${colors.underline("extension-deploy \u2014 set up store credentials")}`);
|
|
1385
|
+
io.log("");
|
|
1386
|
+
io.log("This wizard walks you through creating credentials for each store and");
|
|
1387
|
+
io.log("writes them to a .env.submit file. Press Ctrl+C at any time to abort.");
|
|
1388
|
+
io.log("");
|
|
1389
|
+
const stores = options.stores ? options.stores : await io.multiselect("Which stores do you want to configure?", STORE_CHOICES);
|
|
1390
|
+
if (0 === stores.length) throw new Error("init: at least one store must be selected. Use --init-stores chrome,firefox,edge or make a selection.");
|
|
1391
|
+
const envLines = [
|
|
1392
|
+
"# Generated by `extension-deploy init`.",
|
|
1393
|
+
"# Do not commit secrets to source control.",
|
|
1394
|
+
""
|
|
1395
|
+
];
|
|
1396
|
+
const verified = {};
|
|
1397
|
+
for (const store of stores){
|
|
1398
|
+
io.log("");
|
|
1399
|
+
io.log(colors.underline(colors.blue(labelFor(store))));
|
|
1400
|
+
io.log("");
|
|
1401
|
+
if ("chrome" === store) {
|
|
1402
|
+
const creds = await collectChromeCredentials(deps);
|
|
1403
|
+
envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
|
|
1404
|
+
if (!options.skipVerify) {
|
|
1405
|
+
const verify = deps.verifyChrome ?? verifyChromeCredentials;
|
|
1406
|
+
verified.chrome = normalizeVerify(await verify(creds));
|
|
1407
|
+
logVerify(io, "chrome", verified.chrome);
|
|
1408
|
+
}
|
|
1409
|
+
} else if ("firefox" === store) {
|
|
1410
|
+
const creds = await collectFirefoxCredentials(deps);
|
|
1411
|
+
envLines.push(`FIREFOX_EXTENSION_ID=${creds.addonId}`, `FIREFOX_JWT_ISSUER=${creds.jwtIssuer}`, `FIREFOX_JWT_SECRET=${creds.jwtSecret}`, "");
|
|
1412
|
+
if (!options.skipVerify) {
|
|
1413
|
+
const verify = deps.verifyFirefox ?? verifyFirefoxCredentials;
|
|
1414
|
+
verified.firefox = normalizeVerify(await verify({
|
|
1415
|
+
jwtIssuer: creds.jwtIssuer,
|
|
1416
|
+
jwtSecret: creds.jwtSecret,
|
|
1417
|
+
addonId: creds.addonId
|
|
1418
|
+
}));
|
|
1419
|
+
logVerify(io, "firefox", verified.firefox);
|
|
1420
|
+
}
|
|
1421
|
+
} else {
|
|
1422
|
+
const creds = await collectEdgeCredentials(deps);
|
|
1423
|
+
envLines.push(`EDGE_PRODUCT_ID=${creds.productId}`, `EDGE_CLIENT_ID=${creds.clientId}`, `EDGE_API_KEY=${creds.apiKey}`, "");
|
|
1424
|
+
if (!options.skipVerify) {
|
|
1425
|
+
const verify = deps.verifyEdge ?? verifyEdgeCredentials;
|
|
1426
|
+
verified.edge = normalizeVerify(await verify({
|
|
1427
|
+
clientId: creds.clientId,
|
|
1428
|
+
apiKey: creds.apiKey,
|
|
1429
|
+
productId: creds.productId
|
|
1430
|
+
}));
|
|
1431
|
+
logVerify(io, "edge", verified.edge);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
const envContent = envLines.join("\n");
|
|
1436
|
+
const envPath = resolveEnvPath(deps.cwd, options.outputPath);
|
|
1437
|
+
const exists = await deps.fileExists(envPath);
|
|
1438
|
+
let wrote = false;
|
|
1439
|
+
if (exists && !options.force) {
|
|
1440
|
+
const overwrite = await deps.io.confirm(`${envPath} already exists. Overwrite?`, false);
|
|
1441
|
+
if (overwrite) {
|
|
1442
|
+
await deps.writeFile(envPath, envContent);
|
|
1443
|
+
wrote = true;
|
|
1444
|
+
} else {
|
|
1445
|
+
io.log("");
|
|
1446
|
+
io.log(`Skipped writing ${envPath}. Credential block:`);
|
|
1447
|
+
io.log("");
|
|
1448
|
+
io.log(envContent);
|
|
1449
|
+
}
|
|
1450
|
+
} else {
|
|
1451
|
+
await deps.writeFile(envPath, envContent);
|
|
1452
|
+
wrote = true;
|
|
1453
|
+
}
|
|
1454
|
+
if (wrote) {
|
|
1455
|
+
io.log("");
|
|
1456
|
+
io.log(`${colors.green("\u23F5\u23F5\u23F5")} ${colors.blue("init")} Wrote ${colors.underline(envPath)}`);
|
|
1457
|
+
io.log("");
|
|
1458
|
+
io.log(colors.gray("Next steps:"));
|
|
1459
|
+
io.log(" - Add .env.submit to .gitignore if it isn't already (it contains secrets).");
|
|
1460
|
+
io.log(" - Run `extension-deploy --dry-run` to sanity-check the full submit flow.");
|
|
1461
|
+
io.log("");
|
|
1462
|
+
}
|
|
1463
|
+
return {
|
|
1464
|
+
stores,
|
|
1465
|
+
envPath,
|
|
1466
|
+
envContent,
|
|
1467
|
+
verified,
|
|
1468
|
+
wrote
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1182
1471
|
function labelFor(store) {
|
|
1183
1472
|
var _STORE_CHOICES_find;
|
|
1184
1473
|
return (null == (_STORE_CHOICES_find = STORE_CHOICES.find((c)=>c.value === store)) ? void 0 : _STORE_CHOICES_find.label) ?? String(store);
|
|
@@ -1197,15 +1486,6 @@ function resolveEnvPath(cwd, outputPath) {
|
|
|
1197
1486
|
const target = outputPath ?? ".env.submit";
|
|
1198
1487
|
return external_node_path_default().isAbsolute(target) ? target : external_node_path_default().join(cwd, target);
|
|
1199
1488
|
}
|
|
1200
|
-
async function requireValue(io, label, hint, opts) {
|
|
1201
|
-
const question = hint ? `${label} ${hint}:` : `${label}:`;
|
|
1202
|
-
for(let attempt = 0; attempt < 3; attempt++){
|
|
1203
|
-
const answer = (await io.prompt(question, opts)).trim();
|
|
1204
|
-
if (answer) return answer;
|
|
1205
|
-
io.log(` ${label} is required.`);
|
|
1206
|
-
}
|
|
1207
|
-
throw new Error(`init: ${label} is required.`);
|
|
1208
|
-
}
|
|
1209
1489
|
const external_node_readline_namespaceObject = require("node:readline");
|
|
1210
1490
|
var external_node_readline_default = /*#__PURE__*/ __webpack_require__.n(external_node_readline_namespaceObject);
|
|
1211
1491
|
function createStdinIO() {
|
|
@@ -1261,7 +1541,13 @@ function createStdinIO() {
|
|
|
1261
1541
|
}
|
|
1262
1542
|
const stdinFs = {
|
|
1263
1543
|
async writeFile (filePath, content) {
|
|
1264
|
-
await promises_default().writeFile(filePath, content,
|
|
1544
|
+
await promises_default().writeFile(filePath, content, {
|
|
1545
|
+
encoding: "utf-8",
|
|
1546
|
+
mode: 384
|
|
1547
|
+
});
|
|
1548
|
+
try {
|
|
1549
|
+
await promises_default().chmod(filePath, 384);
|
|
1550
|
+
} catch {}
|
|
1265
1551
|
},
|
|
1266
1552
|
async fileExists (filePath) {
|
|
1267
1553
|
try {
|
|
@@ -1272,173 +1558,12 @@ const stdinFs = {
|
|
|
1272
1558
|
}
|
|
1273
1559
|
}
|
|
1274
1560
|
};
|
|
1275
|
-
function usage() {
|
|
1276
|
-
return `
|
|
1277
|
-
extension-deploy \u{2014} Deploy browser extensions to Chrome, Firefox, and Edge.
|
|
1278
|
-
|
|
1279
|
-
USAGE
|
|
1280
|
-
extension-deploy [options] submit to one or more stores
|
|
1281
|
-
extension-deploy init [options] interactive credential setup wizard
|
|
1282
|
-
extension-deploy watch --chrome [options] poll an in-flight submission
|
|
1283
|
-
extension-deploy watch --firefox [options]
|
|
1284
|
-
extension-deploy watch --edge [options]
|
|
1285
|
-
|
|
1286
|
-
GLOBAL
|
|
1287
|
-
--dry-run Verify auth without uploading or publishing
|
|
1288
|
-
--output-json <path> Write the DeployResult / WatchEvent as JSON to <path>
|
|
1289
|
-
--help Show this help message
|
|
1290
|
-
|
|
1291
|
-
CHROME
|
|
1292
|
-
--chrome-zip <path> Path to Chrome extension ZIP
|
|
1293
|
-
--chrome-extension-id <id> Chrome extension ID
|
|
1294
|
-
--chrome-client-id <id> OAuth2 client ID
|
|
1295
|
-
--chrome-client-secret <secret> OAuth2 client secret
|
|
1296
|
-
--chrome-refresh-token <token> OAuth2 refresh token
|
|
1297
|
-
--chrome-publish-target <target> "default" or "trustedTesters"
|
|
1298
|
-
--chrome-deploy-percentage <n> Staged rollout percentage (1-100)
|
|
1299
|
-
--chrome-review-exemption Request expedited review
|
|
1300
|
-
--chrome-skip-submit-review Upload only, skip publish
|
|
1301
|
-
|
|
1302
|
-
FIREFOX
|
|
1303
|
-
--firefox-zip <path> Path to Firefox extension ZIP
|
|
1304
|
-
--firefox-sources-zip <path> Path to sources ZIP (optional)
|
|
1305
|
-
--firefox-extension-id <id> Addon GUID or email-style ID
|
|
1306
|
-
--firefox-jwt-issuer <issuer> AMO JWT issuer
|
|
1307
|
-
--firefox-jwt-secret <secret> AMO JWT secret
|
|
1308
|
-
--firefox-channel <channel> "listed" or "unlisted"
|
|
1309
|
-
|
|
1310
|
-
EDGE
|
|
1311
|
-
--edge-zip <path> Path to Edge extension ZIP
|
|
1312
|
-
--edge-product-id <id> Partner Center product ID
|
|
1313
|
-
--edge-client-id <id> Partner Center client ID
|
|
1314
|
-
--edge-api-key <key> Partner Center API key (v1.1)
|
|
1315
|
-
--edge-skip-submit-review Upload only, skip publish
|
|
1316
|
-
|
|
1317
|
-
WATCH
|
|
1318
|
-
--watch-submission-id <id> Required for firefox/edge (version id / operation id)
|
|
1319
|
-
--watch-interval <seconds> Poll interval (default 60)
|
|
1320
|
-
--watch-timeout <seconds> Max time to wait for a terminal state (default 3600)
|
|
1321
|
-
|
|
1322
|
-
INIT
|
|
1323
|
-
--init-stores <list> Comma-separated store list (skip picker)
|
|
1324
|
-
--init-output <path> Destination file (default .env.submit)
|
|
1325
|
-
--init-skip-verify Don't run verifyCredentials after collection
|
|
1326
|
-
--init-force Overwrite existing .env.submit without asking
|
|
1327
|
-
|
|
1328
|
-
ENVIRONMENT VARIABLES
|
|
1329
|
-
All flags can be set via SCREAMING_SNAKE_CASE env vars (e.g. CHROME_ZIP,
|
|
1330
|
-
FIREFOX_JWT_SECRET, EDGE_API_KEY). Flags take precedence over env vars.
|
|
1331
|
-
A .env.submit file in the current directory is loaded automatically.
|
|
1332
|
-
`.trim();
|
|
1333
|
-
}
|
|
1334
|
-
const BOOLEAN_FLAGS = new Set([
|
|
1335
|
-
"help",
|
|
1336
|
-
"dryRun",
|
|
1337
|
-
"chromeReviewExemption",
|
|
1338
|
-
"chromeSkipSubmitReview",
|
|
1339
|
-
"edgeSkipSubmitReview",
|
|
1340
|
-
"initSkipVerify",
|
|
1341
|
-
"initForce"
|
|
1342
|
-
]);
|
|
1343
|
-
const INT_FLAGS = new Set([
|
|
1344
|
-
"chromeDeployPercentage",
|
|
1345
|
-
"watchInterval",
|
|
1346
|
-
"watchTimeout"
|
|
1347
|
-
]);
|
|
1348
|
-
function parseArgs(argv) {
|
|
1349
|
-
const flags = {};
|
|
1350
|
-
let i = 0;
|
|
1351
|
-
if ("watch" === argv[0]) {
|
|
1352
|
-
flags.command = "watch";
|
|
1353
|
-
i = 1;
|
|
1354
|
-
} else if ("init" === argv[0]) {
|
|
1355
|
-
flags.command = "init";
|
|
1356
|
-
i = 1;
|
|
1357
|
-
}
|
|
1358
|
-
while(i < argv.length){
|
|
1359
|
-
const arg = argv[i];
|
|
1360
|
-
if ("--help" === arg || "-h" === arg) {
|
|
1361
|
-
flags.help = true;
|
|
1362
|
-
i++;
|
|
1363
|
-
continue;
|
|
1364
|
-
}
|
|
1365
|
-
if ("--chrome" === arg) {
|
|
1366
|
-
flags.watchStore = "chrome";
|
|
1367
|
-
i++;
|
|
1368
|
-
continue;
|
|
1369
|
-
}
|
|
1370
|
-
if ("--firefox" === arg) {
|
|
1371
|
-
flags.watchStore = "firefox";
|
|
1372
|
-
i++;
|
|
1373
|
-
continue;
|
|
1374
|
-
}
|
|
1375
|
-
if ("--edge" === arg) {
|
|
1376
|
-
flags.watchStore = "edge";
|
|
1377
|
-
i++;
|
|
1378
|
-
continue;
|
|
1379
|
-
}
|
|
1380
|
-
if (!(null == arg ? void 0 : arg.startsWith("--"))) {
|
|
1381
|
-
i++;
|
|
1382
|
-
continue;
|
|
1383
|
-
}
|
|
1384
|
-
const key = arg.slice(2).replace(/-([a-z])/g, (_, c)=>c.toUpperCase());
|
|
1385
|
-
if (BOOLEAN_FLAGS.has(key)) {
|
|
1386
|
-
flags[key] = true;
|
|
1387
|
-
i++;
|
|
1388
|
-
continue;
|
|
1389
|
-
}
|
|
1390
|
-
const next = argv[i + 1];
|
|
1391
|
-
if (null == next || next.startsWith("--")) {
|
|
1392
|
-
flags[key] = true;
|
|
1393
|
-
i++;
|
|
1394
|
-
continue;
|
|
1395
|
-
}
|
|
1396
|
-
flags[key] = INT_FLAGS.has(key) ? parseInt(next, 10) : next;
|
|
1397
|
-
i += 2;
|
|
1398
|
-
}
|
|
1399
|
-
return flags;
|
|
1400
|
-
}
|
|
1401
|
-
function loadDotEnv() {
|
|
1402
|
-
try {
|
|
1403
|
-
const envPath = external_node_path_default().resolve(process.cwd(), ".env.submit");
|
|
1404
|
-
const content = external_node_fs_default().readFileSync(envPath, "utf-8");
|
|
1405
|
-
for (const line of content.split("\n")){
|
|
1406
|
-
const trimmed = line.trim();
|
|
1407
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1408
|
-
const eqIdx = trimmed.indexOf("=");
|
|
1409
|
-
if (-1 === eqIdx) continue;
|
|
1410
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
1411
|
-
let value = trimmed.slice(eqIdx + 1).trim();
|
|
1412
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
1413
|
-
if (!(key in process.env)) process.env[key] = value;
|
|
1414
|
-
}
|
|
1415
|
-
} catch {}
|
|
1416
|
-
}
|
|
1417
1561
|
function writeJsonOutput(filePath, payload) {
|
|
1418
1562
|
external_node_fs_default().mkdirSync(external_node_path_default().dirname(external_node_path_default().resolve(filePath)), {
|
|
1419
1563
|
recursive: true
|
|
1420
1564
|
});
|
|
1421
1565
|
external_node_fs_default().writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
|
|
1422
1566
|
}
|
|
1423
|
-
async function main() {
|
|
1424
|
-
loadDotEnv();
|
|
1425
|
-
const flags = parseArgs(process.argv.slice(2));
|
|
1426
|
-
if (flags.help) {
|
|
1427
|
-
console.log(usage());
|
|
1428
|
-
process.exit(0);
|
|
1429
|
-
}
|
|
1430
|
-
if ("watch" === flags.command) return void await runWatch(flags);
|
|
1431
|
-
if ("init" === flags.command) return void await runInitCommand(flags);
|
|
1432
|
-
const config = resolveConfig(flags);
|
|
1433
|
-
try {
|
|
1434
|
-
const result = await deploy(config);
|
|
1435
|
-
if (flags.outputJson) writeJsonOutput(flags.outputJson, result);
|
|
1436
|
-
if (!result.success) process.exit(1);
|
|
1437
|
-
} catch (err) {
|
|
1438
|
-
logError("deploy", err instanceof Error ? err.message : String(err));
|
|
1439
|
-
process.exit(1);
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
1567
|
async function runWatch(flags) {
|
|
1443
1568
|
const store = flags.watchStore;
|
|
1444
1569
|
if (!store) {
|
|
@@ -1534,6 +1659,41 @@ function parseInitStores(raw) {
|
|
|
1534
1659
|
if (0 === parsed.length) throw new Error(`--init-stores: expected a comma-separated list of chrome,firefox,edge (got "${raw}")`);
|
|
1535
1660
|
return parsed;
|
|
1536
1661
|
}
|
|
1662
|
+
function loadDotEnv() {
|
|
1663
|
+
try {
|
|
1664
|
+
const envPath = external_node_path_default().resolve(process.cwd(), ".env.submit");
|
|
1665
|
+
const content = external_node_fs_default().readFileSync(envPath, "utf-8");
|
|
1666
|
+
for (const line of content.split("\n")){
|
|
1667
|
+
const trimmed = line.trim();
|
|
1668
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1669
|
+
const eqIdx = trimmed.indexOf("=");
|
|
1670
|
+
if (-1 === eqIdx) continue;
|
|
1671
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
1672
|
+
let value = trimmed.slice(eqIdx + 1).trim();
|
|
1673
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
1674
|
+
if (!(key in process.env)) process.env[key] = value;
|
|
1675
|
+
}
|
|
1676
|
+
} catch {}
|
|
1677
|
+
}
|
|
1678
|
+
async function main() {
|
|
1679
|
+
loadDotEnv();
|
|
1680
|
+
const flags = parseArgs(process.argv.slice(2));
|
|
1681
|
+
if (flags.help) {
|
|
1682
|
+
console.log(usage());
|
|
1683
|
+
process.exit(0);
|
|
1684
|
+
}
|
|
1685
|
+
if ("watch" === flags.command) return void await runWatch(flags);
|
|
1686
|
+
if ("init" === flags.command) return void await runInitCommand(flags);
|
|
1687
|
+
const config = resolveConfig(flags);
|
|
1688
|
+
try {
|
|
1689
|
+
const result = await deploy(config);
|
|
1690
|
+
if (flags.outputJson) writeJsonOutput(flags.outputJson, result);
|
|
1691
|
+
if (!result.success) process.exit(1);
|
|
1692
|
+
} catch (err) {
|
|
1693
|
+
logError("deploy", err instanceof Error ? err.message : String(err));
|
|
1694
|
+
process.exit(1);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1537
1697
|
exports.chromeOptionsSchema = __webpack_exports__.chromeOptionsSchema;
|
|
1538
1698
|
exports.deploy = __webpack_exports__.deploy;
|
|
1539
1699
|
exports.deployConfigSchema = __webpack_exports__.deployConfigSchema;
|