158 lines
6.1 KiB
JavaScript
158 lines
6.1 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { existsSync, lstatSync, readdirSync, readFileSync, readlinkSync } from 'fs';
|
|
import { dirname, join, relative, sep } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const projectRoot = join(__dirname, '..');
|
|
const repoRoot = join(projectRoot, '..', '..');
|
|
|
|
const mirrors = [
|
|
{ source: 'src', target: 'src' },
|
|
{ source: 'build', target: 'build' },
|
|
{ source: 'assets', target: 'assets' },
|
|
{ source: 'build.zig', target: 'build.zig' },
|
|
{ source: 'build.zig.zon', target: 'build.zig.zon' },
|
|
{ source: 'app.zon', target: 'app.zon' },
|
|
{ source: 'LICENSE', target: 'LICENSE' },
|
|
{ source: 'skills', target: 'skills' },
|
|
{ source: 'skill-data', target: 'skill-data' },
|
|
{ source: 'third_party/webview2', target: 'third_party/webview2' },
|
|
// The @native-sdk/core closure TypeScript app builds resolve from the
|
|
// installed package: the transpiler, the SDK library modules (also the
|
|
// editor package), the rt kernel, and the manifest + lockfile that make
|
|
// `npm ci` work inside packages/core on the npm-installed layout.
|
|
// Entry-by-entry on purpose — test/ and scripts/ are repo-dev surface
|
|
// and must NOT ship, so the whole-directory comparison stays off.
|
|
{ source: 'packages/core/src', target: 'packages/core/src' },
|
|
{ source: 'packages/core/sdk', target: 'packages/core/sdk' },
|
|
{ source: 'packages/core/rt', target: 'packages/core/rt' },
|
|
{ source: 'packages/core/package.json', target: 'packages/core/package.json' },
|
|
{ source: 'packages/core/package-lock.json', target: 'packages/core/package-lock.json' },
|
|
];
|
|
|
|
const errors = [];
|
|
|
|
function displayPath(path) {
|
|
return relative(repoRoot, path).split(sep).join('/');
|
|
}
|
|
|
|
function addError(message) {
|
|
errors.push(message);
|
|
}
|
|
|
|
function compareEntries(sourcePath, targetPath) {
|
|
if (!existsSync(sourcePath)) {
|
|
addError(`missing source ${displayPath(sourcePath)}`);
|
|
return;
|
|
}
|
|
if (!existsSync(targetPath)) {
|
|
addError(`missing mirror ${displayPath(targetPath)}`);
|
|
return;
|
|
}
|
|
|
|
const sourceStat = lstatSync(sourcePath);
|
|
const targetStat = lstatSync(targetPath);
|
|
|
|
if (sourceStat.isSymbolicLink() || targetStat.isSymbolicLink()) {
|
|
if (!sourceStat.isSymbolicLink() || !targetStat.isSymbolicLink()) {
|
|
addError(`type mismatch ${displayPath(sourcePath)} -> ${displayPath(targetPath)}`);
|
|
return;
|
|
}
|
|
const sourceLink = readlinkSync(sourcePath);
|
|
const targetLink = readlinkSync(targetPath);
|
|
if (sourceLink !== targetLink) {
|
|
addError(`symlink mismatch ${displayPath(sourcePath)} -> ${displayPath(targetPath)}`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (sourceStat.isDirectory() || targetStat.isDirectory()) {
|
|
if (!sourceStat.isDirectory() || !targetStat.isDirectory()) {
|
|
addError(`type mismatch ${displayPath(sourcePath)} -> ${displayPath(targetPath)}`);
|
|
return;
|
|
}
|
|
const sourceNames = readdirSync(sourcePath).filter((name) => name !== '.DS_Store').sort();
|
|
const targetNames = readdirSync(targetPath).filter((name) => name !== '.DS_Store').sort();
|
|
const names = new Set([...sourceNames, ...targetNames]);
|
|
for (const name of names) {
|
|
compareEntries(join(sourcePath, name), join(targetPath, name));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!sourceStat.isFile() || !targetStat.isFile()) {
|
|
addError(`unsupported entry type ${displayPath(sourcePath)} -> ${displayPath(targetPath)}`);
|
|
return;
|
|
}
|
|
|
|
const sourceBytes = readFileSync(sourcePath);
|
|
const targetBytes = readFileSync(targetPath);
|
|
if (!sourceBytes.equals(targetBytes)) {
|
|
addError(`content mismatch ${displayPath(sourcePath)} -> ${displayPath(targetPath)}`);
|
|
}
|
|
}
|
|
|
|
for (const mirror of mirrors) {
|
|
compareEntries(join(repoRoot, mirror.source), join(projectRoot, mirror.target));
|
|
}
|
|
|
|
// Every file build/app.zig resolves from the installed package via
|
|
// dep.path must actually ship with the package: it has to exist in the
|
|
// staged mirror and sit under an entry of package.json "files", or every
|
|
// generated app build fails on a missing input after install. Literals
|
|
// reach dep.path directly, through the externalModule helper, and through
|
|
// an inline switch, so collect all three forms.
|
|
function collectDepPathLiterals(source) {
|
|
const literals = new Set();
|
|
for (const match of source.matchAll(/dep\.path\(\s*"([^"]+)"\s*\)/g)) {
|
|
literals.add(match[1]);
|
|
}
|
|
for (const match of source.matchAll(/externalModule\(\s*b\s*,\s*dep\s*,[^)]*?"([^"]+)"\s*\)/g)) {
|
|
literals.add(match[1]);
|
|
}
|
|
for (const match of source.matchAll(/dep\.path\(\s*switch\s*\([^)]*\)\s*\{([^}]*)\}/g)) {
|
|
for (const literal of match[1].matchAll(/"([^"]+)"/g)) {
|
|
literals.add(literal[1]);
|
|
}
|
|
}
|
|
return [...literals].sort();
|
|
}
|
|
|
|
const appZigPath = join(repoRoot, 'build', 'app.zig');
|
|
const packageFiles = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8')).files ?? [];
|
|
|
|
function coveredByFiles(literal) {
|
|
return packageFiles.some((entry) => literal === entry || literal.startsWith(`${entry}/`));
|
|
}
|
|
|
|
for (const literal of collectDepPathLiterals(readFileSync(appZigPath, 'utf8'))) {
|
|
if (!existsSync(join(projectRoot, literal))) {
|
|
addError(`build/app.zig dep.path("${literal}") is missing from the package mirror`);
|
|
}
|
|
if (!coveredByFiles(literal)) {
|
|
addError(`build/app.zig dep.path("${literal}") is not covered by package.json "files"`);
|
|
}
|
|
}
|
|
|
|
if (errors.length > 0) {
|
|
console.error('Package framework mirror is out of sync.');
|
|
for (const error of errors.slice(0, 20)) {
|
|
console.error(` - ${error}`);
|
|
}
|
|
if (errors.length > 20) {
|
|
console.error(` ... ${errors.length - 20} more`);
|
|
}
|
|
console.error('');
|
|
console.error('The package mirror is GENERATED output: copy-framework.js stages it');
|
|
console.error('from the repo-root framework sources (prepack and scripts:check run');
|
|
console.error('the copy first), and the mirror paths are gitignored — committing the');
|
|
console.error('mirror is not the fix. Regenerate it, then re-run this check:');
|
|
console.error('');
|
|
console.error(' node packages/native-sdk/scripts/copy-framework.js (from the repo root)');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('Package framework mirror is in sync.');
|