mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-01 02:01:24 +00:00
Some checks are pending
Gitleaks / gitleaks (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(embeddings): isolate local ONNX inference in a child_process sidecar The analyze parent must not load onnxruntime-node. Fork a sidecar for vectors only and reap it on worker exit; keep Ladybug writes in-process. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(embeddings): share the sidecar client across MCP, serve, and sync Query hosts now use the core façade instead of a second in-process ONNX embedder. Search skips an empty table, sync reaps beside closeLbug, and ready means the stack is resolvable rather than a warm singleton. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(embeddings): refuse Intel Mac and unloadable prefix before npm heal Analyze, sync, install, and the sidecar client now consult the platform blocker before forking or downloading the optional stack. HTTP stays the escape hatch; wasm is not treated as a rescue. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(embeddings): take the ONNX stack off default npm install Pins live in gitnexusEmbeddingStack. embeddings install writes prefix overrides before npm spawn. Leftover 1.6.12 package-first trees are residual. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(publish): drop grammar source from the published tarball Every vendored grammar has 6/6 prebuilds, so files ships those plus Leiden and FTS instead of parser.c. First ship stays above 80 MiB. Co-authored-by: Cursor <cursoragent@cursor.com> * test(embeddings): match MCP missing-stack warn to the R20 copy Default install no longer calls the stack optional, so the once-per-backend stderr assertion must look for the new lead line. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(review): bound sidecar death, cancel writes, and publish-file guards Init-time native crashes no longer respawn a child on every query. Local embedBatch honors AbortSignal after sidecar return, MCP query() surfaces vector-lane degradation, disconnect always reaps, and the grammar prepack guard checks files globs instead of on-disk prebuilds. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(embeddings): share runtime preflight and sidecar reap helpers Analyze and embeddings-sync used the same blocker/prefix/install gate with different error routing. One assessment keeps those paths aligned without changing CLI vs thrown-error behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3287) Keep a reaped sidecar from resetting its replacement, wait for dispose, tighten the publish-files guard, and stop assuming a leftover ONNX tree in CI. Co-authored-by: Cursor <cursoragent@cursor.com> * Address remaining PR review feedback (#3287) Clear the sidecar reap timeout, add init IPC slack, and isolate embeddings-sync tests from HTTP-mode env. Co-authored-by: Cursor <cursoragent@cursor.com> * test(embeddings): unstub globals after sidecar HTTP-mode tests Keep a leaked fetch stub from failing assertions out of later tests in the same file. Co-authored-by: Cursor <cursoragent@cursor.com> * test(embeddings): pin sidecar success cases off darwin/x64 The runtime blocker reads the real process platform before the fork mock, so local-success tests must not inherit an Intel Mac host. Co-authored-by: Cursor <cursoragent@cursor.com> * Address remaining PR review feedback (#3287) Keep vector degradation per query, treat leftover Intel-Mac stacks as not ready, and document that the CLI image no longer ships ONNX. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify embedding sidecar shutdown and search hot paths Drop redundant sidecar reaps and unused child helpers, and run FTS alongside semantic search. Co-authored-by: Cursor <cursoragent@cursor.com> * Address remaining PR review feedback (#3287) Share HF attempt parsing with the sidecar init deadline, abort embed waits without killing the child, and restore last init options on recreate. Co-authored-by: Cursor <cursoragent@cursor.com> * Address remaining PR review feedback (#3287) Treat sub-1 HF attempt env values as invalid, and drop leaked sidecar waiters when IPC send throws. Co-authored-by: Cursor <cursoragent@cursor.com> * Address remaining PR review feedback (#3287) Keep sidecar init on a shared chain; each waiter can abort only its own wait. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): declare embedding-table existence probe as unordered LIMIT The empty-table skip in semanticSearch is existence-only; declare it so the #2787 determinism guard stops failing coverage shard 3/3. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
276 lines
9.9 KiB
JavaScript
276 lines
9.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Publish guard: every vendored tree-sitter grammar must ship a loadable binding.
|
|
*
|
|
* The npm tarball ships a lean vendor/ allow-list (package.json `files`):
|
|
* prebuilds, the bindings entry, node-types.json, and package metadata — not
|
|
* generated parser.c. A grammar is "covered" on a platform-arch tuple if EITHER
|
|
* a prebuild ships for it OR the grammar's full source-build set ships (so the
|
|
* install can source-build it, toolchain permitting). Lean publish is safe ONLY
|
|
* when every grammar has all six prebuilds; dropping source while any grammar
|
|
* still lacks a prebuild would ship a grammar with NO loadable binding.
|
|
*
|
|
* HOW SOURCE INCLUSION IS DECIDED. The `files` allow-list OVERRIDES `.npmignore`
|
|
* for the vendored subtree (verified: an active "vendor/(star-star)/src/parser.c"
|
|
* in .npmignore does NOT drop it from `npm pack`). So `.npmignore` can never
|
|
* exclude vendored source — the ONLY lever is the `files` field. A broad `vendor`
|
|
* ships the whole subtree (source + prebuilds); lean publish narrows `files` to
|
|
* non-source subpaths. This guard therefore reads `files` directly rather than
|
|
* shelling out to `npm pack` (which, in prepack, would re-enter this guard and,
|
|
* on npm versions that don't honor --ignore-scripts for prepare/prepack, run the
|
|
* full build — slow enough to time out and fragile).
|
|
*
|
|
* Wired via `prepack`, so it fails `npm pack` / `npm publish` if the invariant is
|
|
* violated.
|
|
*/
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const TUPLES = [
|
|
'linux-x64',
|
|
'linux-arm64',
|
|
'darwin-x64',
|
|
'darwin-arm64',
|
|
'win32-x64',
|
|
'win32-arm64',
|
|
];
|
|
|
|
// Source-build inputs (relative to vendor/<name>/) whose presence makes a grammar
|
|
// source-buildable. Per-grammar we only require the ones that exist on disk (e.g.
|
|
// tree-sitter-c has no external scanner.c).
|
|
const SOURCE_BUILD_REL = [
|
|
'binding.gyp',
|
|
'bindings/node/binding.cc',
|
|
'src/parser.c',
|
|
'src/scanner.c',
|
|
'src/tree_sitter/parser.h',
|
|
];
|
|
|
|
/**
|
|
* Does the package.json `files` allow-list ship the WHOLE vendor subtree (and
|
|
* therefore the vendored grammar source)? A bare `vendor` (optionally with a
|
|
* trailing slash or `/**`/`/*`) includes everything under vendor/. A lean publish
|
|
* replaces that with non-source subpaths, so this returns false and grammars must
|
|
* then rely on prebuilds.
|
|
*/
|
|
function filesShipsVendorSource(filesField) {
|
|
return filesEntries(filesField).includes('vendor');
|
|
}
|
|
|
|
function normalizeFilesEntry(value) {
|
|
return String(value ?? '')
|
|
.replace(/\\/g, '/')
|
|
.replace(/\/+$/, '')
|
|
.replace(/\/\*\*?$/, '');
|
|
}
|
|
|
|
function filesEntries(filesField) {
|
|
return (filesField || []).map(normalizeFilesEntry);
|
|
}
|
|
|
|
function filesCoverGrammarPrebuilds(entries, grammarName) {
|
|
if (entries.includes('vendor') || entries.includes('vendor/**/prebuilds')) return true;
|
|
return (
|
|
entries.includes(`vendor/${grammarName}/prebuilds`) || entries.includes(`vendor/${grammarName}`)
|
|
);
|
|
}
|
|
|
|
function filesCoverGrammarBindings(entries, grammarName) {
|
|
if (entries.includes('vendor') || entries.includes('vendor/**/bindings/node/index.js')) {
|
|
return true;
|
|
}
|
|
return (
|
|
entries.includes(`vendor/${grammarName}/bindings/node/index.js`) ||
|
|
entries.includes(`vendor/${grammarName}`)
|
|
);
|
|
}
|
|
|
|
function filesCoverGrammarPackageJson(entries, grammarName) {
|
|
if (entries.includes('vendor') || entries.includes('vendor/**/package.json')) return true;
|
|
return (
|
|
entries.includes(`vendor/${grammarName}/package.json`) ||
|
|
entries.includes(`vendor/${grammarName}`)
|
|
);
|
|
}
|
|
|
|
function filesCoverLeiden(entries) {
|
|
if (entries.includes('vendor') || entries.includes('vendor/leiden')) return true;
|
|
return entries.includes('vendor/leiden/index.cjs') && entries.includes('vendor/leiden/utils.cjs');
|
|
}
|
|
|
|
/**
|
|
* Packed-tarball coverage from `files` globs — not on-disk prebuild counts.
|
|
* Lean publish can leave 6/6 `.node` files in the checkout while omitting
|
|
* them from the pack list.
|
|
*/
|
|
function findPackedFilesProblems({ filesField, grammarNames }) {
|
|
const entries = filesEntries(filesField);
|
|
const problems = [];
|
|
for (const name of grammarNames || []) {
|
|
if (!filesCoverGrammarPrebuilds(entries, name)) {
|
|
problems.push(`${name}: package.json files does not cover vendor/${name}/prebuilds`);
|
|
}
|
|
if (!filesCoverGrammarBindings(entries, name)) {
|
|
problems.push(
|
|
`${name}: package.json files does not cover vendor/${name}/bindings/node/index.js`,
|
|
);
|
|
}
|
|
if (!filesCoverGrammarPackageJson(entries, name)) {
|
|
problems.push(`${name}: package.json files does not cover vendor/${name}/package.json`);
|
|
}
|
|
}
|
|
if (!filesCoverLeiden(entries)) {
|
|
problems.push('package.json files does not cover vendor/leiden/index.cjs and utils.cjs');
|
|
}
|
|
return problems;
|
|
}
|
|
|
|
/** The on-disk source-build inputs for a grammar (relative paths). */
|
|
function sourceBuildSet(grammarDir) {
|
|
return SOURCE_BUILD_REL.filter((rel) => fs.existsSync(path.join(grammarDir, rel)));
|
|
}
|
|
|
|
/** True when a grammar can be source-built from its vendored files (has gyp + parser). */
|
|
function isBuildableFromSource(grammarDir) {
|
|
const set = sourceBuildSet(grammarDir);
|
|
return set.includes('binding.gyp') && set.includes('src/parser.c');
|
|
}
|
|
|
|
/** Count platform-arch tuples with a committed prebuilt .node on disk. */
|
|
function countPrebuiltTuples(grammarDir) {
|
|
const pdir = path.join(grammarDir, 'prebuilds');
|
|
let n = 0;
|
|
for (const t of TUPLES) {
|
|
const td = path.join(pdir, t);
|
|
try {
|
|
if (fs.statSync(td).isDirectory() && fs.readdirSync(td).some((f) => f.endsWith('.node'))) {
|
|
n++;
|
|
}
|
|
} catch {
|
|
/* tuple dir absent — not covered */
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Pure core (exported for tests). `grammars` is a list of
|
|
* `{ name, prebuilt: 0..6, shipsSource: boolean }`. Returns human-readable
|
|
* problem strings; an empty array means the pack is publish-safe.
|
|
*/
|
|
function findCoverageProblems({ grammars }) {
|
|
const problems = [];
|
|
for (const g of grammars) {
|
|
if (g.prebuilt < 6 && !g.shipsSource) {
|
|
const missing = 6 - g.prebuilt;
|
|
problems.push(
|
|
`${g.name}: ${g.prebuilt}/6 prebuilds and its vendored source is not shipped ` +
|
|
`(the package.json \`files\` field excludes it, or it is not buildable) — would ship ` +
|
|
`with no loadable binding on ${missing} platform-arch tuple(s).`,
|
|
);
|
|
}
|
|
}
|
|
return problems;
|
|
}
|
|
|
|
/**
|
|
* Stray local source-build outputs under `vendor/<name>/build/`. These would
|
|
* ship in the tarball (`files: ["vendor"]` overrides .gitignore/.npmignore) AND
|
|
* shadow the committed prebuilds — `node-gyp-build` resolves `build/Release`
|
|
* BEFORE `prebuilds/`, so a consumer on the publisher's platform would load the
|
|
* stray (possibly stale/wrong) binding instead of the curated prebuild. The
|
|
* build dir is gitignored and only appears if a maintainer source-built locally
|
|
* (e.g. on a no-prebuild platform); refuse to publish it. (#2144 review.)
|
|
*/
|
|
function findStrayBuildArtifacts(vendorDir) {
|
|
if (!fs.existsSync(vendorDir)) return [];
|
|
return fs
|
|
.readdirSync(vendorDir)
|
|
.filter((d) => /^tree-sitter-/.test(d))
|
|
.filter((d) => fs.existsSync(path.join(vendorDir, d, 'build')))
|
|
.map((d) => `vendor/${d}/build`);
|
|
}
|
|
|
|
function collectGrammars(vendorDir, shipsVendorSource) {
|
|
if (!fs.existsSync(vendorDir)) return [];
|
|
return fs
|
|
.readdirSync(vendorDir)
|
|
.filter((d) => /^tree-sitter-/.test(d))
|
|
.map((name) => {
|
|
const dir = path.join(vendorDir, name);
|
|
return {
|
|
name,
|
|
prebuilt: countPrebuiltTuples(dir),
|
|
// Source ships when `files` includes the vendor subtree AND the grammar
|
|
// actually carries a buildable source set on disk.
|
|
shipsSource: shipsVendorSource && isBuildableFromSource(dir),
|
|
};
|
|
});
|
|
}
|
|
|
|
function main() {
|
|
const gitnexusRoot = path.join(__dirname, '..');
|
|
const vendorDir = path.join(gitnexusRoot, 'vendor');
|
|
const pkg = JSON.parse(fs.readFileSync(path.join(gitnexusRoot, 'package.json'), 'utf8'));
|
|
const shipsVendorSource = filesShipsVendorSource(pkg.files);
|
|
|
|
const grammars = collectGrammars(vendorDir, shipsVendorSource);
|
|
if (grammars.length === 0) {
|
|
console.error(`[publish-guard] No vendored tree-sitter grammars found under ${vendorDir}.`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const stray = findStrayBuildArtifacts(vendorDir);
|
|
if (stray.length > 0) {
|
|
console.error(
|
|
'[publish-guard] Refusing to publish — stray source-build output under vendor/ would\n' +
|
|
'ship and shadow the committed prebuilds (node-gyp-build loads build/Release before\n' +
|
|
'prebuilds/):',
|
|
);
|
|
for (const s of stray) console.error(` - ${s}`);
|
|
console.error('\nFix: remove it before packing, e.g. `rm -rf gitnexus/vendor/*/build`.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const problems = [
|
|
...findCoverageProblems({ grammars }),
|
|
...findPackedFilesProblems({
|
|
filesField: pkg.files,
|
|
grammarNames: grammars.map((g) => g.name),
|
|
}),
|
|
];
|
|
if (problems.length > 0) {
|
|
console.error('[publish-guard] Refusing to publish — a vendored grammar would ship unusable:');
|
|
for (const p of problems) console.error(` - ${p}`);
|
|
console.error(
|
|
'\nFix: either commit the missing prebuilds (run the build-tree-sitter-prebuilds\n' +
|
|
'workflow) or keep the vendored source in the package.json `files` field.',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const sourceShippers = grammars.filter((g) => g.shipsSource).length;
|
|
console.log(
|
|
`[publish-guard] OK — ${grammars.length} vendored grammar(s) covered ` +
|
|
`(${sourceShippers} shipping source, ${grammars.length - sourceShippers} prebuilds-only).`,
|
|
);
|
|
}
|
|
|
|
if (require.main === module) main();
|
|
|
|
module.exports = {
|
|
findCoverageProblems,
|
|
findPackedFilesProblems,
|
|
findStrayBuildArtifacts,
|
|
filesShipsVendorSource,
|
|
filesCoverGrammarPrebuilds,
|
|
filesCoverGrammarBindings,
|
|
filesCoverGrammarPackageJson,
|
|
filesCoverLeiden,
|
|
isBuildableFromSource,
|
|
sourceBuildSet,
|
|
countPrebuiltTuples,
|
|
collectGrammars,
|
|
TUPLES,
|
|
SOURCE_BUILD_REL,
|
|
};
|