@stacksjs/browser-extension 0.70.66 → 0.70.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.d.ts +8 -8
- package/dist/build.js +65 -26
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/sanitize.d.ts +11 -0
- package/dist/sanitize.js +7 -0
- package/dist/types.d.ts +28 -5
- package/package.json +1 -1
package/dist/build.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { BuildOptions, ExtensionConfig, ExtensionTarget } from './types';
|
|
2
2
|
/** Resolve the output directory for a target. */
|
|
3
3
|
export declare function resolveOutdir(config: ExtensionConfig, target: ExtensionTarget, override?: string): string;
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
/**`) + extra `assets` copied in
|
|
5
|
+
* - stx pages → `<name>.html` (+ companion `<name>.js`), sanitized for the
|
|
6
|
+
* extension CSP, with stx dev chunks removed
|
|
7
|
+
* - content + background scripts → classic IIFE bundles
|
|
8
|
+
* - declarativeNetRequest rulesets compiled from their `source` modules
|
|
9
|
+
* - `manifest.json` generated per target
|
|
10
|
+
* - the app's `hooks.postBuild` run last
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
* package doesn't hard-depend on it when only the manifest/packaging APIs are
|
|
12
|
-
* used).
|
|
12
|
+
* Replaces the hand-rolled `build-extension.ts` every extension used to carry.
|
|
13
13
|
*/
|
|
14
14
|
export declare function buildExtension(config: ExtensionConfig, options: BuildOptions): Promise<{ outdir: string, target: ExtensionTarget }>;
|
|
15
15
|
/** Build every configured target. */
|
package/dist/build.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { Glob } from "bun";
|
|
1
2
|
import { cp, mkdir, rm } from "node:fs/promises";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import { join, resolve } from "node:path";
|
|
4
5
|
import { contentScriptOut, generateManifest } from "./manifest";
|
|
6
|
+
import { sanitizeExtensionHtml } from "./sanitize";
|
|
5
7
|
export function resolveOutdir(config, target, override) {
|
|
6
8
|
if (override)
|
|
7
9
|
return override;
|
|
@@ -11,53 +13,90 @@ export function resolveOutdir(config, target, override) {
|
|
|
11
13
|
return config.outdir[target];
|
|
12
14
|
return target === "firefox" ? "dist-firefox" : "dist";
|
|
13
15
|
}
|
|
16
|
+
function normalizePage(page) {
|
|
17
|
+
return typeof page === "string" ? { template: page } : page;
|
|
18
|
+
}
|
|
19
|
+
function pageEntries(pages) {
|
|
20
|
+
if (!pages)
|
|
21
|
+
return [];
|
|
22
|
+
const out = [];
|
|
23
|
+
if (pages.popup)
|
|
24
|
+
out.push({ name: "popup", page: normalizePage(pages.popup) });
|
|
25
|
+
if (pages.options)
|
|
26
|
+
out.push({ name: "options", page: normalizePage(pages.options) });
|
|
27
|
+
for (const [name, page] of Object.entries(pages.extra ?? {}))
|
|
28
|
+
out.push({ name, page: normalizePage(page) });
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
async function buildScript(entry, out, outdir, cwd, minify) {
|
|
32
|
+
const result = await Bun.build({
|
|
33
|
+
entrypoints: [resolve(cwd, entry)],
|
|
34
|
+
target: "browser",
|
|
35
|
+
format: "iife",
|
|
36
|
+
splitting: !1,
|
|
37
|
+
minify
|
|
38
|
+
});
|
|
39
|
+
if (!result.success)
|
|
40
|
+
throw Error(`[browser-extension] failed to build ${entry}: ${result.logs.join(`
|
|
41
|
+
`)}`);
|
|
42
|
+
await Bun.write(join(outdir, out), await result.outputs[0].text());
|
|
43
|
+
}
|
|
14
44
|
export async function buildExtension(config, options) {
|
|
15
45
|
const target = options.target ?? "chrome", cwd = options.cwd ?? process.cwd(), outdir = resolve(cwd, resolveOutdir(config, target, options.outdir)), minify = options.minify ?? !0;
|
|
16
46
|
await rm(outdir, { recursive: !0, force: !0 });
|
|
17
|
-
await mkdir(outdir, { recursive: !0 });
|
|
47
|
+
await mkdir(join(outdir, "rules"), { recursive: !0 });
|
|
48
|
+
await mkdir(join(outdir, "icons"), { recursive: !0 });
|
|
18
49
|
if (config.public) {
|
|
19
50
|
const pub = resolve(cwd, config.public);
|
|
20
51
|
if (existsSync(pub))
|
|
21
52
|
await cp(pub, outdir, { recursive: !0 });
|
|
22
53
|
}
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
54
|
+
for (const [dest, src] of Object.entries(config.assets ?? {})) {
|
|
55
|
+
const s = resolve(cwd, src);
|
|
56
|
+
if (existsSync(s))
|
|
57
|
+
await Bun.write(join(outdir, dest), Bun.file(s));
|
|
58
|
+
}
|
|
59
|
+
const pages = pageEntries(config.pages);
|
|
60
|
+
await Promise.all(pages.filter((p) => p.page.script).map((p) => buildScript(p.page.script, `${p.name}.js`, outdir, cwd, minify)));
|
|
61
|
+
if (pages.length) {
|
|
62
|
+
const { default: stxPlugin } = await import("bun-plugin-stx"), result = await Bun.build({
|
|
63
|
+
entrypoints: pages.map((p) => resolve(cwd, p.page.template)),
|
|
28
64
|
outdir,
|
|
29
|
-
target: "browser",
|
|
30
65
|
minify,
|
|
31
|
-
|
|
66
|
+
naming: { entry: "[name].html" },
|
|
67
|
+
plugins: [stxPlugin()]
|
|
32
68
|
});
|
|
69
|
+
if (!result.success)
|
|
70
|
+
throw Error(`[browser-extension] failed to build pages: ${result.logs.join(`
|
|
71
|
+
`)}`);
|
|
72
|
+
for (const { name, page } of pages) {
|
|
73
|
+
const file = join(outdir, `${name}.html`);
|
|
74
|
+
if (!existsSync(file))
|
|
75
|
+
continue;
|
|
76
|
+
const own = page.script ? [`${name}.js`] : [];
|
|
77
|
+
await Bun.write(file, sanitizeExtensionHtml(await Bun.file(file).text(), own));
|
|
78
|
+
}
|
|
79
|
+
for await (const chunk of new Glob("chunk-*.js").scan(outdir))
|
|
80
|
+
await rm(join(outdir, chunk), { force: !0 });
|
|
33
81
|
}
|
|
34
82
|
const scripts = [];
|
|
35
83
|
if (config.background)
|
|
36
84
|
scripts.push({ entry: config.background, out: "background.js" });
|
|
37
85
|
for (const cs of config.content ?? [])
|
|
38
86
|
scripts.push({ entry: cs.entry, out: contentScriptOut(cs.entry, cs.out) });
|
|
39
|
-
await Promise.all(scripts.map(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
throw Error(`[browser-extension] failed to build ${entry}: ${result.logs.join(`
|
|
48
|
-
`)}`);
|
|
49
|
-
await Bun.write(join(outdir, out), await result.outputs[0].text());
|
|
50
|
-
}));
|
|
87
|
+
await Promise.all(scripts.map((s) => buildScript(s.entry, s.out, outdir, cwd, minify)));
|
|
88
|
+
for (const rule of config.rules ?? []) {
|
|
89
|
+
if (!rule.source)
|
|
90
|
+
continue;
|
|
91
|
+
const mod = await import(resolve(cwd, rule.source)), build = mod.default ?? mod.buildRules ?? mod.rules ?? Object.values(mod).find((v) => typeof v === "function") ?? Object.values(mod).find((v) => Array.isArray(v)), data = typeof build === "function" ? await build() : build;
|
|
92
|
+
await Bun.write(join(outdir, rule.path ?? `rules/${rule.id}.json`), `${JSON.stringify(data, null, 2)}
|
|
93
|
+
`);
|
|
94
|
+
}
|
|
51
95
|
await Bun.write(join(outdir, "manifest.json"), `${JSON.stringify(generateManifest(config, { version: options.version, target }), null, 2)}
|
|
52
96
|
`);
|
|
97
|
+
await config.hooks?.postBuild?.({ config, target, outdir, version: options.version, cwd });
|
|
53
98
|
return { outdir, target };
|
|
54
99
|
}
|
|
55
|
-
function collectPages(config, cwd) {
|
|
56
|
-
const p = config.pages;
|
|
57
|
-
if (!p)
|
|
58
|
-
return [];
|
|
59
|
-
return [p.popup, p.options, ...Object.values(p.extra ?? {})].filter(Boolean).map((e) => resolve(cwd, e));
|
|
60
|
-
}
|
|
61
100
|
export async function buildAllTargets(config, options) {
|
|
62
101
|
for (const target of config.targets ?? ["chrome", "firefox"])
|
|
63
102
|
await buildExtension(config, { ...options, target });
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize an stx-built page for use as an extension page.
|
|
3
|
+
*
|
|
4
|
+
* MV3 extension pages run under a strict CSP (`script-src 'self'`) — no inline
|
|
5
|
+
* `<script>` or `<style>`, and asset URLs must be relative (the page loads from
|
|
6
|
+
* `chrome-extension://…/<name>.html`, not a web root). stx emits dev niceties
|
|
7
|
+
* (SEO meta, an inline style block, an inline hydration script) and absolute
|
|
8
|
+
* `/asset` paths, so strip/rewrite them, keeping only the page's own compiled
|
|
9
|
+
* script(s).
|
|
10
|
+
*/
|
|
11
|
+
export declare function sanitizeExtensionHtml(html: string, ownScripts: string[]): string;
|
package/dist/sanitize.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function sanitizeExtensionHtml(html, ownScripts) {
|
|
2
|
+
const keep = ownScripts.map((s) => `(?![^>]*src="/?${s.replace(".", "\\.")}")`).join("");
|
|
3
|
+
let out = html.replace(/\n?<!-- stx SEO Tags -->[\s\S]*?(?=\n\s*<meta charset=)/, "").replace(/\n?\s*<style\b[\s\S]*?<\/style>/g, "").replace(new RegExp(`\\n?\\s*<script\\b${keep}[\\s\\S]*?<\\/script>`, "g"), "").replaceAll('href="/styles.css"', 'href="styles.css"').replaceAll('href="/icons/', 'href="icons/');
|
|
4
|
+
for (const s of ownScripts)
|
|
5
|
+
out = out.replaceAll(`src="/${s}"`, `src="${s}"`);
|
|
6
|
+
return out;
|
|
7
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -9,17 +9,28 @@ export declare interface ContentScript {
|
|
|
9
9
|
matchAboutBlank?: boolean
|
|
10
10
|
excludeMatches?: string[]
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* A declarativeNetRequest static ruleset. Either point `path` at a pre-built
|
|
14
|
+
* JSON file, or give `source` — a module whose default export is a function (or
|
|
15
|
+
* value) producing the rules array, which the build compiles to
|
|
16
|
+
* `rules/<id>.json`.
|
|
17
|
+
*/
|
|
13
18
|
export declare interface RuleResource {
|
|
14
19
|
id: string
|
|
15
20
|
path?: string
|
|
16
21
|
enabled?: boolean
|
|
22
|
+
source?: string
|
|
23
|
+
}
|
|
24
|
+
/** An extension page: an stx template + an optional companion script. */
|
|
25
|
+
export declare interface ExtensionPage {
|
|
26
|
+
template: string
|
|
27
|
+
script?: string
|
|
17
28
|
}
|
|
18
|
-
/**
|
|
29
|
+
/** stx pages bundled to HTML (popup, options, …). A bare string = template only. */
|
|
19
30
|
export declare interface ExtensionPages {
|
|
20
|
-
popup?: string
|
|
21
|
-
options?: string
|
|
22
|
-
extra?: Record<string, string>
|
|
31
|
+
popup?: string | ExtensionPage
|
|
32
|
+
options?: string | ExtensionPage
|
|
33
|
+
extra?: Record<string, string | ExtensionPage>
|
|
23
34
|
}
|
|
24
35
|
/** Overrides merged verbatim into the generated manifest, per target. */
|
|
25
36
|
export declare interface ManifestOverrides {
|
|
@@ -32,6 +43,14 @@ export declare interface ManifestOverrides {
|
|
|
32
43
|
webAccessibleResources?: Array<{ resources: string[], matches: string[] }>
|
|
33
44
|
extra?: Record<string, unknown>
|
|
34
45
|
}
|
|
46
|
+
/** Context passed to build hooks. */
|
|
47
|
+
export declare interface BuildContext {
|
|
48
|
+
config: ExtensionConfig
|
|
49
|
+
target: ExtensionTarget
|
|
50
|
+
outdir: string
|
|
51
|
+
version: string
|
|
52
|
+
cwd: string
|
|
53
|
+
}
|
|
35
54
|
export declare interface ExtensionConfig {
|
|
36
55
|
name: string
|
|
37
56
|
description: string
|
|
@@ -42,9 +61,13 @@ export declare interface ExtensionConfig {
|
|
|
42
61
|
pages?: ExtensionPages
|
|
43
62
|
icons?: Record<number, string>
|
|
44
63
|
public?: string
|
|
64
|
+
assets?: Record<string, string>
|
|
45
65
|
rules?: RuleResource[]
|
|
46
66
|
manifest?: ManifestOverrides
|
|
47
67
|
outdir?: Partial<Record<ExtensionTarget, string>> | string
|
|
68
|
+
hooks?: {
|
|
69
|
+
postBuild?: (ctx: BuildContext) => void | Promise<void>
|
|
70
|
+
}
|
|
48
71
|
}
|
|
49
72
|
export declare interface BuildOptions {
|
|
50
73
|
target?: ExtensionTarget
|
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.68",
|
|
5
5
|
"description": "Build MV3 browser extensions (Chrome + Firefox) the Stacks way — manifest, content/background scripts, DNR rules, packaging, all config-driven.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|