chore: exclude local resource artifacts from main sync
This commit is contained in:
185
scripts/preview-test-app-watch.mjs
Normal file
185
scripts/preview-test-app-watch.mjs
Normal file
@@ -0,0 +1,185 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const rootDir = process.cwd();
|
||||
const viteBin = resolve(rootDir, 'node_modules/vite/bin/vite.js');
|
||||
const serveScript = resolve(rootDir, 'scripts/serve-app-dist.mjs');
|
||||
const appDistDir = '/tmp/ai-code-test-app-dist';
|
||||
const buildEnv = {
|
||||
...process.env,
|
||||
VITE_FILTER_PUBLIC_DIR: 'true',
|
||||
VITE_DISABLE_MODULE_PRELOAD: 'true',
|
||||
};
|
||||
const serveEnv = {
|
||||
...process.env,
|
||||
APP_DIST_DIR: appDistDir,
|
||||
};
|
||||
|
||||
function log(message) {
|
||||
console.log(`[preview:test-app:watch] ${new Date().toISOString()} ${message}`);
|
||||
}
|
||||
|
||||
function logError(message) {
|
||||
console.error(`[preview:test-app:watch] ${new Date().toISOString()} ${message}`);
|
||||
}
|
||||
|
||||
function runNodeScript(args, env, options = {}) {
|
||||
return spawn(process.execPath, args, {
|
||||
cwd: rootDir,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
function pipePrefixedLines(stream, writer, prefix, onLine) {
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
|
||||
let buffer = '';
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', (chunk) => {
|
||||
buffer += chunk;
|
||||
const lines = buffer.split(/\r?\n/u);
|
||||
buffer = lines.pop() ?? '';
|
||||
|
||||
for (const line of lines) {
|
||||
onLine?.(line);
|
||||
writer.write(`${prefix}${line}\n`);
|
||||
}
|
||||
});
|
||||
stream.on('end', () => {
|
||||
if (!buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
onLine?.(buffer);
|
||||
writer.write(`${prefix}${buffer}\n`);
|
||||
buffer = '';
|
||||
});
|
||||
}
|
||||
|
||||
function attachWatchBuildLogging(child) {
|
||||
let rebuildStartedAt = null;
|
||||
let serviceWorkerBuildPending = false;
|
||||
|
||||
const handleLine = (line) => {
|
||||
const normalized = line.replace(/\u001b\[[0-9;]*m/g, '').trim();
|
||||
|
||||
if (!normalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalized.startsWith('Building src/sw.js service worker')) {
|
||||
serviceWorkerBuildPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalized.includes('building client environment for production')) {
|
||||
if (serviceWorkerBuildPending) {
|
||||
serviceWorkerBuildPending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
rebuildStartedAt = Date.now();
|
||||
log('watch rebuild started');
|
||||
return;
|
||||
}
|
||||
|
||||
const builtMatch = normalized.match(/^built in (\d+)ms\.$/u);
|
||||
if (!builtMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reportedMs = Number.parseInt(builtMatch[1], 10);
|
||||
const wallMs = rebuildStartedAt === null ? null : Date.now() - rebuildStartedAt;
|
||||
const durationText = wallMs === null ? `vite=${reportedMs}ms` : `vite=${reportedMs}ms, wall=${wallMs}ms`;
|
||||
log(`watch rebuild completed (${durationText})`);
|
||||
rebuildStartedAt = null;
|
||||
};
|
||||
|
||||
pipePrefixedLines(child.stdout, process.stdout, '[preview:watch:vite] ', handleLine);
|
||||
pipePrefixedLines(child.stderr, process.stderr, '[preview:watch:vite] ', handleLine);
|
||||
}
|
||||
|
||||
function waitForExit(child) {
|
||||
return new Promise((resolveExit, rejectExit) => {
|
||||
child.once('error', rejectExit);
|
||||
child.once('exit', (code, signal) => {
|
||||
if (code === 0) {
|
||||
resolveExit();
|
||||
return;
|
||||
}
|
||||
|
||||
rejectExit(new Error(`process exited with code ${code ?? 'null'}${signal ? ` (signal: ${signal})` : ''}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function terminateChild(child, signal = 'SIGTERM') {
|
||||
if (child.killed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
child.kill(signal);
|
||||
} catch {
|
||||
// Ignore termination races during shutdown.
|
||||
}
|
||||
}
|
||||
|
||||
const backgroundChildren = [];
|
||||
let shuttingDown = false;
|
||||
|
||||
function shutdown(exitCode = 0) {
|
||||
if (shuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
shuttingDown = true;
|
||||
backgroundChildren.forEach((child) => terminateChild(child));
|
||||
setTimeout(() => {
|
||||
backgroundChildren.forEach((child) => terminateChild(child, 'SIGKILL'));
|
||||
process.exit(exitCode);
|
||||
}, 5_000).unref();
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => shutdown(0));
|
||||
process.on('SIGTERM', () => shutdown(0));
|
||||
|
||||
try {
|
||||
log('initial build started');
|
||||
await waitForExit(runNodeScript([viteBin, 'build', '--outDir', appDistDir], buildEnv));
|
||||
log('initial build completed');
|
||||
|
||||
const watchBuild = runNodeScript([viteBin, 'build', '--watch', '--clearScreen', 'false', '--outDir', appDistDir], buildEnv, {
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
});
|
||||
const previewServer = runNodeScript([serveScript], serveEnv);
|
||||
backgroundChildren.push(watchBuild, previewServer);
|
||||
attachWatchBuildLogging(watchBuild);
|
||||
log('preview server and build watcher are running');
|
||||
|
||||
watchBuild.once('exit', (code, signal) => {
|
||||
if (shuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
logError(`build watcher stopped: code=${code ?? 'null'} signal=${signal ?? 'none'}`);
|
||||
shutdown(code && code > 0 ? code : 1);
|
||||
});
|
||||
|
||||
previewServer.once('exit', (code, signal) => {
|
||||
if (shuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
logError(`preview server stopped: code=${code ?? 'null'} signal=${signal ?? 'none'}`);
|
||||
shutdown(code && code > 0 ? code : 1);
|
||||
});
|
||||
} catch (error) {
|
||||
logError('failed to prepare preview build');
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user