mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-28 06:22:22 +00:00
126 lines
5.5 KiB
TypeScript
126 lines
5.5 KiB
TypeScript
import { execFileSync, execSync } from 'node:child_process';
|
|
import { createRequire } from 'node:module';
|
|
import { access, cp, mkdir, readFile, symlink, writeFile } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { setupMiniRepo } from '../helpers/mini-repo.js';
|
|
import { createTempDir } from '../helpers/test-db.js';
|
|
import { analyzeRuntime, PACKAGE_ROOT } from '../helpers/parse-identity-runtime.js';
|
|
|
|
describe('automatic parser identity in the installed distribution', () => {
|
|
it('uses packed artifacts without source, Git metadata, a sibling shared package, or TypeScript', async () => {
|
|
const artifacts = await createTempDir();
|
|
const repo = await setupMiniRepo();
|
|
try {
|
|
// The integration gate builds first. Pack that output with npm's actual
|
|
// files/bundled-dependencies rules; do not rerun a build in a parallel suite.
|
|
const packSource = path.join(artifacts.dbPath, 'pack-source');
|
|
await cp(PACKAGE_ROOT, packSource, { recursive: true, dereference: true });
|
|
const originalManifest = await readFile(path.join(PACKAGE_ROOT, 'package.json'), 'utf8');
|
|
const packManifest = JSON.parse(originalManifest);
|
|
// npm 10 runs prepare even with --ignore-scripts. Remove it only from
|
|
// this private copy, then restore the original manifest after unpacking.
|
|
delete packManifest.scripts.prepare;
|
|
await writeFile(path.join(packSource, 'package.json'), JSON.stringify(packManifest));
|
|
const packed = JSON.parse(
|
|
// npm.cmd requires a shell on Windows. Keep paths out of the command.
|
|
execSync('npm pack --ignore-scripts --json', {
|
|
cwd: packSource,
|
|
env: { ...process.env, npm_config_pack_destination: artifacts.dbPath },
|
|
encoding: 'utf8',
|
|
timeout: 120_000,
|
|
maxBuffer: 8 * 1024 * 1024,
|
|
}),
|
|
) as Array<{ filename: string }>;
|
|
execFileSync('tar', [
|
|
'-xzf',
|
|
path.join(artifacts.dbPath, packed[0].filename),
|
|
'-C',
|
|
artifacts.dbPath,
|
|
]);
|
|
const runtime = path.join(artifacts.dbPath, 'package');
|
|
await writeFile(path.join(runtime, 'package.json'), originalManifest);
|
|
const manifest = JSON.parse(await readFile(path.join(runtime, 'package.json'), 'utf8'));
|
|
// Supply already-installed production dependencies offline. Bundled
|
|
// grammars stay the independent physical files produced by npm pack.
|
|
for (const name of Object.keys(manifest.dependencies)) {
|
|
const target = path.join(runtime, 'node_modules', name);
|
|
if (
|
|
await access(target).then(
|
|
() => true,
|
|
() => false,
|
|
)
|
|
)
|
|
continue;
|
|
await mkdir(path.dirname(target), { recursive: true });
|
|
await symlink(path.join(PACKAGE_ROOT, 'node_modules', name), target, 'junction');
|
|
}
|
|
for (const absent of [
|
|
'src',
|
|
'.git',
|
|
'../gitnexus-shared',
|
|
'node_modules/typescript',
|
|
'node_modules/gitnexus-shared',
|
|
]) {
|
|
expect(
|
|
await access(path.join(runtime, absent)).then(
|
|
() => true,
|
|
() => false,
|
|
),
|
|
absent,
|
|
).toBe(false);
|
|
}
|
|
const installedRequire = createRequire(path.join(runtime, 'package.json'));
|
|
expect(() => installedRequire.resolve('typescript')).toThrow();
|
|
expect(() => installedRequire.resolve('gitnexus-shared')).toThrow();
|
|
await writeFile(
|
|
path.join(repo.dbPath, 'src/probe.ts'),
|
|
'export function parserProbe() { return 1; }\n',
|
|
);
|
|
const run = (force = false) =>
|
|
analyzeRuntime(runtime, repo.dbPath, path.join(artifacts.dbPath, 'home'), {
|
|
options: { force },
|
|
});
|
|
const cold = await run();
|
|
expect(cold.error).toBeUndefined();
|
|
expect(cold.meta?.parseCacheIdentity?.base).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
expect(cold.facts).toContainEqual({ kind: 'Function', name: 'parserProbe' });
|
|
const warm = await run(true);
|
|
expect(warm.error).toBeUndefined();
|
|
expect(warm.reparsed).toBe(0);
|
|
expect(warm.hits).toBe(cold.reparsed);
|
|
expect(warm.facts).toEqual(cold.facts);
|
|
|
|
const queries = path.join(runtime, 'dist/core/ingestion/tree-sitter-queries.js');
|
|
const source = await readFile(queries, 'utf8');
|
|
const changed = source.replace(
|
|
'(function_declaration\n name: (identifier) @name) @definition.function',
|
|
'(function_declaration\n name: (identifier) @name) @definition.class',
|
|
);
|
|
expect(changed).not.toBe(source);
|
|
await writeFile(queries, changed);
|
|
const captures = await run();
|
|
expect(captures.error).toBeUndefined();
|
|
expect(captures.hits).toBe(0);
|
|
expect(captures.meta?.parseCacheIdentity).not.toEqual(cold.meta?.parseCacheIdentity);
|
|
expect(captures.facts).toContainEqual({ kind: 'Class', name: 'parserProbe' });
|
|
|
|
const metadata = path.join(
|
|
runtime,
|
|
'node_modules/tree-sitter-typescript/typescript/src/node-types.json',
|
|
);
|
|
await writeFile(metadata, `${await readFile(metadata, 'utf8')}\n`);
|
|
const grammar = await run();
|
|
expect(grammar.error).toBeUndefined();
|
|
expect(grammar.hits).toBe(0);
|
|
expect(grammar.meta?.parseCacheIdentity).not.toEqual(captures.meta?.parseCacheIdentity);
|
|
expect(grammar.facts).toEqual(captures.facts);
|
|
expect(JSON.parse(await readFile(path.join(runtime, 'package.json'), 'utf8')).version).toBe(
|
|
manifest.version,
|
|
);
|
|
} finally {
|
|
await repo.cleanup();
|
|
await artifacts.cleanup();
|
|
}
|
|
}, 240_000);
|
|
});
|