63 lines
3.4 KiB
JavaScript
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)) }));
|
|
}
|