@extension.dev/deploy 0.3.0 → 1.1.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 +21 -0
- package/README.md +10 -2
- package/dist/module.js +146 -150
- package/dist/module.mjs +145 -149
- package/dist/src/chrome.d.ts +15 -26
- package/dist/src/chrome.d.ts.map +1 -1
- package/dist/src/cli-commands.d.ts.map +1 -1
- package/dist/src/cli-usage.d.ts.map +1 -1
- package/dist/src/config.d.ts +6 -8
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/init-chrome.d.ts +1 -0
- package/dist/src/init-chrome.d.ts.map +1 -1
- package/dist/src/init.d.ts.map +1 -1
- package/dist/src/types.d.ts +71 -35
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/watch.d.ts +5 -3
- package/dist/src/watch.d.ts.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
- Chrome: service account authentication (`CHROME_SERVICE_ACCOUNT_JSON` /
|
|
6
|
+
`--chrome-service-account-json`, JSON key content or file path) as an
|
|
7
|
+
alternative to the OAuth client/secret/refresh-token trio. The service
|
|
8
|
+
account wins when both are set. Avoids the 7-day refresh-token expiry of
|
|
9
|
+
OAuth clients whose consent screen is in "Testing" status. Add the
|
|
10
|
+
account's email to your publisher in the dev console (Account section).
|
|
11
|
+
|
|
12
|
+
## 1.0.0
|
|
13
|
+
|
|
14
|
+
- BREAKING: Chrome Web Store API v2 only (`chromewebstore.googleapis.com`).
|
|
15
|
+
`CHROME_PUBLISHER_ID` is required; the v1.1 path, `publishTarget`, and the
|
|
16
|
+
trusted-testers target were removed (staged rollout via
|
|
17
|
+
`CHROME_DEPLOY_PERCENTAGE` replaces them).
|
|
18
|
+
|
|
19
|
+
## 0.3.0
|
|
20
|
+
|
|
21
|
+
- Chrome Web Store API v2 support, opt-in via `CHROME_PUBLISHER_ID`
|
|
22
|
+
(v1.1 fallback retained).
|
|
23
|
+
|
|
3
24
|
## 0.2.5
|
|
4
25
|
|
|
5
26
|
Current release. Earlier history is tracked in the source monorepo; entries
|
package/README.md
CHANGED
|
@@ -78,14 +78,22 @@ Run `extension-deploy --help` for the full list of flags.
|
|
|
78
78
|
| ---------------------------------- | --------------------------- | --------------------------------- |
|
|
79
79
|
| `--chrome-zip <path>` | `CHROME_ZIP` | Path to the extension ZIP file |
|
|
80
80
|
| `--chrome-extension-id <id>` | `CHROME_EXTENSION_ID` | Chrome Web Store extension ID |
|
|
81
|
+
| `--chrome-publisher-id <uuid>` | `CHROME_PUBLISHER_ID` | Publisher UUID from the dev console (required) |
|
|
82
|
+
| `--chrome-service-account-json <v>` | `CHROME_SERVICE_ACCOUNT_JSON` | Service account JSON key (content or file path); alternative to the OAuth trio below |
|
|
81
83
|
| `--chrome-client-id <id>` | `CHROME_CLIENT_ID` | OAuth2 client ID |
|
|
82
84
|
| `--chrome-client-secret <secret>` | `CHROME_CLIENT_SECRET` | OAuth2 client secret |
|
|
83
85
|
| `--chrome-refresh-token <token>` | `CHROME_REFRESH_TOKEN` | OAuth2 refresh token |
|
|
84
|
-
| `--chrome-publish-target <target>` | `CHROME_PUBLISH_TARGET` | `"default"` or `"trustedTesters"` |
|
|
85
86
|
| `--chrome-deploy-percentage <n>` | `CHROME_DEPLOY_PERCENTAGE` | Staged rollout percentage (1-100) |
|
|
86
87
|
| `--chrome-review-exemption` | `CHROME_REVIEW_EXEMPTION` | Request expedited review |
|
|
87
88
|
| `--chrome-skip-submit-review` | `CHROME_SKIP_SUBMIT_REVIEW` | Upload only, skip publish step |
|
|
88
89
|
|
|
90
|
+
Chrome accepts two credential shapes: a Google Cloud **service account** JSON key
|
|
91
|
+
(recommended for CI; add the account's email to your publisher under
|
|
92
|
+
dev console > Account, one per publisher) or the classic OAuth2
|
|
93
|
+
client ID / client secret / refresh token trio. When both are set, the service
|
|
94
|
+
account is used. Service accounts avoid the 7-day refresh-token expiry that
|
|
95
|
+
affects OAuth clients whose consent screen is in "Testing" status.
|
|
96
|
+
|
|
89
97
|
### Firefox AMO
|
|
90
98
|
|
|
91
99
|
| Flag | Env var | Description |
|
|
@@ -156,7 +164,7 @@ const result = await deploy({
|
|
|
156
164
|
clientId: "123.apps.googleusercontent.com",
|
|
157
165
|
clientSecret: "GOCSPX-xxx",
|
|
158
166
|
refreshToken: "1//0xxx",
|
|
159
|
-
|
|
167
|
+
publisherId: "00000000-0000-0000-0000-000000000000", // required
|
|
160
168
|
deployPercentage: 10, // optional: staged rollout
|
|
161
169
|
},
|
|
162
170
|
firefox: {
|
package/dist/module.js
CHANGED
|
@@ -59,21 +59,28 @@ const external_zod_namespaceObject = require("zod");
|
|
|
59
59
|
const chromeOptionsSchema = external_zod_namespaceObject.z.object({
|
|
60
60
|
zip: external_zod_namespaceObject.z.string().min(1),
|
|
61
61
|
extensionId: external_zod_namespaceObject.z.string().min(1).trim(),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"trustedTesters"
|
|
68
|
-
]).default("default"),
|
|
62
|
+
serviceAccountJson: external_zod_namespaceObject.z.string().trim().default(""),
|
|
63
|
+
clientId: external_zod_namespaceObject.z.string().trim().default(""),
|
|
64
|
+
clientSecret: external_zod_namespaceObject.z.string().trim().default(""),
|
|
65
|
+
refreshToken: external_zod_namespaceObject.z.string().trim().default(""),
|
|
66
|
+
publisherId: external_zod_namespaceObject.z.string().min(1).trim(),
|
|
69
67
|
deployPercentage: external_zod_namespaceObject.z.number().int().min(1).max(100).optional(),
|
|
70
68
|
reviewExemption: external_zod_namespaceObject.z.boolean().default(false),
|
|
71
|
-
skipSubmitReview: external_zod_namespaceObject.z.boolean().default(false)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"
|
|
76
|
-
|
|
69
|
+
skipSubmitReview: external_zod_namespaceObject.z.boolean().default(false)
|
|
70
|
+
}).superRefine((val, ctx)=>{
|
|
71
|
+
if (val.serviceAccountJson) return;
|
|
72
|
+
const missing = [
|
|
73
|
+
"clientId",
|
|
74
|
+
"clientSecret",
|
|
75
|
+
"refreshToken"
|
|
76
|
+
].filter((k)=>!val[k]);
|
|
77
|
+
for (const key of missing)ctx.addIssue({
|
|
78
|
+
code: external_zod_namespaceObject.z.ZodIssueCode.custom,
|
|
79
|
+
path: [
|
|
80
|
+
key
|
|
81
|
+
],
|
|
82
|
+
message: "Required unless a service account key is provided (CHROME_SERVICE_ACCOUNT_JSON)."
|
|
83
|
+
});
|
|
77
84
|
});
|
|
78
85
|
const firefoxOptionsSchema = external_zod_namespaceObject.z.object({
|
|
79
86
|
zip: external_zod_namespaceObject.z.string().min(1),
|
|
@@ -117,15 +124,14 @@ function resolveConfig(flags) {
|
|
|
117
124
|
chrome: null == chromeZip ? void 0 : {
|
|
118
125
|
zip: chromeZip,
|
|
119
126
|
extensionId: flags.chromeExtensionId ?? envStr("CHROME_EXTENSION_ID") ?? "",
|
|
127
|
+
serviceAccountJson: flags.chromeServiceAccountJson ?? envStr("CHROME_SERVICE_ACCOUNT_JSON") ?? "",
|
|
120
128
|
clientId: flags.chromeClientId ?? envStr("CHROME_CLIENT_ID") ?? "",
|
|
121
129
|
clientSecret: flags.chromeClientSecret ?? envStr("CHROME_CLIENT_SECRET") ?? "",
|
|
122
130
|
refreshToken: flags.chromeRefreshToken ?? envStr("CHROME_REFRESH_TOKEN") ?? "",
|
|
123
|
-
|
|
131
|
+
publisherId: flags.chromePublisherId ?? envStr("CHROME_PUBLISHER_ID") ?? "",
|
|
124
132
|
deployPercentage: flags.chromeDeployPercentage ?? envInt("CHROME_DEPLOY_PERCENTAGE"),
|
|
125
133
|
reviewExemption: flags.chromeReviewExemption ?? envBool("CHROME_REVIEW_EXEMPTION"),
|
|
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")
|
|
134
|
+
skipSubmitReview: flags.chromeSkipSubmitReview ?? envBool("CHROME_SKIP_SUBMIT_REVIEW")
|
|
129
135
|
},
|
|
130
136
|
firefox: null == firefoxZip ? void 0 : {
|
|
131
137
|
zip: firefoxZip,
|
|
@@ -149,10 +155,11 @@ function pathToEnvName(segments) {
|
|
|
149
155
|
}
|
|
150
156
|
const SETUP_HINTS = {
|
|
151
157
|
CHROME_EXTENSION_ID: "Find this in the Chrome Web Store Developer Dashboard URL for your extension.",
|
|
158
|
+
CHROME_SERVICE_ACCOUNT_JSON: "Optional alternative to OAuth: a Google Cloud service account JSON key (content or file path), added to your publisher under dev console > Account.",
|
|
152
159
|
CHROME_CLIENT_ID: "Create OAuth 2.0 credentials at https://console.cloud.google.com/apis/credentials",
|
|
153
160
|
CHROME_CLIENT_SECRET: "Generated alongside the Client ID in the Google Cloud Console.",
|
|
154
161
|
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>).
|
|
162
|
+
CHROME_PUBLISHER_ID: "The publisher UUID from your Chrome Web Store dev console URL (chrome.google.com/webstore/devconsole/<UUID>).",
|
|
156
163
|
CHROME_ZIP: "Path to the .zip file built for Chrome Web Store upload.",
|
|
157
164
|
FIREFOX_EXTENSION_ID: "The addon GUID ({uuid} format) or email-style ID from your addon's AMO page.",
|
|
158
165
|
FIREFOX_JWT_ISSUER: "API key from https://addons.mozilla.org/developers/addon/api/key/",
|
|
@@ -193,6 +200,8 @@ function envInt(name) {
|
|
|
193
200
|
const val = process.env[name];
|
|
194
201
|
return null == val ? void 0 : parseInt(val, 10);
|
|
195
202
|
}
|
|
203
|
+
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
204
|
+
var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
|
|
196
205
|
const promises_namespaceObject = require("node:fs/promises");
|
|
197
206
|
var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
|
|
198
207
|
const DEFAULT_TIMEOUT_MS = 60000;
|
|
@@ -268,27 +277,64 @@ function logDryStep(scope, step, details) {
|
|
|
268
277
|
const UPLOAD_TIMEOUT_MS = 120000;
|
|
269
278
|
const PUBLISH_TIMEOUT_MS = 60000;
|
|
270
279
|
const CWS_OAUTH_URL = "https://oauth2.googleapis.com/token";
|
|
271
|
-
const CWS_UPLOAD_BASE = "https://www.googleapis.com/upload/chromewebstore/v1.1/items";
|
|
272
|
-
const CWS_PUBLISH_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
|
|
273
|
-
const CWS_ITEMS_BASE = "https://www.googleapis.com/chromewebstore/v1.1/items";
|
|
274
280
|
const CWS_V2_BASE = "https://chromewebstore.googleapis.com/v2";
|
|
275
281
|
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
282
|
function v2ItemName(publisherId, extensionId) {
|
|
281
283
|
return `publishers/${encodeURIComponent(publisherId)}/items/${encodeURIComponent(extensionId)}`;
|
|
282
284
|
}
|
|
283
285
|
function requirePublisherId(publisherId, op) {
|
|
284
286
|
const id = String(publisherId || "").trim();
|
|
285
|
-
if (!id) throw new Error(`Chrome ${op}
|
|
287
|
+
if (!id) throw new Error(`Chrome ${op} needs a publisher ID (the UUID from your Chrome Web Store dev console URL).`);
|
|
286
288
|
return id;
|
|
287
289
|
}
|
|
288
290
|
function chromeStoreUrl(extensionId) {
|
|
289
291
|
return `https://chromewebstore.google.com/detail/${extensionId}`;
|
|
290
292
|
}
|
|
291
|
-
|
|
293
|
+
const CWS_SCOPE = "https://www.googleapis.com/auth/chromewebstore";
|
|
294
|
+
async function resolveServiceAccountKey(value) {
|
|
295
|
+
const trimmed = value.trim();
|
|
296
|
+
const raw = trimmed.startsWith("{") ? trimmed : await promises_default().readFile(trimmed, "utf-8").catch(()=>{
|
|
297
|
+
throw new Error(`Chrome service account key: "${trimmed.slice(0, 80)}" is neither JSON nor a readable file path.`);
|
|
298
|
+
});
|
|
299
|
+
let parsed;
|
|
300
|
+
try {
|
|
301
|
+
parsed = JSON.parse(raw);
|
|
302
|
+
} catch {
|
|
303
|
+
throw new Error("Chrome service account key is not valid JSON.");
|
|
304
|
+
}
|
|
305
|
+
if (!parsed.client_email || !parsed.private_key) throw new Error("Chrome service account key is missing client_email or private_key. Download the JSON key from the Google Cloud Console.");
|
|
306
|
+
return {
|
|
307
|
+
client_email: parsed.client_email,
|
|
308
|
+
private_key: parsed.private_key
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async function authenticateServiceAccount(serviceAccountJson) {
|
|
312
|
+
const key = await resolveServiceAccountKey(serviceAccountJson);
|
|
313
|
+
const iat = Math.floor(Date.now() / 1000);
|
|
314
|
+
const encode = (obj)=>Buffer.from(JSON.stringify(obj)).toString("base64url");
|
|
315
|
+
const unsigned = `${encode({
|
|
316
|
+
alg: "RS256",
|
|
317
|
+
typ: "JWT"
|
|
318
|
+
})}.${encode({
|
|
319
|
+
iss: key.client_email,
|
|
320
|
+
scope: CWS_SCOPE,
|
|
321
|
+
aud: CWS_OAUTH_URL,
|
|
322
|
+
iat,
|
|
323
|
+
exp: iat + 3600
|
|
324
|
+
})}`;
|
|
325
|
+
const signature = external_node_crypto_default().createSign("RSA-SHA256").update(unsigned).sign(key.private_key).toString("base64url");
|
|
326
|
+
return fetchJson(CWS_OAUTH_URL, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
body: new URLSearchParams({
|
|
329
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
330
|
+
assertion: `${unsigned}.${signature}`
|
|
331
|
+
}).toString(),
|
|
332
|
+
headers: {
|
|
333
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
async function authenticateOauth(clientId, clientSecret, refreshToken) {
|
|
292
338
|
return fetchJson(CWS_OAUTH_URL, {
|
|
293
339
|
method: "POST",
|
|
294
340
|
body: JSON.stringify({
|
|
@@ -303,27 +349,21 @@ async function authenticate(clientId, clientSecret, refreshToken) {
|
|
|
303
349
|
}
|
|
304
350
|
});
|
|
305
351
|
}
|
|
306
|
-
async function
|
|
352
|
+
async function authenticate(creds) {
|
|
353
|
+
if (creds.serviceAccountJson) return authenticateServiceAccount(creds.serviceAccountJson);
|
|
354
|
+
if (creds.clientId && creds.clientSecret && creds.refreshToken) return authenticateOauth(creds.clientId, creds.clientSecret, creds.refreshToken);
|
|
355
|
+
throw new Error("Chrome credentials missing: provide a service account key (CHROME_SERVICE_ACCOUNT_JSON) or the OAuth client ID, client secret, and refresh token.");
|
|
356
|
+
}
|
|
357
|
+
async function chrome_upload(extensionId, publisherId, zipPath, token) {
|
|
307
358
|
const body = await promises_default().readFile(zipPath);
|
|
308
|
-
|
|
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
|
-
}
|
|
359
|
+
const url = `${CWS_V2_UPLOAD_BASE}/${v2ItemName(publisherId, extensionId)}:upload`;
|
|
323
360
|
const res = await fetch(url, {
|
|
324
|
-
method,
|
|
361
|
+
method: "POST",
|
|
325
362
|
body,
|
|
326
|
-
headers
|
|
363
|
+
headers: {
|
|
364
|
+
Authorization: `${token.token_type} ${token.access_token}`,
|
|
365
|
+
"Content-Type": "application/zip"
|
|
366
|
+
},
|
|
327
367
|
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
|
|
328
368
|
});
|
|
329
369
|
if (!res.ok) {
|
|
@@ -332,44 +372,22 @@ async function chrome_upload(extensionId, zipPath, token, ctx) {
|
|
|
332
372
|
}
|
|
333
373
|
}
|
|
334
374
|
async function publish(params) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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}`);
|
|
375
|
+
const reqBody = {
|
|
376
|
+
publishType: "DEFAULT_PUBLISH"
|
|
377
|
+
};
|
|
378
|
+
if (null != params.deployPercentage) reqBody.deployInfos = [
|
|
379
|
+
{
|
|
380
|
+
deployPercentage: params.deployPercentage
|
|
359
381
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
url.searchParams.set("publishTarget", params.publishTarget);
|
|
364
|
-
if (null != params.deployPercentage) url.searchParams.set("deployPercentage", String(params.deployPercentage));
|
|
365
|
-
if (null != params.reviewExemption) url.searchParams.set("reviewExemption", String(params.reviewExemption));
|
|
366
|
-
const res = await fetch(url.href, {
|
|
382
|
+
];
|
|
383
|
+
if (params.reviewExemption) reqBody.skipReview = true;
|
|
384
|
+
const res = await fetch(`${CWS_V2_BASE}/${v2ItemName(params.publisherId, params.extensionId)}:publish`, {
|
|
367
385
|
method: "POST",
|
|
368
386
|
headers: {
|
|
369
387
|
Authorization: `${params.token.token_type} ${params.token.access_token}`,
|
|
370
|
-
"
|
|
371
|
-
"Content-Length": "0"
|
|
388
|
+
"Content-Type": "application/json"
|
|
372
389
|
},
|
|
390
|
+
body: JSON.stringify(reqBody),
|
|
373
391
|
signal: AbortSignal.timeout(PUBLISH_TIMEOUT_MS)
|
|
374
392
|
});
|
|
375
393
|
if (!res.ok) {
|
|
@@ -380,9 +398,9 @@ async function publish(params) {
|
|
|
380
398
|
async function publishChrome(options, dryRun) {
|
|
381
399
|
await assertFilePresent(options.zip);
|
|
382
400
|
const zipSize = await getFileSize(options.zip);
|
|
383
|
-
const
|
|
384
|
-
log("chrome", `Authenticating with Chrome Web Store (API ${
|
|
385
|
-
const token = await authenticate(options
|
|
401
|
+
const publisherId = requirePublisherId(options.publisherId, "submit");
|
|
402
|
+
log("chrome", `Authenticating with Chrome Web Store (API v2, ${options.serviceAccountJson ? "service account" : "OAuth"})`);
|
|
403
|
+
const token = await authenticate(options);
|
|
386
404
|
const outcome = {
|
|
387
405
|
storeUrl: chromeStoreUrl(options.extensionId),
|
|
388
406
|
submissionId: options.extensionId
|
|
@@ -390,17 +408,13 @@ async function publishChrome(options, dryRun) {
|
|
|
390
408
|
if (dryRun) {
|
|
391
409
|
logDryStep("chrome", "Authentication succeeded", {
|
|
392
410
|
tokenType: token.token_type,
|
|
393
|
-
scope: "chromewebstore"
|
|
394
|
-
apiVersion
|
|
411
|
+
scope: "chromewebstore"
|
|
395
412
|
});
|
|
396
413
|
logDryStep("chrome", "Would upload ZIP", {
|
|
397
414
|
file: options.zip,
|
|
398
|
-
size: formatBytes(zipSize)
|
|
399
|
-
apiVersion
|
|
415
|
+
size: formatBytes(zipSize)
|
|
400
416
|
});
|
|
401
417
|
options.skipSubmitReview ? logDryStep("chrome", "Would skip publish (skipSubmitReview=true)") : logDryStep("chrome", "Would publish for review", {
|
|
402
|
-
apiVersion,
|
|
403
|
-
publishTarget: options.publishTarget ?? "default",
|
|
404
418
|
deployPercentage: options.deployPercentage,
|
|
405
419
|
reviewExemption: options.reviewExemption
|
|
406
420
|
});
|
|
@@ -408,10 +422,7 @@ async function publishChrome(options, dryRun) {
|
|
|
408
422
|
return outcome;
|
|
409
423
|
}
|
|
410
424
|
log("chrome", `Uploading ZIP file (${formatBytes(zipSize)})`);
|
|
411
|
-
await chrome_upload(options.extensionId, options.zip, token
|
|
412
|
-
apiVersion,
|
|
413
|
-
publisherId: options.publisherId
|
|
414
|
-
});
|
|
425
|
+
await chrome_upload(options.extensionId, publisherId, options.zip, token);
|
|
415
426
|
if (options.skipSubmitReview) {
|
|
416
427
|
log("chrome", "Skipping publish step (skipSubmitReview=true)");
|
|
417
428
|
return outcome;
|
|
@@ -419,77 +430,54 @@ async function publishChrome(options, dryRun) {
|
|
|
419
430
|
log("chrome", "Publishing extension");
|
|
420
431
|
await publish({
|
|
421
432
|
extensionId: options.extensionId,
|
|
422
|
-
|
|
433
|
+
publisherId,
|
|
423
434
|
token,
|
|
424
435
|
deployPercentage: options.deployPercentage,
|
|
425
|
-
reviewExemption: options.reviewExemption
|
|
426
|
-
apiVersion,
|
|
427
|
-
publisherId: options.publisherId
|
|
436
|
+
reviewExemption: options.reviewExemption
|
|
428
437
|
});
|
|
429
438
|
return outcome;
|
|
430
439
|
}
|
|
431
440
|
async function getChromeItem(params) {
|
|
432
|
-
|
|
441
|
+
var _rev_distributionChannels_, _rev_distributionChannels;
|
|
442
|
+
const token = await authenticate(params);
|
|
433
443
|
const auth = {
|
|
434
444
|
Authorization: `${token.token_type} ${token.access_token}`
|
|
435
445
|
};
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
}
|
|
451
|
-
const url = `${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=${params.projection ?? "DRAFT"}`;
|
|
452
|
-
return fetchJson(url, {
|
|
453
|
-
headers: {
|
|
454
|
-
...auth,
|
|
455
|
-
"x-goog-api-version": "2"
|
|
456
|
-
}
|
|
446
|
+
const pub = requirePublisherId(params.publisherId, "status");
|
|
447
|
+
const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
|
|
448
|
+
headers: auth
|
|
457
449
|
});
|
|
450
|
+
const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
|
|
451
|
+
return {
|
|
452
|
+
uploadState: status.lastAsyncUploadState,
|
|
453
|
+
crxVersion: null == rev ? void 0 : null == (_rev_distributionChannels = rev.distributionChannels) ? void 0 : null == (_rev_distributionChannels_ = _rev_distributionChannels[0]) ? void 0 : _rev_distributionChannels_.crxVersion,
|
|
454
|
+
publicKey: status.publicKey,
|
|
455
|
+
state: null == rev ? void 0 : rev.state
|
|
456
|
+
};
|
|
458
457
|
}
|
|
459
458
|
async function verifyChromeCredentials(params) {
|
|
460
459
|
let token;
|
|
461
460
|
try {
|
|
462
|
-
token = await authenticate(params
|
|
461
|
+
token = await authenticate(params);
|
|
463
462
|
} catch (err) {
|
|
463
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
464
464
|
return {
|
|
465
465
|
ok: false,
|
|
466
|
-
message: `OAuth token exchange failed. Verify your Client ID, Client Secret, and Refresh Token are correct.\n${
|
|
466
|
+
message: params.serviceAccountJson ? `Service account token exchange failed. Verify the JSON key is intact and the account was added to your publisher (dev console > Account).\n${detail}` : `OAuth token exchange failed. Verify your Client ID, Client Secret, and Refresh Token are correct.\n${detail}`
|
|
467
467
|
};
|
|
468
468
|
}
|
|
469
469
|
const auth = {
|
|
470
470
|
Authorization: `${token.token_type} ${token.access_token}`
|
|
471
471
|
};
|
|
472
|
-
const version = resolveChromeApiVersion(params);
|
|
473
472
|
try {
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
}
|
|
485
|
-
const item = await fetchJson(`${CWS_ITEMS_BASE}/${encodeURIComponent(params.extensionId)}?projection=DRAFT`, {
|
|
473
|
+
const pub = requirePublisherId(params.publisherId, "verify");
|
|
474
|
+
const status = await fetchJson(`${CWS_V2_BASE}/${v2ItemName(pub, params.extensionId)}:fetchStatus`, {
|
|
486
475
|
headers: auth
|
|
487
476
|
});
|
|
488
|
-
const
|
|
477
|
+
const rev = status.submittedItemRevisionStatus || status.publishedItemRevisionStatus;
|
|
489
478
|
return {
|
|
490
479
|
ok: true,
|
|
491
|
-
message: `Credentials verified.
|
|
492
|
-
extensionTitle: title
|
|
480
|
+
message: `Credentials verified (Chrome Web Store API v2). Item state: ${(null == rev ? void 0 : rev.state) || "unknown"}.`
|
|
493
481
|
};
|
|
494
482
|
} catch (err) {
|
|
495
483
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -499,7 +487,7 @@ async function verifyChromeCredentials(params) {
|
|
|
499
487
|
};
|
|
500
488
|
if (msg.includes("403")) return {
|
|
501
489
|
ok: false,
|
|
502
|
-
message:
|
|
490
|
+
message: params.serviceAccountJson ? "Access denied. Check the publisher ID, and that this service account's email was added to the publisher (dev console > Account)." : "Access denied. Check the publisher ID matches this extension's account, and that the extension ID is correct."
|
|
503
491
|
};
|
|
504
492
|
return {
|
|
505
493
|
ok: false,
|
|
@@ -512,8 +500,6 @@ function delay(ms) {
|
|
|
512
500
|
setTimeout(resolve, ms);
|
|
513
501
|
});
|
|
514
502
|
}
|
|
515
|
-
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
516
|
-
var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
|
|
517
503
|
const AMO_BASE = "https://addons.mozilla.org/api/v5/addons";
|
|
518
504
|
function signJwt(issuer, secret, expiresInSeconds = 30) {
|
|
519
505
|
const header = {
|
|
@@ -989,10 +975,12 @@ GLOBAL
|
|
|
989
975
|
CHROME
|
|
990
976
|
--chrome-zip <path> Path to Chrome extension ZIP
|
|
991
977
|
--chrome-extension-id <id> Chrome extension ID
|
|
978
|
+
--chrome-publisher-id <uuid> Publisher UUID from the dev console (required)
|
|
979
|
+
--chrome-service-account-json <v> Service account JSON key content or file path
|
|
980
|
+
(alternative to the OAuth flags below)
|
|
992
981
|
--chrome-client-id <id> OAuth2 client ID
|
|
993
982
|
--chrome-client-secret <secret> OAuth2 client secret
|
|
994
983
|
--chrome-refresh-token <token> OAuth2 refresh token
|
|
995
|
-
--chrome-publish-target <target> "default" or "trustedTesters"
|
|
996
984
|
--chrome-deploy-percentage <n> Staged rollout percentage (1-100)
|
|
997
985
|
--chrome-review-exemption Request expedited review
|
|
998
986
|
--chrome-skip-submit-review Upload only, skip publish
|
|
@@ -1257,11 +1245,13 @@ async function collectChromeCredentials(deps) {
|
|
|
1257
1245
|
io.log(" 2. OAuth 2.0 credentials (application type: Desktop app):");
|
|
1258
1246
|
io.log(" https://console.cloud.google.com/apis/credentials");
|
|
1259
1247
|
io.log(" 3. Your extension ID from the Chrome Web Store Developer Dashboard.");
|
|
1248
|
+
io.log(" 4. Your publisher ID (the UUID in your dev console URL: chrome.google.com/webstore/devconsole/<UUID>).");
|
|
1260
1249
|
io.log("");
|
|
1261
1250
|
io.log("This wizard will take your Client ID + Secret and walk you through");
|
|
1262
1251
|
io.log("generating a refresh token automatically.");
|
|
1263
1252
|
io.log("");
|
|
1264
1253
|
const extensionId = await requireValue(io, "Chrome extension ID", "(32-character ID from the CWS dashboard URL)");
|
|
1254
|
+
const publisherId = await requireValue(io, "Chrome publisher ID", "(UUID from your dev console URL)");
|
|
1265
1255
|
const clientId = await requireValue(io, "OAuth Client ID");
|
|
1266
1256
|
const clientSecret = await requireValue(io, "OAuth Client Secret", void 0, {
|
|
1267
1257
|
secret: true
|
|
@@ -1269,6 +1259,7 @@ async function collectChromeCredentials(deps) {
|
|
|
1269
1259
|
const refreshToken = await obtainChromeRefreshToken(deps, clientId, clientSecret);
|
|
1270
1260
|
return {
|
|
1271
1261
|
extensionId,
|
|
1262
|
+
publisherId,
|
|
1272
1263
|
clientId,
|
|
1273
1264
|
clientSecret,
|
|
1274
1265
|
refreshToken
|
|
@@ -1400,7 +1391,7 @@ async function runInit(deps, options = {}) {
|
|
|
1400
1391
|
io.log("");
|
|
1401
1392
|
if ("chrome" === store) {
|
|
1402
1393
|
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}`, "");
|
|
1394
|
+
envLines.push(`CHROME_EXTENSION_ID=${creds.extensionId}`, `CHROME_PUBLISHER_ID=${creds.publisherId}`, `CHROME_CLIENT_ID=${creds.clientId}`, `CHROME_CLIENT_SECRET=${creds.clientSecret}`, `CHROME_REFRESH_TOKEN=${creds.refreshToken}`, "");
|
|
1404
1395
|
if (!options.skipVerify) {
|
|
1405
1396
|
const verify = deps.verifyChrome ?? verifyChromeCredentials;
|
|
1406
1397
|
verified.chrome = normalizeVerify(await verify(creds));
|
|
@@ -1582,16 +1573,21 @@ async function runWatch(flags) {
|
|
|
1582
1573
|
const timeoutMs = (flags.watchTimeout ?? 3600) * 1000;
|
|
1583
1574
|
let terminal;
|
|
1584
1575
|
try {
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1576
|
+
if ("chrome" === store) {
|
|
1577
|
+
const serviceAccountJson = flags.chromeServiceAccountJson ?? process.env.CHROME_SERVICE_ACCOUNT_JSON;
|
|
1578
|
+
terminal = await watchChromeSubmission({
|
|
1579
|
+
serviceAccountJson,
|
|
1580
|
+
clientId: serviceAccountJson ? flags.chromeClientId : mustEnv("CHROME_CLIENT_ID", flags.chromeClientId),
|
|
1581
|
+
clientSecret: serviceAccountJson ? flags.chromeClientSecret : mustEnv("CHROME_CLIENT_SECRET", flags.chromeClientSecret),
|
|
1582
|
+
refreshToken: serviceAccountJson ? flags.chromeRefreshToken : mustEnv("CHROME_REFRESH_TOKEN", flags.chromeRefreshToken),
|
|
1583
|
+
extensionId: mustEnv("CHROME_EXTENSION_ID", flags.chromeExtensionId),
|
|
1584
|
+
publisherId: mustEnv("CHROME_PUBLISHER_ID", flags.chromePublisherId)
|
|
1585
|
+
}, {
|
|
1586
|
+
pollIntervalMs,
|
|
1587
|
+
timeoutMs,
|
|
1588
|
+
onEvent
|
|
1589
|
+
});
|
|
1590
|
+
} else terminal = "firefox" === store ? await watchFirefoxSubmission({
|
|
1595
1591
|
jwtIssuer: mustEnv("FIREFOX_JWT_ISSUER", flags.firefoxJwtIssuer),
|
|
1596
1592
|
jwtSecret: mustEnv("FIREFOX_JWT_SECRET", flags.firefoxJwtSecret),
|
|
1597
1593
|
extensionId: mustEnv("FIREFOX_EXTENSION_ID", flags.firefoxExtensionId),
|