Skip to content

fix: devendor tree-sitter-proto install lifecycle to prevent ENOTEMPTY on global upgrade#846

Merged
magyargergo merged 3 commits into
mainfrom
fix/issue-836-hoist-tree-sitter-proto-deps
Apr 15, 2026
Merged

fix: devendor tree-sitter-proto install lifecycle to prevent ENOTEMPTY on global upgrade#846
magyargergo merged 3 commits into
mainfrom
fix/issue-836-hoist-tree-sitter-proto-deps

Conversation

@magyargergo

@magyargergo magyargergo commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

Closes #836. PR #843 tried to fix this earlier but it did not work. The reporter confirmed the same error still happens on 1.6.2-rc.8.

Why PR #843 did not work

PR #843 added a preinstall script that cleans up leftover files from a previous install. The problem is that preinstall runs on the new package being installed, not on the old one being removed. By the time npm is unpacking the new version, it has already failed trying to remove the old one. The preinstall script never gets a chance to help.

The other change in PR #843 was adding entries to .npmignore. That was a good idea for a different reason (keeping the published tarball clean), but it was not what caused this bug. I verified by downloading the actual 1.6.2-rc.8 tarball from npm and confirming those directories are not in it. The hazard is created at install time, not shipped with the package.

What actually causes the bug

Our package.json declares tree-sitter-proto as a local file dependency that lives under vendor/tree-sitter-proto/. That vendored folder has its own package.json declaring its own dependencies (node-addon-api and node-gyp-build) plus an install script that compiles a native binary.

When npm processes a file dependency, it treats it like any other package. It installs the transitive dependencies into a nested node_modules folder inside vendor/tree-sitter-proto/, then runs the install script there, which writes a build/ folder in the same place.

When a user runs npm install -g gitnexus@new over an existing global install, npm first tries to remove the old gitnexus directory. On macOS it cannot remove vendor/tree-sitter-proto/node_modules/node-addon-api cleanly and aborts with ENOTEMPTY.

Every other tree-sitter grammar we use (c, cpp, python, rust, swift, kotlin, dart, and so on) avoids this because they install into node_modules/, which npm manages and knows how to clean up. tree-sitter-proto was the only one writing into the vendor/ folder.

The fix

The goal is simple. Nothing should write anything under vendor/ during install. That is the same invariant every other grammar in the project already follows.

Step 1. In vendor/tree-sitter-proto/package.json, remove the dependencies block and remove the install script. Without those, npm has no reason to create a nested node_modules or run anything in the vendor folder.

Step 2. In gitnexus/package.json, move node-addon-api and node-gyp-build into optionalDependencies at the top level. They resolve from gitnexus/node_modules/ the normal way. The version ranges match what the vendored package declared before, so nothing changes about which versions get installed.

Step 3. Add a new postinstall script, scripts/build-tree-sitter-proto.cjs. It compiles the native binary by running node-gyp-build inside node_modules/tree-sitter-proto/ (which npm manages), never under vendor/. The script is modeled on the existing patch-tree-sitter-swift.cjs and follows the same best effort pattern: if the compiler toolchain is missing, or the user installed with --ignore-scripts, or the optional dependency was skipped, the script warns and exits cleanly. Non-proto functionality keeps working.

Step 4. Delete scripts/preinstall-cleanup.cjs. It was dead code and keeping it around made it look like the bug was fixed.

Step 5. In gitnexus/package.json, chain the new script after the Swift patch in postinstall, and remove the preinstall entry. The .npmignore entries from PR #843 stay because they are still good defense in depth.

Step 6. Add two rules to the root .gitignore for gitnexus/vendor/**/build and gitnexus/vendor/**/node_modules. The generic node_modules rule already covered the nested case, but build was not explicitly ignored. I checked git history and those paths have never been accidentally committed, so this is preventative.

Step 7. Add a new CI workflow, ci-global-upgrade.yml. It runs a matrix across macOS, Ubuntu, and Windows that installs the previously published 1.6.2-rc.8 globally, packs the current branch, installs the packed tarball over the old version, and verifies the CLI still works. It also checks that vendor/tree-sitter-proto/node_modules and vendor/tree-sitter-proto/build do not exist after install. A second lane installs with --ignore-scripts and confirms the CLI boots in degraded mode. This workflow is wired into ci.yml as a required gate.

How I verified locally on Windows

  • A clean npm install produces no node_modules or build folder inside vendor/tree-sitter-proto/. Only source files remain.
  • npm pack produces a tarball that contains no vendor/tree-sitter-proto/node_modules or /build paths.
  • Installing the packed tarball globally creates vendor/tree-sitter-proto/ as a real extracted directory. The hoisted build deps land in gitnexus/node_modules/ as expected.
  • The postinstall script compiles the binding, and require('tree-sitter-proto') loads with the grammar name 'proto' and populated nodeTypeInfo.
  • The script is idempotent (a second run is a no-op). The --ignore-scripts path exits cleanly with a warning.
  • npx tsc --noEmit passes.

About the ABI

Worth noting because it almost sent me down the wrong path. gitnexus runs tree-sitter 0.21.1, which can only load grammars built with ABI 13 or 14. Every grammar in the project is ABI 14, including the vendored tree-sitter-proto. I briefly considered switching to upstream coder3101/tree-sitter-proto via git URL, but their current main is now ABI 15, which would not load. So vendoring the source and compiling at install time is the right shape for our current runtime. When we upgrade tree-sitter to 0.22 or later, that changes.

What comes later

The 2026 standard for tree-sitter grammars is to ship prebuilt native binaries in the npm tarball using prebuildify. Every first party tree-sitter grammar does this. Upstream tree-sitter-proto is set up for it but has never actually been published to npm. Migrating us to that shape would eliminate the install-time compile entirely, including the --ignore-scripts edge case. It needs a platform matrix in CI and a binary publication workflow, so it is a bigger change and I am saving it for a follow-up.

Test plan

  • Clean install produces no install-time artifacts under vendor/
  • Packed tarball contains no vendor/**/node_modules or vendor/**/build paths
  • Global install and upgrade smoke works on Windows
  • require('tree-sitter-proto') loads and exposes nodeTypeInfo after postinstall
  • Install with --ignore-scripts succeeds and the postinstall script exits 0 with a warning
  • npx tsc --noEmit passes
  • The new ci-global-upgrade job passes on macOS, Ubuntu, and Windows
  • @magyargergo validates the upgrade from 1.6.2-rc.8 on macOS once a new rc is published

… global upgrade

PR #843's preinstall cleanup hook cannot address the reported bug because
it runs on the NEW package's staging tree, not the OLD install being
removed. Issue #836 still reproduces on 1.6.2-rc.8.

Root cause: vendor/tree-sitter-proto was declared as `file:` dep with its
own `dependencies` and `install` script, so npm created
`vendor/tree-sitter-proto/node_modules/node-addon-api/` at install time,
which blocked npm's rmdir on global upgrade.

Changes:
- Strip `dependencies` and `install` script from the vendored sub-package's
  package.json so npm no longer creates a nested node_modules or runs a
  lifecycle script under vendor/.
- Hoist `node-addon-api` and `node-gyp-build` into gitnexus
  optionalDependencies; npm resolves them at the consumer's top level.
- Add scripts/build-tree-sitter-proto.cjs modeled on patch-tree-sitter-swift.cjs.
  Runs at gitnexus postinstall, best-effort: skips cleanly on missing
  toolchain or --ignore-scripts so non-proto functionality keeps working.
- Remove scripts/preinstall-cleanup.cjs — dead code; cannot run against
  the old install being removed.
- Keep .npmignore entries from PR #843 (tarball hygiene, still correct).
- Add explicit .gitignore rules for gitnexus/vendor/**/build and
  gitnexus/vendor/**/node_modules (closes the repo-side hygiene gap).
- Add .github/workflows/ci-global-upgrade.yml: matrix smoke test that
  installs the previously-published rc globally, upgrades to the packed
  current branch, and verifies no vendor install-time artifacts survive.
  Runs on macOS (reporter's platform), Linux, and Windows. Also includes
  an --ignore-scripts degraded-mode lane. Wired into ci.yml gate.

Plan: docs/plans/2026-04-15-002-fix-tree-sitter-proto-vendor-deps-plan.md

Phase 1 (this commit) addresses the reported `node_modules/node-addon-api`
hazard. Phase 2 (follow-up) will migrate to prebuildify + prebuilt .node
binaries in the tarball — the 2026 canonical shape for tree-sitter
grammars, which eliminates the postinstall compile path entirely.

Refs #836
@vercel

vercel Bot commented Apr 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gitnexus Ready Ready Preview, Comment Apr 15, 2026 4:22pm

Request Review

@github-actions github-actions Bot added the bug Something isn't working label Apr 15, 2026
…exus

Three issues caught by CI on PR #846:

1. Concurrency linter rejected the `CIGU-` prefix (allowlist is
   `${{ github.workflow }}` or substring `CI-`). The literal-prefix
   guidance in ci.yml is specifically about disambiguating when
   reusable workflows run in nested contexts, and ci-global-upgrade
   doesn't need its own concurrency block at all — the caller
   (ci.yml) already governs concurrency for nested invocations.

2. `npm install` in gitnexus/ runs `prepare: node scripts/build.js`,
   which depends on gitnexus-shared/dist being built first. Other CI
   jobs handle this via the setup-gitnexus composite action. Use it
   here too (with build: 'false' — we only need the dep graph, then
   npm pack runs prepack which builds gitnexus itself).

3. Removed `pull_request` and `workflow_dispatch` triggers. The
   workflow is now pure `workflow_call` — invoked once from ci.yml
   via `uses:`. This avoids the duplicate-run problem where both the
   top-level pull_request trigger AND the nested workflow_call would
   fire on every PR.
Two fixes for ci-global-upgrade failures on PR #846:

1. The guard after the upgrade step was rejecting vendor/tree-sitter-proto/build/
   in the global install. That was too strict. The original #836 bug was
   about vendor/tree-sitter-proto/node_modules/ specifically, not build/.
   The build/ directory appears because node-gyp-build compiles through the
   symlink npm creates at node_modules/gitnexus/node_modules/tree-sitter-proto,
   and its contents are plain .node, .obj, .lib files that rmdir handles
   without trouble. We know this empirically because the test got past the
   upgrade step in the run where the old vendor/node_modules was present.
   The guard now only flags nested node_modules, which is what the fix
   actually removes.

2. The Windows --ignore-scripts lane failed with ENOENT when npm tried to
   open the tarball. The path was computed in a bash step using $(pwd),
   which on Windows returns /d/a/... form, but npm install ran in the
   default cmd shell and received a mangled Windows path. Adding
   shell: bash to the install steps keeps path handling consistent.
@github-actions

Copy link
Copy Markdown
Contributor

CI Report

All checks passed

Pipeline Status

Stage Status Details
✅ Typecheck success tsc --noEmit
✅ Tests success unit tests, 3 platforms
✅ E2E success gitnexus-web changes only

Test Results

Tests Passed Failed Skipped Duration
6354 6257 0 97 245s

✅ All 6257 tests passed

97 test(s) skipped — expand for details
  • Swift MethodExtractor > isTypeDeclaration > recognizes class_declaration
  • Swift MethodExtractor > isTypeDeclaration > recognizes protocol_declaration
  • Swift MethodExtractor > isTypeDeclaration > rejects import_declaration
  • Swift MethodExtractor > visibility > extracts public method
  • Swift MethodExtractor > visibility > extracts private method
  • Swift MethodExtractor > visibility > defaults to internal when no modifier
  • Swift MethodExtractor > protocol methods > marks protocol method as abstract
  • Swift MethodExtractor > static and class methods > detects static func as isStatic
  • Swift MethodExtractor > static and class methods > detects class func as isStatic
  • Swift MethodExtractor > parameters > extracts parameters with types and default values
  • Swift MethodExtractor > return type > extracts return type from -> annotation
  • Swift MethodExtractor > annotations > extracts @objc attribute
  • Swift MethodExtractor > isFinal > detects final func
  • Swift MethodExtractor > isFinal > is false when not final
  • Swift MethodExtractor > isAsync > detects async func
  • Swift MethodExtractor > isOverride > detects override method
  • buildTypeEnv > constructor inference (Tier 1 fallback) > lookupClassByName regression coverage > Swift lookupClassByName regression coverage > Swift cross-file constructor inference uses lookupClassByName
  • buildTypeEnv > constructor inference (Tier 1 fallback) > lookupClassByName regression coverage > Swift lookupClassByName regression coverage > Swift explicit init inference uses lookupClassByName
  • buildTypeEnv > constructor inference (Tier 1 fallback) > lookupClassByName regression coverage > Swift lookupClassByName regression coverage > Swift cross-file constructor inference does not bind plain functions
  • buildTypeEnv > known limitations (documented skip tests) > Ruby block parameter: users.each { |user| } — closure param inference, different feature
  • Swift constructor-inferred type resolution > detects User and Repo classes, both with save methods
  • Swift constructor-inferred type resolution > resolves user.save() to Models/User.swift via constructor-inferred type
  • Swift constructor-inferred type resolution > resolves repo.save() to Models/Repo.swift via constructor-inferred type
  • Swift constructor-inferred type resolution > emits exactly 2 save() CALLS edges (one per receiver type)
  • Swift self resolution > detects User and Repo classes, each with a save function
  • Swift self resolution > resolves self.save() inside User.process to User.save, not Repo.save
  • Swift parent resolution > detects BaseModel and User classes plus Serializable protocol
  • Swift parent resolution > emits EXTENDS edge: User → BaseModel
  • Swift parent resolution > emits IMPLEMENTS edge: User → Serializable (protocol conformance)
  • Swift cross-file User.init() inference > resolves user.save() via User.init(name:) inference
  • Swift cross-file User.init() inference > resolves user.greet() via User.init(name:) inference
  • Swift return type inference > detects User class and getUser function
  • Swift return type inference > detects save function on User (Swift class methods are Function nodes)
  • Swift return type inference > resolves user.save() to User#save via return type of getUser() -> User
  • Swift return-type inference via function return type > resolves user.save() to User#save via return type of getUser()
  • Swift return-type inference via function return type > user.save() does NOT resolve to Repo#save
  • Swift return-type inference via function return type > resolves repo.save() to Repo#save via return type of getRepo()
  • Swift implicit imports (cross-file visibility) > detects UserService class in Models.swift
  • Swift implicit imports (cross-file visibility) > resolves UserService() constructor call across files (no explicit import)
  • Swift implicit imports (cross-file visibility) > resolves service.fetchUser() member call across files
  • Swift implicit imports (cross-file visibility) > creates IMPORTS edges between files in the same module
  • Swift extension deduplication > detects Product class
  • Swift extension deduplication > resolves Product() constructor despite extension creating duplicate class node
  • Swift extension deduplication > resolves product.save() to Product.swift (primary definition)
  • Swift constructor call fallback (no new keyword) > resolves OCRService() as constructor call across files
  • Swift constructor call fallback (no new keyword) > resolves ocr.recognize() member call via constructor-inferred type
  • Swift export visibility (internal vs private) > resolves PublicService() constructor across files
  • Swift export visibility (internal vs private) > resolves internalHelper() across files (internal = module-scoped)
  • Swift if let / guard let binding resolution > detects User and Repo classes
  • Swift if let / guard let binding resolution > resolves user.save() inside if-let to User#save
  • Swift if let / guard let binding resolution > resolves repo.save() inside guard-let to Repo#save
  • Swift if let / guard let binding resolution > user.save() in if-let does NOT resolve to Repo#save
  • Swift await / try expression unwrapping > resolves user.save() via await fetchUser() return type
  • Swift await / try expression unwrapping > resolves repo.save() via try parseRepo() return type
  • Swift await / try expression unwrapping > detects fetchUser and parseRepo as functions
  • Swift for-in loop element type inference > detects User and Repo classes
  • Swift for-in loop element type inference > creates implicit import edges between files
  • Swift field-type resolution > detects classes and their properties
  • Swift field-type resolution > emits HAS_PROPERTY edges from class to field
  • Swift field-type resolution > resolves field-chain call user.address.save() → Address#save
  • Swift field-type resolution > emits ACCESSES edges for field reads in chains
  • Swift field-type resolution > populates field metadata (visibility, declaredType) on Property nodes
  • Swift call-result binding > resolves call-result-bound method call user.save() → User#save
  • Swift call-result binding > getUser() is present as a defined function
  • Swift call-result binding > emits processUser -> getUser CALLS edge for let-assigned free function call
  • Swift method enrichment > detects Animal protocol and Dog class
  • Swift method enrichment > emits IMPLEMENTS edge Dog -> Animal
  • Swift method enrichment > emits HAS_METHOD edges for Dog methods
  • Swift method enrichment > marks protocol Animal.speak as isAbstract
  • Swift method enrichment > marks Dog.speak as NOT isAbstract
  • Swift method enrichment > marks breathe as isFinal
  • Swift method enrichment > marks classify as isStatic
  • Swift method enrichment > captures @objc annotation on breathe
  • Swift method enrichment > populates parameterTypes for classify(_ name: String)
  • Swift method enrichment > records parameterCount for classify
  • Swift method enrichment > records returnType for speak
  • Swift method enrichment > resolves dog.speak() CALLS edge
  • Swift method enrichment > resolves Dog.classify("dog") CALLS edge
  • Swift abstract dispatch > detects Repository protocol and SqlRepository class
  • Swift abstract dispatch > emits IMPLEMENTS edge SqlRepository -> Repository
  • Swift abstract dispatch > emits HAS_METHOD edges for Repository.find and Repository.save
  • Swift abstract dispatch > emits HAS_METHOD edges for SqlRepository.find and SqlRepository.save
  • Swift abstract dispatch > marks base Repository.find as isAbstract
  • Swift abstract dispatch > marks base Repository.save as isAbstract
  • Swift abstract dispatch > marks concrete SqlRepository.find as NOT isAbstract
  • Swift abstract dispatch > resolves repo.find(id: 42) CALLS edge
  • Swift abstract dispatch > resolves repo.save(entity: user) CALLS edge
  • Swift abstract dispatch > populates parameterTypes for Repository.find
  • Swift abstract dispatch > populates parameterTypes for Repository.save
  • Swift abstract dispatch > records returnType for SqlRepository.find
  • Swift abstract dispatch > emits METHOD_IMPLEMENTS edges from SqlRepository methods → Repository protocol methods
  • Swift overloaded method disambiguation > detects 2 distinct find Method nodes on SqlRepository
  • Swift overloaded method disambiguation > emits METHOD_IMPLEMENTS edges for both find overloads
  • Swift overloaded method disambiguation > emits METHOD_IMPLEMENTS edge for save
  • Swift overloaded method disambiguation > emits exactly 3 METHOD_IMPLEMENTS edges total
  • Swift Child extends Parent — inherited method resolution (SM-9) > detects Parent and Child classes
  • Swift Child extends Parent — inherited method resolution (SM-9) > resolves c.parentMethod() to Parent.parentMethod via first-wins MRO walk

Code Coverage

Tests

Metric Coverage Covered Base Delta Status
Statements 73.23% 16993/23204 73.18% 📈 +0.0 🟢 ██████████████░░░░░░
Branches 61.79% 10772/17433 61.77% 📈 +0.0 🟢 ████████████░░░░░░░░
Functions 78.92% 1595/2021 78.92% = 0.0 🟢 ███████████████░░░░░
Lines 75.65% 15438/20407 75.59% 📈 +0.1 🟢 ███████████████░░░░░

📋 View full run · Generated by CI

@magyargergo

Copy link
Copy Markdown
Collaborator Author

@claude review this solution

@github-actions

github-actions Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 1m 57s —— View job


Code Review

  • Gather context
  • Read changed files
  • Review build script
  • Review CI workflow
  • Review package.json changes
  • Post review findings

This is a well-reasoned fix. The root cause diagnosis is correct: npm's file: dependency handling creates nested artifacts inside vendor/ during install, and those directories block rmdir on global upgrade. The approach — stripping dependencies/install from the vendored package and hoisting build deps into the top-level optionalDependencies — is the right shape for this constraint.

What's good

The _vendoredBy annotation in vendor/tree-sitter-proto/package.json (line 8) is excellent defensive documentation. It explicitly names the original bug and explains why the deps block must not be re-added. This will prevent the same regression from being reintroduced.

The build-tree-sitter-proto.cjs script correctly mirrors the best-effort pattern from patch-tree-sitter-swift.cjs. All the important edge cases are handled: absent optional dep, missing build toolchain, --ignore-scripts path, and idempotency.

The CI workflow is a meaningful addition. The matrix across all three platforms, the --ignore-scripts degraded-mode lane, and the explicit check for vendor/.../node_modules are all appropriate. The comments explaining why each step exists are clear.

The preinstall-cleanup.cjs deletion is correct. Keeping dead code that suggested the bug was already fixed would have been actively misleading.


Issues and observations

1. Build failure output is swallowed (build-tree-sitter-proto.cjs, line 76)

When execSync throws, err.message typically just says Command failed: npx node-gyp rebuild. The actual compiler output is on err.stdout/err.stderr and is not logged. A user who hits a real compiler failure (missing Python, wrong Xcode SDK, etc.) will see no useful diagnostic. Contrast with the Swift script which has the same limitation but the user has a manual fallback printed; the proto script's fallback only says "install without --no-optional". Logging err.stdout and err.stderr on failure (at warn level) would make this much easier to debug.

Fix this →

2. Vendor package.json description says "compatible with tree-sitter 0.25" (vendor/tree-sitter-proto/package.json, line 4)

The description reads: "ABI 14 build from coder3101/tree-sitter-proto latest grammar.js, compatible with tree-sitter 0.25". Gitnexus currently runs tree-sitter 0.21.1. The PR description explicitly notes that upstream main is now ABI 15 and would not load with 0.21. "Compatible with tree-sitter 0.25" is therefore either wrong or at least confusing for future maintainers. It should say "compatible with tree-sitter 0.21.x (ABI 14)" to be accurate.

3. CI comment about build/ appearing on global install is inaccurate (ci-global-upgrade.yml, lines 66–69)

The comment says vendor/tree-sitter-proto/build/ "can still appear because node-gyp-build compiles through the npm-created symlink". This is true for local installs where npm symlinks file: deps, but not for npm install -g where npm copies the package. In the global install scenario under test, the build output will land in node_modules/tree-sitter-proto/build/ (a separate copy), not in vendor/. The comment could mislead someone investigating a future issue into thinking a build/ artifact under vendor/ is expected in a global install.

4. ignore-scripts lane does not assert degraded-mode behavior

The lane installs with --ignore-scripts, runs gitnexus --version, and calls it done. This verifies the CLI boots, but it doesn't assert that proto parsing is actually unavailable (or at least fails gracefully rather than hard-crashing). If a future change accidentally makes the proto loader throw an uncaught error when the binding is absent, --version would still pass. This is a low-priority improvement, not a blocker.

5. Minor: timeout inconsistency between build scripts

build-tree-sitter-proto.cjs uses timeout: 180000 (3 min) while patch-tree-sitter-swift.cjs uses timeout: 120000 (2 min). Three minutes is more generous for a full native compile. The difference is deliberate and reasonable — just worth confirming intentional.


Summary

The fix is correct, the approach is sound, and the CI addition is a genuine improvement that fills a real gap. The issues above are minor: swallowed build output (#1) and the inaccurate vendor description (#2) are the most likely to cause real confusion later. None are blockers. Overall this is a clean, well-documented solution.
· Branch

@magyargergo magyargergo merged commit fec06b8 into main Apr 15, 2026
20 checks passed
magyargergo added a commit that referenced this pull request Apr 18, 2026
Bumps gitnexus to v1.6.2 and adds the matching CHANGELOG entry.

Highlights since v1.6.1 (61 commits):
  - Docker support (#848)
  - Language-agnostic heritage / call / variable extractors
    (config+factory pattern, #877 #878 #890)
  - AST-aware embedding chunking (#889)
  - jQuery / axios HTTP consumer detection (#887)
  - SemanticModel wired as first-class resolution input, SM-20 (#885)
  - ImportSemantics split into per-strategy hooks (#886)
  - Python dotted-import fix (#899); worker warnings non-terminal
    (#900 / #261); global-install ENOTEMPTY fixes (#843 #846);
    embeddings staleness fix (#831)

See gitnexus/CHANGELOG.md for the full list.

After merge, tag `v1.6.2` triggers publish.yml which runs CI,
verifies tag↔package.json match, publishes to npm with provenance,
and creates the GitHub Release using the extracted CHANGELOG body.
@magyargergo magyargergo mentioned this pull request Apr 18, 2026
5 tasks
magyargergo added a commit that referenced this pull request Apr 18, 2026
Bumps gitnexus to v1.6.2 and adds the matching CHANGELOG entry.

Highlights since v1.6.1 (61 commits):
  - Docker support (#848)
  - Language-agnostic heritage / call / variable extractors
    (config+factory pattern, #877 #878 #890)
  - AST-aware embedding chunking (#889)
  - jQuery / axios HTTP consumer detection (#887)
  - SemanticModel wired as first-class resolution input, SM-20 (#885)
  - ImportSemantics split into per-strategy hooks (#886)
  - Python dotted-import fix (#899); worker warnings non-terminal
    (#900 / #261); global-install ENOTEMPTY fixes (#843 #846);
    embeddings staleness fix (#831)

See gitnexus/CHANGELOG.md for the full list.

After merge, tag `v1.6.2` triggers publish.yml which runs CI,
verifies tag↔package.json match, publishes to npm with provenance,
and creates the GitHub Release using the extracted CHANGELOG body.
github714801013 pushed a commit to github714801013/GitNexus that referenced this pull request Apr 28, 2026
…Y on global upgrade (abhigyanpatwari#846)

* fix: devendor tree-sitter-proto install lifecycle to fix ENOTEMPTY on global upgrade

PR abhigyanpatwari#843's preinstall cleanup hook cannot address the reported bug because
it runs on the NEW package's staging tree, not the OLD install being
removed. Issue abhigyanpatwari#836 still reproduces on 1.6.2-rc.8.

Root cause: vendor/tree-sitter-proto was declared as `file:` dep with its
own `dependencies` and `install` script, so npm created
`vendor/tree-sitter-proto/node_modules/node-addon-api/` at install time,
which blocked npm's rmdir on global upgrade.

Changes:
- Strip `dependencies` and `install` script from the vendored sub-package's
  package.json so npm no longer creates a nested node_modules or runs a
  lifecycle script under vendor/.
- Hoist `node-addon-api` and `node-gyp-build` into gitnexus
  optionalDependencies; npm resolves them at the consumer's top level.
- Add scripts/build-tree-sitter-proto.cjs modeled on patch-tree-sitter-swift.cjs.
  Runs at gitnexus postinstall, best-effort: skips cleanly on missing
  toolchain or --ignore-scripts so non-proto functionality keeps working.
- Remove scripts/preinstall-cleanup.cjs — dead code; cannot run against
  the old install being removed.
- Keep .npmignore entries from PR abhigyanpatwari#843 (tarball hygiene, still correct).
- Add explicit .gitignore rules for gitnexus/vendor/**/build and
  gitnexus/vendor/**/node_modules (closes the repo-side hygiene gap).
- Add .github/workflows/ci-global-upgrade.yml: matrix smoke test that
  installs the previously-published rc globally, upgrades to the packed
  current branch, and verifies no vendor install-time artifacts survive.
  Runs on macOS (reporter's platform), Linux, and Windows. Also includes
  an --ignore-scripts degraded-mode lane. Wired into ci.yml gate.

Plan: docs/plans/2026-04-15-002-fix-tree-sitter-proto-vendor-deps-plan.md

Phase 1 (this commit) addresses the reported `node_modules/node-addon-api`
hazard. Phase 2 (follow-up) will migrate to prebuildify + prebuilt .node
binaries in the tarball — the 2026 canonical shape for tree-sitter
grammars, which eliminates the postinstall compile path entirely.

Refs abhigyanpatwari#836

* fix(ci): ci-global-upgrade should be reusable-only and use setup-gitnexus

Three issues caught by CI on PR abhigyanpatwari#846:

1. Concurrency linter rejected the `CIGU-` prefix (allowlist is
   `${{ github.workflow }}` or substring `CI-`). The literal-prefix
   guidance in ci.yml is specifically about disambiguating when
   reusable workflows run in nested contexts, and ci-global-upgrade
   doesn't need its own concurrency block at all — the caller
   (ci.yml) already governs concurrency for nested invocations.

2. `npm install` in gitnexus/ runs `prepare: node scripts/build.js`,
   which depends on gitnexus-shared/dist being built first. Other CI
   jobs handle this via the setup-gitnexus composite action. Use it
   here too (with build: 'false' — we only need the dep graph, then
   npm pack runs prepack which builds gitnexus itself).

3. Removed `pull_request` and `workflow_dispatch` triggers. The
   workflow is now pure `workflow_call` — invoked once from ci.yml
   via `uses:`. This avoids the duplicate-run problem where both the
   top-level pull_request trigger AND the nested workflow_call would
   fire on every PR.

* fix(ci): relax vendor build/ guard and use bash shell on Windows

Two fixes for ci-global-upgrade failures on PR abhigyanpatwari#846:

1. The guard after the upgrade step was rejecting vendor/tree-sitter-proto/build/
   in the global install. That was too strict. The original abhigyanpatwari#836 bug was
   about vendor/tree-sitter-proto/node_modules/ specifically, not build/.
   The build/ directory appears because node-gyp-build compiles through the
   symlink npm creates at node_modules/gitnexus/node_modules/tree-sitter-proto,
   and its contents are plain .node, .obj, .lib files that rmdir handles
   without trouble. We know this empirically because the test got past the
   upgrade step in the run where the old vendor/node_modules was present.
   The guard now only flags nested node_modules, which is what the fix
   actually removes.

2. The Windows --ignore-scripts lane failed with ENOENT when npm tried to
   open the tarball. The path was computed in a bash step using $(pwd),
   which on Windows returns /d/a/... form, but npm install ran in the
   default cmd shell and received a mangled Windows path. Adding
   shell: bash to the install steps keeps path handling consistent.
github714801013 pushed a commit to github714801013/GitNexus that referenced this pull request Apr 28, 2026
Bumps gitnexus to v1.6.2 and adds the matching CHANGELOG entry.

Highlights since v1.6.1 (61 commits):
  - Docker support (abhigyanpatwari#848)
  - Language-agnostic heritage / call / variable extractors
    (config+factory pattern, abhigyanpatwari#877 abhigyanpatwari#878 abhigyanpatwari#890)
  - AST-aware embedding chunking (abhigyanpatwari#889)
  - jQuery / axios HTTP consumer detection (abhigyanpatwari#887)
  - SemanticModel wired as first-class resolution input, SM-20 (abhigyanpatwari#885)
  - ImportSemantics split into per-strategy hooks (abhigyanpatwari#886)
  - Python dotted-import fix (abhigyanpatwari#899); worker warnings non-terminal
    (abhigyanpatwari#900 / abhigyanpatwari#261); global-install ENOTEMPTY fixes (abhigyanpatwari#843 abhigyanpatwari#846);
    embeddings staleness fix (abhigyanpatwari#831)

See gitnexus/CHANGELOG.md for the full list.

After merge, tag `v1.6.2` triggers publish.yml which runs CI,
verifies tag↔package.json match, publishes to npm with provenance,
and creates the GitHub Release using the extracted CHANGELOG body.
@magyargergo magyargergo deleted the fix/issue-836-hoist-tree-sitter-proto-deps branch May 21, 2026 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ENOTEMPTY error when upgrading gitnexus globally (node-addon-api rmdir failure)

1 participant