Sync generated provider output

This commit is contained in:
github-actions[bot]
2026-06-09 17:32:23 +00:00
parent f24f9fca8b
commit c169b84f20
26 changed files with 975 additions and 338 deletions
@@ -22,6 +22,12 @@ import net from 'node:net';
import { fileURLToPath } from 'node:url';
import { parseDesignMd } from './lib/design-parser.mjs';
import { resolveContextDir } from './context.mjs';
import {
assembleLiveBrowserScript,
assertLiveBrowserScriptParts,
readLiveBrowserScriptParts,
resolveLiveBrowserScriptParts,
} from './live/browser-script-parts.mjs';
import { createLiveSessionStore } from './live/session-store.mjs';
import { validateEvent } from './live/event-validation.mjs';
import { createManualEditRoutes } from './live/manual-edit-routes.mjs';
@@ -347,19 +353,18 @@ function loadBrowserScripts() {
try { detectScript = fs.readFileSync(p, 'utf-8'); break; } catch { /* try next */ }
}
// live-browser.js: DO NOT cache. Return the path so the /live.js handler
// can re-read on every request. Editing the browser script during iteration
// should land on the next tab reload, not require a server restart.
const sessionPath = path.join(__dirname, 'live-browser-session.js');
const livePath = path.join(__dirname, 'live-browser.js');
for (const p of [sessionPath, livePath]) {
if (!fs.existsSync(p)) {
process.stderr.write('Error: live browser script not found at ' + p + '\n');
process.exit(1);
}
// Browser script parts: DO NOT cache. Return paths so the /live.js handler
// can re-read every part on each request. Editing browser code during
// iteration should land on the next tab reload, not require a server restart.
const liveScriptParts = resolveLiveBrowserScriptParts(__dirname);
try {
assertLiveBrowserScriptParts(liveScriptParts);
} catch (err) {
process.stderr.write('Error: ' + err.message + '\n');
process.exit(1);
}
return { detectScript, sessionPath, livePath };
return { detectScript, liveScriptParts };
}
function hasProjectContext() {
@@ -379,7 +384,7 @@ function statOrNull(filePath) {
// HTTP request handler
// ---------------------------------------------------------------------------
function createRequestHandler({ detectScript, sessionPath, livePath }) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
res.setHeader('Access-Control-Allow-Origin', '*');
@@ -395,24 +400,20 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) {
// the next tab reload. No-store headers prevent browser caching across
// sessions — during iteration, a cached old script silently breaks
// every subsequent session.
let sessionScript;
let liveScript;
let parts;
try {
sessionScript = fs.readFileSync(sessionPath, 'utf-8');
liveScript = fs.readFileSync(livePath, 'utf-8');
parts = readLiveBrowserScriptParts(liveScriptParts);
} catch (err) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Error reading live browser scripts: ' + err.message);
return;
}
const body =
`window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` +
`window.__IMPECCABLE_PORT__ = ${state.port};\n` +
// Canonical command vocabulary (values + labels + icons). live-browser.js
// builds its action picker from this instead of an inline copy.
`window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` +
sessionScript + '\n' +
liveScript;
const body = assembleLiveBrowserScript({
token: state.token,
port: state.port,
vocabulary: LIVE_COMMANDS,
parts,
});
res.writeHead(200, {
'Content-Type': 'application/javascript',
'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
@@ -1116,8 +1117,8 @@ const annotRoot = getLiveAnnotationsDir(process.cwd());
fs.mkdirSync(annotRoot, { recursive: true });
state.sessionDir = fs.mkdtempSync(path.join(annotRoot, 'session-'));
const { detectScript, sessionPath, livePath } = loadBrowserScripts();
httpServer = http.createServer(createRequestHandler({ detectScript, sessionPath, livePath }));
const { detectScript, liveScriptParts } = loadBrowserScripts();
httpServer = http.createServer(createRequestHandler({ detectScript, liveScriptParts }));
httpServer.listen(state.port, '127.0.0.1', () => {
writeLiveServerInfo(process.cwd(), { pid: process.pid, port: state.port, token: state.token });
@@ -0,0 +1,48 @@
import fs from 'node:fs';
import path from 'node:path';
export const LIVE_BROWSER_SCRIPT_PARTS = Object.freeze([
Object.freeze({ name: 'session-state', file: 'live-browser-session.js' }),
Object.freeze({ name: 'browser-ui', file: 'live-browser.js' }),
]);
export function resolveLiveBrowserScriptParts(scriptsDir, parts = LIVE_BROWSER_SCRIPT_PARTS) {
if (!scriptsDir) throw new Error('scriptsDir is required');
return parts.map((part, index) => ({
...part,
index,
path: path.join(scriptsDir, part.file),
}));
}
export function assertLiveBrowserScriptParts(parts, exists = fs.existsSync) {
for (const part of parts) {
if (!exists(part.path)) {
throw new Error(`Live browser script part missing: ${part.name} (${part.path})`);
}
}
return parts;
}
export function readLiveBrowserScriptParts(parts, readFile = (filePath) => fs.readFileSync(filePath, 'utf-8')) {
return parts.map((part) => ({
...part,
source: readFile(part.path),
}));
}
export function assembleLiveBrowserScript({ token, port, vocabulary, parts }) {
const prelude =
`window.__IMPECCABLE_TOKEN__ = '${token}';\n` +
`window.__IMPECCABLE_PORT__ = ${port};\n` +
// Canonical command vocabulary (values + labels + icons). live-browser.js
// builds its action picker from this instead of an inline copy.
`window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(vocabulary)};\n`;
const body = parts.map((part) => {
const file = part.file || path.basename(part.path || '');
return `// --- impeccable live script part: ${part.name} (${file}) ---\n${part.source}`;
}).join('\n');
return prelude + body;
}