mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-16 08:06:24 +03:00
Refresh the Impeccable product experience
Rework the landing page proof, steering demo, feature grid, slop catalog, detector coverage, theming, Live workflow, and responsive behavior.\n\nAI-assisted implementation by OpenAI Codex.
This commit is contained in:
+20
-92
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import { execFileSync, spawn } from 'node:child_process';
|
||||
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -32,7 +32,8 @@ export { SCRIPTS_DIR, FIXTURES_DIR, REPO_ROOT };
|
||||
// Stage
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function stageFixture(name, fixture, { fixtureRoot = join(FIXTURES_DIR, name) } = {}) {
|
||||
export function stageFixture(name, fixture) {
|
||||
const fixtureRoot = join(FIXTURES_DIR, name);
|
||||
const gitignore = readFileSync(join(fixtureRoot, 'gitignore.txt'), 'utf-8');
|
||||
|
||||
const tmp = mkdtempSync(join(tmpdir(), 'impeccable-e2e-'));
|
||||
@@ -55,7 +56,6 @@ export function runInstall(tmp, command, { timeoutMs = readTimeoutEnv('IMPECCABL
|
||||
const installArgs = addNpmInstallDefaults(cmd, args);
|
||||
try {
|
||||
execFileSync(cmd, installArgs, { cwd: tmp, stdio: 'inherit', timeout: timeoutMs });
|
||||
repairMissingRollupOptionalBinary(tmp, { timeoutMs });
|
||||
} catch (err) {
|
||||
if (err.signal === 'SIGTERM' || err.signal === 'SIGKILL' || err.killed) {
|
||||
err.message = `fixture dependency install timed out after ${timeoutMs}ms: ${cmd} ${installArgs.join(' ')}`;
|
||||
@@ -64,26 +64,11 @@ export function runInstall(tmp, command, { timeoutMs = readTimeoutEnv('IMPECCABL
|
||||
}
|
||||
}
|
||||
|
||||
function repairMissingRollupOptionalBinary(tmp, { timeoutMs }) {
|
||||
if (process.platform !== 'darwin' || process.arch !== 'arm64') return;
|
||||
const rollupPackage = join(tmp, 'node_modules', 'rollup', 'package.json');
|
||||
const nativePackage = join(tmp, 'node_modules', '@rollup', 'rollup-darwin-arm64', 'package.json');
|
||||
if (!existsSync(rollupPackage) || existsSync(nativePackage)) return;
|
||||
const version = JSON.parse(readFileSync(rollupPackage, 'utf-8')).version;
|
||||
execFileSync('npm', [
|
||||
'install', '--no-save', '--no-audit', '--no-fund', '--no-progress',
|
||||
`@rollup/rollup-darwin-arm64@${version}`,
|
||||
], { cwd: tmp, stdio: 'inherit', timeout: timeoutMs });
|
||||
}
|
||||
|
||||
function addNpmInstallDefaults(cmd, args) {
|
||||
if (cmd !== 'npm') return args;
|
||||
if (!['install', 'ci'].includes(args[0])) return args;
|
||||
const out = [...args];
|
||||
// npm can omit platform-specific Rollup binaries unless optional
|
||||
// dependencies are requested explicitly (npm/cli#4828). Astro/Vite then
|
||||
// fail before Live starts on fresh staged fixtures.
|
||||
for (const flag of ['--no-progress', '--include=optional']) {
|
||||
for (const flag of ['--prefer-offline', '--no-progress']) {
|
||||
if (!out.some((arg) => arg === flag || arg.startsWith(flag + '='))) out.push(flag);
|
||||
}
|
||||
return out;
|
||||
@@ -215,57 +200,29 @@ export async function stopDevServer(child) {
|
||||
* @param {object} opts
|
||||
* @param {string} opts.name fixture name
|
||||
* @param {object} opts.fixture fixture.json contents
|
||||
* @param {string=} opts.fixtureRoot fixture directory; defaults to the public framework fixture tree
|
||||
* @param {import('playwright').Browser} opts.browser shared browser instance
|
||||
* @param {object} opts.agent VariantAgent (defaults to fake)
|
||||
* @param {object|function=} opts.wrapTarget live-wrap target or event mapper
|
||||
* @param {(context: object) => Promise<object|void>} [opts.startWorker]
|
||||
* Optional production worker factory. Return {stop, done}; when used,
|
||||
* omit `agent` so the deterministic in-process loop is not started.
|
||||
* @param {(context: object) => Promise<void>|void} [opts.prepareTmp]
|
||||
* @param {(msg: string) => void} [opts.log]
|
||||
*/
|
||||
export async function bootFixtureSession({
|
||||
name,
|
||||
fixture,
|
||||
fixtureRoot,
|
||||
browser,
|
||||
agent,
|
||||
wrapTarget,
|
||||
startWorker,
|
||||
prepareTmp,
|
||||
log = () => {},
|
||||
trace = () => {},
|
||||
progressive = false,
|
||||
progressiveDelayMs = 0,
|
||||
progressiveInitialCount = 1,
|
||||
atomicDelayMs = 0,
|
||||
keepTmp = false,
|
||||
}) {
|
||||
export async function bootFixtureSession({ name, fixture, browser, agent, wrapTarget, log = () => {} }) {
|
||||
const runtime = fixture.runtime;
|
||||
if (!runtime) throw new Error(`fixture ${name} has no runtime block`);
|
||||
|
||||
const tmp = stageFixture(name, fixture, { fixtureRoot });
|
||||
const tmp = stageFixture(name, fixture);
|
||||
let live;
|
||||
let dev;
|
||||
let agentAbort;
|
||||
let agentDone;
|
||||
let externalWorker;
|
||||
let ctx;
|
||||
|
||||
const teardown = async () => {
|
||||
try { if (ctx) await ctx.close(); } catch {}
|
||||
try { if (agentAbort) agentAbort.abort(); } catch {}
|
||||
try { if (agentDone) await agentDone.catch(() => {}); } catch {}
|
||||
try { if (externalWorker?.stop) await externalWorker.stop(); } catch {}
|
||||
try { if (externalWorker?.done) await externalWorker.done.catch(() => {}); } catch {}
|
||||
try { if (dev?.child) await stopDevServer(dev.child); } catch {}
|
||||
try { if (live) stopLiveServer(tmp); } catch {}
|
||||
if (!keepTmp) {
|
||||
try { rmSync(tmp, { recursive: true, force: true }); } catch {}
|
||||
} else {
|
||||
log(`kept staged fixture at ${tmp}`);
|
||||
}
|
||||
try { rmSync(tmp, { recursive: true, force: true }); } catch {}
|
||||
};
|
||||
|
||||
const stopLiveForDeferredWork = () => {
|
||||
@@ -276,67 +233,41 @@ export async function bootFixtureSession({
|
||||
|
||||
try {
|
||||
const startedAt = Date.now();
|
||||
if (prepareTmp) await prepareTmp({ tmp, fixture, scriptsDir: SCRIPTS_DIR, trace, log });
|
||||
trace('setup.install.start', { fixture: name });
|
||||
log(`installing deps`);
|
||||
runInstall(tmp, runtime.install);
|
||||
trace('setup.install.end', { fixture: name });
|
||||
log(`deps installed in ${formatDuration(Date.now() - startedAt)}`);
|
||||
|
||||
const liveStartedAt = Date.now();
|
||||
trace('setup.live_server.start', { fixture: name });
|
||||
log(`starting live-server`);
|
||||
live = startLiveServer(tmp);
|
||||
trace('setup.live_server.end', { fixture: name, port: live.port });
|
||||
log(`live-server ready in ${formatDuration(Date.now() - liveStartedAt)}`);
|
||||
|
||||
if (startWorker) {
|
||||
trace('setup.worker.start', { fixture: name });
|
||||
externalWorker = await startWorker({ tmp, fixture, scriptsDir: SCRIPTS_DIR, live, trace, log });
|
||||
trace('setup.worker.end', { fixture: name });
|
||||
}
|
||||
|
||||
const injectStartedAt = Date.now();
|
||||
trace('setup.inject.start', { fixture: name });
|
||||
log(`live-inject --port ${live.port}`);
|
||||
const injectResult = runInject(tmp, live.port);
|
||||
if (!injectResult.ok) throw new Error('live-inject failed: ' + JSON.stringify(injectResult));
|
||||
trace('setup.inject.end', { fixture: name, files: injectResult.files || injectResult.pageFiles || [] });
|
||||
log(`live-inject complete in ${formatDuration(Date.now() - injectStartedAt)}`);
|
||||
|
||||
const devStartedAt = Date.now();
|
||||
trace('setup.dev_server.start', { fixture: name });
|
||||
log(`spawning dev server: ${runtime.devCommand.join(' ')}`);
|
||||
dev = startDevServer(tmp, runtime);
|
||||
const { port: devPort } = await dev.ready;
|
||||
trace('setup.dev_server.end', { fixture: name, port: devPort });
|
||||
log(`dev server ready on ${devPort} in ${formatDuration(Date.now() - devStartedAt)}`);
|
||||
|
||||
// Agent loop runs concurrently — abort on teardown.
|
||||
if (agent) {
|
||||
agentAbort = new AbortController();
|
||||
const loopOptions = {
|
||||
tmp,
|
||||
scriptsDir: SCRIPTS_DIR,
|
||||
port: live.port,
|
||||
token: live.token,
|
||||
agent,
|
||||
wrapTarget,
|
||||
signal: agentAbort.signal,
|
||||
trace,
|
||||
progressive,
|
||||
progressiveDelayMs,
|
||||
progressiveInitialCount,
|
||||
atomicDelayMs,
|
||||
steerSourceFile: runtime.steer?.sourceFile,
|
||||
steerTarget: runtime.steer?.target,
|
||||
};
|
||||
const loops = [runAgentLoop({ ...loopOptions, log: (m) => log('[worker] ' + m) })];
|
||||
if (progressive) {
|
||||
loops.push(runAgentLoop({ ...loopOptions, log: (m) => log('[supervisor] ' + m) }));
|
||||
}
|
||||
agentDone = Promise.all(loops);
|
||||
}
|
||||
agentAbort = new AbortController();
|
||||
agentDone = runAgentLoop({
|
||||
tmp,
|
||||
scriptsDir: SCRIPTS_DIR,
|
||||
port: live.port,
|
||||
token: live.token,
|
||||
agent,
|
||||
wrapTarget,
|
||||
signal: agentAbort.signal,
|
||||
log: (m) => log('[agent] ' + m),
|
||||
steerSourceFile: runtime.steer?.sourceFile,
|
||||
steerTarget: runtime.steer?.target,
|
||||
});
|
||||
|
||||
const scheme = runtime.scheme || 'http';
|
||||
ctx = await browser.newContext({
|
||||
@@ -352,12 +283,10 @@ export async function bootFixtureSession({
|
||||
});
|
||||
|
||||
const pageStartedAt = Date.now();
|
||||
trace('setup.page_load.start', { fixture: name });
|
||||
await page.goto(`${scheme}://127.0.0.1:${devPort}`, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 30_000,
|
||||
});
|
||||
trace('setup.page_load.end', { fixture: name });
|
||||
log(`page loaded in ${formatDuration(Date.now() - pageStartedAt)}`);
|
||||
|
||||
return {
|
||||
@@ -366,7 +295,6 @@ export async function bootFixtureSession({
|
||||
ctx,
|
||||
dev,
|
||||
live,
|
||||
worker: externalWorker,
|
||||
consoleErrors,
|
||||
stopLiveServer: stopLiveForDeferredWork,
|
||||
teardown,
|
||||
|
||||
Reference in New Issue
Block a user