Files

63 lines
3.4 KiB
JavaScript

import { createHash } from 'node:crypto';
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
import { delimiter, dirname, join, relative, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileSync } from 'node:child_process';
const script = fileURLToPath(import.meta.url);
const repo = resolve(dirname(script), '..');
const web = join(repo, 'web');
const cache = join(repo, 'bin', 'build-cache');
mkdirSync(cache, { recursive: true });
const hash = value => createHash('sha256').update(value).digest('hex');
const readJSON = path => { try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; } };
const runtimeEnvironment = { ...process.env };
const pathKey = Object.keys(runtimeEnvironment).find(key => key.toLowerCase() === 'path') || 'PATH';
runtimeEnvironment[pathKey] = dirname(process.execPath) + delimiter + (runtimeEnvironment[pathKey] || '');
const npm = args => process.platform === 'win32'
? execFileSync('cmd.exe', ['/d', '/s', '/c', `npm.cmd ${args.join(' ')}`], { cwd: web, env: runtimeEnvironment, stdio: 'inherit', windowsHide: true })
: execFileSync('npm', args, { cwd: web, env: runtimeEnvironment, stdio: 'inherit' });
const dependencyKey = hash(JSON.stringify([
process.version, process.arch, process.platform,
readFileSync(join(web, 'package.json'), 'utf8'),
readFileSync(join(web, 'package-lock.json'), 'utf8'),
existsSync(join(web, '.npmrc')) ? readFileSync(join(web, '.npmrc'), 'utf8') : '',
]));
const dependencyStamp = join(web, 'node_modules', '.linkport-dependencies.json');
if (readJSON(dependencyStamp)?.key !== dependencyKey || !existsSync(join(web, 'node_modules', '.bin', process.platform === 'win32' ? 'vite.cmd' : 'vite'))) {
npm(['ci', '--no-audit', '--no-fund']);
writeFileSync(dependencyStamp, JSON.stringify({ key: dependencyKey }));
}
function files(root, skip = new Set()) {
if (!existsSync(root)) return [];
return readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name)).flatMap(entry => {
if (skip.has(entry.name) || entry.name === '.gitkeep') return [];
const path = join(root, entry.name);
return entry.isDirectory() ? files(path, skip) : [path];
});
}
function digest(paths) {
return hash(JSON.stringify(paths.map(path => [relative(repo, path), hash(readFileSync(path))])));
}
const excluded = new Set(['node_modules', 'dist', 'test-results', 'playwright-report', 'tests']);
const sourceKey = () => hash(JSON.stringify([
dependencyKey, digest([...files(web, excluded), script]),
Object.entries(process.env).filter(([key]) => /^(VITE_|NODE_|NPM_CONFIG_)/i.test(key)).sort(),
]));
const key = sourceKey();
const stamp = join(cache, 'web.json');
const saved = readJSON(stamp);
const dist = join(web, 'dist');
if (!process.argv.includes('--force') && saved?.key === key && existsSync(join(dist, 'index.html')) && saved.output === digest(files(dist))) {
console.log('Frontend cache hit: source and output checksums match.');
} else {
console.log('Building frontend: cache missing, changed or forced.');
const keep = join(dist, '.gitkeep');
const keepBytes = existsSync(keep) ? readFileSync(keep) : Buffer.from('\n');
try { npm(['run', 'build']); }
finally { mkdirSync(dist, { recursive: true }); writeFileSync(keep, keepBytes); }
if (sourceKey() !== key) throw new Error('Frontend changed during build. Retry packaging.');
writeFileSync(stamp, JSON.stringify({ key, output: digest(files(dist)) }));
}