@stacksjs/browser-extension 0.70.120 → 0.70.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/dist/chrome-web-store.d.ts +55 -0
- package/dist/chrome-web-store.js +137 -0
- package/dist/firefox-addons.d.ts +22 -0
- package/dist/firefox-addons.js +82 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types.d.ts +18 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -17,6 +17,52 @@ buddy extension:build --target safari # → dist-safari/ (browser.* namespace,
|
|
|
17
17
|
buddy extension:package # build + zip store-ready archives
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
## Store publishing
|
|
21
|
+
|
|
22
|
+
Stacks owns the store-specific upload clients as well as the builds. Chrome
|
|
23
|
+
uses Web Store API v2 with a service account; Firefox uses Mozilla's official
|
|
24
|
+
`web-ext` client and AMO v5 API; Safari uses App Store Connect and Xcode.
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
buddy extension:chrome:status
|
|
28
|
+
buddy extension:chrome:publish # build, zip, upload, submit for review
|
|
29
|
+
buddy extension:chrome:publish --upload-only
|
|
30
|
+
buddy extension:firefox:publish # build, submit, sign through AMO
|
|
31
|
+
buddy extension:safari:provision # register Bundle IDs + check app record
|
|
32
|
+
buddy extension:safari:publish
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Chrome reads `CHROME_WEB_STORE_SERVICE_ACCOUNT_PATH` (or
|
|
36
|
+
`GOOGLE_APPLICATION_CREDENTIALS`) and the configured
|
|
37
|
+
`chromeWebStore.publisherId`/`itemId`. The API only updates existing items, so
|
|
38
|
+
create the initial Developer Dashboard item once and link the service-account
|
|
39
|
+
email to the publisher account. Firefox reads `AMO_JWT_ISSUER` and
|
|
40
|
+
`AMO_JWT_SECRET`; `web-ext` can create the initial listing when
|
|
41
|
+
`firefoxAddons.license` and `firefoxAddons.categories` are configured.
|
|
42
|
+
|
|
43
|
+
For tag-driven publication, call Stacks' reusable workflow from the extension
|
|
44
|
+
repository instead of duplicating store orchestration:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
jobs:
|
|
48
|
+
publish:
|
|
49
|
+
uses: stacksjs/stacks/.github/workflows/browser-extension-release.yml@v0.70.122
|
|
50
|
+
with:
|
|
51
|
+
chrome-publisher-id: ${{ vars.CHROME_WEB_STORE_PUBLISHER_ID }}
|
|
52
|
+
safari-enabled: ${{ vars.ENABLE_SAFARI_PUBLISH == 'true' }}
|
|
53
|
+
secrets:
|
|
54
|
+
CHROME_WEB_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.CHROME_WEB_STORE_SERVICE_ACCOUNT_JSON }}
|
|
55
|
+
AMO_JWT_ISSUER: ${{ secrets.AMO_JWT_ISSUER }}
|
|
56
|
+
AMO_JWT_SECRET: ${{ secrets.AMO_JWT_SECRET }}
|
|
57
|
+
APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
|
|
58
|
+
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
59
|
+
APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
It packages every configured target, publishes Chrome and Firefox in
|
|
63
|
+
independent jobs, optionally uploads Safari with stable Xcode, and creates the
|
|
64
|
+
GitHub Release only after the enabled stores succeed.
|
|
65
|
+
|
|
20
66
|
## Safari
|
|
21
67
|
|
|
22
68
|
Safari Web Extensions ship inside a macOS app, so the safari target has two
|
|
@@ -54,6 +100,8 @@ export default defineExtension({
|
|
|
54
100
|
name: 'My Extension',
|
|
55
101
|
description: 'Does something useful.',
|
|
56
102
|
geckoId: 'my-ext@example.com', // required to ship on Firefox
|
|
103
|
+
chromeWebStore: { publisherId: 'publisher-id', itemId: 'extension-id' },
|
|
104
|
+
firefoxAddons: { license: 'MIT', categories: ['privacy-security'] },
|
|
57
105
|
safariBundleId: 'com.example.MyExtension', // Safari container app bundle id
|
|
58
106
|
targets: ['chrome', 'firefox'],
|
|
59
107
|
|
|
@@ -109,6 +157,8 @@ import {
|
|
|
109
157
|
buildExtension,
|
|
110
158
|
buildAllTargets,
|
|
111
159
|
packageExtension,
|
|
160
|
+
publishChromeExtension,
|
|
161
|
+
publishFirefoxExtension,
|
|
112
162
|
generateManifest,
|
|
113
163
|
rewriteBrowserNamespace,
|
|
114
164
|
scaffoldSafariApp,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ChromeWebStoreConfig, ExtensionConfig } from './types';
|
|
2
|
+
/** Build the RS256 assertion Google exchanges for a short-lived OAuth token. */
|
|
3
|
+
export declare function chromeWebStoreServiceAccountAssertion(serviceAccount: GoogleServiceAccount, now?: unknown): string;
|
|
4
|
+
/** Build, package, upload, and optionally submit an existing Chrome Web Store item. */
|
|
5
|
+
export declare function publishChromeExtension(config: ExtensionConfig, options: ChromeWebStorePublishOptions): Promise<{ packagePath: string, upload: ChromeUploadResult, publish?: ChromePublishResult }>;
|
|
6
|
+
export declare interface GoogleServiceAccount {
|
|
7
|
+
client_email: string
|
|
8
|
+
private_key: string
|
|
9
|
+
token_uri?: string
|
|
10
|
+
}
|
|
11
|
+
export declare interface ChromeWebStoreAuth {
|
|
12
|
+
accessToken?: string
|
|
13
|
+
serviceAccountPath?: string
|
|
14
|
+
}
|
|
15
|
+
export declare interface ChromeWebStoreClientOptions extends ChromeWebStoreAuth {
|
|
16
|
+
baseUrl?: string
|
|
17
|
+
fetch?: typeof globalThis.fetch
|
|
18
|
+
now?: () => number
|
|
19
|
+
wait?: (milliseconds: number) => Promise<void>
|
|
20
|
+
}
|
|
21
|
+
export declare interface ChromeWebStoreStatus {
|
|
22
|
+
name: string
|
|
23
|
+
itemId: string
|
|
24
|
+
lastAsyncUploadState?: 'UPLOAD_STATE_UNSPECIFIED' | 'SUCCEEDED' | 'IN_PROGRESS' | 'FAILED' | 'NOT_FOUND'
|
|
25
|
+
publishedItemRevisionStatus?: { state: string, distributionChannels?: Array<{ deployPercentage: number, crxVersion: string }> }
|
|
26
|
+
submittedItemRevisionStatus?: { state: string, distributionChannels?: Array<{ deployPercentage: number, crxVersion: string }> }
|
|
27
|
+
takenDown?: boolean
|
|
28
|
+
warned?: boolean
|
|
29
|
+
}
|
|
30
|
+
export declare interface ChromeUploadResult {
|
|
31
|
+
name: string
|
|
32
|
+
itemId: string
|
|
33
|
+
crxVersion?: string
|
|
34
|
+
uploadState: 'UPLOAD_STATE_UNSPECIFIED' | 'SUCCEEDED' | 'IN_PROGRESS' | 'FAILED' | 'NOT_FOUND'
|
|
35
|
+
}
|
|
36
|
+
export declare interface ChromePublishResult {
|
|
37
|
+
name: string
|
|
38
|
+
itemId: string
|
|
39
|
+
state: string
|
|
40
|
+
warningInfo?: { warnings?: Array<{ reason: string, description: string }> }
|
|
41
|
+
}
|
|
42
|
+
export declare interface ChromeWebStorePublishOptions extends ChromeWebStoreClientOptions {
|
|
43
|
+
version: string
|
|
44
|
+
cwd?: string
|
|
45
|
+
packagePath?: string
|
|
46
|
+
uploadOnly?: boolean
|
|
47
|
+
blockOnWarnings?: boolean
|
|
48
|
+
}
|
|
49
|
+
export declare class ChromeWebStoreClient {
|
|
50
|
+
constructor(options?: ChromeWebStoreClientOptions);
|
|
51
|
+
fetchStatus(config: ChromeWebStoreConfig): Promise<ChromeWebStoreStatus>;
|
|
52
|
+
upload(config: ChromeWebStoreConfig, packagePath: string): Promise<ChromeUploadResult>;
|
|
53
|
+
publish(config: ChromeWebStoreConfig, options?: { blockOnWarnings?: boolean }): Promise<ChromePublishResult>;
|
|
54
|
+
waitForUpload(config: ChromeWebStoreConfig, maxAttempts?: number): Promise<ChromeWebStoreStatus>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createPrivateKey, sign } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { packageExtension } from "./package";
|
|
5
|
+
const chromeWebStoreScope = "https://www.googleapis.com/auth/chromewebstore", chromeWebStoreBaseUrl = "https://chromewebstore.googleapis.com";
|
|
6
|
+
function base64urlJson(value) {
|
|
7
|
+
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
8
|
+
}
|
|
9
|
+
export function chromeWebStoreServiceAccountAssertion(serviceAccount, now = Math.floor(Date.now() / 1000)) {
|
|
10
|
+
const tokenUri = serviceAccount.token_uri ?? "https://oauth2.googleapis.com/token", input = `${base64urlJson({ alg: "RS256", typ: "JWT" })}.${base64urlJson({
|
|
11
|
+
iss: serviceAccount.client_email,
|
|
12
|
+
scope: chromeWebStoreScope,
|
|
13
|
+
aud: tokenUri,
|
|
14
|
+
iat: now,
|
|
15
|
+
exp: now + 3600
|
|
16
|
+
})}`;
|
|
17
|
+
let key;
|
|
18
|
+
try {
|
|
19
|
+
key = createPrivateKey(serviceAccount.private_key);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
throw Error(`[browser-extension] Chrome Web Store service-account key could not be parsed: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
+
}
|
|
23
|
+
return `${input}.${sign("sha256", Buffer.from(input), key).toString("base64url")}`;
|
|
24
|
+
}
|
|
25
|
+
async function resolveChromeAccessToken(options) {
|
|
26
|
+
const supplied = options.accessToken ?? process.env.CHROME_WEB_STORE_ACCESS_TOKEN;
|
|
27
|
+
if (supplied)
|
|
28
|
+
return supplied;
|
|
29
|
+
const configuredPath = options.serviceAccountPath ?? process.env.CHROME_WEB_STORE_SERVICE_ACCOUNT_PATH ?? process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
30
|
+
if (!configuredPath)
|
|
31
|
+
throw Error("[browser-extension] missing Chrome Web Store credentials: CHROME_WEB_STORE_SERVICE_ACCOUNT_PATH or CHROME_WEB_STORE_ACCESS_TOKEN");
|
|
32
|
+
const keyPath = resolve(configuredPath);
|
|
33
|
+
if (!existsSync(keyPath))
|
|
34
|
+
throw Error(`[browser-extension] Chrome Web Store service-account key not found: ${keyPath}`);
|
|
35
|
+
let serviceAccount;
|
|
36
|
+
try {
|
|
37
|
+
serviceAccount = JSON.parse(readFileSync(keyPath, "utf8"));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw Error(`[browser-extension] Chrome Web Store service-account JSON could not be parsed: ${error instanceof Error ? error.message : String(error)}`);
|
|
40
|
+
}
|
|
41
|
+
if (!serviceAccount.client_email || !serviceAccount.private_key)
|
|
42
|
+
throw Error("[browser-extension] Chrome Web Store service-account JSON needs client_email and private_key");
|
|
43
|
+
const tokenUri = serviceAccount.token_uri ?? "https://oauth2.googleapis.com/token", response = await (options.fetch ?? globalThis.fetch)(tokenUri, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
46
|
+
body: new URLSearchParams({
|
|
47
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
48
|
+
assertion: chromeWebStoreServiceAccountAssertion(serviceAccount, options.now?.())
|
|
49
|
+
})
|
|
50
|
+
}), body = await response.json().catch(() => ({}));
|
|
51
|
+
if (!response.ok || !body.access_token)
|
|
52
|
+
throw Error(`[browser-extension] Google OAuth token exchange failed (${response.status})${body.error_description ? `: ${body.error_description}` : ""}`);
|
|
53
|
+
return body.access_token;
|
|
54
|
+
}
|
|
55
|
+
function storeItemName(config) {
|
|
56
|
+
return `publishers/${encodeURIComponent(config.publisherId)}/items/${encodeURIComponent(config.itemId)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class ChromeWebStoreClient {
|
|
60
|
+
options;
|
|
61
|
+
baseUrl;
|
|
62
|
+
accessToken;
|
|
63
|
+
constructor(options = {}) {
|
|
64
|
+
this.options = options;
|
|
65
|
+
this.baseUrl = (options.baseUrl ?? chromeWebStoreBaseUrl).replace(/\/$/, "");
|
|
66
|
+
}
|
|
67
|
+
async request(path, init = {}) {
|
|
68
|
+
this.accessToken ??= resolveChromeAccessToken(this.options);
|
|
69
|
+
const response = await (this.options.fetch ?? globalThis.fetch)(`${this.baseUrl}${path}`, {
|
|
70
|
+
...init,
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: `Bearer ${await this.accessToken}`,
|
|
73
|
+
...init.headers
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const body = await response.json().catch(() => ({})), detail = body.error?.message ?? body.message;
|
|
78
|
+
throw Error(`[browser-extension] Chrome Web Store ${init.method ?? "GET"} ${path} failed (${response.status})${detail ? `: ${detail}` : ""}`);
|
|
79
|
+
}
|
|
80
|
+
return await response.json();
|
|
81
|
+
}
|
|
82
|
+
fetchStatus(config) {
|
|
83
|
+
return this.request(`/v2/${storeItemName(config)}:fetchStatus`);
|
|
84
|
+
}
|
|
85
|
+
async upload(config, packagePath) {
|
|
86
|
+
const file = Bun.file(resolve(packagePath));
|
|
87
|
+
if (!await file.exists())
|
|
88
|
+
throw Error(`[browser-extension] Chrome package not found: ${resolve(packagePath)}`);
|
|
89
|
+
return await this.request(`/upload/v2/${storeItemName(config)}:upload`, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "Content-Type": "application/zip" },
|
|
92
|
+
body: new Uint8Array(await file.arrayBuffer())
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
publish(config, options = {}) {
|
|
96
|
+
if (config.deployPercentage !== void 0 && (!Number.isInteger(config.deployPercentage) || config.deployPercentage < 0 || config.deployPercentage > 100))
|
|
97
|
+
throw Error("[browser-extension] chromeWebStore.deployPercentage must be an integer from 0 to 100");
|
|
98
|
+
const body = {
|
|
99
|
+
publishType: config.publishType ?? "DEFAULT_PUBLISH",
|
|
100
|
+
skipReview: config.skipReview ?? !1,
|
|
101
|
+
blockOnWarnings: options.blockOnWarnings ?? !0
|
|
102
|
+
};
|
|
103
|
+
if (config.deployPercentage !== void 0)
|
|
104
|
+
body.deployInfos = [{ deployPercentage: config.deployPercentage }];
|
|
105
|
+
return this.request(`/v2/${storeItemName(config)}:publish`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/json" },
|
|
108
|
+
body: JSON.stringify(body)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async waitForUpload(config, maxAttempts = 30) {
|
|
112
|
+
for (let attempt = 0;attempt < maxAttempts; attempt += 1) {
|
|
113
|
+
const status = await this.fetchStatus(config);
|
|
114
|
+
if (status.lastAsyncUploadState !== "IN_PROGRESS") {
|
|
115
|
+
if (status.lastAsyncUploadState === "FAILED")
|
|
116
|
+
throw Error("[browser-extension] Chrome Web Store package processing failed");
|
|
117
|
+
return status;
|
|
118
|
+
}
|
|
119
|
+
await (this.options.wait ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))))(2000);
|
|
120
|
+
}
|
|
121
|
+
throw Error("[browser-extension] Chrome Web Store package processing timed out");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export async function publishChromeExtension(config, options) {
|
|
125
|
+
if (!config.chromeWebStore)
|
|
126
|
+
throw Error("[browser-extension] Chrome publishing needs chromeWebStore.publisherId and chromeWebStore.itemId in config/extension.ts");
|
|
127
|
+
const cwd = options.cwd ?? process.cwd(), packagePath = options.packagePath ?? await packageExtension(config, { target: "chrome", version: options.version, cwd }), client = new ChromeWebStoreClient(options), upload = await client.upload(config.chromeWebStore, packagePath);
|
|
128
|
+
if (upload.uploadState === "IN_PROGRESS")
|
|
129
|
+
await client.waitForUpload(config.chromeWebStore);
|
|
130
|
+
else if (upload.uploadState !== "SUCCEEDED")
|
|
131
|
+
throw Error(`[browser-extension] Chrome Web Store upload failed with state ${upload.uploadState}`);
|
|
132
|
+
return {
|
|
133
|
+
packagePath,
|
|
134
|
+
upload,
|
|
135
|
+
publish: options.uploadOnly ? void 0 : await client.publish(config.chromeWebStore, { blockOnWarnings: options.blockOnWarnings })
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ExtensionConfig, FirefoxAddonsConfig } from './types';
|
|
2
|
+
/** Metadata accepted by AMO v5 when web-ext creates the first listed version. */
|
|
3
|
+
export declare function firefoxListingMetadata(config: ExtensionConfig, store: FirefoxAddonsConfig): Record<string, unknown> | undefined;
|
|
4
|
+
/** Build and submit a Firefox extension through Mozilla's official web-ext/AMO v5 flow. */
|
|
5
|
+
export declare function publishFirefoxExtension(config: ExtensionConfig, options: FirefoxPublishOptions): Promise<FirefoxPublishResult>;
|
|
6
|
+
export declare interface FirefoxAddonsAuth {
|
|
7
|
+
issuer?: string
|
|
8
|
+
secret?: string
|
|
9
|
+
}
|
|
10
|
+
export declare interface FirefoxPublishOptions extends FirefoxAddonsAuth {
|
|
11
|
+
version: string
|
|
12
|
+
cwd?: string
|
|
13
|
+
build?: boolean
|
|
14
|
+
sourceCodePath?: string
|
|
15
|
+
timeout?: number
|
|
16
|
+
approvalTimeout?: number
|
|
17
|
+
}
|
|
18
|
+
export declare interface FirefoxPublishResult {
|
|
19
|
+
artifactsDir: string
|
|
20
|
+
artifacts: string[]
|
|
21
|
+
channel: 'listed' | 'unlisted'
|
|
22
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { buildExtension, resolveOutdir } from "./build";
|
|
6
|
+
function resolveFirefoxAuth(options) {
|
|
7
|
+
const issuer = options.issuer ?? process.env.AMO_JWT_ISSUER ?? process.env.WEB_EXT_API_KEY, secret = options.secret ?? process.env.AMO_JWT_SECRET ?? process.env.WEB_EXT_API_SECRET, missing = [!issuer && "AMO_JWT_ISSUER", !secret && "AMO_JWT_SECRET"].filter(Boolean);
|
|
8
|
+
if (missing.length)
|
|
9
|
+
throw Error(`[browser-extension] missing Firefox Add-ons credentials: ${missing.join(", ")}`);
|
|
10
|
+
return { issuer, secret };
|
|
11
|
+
}
|
|
12
|
+
export function firefoxListingMetadata(config, store) {
|
|
13
|
+
if (!store.license && !store.categories?.length)
|
|
14
|
+
return;
|
|
15
|
+
if (!store.license || !store.categories?.length)
|
|
16
|
+
throw Error("[browser-extension] a new Firefox listing needs both firefoxAddons.license and firefoxAddons.categories");
|
|
17
|
+
return {
|
|
18
|
+
version: { license: store.license },
|
|
19
|
+
categories: { firefox: store.categories },
|
|
20
|
+
summary: { "en-US": config.description },
|
|
21
|
+
...store.homepage ? { homepage: { "en-US": store.homepage } } : {},
|
|
22
|
+
...store.supportEmail ? { support_email: { "en-US": store.supportEmail } } : {},
|
|
23
|
+
requires_payment: store.requiresPayment ?? !1
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export async function publishFirefoxExtension(config, options) {
|
|
27
|
+
if (!config.geckoId)
|
|
28
|
+
throw Error("[browser-extension] Firefox publishing needs geckoId in config/extension.ts");
|
|
29
|
+
const store = config.firefoxAddons ?? {}, auth = resolveFirefoxAuth(options), cwd = options.cwd ?? process.cwd();
|
|
30
|
+
if (options.build !== !1)
|
|
31
|
+
await buildExtension(config, { target: "firefox", version: options.version, cwd });
|
|
32
|
+
const sourceDir = resolve(cwd, resolveOutdir(config, "firefox")), artifactsDir = resolve(cwd, store.artifactsDir ?? "web-ext-artifacts"), executable = Bun.which("web-ext");
|
|
33
|
+
if (!executable)
|
|
34
|
+
throw Error("[browser-extension] web-ext is unavailable; reinstall @stacksjs/browser-extension dependencies");
|
|
35
|
+
await mkdir(artifactsDir, { recursive: !0 });
|
|
36
|
+
const before = new Set(existsSync(artifactsDir) ? readdirSync(artifactsDir) : []), tempDir = await mkdtemp(join(tmpdir(), "stacks-firefox-publish-"));
|
|
37
|
+
try {
|
|
38
|
+
const args = [
|
|
39
|
+
executable,
|
|
40
|
+
"sign",
|
|
41
|
+
"--source-dir",
|
|
42
|
+
sourceDir,
|
|
43
|
+
"--artifacts-dir",
|
|
44
|
+
artifactsDir,
|
|
45
|
+
"--channel",
|
|
46
|
+
store.channel ?? "listed",
|
|
47
|
+
"--timeout",
|
|
48
|
+
String(options.timeout ?? 300000),
|
|
49
|
+
"--approval-timeout",
|
|
50
|
+
String(options.approvalTimeout ?? 0),
|
|
51
|
+
"--no-input",
|
|
52
|
+
"--boring"
|
|
53
|
+
], metadata = firefoxListingMetadata(config, store);
|
|
54
|
+
if (metadata) {
|
|
55
|
+
const metadataPath = join(tempDir, "amo-metadata.json");
|
|
56
|
+
await Bun.write(metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
57
|
+
`);
|
|
58
|
+
args.push("--amo-metadata", metadataPath);
|
|
59
|
+
}
|
|
60
|
+
if (options.sourceCodePath)
|
|
61
|
+
args.push("--upload-source-code", resolve(cwd, options.sourceCodePath));
|
|
62
|
+
const exitCode = await Bun.spawn(args, {
|
|
63
|
+
cwd,
|
|
64
|
+
env: {
|
|
65
|
+
...process.env,
|
|
66
|
+
WEB_EXT_API_KEY: auth.issuer,
|
|
67
|
+
WEB_EXT_API_SECRET: auth.secret
|
|
68
|
+
},
|
|
69
|
+
stdout: "inherit",
|
|
70
|
+
stderr: "inherit"
|
|
71
|
+
}).exited;
|
|
72
|
+
if (exitCode !== 0)
|
|
73
|
+
throw Error(`[browser-extension] Firefox Add-ons submission failed (${exitCode})`);
|
|
74
|
+
} finally {
|
|
75
|
+
await rm(tempDir, { recursive: !0, force: !0 });
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
artifactsDir,
|
|
79
|
+
artifacts: readdirSync(artifactsDir).filter((file) => !before.has(file)),
|
|
80
|
+
channel: store.channel ?? "listed"
|
|
81
|
+
};
|
|
82
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export * from './build';
|
|
11
11
|
export * from './app-store-connect';
|
|
12
|
+
export * from './chrome-web-store';
|
|
12
13
|
export * from './config';
|
|
14
|
+
export * from './firefox-addons';
|
|
13
15
|
export * from './manifest';
|
|
14
16
|
export * from './package';
|
|
15
17
|
export * from './safari';
|
package/dist/index.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -52,10 +52,28 @@ export declare interface BuildContext {
|
|
|
52
52
|
version: string
|
|
53
53
|
cwd: string
|
|
54
54
|
}
|
|
55
|
+
export declare interface ChromeWebStoreConfig {
|
|
56
|
+
publisherId: string
|
|
57
|
+
itemId: string
|
|
58
|
+
publishType?: 'DEFAULT_PUBLISH' | 'STAGED_PUBLISH'
|
|
59
|
+
deployPercentage?: number
|
|
60
|
+
skipReview?: boolean
|
|
61
|
+
}
|
|
62
|
+
export declare interface FirefoxAddonsConfig {
|
|
63
|
+
channel?: 'listed' | 'unlisted'
|
|
64
|
+
license?: string
|
|
65
|
+
categories?: string[]
|
|
66
|
+
homepage?: string
|
|
67
|
+
supportEmail?: string
|
|
68
|
+
requiresPayment?: boolean
|
|
69
|
+
artifactsDir?: string
|
|
70
|
+
}
|
|
55
71
|
export declare interface ExtensionConfig {
|
|
56
72
|
name: string
|
|
57
73
|
description: string
|
|
58
74
|
geckoId?: string
|
|
75
|
+
chromeWebStore?: ChromeWebStoreConfig
|
|
76
|
+
firefoxAddons?: FirefoxAddonsConfig
|
|
59
77
|
safariBundleId?: string
|
|
60
78
|
safariTeamId?: string
|
|
61
79
|
safariExclude?: string[]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/browser-extension",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.122",
|
|
5
5
|
"description": "Build MV3 browser extensions (Chrome, Firefox, Safari) the Stacks way — manifest, content/background scripts, DNR rules, packaging, Safari container app, all config-driven.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -59,5 +59,8 @@
|
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"better-dx": "^0.2.17"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"web-ext": "^10.5.0"
|
|
62
65
|
}
|
|
63
66
|
}
|