extrojs 0.2.0 → 0.3.1

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.
Files changed (83) hide show
  1. package/client.d.ts +7 -15
  2. package/dist/commands/build.js +1 -1
  3. package/dist/commands/dev.js +12 -29
  4. package/dist/config.d.ts +2 -2
  5. package/dist/core/asset.d.ts +10 -0
  6. package/dist/core/asset.js +10 -0
  7. package/dist/dev-assets.d.ts +3 -3
  8. package/dist/dev-assets.js +18 -16
  9. package/dist/exports/asset.d.ts +1 -0
  10. package/dist/exports/asset.js +1 -0
  11. package/dist/exports/link.d.ts +1 -0
  12. package/dist/exports/link.js +1 -0
  13. package/dist/exports/navigation.d.ts +2 -0
  14. package/dist/exports/navigation.js +1 -0
  15. package/dist/exports/runtime.d.ts +2 -0
  16. package/dist/exports/runtime.js +3 -0
  17. package/dist/load-config.d.ts +1 -1
  18. package/dist/paths.d.ts +1 -1
  19. package/dist/plugin/app-tree.d.ts +59 -0
  20. package/dist/plugin/app-tree.js +214 -0
  21. package/dist/plugin/asset-inventory.d.ts +24 -0
  22. package/dist/plugin/asset-inventory.js +9 -0
  23. package/dist/plugin/dev-reactions.d.ts +59 -0
  24. package/dist/plugin/dev-reactions.js +62 -0
  25. package/dist/plugin/emit-assets.d.ts +56 -0
  26. package/dist/plugin/emit-assets.js +47 -0
  27. package/dist/plugin/generators/html.d.ts +64 -0
  28. package/dist/plugin/generators/html.js +167 -0
  29. package/dist/plugin/generators/icons.d.ts +15 -0
  30. package/dist/plugin/generators/icons.js +16 -0
  31. package/dist/plugin/generators/public.d.ts +17 -0
  32. package/dist/plugin/generators/public.js +20 -0
  33. package/dist/plugin/icons.d.ts +5 -0
  34. package/dist/plugin/icons.js +20 -0
  35. package/dist/plugin/index.d.ts +31 -0
  36. package/dist/plugin/index.js +246 -0
  37. package/dist/plugin/internal.d.ts +14 -0
  38. package/dist/plugin/internal.js +6 -0
  39. package/dist/plugin/manifest.d.ts +29 -0
  40. package/dist/plugin/manifest.js +68 -0
  41. package/dist/plugin/public.d.ts +21 -0
  42. package/dist/plugin/public.js +69 -0
  43. package/dist/plugin/runtimes/clients/csui-mount.js +90 -0
  44. package/dist/plugin/runtimes/clients/dev-bridge.js +194 -0
  45. package/dist/plugin/runtimes/csui-mount.d.ts +18 -0
  46. package/dist/plugin/runtimes/csui-mount.js +21 -0
  47. package/dist/plugin/runtimes/dev-bridge.d.ts +22 -0
  48. package/dist/plugin/runtimes/dev-bridge.js +19 -0
  49. package/dist/plugin/runtimes/routes-module.d.ts +20 -0
  50. package/dist/plugin/runtimes/routes-module.js +51 -0
  51. package/dist/plugin/runtimes/runtime-module.d.ts +16 -0
  52. package/dist/plugin/runtimes/runtime-module.js +40 -0
  53. package/dist/plugin/surfaces.d.ts +37 -0
  54. package/dist/plugin/surfaces.js +67 -0
  55. package/dist/plugin/types/index.d.ts +9 -0
  56. package/dist/plugin/types/index.js +1 -0
  57. package/dist/plugin/utils/read-json.d.ts +1 -0
  58. package/dist/plugin/utils/read-json.js +8 -0
  59. package/dist/react/env.d.ts +13 -0
  60. package/dist/react/env.js +1 -0
  61. package/dist/router/build-tree.d.ts +46 -0
  62. package/dist/router/build-tree.js +56 -0
  63. package/dist/router/context.d.ts +13 -0
  64. package/dist/router/context.js +2 -0
  65. package/dist/router/create-router.d.ts +10 -0
  66. package/dist/router/create-router.js +126 -0
  67. package/dist/router/defaults.d.ts +24 -0
  68. package/dist/router/defaults.js +25 -0
  69. package/dist/router/error-boundary.d.ts +23 -0
  70. package/dist/router/error-boundary.js +21 -0
  71. package/dist/router/hooks.d.ts +18 -0
  72. package/dist/router/hooks.js +34 -0
  73. package/dist/router/index.d.ts +8 -0
  74. package/dist/router/index.js +7 -0
  75. package/dist/router/link.d.ts +305 -0
  76. package/dist/router/link.js +30 -0
  77. package/dist/router/match.d.ts +14 -0
  78. package/dist/router/match.js +27 -0
  79. package/dist/router/types.d.ts +55 -0
  80. package/dist/router/types.js +1 -0
  81. package/dist/types/index.d.ts +152 -0
  82. package/dist/types/index.js +1 -0
  83. package/package.json +39 -7
@@ -0,0 +1,62 @@
1
+ import { routeManifest } from "./app-tree.js";
2
+ /**
3
+ * A changed file under `src/app/background/` dirties background; under
4
+ * `src/app/content/` dirties content; anything else (shared code) dirties both.
5
+ * This is the only place that knows the `src/app` layout, so the CLI no longer
6
+ * hardcodes those paths.
7
+ */
8
+ export function classifyScriptChange(changedPath) {
9
+ const p = changedPath.replace(/\\/g, "/");
10
+ const background = p.includes("/src/app/background/");
11
+ const content = p.includes("/src/app/content/");
12
+ if (!background && !content)
13
+ return { background: true, content: true };
14
+ return { background, content };
15
+ }
16
+ /** Union two dirty states (accumulating `change` events across one build). */
17
+ export function mergeDirty(a, b) {
18
+ return {
19
+ background: a.background || b.background,
20
+ content: a.content || b.content,
21
+ };
22
+ }
23
+ /**
24
+ * Resolve the accumulated dirty state at `BUNDLE_END`. No classified change
25
+ * (the initial build, or an `extro dev` restart) conservatively means both,
26
+ * matching the broadcast-on-first-build behavior.
27
+ */
28
+ export function resolveFlush(dirty) {
29
+ if (!dirty.background && !dirty.content) {
30
+ return { background: true, content: true };
31
+ }
32
+ return { background: dirty.background, content: dirty.content };
33
+ }
34
+ /**
35
+ * Diff two AppTrees into a {@link DevReaction}. A birth short-circuits the
36
+ * invalidate scan: the session has to restart regardless of any route delta.
37
+ */
38
+ export function decideTreeReaction(prev, next, routables) {
39
+ if (!prev.scripts.background && next.scripts.background) {
40
+ return { kind: "restart", surface: "background" };
41
+ }
42
+ if (!prev.scripts.content && next.scripts.content) {
43
+ return { kind: "restart", surface: "content" };
44
+ }
45
+ for (const surface of routables) {
46
+ const had = (prev.surfaces[surface]?.routes.length ?? 0) > 0;
47
+ const has = (next.surfaces[surface]?.routes.length ?? 0) > 0;
48
+ if (has && !had)
49
+ return { kind: "restart", surface };
50
+ }
51
+ // The Route manifest IS what the routes module emits and is fully
52
+ // serializable, so its stable stringify is a faithful identity: invalidation
53
+ // can no longer drift from the contract (ADR 0005). This is what kills the
54
+ // historical HMR-drift bug class.
55
+ const changed = [];
56
+ for (const surface of routables) {
57
+ const key = (t) => JSON.stringify(routeManifest(t, surface));
58
+ if (key(prev) !== key(next))
59
+ changed.push(surface);
60
+ }
61
+ return changed.length ? { kind: "invalidate", surfaces: changed } : { kind: "noop" };
62
+ }
@@ -0,0 +1,56 @@
1
+ import type { ExtroConfig, ManifestV3 } from "../types/index.js";
2
+ import type { AppTree } from "./app-tree.js";
3
+ import type { AssetInventory } from "./asset-inventory.js";
4
+ import type { RoutableSurface } from "./surfaces.js";
5
+ /**
6
+ * Receives one finished asset at a time. Sync (Vite's `emitFile`) and async
7
+ * (`fs.writeFile`) sinks both fit — callers that await the seam will see
8
+ * any rejection from the async path.
9
+ */
10
+ export type EmitSink = (fileName: string, source: string) => void | Promise<void>;
11
+ export interface AssetOptions {
12
+ tree: AppTree;
13
+ /**
14
+ * The build's discovered icons + Public assets. Both call paths
15
+ * (`generateBundle`, `writeDevAssets`) run `discoverAssets` once at their
16
+ * filesystem edge and pass the result in, so composition stays pure.
17
+ */
18
+ inventory: AssetInventory;
19
+ pkg: {
20
+ name?: string;
21
+ description?: string;
22
+ version?: string;
23
+ };
24
+ config: ExtroConfig;
25
+ dev?: {
26
+ port: number;
27
+ signalPort: number;
28
+ };
29
+ }
30
+ export interface Artifacts {
31
+ manifest: ManifestV3;
32
+ html: Partial<Record<RoutableSurface, string>>;
33
+ /**
34
+ * The dev probe (`extro-dev.js`) the dev shells load to reveal the
35
+ * offline screen when the dev server is unreachable. Absent in prod
36
+ * builds and when there are no HTML surfaces to probe for.
37
+ */
38
+ devProbe?: string;
39
+ }
40
+ /**
41
+ * Pure projection from inputs to artifact strings: no filesystem access (the
42
+ * Asset inventory is discovered at the caller's edge and passed in). Exposed
43
+ * alongside `emitAssets` so tests can assert "given this tree + inventory, this
44
+ * is the manifest" without a sink and without staging files on disk.
45
+ */
46
+ export declare function composeArtifacts(opts: AssetOptions): Artifacts;
47
+ /**
48
+ * Canonical emission of Extro's static outputs (manifest + per-surface HTML).
49
+ * Both the build (`generateBundle`) and dev (`writeDevAssets`) call paths go
50
+ * through here — the only difference between them is the sink.
51
+ *
52
+ * Icons live outside this seam: they are emitted once during build via
53
+ * Rollup's binary-asset path and are not needed in dev (the initial
54
+ * `viteBuild` already wrote them to disk).
55
+ */
56
+ export declare function emitAssets(opts: AssetOptions, emit: EmitSink): Promise<void>;
@@ -0,0 +1,47 @@
1
+ import { generateManifest } from "./manifest.js";
2
+ import { DEV_PROBE_FILE, generateDevProbe, generateHTML, } from "./generators/html.js";
3
+ // ---------------------------------------------------------------------------
4
+ // Public API
5
+ // ---------------------------------------------------------------------------
6
+ /**
7
+ * Pure projection from inputs to artifact strings: no filesystem access (the
8
+ * Asset inventory is discovered at the caller's edge and passed in). Exposed
9
+ * alongside `emitAssets` so tests can assert "given this tree + inventory, this
10
+ * is the manifest" without a sink and without staging files on disk.
11
+ */
12
+ export function composeArtifacts(opts) {
13
+ const manifest = generateManifest(opts);
14
+ const surfaces = Object.keys(opts.tree.surfaces);
15
+ const html = {};
16
+ for (const surface of surfaces) {
17
+ html[surface] = generateHTML({
18
+ surface,
19
+ dev: opts.dev ? { port: opts.dev.port } : undefined,
20
+ });
21
+ }
22
+ const devProbe = opts.dev && surfaces.length > 0
23
+ ? generateDevProbe({ port: opts.dev.port })
24
+ : undefined;
25
+ return { manifest, html, devProbe };
26
+ }
27
+ /**
28
+ * Canonical emission of Extro's static outputs (manifest + per-surface HTML).
29
+ * Both the build (`generateBundle`) and dev (`writeDevAssets`) call paths go
30
+ * through here — the only difference between them is the sink.
31
+ *
32
+ * Icons live outside this seam: they are emitted once during build via
33
+ * Rollup's binary-asset path and are not needed in dev (the initial
34
+ * `viteBuild` already wrote them to disk).
35
+ */
36
+ export async function emitAssets(opts, emit) {
37
+ const { manifest, html, devProbe } = composeArtifacts(opts);
38
+ await emit("manifest.json", JSON.stringify(manifest, null, 2));
39
+ if (devProbe) {
40
+ await emit(DEV_PROBE_FILE, devProbe);
41
+ }
42
+ for (const [surface, body] of Object.entries(html)) {
43
+ if (!body)
44
+ continue;
45
+ await emit(`${surface}.html`, body);
46
+ }
47
+ }
@@ -0,0 +1,64 @@
1
+ interface GenerateHTMLOptions {
2
+ surface: string;
3
+ dev?: {
4
+ port: number;
5
+ };
6
+ }
7
+ /**
8
+ * The dev probe script emitted next to the HTML shells in the dev output
9
+ * dir. Referenced by the shells and reserved against Public assets, so the
10
+ * name lives here once.
11
+ */
12
+ export declare const DEV_PROBE_FILE = "extro-dev.js";
13
+ /**
14
+ * @describe Generates the HTML shell for a surface. In dev mode, the shell
15
+ * points at the Vite dev server (with @vite/client for HMR) instead of the
16
+ * built bundle, and carries a hidden brand-styled "dev server offline"
17
+ * screen inside #root. The probe script (generateDevProbe) reveals it only
18
+ * when the dev server is unreachable; on a normal start it never paints,
19
+ * and React's createRoot replaces #root's children on mount.
20
+ */
21
+ export declare function generateHTML({ surface, dev }: GenerateHTMLOptions): string;
22
+ export interface DevScreen {
23
+ title: string;
24
+ /** HTML body content (paragraphs, code, etc.). Inserted as-is. */
25
+ body: string;
26
+ /**
27
+ * When true, the screen fills the viewport and centers its card. Use for
28
+ * surfaces with a real viewport (options tab, sidepanel). When false
29
+ * (popup), the screen sizes to content with a fixed minimum.
30
+ */
31
+ fill?: boolean;
32
+ /**
33
+ * When true, the screen ships with the `hidden` attribute and stays out
34
+ * of the first paint until something removes it (the dev probe, for the
35
+ * offline screen).
36
+ */
37
+ hidden?: boolean;
38
+ }
39
+ /**
40
+ * @describe Renders a brand-styled "developer screen" card. Used as the
41
+ * pre-render inside #root for offline-dev-server fallback today, intended
42
+ * for reuse in other build-time / runtime dev panels.
43
+ */
44
+ export declare const renderDevScreen: ({ title, body, fill, hidden, }: DevScreen) => string;
45
+ /**
46
+ * @describe Global styles for any `.extro-dev-screen` rendered in dev. Body
47
+ * is painted dark only while a visible screen is in the DOM (via `:has()`),
48
+ * so the user's app keeps default styling both before React mounts (screen
49
+ * still hidden) and after (screen wiped from #root).
50
+ */
51
+ export declare const devScreenStyles: () => string;
52
+ /**
53
+ * @describe Generates the dev probe (`extro-dev.js`), emitted into the dev
54
+ * output dir next to the HTML shells. MV3 CSP bans inline scripts but allows
55
+ * 'self', so the probe loads from the extension origin and runs even when
56
+ * the Vite dev server is down. It reveals the hidden offline screen only
57
+ * when the @vite/client script fails to fetch, the one unambiguous signal
58
+ * that the server is unreachable; a failing user module while the server is
59
+ * up stays Vite's error overlay's problem.
60
+ */
61
+ export declare const generateDevProbe: ({ port }: {
62
+ port: number;
63
+ }) => string;
64
+ export {};
@@ -0,0 +1,167 @@
1
+ /**
2
+ * The dev probe script emitted next to the HTML shells in the dev output
3
+ * dir. Referenced by the shells and reserved against Public assets, so the
4
+ * name lives here once.
5
+ */
6
+ export const DEV_PROBE_FILE = "extro-dev.js";
7
+ /**
8
+ * @describe Generates the HTML shell for a surface. In dev mode, the shell
9
+ * points at the Vite dev server (with @vite/client for HMR) instead of the
10
+ * built bundle, and carries a hidden brand-styled "dev server offline"
11
+ * screen inside #root. The probe script (generateDevProbe) reveals it only
12
+ * when the dev server is unreachable; on a normal start it never paints,
13
+ * and React's createRoot replaces #root's children on mount.
14
+ */
15
+ export function generateHTML({ surface, dev }) {
16
+ const title = surface.charAt(0).toUpperCase() + surface.slice(1);
17
+ const scripts = dev
18
+ ? `
19
+ <script src="./${DEV_PROBE_FILE}"></script>
20
+ <script type="module" src="http://localhost:${dev.port}/@vite/client"></script>
21
+ <script type="module" src="http://localhost:${dev.port}/@id/@vitejs/plugin-react/preamble"></script>
22
+ <script type="module" src="http://localhost:${dev.port}/@id/virtual:extro/runtime/${surface}"></script>
23
+ `
24
+ : `<script type="module" src="./${surface}.js"></script>`;
25
+ const rootContent = dev
26
+ ? renderDevScreen({
27
+ title: "Dev server isn't running",
28
+ body: `
29
+ <p>Start it with <code>extro dev</code>, then reopen this surface.</p>
30
+ <p>Expected at <code>http://localhost:${dev.port}</code>.</p>
31
+ `,
32
+ // Popups size to content (no viewport to fill); everything else
33
+ // (options/sidepanel/tab) gets a full-viewport centered layout.
34
+ fill: surface !== "popup",
35
+ // Hidden until the probe proves the dev server is down, so a normal
36
+ // start never paints it (the screen used to flash for the moment
37
+ // between document load and React mount).
38
+ hidden: true,
39
+ })
40
+ : "";
41
+ return `
42
+ <!doctype html>
43
+ <html>
44
+ <head>
45
+ <title>${title}</title>
46
+ ${dev ? devScreenStyles() : ""}
47
+ </head>
48
+ <body>
49
+ <div id="root">${rootContent}</div>
50
+ ${scripts.trim()}
51
+ </body>
52
+ </html>
53
+ `.trim();
54
+ }
55
+ /**
56
+ * @describe Renders a brand-styled "developer screen" card. Used as the
57
+ * pre-render inside #root for offline-dev-server fallback today, intended
58
+ * for reuse in other build-time / runtime dev panels.
59
+ */
60
+ export const renderDevScreen = ({ title, body, fill = false, hidden = false, }) => {
61
+ const cls = `extro-dev-screen${fill ? " extro-dev-screen--fill" : ""}`;
62
+ return `
63
+ <div class="${cls}"${hidden ? " hidden" : ""}>
64
+ <div class="extro-dev-screen__card">
65
+ <span class="extro-dev-screen__tag">EXTRO</span>
66
+ <h1>${title}</h1>
67
+ ${body.trim()}
68
+ </div>
69
+ </div>
70
+ `.trim();
71
+ };
72
+ /**
73
+ * @describe Global styles for any `.extro-dev-screen` rendered in dev. Body
74
+ * is painted dark only while a visible screen is in the DOM (via `:has()`),
75
+ * so the user's app keeps default styling both before React mounts (screen
76
+ * still hidden) and after (screen wiped from #root).
77
+ */
78
+ export const devScreenStyles = () => `
79
+ <style>
80
+ body { margin: 0; }
81
+ body:has(.extro-dev-screen:not([hidden])) { background: #0a0a0a; }
82
+ .extro-dev-screen {
83
+ box-sizing: border-box;
84
+ min-width: 360px;
85
+ min-height: 240px;
86
+ padding: 24px 28px;
87
+ background: #0a0a0a;
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ opacity: 0;
92
+ animation: extro-dev-screen-in 0.18s forwards;
93
+ }
94
+ .extro-dev-screen[hidden] {
95
+ display: none;
96
+ }
97
+ .extro-dev-screen--fill {
98
+ min-height: 100vh;
99
+ }
100
+ .extro-dev-screen__card {
101
+ box-sizing: border-box;
102
+ width: 100%;
103
+ max-width: 480px;
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: 10px;
107
+ font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
108
+ color: #e5e5e5;
109
+ }
110
+ .extro-dev-screen__tag {
111
+ align-self: flex-start;
112
+ font-size: 11px;
113
+ font-weight: 600;
114
+ letter-spacing: 0.08em;
115
+ padding: 3px 8px;
116
+ color: #0a0a0a;
117
+ background: #CC785C;
118
+ border-radius: 3px;
119
+ }
120
+ .extro-dev-screen h1 {
121
+ margin: 4px 0 0;
122
+ font-size: 15px;
123
+ font-weight: 600;
124
+ color: #fafafa;
125
+ }
126
+ .extro-dev-screen p {
127
+ margin: 0;
128
+ font-size: 13px;
129
+ line-height: 1.5;
130
+ color: #a3a3a3;
131
+ }
132
+ .extro-dev-screen code {
133
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
134
+ font-size: 12px;
135
+ padding: 1px 6px;
136
+ color: #CC785C;
137
+ background: #1a1a1a;
138
+ border-radius: 3px;
139
+ }
140
+ @keyframes extro-dev-screen-in { to { opacity: 1; } }
141
+ </style>
142
+ `.trim();
143
+ /**
144
+ * @describe Generates the dev probe (`extro-dev.js`), emitted into the dev
145
+ * output dir next to the HTML shells. MV3 CSP bans inline scripts but allows
146
+ * 'self', so the probe loads from the extension origin and runs even when
147
+ * the Vite dev server is down. It reveals the hidden offline screen only
148
+ * when the @vite/client script fails to fetch, the one unambiguous signal
149
+ * that the server is unreachable; a failing user module while the server is
150
+ * up stays Vite's error overlay's problem.
151
+ */
152
+ export const generateDevProbe = ({ port }) => `
153
+ // Generated by Extro. Reveals the "dev server isn't running" screen when
154
+ // the Vite dev server can't be reached. Script fetch errors don't bubble,
155
+ // hence the capture-phase listener; this classic script runs before the
156
+ // module scripts below it can fail.
157
+ window.addEventListener(
158
+ "error",
159
+ (event) => {
160
+ const el = event.target;
161
+ if (!(el instanceof HTMLScriptElement)) return;
162
+ if (el.src !== "http://localhost:${port}/@vite/client") return;
163
+ document.querySelector(".extro-dev-screen")?.removeAttribute("hidden");
164
+ },
165
+ true,
166
+ );
167
+ `.trimStart();
@@ -0,0 +1,15 @@
1
+ import type { PluginContextLike } from "../types/index.js";
2
+ interface EmitIconsOptions {
3
+ ctx: PluginContextLike;
4
+ root: string;
5
+ /** The recognized icon set from the Asset inventory (size -> "icons/16.png"). */
6
+ icons: Record<string, string> | null;
7
+ }
8
+ /**
9
+ * @file generators/icons.ts
10
+ * @description Emits the recognized extension icons named by the Asset
11
+ * inventory. Discovery already decided which sizes exist, so this only reads
12
+ * bytes: what ships is exactly what `manifest.icons` references.
13
+ */
14
+ export declare function emitIcons({ ctx, root, icons }: EmitIconsOptions): void;
15
+ export {};
@@ -0,0 +1,16 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ /**
4
+ * @file generators/icons.ts
5
+ * @description Emits the recognized extension icons named by the Asset
6
+ * inventory. Discovery already decided which sizes exist, so this only reads
7
+ * bytes: what ships is exactly what `manifest.icons` references.
8
+ */
9
+ export function emitIcons({ ctx, root, icons }) {
10
+ if (!icons)
11
+ return;
12
+ for (const rel of Object.values(icons)) {
13
+ const source = fs.readFileSync(path.join(root, rel));
14
+ ctx.emitFile({ type: "asset", fileName: rel, source });
15
+ }
16
+ }
@@ -0,0 +1,17 @@
1
+ import type { PluginContextLike } from "../types/index.js";
2
+ import { type PublicAssets } from "../public.js";
3
+ interface EmitPublicAssetsOptions {
4
+ ctx: PluginContextLike;
5
+ root: string;
6
+ /** The partitioned Public assets from the Asset inventory. */
7
+ publicAssets: PublicAssets;
8
+ }
9
+ /**
10
+ * @file generators/public.ts
11
+ * @description Emits Public assets into the build output with their original
12
+ * names, from the partition the Asset inventory already computed. Mirrors
13
+ * `emitIcons`; the collision guard skips any file that would overwrite a
14
+ * generated output and warns instead.
15
+ */
16
+ export declare function emitPublicAssets({ ctx, root, publicAssets }: EmitPublicAssetsOptions): void;
17
+ export {};
@@ -0,0 +1,20 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { PUBLIC_DIR } from "../public.js";
4
+ /**
5
+ * @file generators/public.ts
6
+ * @description Emits Public assets into the build output with their original
7
+ * names, from the partition the Asset inventory already computed. Mirrors
8
+ * `emitIcons`; the collision guard skips any file that would overwrite a
9
+ * generated output and warns instead.
10
+ */
11
+ export function emitPublicAssets({ ctx, root, publicAssets }) {
12
+ const { files, conflicts } = publicAssets;
13
+ for (const conflict of conflicts) {
14
+ ctx.warn(`public/${conflict} collides with a generated output; skipping. Rename it to ship it.`);
15
+ }
16
+ for (const file of files) {
17
+ const source = fs.readFileSync(path.join(root, PUBLIC_DIR, file));
18
+ ctx.emitFile({ type: "asset", fileName: file, source });
19
+ }
20
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @file icons.ts
3
+ * @description Detects the icons for the extension.
4
+ */
5
+ export declare function detectIcons(root: string): Record<string, string> | undefined;
@@ -0,0 +1,20 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ /**
4
+ * @file icons.ts
5
+ * @description Detects the icons for the extension.
6
+ */
7
+ export function detectIcons(root) {
8
+ const iconsDir = path.join(root, "icons");
9
+ if (!fs.existsSync(iconsDir))
10
+ return undefined;
11
+ const sizes = ["16", "32", "48", "128"];
12
+ const icons = {};
13
+ for (const size of sizes) {
14
+ const file = path.join(iconsDir, `${size}.png`);
15
+ if (fs.existsSync(file)) {
16
+ icons[size] = `icons/${size}.png`;
17
+ }
18
+ }
19
+ return Object.keys(icons).length ? icons : undefined;
20
+ }
@@ -0,0 +1,31 @@
1
+ import type { Plugin } from "vite";
2
+ import type { ExtroConfig } from "../types/index.js";
3
+ interface ExtroPluginOptions {
4
+ root: string;
5
+ config?: ExtroConfig;
6
+ /**
7
+ * When true, only background/content are bundled. Manifest + HTML emission
8
+ * is skipped (writeDevAssets handles those separately during `extro dev`).
9
+ * Used by the dev build-watch sidecar.
10
+ */
11
+ scriptsOnly?: boolean;
12
+ /**
13
+ * When set, wrap the background entry in the dev bridge so a service
14
+ * worker exists in dev mode (even if the user didn't write one) to
15
+ * receive rebuild signals from the CLI's WS server and forward Vite HMR
16
+ * events to content scripts.
17
+ */
18
+ devBridge?: {
19
+ signalPort: number;
20
+ vitePort: number;
21
+ };
22
+ /**
23
+ * Called from the dev-server plugin's `handleHotUpdate` with a payload
24
+ * shaped like Vite's own HMR `update` event. The CLI uses this to
25
+ * broadcast HMR over the signal WS (avoiding Vite's HMR WS, whose
26
+ * origin check rejects chrome-extension:// service workers).
27
+ */
28
+ broadcastHmr?: (payload: object) => void;
29
+ }
30
+ export declare function extro(options: ExtroPluginOptions): Plugin;
31
+ export {};