@wojciechpiskorz/astroix 0.0.4 → 0.0.5

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/index.js CHANGED
@@ -4,86 +4,14 @@ import { fileURLToPath as fileURLToPath2 } from "url";
4
4
  import tailwindcss from "@tailwindcss/vite";
5
5
  import react from "@vitejs/plugin-react";
6
6
 
7
- // src/node/source-mode.ts
8
- import { existsSync } from "fs";
9
- import { fileURLToPath, URL as NodeURL } from "url";
10
- var candidates = [
11
- fileURLToPath(new NodeURL("../../src/client/entry.tsx", import.meta.url)),
12
- fileURLToPath(new NodeURL("../src/client/entry.tsx", import.meta.url))
13
- ];
14
- var clientEntryPath = candidates.find((path) => existsSync(path)) ?? null;
15
- var chromeArtifactPath = fileURLToPath(
16
- new NodeURL("./chrome.js", import.meta.url)
17
- );
18
- function isSourceMode() {
19
- return clientEntryPath !== null;
20
- }
21
-
22
- // src/node/tailwind-guard.ts
23
- function hostRegistersTailwind(viteConfig) {
24
- const seen = /* @__PURE__ */ new Set();
25
- const walk = (input) => {
26
- if (Array.isArray(input)) {
27
- for (const entry of input) walk(entry);
28
- return;
29
- }
30
- if (!input || typeof input !== "object") return;
31
- const plugin = input;
32
- if (typeof plugin.name === "string") seen.add(plugin.name);
33
- };
34
- walk(viteConfig?.plugins);
35
- for (const name of seen) {
36
- if (name.startsWith("@tailwindcss/vite")) return true;
37
- }
38
- return false;
39
- }
40
-
41
- // src/node/vite-plugin.ts
42
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
7
+ // src/node/content.ts
8
+ import { existsSync as existsSync2 } from "fs";
43
9
  import { join as join2 } from "path";
44
-
45
- // src/node/chrome-html.ts
46
- function chromeHtml() {
47
- return `<!doctype html>
48
- <html lang="en">
49
- <head>
50
- <meta charset="utf-8" />
51
- <meta name="viewport" content="width=device-width, initial-scale=1" />
52
- <title>astroix builder</title>
53
- <style>
54
- html, body { margin: 0; height: 100%; }
55
- #astroix-root { display: block; height: 100vh; }
56
- </style>
57
- </head>
58
- <body>
59
- <div id="astroix-root"></div>
60
- <script type="module" src="/virtual:astroix/chrome"></script>
61
- </body>
62
- </html>`;
63
- }
64
-
65
- // src/node/document-request.ts
66
- function isDocumentRequest(input) {
67
- if (input.method !== "GET" && input.method !== "HEAD") return false;
68
- if (!(input.accept ?? "").includes("text/html")) return false;
69
- let url;
70
- try {
71
- url = new URL(input.url, "http://astroix.internal");
72
- } catch {
73
- return false;
74
- }
75
- if (url.searchParams.has("builder")) return false;
76
- const { pathname } = url;
77
- if (pathname.startsWith("/@") || pathname.startsWith("/__") || pathname.startsWith("/_astro") || pathname.startsWith("/virtual:")) {
78
- return false;
79
- }
80
- if (/(^|\/)[^/]*\.[a-zA-Z0-9]+$/.test(pathname)) return false;
81
- return true;
82
- }
10
+ import { createServerModuleRunner } from "vite";
83
11
 
84
12
  // src/node/rest.ts
85
13
  import { createHash } from "crypto";
86
- import { existsSync as existsSync2, readdirSync, readFileSync, writeFileSync } from "fs";
14
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
87
15
  import { join, relative, resolve, sep } from "path";
88
16
  import postcss2 from "postcss";
89
17
 
@@ -196,8 +124,7 @@ function registerRestEndpoints(server, options) {
196
124
  }
197
125
  async function handleApiRequest(req, res, next, server, options) {
198
126
  try {
199
- const secFetchSite = req.headers["sec-fetch-site"];
200
- if (typeof secFetchSite === "string" && secFetchSite !== "same-origin" && secFetchSite !== "none") {
127
+ if (isCrossOriginTraffic(req)) {
201
128
  json(res, 403, { error: "cross-origin builder traffic is not allowed" });
202
129
  return;
203
130
  }
@@ -217,7 +144,7 @@ async function handleApiRequest(req, res, next, server, options) {
217
144
  if (req.method === "GET" && url.pathname === "/file") {
218
145
  const file = url.searchParams.get("file");
219
146
  const absPath = file === null ? null : safeResolve(options.root, file);
220
- if (file === null || absPath === null || !existsSync2(absPath)) {
147
+ if (file === null || absPath === null || !existsSync(absPath)) {
221
148
  json(res, 400, { error: `file is missing or outside the project root: ${file ?? ""}` });
222
149
  return;
223
150
  }
@@ -316,7 +243,7 @@ async function resolveCompiledCss(server, root, file, styleBlockIndex) {
316
243
  return extractCssFromModuleCode(code);
317
244
  }
318
245
  function collectSources(srcDir) {
319
- if (!existsSync2(srcDir)) return [];
246
+ if (!existsSync(srcDir)) return [];
320
247
  const sources = [];
321
248
  const walk = (dir) => {
322
249
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
@@ -356,6 +283,15 @@ function parseEditBody(body) {
356
283
  function sha256(text) {
357
284
  return createHash("sha256").update(text).digest("hex");
358
285
  }
286
+ function isCrossOriginTraffic(req) {
287
+ const secFetchSite = req.headers["sec-fetch-site"];
288
+ return typeof secFetchSite === "string" && secFetchSite !== "same-origin" && secFetchSite !== "none";
289
+ }
290
+ function json(res, status, body) {
291
+ res.statusCode = status;
292
+ res.setHeader("content-type", "application/json; charset=utf-8");
293
+ res.end(JSON.stringify(body));
294
+ }
359
295
  function readJsonBody(req) {
360
296
  return new Promise((resolveBody, reject) => {
361
297
  const chunks = [];
@@ -379,10 +315,166 @@ function readJsonBody(req) {
379
315
  req.on("error", reject);
380
316
  });
381
317
  }
382
- function json(res, status, body) {
383
- res.statusCode = status;
384
- res.setHeader("content-type", "application/json; charset=utf-8");
385
- res.end(JSON.stringify(body));
318
+
319
+ // src/node/content.ts
320
+ function toRouteInfos(routes) {
321
+ return routes.flatMap((route) => {
322
+ if (route.type !== "page") return [];
323
+ return [
324
+ {
325
+ pattern: route.pattern,
326
+ segments: route.segments.map((segment) => segment.map((part) => ({ ...part }))),
327
+ params: [...route.params]
328
+ }
329
+ ];
330
+ });
331
+ }
332
+ function registerContentEndpoints(server, options) {
333
+ server.middlewares.use(API_PREFIX, (req, res, next) => {
334
+ void handleContentRequest(req, res, next, server, options);
335
+ });
336
+ }
337
+ async function handleContentRequest(req, res, next, server, options) {
338
+ try {
339
+ if (isCrossOriginTraffic(req)) {
340
+ json(res, 403, { error: "cross-origin builder traffic is not allowed" });
341
+ return;
342
+ }
343
+ const url = new URL(req.url ?? "/", "http://astroix.internal");
344
+ if (req.method === "GET" && url.pathname === "/collections") {
345
+ const runner = createServerModuleRunner(server.environments.ssr);
346
+ const configPath = findContentConfigPath(options.srcDir);
347
+ const configModule = configPath === null ? null : await runner.import(configPath);
348
+ const contentModule = await runner.import("astro:content");
349
+ json(res, 200, await assembleCollectionsPayload(configModule, contentModule));
350
+ return;
351
+ }
352
+ if (req.method === "GET" && url.pathname === "/routes") {
353
+ json(res, 200, options.routes.current);
354
+ return;
355
+ }
356
+ next();
357
+ } catch (error) {
358
+ next(error instanceof Error ? error : new Error(String(error)));
359
+ }
360
+ }
361
+ async function assembleCollectionsPayload(configModule, contentModule) {
362
+ const definitions = toDefinitionMap(configModule?.collections);
363
+ const collections = [];
364
+ for (const name of Object.keys(definitions).sort()) {
365
+ const entries = await loadEntries(contentModule, name);
366
+ collections.push({ name, hasSchema: definitions[name]?.schema !== void 0, entries });
367
+ }
368
+ return collections;
369
+ }
370
+ function toDefinitionMap(collections) {
371
+ if (typeof collections !== "object" || collections === null) return {};
372
+ const definitions = {};
373
+ for (const [name, definition] of Object.entries(collections)) {
374
+ if (typeof definition === "object" && definition !== null) {
375
+ definitions[name] = definition;
376
+ }
377
+ }
378
+ return definitions;
379
+ }
380
+ async function loadEntries(contentModule, name) {
381
+ const raw = await contentModule.getCollection?.(name) ?? [];
382
+ return raw.filter(
383
+ (entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string"
384
+ ).map((entry) => ({
385
+ id: entry.id,
386
+ filePath: typeof entry.filePath === "string" ? entry.filePath : null,
387
+ data: entry.data ?? null,
388
+ body: typeof entry.body === "string" ? entry.body : null
389
+ })).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
390
+ }
391
+ function findContentConfigPath(srcDir) {
392
+ const candidates2 = [
393
+ ["content.config.mjs", "content.config.js", "content.config.mts", "content.config.ts"].map(
394
+ (name) => join2(srcDir, name)
395
+ ),
396
+ ["config.ts", "config.js", "config.mjs", "config.mts"].map(
397
+ (name) => join2(srcDir, "content", name)
398
+ )
399
+ ].flat();
400
+ return candidates2.find((candidate) => existsSync2(candidate)) ?? null;
401
+ }
402
+
403
+ // src/node/source-mode.ts
404
+ import { existsSync as existsSync3 } from "fs";
405
+ import { fileURLToPath, URL as NodeURL } from "url";
406
+ var candidates = [
407
+ fileURLToPath(new NodeURL("../../src/client/entry.tsx", import.meta.url)),
408
+ fileURLToPath(new NodeURL("../src/client/entry.tsx", import.meta.url))
409
+ ];
410
+ var clientEntryPath = candidates.find((path) => existsSync3(path)) ?? null;
411
+ var chromeArtifactPath = fileURLToPath(
412
+ new NodeURL("./chrome.js", import.meta.url)
413
+ );
414
+ function isSourceMode() {
415
+ return clientEntryPath !== null;
416
+ }
417
+
418
+ // src/node/tailwind-guard.ts
419
+ function hostRegistersTailwind(viteConfig) {
420
+ const seen = /* @__PURE__ */ new Set();
421
+ const walk = (input) => {
422
+ if (Array.isArray(input)) {
423
+ for (const entry of input) walk(entry);
424
+ return;
425
+ }
426
+ if (!input || typeof input !== "object") return;
427
+ const plugin = input;
428
+ if (typeof plugin.name === "string") seen.add(plugin.name);
429
+ };
430
+ walk(viteConfig?.plugins);
431
+ for (const name of seen) {
432
+ if (name.startsWith("@tailwindcss/vite")) return true;
433
+ }
434
+ return false;
435
+ }
436
+
437
+ // src/node/vite-plugin.ts
438
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
439
+ import { join as join3 } from "path";
440
+
441
+ // src/node/chrome-html.ts
442
+ function chromeHtml() {
443
+ return `<!doctype html>
444
+ <html lang="en">
445
+ <head>
446
+ <meta charset="utf-8" />
447
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
448
+ <title>astroix builder</title>
449
+ <style>
450
+ html, body { margin: 0; height: 100%; }
451
+ #astroix-root { display: block; height: 100vh; }
452
+ </style>
453
+ </head>
454
+ <body>
455
+ <div id="astroix-root"></div>
456
+ <script type="module" src="/virtual:astroix/chrome"></script>
457
+ </body>
458
+ </html>`;
459
+ }
460
+
461
+ // src/node/document-request.ts
462
+ function isDocumentRequest(input) {
463
+ if (input.method !== "GET" && input.method !== "HEAD") return false;
464
+ if (!(input.accept ?? "").includes("text/html")) return false;
465
+ let url;
466
+ try {
467
+ url = new URL(input.url, "http://astroix.internal");
468
+ } catch {
469
+ return false;
470
+ }
471
+ if (url.searchParams.has("builder")) return false;
472
+ const { pathname } = url;
473
+ if (pathname.startsWith("/@") || pathname.startsWith("/__") || pathname.startsWith("/_astro") || pathname.startsWith("/virtual:")) {
474
+ return false;
475
+ }
476
+ if (/(^|\/)[^/]*\.[a-zA-Z0-9]+$/.test(pathname)) return false;
477
+ return true;
386
478
  }
387
479
 
388
480
  // src/node/watch-sync.ts
@@ -416,7 +508,7 @@ function registerFileSync(server, options) {
416
508
 
417
509
  // src/node/vite-plugin.ts
418
510
  var VIRTUAL_CHROME_ID = "virtual:astroix/chrome";
419
- function astroixVitePlugin(options = {}) {
511
+ function astroixVitePlugin(options) {
420
512
  return {
421
513
  name: "astroix",
422
514
  configureServer(server) {
@@ -437,14 +529,10 @@ function astroixVitePlugin(options = {}) {
437
529
  }
438
530
  })();
439
531
  });
440
- registerRestEndpoints(server, {
441
- root: server.config.root,
442
- srcDir: options.srcDir ?? join2(server.config.root, "src")
443
- });
444
- registerFileSync(server, {
445
- root: server.config.root,
446
- srcDir: options.srcDir ?? join2(server.config.root, "src")
447
- });
532
+ const srcDir = options.srcDir ?? join3(server.config.root, "src");
533
+ registerRestEndpoints(server, { root: server.config.root, srcDir });
534
+ registerContentEndpoints(server, { srcDir, routes: options.routes });
535
+ registerFileSync(server, { root: server.config.root, srcDir });
448
536
  },
449
537
  resolveId(id) {
450
538
  if (id === VIRTUAL_CHROME_ID || id === `/${VIRTUAL_CHROME_ID}`) {
@@ -459,7 +547,7 @@ function astroixVitePlugin(options = {}) {
459
547
  mountChrome();
460
548
  `;
461
549
  }
462
- if (!existsSync3(chromeArtifactPath)) {
550
+ if (!existsSync4(chromeArtifactPath)) {
463
551
  throw new Error(
464
552
  "astroix: prebuilt chrome bundle is missing from the package build (expected dist/chrome.js)"
465
553
  );
@@ -477,12 +565,18 @@ var CANVAS_SCRIPT = `if (window.parent !== window && new URLSearchParams(locatio
477
565
  }
478
566
  `;
479
567
  function astroix() {
568
+ const routesState = { current: [] };
480
569
  return {
481
570
  name: "astroix",
482
571
  hooks: {
572
+ "astro:routes:resolved": ({ routes }) => {
573
+ routesState.current = toRouteInfos(routes);
574
+ },
483
575
  "astro:config:setup": ({ config, command, updateConfig, injectScript, logger }) => {
484
576
  if (command !== "dev") return;
485
- const plugins = [astroixVitePlugin({ srcDir: fileURLToPath2(config.srcDir) })];
577
+ const plugins = [
578
+ astroixVitePlugin({ srcDir: fileURLToPath2(config.srcDir), routes: routesState })
579
+ ];
486
580
  let vitePatch = { plugins };
487
581
  if (isSourceMode()) {
488
582
  const clientDir = dirname(clientEntryPath ?? "");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/node/index.ts","../src/node/source-mode.ts","../src/node/tailwind-guard.ts","../src/node/vite-plugin.ts","../src/node/chrome-html.ts","../src/node/document-request.ts","../src/node/rest.ts","../src/core/indexer.ts","../src/core/splice-writer.ts","../src/node/watch-sync.ts"],"sourcesContent":["import { dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport tailwindcss from '@tailwindcss/vite';\nimport react from '@vitejs/plugin-react';\nimport type { AstroIntegration } from 'astro';\nimport type { Plugin as VitePlugin } from 'vite';\nimport { clientEntryPath, isSourceMode } from './source-mode';\nimport { hostRegistersTailwind } from './tailwind-guard';\nimport { astroixVitePlugin } from './vite-plugin';\n\n/**\n * Canvas script injected into every dev page (the chrome document itself is\n * never Astro-rendered, so this only ever runs inside host pages). It decides\n * on its own whether it is the builder canvas: inside the iframe\n * (`window.parent !== window`) with `?builder=0`, it hides Astro's dev\n * toolbar — the toolbar stays available on normal page loads (spec #2).\n */\nconst CANVAS_SCRIPT = `if (window.parent !== window && new URLSearchParams(location.search).get('builder') === '0') {\n const style = document.createElement('style');\n style.textContent = 'astro-dev-toolbar{display:none!important}';\n document.head.append(style);\n}\n`;\n\n/**\n * Astroix — dev-only visual builder integration.\n *\n * In dev: the astroix Vite plugin serves the builder chrome over every\n * top-level URL (default-on) with the `?builder=0` escape hatch, and the\n * virtual chrome module delivers the app (source mode in this checkout per\n * ADR-0001; the prebuilt bundle lands with the chrome packaging slice).\n * Any other command registers nothing — the dev-only guarantee.\n */\nfunction astroix(): AstroIntegration {\n return {\n name: 'astroix',\n hooks: {\n 'astro:config:setup': ({ config, command, updateConfig, injectScript, logger }) => {\n if (command !== 'dev') return;\n\n // The resolved config turns dir strings into URLs (trailing slash included) —\n // the plugin wants clean paths.\n const plugins: VitePlugin[] = [astroixVitePlugin({ srcDir: fileURLToPath(config.srcDir) })];\n // The chrome sources live outside the host root and are served via\n // /@fs, which has two consequences fixed below: (a) deps discovered\n // from /@fs files resolve against the importer's location, so `react`\n // can enter the optimizer from two paths and mount twice (Invalid\n // hook call) — dedupe pins every resolution to the host root's React,\n // which in the dev checkout is our own 19; (b) HMR re-fetches carry a\n // `?t=` timestamp that misses the import-chain fs exemption — the\n // checkout root joins the allow list so chrome modules always serve.\n let vitePatch: {\n plugins: VitePlugin[];\n resolve?: { dedupe: string[] };\n server?: { fs: { allow: string[] } };\n } = { plugins };\n\n if (isSourceMode()) {\n // ADR-0001 source mode: chrome from this checkout's source, with\n // fast-refresh scoped to chrome files only (host code untouched).\n const clientDir = dirname(clientEntryPath ?? '');\n // compiler: true = React Compiler via oxc (stack #4: no manual memoization).\n plugins.push(\n ...react({\n include: new RegExp(`^${escapeRegExp(clientDir)}/.*\\\\.tsx?$`),\n compiler: true,\n }),\n );\n if (hostRegistersTailwind(config.vite)) {\n logger.info('host already registers @tailwindcss/vite — reusing it for the chrome');\n } else {\n plugins.push(...tailwindcss());\n }\n vitePatch = {\n plugins,\n resolve: { dedupe: ['react', 'react-dom'] },\n server: { fs: { allow: [dirname(dirname(clientDir))] } },\n };\n }\n\n updateConfig({ vite: vitePatch });\n injectScript('page', CANVAS_SCRIPT);\n },\n },\n };\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replaceAll(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport default astroix;\n","import { existsSync } from 'node:fs';\nimport { fileURLToPath, URL as NodeURL } from 'node:url';\n\n/**\n * Dev-checkout detection for the ADR-0001 mode switch: the chrome client\n * sources exist next to the integration only when the package runs from this\n * repo (an installed package ships no `src/`). Two candidate depths because\n * this module executes from `src/node/` during development and from the\n * bundled `dist/index.js` at runtime. `URL` comes from `node:url` because\n * happy-dom (unit tests) patches the global `URL` with its own class, which\n * `fileURLToPath` rejects.\n */\nconst candidates = [\n fileURLToPath(new NodeURL('../../src/client/entry.tsx', import.meta.url)),\n fileURLToPath(new NodeURL('../src/client/entry.tsx', import.meta.url)),\n];\n\nexport const clientEntryPath: string | null = candidates.find((path) => existsSync(path)) ?? null;\n\n/**\n * The prebuilt chrome bundle (ADR-0001): a self-contained ESM shipped inside\n * `dist/`. Served by the virtual chrome module when the dev-checkout sources\n * are absent — the consumer-facing delivery mode.\n */\nexport const chromeArtifactPath: string = fileURLToPath(\n new NodeURL('./chrome.js', import.meta.url),\n);\n\nexport function isSourceMode(): boolean {\n return clientEntryPath !== null;\n}\n","import type { AstroUserConfig } from 'astro';\n\n/**\n * Guard: skip our Tailwind plugin when the host already registered one.\n * The `@tailwindcss/vite` factory returns an array whose plugin names start\n * with `@tailwindcss/vite:` (verified on 4.x) — that prefix is the check.\n */\nexport function hostRegistersTailwind(viteConfig: AstroUserConfig['vite']): boolean {\n const seen = new Set<string>();\n const walk = (input: unknown): void => {\n if (Array.isArray(input)) {\n for (const entry of input) walk(entry);\n return;\n }\n if (!input || typeof input !== 'object') return;\n const plugin = input as { name?: unknown };\n if (typeof plugin.name === 'string') seen.add(plugin.name);\n };\n walk(viteConfig?.plugins);\n for (const name of seen) {\n if (name.startsWith('@tailwindcss/vite')) return true;\n }\n return false;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Plugin, ViteDevServer } from 'vite';\nimport { chromeHtml } from './chrome-html';\nimport { isDocumentRequest } from './document-request';\nimport { registerRestEndpoints } from './rest';\nimport { chromeArtifactPath, clientEntryPath } from './source-mode';\nimport { registerFileSync } from './watch-sync';\n\nexport const VIRTUAL_CHROME_ID = 'virtual:astroix/chrome';\n\nexport interface AstroixPluginOptions {\n /** Absolute Astro src dir with the sources to index; defaults to `<root>/src`. */\n srcDir?: string;\n}\n\n/**\n * The astroix Vite plugin: default-on chrome over every top-level dev URL.\n * The middleware is registered in the body of `configureServer` (pre-internal)\n * because Astro's dev handler lives in a post-hook and never calls `next()` —\n * this is the only position that sees every request (core-reuse §1). The\n * chrome HTML passes through `server.transformIndexHtml` (the plugin hook\n * never fires for Astro pages, the server API does) which injects the Vite\n * client and the plugin-react preamble.\n */\nexport function astroixVitePlugin(options: AstroixPluginOptions = {}): Plugin {\n return {\n name: 'astroix',\n configureServer(server: ViteDevServer) {\n server.middlewares.use((req, res, next) => {\n void (async () => {\n try {\n const url = req.url ?? '/';\n if (\n !isDocumentRequest({ method: req.method ?? 'GET', url, accept: req.headers.accept })\n ) {\n next();\n return;\n }\n const html = await server.transformIndexHtml(url, chromeHtml());\n res.statusCode = 200;\n res.setHeader('content-type', 'text/html; charset=utf-8');\n res.end(html);\n } catch (error) {\n next(error instanceof Error ? error : new Error(String(error)));\n }\n })();\n });\n registerRestEndpoints(server, {\n root: server.config.root,\n srcDir: options.srcDir ?? join(server.config.root, 'src'),\n });\n registerFileSync(server, {\n root: server.config.root,\n srcDir: options.srcDir ?? join(server.config.root, 'src'),\n });\n },\n resolveId(id) {\n // The HTML references `/virtual:astroix/chrome`; imports may use the bare id.\n if (id === VIRTUAL_CHROME_ID || id === `/${VIRTUAL_CHROME_ID}`) {\n return VIRTUAL_CHROME_ID;\n }\n return null;\n },\n load(id) {\n if (id !== VIRTUAL_CHROME_ID) return null;\n if (clientEntryPath !== null) {\n // ADR-0001 source mode: the chrome loads from this checkout's source,\n // so the host dev server transforms it (fast-refresh, Tailwind).\n return `import { mountChrome } from '/@fs${clientEntryPath}';\\nmountChrome();\\n`;\n }\n // ADR-0001 prebuilt mode: serve the shipped bundle — a self-contained\n // ESM with react, the compiled CSS and CodeMirror inside, so foreign\n // hosts resolve none of our chrome dependencies. Missing artifact =\n // broken package build; fail loudly, never silently (ADR-0001).\n if (!existsSync(chromeArtifactPath)) {\n throw new Error(\n 'astroix: prebuilt chrome bundle is missing from the package build (expected dist/chrome.js)',\n );\n }\n return readFileSync(chromeArtifactPath, 'utf8');\n },\n };\n}\n","/**\n * The chrome document shell: a mount point and the virtual-module reference.\n * Layout lives inside the shadow root (React app); the document only resets\n * geometry so the shadow host can fill the viewport.\n */\nexport function chromeHtml(): string {\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>astroix builder</title>\n <style>\n html, body { margin: 0; height: 100%; }\n #astroix-root { display: block; height: 100vh; }\n </style>\n </head>\n <body>\n <div id=\"astroix-root\"></div>\n <script type=\"module\" src=\"/virtual:astroix/chrome\"></script>\n </body>\n</html>`;\n}\n","/**\n * Is this request a top-level document navigation the builder should wrap?\n * Deliberately conservative: anything asset-like, internal to Vite/Astro, or\n * carrying an explicit `builder` param falls through to the host.\n */\nexport function isDocumentRequest(input: {\n method: string;\n url: string;\n accept?: string | undefined;\n}): boolean {\n if (input.method !== 'GET' && input.method !== 'HEAD') return false;\n if (!(input.accept ?? '').includes('text/html')) return false;\n\n let url: URL;\n try {\n url = new URL(input.url, 'http://astroix.internal');\n } catch {\n return false;\n }\n if (url.searchParams.has('builder')) return false;\n\n const { pathname } = url;\n if (\n pathname.startsWith('/@') ||\n pathname.startsWith('/__') ||\n pathname.startsWith('/_astro') ||\n pathname.startsWith('/virtual:')\n ) {\n return false;\n }\n // A dot in the last path segment reads as an asset (`home.css`, `foo.png`).\n if (/(^|\\/)[^/]*\\.[a-zA-Z0-9]+$/.test(pathname)) return false;\n return true;\n}\n","import { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { join, relative, resolve, sep } from 'node:path';\nimport postcss from 'postcss';\nimport type { ViteDevServer } from 'vite';\nimport { buildCssIndex, type SourceFile } from '../core/indexer';\nimport type { IndexPayloadRecord } from '../core/matcher';\nimport { SpliceRangeError, spliceText } from '../core/splice-writer';\n\nconst API_PREFIX = '/__astroix';\nconst MAX_BODY_BYTES = 1_000_000;\n\nexport interface RestOptions {\n /** Absolute project root (Vite root). */\n root: string;\n /** Absolute Astro src dir holding the css/astro sources to index. */\n srcDir: string;\n}\n\n/**\n * Registers the chrome↔node contract on the Vite connect middleware\n * (core-reuse §2 — like core's `/_astro/status`, not Astro app middleware):\n *\n * - `GET /__astroix/index` — the index payload: edit-truth records joined\n * with compiled scoped forms from the client module graph.\n * - `POST /__astroix/edit` — `{ file, range, replacement }` spliced to disk.\n *\n * Same-origin only: a browser `sec-fetch-site` header that is not\n * same-origin/none is rejected (T2).\n */\nexport function registerRestEndpoints(server: ViteDevServer, options: RestOptions): void {\n server.middlewares.use(API_PREFIX, (req, res, next) => {\n void handleApiRequest(req, res, next, server, options);\n });\n}\n\nasync function handleApiRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void,\n server: ViteDevServer,\n options: RestOptions,\n): Promise<void> {\n try {\n const secFetchSite = req.headers['sec-fetch-site'];\n if (\n typeof secFetchSite === 'string' &&\n secFetchSite !== 'same-origin' &&\n secFetchSite !== 'none'\n ) {\n json(res, 403, { error: 'cross-origin builder traffic is not allowed' });\n return;\n }\n\n const url = new URL(req.url ?? '/', 'http://astroix.internal');\n\n // The middleware is mounted at /__astroix (connect strips the prefix),\n // so the GET path arrives as /index.\n if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/index')) {\n const payload = await buildIndexPayload(collectSources(options.srcDir), (file, blockIndex) =>\n resolveCompiledCss(server, options.root, file, blockIndex),\n );\n // Payload paths are project-relative; the join worked in absolute space.\n json(\n res,\n 200,\n payload.map((record) => ({ ...record, file: toRelative(options.root, record.file) })),\n );\n return;\n }\n\n // File content for the editor pane — a dedicated endpoint (not payload\n // fields) so contents are fresh exactly when a rule is opened and the\n // payload stays small. Same root confinement as the edit endpoint.\n if (req.method === 'GET' && url.pathname === '/file') {\n const file = url.searchParams.get('file');\n const absPath = file === null ? null : safeResolve(options.root, file);\n if (file === null || absPath === null || !existsSync(absPath)) {\n json(res, 400, { error: `file is missing or outside the project root: ${file ?? ''}` });\n return;\n }\n json(res, 200, { file, contents: readFileSync(absPath, 'utf8') });\n return;\n }\n\n if (req.method === 'POST' && url.pathname === '/edit') {\n const body = await readJsonBody(req);\n const { file, range, replacement, expected } = parseEditBody(body);\n if (file === null || range === null || replacement === null) {\n json(res, 400, { error: 'expected { file, range: { start, end }, replacement }' });\n return;\n }\n const absPath = safeResolve(options.root, file);\n if (absPath === null) {\n json(res, 400, { error: `file is outside the project root: ${file}` });\n return;\n }\n const contents = readFileSync(absPath, 'utf8');\n // Optimistic write check: the chrome sends the hash of the content it\n // based its edit on. A mismatch means the file changed on disk under us\n // (IDE edit racing the debounce) — refuse instead of splicing stale\n // offsets into a shifted file, and hand back the current contents so\n // the editor can reload in one roundtrip.\n if (expected !== null && sha256(contents) !== expected) {\n json(res, 409, { error: 'file changed on disk', contents });\n return;\n }\n try {\n writeFileSync(\n absPath,\n spliceText(contents, { start: range[0], end: range[1], replacement }),\n );\n } catch (error) {\n if (error instanceof SpliceRangeError) {\n json(res, 400, { error: error.message });\n return;\n }\n throw error;\n }\n json(res, 200, { ok: true });\n return;\n }\n\n next();\n } catch (error) {\n next(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/** Supplies the compiled css of a scoped style module, or null when absent. */\nexport type CompiledCssResolver = (file: string, styleBlockIndex: number) => Promise<string | null>;\n\n/**\n * The module-graph hybrid join: static records plus effective selectors for\n * scoped rules. Scoped records of one style block correlate with the compiled\n * rules of module `{file}.astro?astro&type=style&index={N}` in rule order; a\n * block with no compiled module (not loaded on the current route, or a rule\n * count mismatch) stays listed without an effective selector — the liveness\n * line of v1.\n */\nexport async function buildIndexPayload(\n sources: SourceFile[],\n resolveCompiledCss: CompiledCssResolver,\n): Promise<IndexPayloadRecord[]> {\n const payload: IndexPayloadRecord[] = buildCssIndex(sources).map((record) => ({\n ...record,\n effectiveSelector: null,\n }));\n\n const blocks = new Map<string, { file: string; styleBlockIndex: number; positions: number[] }>();\n payload.forEach((record, position) => {\n if (!record.scoped || record.styleBlockIndex === null) return;\n const key = `${record.file}\\u0000${record.styleBlockIndex}`;\n const block = blocks.get(key) ?? {\n file: record.file,\n styleBlockIndex: record.styleBlockIndex,\n positions: [],\n };\n block.positions.push(position);\n blocks.set(key, block);\n });\n\n for (const block of blocks.values()) {\n const css = await resolveCompiledCss(block.file, block.styleBlockIndex);\n if (css === null) continue;\n const selectors = compiledSelectors(css);\n block.positions.forEach((position, ruleOrder) => {\n const effectiveSelector = selectors[ruleOrder];\n const record = payload[position];\n if (effectiveSelector !== undefined && record !== undefined) {\n record.effectiveSelector = effectiveSelector;\n }\n });\n }\n return payload;\n}\n\n/** Selectors of the compiled css in rule order — the join's correlation key. */\nexport function compiledSelectors(css: string): string[] {\n const selectors: string[] = [];\n postcss.parse(css).walkRules((rule) => {\n selectors.push(rule.selector);\n });\n return selectors;\n}\n\n/** Pulls the css text out of a dev-transformed css module's code. */\nexport function extractCssFromModuleCode(code: string): string | null {\n const match = code.match(/__vite__css = (\"(?:[^\"\\\\]|\\\\.)*\")/);\n if (match?.[1] === undefined) return null;\n try {\n return JSON.parse(match[1]) as string;\n } catch {\n return null;\n }\n}\n\nasync function resolveCompiledCss(\n server: ViteDevServer,\n root: string,\n file: string,\n styleBlockIndex: number,\n): Promise<string | null> {\n const moduleUrl = `/${toRelative(root, file)}?astro&type=style&index=${styleBlockIndex}&lang.css`;\n const module = await server.environments.client.moduleGraph.getModuleByUrl(moduleUrl);\n const code = module?.transformResult?.code;\n if (code === undefined || code === null) return null;\n return extractCssFromModuleCode(code);\n}\n\n/** Walks `src/**` collecting the css/astro sources the indexer consumes. */\nexport function collectSources(srcDir: string): SourceFile[] {\n if (!existsSync(srcDir)) return [];\n const sources: SourceFile[] = [];\n const walk = (dir: string): void => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.name.endsWith('.css') || entry.name.endsWith('.astro')) {\n sources.push({ file: full, contents: readFileSync(full, 'utf8') });\n }\n }\n };\n walk(srcDir);\n return sources;\n}\n\n/** Project-relative posix path — also the file id used by the sync events. */\nexport function toRelative(root: string, file: string): string {\n return relative(root, file).split(sep).join('/');\n}\n\nfunction safeResolve(root: string, file: string): string | null {\n const absPath = resolve(root, file);\n if (absPath !== root && !absPath.startsWith(`${root}${sep}`)) return null;\n return absPath;\n}\n\nfunction parseEditBody(body: unknown): {\n file: string | null;\n range: [number, number] | null;\n replacement: string | null;\n expected: string | null;\n} {\n if (body === null || typeof body !== 'object') {\n return { file: null, range: null, replacement: null, expected: null };\n }\n const { file, range, replacement, expected } = body as Record<string, unknown>;\n const validRange =\n typeof range === 'object' &&\n range !== null &&\n typeof (range as Record<string, unknown>).start === 'number' &&\n typeof (range as Record<string, unknown>).end === 'number'\n ? ([(range as Record<string, unknown>).start, (range as Record<string, unknown>).end] as [\n number,\n number,\n ])\n : null;\n return {\n file: typeof file === 'string' ? file : null,\n range: validRange,\n replacement: typeof replacement === 'string' ? replacement : null,\n expected: typeof expected === 'string' ? expected : null,\n };\n}\n\nfunction sha256(text: string): string {\n return createHash('sha256').update(text).digest('hex');\n}\n\nfunction readJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolveBody, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n reject(new Error('request body too large'));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on('end', () => {\n try {\n resolveBody(JSON.parse(Buffer.concat(chunks).toString('utf8')));\n } catch {\n reject(new Error('request body is not valid JSON'));\n }\n });\n req.on('error', reject);\n });\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n res.statusCode = status;\n res.setHeader('content-type', 'application/json; charset=utf-8');\n res.end(JSON.stringify(body));\n}\n","import { extractStylesSync } from '@astrojs/compiler-binding';\nimport postcss from 'postcss';\n\n/** A project CSS source to index: path + raw contents. No IO happens here. */\nexport interface SourceFile {\n file: string;\n contents: string;\n}\n\n/**\n * One rule from the edit-truth index. The range is in character offsets of\n * `file` and covers the rule from its selector through the closing brace\n * (end-exclusive) — the splice-writer edits inside these bounds.\n */\nexport interface CssRuleRecord {\n /** Selector text verbatim from source (source space — no cid synthesis here). */\n selector: string;\n file: string;\n range: { start: number; end: number };\n /**\n * One-based line of the rule's selector in `file`, derived from the range\n * at index time (the indexer holds the contents) — the rule list shows it.\n */\n line: number;\n /** Condition of the nearest `@media` ancestor, or null at the top level. */\n media: string | null;\n /** True for rules from a scoped `<style>` block (the compiler applies the cid). */\n scoped: boolean;\n /**\n * Zero-based style-block index correlating with the module-graph module id\n * `{file}.astro?astro&type=style&index={N}` — the join key for the index\n * payload. Null when the block is not in the module graph (`is:inline`).\n */\n styleBlockIndex: number | null;\n}\n\ninterface BlockMeta {\n scoped: boolean;\n styleBlockIndex: number | null;\n baseOffset: number;\n}\n\nconst STYLE_TAG = /<style\\b[^>]*>([\\s\\S]*?)<\\/style>/g;\n\n/**\n * The indexer: scans project CSS sources into the edit-truth index\n * (selector → file, source range, media condition). Dev generates no CSS\n * sourcemaps, so this static scan is the only mapping to what's on disk —\n * and the only one that sees `is:inline` blocks.\n */\nexport function buildCssIndex(sources: SourceFile[]): CssRuleRecord[] {\n const records: CssRuleRecord[] = [];\n for (const source of sources) {\n const fileRecords = source.file.endsWith('.css')\n ? indexStylesheet(source.file, source.contents, {\n scoped: false,\n styleBlockIndex: null,\n baseOffset: 0,\n })\n : source.file.endsWith('.astro')\n ? indexAstroStyles(source.file, source.contents)\n : [];\n // Lines derive from absolute offsets against the whole file — the .astro\n // blocks were parsed as substrings but carry absolute ranges.\n for (const record of fileRecords) {\n records.push({ ...record, line: lineAt(source.contents, record.range.start) });\n }\n }\n return records;\n}\n\n/** One-based line number of a character offset. */\nfunction lineAt(contents: string, offset: number): number {\n let line = 1;\n for (let i = 0; i < offset && i < contents.length; i++) {\n if (contents[i] === '\\n') line += 1;\n }\n return line;\n}\n\nfunction indexStylesheet(\n file: string,\n css: string,\n meta: BlockMeta,\n): Omit<CssRuleRecord, 'line'>[] {\n const records: Omit<CssRuleRecord, 'line'>[] = [];\n postcss.parse(css).walkRules((rule) => {\n const start = rule.source?.start;\n const end = rule.source?.end;\n if (start === undefined || end === undefined) return;\n records.push({\n selector: rule.selector,\n file,\n range: { start: start.offset + meta.baseOffset, end: end.offset + meta.baseOffset },\n media: nearestMediaCondition(rule),\n scoped: meta.scoped,\n styleBlockIndex: meta.styleBlockIndex,\n });\n });\n return records;\n}\n\nfunction indexAstroStyles(file: string, source: string): Omit<CssRuleRecord, 'line'>[] {\n const records: Omit<CssRuleRecord, 'line'>[] = [];\n // extractStylesSync returns only blocks the compiler would process —\n // `is:inline` (and expression-attribute blocks) never make it there, so the\n // raw tag scan is the edit-truth pass and the compiler blocks supply the\n // module-graph index.\n const processed = extractStylesSync(source);\n let next = 0;\n\n for (const match of source.matchAll(STYLE_TAG)) {\n const content = match[1];\n if (content === undefined) continue;\n const openTag = match[0].slice(0, match[0].indexOf('>') + 1);\n const contentStart = match.index + openTag.length;\n\n const compilerBlock = processed[next];\n if (compilerBlock !== undefined && compilerBlock.content === content) {\n next += 1;\n records.push(\n ...indexStylesheet(file, content, {\n scoped: compilerBlock.attrs['is:global'] === undefined,\n styleBlockIndex: compilerBlock.index,\n baseOffset: contentStart,\n }),\n );\n } else {\n records.push(\n ...indexStylesheet(file, content, {\n scoped: false,\n styleBlockIndex: null,\n baseOffset: contentStart,\n }),\n );\n }\n }\n return records;\n}\n\nfunction nearestMediaCondition(node: postcss.Node): string | null {\n for (let parent = node.parent; parent !== undefined; parent = parent.parent) {\n if (parent.type !== 'atrule') continue;\n const atRule = parent as postcss.AtRule;\n if (atRule.name === 'media') {\n return atRule.params;\n }\n }\n return null;\n}\n","/**\n * A single text edit: replace the half-open range `[start, end)` of the file\n * content with `replacement`. Zero-length ranges insert purely. The editor\n * debounces and sends one edit at a time — this is the only write primitive.\n */\nexport interface SpliceEdit {\n start: number;\n end: number;\n replacement: string;\n}\n\n/** Thrown for ranges that do not fit the content — never produces partial output. */\nexport class SpliceRangeError extends Error {\n constructor(start: number, end: number, contentLength: number) {\n super(`Invalid splice range [${start}, ${end}) for content of length ${contentLength}`);\n this.name = 'SpliceRangeError';\n }\n}\n\n/**\n * The splice-writer primitive: (content, range, replacement) → new content.\n * Text-splice only — never reprints the file. Every byte outside the replaced\n * range stays identical, so formatting, comments and agent conventions\n * survive and the git diff is minimal.\n */\nexport function spliceText(content: string, edit: SpliceEdit): string {\n const { start, end, replacement } = edit;\n if (\n !Number.isInteger(start) ||\n !Number.isInteger(end) ||\n start < 0 ||\n end > content.length ||\n start > end\n ) {\n throw new SpliceRangeError(start, end, content.length);\n }\n return content.slice(0, start) + replacement + content.slice(end);\n}\n\n/**\n * Append a rule at EOF with exactly one added line, regardless of whether the\n * original ends with a newline (no accidental blank runs; the file's\n * trailing-newline convention is preserved).\n */\nexport function appendRule(content: string, rule: string): string {\n if (content === '') {\n return rule;\n }\n const endsWithNewline = content.endsWith('\\n');\n const body = endsWithNewline ? content : `${content}\\n`;\n return `${body}${rule}${endsWithNewline ? '\\n' : ''}`;\n}\n","import { sep } from 'node:path';\nimport type { ViteDevServer } from 'vite';\nimport { toRelative } from './rest';\n\n/**\n * The file→chrome half of the sync (spec #13): the host watcher is the only\n * FS subscriber; css/astro changes under the project's src dir are debounced\n * per file and pushed to the chrome as `astroix:file-changed` over the Vite\n * WebSocket — the same channel Astro uses for its own events. The chrome\n * refetches content/payload on receipt; its own writes echo back as no-ops\n * (content compare client-side).\n */\nexport function registerFileSync(\n server: ViteDevServer,\n options: { root: string; srcDir: string },\n): void {\n // astro hands srcDir as a URL that keeps a trailing slash — strip it or\n // the startsWith filter below never matches\n const srcDir = options.srcDir.split(sep).join('/').replace(/\\/+$/, '');\n const pending = new Map<string, ReturnType<typeof setTimeout>>();\n\n const push = (file: string): void => {\n const timer = pending.get(file);\n if (timer !== undefined) clearTimeout(timer);\n pending.set(\n file,\n setTimeout(() => {\n pending.delete(file);\n server.ws.send('astroix:file-changed', { file: toRelative(options.root, file) });\n }, 100),\n );\n };\n\n const isWatchedSource = (file: string): boolean => {\n const norm = file.split(sep).join('/');\n if (!norm.startsWith(`${srcDir}/`)) return false;\n return norm.endsWith('.css') || norm.endsWith('.astro');\n };\n\n // `add` matters too: IDE atomic saves (write temp + rename) can surface as\n // add instead of change depending on the editor\n server.watcher.on('change', (file) => {\n if (isWatchedSource(file)) push(file);\n });\n server.watcher.on('add', (file) => {\n if (isWatchedSource(file)) push(file);\n });\n}\n"],"mappings":"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","names":["fileURLToPath","existsSync","readFileSync","join","existsSync","postcss","existsSync","resolveCompiledCss","postcss","sep","sep","join","existsSync","readFileSync","fileURLToPath"]}
1
+ {"version":3,"sources":["../src/node/index.ts","../src/node/content.ts","../src/node/rest.ts","../src/core/indexer.ts","../src/core/splice-writer.ts","../src/node/source-mode.ts","../src/node/tailwind-guard.ts","../src/node/vite-plugin.ts","../src/node/chrome-html.ts","../src/node/document-request.ts","../src/node/watch-sync.ts"],"sourcesContent":["import { dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport tailwindcss from '@tailwindcss/vite';\nimport react from '@vitejs/plugin-react';\nimport type { AstroIntegration } from 'astro';\nimport type { Plugin as VitePlugin } from 'vite';\nimport { type RoutesState, toRouteInfos } from './content';\nimport { clientEntryPath, isSourceMode } from './source-mode';\nimport { hostRegistersTailwind } from './tailwind-guard';\nimport { astroixVitePlugin } from './vite-plugin';\n\n/**\n * Canvas script injected into every dev page (the chrome document itself is\n * never Astro-rendered, so this only ever runs inside host pages). It decides\n * on its own whether it is the builder canvas: inside the iframe\n * (`window.parent !== window`) with `?builder=0`, it hides Astro's dev\n * toolbar — the toolbar stays available on normal page loads (spec #2).\n */\nconst CANVAS_SCRIPT = `if (window.parent !== window && new URLSearchParams(location.search).get('builder') === '0') {\n const style = document.createElement('style');\n style.textContent = 'astro-dev-toolbar{display:none!important}';\n document.head.append(style);\n}\n`;\n\n/**\n * Astroix — dev-only visual builder integration.\n *\n * In dev: the astroix Vite plugin serves the builder chrome over every\n * top-level URL (default-on) with the `?builder=0` escape hatch, and the\n * virtual chrome module delivers the app (source mode in this checkout per\n * ADR-0001; the prebuilt bundle lands with the chrome packaging slice).\n * Any other command registers nothing — the dev-only guarantee.\n */\nfunction astroix(): AstroIntegration {\n // Routes captured from the hook below and served at `GET /__astroix/routes`\n // (spec Impl #13). Lives on the integration instance so both hooks —\n // the writer and the plugin that serves the state — share one container\n // across dev restarts (restarts re-run the routes hook on the same instance).\n const routesState: RoutesState = { current: [] };\n return {\n name: 'astroix',\n hooks: {\n 'astro:routes:resolved': ({ routes }) => {\n routesState.current = toRouteInfos(routes);\n },\n 'astro:config:setup': ({ config, command, updateConfig, injectScript, logger }) => {\n if (command !== 'dev') return;\n\n // The resolved config turns dir strings into URLs (trailing slash included) —\n // the plugin wants clean paths.\n const plugins: VitePlugin[] = [\n astroixVitePlugin({ srcDir: fileURLToPath(config.srcDir), routes: routesState }),\n ];\n // The chrome sources live outside the host root and are served via\n // /@fs, which has two consequences fixed below: (a) deps discovered\n // from /@fs files resolve against the importer's location, so `react`\n // can enter the optimizer from two paths and mount twice (Invalid\n // hook call) — dedupe pins every resolution to the host root's React,\n // which in the dev checkout is our own 19; (b) HMR re-fetches carry a\n // `?t=` timestamp that misses the import-chain fs exemption — the\n // checkout root joins the allow list so chrome modules always serve.\n let vitePatch: {\n plugins: VitePlugin[];\n resolve?: { dedupe: string[] };\n server?: { fs: { allow: string[] } };\n } = { plugins };\n\n if (isSourceMode()) {\n // ADR-0001 source mode: chrome from this checkout's source, with\n // fast-refresh scoped to chrome files only (host code untouched).\n const clientDir = dirname(clientEntryPath ?? '');\n // compiler: true = React Compiler via oxc (stack #4: no manual memoization).\n plugins.push(\n ...react({\n include: new RegExp(`^${escapeRegExp(clientDir)}/.*\\\\.tsx?$`),\n compiler: true,\n }),\n );\n if (hostRegistersTailwind(config.vite)) {\n logger.info('host already registers @tailwindcss/vite — reusing it for the chrome');\n } else {\n plugins.push(...tailwindcss());\n }\n vitePatch = {\n plugins,\n resolve: { dedupe: ['react', 'react-dom'] },\n server: { fs: { allow: [dirname(dirname(clientDir))] } },\n };\n }\n\n updateConfig({ vite: vitePatch });\n injectScript('page', CANVAS_SCRIPT);\n },\n },\n };\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replaceAll(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport default astroix;\n","import { existsSync } from 'node:fs';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { join } from 'node:path';\nimport type { IntegrationResolvedRoute } from 'astro';\nimport { createServerModuleRunner, type ViteDevServer } from 'vite';\nimport type { RouteInfo } from '../core/route-resolver';\nimport { API_PREFIX, isCrossOriginTraffic, json } from './rest';\n\n/** A single collection entry as served to the chrome (core's getCollection shape, JSON-projected). */\nexport interface CollectionEntryRecord {\n /** Slugified source path (glob loader id), e.g. `2024/post`. */\n id: string;\n /** Root-relative posix source path, or null for store entries without one. */\n filePath: string | null;\n /** Parsed frontmatter (zod output). */\n data: unknown;\n /** Raw markdown body, or null for data-only entries. */\n body: string | null;\n}\n\n/** A collection with its entries and schema presence (spec Impl #4 — read side). */\nexport interface CollectionRecord {\n name: string;\n hasSchema: boolean;\n entries: CollectionEntryRecord[];\n}\n\n/** Shared container between the `astro:routes:resolved` hook (writer) and the REST layer (reader). */\nexport interface RoutesState {\n current: RouteInfo[];\n}\n\n/**\n * Projects hook routes to the `RouteInfo` contract of `src/core/route-resolver`\n * (single source of truth per the core-first ruling on PR #77): page routes\n * only — the resolver's contract filters out `endpoint`/`redirect`/`fallback`\n * types at the payload — with Astro's own `segments` parse carried along,\n * deep-copied so no live core object is held between hook runs.\n */\nexport function toRouteInfos(routes: readonly IntegrationResolvedRoute[]): RouteInfo[] {\n return routes.flatMap((route) => {\n if (route.type !== 'page') return [];\n return [\n {\n pattern: route.pattern,\n segments: route.segments.map((segment) => segment.map((part) => ({ ...part }))),\n params: [...route.params],\n },\n ];\n });\n}\n\nexport interface ContentRestOptions {\n /** Absolute Astro src dir (where the content config lives). */\n srcDir: string;\n /** Routes captured by the integration's `astro:routes:resolved` hook. */\n routes: RoutesState;\n}\n\n/**\n * The content read-side endpoints (core-reuse §3):\n *\n * - `GET /__astroix/collections` — collections + entries through core's own\n * `astro:content` module: parsed `data`, `body`, `filePath` per entry, plus\n * schema presence from the content config. **Stateless doctrine**: a fresh\n * module runner per request, no module held between requests — core clears\n * its caches on invalidation, so anything we cache would go stale.\n * - `GET /__astroix/routes` — the routes array captured from\n * `astro:routes:resolved` (re-runs on route changes via dev restarts).\n *\n * Raw entry bytes go through the existing root-confined `GET /__astroix/file`.\n */\nexport function registerContentEndpoints(server: ViteDevServer, options: ContentRestOptions): void {\n server.middlewares.use(API_PREFIX, (req, res, next) => {\n void handleContentRequest(req, res, next, server, options);\n });\n}\n\nasync function handleContentRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void,\n server: ViteDevServer,\n options: ContentRestOptions,\n): Promise<void> {\n try {\n if (isCrossOriginTraffic(req)) {\n json(res, 403, { error: 'cross-origin builder traffic is not allowed' });\n return;\n }\n\n const url = new URL(req.url ?? '/', 'http://astroix.internal');\n\n if (req.method === 'GET' && url.pathname === '/collections') {\n const runner = createServerModuleRunner(server.environments.ssr);\n const configPath = findContentConfigPath(options.srcDir);\n const configModule =\n configPath === null ? null : ((await runner.import(configPath)) as RawContentConfig);\n const contentModule = (await runner.import('astro:content')) as RawContentModule;\n json(res, 200, await assembleCollectionsPayload(configModule, contentModule));\n return;\n }\n\n if (req.method === 'GET' && url.pathname === '/routes') {\n json(res, 200, options.routes.current);\n return;\n }\n\n next();\n } catch (error) {\n next(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/** The user's `content.config` module as the runner evaluates it. */\nexport interface RawContentConfig {\n collections?: unknown;\n}\n\n/** `astro:content` as the runner evaluates it — only the surface this module consumes. */\nexport interface RawContentModule {\n getCollection?: (name: string) => Promise<unknown[]>;\n}\n\n/**\n * Joins the config's collection definitions (names, schema presence) with\n * core's `getCollection` results. Deterministic: collections and entries are\n * name/id-sorted regardless of store iteration order.\n */\nexport async function assembleCollectionsPayload(\n configModule: RawContentConfig | null,\n contentModule: RawContentModule,\n): Promise<CollectionRecord[]> {\n const definitions = toDefinitionMap(configModule?.collections);\n const collections: CollectionRecord[] = [];\n for (const name of Object.keys(definitions).sort()) {\n const entries = await loadEntries(contentModule, name);\n collections.push({ name, hasSchema: definitions[name]?.schema !== undefined, entries });\n }\n return collections;\n}\n\n/** `Record<string, { schema?: unknown }>` or an empty record — never throws on a malformed config. */\nfunction toDefinitionMap(collections: unknown): Record<string, { schema?: unknown }> {\n if (typeof collections !== 'object' || collections === null) return {};\n const definitions: Record<string, { schema?: unknown }> = {};\n for (const [name, definition] of Object.entries(collections)) {\n if (typeof definition === 'object' && definition !== null) {\n definitions[name] = definition as { schema?: unknown };\n }\n }\n return definitions;\n}\n\nasync function loadEntries(\n contentModule: RawContentModule,\n name: string,\n): Promise<CollectionEntryRecord[]> {\n const raw = (await contentModule.getCollection?.(name)) ?? [];\n return (\n raw\n .filter(\n (entry): entry is { id: string; filePath?: unknown; data?: unknown; body?: unknown } =>\n typeof entry === 'object' &&\n entry !== null &&\n typeof (entry as { id?: unknown }).id === 'string',\n )\n .map((entry) => ({\n id: entry.id,\n filePath: typeof entry.filePath === 'string' ? entry.filePath : null,\n data: entry.data ?? null,\n body: typeof entry.body === 'string' ? entry.body : null,\n }))\n // Code-unit order, like the collection-name sort above — localeCompare\n // follows process collation, which can order ids per machine.\n .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n );\n}\n\n/**\n * The content config path, mirroring core's search order\n * (`src/content.config.{mjs,js,mts,ts}`, then the legacy `src/content/config.*`).\n */\nexport function findContentConfigPath(srcDir: string): string | null {\n const candidates = [\n ['content.config.mjs', 'content.config.js', 'content.config.mts', 'content.config.ts'].map(\n (name) => join(srcDir, name),\n ),\n ['config.ts', 'config.js', 'config.mjs', 'config.mts'].map((name) =>\n join(srcDir, 'content', name),\n ),\n ].flat();\n return candidates.find((candidate) => existsSync(candidate)) ?? null;\n}\n","import { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { join, relative, resolve, sep } from 'node:path';\nimport postcss from 'postcss';\nimport type { ViteDevServer } from 'vite';\nimport { buildCssIndex, type SourceFile } from '../core/indexer';\nimport type { IndexPayloadRecord } from '../core/matcher';\nimport { SpliceRangeError, spliceText } from '../core/splice-writer';\n\nexport const API_PREFIX = '/__astroix';\nconst MAX_BODY_BYTES = 1_000_000;\n\nexport interface RestOptions {\n /** Absolute project root (Vite root). */\n root: string;\n /** Absolute Astro src dir holding the css/astro sources to index. */\n srcDir: string;\n}\n\n/**\n * Registers the chrome↔node contract on the Vite connect middleware\n * (core-reuse §2 — like core's `/_astro/status`, not Astro app middleware):\n *\n * - `GET /__astroix/index` — the index payload: edit-truth records joined\n * with compiled scoped forms from the client module graph.\n * - `POST /__astroix/edit` — `{ file, range, replacement }` spliced to disk.\n *\n * Same-origin only: a browser `sec-fetch-site` header that is not\n * same-origin/none is rejected (T2).\n */\nexport function registerRestEndpoints(server: ViteDevServer, options: RestOptions): void {\n server.middlewares.use(API_PREFIX, (req, res, next) => {\n void handleApiRequest(req, res, next, server, options);\n });\n}\n\nasync function handleApiRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void,\n server: ViteDevServer,\n options: RestOptions,\n): Promise<void> {\n try {\n if (isCrossOriginTraffic(req)) {\n json(res, 403, { error: 'cross-origin builder traffic is not allowed' });\n return;\n }\n\n const url = new URL(req.url ?? '/', 'http://astroix.internal');\n\n // The middleware is mounted at /__astroix (connect strips the prefix),\n // so the GET path arrives as /index.\n if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/index')) {\n const payload = await buildIndexPayload(collectSources(options.srcDir), (file, blockIndex) =>\n resolveCompiledCss(server, options.root, file, blockIndex),\n );\n // Payload paths are project-relative; the join worked in absolute space.\n json(\n res,\n 200,\n payload.map((record) => ({ ...record, file: toRelative(options.root, record.file) })),\n );\n return;\n }\n\n // File content for the editor pane — a dedicated endpoint (not payload\n // fields) so contents are fresh exactly when a rule is opened and the\n // payload stays small. Same root confinement as the edit endpoint.\n if (req.method === 'GET' && url.pathname === '/file') {\n const file = url.searchParams.get('file');\n const absPath = file === null ? null : safeResolve(options.root, file);\n if (file === null || absPath === null || !existsSync(absPath)) {\n json(res, 400, { error: `file is missing or outside the project root: ${file ?? ''}` });\n return;\n }\n json(res, 200, { file, contents: readFileSync(absPath, 'utf8') });\n return;\n }\n\n if (req.method === 'POST' && url.pathname === '/edit') {\n const body = await readJsonBody(req);\n const { file, range, replacement, expected } = parseEditBody(body);\n if (file === null || range === null || replacement === null) {\n json(res, 400, { error: 'expected { file, range: { start, end }, replacement }' });\n return;\n }\n const absPath = safeResolve(options.root, file);\n if (absPath === null) {\n json(res, 400, { error: `file is outside the project root: ${file}` });\n return;\n }\n const contents = readFileSync(absPath, 'utf8');\n // Optimistic write check: the chrome sends the hash of the content it\n // based its edit on. A mismatch means the file changed on disk under us\n // (IDE edit racing the debounce) — refuse instead of splicing stale\n // offsets into a shifted file, and hand back the current contents so\n // the editor can reload in one roundtrip.\n if (expected !== null && sha256(contents) !== expected) {\n json(res, 409, { error: 'file changed on disk', contents });\n return;\n }\n try {\n writeFileSync(\n absPath,\n spliceText(contents, { start: range[0], end: range[1], replacement }),\n );\n } catch (error) {\n if (error instanceof SpliceRangeError) {\n json(res, 400, { error: error.message });\n return;\n }\n throw error;\n }\n json(res, 200, { ok: true });\n return;\n }\n\n next();\n } catch (error) {\n next(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/** Supplies the compiled css of a scoped style module, or null when absent. */\nexport type CompiledCssResolver = (file: string, styleBlockIndex: number) => Promise<string | null>;\n\n/**\n * The module-graph hybrid join: static records plus effective selectors for\n * scoped rules. Scoped records of one style block correlate with the compiled\n * rules of module `{file}.astro?astro&type=style&index={N}` in rule order; a\n * block with no compiled module (not loaded on the current route, or a rule\n * count mismatch) stays listed without an effective selector — the liveness\n * line of v1.\n */\nexport async function buildIndexPayload(\n sources: SourceFile[],\n resolveCompiledCss: CompiledCssResolver,\n): Promise<IndexPayloadRecord[]> {\n const payload: IndexPayloadRecord[] = buildCssIndex(sources).map((record) => ({\n ...record,\n effectiveSelector: null,\n }));\n\n const blocks = new Map<string, { file: string; styleBlockIndex: number; positions: number[] }>();\n payload.forEach((record, position) => {\n if (!record.scoped || record.styleBlockIndex === null) return;\n const key = `${record.file}\\u0000${record.styleBlockIndex}`;\n const block = blocks.get(key) ?? {\n file: record.file,\n styleBlockIndex: record.styleBlockIndex,\n positions: [],\n };\n block.positions.push(position);\n blocks.set(key, block);\n });\n\n for (const block of blocks.values()) {\n const css = await resolveCompiledCss(block.file, block.styleBlockIndex);\n if (css === null) continue;\n const selectors = compiledSelectors(css);\n block.positions.forEach((position, ruleOrder) => {\n const effectiveSelector = selectors[ruleOrder];\n const record = payload[position];\n if (effectiveSelector !== undefined && record !== undefined) {\n record.effectiveSelector = effectiveSelector;\n }\n });\n }\n return payload;\n}\n\n/** Selectors of the compiled css in rule order — the join's correlation key. */\nexport function compiledSelectors(css: string): string[] {\n const selectors: string[] = [];\n postcss.parse(css).walkRules((rule) => {\n selectors.push(rule.selector);\n });\n return selectors;\n}\n\n/** Pulls the css text out of a dev-transformed css module's code. */\nexport function extractCssFromModuleCode(code: string): string | null {\n const match = code.match(/__vite__css = (\"(?:[^\"\\\\]|\\\\.)*\")/);\n if (match?.[1] === undefined) return null;\n try {\n return JSON.parse(match[1]) as string;\n } catch {\n return null;\n }\n}\n\nasync function resolveCompiledCss(\n server: ViteDevServer,\n root: string,\n file: string,\n styleBlockIndex: number,\n): Promise<string | null> {\n const moduleUrl = `/${toRelative(root, file)}?astro&type=style&index=${styleBlockIndex}&lang.css`;\n const module = await server.environments.client.moduleGraph.getModuleByUrl(moduleUrl);\n const code = module?.transformResult?.code;\n if (code === undefined || code === null) return null;\n return extractCssFromModuleCode(code);\n}\n\n/** Walks `src/**` collecting the css/astro sources the indexer consumes. */\nexport function collectSources(srcDir: string): SourceFile[] {\n if (!existsSync(srcDir)) return [];\n const sources: SourceFile[] = [];\n const walk = (dir: string): void => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.name.endsWith('.css') || entry.name.endsWith('.astro')) {\n sources.push({ file: full, contents: readFileSync(full, 'utf8') });\n }\n }\n };\n walk(srcDir);\n return sources;\n}\n\n/** Project-relative posix path — also the file id used by the sync events. */\nexport function toRelative(root: string, file: string): string {\n return relative(root, file).split(sep).join('/');\n}\n\nfunction safeResolve(root: string, file: string): string | null {\n const absPath = resolve(root, file);\n if (absPath !== root && !absPath.startsWith(`${root}${sep}`)) return null;\n return absPath;\n}\n\nfunction parseEditBody(body: unknown): {\n file: string | null;\n range: [number, number] | null;\n replacement: string | null;\n expected: string | null;\n} {\n if (body === null || typeof body !== 'object') {\n return { file: null, range: null, replacement: null, expected: null };\n }\n const { file, range, replacement, expected } = body as Record<string, unknown>;\n const validRange =\n typeof range === 'object' &&\n range !== null &&\n typeof (range as Record<string, unknown>).start === 'number' &&\n typeof (range as Record<string, unknown>).end === 'number'\n ? ([(range as Record<string, unknown>).start, (range as Record<string, unknown>).end] as [\n number,\n number,\n ])\n : null;\n return {\n file: typeof file === 'string' ? file : null,\n range: validRange,\n replacement: typeof replacement === 'string' ? replacement : null,\n expected: typeof expected === 'string' ? expected : null,\n };\n}\n\nfunction sha256(text: string): string {\n return createHash('sha256').update(text).digest('hex');\n}\n\n/**\n * The builder endpoints serve same-origin chrome traffic only: a browser\n * `sec-fetch-site` header that is not same-origin/none means cross-origin\n * (T2). Shared by every `/__astroix` middleware.\n */\nexport function isCrossOriginTraffic(req: IncomingMessage): boolean {\n const secFetchSite = req.headers['sec-fetch-site'];\n return (\n typeof secFetchSite === 'string' && secFetchSite !== 'same-origin' && secFetchSite !== 'none'\n );\n}\n\nexport function json(res: ServerResponse, status: number, body: unknown): void {\n res.statusCode = status;\n res.setHeader('content-type', 'application/json; charset=utf-8');\n res.end(JSON.stringify(body));\n}\n\nfunction readJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolveBody, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n reject(new Error('request body too large'));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on('end', () => {\n try {\n resolveBody(JSON.parse(Buffer.concat(chunks).toString('utf8')));\n } catch {\n reject(new Error('request body is not valid JSON'));\n }\n });\n req.on('error', reject);\n });\n}\n","import { extractStylesSync } from '@astrojs/compiler-binding';\nimport postcss from 'postcss';\n\n/** A project CSS source to index: path + raw contents. No IO happens here. */\nexport interface SourceFile {\n file: string;\n contents: string;\n}\n\n/**\n * One rule from the edit-truth index. The range is in character offsets of\n * `file` and covers the rule from its selector through the closing brace\n * (end-exclusive) — the splice-writer edits inside these bounds.\n */\nexport interface CssRuleRecord {\n /** Selector text verbatim from source (source space — no cid synthesis here). */\n selector: string;\n file: string;\n range: { start: number; end: number };\n /**\n * One-based line of the rule's selector in `file`, derived from the range\n * at index time (the indexer holds the contents) — the rule list shows it.\n */\n line: number;\n /** Condition of the nearest `@media` ancestor, or null at the top level. */\n media: string | null;\n /** True for rules from a scoped `<style>` block (the compiler applies the cid). */\n scoped: boolean;\n /**\n * Zero-based style-block index correlating with the module-graph module id\n * `{file}.astro?astro&type=style&index={N}` — the join key for the index\n * payload. Null when the block is not in the module graph (`is:inline`).\n */\n styleBlockIndex: number | null;\n}\n\ninterface BlockMeta {\n scoped: boolean;\n styleBlockIndex: number | null;\n baseOffset: number;\n}\n\nconst STYLE_TAG = /<style\\b[^>]*>([\\s\\S]*?)<\\/style>/g;\n\n/**\n * The indexer: scans project CSS sources into the edit-truth index\n * (selector → file, source range, media condition). Dev generates no CSS\n * sourcemaps, so this static scan is the only mapping to what's on disk —\n * and the only one that sees `is:inline` blocks.\n */\nexport function buildCssIndex(sources: SourceFile[]): CssRuleRecord[] {\n const records: CssRuleRecord[] = [];\n for (const source of sources) {\n const fileRecords = source.file.endsWith('.css')\n ? indexStylesheet(source.file, source.contents, {\n scoped: false,\n styleBlockIndex: null,\n baseOffset: 0,\n })\n : source.file.endsWith('.astro')\n ? indexAstroStyles(source.file, source.contents)\n : [];\n // Lines derive from absolute offsets against the whole file — the .astro\n // blocks were parsed as substrings but carry absolute ranges.\n for (const record of fileRecords) {\n records.push({ ...record, line: lineAt(source.contents, record.range.start) });\n }\n }\n return records;\n}\n\n/** One-based line number of a character offset. */\nfunction lineAt(contents: string, offset: number): number {\n let line = 1;\n for (let i = 0; i < offset && i < contents.length; i++) {\n if (contents[i] === '\\n') line += 1;\n }\n return line;\n}\n\nfunction indexStylesheet(\n file: string,\n css: string,\n meta: BlockMeta,\n): Omit<CssRuleRecord, 'line'>[] {\n const records: Omit<CssRuleRecord, 'line'>[] = [];\n postcss.parse(css).walkRules((rule) => {\n const start = rule.source?.start;\n const end = rule.source?.end;\n if (start === undefined || end === undefined) return;\n records.push({\n selector: rule.selector,\n file,\n range: { start: start.offset + meta.baseOffset, end: end.offset + meta.baseOffset },\n media: nearestMediaCondition(rule),\n scoped: meta.scoped,\n styleBlockIndex: meta.styleBlockIndex,\n });\n });\n return records;\n}\n\nfunction indexAstroStyles(file: string, source: string): Omit<CssRuleRecord, 'line'>[] {\n const records: Omit<CssRuleRecord, 'line'>[] = [];\n // extractStylesSync returns only blocks the compiler would process —\n // `is:inline` (and expression-attribute blocks) never make it there, so the\n // raw tag scan is the edit-truth pass and the compiler blocks supply the\n // module-graph index.\n const processed = extractStylesSync(source);\n let next = 0;\n\n for (const match of source.matchAll(STYLE_TAG)) {\n const content = match[1];\n if (content === undefined) continue;\n const openTag = match[0].slice(0, match[0].indexOf('>') + 1);\n const contentStart = match.index + openTag.length;\n\n const compilerBlock = processed[next];\n if (compilerBlock !== undefined && compilerBlock.content === content) {\n next += 1;\n records.push(\n ...indexStylesheet(file, content, {\n scoped: compilerBlock.attrs['is:global'] === undefined,\n styleBlockIndex: compilerBlock.index,\n baseOffset: contentStart,\n }),\n );\n } else {\n records.push(\n ...indexStylesheet(file, content, {\n scoped: false,\n styleBlockIndex: null,\n baseOffset: contentStart,\n }),\n );\n }\n }\n return records;\n}\n\nfunction nearestMediaCondition(node: postcss.Node): string | null {\n for (let parent = node.parent; parent !== undefined; parent = parent.parent) {\n if (parent.type !== 'atrule') continue;\n const atRule = parent as postcss.AtRule;\n if (atRule.name === 'media') {\n return atRule.params;\n }\n }\n return null;\n}\n","/**\n * A single text edit: replace the half-open range `[start, end)` of the file\n * content with `replacement`. Zero-length ranges insert purely. The editor\n * debounces and sends one edit at a time — this is the only write primitive.\n */\nexport interface SpliceEdit {\n start: number;\n end: number;\n replacement: string;\n}\n\n/** Thrown for ranges that do not fit the content — never produces partial output. */\nexport class SpliceRangeError extends Error {\n constructor(start: number, end: number, contentLength: number) {\n super(`Invalid splice range [${start}, ${end}) for content of length ${contentLength}`);\n this.name = 'SpliceRangeError';\n }\n}\n\n/**\n * The splice-writer primitive: (content, range, replacement) → new content.\n * Text-splice only — never reprints the file. Every byte outside the replaced\n * range stays identical, so formatting, comments and agent conventions\n * survive and the git diff is minimal.\n */\nexport function spliceText(content: string, edit: SpliceEdit): string {\n const { start, end, replacement } = edit;\n if (\n !Number.isInteger(start) ||\n !Number.isInteger(end) ||\n start < 0 ||\n end > content.length ||\n start > end\n ) {\n throw new SpliceRangeError(start, end, content.length);\n }\n return content.slice(0, start) + replacement + content.slice(end);\n}\n\n/**\n * Append a rule at EOF with exactly one added line, regardless of whether the\n * original ends with a newline (no accidental blank runs; the file's\n * trailing-newline convention is preserved).\n */\nexport function appendRule(content: string, rule: string): string {\n if (content === '') {\n return rule;\n }\n const endsWithNewline = content.endsWith('\\n');\n const body = endsWithNewline ? content : `${content}\\n`;\n return `${body}${rule}${endsWithNewline ? '\\n' : ''}`;\n}\n","import { existsSync } from 'node:fs';\nimport { fileURLToPath, URL as NodeURL } from 'node:url';\n\n/**\n * Dev-checkout detection for the ADR-0001 mode switch: the chrome client\n * sources exist next to the integration only when the package runs from this\n * repo (an installed package ships no `src/`). Two candidate depths because\n * this module executes from `src/node/` during development and from the\n * bundled `dist/index.js` at runtime. `URL` comes from `node:url` because\n * happy-dom (unit tests) patches the global `URL` with its own class, which\n * `fileURLToPath` rejects.\n */\nconst candidates = [\n fileURLToPath(new NodeURL('../../src/client/entry.tsx', import.meta.url)),\n fileURLToPath(new NodeURL('../src/client/entry.tsx', import.meta.url)),\n];\n\nexport const clientEntryPath: string | null = candidates.find((path) => existsSync(path)) ?? null;\n\n/**\n * The prebuilt chrome bundle (ADR-0001): a self-contained ESM shipped inside\n * `dist/`. Served by the virtual chrome module when the dev-checkout sources\n * are absent — the consumer-facing delivery mode.\n */\nexport const chromeArtifactPath: string = fileURLToPath(\n new NodeURL('./chrome.js', import.meta.url),\n);\n\nexport function isSourceMode(): boolean {\n return clientEntryPath !== null;\n}\n","import type { AstroUserConfig } from 'astro';\n\n/**\n * Guard: skip our Tailwind plugin when the host already registered one.\n * The `@tailwindcss/vite` factory returns an array whose plugin names start\n * with `@tailwindcss/vite:` (verified on 4.x) — that prefix is the check.\n */\nexport function hostRegistersTailwind(viteConfig: AstroUserConfig['vite']): boolean {\n const seen = new Set<string>();\n const walk = (input: unknown): void => {\n if (Array.isArray(input)) {\n for (const entry of input) walk(entry);\n return;\n }\n if (!input || typeof input !== 'object') return;\n const plugin = input as { name?: unknown };\n if (typeof plugin.name === 'string') seen.add(plugin.name);\n };\n walk(viteConfig?.plugins);\n for (const name of seen) {\n if (name.startsWith('@tailwindcss/vite')) return true;\n }\n return false;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Plugin, ViteDevServer } from 'vite';\nimport { chromeHtml } from './chrome-html';\nimport type { RoutesState } from './content';\nimport { registerContentEndpoints } from './content';\nimport { isDocumentRequest } from './document-request';\nimport { registerRestEndpoints } from './rest';\nimport { chromeArtifactPath, clientEntryPath } from './source-mode';\nimport { registerFileSync } from './watch-sync';\n\nexport const VIRTUAL_CHROME_ID = 'virtual:astroix/chrome';\n\nexport interface AstroixPluginOptions {\n /** Absolute Astro src dir with the sources to index; defaults to `<root>/src`. */\n srcDir?: string;\n /** Routes captured by the integration's `astro:routes:resolved` hook, served at `/__astroix/routes`. */\n routes: RoutesState;\n}\n\n/**\n * The astroix Vite plugin: default-on chrome over every top-level dev URL.\n * The middleware is registered in the body of `configureServer` (pre-internal)\n * because Astro's dev handler lives in a post-hook and never calls `next()` —\n * this is the only position that sees every request (core-reuse §1). The\n * chrome HTML passes through `server.transformIndexHtml` (the plugin hook\n * never fires for Astro pages, the server API does) which injects the Vite\n * client and the plugin-react preamble.\n */\nexport function astroixVitePlugin(options: AstroixPluginOptions): Plugin {\n return {\n name: 'astroix',\n configureServer(server: ViteDevServer) {\n server.middlewares.use((req, res, next) => {\n void (async () => {\n try {\n const url = req.url ?? '/';\n if (\n !isDocumentRequest({ method: req.method ?? 'GET', url, accept: req.headers.accept })\n ) {\n next();\n return;\n }\n const html = await server.transformIndexHtml(url, chromeHtml());\n res.statusCode = 200;\n res.setHeader('content-type', 'text/html; charset=utf-8');\n res.end(html);\n } catch (error) {\n next(error instanceof Error ? error : new Error(String(error)));\n }\n })();\n });\n const srcDir = options.srcDir ?? join(server.config.root, 'src');\n registerRestEndpoints(server, { root: server.config.root, srcDir });\n registerContentEndpoints(server, { srcDir, routes: options.routes });\n registerFileSync(server, { root: server.config.root, srcDir });\n },\n resolveId(id) {\n // The HTML references `/virtual:astroix/chrome`; imports may use the bare id.\n if (id === VIRTUAL_CHROME_ID || id === `/${VIRTUAL_CHROME_ID}`) {\n return VIRTUAL_CHROME_ID;\n }\n return null;\n },\n load(id) {\n if (id !== VIRTUAL_CHROME_ID) return null;\n if (clientEntryPath !== null) {\n // ADR-0001 source mode: the chrome loads from this checkout's source,\n // so the host dev server transforms it (fast-refresh, Tailwind).\n return `import { mountChrome } from '/@fs${clientEntryPath}';\\nmountChrome();\\n`;\n }\n // ADR-0001 prebuilt mode: serve the shipped bundle — a self-contained\n // ESM with react, the compiled CSS and CodeMirror inside, so foreign\n // hosts resolve none of our chrome dependencies. Missing artifact =\n // broken package build; fail loudly, never silently (ADR-0001).\n if (!existsSync(chromeArtifactPath)) {\n throw new Error(\n 'astroix: prebuilt chrome bundle is missing from the package build (expected dist/chrome.js)',\n );\n }\n return readFileSync(chromeArtifactPath, 'utf8');\n },\n };\n}\n","/**\n * The chrome document shell: a mount point and the virtual-module reference.\n * Layout lives inside the shadow root (React app); the document only resets\n * geometry so the shadow host can fill the viewport.\n */\nexport function chromeHtml(): string {\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>astroix builder</title>\n <style>\n html, body { margin: 0; height: 100%; }\n #astroix-root { display: block; height: 100vh; }\n </style>\n </head>\n <body>\n <div id=\"astroix-root\"></div>\n <script type=\"module\" src=\"/virtual:astroix/chrome\"></script>\n </body>\n</html>`;\n}\n","/**\n * Is this request a top-level document navigation the builder should wrap?\n * Deliberately conservative: anything asset-like, internal to Vite/Astro, or\n * carrying an explicit `builder` param falls through to the host.\n */\nexport function isDocumentRequest(input: {\n method: string;\n url: string;\n accept?: string | undefined;\n}): boolean {\n if (input.method !== 'GET' && input.method !== 'HEAD') return false;\n if (!(input.accept ?? '').includes('text/html')) return false;\n\n let url: URL;\n try {\n url = new URL(input.url, 'http://astroix.internal');\n } catch {\n return false;\n }\n if (url.searchParams.has('builder')) return false;\n\n const { pathname } = url;\n if (\n pathname.startsWith('/@') ||\n pathname.startsWith('/__') ||\n pathname.startsWith('/_astro') ||\n pathname.startsWith('/virtual:')\n ) {\n return false;\n }\n // A dot in the last path segment reads as an asset (`home.css`, `foo.png`).\n if (/(^|\\/)[^/]*\\.[a-zA-Z0-9]+$/.test(pathname)) return false;\n return true;\n}\n","import { sep } from 'node:path';\nimport type { ViteDevServer } from 'vite';\nimport { toRelative } from './rest';\n\n/**\n * The file→chrome half of the sync (spec #13): the host watcher is the only\n * FS subscriber; css/astro changes under the project's src dir are debounced\n * per file and pushed to the chrome as `astroix:file-changed` over the Vite\n * WebSocket — the same channel Astro uses for its own events. The chrome\n * refetches content/payload on receipt; its own writes echo back as no-ops\n * (content compare client-side).\n */\nexport function registerFileSync(\n server: ViteDevServer,\n options: { root: string; srcDir: string },\n): void {\n // astro hands srcDir as a URL that keeps a trailing slash — strip it or\n // the startsWith filter below never matches\n const srcDir = options.srcDir.split(sep).join('/').replace(/\\/+$/, '');\n const pending = new Map<string, ReturnType<typeof setTimeout>>();\n\n const push = (file: string): void => {\n const timer = pending.get(file);\n if (timer !== undefined) clearTimeout(timer);\n pending.set(\n file,\n setTimeout(() => {\n pending.delete(file);\n server.ws.send('astroix:file-changed', { file: toRelative(options.root, file) });\n }, 100),\n );\n };\n\n const isWatchedSource = (file: string): boolean => {\n const norm = file.split(sep).join('/');\n if (!norm.startsWith(`${srcDir}/`)) return false;\n return norm.endsWith('.css') || norm.endsWith('.astro');\n };\n\n // `add` matters too: IDE atomic saves (write temp + rename) can surface as\n // add instead of change depending on the editor\n server.watcher.on('change', (file) => {\n if (isWatchedSource(file)) push(file);\n });\n server.watcher.on('add', (file) => {\n if (isWatchedSource(file)) push(file);\n });\n}\n"],"mappings":"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","names":["fileURLToPath","existsSync","join","postcss","resolveCompiledCss","postcss","candidates","join","existsSync","existsSync","existsSync","readFileSync","join","sep","sep","join","existsSync","readFileSync","fileURLToPath"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wojciechpiskorz/astroix",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Dev-only Astro 7 integration — a visual builder for Content Collections content and repo-mapped CSS",
5
5
  "type": "module",
6
6
  "license": "MIT",