mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
111 lines
5.4 KiB
TypeScript
111 lines
5.4 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
import type { Plugin } from 'vite';
|
|
|
|
/** Evidence for the narrow browser-only license decision. Never shipped in dist. */
|
|
export function licenseBuildInventory(root: string): { app: Plugin; workers: () => Plugin[] } {
|
|
const proofPath = join(root, '.vite/license-build.json');
|
|
const modules = new Set<string>();
|
|
const externals = new Set<string>();
|
|
const chunks = new Set<string>();
|
|
const generatedAssets: Record<string, string> = {};
|
|
const inputs: Record<string, string> = {};
|
|
let workerBuilds = 0;
|
|
let failed = false;
|
|
let outDir = join(root, 'dist');
|
|
const hash = (path: string) => createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
const slash = (path: string) => path.replaceAll('\\', '/');
|
|
const inside = (base: string, path: string) => {
|
|
const rel = relative(base, path);
|
|
return rel !== '..' && !rel.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`) && !isAbsolute(rel);
|
|
};
|
|
function localPath(path: string): string {
|
|
const actual = realpathSync(path);
|
|
const dependencies = realpathSync(join(root, 'node_modules'));
|
|
if (inside(dependencies, actual)) return `node_modules/${slash(relative(dependencies, actual))}`;
|
|
if (inside(realpathSync(root), actual)) return slash(relative(realpathSync(root), actual));
|
|
throw new Error(`License inventory input outside the UI: ${path}`);
|
|
}
|
|
function record(path: string): string | undefined {
|
|
const clean = path.split('?')[0];
|
|
if (!clean || clean.includes('\0') || !isAbsolute(clean) || !existsSync(clean) || !statSync(clean).isFile()) return;
|
|
const name = localPath(clean);
|
|
inputs[name] = hash(clean);
|
|
return name;
|
|
}
|
|
function files(dir: string): string[] {
|
|
return readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
|
|
const path = join(dir, entry.name);
|
|
return entry.isDirectory() ? files(path) : [path];
|
|
});
|
|
}
|
|
const plugin = (worker: boolean): Plugin => ({
|
|
name: `cbm-license-inventory-${worker ? 'worker' : 'app'}`,
|
|
apply: 'build',
|
|
configResolved(config) {
|
|
if (!worker) outDir = resolve(config.root, config.build.outDir);
|
|
},
|
|
buildStart() {
|
|
if (worker) { workerBuilds++; return; }
|
|
rmSync(proofPath, { force: true });
|
|
modules.clear(); externals.clear(); chunks.clear();
|
|
for (const key of Object.keys(inputs)) delete inputs[key];
|
|
for (const key of Object.keys(generatedAssets)) delete generatedAssets[key];
|
|
workerBuilds = 0;
|
|
failed = false;
|
|
for (const file of ['vite.config.ts', 'tools/license-build-inventory.ts', 'package.json',
|
|
'node_modules/@huggingface/transformers/package.json']) record(join(root, file));
|
|
},
|
|
buildEnd(error) { if (error) failed = true; },
|
|
generateBundle: { order: 'post', handler(_options, bundle) {
|
|
for (const id of this.getModuleIds()) {
|
|
const info = this.getModuleInfo(id);
|
|
if (info?.isExternal) externals.add(id);
|
|
else modules.add(record(id) ?? id);
|
|
}
|
|
for (const path of this.getWatchFiles()) record(path);
|
|
for (const [name, output] of Object.entries(bundle)) {
|
|
if (output.type === 'chunk') chunks.add(name);
|
|
else if (name === 'index.html' || name.endsWith('.css')) {
|
|
// Only bundler outputs qualify. Files copied from public never
|
|
// enter this hook; record bytes before inspecting the dist tree.
|
|
generatedAssets[name] = createHash('sha256').update(output.source).digest('hex');
|
|
}
|
|
}
|
|
} },
|
|
closeBundle() {
|
|
if (worker || failed || !existsSync(outDir)) return;
|
|
const outputs: Record<string, string> = {};
|
|
const runtimeInputs: Record<string, string> = {};
|
|
// Vite emits ONNX's runtime through new URL(..., import.meta.url). Match
|
|
// bytes to its installed input, rather than trusting an output filename.
|
|
const ortDir = join(root, 'node_modules/onnxruntime-web/dist');
|
|
const runtimeSources = existsSync(ortDir) ? files(ortDir).filter(path => /\.(wasm|m?js)$/.test(path)) : [];
|
|
const runtimeHashes = new Map(runtimeSources.map(path => [hash(path), path]));
|
|
for (const path of files(outDir).sort()) {
|
|
const name = slash(relative(outDir, path));
|
|
outputs[name] = hash(path);
|
|
if (/\.(wasm|m?js)$/.test(name)) {
|
|
const source = runtimeHashes.get(outputs[name]);
|
|
if (source) runtimeInputs[name] = record(source)!;
|
|
}
|
|
}
|
|
// Public copies are not Rollup modules. Record their inputs, while the
|
|
// verifier still rejects copied executable payloads outside the graph.
|
|
const publicDir = join(root, 'public');
|
|
if (existsSync(publicDir)) for (const path of files(publicDir)) record(path);
|
|
mkdirSync(join(root, '.vite'), { recursive: true });
|
|
writeFileSync(proofPath, JSON.stringify({
|
|
version: 1,
|
|
lock_sha256: hash(join(root, 'package-lock.json')),
|
|
inputs: Object.fromEntries(Object.entries(inputs).sort()),
|
|
modules: [...modules].sort(), externals: [...externals].sort(),
|
|
chunks: [...chunks].sort(), worker_builds: workerBuilds,
|
|
outputs, runtime_inputs: runtimeInputs, generated_assets: generatedAssets,
|
|
}, null, 2) + '\n');
|
|
},
|
|
});
|
|
return { app: plugin(false), workers: () => [plugin(true)] };
|
|
}
|