From 7fe3eaa19a856458873f31cfd9b26b3ed4e92dae Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 15:00:28 -0700 Subject: [PATCH 01/14] chore: use upstream tree-sitter runtime --- .changeset/clean-tree-sitter-runtime.md | 7 + packages/core/README.md | 2 +- packages/core/package.json | 4 +- packages/core/src/cli/doctor/command.ts | 6 +- .../core/tests/cli/doctor/command.test.ts | 2 +- packages/extension/docs/README.md | 2 +- packages/extension/package.json | 2 +- packages/tldraw/package.json | 2 +- patches/README.md | 13 -- patches/tree-sitter@0.25.0.patch | 29 --- pnpm-lock.yaml | 177 +++++++++--------- pnpm-workspace.yaml | 2 - scripts/validate-vsix-native-artifacts.mjs | 8 +- tests/release/corePackage.test.mjs | 2 +- tests/release/vsixNativeArtifacts.test.mjs | 13 +- 15 files changed, 122 insertions(+), 149 deletions(-) create mode 100644 .changeset/clean-tree-sitter-runtime.md delete mode 100644 patches/README.md delete mode 100644 patches/tree-sitter@0.25.0.patch diff --git a/.changeset/clean-tree-sitter-runtime.md b/.changeset/clean-tree-sitter-runtime.md new file mode 100644 index 0000000000..2fecb05a9e --- /dev/null +++ b/.changeset/clean-tree-sitter-runtime.md @@ -0,0 +1,7 @@ +--- +"@codegraphy-dev/core": patch +"@codegraphy-dev/extension": patch +"@codegraphy-dev/tldraw": patch +--- + +Use the upstream Tree-sitter release with Node 23 and newer support instead of the temporary local native-build patch. diff --git a/packages/core/README.md b/packages/core/README.md index bb6c39fb57..d448463787 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -4,7 +4,7 @@ Shared CodeGraphy engine package for workspace indexing, Graph Cache access, plu This package is the headless core used by the VS Code extension and CLI. -The published CLI currently supports Node.js 20 through 22; Node 22 LTS is recommended. Node 23 and newer require an upstream Tree-sitter native build that is not yet available to npm consumers. +The published CLI supports Node.js 20 and newer. An active Long-Term Support release is recommended. The VS Code extension bundles this package for extension runtime behavior. Users install `@codegraphy-dev/core` globally only when they want terminal workflows such as Indexing, diagnostics, graph queries, Graph Scope and filter configuration, plugin registration, or workspace plugin enablement. diff --git a/packages/core/package.json b/packages/core/package.json index 8c5a310552..1e7cfbbe13 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -28,7 +28,7 @@ } }, "engines": { - "node": ">=20 <23" + "node": ">=20" }, "publishConfig": { "access": "public" @@ -53,7 +53,7 @@ "@tree-sitter-grammars/tree-sitter-lua": "0.4.1", "libsql": "0.5.29", "minimatch": "^10.2.5", - "tree-sitter": "^0.25.0", + "tree-sitter": "^0.25.1", "tree-sitter-c": "0.24.1", "tree-sitter-c-sharp": "0.23.1", "tree-sitter-cpp": "0.23.4", diff --git a/packages/core/src/cli/doctor/command.ts b/packages/core/src/cli/doctor/command.ts index d85d8657c5..599fd3ea13 100644 --- a/packages/core/src/cli/doctor/command.ts +++ b/packages/core/src/cli/doctor/command.ts @@ -93,7 +93,7 @@ function readSettingsCheck(workspaceRoot: string): Record { export function runDoctorCommand(command: CliCommand): CommandExecutionResult { const workspaceRoot = resolveCodeGraphyWorkspacePath(command.workspacePath, process.cwd()); const runtimeMajor = Number(process.versions.node.split('.')[0]); - const runtimeOk = runtimeMajor >= 20 && runtimeMajor < 23; + const runtimeOk = runtimeMajor >= 20; const settingsCheck = readSettingsCheck(workspaceRoot); const status = readCodeGraphyWorkspaceStatus(workspaceRoot); const meta = readCodeGraphyWorkspaceMeta(workspaceRoot); @@ -108,8 +108,8 @@ export function runDoctorCommand(command: CliCommand): CommandExecutionResult { runtime: { ok: runtimeOk, version: process.version, - supported: '>=20 <23', - ...(runtimeOk ? {} : { action: 'Use Node.js 20, 21, or 22.' }), + supported: '>=20', + ...(runtimeOk ? {} : { action: 'Use Node.js 20 or newer.' }), }, settings: settingsCheck, cache: createDoctorCacheCheck({ diff --git a/packages/core/tests/cli/doctor/command.test.ts b/packages/core/tests/cli/doctor/command.test.ts index e3231b9fdd..f05d757297 100644 --- a/packages/core/tests/cli/doctor/command.test.ts +++ b/packages/core/tests/cli/doctor/command.test.ts @@ -166,7 +166,7 @@ describe('cli doctor', () => { details: { healthy: false, checks: { - runtime: { supported: expect.any(String) }, + runtime: { ok: true, supported: '>=20' }, settings: { ok: false, action: 'Run `codegraphy index` to create workspace settings.' }, cache: { ok: false, state: 'missing', action: 'Run `codegraphy index`.' }, plugins: { ok: true, warnings: [] }, diff --git a/packages/extension/docs/README.md b/packages/extension/docs/README.md index 8a47afaafd..17374c5ba8 100644 --- a/packages/extension/docs/README.md +++ b/packages/extension/docs/README.md @@ -18,7 +18,7 @@ Current extension behavior to keep in mind while reading the package docs: - The extension loads workspace-local CSS Snippets listed under `cssSnippets` in `.codegraphy/settings.json`. They can style stable `data-codegraphy-*` hooks without rebuilding a VS Code theme. - the language plugins in `packages/plugin-*` are now mostly for ecosystem filters and optional semantic enrichment rather than baseline file coloring -The published CLI supports Node 20 through 22. Local tooling and CI use the repository-pinned Node runtime. +The published CLI supports Node 20 and newer. Local tooling and CI use the repository-pinned Node runtime. The source tree is split by runtime boundary: diff --git a/packages/extension/package.json b/packages/extension/package.json index 2f6a7cd3ce..4f93b7117f 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -64,7 +64,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "tailwind-merge": "^3.4.0", - "tree-sitter": "^0.25.0", + "tree-sitter": "^0.25.1", "tree-sitter-c": "0.24.1", "tree-sitter-c-sharp": "0.23.1", "tree-sitter-cpp": "0.23.4", diff --git a/packages/tldraw/package.json b/packages/tldraw/package.json index efd2ff43ff..d540abc6a8 100644 --- a/packages/tldraw/package.json +++ b/packages/tldraw/package.json @@ -24,7 +24,7 @@ "codegraphy-tldraw": "./dist/codegraphy-tldraw.js" }, "engines": { - "node": ">=20 <23" + "node": ">=20" }, "os": [ "darwin" diff --git a/patches/README.md b/patches/README.md deleted file mode 100644 index 34fb93f880..0000000000 --- a/patches/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Dependency Patches - -## `tree-sitter@0.25.0` - -CodeGraphy carries a temporary pnpm patch for `tree-sitter@0.25.0` so the native Node binding builds with C++20 on Node 23 and newer. - -Upstream has already merged the equivalent fix in tree-sitter/node-tree-sitter#258, but the fixed npm release is not currently published. The follow-up issue is: - -https://github.com/tree-sitter/node-tree-sitter/issues/276 - -Remove this patch once `tree-sitter` publishes a release that includes the Node 23+ C++20 build fix, then replace it with a normal dependency update. - -This pnpm patch only applies inside this workspace; npm does not propagate a package publisher's pnpm patches to consumers. Until the upstream release is available, `@codegraphy-dev/core` therefore declares Node 20 through 22 as its supported CLI range. diff --git a/patches/tree-sitter@0.25.0.patch b/patches/tree-sitter@0.25.0.patch deleted file mode 100644 index 3e658b0cd2..0000000000 --- a/patches/tree-sitter@0.25.0.patch +++ /dev/null @@ -1,29 +0,0 @@ -diff --git a/binding.gyp b/binding.gyp -index bb40aa7e8d41644ba690761e9064fc1d0f2dcd6b..1e1641b2e3d6733642ea0ff06cd88b3b3023fd6c 100644 ---- a/binding.gyp -+++ b/binding.gyp -@@ -25,13 +25,13 @@ - "NAPI_VERSION=<(napi_build_version)", - ], - "cflags_cc": [ -- "-std=c++17" -+ "-std=c++20" - ], - "conditions": [ - ["OS=='mac'", { - "xcode_settings": { - "GCC_SYMBOLS_PRIVATE_EXTERN": "YES", # -fvisibility=hidden -- "CLANG_CXX_LANGUAGE_STANDARD": "c++17", -+ "CLANG_CXX_LANGUAGE_STANDARD": "c++20", - "MACOSX_DEPLOYMENT_TARGET": "10.9", - }, - }], -@@ -39,7 +39,7 @@ - "msvs_settings": { - "VCCLCompilerTool": { - "AdditionalOptions": [ -- "/std:c++17", -+ "/std:c++20", - ], - "RuntimeLibrary": 0, - }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e635dc597a..d1334993ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,11 +16,6 @@ overrides: packageExtensionsChecksum: sha256-XUHXilG696aPT8nrf3+inq4J0SEpv/Bjg7CPUKMFDu0= -patchedDependencies: - tree-sitter@0.25.0: - hash: 581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb - path: patches/tree-sitter@0.25.0.patch - importers: .: @@ -208,13 +203,13 @@ importers: version: link:../plugin-markdown '@driftlog/tree-sitter-dart': specifier: 1.0.4 - version: 1.0.4(node-addon-api@8.7.0)(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 1.0.4(node-addon-api@8.7.0)(tree-sitter@0.25.1) '@tree-sitter-grammars/tree-sitter-kotlin': specifier: 1.1.0 - version: 1.1.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 1.1.0(tree-sitter@0.25.1) '@tree-sitter-grammars/tree-sitter-lua': specifier: 0.4.1 - version: 0.4.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.4.1(tree-sitter@0.25.1) libsql: specifier: 0.5.29 version: 0.5.29 @@ -222,53 +217,53 @@ importers: specifier: ^10.2.5 version: 10.2.5 tree-sitter: - specifier: ^0.25.0 - version: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + specifier: ^0.25.1 + version: 0.25.1 tree-sitter-c: specifier: 0.24.1 - version: 0.24.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.24.1(tree-sitter@0.25.1) tree-sitter-c-sharp: specifier: 0.23.1 - version: 0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.1(tree-sitter@0.25.1) tree-sitter-cpp: specifier: 0.23.4 - version: 0.23.4(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.4(tree-sitter@0.25.1) tree-sitter-go: specifier: 0.25.0 - version: 0.25.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.25.0(tree-sitter@0.25.1) tree-sitter-haskell: specifier: 0.23.1 - version: 0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.1(tree-sitter@0.25.1) tree-sitter-java: specifier: 0.23.5 - version: 0.23.5(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.5(tree-sitter@0.25.1) tree-sitter-javascript: specifier: ^0.25.0 - version: 0.25.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.25.0(tree-sitter@0.25.1) tree-sitter-objc: specifier: 3.0.2 - version: 3.0.2(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 3.0.2(tree-sitter@0.25.1) tree-sitter-php: specifier: 0.24.2 - version: 0.24.2(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.24.2(tree-sitter@0.25.1) tree-sitter-python: specifier: 0.25.0 - version: 0.25.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.25.0(tree-sitter@0.25.1) tree-sitter-ruby: specifier: 0.23.1 - version: 0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.1(tree-sitter@0.25.1) tree-sitter-rust: specifier: 0.24.0 - version: 0.24.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.24.0(tree-sitter@0.25.1) tree-sitter-scala: specifier: 0.24.0 - version: 0.24.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.24.0(tree-sitter@0.25.1) tree-sitter-swift: specifier: 0.7.1 - version: 0.7.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.7.1(tree-sitter@0.25.1) tree-sitter-typescript: specifier: ^0.23.2 - version: 0.23.2(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.2(tree-sitter@0.25.1) zod: specifier: ^4.3.6 version: 4.3.6 @@ -289,7 +284,7 @@ importers: version: link:../plugin-api '@driftlog/tree-sitter-dart': specifier: 1.0.4 - version: 1.0.4(node-addon-api@8.7.0)(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 1.0.4(node-addon-api@8.7.0)(tree-sitter@0.25.1) '@floating-ui/react': specifier: ^0.27.19 version: 0.27.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -334,10 +329,10 @@ importers: version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tree-sitter-grammars/tree-sitter-kotlin': specifier: 1.1.0 - version: 1.1.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 1.1.0(tree-sitter@0.25.1) '@tree-sitter-grammars/tree-sitter-lua': specifier: 0.4.1 - version: 0.4.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.4.1(tree-sitter@0.25.1) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -378,53 +373,53 @@ importers: specifier: ^3.4.0 version: 3.5.0 tree-sitter: - specifier: ^0.25.0 - version: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + specifier: ^0.25.1 + version: 0.25.1 tree-sitter-c: specifier: 0.24.1 - version: 0.24.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.24.1(tree-sitter@0.25.1) tree-sitter-c-sharp: specifier: 0.23.1 - version: 0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.1(tree-sitter@0.25.1) tree-sitter-cpp: specifier: 0.23.4 - version: 0.23.4(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.4(tree-sitter@0.25.1) tree-sitter-go: specifier: 0.25.0 - version: 0.25.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.25.0(tree-sitter@0.25.1) tree-sitter-haskell: specifier: 0.23.1 - version: 0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.1(tree-sitter@0.25.1) tree-sitter-java: specifier: 0.23.5 - version: 0.23.5(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.5(tree-sitter@0.25.1) tree-sitter-javascript: specifier: ^0.25.0 - version: 0.25.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.25.0(tree-sitter@0.25.1) tree-sitter-objc: specifier: 3.0.2 - version: 3.0.2(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 3.0.2(tree-sitter@0.25.1) tree-sitter-php: specifier: 0.24.2 - version: 0.24.2(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.24.2(tree-sitter@0.25.1) tree-sitter-python: specifier: 0.25.0 - version: 0.25.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.25.0(tree-sitter@0.25.1) tree-sitter-ruby: specifier: 0.23.1 - version: 0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.1(tree-sitter@0.25.1) tree-sitter-rust: specifier: 0.24.0 - version: 0.24.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.24.0(tree-sitter@0.25.1) tree-sitter-scala: specifier: 0.24.0 - version: 0.24.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.24.0(tree-sitter@0.25.1) tree-sitter-swift: specifier: 0.7.1 - version: 0.7.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.7.1(tree-sitter@0.25.1) tree-sitter-typescript: specifier: ^0.23.2 - version: 0.23.2(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + version: 0.23.2(tree-sitter@0.25.1) zod: specifier: ^4.3.6 version: 4.3.6 @@ -7312,8 +7307,8 @@ packages: tree-sitter: optional: true - tree-sitter@0.25.0: - resolution: {integrity: sha512-PGZZzFW63eElZJDe/b/R/LbsjDDYJa5UEjLZJB59RQsMX+fo0j54fqBPn1MGKav/QNa0JR0zBiVaikYDWCj5KQ==} + tree-sitter@0.25.1: + resolution: {integrity: sha512-mrcEdkYtHfrK1A6fs3O6FxkBo0Qig5XUXqHhxUOQu0bmPo00QF4XaSx4edpazdHwxnSCjlGKGgIqWdaN4dvTLA==} ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} @@ -8356,12 +8351,12 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} - '@driftlog/tree-sitter-dart@1.0.4(node-addon-api@8.7.0)(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb))': + '@driftlog/tree-sitter-dart@1.0.4(node-addon-api@8.7.0)(tree-sitter@0.25.1)': dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 '@emnapi/core@1.10.0': dependencies: @@ -10830,20 +10825,20 @@ snapshots: dependencies: '@tldraw/utils': 5.2.5 - '@tree-sitter-grammars/tree-sitter-kotlin@1.1.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb))': + '@tree-sitter-grammars/tree-sitter-kotlin@1.1.0(tree-sitter@0.25.1)': dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 npm-check-updates: 17.1.18 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - '@tree-sitter-grammars/tree-sitter-lua@0.4.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb))': + '@tree-sitter-grammars/tree-sitter-lua@0.4.1(tree-sitter@0.25.1)': dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 '@turbo/darwin-64@2.10.6': optional: true @@ -14747,132 +14742,132 @@ snapshots: tree-kill@1.2.2: {} - tree-sitter-c-sharp@0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-c-sharp@0.23.1(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-c@0.23.6(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-c@0.23.6(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-c@0.24.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-c@0.24.1(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 tree-sitter-cli@0.23.2: {} - tree-sitter-cpp@0.23.4(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-cpp@0.23.4(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 - tree-sitter-c: 0.23.6(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + tree-sitter-c: 0.23.6(tree-sitter@0.25.1) optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-go@0.25.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-go@0.25.0(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-haskell@0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-haskell@0.23.1(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-java@0.23.5(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-java@0.23.5(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-javascript@0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-javascript@0.23.1(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-javascript@0.25.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-javascript@0.25.0(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-objc@3.0.2(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-objc@3.0.2(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 - tree-sitter-c: 0.23.6(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + tree-sitter-c: 0.23.6(tree-sitter@0.25.1) optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-php@0.24.2(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-php@0.24.2(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-python@0.25.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-python@0.25.0(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-ruby@0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-ruby@0.23.1(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-rust@0.24.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-rust@0.24.0(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-scala@0.24.0(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-scala@0.24.0(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter-swift@0.7.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-swift@0.7.1(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 tree-sitter-cli: 0.23.2 which: 2.0.2 - tree-sitter-typescript@0.23.2(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)): + tree-sitter-typescript@0.23.2(tree-sitter@0.25.1): dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 - tree-sitter-javascript: 0.23.1(tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb)) + tree-sitter-javascript: 0.23.1(tree-sitter@0.25.1) optionalDependencies: - tree-sitter: 0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb) + tree-sitter: 0.25.1 - tree-sitter@0.25.0(patch_hash=581e1c376edeabe3d25b64ea7bac59e14cc731627b17b97acccf0cce1dd051cb): + tree-sitter@0.25.1: dependencies: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 67c255f4a8..2c3a59fa8c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -68,5 +68,3 @@ packageExtensions: "react-force-graph-3d@*": dependencies: "@types/react": "^18.2.48" -patchedDependencies: - tree-sitter@0.25.0: patches/tree-sitter@0.25.0.patch diff --git a/scripts/validate-vsix-native-artifacts.mjs b/scripts/validate-vsix-native-artifacts.mjs index b4d181fd6a..15712c618e 100644 --- a/scripts/validate-vsix-native-artifacts.mjs +++ b/scripts/validate-vsix-native-artifacts.mjs @@ -17,10 +17,16 @@ const libsqlNativeBinaryPathByTarget = { 'win32-x64': 'extension/dist/node_modules/@libsql/win32-x64-msvc/index.node', }; +const treeSitterNativeBinaryPathByTarget = { + 'linux-x64': 'extension/dist/node_modules/tree-sitter/prebuilds/linux-x64/tree-sitter.node', + 'darwin-arm64': 'extension/dist/node_modules/tree-sitter/prebuilds/darwin-arm64/tree-sitter.node', + 'win32-x64': 'extension/dist/node_modules/tree-sitter/prebuilds/win32-x64/tree-sitter.node', +}; + function nativeBinaryPathsForTarget(target) { return [ libsqlNativeBinaryPathByTarget[target], - 'extension/dist/node_modules/tree-sitter/build/Release/tree_sitter_runtime_binding.node', + treeSitterNativeBinaryPathByTarget[target], ]; } diff --git a/tests/release/corePackage.test.mjs b/tests/release/corePackage.test.mjs index 81a290acb1..dd29d8029c 100644 --- a/tests/release/corePackage.test.mjs +++ b/tests/release/corePackage.test.mjs @@ -31,7 +31,7 @@ test('Core package declares the Node range supported by its native dependencies' fs.readFileSync(path.join(repoRoot, 'packages', 'core', 'package.json'), 'utf8'), ); - assert.equal(manifest.engines.node, '>=20 <23'); + assert.equal(manifest.engines.node, '>=20'); }); test('Core publishes dependency-free Graph Scope defaults as a focused subpath', () => { diff --git a/tests/release/vsixNativeArtifacts.test.mjs b/tests/release/vsixNativeArtifacts.test.mjs index 9ec9bb449d..22e08a457e 100644 --- a/tests/release/vsixNativeArtifacts.test.mjs +++ b/tests/release/vsixNativeArtifacts.test.mjs @@ -66,7 +66,7 @@ test('rejects a linux x64 VSIX with a macOS Apple Silicon Tree-sitter binding', assert.throws( () => validateVsixNativeArtifacts({ artifactsDir, version }), - /linux-x64\.vsix contains Mach-O arm64 at extension\/dist\/node_modules\/tree-sitter\/build\/Release\/tree_sitter_runtime_binding\.node; expected ELF x86-64\./, + /linux-x64\.vsix contains Mach-O arm64 at extension\/dist\/node_modules\/tree-sitter\/prebuilds\/linux-x64\/tree-sitter\.node; expected ELF x86-64\./, ); }); @@ -103,7 +103,7 @@ function writeVsixFixture({ ); writeFixtureBinary( fixtureRoot, - 'extension/dist/node_modules/tree-sitter/build/Release/tree_sitter_runtime_binding.node', + treeSitterNativeBinaryPath(target), treeSitterBinary, ); @@ -128,6 +128,15 @@ function libsqlNativeBinaryPath(target) { return `extension/dist/node_modules/@libsql/${packageByTarget[target]}/index.node`; } +function treeSitterNativeBinaryPath(target) { + const prebuildByTarget = { + 'linux-x64': 'linux-x64', + 'darwin-arm64': 'darwin-arm64', + 'win32-x64': 'win32-x64', + }; + return `extension/dist/node_modules/tree-sitter/prebuilds/${prebuildByTarget[target]}/tree-sitter.node`; +} + function writeFixtureBinary(rootDir, relativePath, binary) { const filePath = path.join(rootDir, relativePath); mkdirSync(path.dirname(filePath), { recursive: true }); From 063c11dae79da1c25f3bc9970ebe56941deca5cf Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 15:06:33 -0700 Subject: [PATCH 02/14] fix: stage target-specific extension runtimes --- .../extension/scripts/externalPackages.ts | 3 + packages/extension/scripts/runtimePackages.ts | 305 ++++++++++++++++-- .../scripts/validateNativeRuntime.mjs | 92 +++++- .../extension/build/runtimePackages.test.ts | 47 ++- 4 files changed, 402 insertions(+), 45 deletions(-) diff --git a/packages/extension/scripts/externalPackages.ts b/packages/extension/scripts/externalPackages.ts index 82f3bd6133..531a09c5d1 100644 --- a/packages/extension/scripts/externalPackages.ts +++ b/packages/extension/scripts/externalPackages.ts @@ -3,7 +3,10 @@ import { EXTENSION_RUNTIME_PACKAGE_NAMES } from './runtimePackages'; export { copyRuntimePackage, EXTENSION_RUNTIME_PACKAGE_NAMES, + EXTENSION_RUNTIME_TARGETS, + getExtensionRuntimePackageNames, getVendoredPackageRootPath, + resolveExtensionRuntimeTarget, resolveRuntimePackageRootPath, syncExtensionRuntimePackages, } from './runtimePackages'; diff --git a/packages/extension/scripts/runtimePackages.ts b/packages/extension/scripts/runtimePackages.ts index 857834481d..571ac6fcea 100644 --- a/packages/extension/scripts/runtimePackages.ts +++ b/packages/extension/scripts/runtimePackages.ts @@ -1,20 +1,50 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { createRequire } from 'node:module'; -import { currentTarget } from '@neon-rs/load'; -import { familySync, GLIBC, MUSL } from 'detect-libc'; const require = createRequire(import.meta.url); -export const EXTENSION_RUNTIME_PACKAGE_NAMES = [ - 'libsql', - '@neon-rs/load', - 'detect-libc', - 'esbuild', - getLibsqlNativePackageName(), - 'material-icon-theme', - 'node-gyp-build', - 'tree-sitter', +export const EXTENSION_RUNTIME_TARGETS = [ + 'linux-x64', + 'darwin-arm64', + 'win32-x64', +] as const; + +export type ExtensionRuntimeTarget = typeof EXTENSION_RUNTIME_TARGETS[number]; + +type RuntimeTargetConfig = { + platform: NodeJS.Platform; + arch: string; + libsqlPackageName: string; + esbuildPackageName: string; + nativePrebuildDirectory: string; +}; + +const RUNTIME_TARGET_CONFIG = { + 'linux-x64': { + platform: 'linux', + arch: 'x64', + libsqlPackageName: '@libsql/linux-x64-gnu', + esbuildPackageName: '@esbuild/linux-x64', + nativePrebuildDirectory: 'linux-x64', + }, + 'darwin-arm64': { + platform: 'darwin', + arch: 'arm64', + libsqlPackageName: '@libsql/darwin-arm64', + esbuildPackageName: '@esbuild/darwin-arm64', + nativePrebuildDirectory: 'darwin-arm64', + }, + 'win32-x64': { + platform: 'win32', + arch: 'x64', + libsqlPackageName: '@libsql/win32-x64-msvc', + esbuildPackageName: '@esbuild/win32-x64', + nativePrebuildDirectory: 'win32-x64', + }, +} satisfies Record; + +const TREE_SITTER_GRAMMAR_PACKAGE_NAMES = [ 'tree-sitter-c', 'tree-sitter-cpp', 'tree-sitter-c-sharp', @@ -35,16 +65,87 @@ export const EXTENSION_RUNTIME_PACKAGE_NAMES = [ 'tree-sitter-typescript', ] as const; -export function getLibsqlNativePackageName(): string { - let target = currentTarget(); - if (familySync() === GLIBC) { - if (target === 'linux-x64-musl') target = 'linux-x64-gnu'; - if (target === 'linux-arm64-musl') target = 'linux-arm64-gnu'; - } else if (familySync() === MUSL && target === 'linux-arm-gnueabihf') { - target = 'linux-arm-musleabihf'; +export const EXTENSION_RUNTIME_PACKAGE_NAMES = [ + 'libsql', + '@neon-rs/load', + 'detect-libc', + 'esbuild', + 'material-icon-theme', + 'node-gyp-build', + 'tree-sitter', + ...TREE_SITTER_GRAMMAR_PACKAGE_NAMES, +] as const; + +type RuntimeTargetOptions = { + environment?: Partial>; + platform?: NodeJS.Platform; + arch?: string; +}; + +type RuntimePackagePlan = { + packageName: string; + relativeFilePaths: string[]; + resolvePackageRootPath?: (packageName: string) => string; +}; + +function isExtensionRuntimeTarget(value: string): value is ExtensionRuntimeTarget { + return EXTENSION_RUNTIME_TARGETS.includes(value as ExtensionRuntimeTarget); +} + +export function resolveExtensionRuntimeTarget({ + environment = process.env, + platform = process.platform, + arch = process.arch, +}: RuntimeTargetOptions = {}): ExtensionRuntimeTarget { + const requestedTargets = environment.CODEGRAPHY_VSIX_TARGETS + ?.split(',') + .map(target => target.trim()) + .filter(Boolean); + + if (requestedTargets && requestedTargets.length !== 1) { + throw new Error('Extension runtime staging requires exactly one VSIX target.'); } - return `@libsql/${target}`; + const hostTarget = EXTENSION_RUNTIME_TARGETS.find((target) => { + const config = RUNTIME_TARGET_CONFIG[target]; + return config.platform === platform && config.arch === arch; + }); + if (!hostTarget) { + throw new Error( + `Unsupported extension runtime host: ${platform}-${arch}. ` + + `Supported targets are ${EXTENSION_RUNTIME_TARGETS.join(', ')}.`, + ); + } + + const requestedTarget = requestedTargets?.[0]; + if (!requestedTarget) { + return hostTarget; + } + if (!isExtensionRuntimeTarget(requestedTarget)) { + throw new Error( + `Unsupported extension runtime target: ${requestedTarget}. ` + + `Supported targets are ${EXTENSION_RUNTIME_TARGETS.join(', ')}.`, + ); + } + if (requestedTarget !== hostTarget) { + throw new Error( + `Cannot stage ${requestedTarget} native files on ${hostTarget}. ` + + 'Build the VSIX on its matching host.', + ); + } + + return requestedTarget; +} + +export function getExtensionRuntimePackageNames( + target: ExtensionRuntimeTarget, +): string[] { + const config = RUNTIME_TARGET_CONFIG[target]; + return [ + ...EXTENSION_RUNTIME_PACKAGE_NAMES, + config.libsqlPackageName, + config.esbuildPackageName, + ]; } function resolvePackageEntryPath(packageName: string): string { @@ -68,6 +169,13 @@ export function resolveRuntimePackageRootPath( return currentPath; } +function resolveEsbuildBinaryPackageRootPath(packageName: string): string { + const manifestPath = require.resolve(`${packageName}/package.json`, { + paths: [require.resolve('esbuild')], + }); + return path.dirname(manifestPath); +} + export function getVendoredPackageRootPath( outputFilePath: string, packageName: string, @@ -105,25 +213,166 @@ function normalizeVendoredPackageEntrypoint(packageRootPath: string): void { export function copyRuntimePackage( outputFilePath: string, packageName: string, + relativeFilePaths: readonly string[], resolvePackageRootPath: (packageName: string) => string = resolveRuntimePackageRootPath, ): string { const sourcePath = resolvePackageRootPath(packageName); const targetPath = getVendoredPackageRootPath(outputFilePath, packageName); - fs.mkdirSync(path.dirname(targetPath), { recursive: true }); - fs.cpSync(sourcePath, targetPath, { - recursive: true, - force: true, - dereference: true, - }); - normalizeVendoredPackageEntrypoint(targetPath); + fs.rmSync(targetPath, { recursive: true, force: true }); + + for (const relativeFilePath of [...new Set(relativeFilePaths)]) { + const sourceFilePath = path.join(sourcePath, relativeFilePath); + if (!fs.existsSync(sourceFilePath) || !fs.statSync(sourceFilePath).isFile()) { + throw new Error(`Runtime package file is not a file: ${packageName}/${relativeFilePath}`); + } + const targetFilePath = path.join(targetPath, relativeFilePath); + fs.mkdirSync(path.dirname(targetFilePath), { recursive: true }); + fs.cpSync(sourceFilePath, targetFilePath, { force: true }); + } + + normalizeVendoredPackageEntrypoint(targetPath); return targetPath; } +function listRelativeFiles( + packageRootPath: string, + relativeDirectoryPath: string, +): string[] { + const directoryPath = path.join(packageRootPath, relativeDirectoryPath); + if (!fs.existsSync(directoryPath)) { + return []; + } + + return fs.readdirSync(directoryPath, { recursive: true, withFileTypes: true }) + .filter(entry => entry.isFile()) + .map(entry => path.relative(packageRootPath, path.join(entry.parentPath, entry.name)) + .split(path.sep) + .join('/')); +} + +function legalFilePaths(packageRootPath: string): string[] { + return fs.readdirSync(packageRootPath, { withFileTypes: true }) + .filter(entry => entry.isFile() && /^(?:licen[cs]e|notice)(?:\.|$)/i.test(entry.name)) + .map(entry => entry.name); +} + +function staticPackagePlan( + packageName: string, + relativeFilePaths: readonly string[], + resolvePackageRootPath?: (packageName: string) => string, +): RuntimePackagePlan { + const packageRootPath = (resolvePackageRootPath ?? resolveRuntimePackageRootPath)(packageName); + return { + packageName, + relativeFilePaths: [...relativeFilePaths, ...legalFilePaths(packageRootPath)], + ...(resolvePackageRootPath ? { resolvePackageRootPath } : {}), + }; +} + +function treeSitterGrammarPlan( + packageName: string, + nativePrebuildDirectory: string, +): RuntimePackagePlan { + const packageRootPath = resolveRuntimePackageRootPath(packageName); + const bindingFilePaths = listRelativeFiles(packageRootPath, 'bindings/node') + .filter(filePath => filePath.endsWith('.js') && !filePath.endsWith('_test.js')); + const nodeTypeFilePaths = listRelativeFiles(packageRootPath, '') + .filter(filePath => filePath.endsWith('src/node-types.json')); + const prebuildFilePaths = listRelativeFiles( + packageRootPath, + path.join('prebuilds', nativePrebuildDirectory), + ).filter(filePath => filePath.endsWith('.node')); + const builtFilePaths = packageName === '@driftlog/tree-sitter-dart' + ? listRelativeFiles(packageRootPath, path.join('build', 'Release')) + .filter(filePath => filePath.endsWith('.node')) + : []; + const rootEntrypointFilePaths = packageName === '@driftlog/tree-sitter-dart' + ? ['index.js'] + : []; + const nativeFilePaths = [...prebuildFilePaths, ...builtFilePaths]; + + if (nativeFilePaths.length === 0) { + throw new Error(`No ${nativePrebuildDirectory} native binding found for ${packageName}.`); + } + + return staticPackagePlan(packageName, [ + 'package.json', + ...rootEntrypointFilePaths, + ...bindingFilePaths, + ...nodeTypeFilePaths, + ...nativeFilePaths, + ]); +} + +function createRuntimePackagePlans(target: ExtensionRuntimeTarget): RuntimePackagePlan[] { + const config = RUNTIME_TARGET_CONFIG[target]; + const materialIconThemeRoot = resolveRuntimePackageRootPath('material-icon-theme'); + + return [ + staticPackagePlan('libsql', [ + 'package.json', + 'index.js', + 'auth.js', + 'promise.js', + 'sqlite-error.js', + ]), + staticPackagePlan('@neon-rs/load', ['package.json', 'dist/index.js']), + staticPackagePlan('detect-libc', [ + 'package.json', + 'lib/detect-libc.js', + 'lib/filesystem.js', + 'lib/process.js', + ]), + staticPackagePlan('esbuild', [ + 'package.json', + 'lib/main.js', + ]), + staticPackagePlan(config.esbuildPackageName, [ + 'package.json', + config.platform === 'win32' ? 'esbuild.exe' : 'bin/esbuild', + ], resolveEsbuildBinaryPackageRootPath), + staticPackagePlan(config.libsqlPackageName, ['package.json', 'index.node']), + staticPackagePlan('material-icon-theme', [ + 'package.json', + 'dist/material-icons.json', + ...listRelativeFiles(materialIconThemeRoot, 'icons'), + ]), + staticPackagePlan('node-gyp-build', [ + 'package.json', + 'index.js', + 'node-gyp-build.js', + ]), + staticPackagePlan('tree-sitter', [ + 'package.json', + 'index.js', + path.join( + 'prebuilds', + config.nativePrebuildDirectory, + 'tree-sitter.node', + ), + ]), + ...TREE_SITTER_GRAMMAR_PACKAGE_NAMES.map(packageName => treeSitterGrammarPlan( + packageName, + config.nativePrebuildDirectory, + )), + ]; +} + export function syncExtensionRuntimePackages( outputFilePath: string, - packageNames: readonly string[] = EXTENSION_RUNTIME_PACKAGE_NAMES, + target: ExtensionRuntimeTarget = resolveExtensionRuntimeTarget(), ): string[] { - return packageNames.map(packageName => copyRuntimePackage(outputFilePath, packageName)); + fs.rmSync(path.join(path.dirname(outputFilePath), 'node_modules'), { + recursive: true, + force: true, + }); + + return createRuntimePackagePlans(target).map(plan => copyRuntimePackage( + outputFilePath, + plan.packageName, + plan.relativeFilePaths, + plan.resolvePackageRootPath, + )); } diff --git a/packages/extension/scripts/validateNativeRuntime.mjs b/packages/extension/scripts/validateNativeRuntime.mjs index 834a4f4cc9..d626e8ca6c 100644 --- a/packages/extension/scripts/validateNativeRuntime.mjs +++ b/packages/extension/scripts/validateNativeRuntime.mjs @@ -2,11 +2,42 @@ import fs from 'node:fs'; import { createRequire } from 'node:module'; import os from 'node:os'; import path from 'node:path'; -import { fileURLToPath } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; const require = createRequire(import.meta.url); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..'); -const vendoredSQLitePath = path.join(repoRoot, 'dist', 'node_modules', 'libsql'); +const vendoredRuntimeRoot = path.join(repoRoot, 'dist', 'node_modules'); +const vendoredRequire = createRequire(path.join(vendoredRuntimeRoot, 'runtime-check.js')); +const vendoredSQLitePath = path.join(vendoredRuntimeRoot, 'libsql'); + +const treeSitterLanguageModules = [ + { label: 'C', packageName: 'tree-sitter-c' }, + { label: 'C++', packageName: 'tree-sitter-cpp' }, + { label: 'C#', packageName: 'tree-sitter-c-sharp' }, + { label: 'Dart', packageName: '@driftlog/tree-sitter-dart' }, + { label: 'Go', packageName: 'tree-sitter-go' }, + { label: 'Haskell', packageName: 'tree-sitter-haskell' }, + { label: 'Java', packageName: 'tree-sitter-java' }, + { label: 'JavaScript', packageName: 'tree-sitter-javascript' }, + { label: 'Kotlin', packageName: '@tree-sitter-grammars/tree-sitter-kotlin' }, + { + label: 'Lua', + packageName: '@tree-sitter-grammars/tree-sitter-lua/bindings/node/index.js', + }, + { label: 'Objective-C', packageName: 'tree-sitter-objc' }, + { label: 'PHP', packageName: 'tree-sitter-php', exportName: 'php' }, + { label: 'Python', packageName: 'tree-sitter-python' }, + { label: 'Ruby', packageName: 'tree-sitter-ruby' }, + { label: 'Rust', packageName: 'tree-sitter-rust' }, + { label: 'Scala', packageName: 'tree-sitter-scala' }, + { label: 'Swift', packageName: 'tree-sitter-swift' }, + { label: 'TSX', packageName: 'tree-sitter-typescript', exportName: 'tsx' }, + { + label: 'TypeScript', + packageName: 'tree-sitter-typescript', + exportName: 'typescript', + }, +]; try { const Database = require(vendoredSQLitePath); @@ -75,3 +106,60 @@ try { } console.log(`Loaded vendored SQLite native runtime from ${vendoredSQLitePath}`); + +try { + const Parser = vendoredRequire('tree-sitter'); + + for (const languageModule of treeSitterLanguageModules) { + const modulePath = vendoredRequire.resolve(languageModule.packageName); + const importedModule = await import(pathToFileURL(modulePath).href); + const moduleValue = importedModule.default ?? importedModule; + const language = languageModule.exportName + ? moduleValue[languageModule.exportName] ?? importedModule[languageModule.exportName] + : moduleValue; + const parser = new Parser(); + parser.setLanguage(language); + const tree = parser.parse('const value = 1'); + if (!tree.rootNode) { + throw new Error(`${languageModule.label} did not return a syntax tree.`); + } + } +} catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Unable to load vendored Tree-sitter runtimes: ${message}`); +} + +try { + const esbuild = vendoredRequire('esbuild'); + const result = esbuild.transformSync('const value: number = 1', { loader: 'ts' }); + if (!result.code.includes('const value = 1')) { + throw new Error('TypeScript transform returned unexpected output.'); + } +} catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Unable to run the vendored esbuild runtime: ${message}`); +} + +try { + const materialThemeRoot = path.join(vendoredRuntimeRoot, 'material-icon-theme'); + const manifestPath = path.join(materialThemeRoot, 'dist', 'material-icons.json'); + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + const iconDefinition = Object.values(manifest.iconDefinitions ?? {})[0]; + const iconPath = iconDefinition?.iconPath; + if (typeof iconPath !== 'string') { + throw new Error('Material icon manifest has no icon definitions.'); + } + + const resolvedIconPath = path.resolve(path.dirname(manifestPath), iconPath); + if (!fs.existsSync(resolvedIconPath)) { + throw new Error(`Material icon is missing at ${resolvedIconPath}.`); + } +} catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Unable to load the vendored Material icon theme: ${message}`); +} + +console.log( + `Loaded ${treeSitterLanguageModules.length} vendored Tree-sitter languages, ` + + 'esbuild, and the Material icon theme.', +); diff --git a/packages/extension/tests/extension/build/runtimePackages.test.ts b/packages/extension/tests/extension/build/runtimePackages.test.ts index 4637aebbb8..e9fd691dac 100644 --- a/packages/extension/tests/extension/build/runtimePackages.test.ts +++ b/packages/extension/tests/extension/build/runtimePackages.test.ts @@ -6,9 +6,10 @@ import { copyRuntimePackage, EXTENSION_EXTERNAL_PACKAGE_NAMES, EXTENSION_RUNTIME_PACKAGE_NAMES, + getExtensionRuntimePackageNames, getVendoredPackageRootPath, + resolveExtensionRuntimeTarget, resolveRuntimePackageRootPath, - syncExtensionRuntimePackages, } from '../../../scripts/externalPackages'; const EXTENSION_PACKAGE_ROOT = path.resolve( @@ -24,18 +25,23 @@ describe('runtime package build support', () => { expect(fs.existsSync(path.join(packageRootPath, 'package.json'))).toBe(true); }); - it('copies a scoped runtime package into dist/node_modules', () => { + it('copies only selected runtime files and clears stale package output', () => { const tempDirectoryPath = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraphy-runtime-build-')); const outputFilePath = path.join(tempDirectoryPath, 'dist', 'extension.js'); const sourcePackageRootPath = path.join(tempDirectoryPath, 'vendor', 'libsql'); const sourcePackageJsonPath = path.join(sourcePackageRootPath, 'package.json'); + const targetPackageRootPath = getVendoredPackageRootPath(outputFilePath, 'libsql'); fs.mkdirSync(sourcePackageRootPath, { recursive: true }); fs.writeFileSync(sourcePackageJsonPath, '{"name":"libsql"}'); + fs.writeFileSync(path.join(sourcePackageRootPath, 'README.md'), 'not runtime'); + fs.mkdirSync(targetPackageRootPath, { recursive: true }); + fs.writeFileSync(path.join(targetPackageRootPath, 'stale.node'), 'stale'); const copiedPackageRootPath = copyRuntimePackage( outputFilePath, 'libsql', + ['package.json'], () => sourcePackageRootPath, ); @@ -43,6 +49,8 @@ describe('runtime package build support', () => { expect(fs.readFileSync(path.join(copiedPackageRootPath, 'package.json'), 'utf8')).toBe( '{"name":"libsql"}', ); + expect(fs.existsSync(path.join(copiedPackageRootPath, 'README.md'))).toBe(false); + expect(fs.existsSync(path.join(copiedPackageRootPath, 'stale.node'))).toBe(false); }); it('normalizes copied package entrypoints that point at extensionless directories', () => { @@ -66,6 +74,7 @@ describe('runtime package build support', () => { const copiedPackageRootPath = copyRuntimePackage( outputFilePath, '@tree-sitter-grammars/tree-sitter-lua', + ['package.json', 'bindings/node/index.js'], () => sourcePackageRootPath, ); const copiedPackageJson = JSON.parse( @@ -75,28 +84,36 @@ describe('runtime package build support', () => { expect(copiedPackageJson.main).toBe('bindings/node/index.js'); }); - it('syncs every requested runtime package', () => { - const tempDirectoryPath = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraphy-runtime-sync-')); - const outputFilePath = path.join(tempDirectoryPath, 'dist', 'extension.js'); - const copiedPackages: string[] = []; - - const targetPaths = syncExtensionRuntimePackages(outputFilePath, ['tree-sitter'],); - copiedPackages.push(...targetPaths); + it('selects target-specific native runtime packages', () => { + expect(getExtensionRuntimePackageNames('darwin-arm64')).toEqual( + expect.arrayContaining([ + '@libsql/darwin-arm64', + '@esbuild/darwin-arm64', + 'tree-sitter', + ]), + ); + expect(getExtensionRuntimePackageNames('darwin-arm64')).not.toContain( + '@libsql/linux-x64-gnu', + ); + }); - expect(copiedPackages).toEqual([ - getVendoredPackageRootPath(outputFilePath, 'tree-sitter'), - ]); - expect(fs.existsSync(path.join(copiedPackages[0], 'package.json'))).toBe(true); + it('uses one explicit VSIX target when the build provides it', () => { + expect(resolveExtensionRuntimeTarget({ + environment: { CODEGRAPHY_VSIX_TARGETS: 'darwin-arm64' }, + platform: 'darwin', + arch: 'arm64', + })).toBe('darwin-arm64'); }); it('vendors every Tree-sitter grammar needed by the core runtime', () => { - expect(EXTENSION_RUNTIME_PACKAGE_NAMES).toEqual( + expect(getExtensionRuntimePackageNames('darwin-arm64')).toEqual( expect.arrayContaining([ 'libsql', '@neon-rs/load', 'detect-libc', 'esbuild', - expect.stringMatching(/^@libsql\//), + '@libsql/darwin-arm64', + '@esbuild/darwin-arm64', 'material-icon-theme', 'tree-sitter', 'tree-sitter-c', From bee323a0d7263aaaf0299fa7534359eeaf4a0e12 Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 15:08:36 -0700 Subject: [PATCH 03/14] fix: trim VSIX runtime payload --- .changeset/slim-vsix-runtime.md | 5 +++++ package.json | 28 ++++++++++++++--------- scripts/release-core.mjs | 5 +++-- tests/release/releaseCore.test.mjs | 36 +++++++++++++++++------------- 4 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 .changeset/slim-vsix-runtime.md diff --git a/.changeset/slim-vsix-runtime.md b/.changeset/slim-vsix-runtime.md new file mode 100644 index 0000000000..946bd16555 --- /dev/null +++ b/.changeset/slim-vsix-runtime.md @@ -0,0 +1,5 @@ +--- +"@codegraphy-dev/extension": patch +--- + +Reduce the installed extension size by shipping only the runtime files and native binaries needed by each platform-specific VSIX. diff --git a/package.json b/package.json index bf7e466b72..8af9e359aa 100644 --- a/package.json +++ b/package.json @@ -55,23 +55,31 @@ "vscode": "^1.85.0" }, "files": [ - "dist/**", + "dist/extension.js", + "dist/node_modules/**", + "dist/webview/index.css", + "dist/webview/index.js", + "dist/webview/index.wasm", "assets/**", - "packages/plugin-godot/assets/**", - "packages/plugin-godot/dist/**", + "packages/plugin-godot/assets/godot.svg", + "packages/plugin-godot/dist/extension.js", + "packages/plugin-godot/dist/plugin.js", "packages/plugin-godot/package.json", - "packages/plugin-markdown/dist/**", + "packages/plugin-markdown/dist/plugin.js", "packages/plugin-markdown/package.json", - "packages/plugin-particles/dist/**", + "packages/plugin-particles/dist/plugin.js", + "packages/plugin-particles/dist/webview.js", "packages/plugin-particles/package.json", - "packages/plugin-svelte/dist/**", + "packages/plugin-svelte/dist/plugin.js", "packages/plugin-svelte/package.json", - "packages/plugin-typescript/dist/**", + "packages/plugin-typescript/dist/plugin.js", "packages/plugin-typescript/package.json", - "packages/plugin-unity/assets/**", - "packages/plugin-unity/dist/**", + "packages/plugin-unity/assets/NOTICE.md", + "packages/plugin-unity/assets/unity.svg", + "packages/plugin-unity/dist/extension.js", + "packages/plugin-unity/dist/plugin.js", "packages/plugin-unity/package.json", - "packages/plugin-vue/dist/**", + "packages/plugin-vue/dist/plugin.js", "packages/plugin-vue/package.json", "docs/**", "README.md", diff --git a/scripts/release-core.mjs b/scripts/release-core.mjs index 5e8e6f14ed..558b1e8b1f 100644 --- a/scripts/release-core.mjs +++ b/scripts/release-core.mjs @@ -64,11 +64,12 @@ export function collectCoreReleaseBuildFilters(rootManifest, baseDir = repoRoot) const filters = ['@codegraphy-dev/extension...']; for (const entry of collectCoreReleaseEntries(rootManifest)) { - if (!/^packages\/[^/]+\/dist$/.test(entry)) { + const packageDirectory = entry.match(/^(packages\/[^/]+)(?:\/|$)/)?.[1]; + if (!packageDirectory) { continue; } - const packageManifestPath = path.join(baseDir, path.dirname(entry), 'package.json'); + const packageManifestPath = path.join(baseDir, packageDirectory, 'package.json'); if (!existsSync(packageManifestPath)) { continue; } diff --git a/tests/release/releaseCore.test.mjs b/tests/release/releaseCore.test.mjs index 97172dcfbc..fa77837889 100644 --- a/tests/release/releaseCore.test.mjs +++ b/tests/release/releaseCore.test.mjs @@ -12,11 +12,11 @@ test('core extension release includes built-in Extension plugin icon assets', () ); assert.ok( - releaseCore.collectCoreReleaseEntries(rootManifest).includes('packages/plugin-unity/assets'), + releaseCore.collectCoreReleaseEntries(rootManifest).includes('packages/plugin-unity/assets/unity.svg'), 'Unity plugin icon assets must be staged so plugin legend and graph node image URLs resolve in packaged webviews.', ); assert.ok( - releaseCore.collectCoreReleaseEntries(rootManifest).includes('packages/plugin-godot/assets'), + releaseCore.collectCoreReleaseEntries(rootManifest).includes('packages/plugin-godot/assets/godot.svg'), 'Godot plugin icon assets must be staged so plugin legend and graph node image URLs resolve in packaged webviews.', ); }); @@ -27,23 +27,27 @@ test('core extension release includes every bundled plugin runtime and package d ); const entries = releaseCore.collectCoreReleaseEntries(rootManifest); - for (const plugin of [ - 'godot', - 'markdown', - 'particles', - 'svelte', - 'typescript', - 'unity', - 'vue', - ]) { + const runtimeFilesByPlugin = { + godot: ['plugin.js', 'extension.js'], + markdown: ['plugin.js'], + particles: ['plugin.js', 'webview.js'], + svelte: ['plugin.js'], + typescript: ['plugin.js'], + unity: ['plugin.js', 'extension.js'], + vue: ['plugin.js'], + }; + + for (const [plugin, runtimeFiles] of Object.entries(runtimeFilesByPlugin)) { assert.ok( entries.includes(`packages/plugin-${plugin}/package.json`), `${plugin} package manifest must be staged so the host-neutral loader can discover it.`, ); - assert.ok( - entries.includes(`packages/plugin-${plugin}/dist`), - `${plugin} runtime must be staged so packaged extensions can load it.`, - ); + for (const runtimeFile of runtimeFiles) { + assert.ok( + entries.includes(`packages/plugin-${plugin}/dist/${runtimeFile}`), + `${plugin} runtime must be staged so packaged extensions can load it.`, + ); + } } }); @@ -123,7 +127,7 @@ test('release preparation builds bundled workspace plugin packages before stagin JSON.stringify({ files: [ 'dist/**', - 'packages/plugin-example/dist/**', + 'packages/plugin-example/dist/plugin.js', ], }), ); From 9a0ba1836fece516b7ccb022c29dcfdbf5e76dd3 Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 15:13:33 -0700 Subject: [PATCH 04/14] fix: launch current VS Code smoke host --- scripts/smoke-installed-vsix.mjs | 40 +++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/scripts/smoke-installed-vsix.mjs b/scripts/smoke-installed-vsix.mjs index 80be608632..06536d7717 100644 --- a/scripts/smoke-installed-vsix.mjs +++ b/scripts/smoke-installed-vsix.mjs @@ -17,7 +17,11 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..' const requireFromExtension = createRequire( path.join(repoRoot, 'packages', 'extension', 'package.json'), ); -const { runTests, runVSCodeCommand } = requireFromExtension('@vscode/test-electron'); +const { + downloadAndUnzipVSCode, + resolveCliArgsFromVSCodeExecutablePath, + runTests, +} = requireFromExtension('@vscode/test-electron'); const hostTargetByPlatform = { linux: { @@ -97,17 +101,32 @@ async function smokeInstalledVsix({ target, vsixPath }) { prepareScenarioWorkspacePlugins(scenario, repoRoot, workspacePath, pluginCacheHomeDir, false); await writeHarnessExtension(harnessPath); - await runVSCodeCommand([ + const downloadedExecutablePath = await downloadAndUnzipVSCode(); + const vscodeExecutablePath = resolveCurrentVsCodeExecutablePath(downloadedExecutablePath); + const [cliPath, ...cliArgs] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath); + const installResult = spawnSync(cliPath, [ + ...cliArgs, ...profileArgs, '--install-extension', vsixPath, '--force', - ]); + ], { + encoding: 'utf8', + shell: process.platform === 'win32', + }); + if (installResult.status !== 0) { + throw new Error( + `Unable to install ${path.basename(vsixPath)}.\n` + + `${installResult.stdout}${installResult.stderr}`, + ); + } await runTests({ extensionDevelopmentPath: harnessPath, extensionTestsPath, + vscodeExecutablePath, extensionTestsEnv: { + ELECTRON_RUN_AS_NODE: undefined, CODEGRAPHY_E2E_SCENARIO: 'typescript', CODEGRAPHY_E2E_GREP: 'extension activates without error|all commands are registered|manual graph indexing creates scenario edges', }, @@ -132,6 +151,21 @@ async function smokeInstalledVsix({ target, vsixPath }) { } } +function resolveCurrentVsCodeExecutablePath(downloadedExecutablePath) { + if (existsSync(downloadedExecutablePath)) { + return downloadedExecutablePath; + } + + if (process.platform === 'darwin' && path.basename(downloadedExecutablePath) === 'Electron') { + const currentExecutablePath = path.join(path.dirname(downloadedExecutablePath), 'Code'); + if (existsSync(currentExecutablePath)) { + return currentExecutablePath; + } + } + + throw new Error(`Downloaded VS Code executable is missing at ${downloadedExecutablePath}.`); +} + function buildScenarioWorkspacePluginPackages(scenario) { for (const relativePath of scenario.workspacePluginPackageRelativePaths) { const packageRoot = path.resolve(repoRoot, relativePath); From 47f88e6b82fb2662299cc9548b5027315190ca19 Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 15:22:43 -0700 Subject: [PATCH 05/14] refactor: separate extension runtime staging --- CONTRIBUTING.md | 2 +- package.json | 8 +- .../extension/scripts/runtimePackagePlan.ts | 212 ++++++++++++ packages/extension/scripts/runtimePackages.ts | 317 +----------------- packages/extension/scripts/runtimeTarget.ts | 94 ++++++ .../build/runtimePackagePlan.test.ts | 55 +++ .../extension/build/runtimePackages.test.ts | 71 ---- .../extension/build/runtimeTarget.test.ts | 27 ++ 8 files changed, 414 insertions(+), 372 deletions(-) create mode 100644 packages/extension/scripts/runtimePackagePlan.ts create mode 100644 packages/extension/scripts/runtimeTarget.ts create mode 100644 packages/extension/tests/extension/build/runtimePackagePlan.test.ts create mode 100644 packages/extension/tests/extension/build/runtimeTarget.test.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b577be9a4..93bed89212 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ CodeGraphy accepts focused changes through pull requests against `main`. ### Prerequisites -- Node.js 20 through 22; use the repository-pinned Node 22 runtime +- Node.js 20 or newer; use the repository-pinned Node 22 runtime - pnpm 10+ - VS Code 1.85+ diff --git a/package.json b/package.json index 8af9e359aa..b179956948 100644 --- a/package.json +++ b/package.json @@ -64,24 +64,30 @@ "packages/plugin-godot/assets/godot.svg", "packages/plugin-godot/dist/extension.js", "packages/plugin-godot/dist/plugin.js", + "packages/plugin-godot/LICENSE", "packages/plugin-godot/package.json", "packages/plugin-markdown/dist/plugin.js", + "packages/plugin-markdown/LICENSE", "packages/plugin-markdown/package.json", "packages/plugin-particles/dist/plugin.js", "packages/plugin-particles/dist/webview.js", + "packages/plugin-particles/LICENSE", "packages/plugin-particles/package.json", "packages/plugin-svelte/dist/plugin.js", + "packages/plugin-svelte/LICENSE", "packages/plugin-svelte/package.json", "packages/plugin-typescript/dist/plugin.js", + "packages/plugin-typescript/LICENSE", "packages/plugin-typescript/package.json", "packages/plugin-unity/assets/NOTICE.md", "packages/plugin-unity/assets/unity.svg", "packages/plugin-unity/dist/extension.js", "packages/plugin-unity/dist/plugin.js", + "packages/plugin-unity/LICENSE", "packages/plugin-unity/package.json", "packages/plugin-vue/dist/plugin.js", + "packages/plugin-vue/LICENSE", "packages/plugin-vue/package.json", - "docs/**", "README.md", "CONTRIBUTING.md", "LICENSE" diff --git a/packages/extension/scripts/runtimePackagePlan.ts b/packages/extension/scripts/runtimePackagePlan.ts new file mode 100644 index 0000000000..7aa3bcb6a8 --- /dev/null +++ b/packages/extension/scripts/runtimePackagePlan.ts @@ -0,0 +1,212 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { createRequire } from 'node:module'; +import { + RUNTIME_TARGET_CONFIG, + type ExtensionRuntimeTarget, +} from './runtimeTarget'; + +const require = createRequire(import.meta.url); + +const TREE_SITTER_GRAMMAR_PACKAGE_NAMES = [ + 'tree-sitter-c', + 'tree-sitter-cpp', + 'tree-sitter-c-sharp', + '@driftlog/tree-sitter-dart', + 'tree-sitter-go', + 'tree-sitter-haskell', + 'tree-sitter-java', + 'tree-sitter-javascript', + '@tree-sitter-grammars/tree-sitter-kotlin', + '@tree-sitter-grammars/tree-sitter-lua', + 'tree-sitter-objc', + 'tree-sitter-php', + 'tree-sitter-python', + 'tree-sitter-ruby', + 'tree-sitter-rust', + 'tree-sitter-scala', + 'tree-sitter-swift', + 'tree-sitter-typescript', +] as const; + +export const EXTENSION_RUNTIME_PACKAGE_NAMES = [ + 'libsql', + '@neon-rs/load', + 'detect-libc', + 'esbuild', + 'material-icon-theme', + 'node-gyp-build', + 'tree-sitter', + ...TREE_SITTER_GRAMMAR_PACKAGE_NAMES, +] as const; + +export type RuntimePackagePlan = { + packageName: string; + relativeFilePaths: string[]; + resolvePackageRootPath?: (packageName: string) => string; +}; + +function resolvePackageEntryPath(packageName: string): string { + return require.resolve(packageName); +} + +export function resolveRuntimePackageRootPath( + packageName: string, + resolveEntryPath: (packageName: string) => string = resolvePackageEntryPath, +): string { + let currentPath = path.dirname(resolveEntryPath(packageName)); + + while (!fs.existsSync(path.join(currentPath, 'package.json'))) { + const parentPath = path.dirname(currentPath); + if (parentPath === currentPath) { + throw new Error(`Unable to find package root for ${packageName}`); + } + currentPath = parentPath; + } + + return currentPath; +} + +function resolveEsbuildBinaryPackageRootPath(packageName: string): string { + const manifestPath = require.resolve(`${packageName}/package.json`, { + paths: [require.resolve('esbuild')], + }); + return path.dirname(manifestPath); +} + +function listRelativeFiles( + packageRootPath: string, + relativeDirectoryPath: string, +): string[] { + const directoryPath = path.join(packageRootPath, relativeDirectoryPath); + if (!fs.existsSync(directoryPath)) { + return []; + } + + return fs.readdirSync(directoryPath, { recursive: true, withFileTypes: true }) + .filter(entry => entry.isFile()) + .map(entry => path.relative(packageRootPath, path.join(entry.parentPath, entry.name)) + .split(path.sep) + .join('/')); +} + +function legalFilePaths(packageRootPath: string): string[] { + return fs.readdirSync(packageRootPath, { withFileTypes: true }) + .filter(entry => entry.isFile() && /^(?:licen[cs]e|notice)(?:\.|$)/i.test(entry.name)) + .map(entry => entry.name); +} + +function staticPackagePlan( + packageName: string, + relativeFilePaths: readonly string[], + resolvePackageRootPath?: (packageName: string) => string, +): RuntimePackagePlan { + const packageRootPath = (resolvePackageRootPath ?? resolveRuntimePackageRootPath)(packageName); + return { + packageName, + relativeFilePaths: [...relativeFilePaths, ...legalFilePaths(packageRootPath)], + ...(resolvePackageRootPath ? { resolvePackageRootPath } : {}), + }; +} + +function treeSitterGrammarPlan( + packageName: string, + nativePrebuildDirectory: string, +): RuntimePackagePlan { + const packageRootPath = resolveRuntimePackageRootPath(packageName); + const bindingFilePaths = listRelativeFiles(packageRootPath, 'bindings/node') + .filter(filePath => filePath.endsWith('.js') && !filePath.endsWith('_test.js')); + const nodeTypeFilePaths = listRelativeFiles(packageRootPath, '') + .filter(filePath => filePath.endsWith('src/node-types.json')); + const prebuildFilePaths = listRelativeFiles( + packageRootPath, + path.join('prebuilds', nativePrebuildDirectory), + ).filter(filePath => filePath.endsWith('.node')); + const builtFilePaths = packageName === '@driftlog/tree-sitter-dart' + ? listRelativeFiles(packageRootPath, path.join('build', 'Release')) + .filter(filePath => filePath.endsWith('.node')) + : []; + const rootEntrypointFilePaths = packageName === '@driftlog/tree-sitter-dart' + ? ['index.js'] + : []; + const nativeFilePaths = [...prebuildFilePaths, ...builtFilePaths]; + + if (nativeFilePaths.length === 0) { + throw new Error(`No ${nativePrebuildDirectory} native binding found for ${packageName}.`); + } + + return staticPackagePlan(packageName, [ + 'package.json', + ...rootEntrypointFilePaths, + ...bindingFilePaths, + ...nodeTypeFilePaths, + ...nativeFilePaths, + ]); +} + +export function getExtensionRuntimePackageNames( + target: ExtensionRuntimeTarget, +): string[] { + const config = RUNTIME_TARGET_CONFIG[target]; + return [ + ...EXTENSION_RUNTIME_PACKAGE_NAMES, + config.libsqlPackageName, + config.esbuildPackageName, + ]; +} + +export function createRuntimePackagePlans( + target: ExtensionRuntimeTarget, +): RuntimePackagePlan[] { + const config = RUNTIME_TARGET_CONFIG[target]; + const materialIconThemeRoot = resolveRuntimePackageRootPath('material-icon-theme'); + + return [ + staticPackagePlan('libsql', [ + 'package.json', + 'index.js', + 'auth.js', + 'promise.js', + 'sqlite-error.js', + ]), + staticPackagePlan('@neon-rs/load', ['package.json', 'dist/index.js']), + staticPackagePlan('detect-libc', [ + 'package.json', + 'lib/detect-libc.js', + 'lib/filesystem.js', + 'lib/process.js', + ]), + staticPackagePlan('esbuild', [ + 'package.json', + 'lib/main.js', + ]), + staticPackagePlan(config.esbuildPackageName, [ + 'package.json', + config.platform === 'win32' ? 'esbuild.exe' : 'bin/esbuild', + ], resolveEsbuildBinaryPackageRootPath), + staticPackagePlan(config.libsqlPackageName, ['package.json', 'index.node']), + staticPackagePlan('material-icon-theme', [ + 'package.json', + 'dist/material-icons.json', + ...listRelativeFiles(materialIconThemeRoot, 'icons'), + ]), + staticPackagePlan('node-gyp-build', [ + 'package.json', + 'index.js', + 'node-gyp-build.js', + ]), + staticPackagePlan('tree-sitter', [ + 'package.json', + 'index.js', + path.join( + 'prebuilds', + config.nativePrebuildDirectory, + 'tree-sitter.node', + ), + ]), + ...TREE_SITTER_GRAMMAR_PACKAGE_NAMES.map(packageName => treeSitterGrammarPlan( + packageName, + config.nativePrebuildDirectory, + )), + ]; +} diff --git a/packages/extension/scripts/runtimePackages.ts b/packages/extension/scripts/runtimePackages.ts index 571ac6fcea..fcac767fd6 100644 --- a/packages/extension/scripts/runtimePackages.ts +++ b/packages/extension/scripts/runtimePackages.ts @@ -1,180 +1,23 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; -import { createRequire } from 'node:module'; - -const require = createRequire(import.meta.url); - -export const EXTENSION_RUNTIME_TARGETS = [ - 'linux-x64', - 'darwin-arm64', - 'win32-x64', -] as const; - -export type ExtensionRuntimeTarget = typeof EXTENSION_RUNTIME_TARGETS[number]; - -type RuntimeTargetConfig = { - platform: NodeJS.Platform; - arch: string; - libsqlPackageName: string; - esbuildPackageName: string; - nativePrebuildDirectory: string; -}; - -const RUNTIME_TARGET_CONFIG = { - 'linux-x64': { - platform: 'linux', - arch: 'x64', - libsqlPackageName: '@libsql/linux-x64-gnu', - esbuildPackageName: '@esbuild/linux-x64', - nativePrebuildDirectory: 'linux-x64', - }, - 'darwin-arm64': { - platform: 'darwin', - arch: 'arm64', - libsqlPackageName: '@libsql/darwin-arm64', - esbuildPackageName: '@esbuild/darwin-arm64', - nativePrebuildDirectory: 'darwin-arm64', - }, - 'win32-x64': { - platform: 'win32', - arch: 'x64', - libsqlPackageName: '@libsql/win32-x64-msvc', - esbuildPackageName: '@esbuild/win32-x64', - nativePrebuildDirectory: 'win32-x64', - }, -} satisfies Record; - -const TREE_SITTER_GRAMMAR_PACKAGE_NAMES = [ - 'tree-sitter-c', - 'tree-sitter-cpp', - 'tree-sitter-c-sharp', - '@driftlog/tree-sitter-dart', - 'tree-sitter-go', - 'tree-sitter-haskell', - 'tree-sitter-java', - 'tree-sitter-javascript', - '@tree-sitter-grammars/tree-sitter-kotlin', - '@tree-sitter-grammars/tree-sitter-lua', - 'tree-sitter-objc', - 'tree-sitter-php', - 'tree-sitter-python', - 'tree-sitter-ruby', - 'tree-sitter-rust', - 'tree-sitter-scala', - 'tree-sitter-swift', - 'tree-sitter-typescript', -] as const; - -export const EXTENSION_RUNTIME_PACKAGE_NAMES = [ - 'libsql', - '@neon-rs/load', - 'detect-libc', - 'esbuild', - 'material-icon-theme', - 'node-gyp-build', - 'tree-sitter', - ...TREE_SITTER_GRAMMAR_PACKAGE_NAMES, -] as const; - -type RuntimeTargetOptions = { - environment?: Partial>; - platform?: NodeJS.Platform; - arch?: string; -}; - -type RuntimePackagePlan = { - packageName: string; - relativeFilePaths: string[]; - resolvePackageRootPath?: (packageName: string) => string; -}; - -function isExtensionRuntimeTarget(value: string): value is ExtensionRuntimeTarget { - return EXTENSION_RUNTIME_TARGETS.includes(value as ExtensionRuntimeTarget); -} - -export function resolveExtensionRuntimeTarget({ - environment = process.env, - platform = process.platform, - arch = process.arch, -}: RuntimeTargetOptions = {}): ExtensionRuntimeTarget { - const requestedTargets = environment.CODEGRAPHY_VSIX_TARGETS - ?.split(',') - .map(target => target.trim()) - .filter(Boolean); - - if (requestedTargets && requestedTargets.length !== 1) { - throw new Error('Extension runtime staging requires exactly one VSIX target.'); - } - - const hostTarget = EXTENSION_RUNTIME_TARGETS.find((target) => { - const config = RUNTIME_TARGET_CONFIG[target]; - return config.platform === platform && config.arch === arch; - }); - if (!hostTarget) { - throw new Error( - `Unsupported extension runtime host: ${platform}-${arch}. ` - + `Supported targets are ${EXTENSION_RUNTIME_TARGETS.join(', ')}.`, - ); - } - - const requestedTarget = requestedTargets?.[0]; - if (!requestedTarget) { - return hostTarget; - } - if (!isExtensionRuntimeTarget(requestedTarget)) { - throw new Error( - `Unsupported extension runtime target: ${requestedTarget}. ` - + `Supported targets are ${EXTENSION_RUNTIME_TARGETS.join(', ')}.`, - ); - } - if (requestedTarget !== hostTarget) { - throw new Error( - `Cannot stage ${requestedTarget} native files on ${hostTarget}. ` - + 'Build the VSIX on its matching host.', - ); - } - - return requestedTarget; -} - -export function getExtensionRuntimePackageNames( - target: ExtensionRuntimeTarget, -): string[] { - const config = RUNTIME_TARGET_CONFIG[target]; - return [ - ...EXTENSION_RUNTIME_PACKAGE_NAMES, - config.libsqlPackageName, - config.esbuildPackageName, - ]; -} - -function resolvePackageEntryPath(packageName: string): string { - return require.resolve(packageName); -} - -export function resolveRuntimePackageRootPath( - packageName: string, - resolveEntryPath: (packageName: string) => string = resolvePackageEntryPath, -): string { - let currentPath = path.dirname(resolveEntryPath(packageName)); - - while (!fs.existsSync(path.join(currentPath, 'package.json'))) { - const parentPath = path.dirname(currentPath); - if (parentPath === currentPath) { - throw new Error(`Unable to find package root for ${packageName}`); - } - currentPath = parentPath; - } - - return currentPath; -} - -function resolveEsbuildBinaryPackageRootPath(packageName: string): string { - const manifestPath = require.resolve(`${packageName}/package.json`, { - paths: [require.resolve('esbuild')], - }); - return path.dirname(manifestPath); -} +import { + createRuntimePackagePlans, + resolveRuntimePackageRootPath, +} from './runtimePackagePlan'; +import { + resolveExtensionRuntimeTarget, + type ExtensionRuntimeTarget, +} from './runtimeTarget'; + +export { + EXTENSION_RUNTIME_PACKAGE_NAMES, + getExtensionRuntimePackageNames, + resolveRuntimePackageRootPath, +} from './runtimePackagePlan'; +export { + EXTENSION_RUNTIME_TARGETS, + resolveExtensionRuntimeTarget, +} from './runtimeTarget'; export function getVendoredPackageRootPath( outputFilePath: string, @@ -236,130 +79,6 @@ export function copyRuntimePackage( return targetPath; } -function listRelativeFiles( - packageRootPath: string, - relativeDirectoryPath: string, -): string[] { - const directoryPath = path.join(packageRootPath, relativeDirectoryPath); - if (!fs.existsSync(directoryPath)) { - return []; - } - - return fs.readdirSync(directoryPath, { recursive: true, withFileTypes: true }) - .filter(entry => entry.isFile()) - .map(entry => path.relative(packageRootPath, path.join(entry.parentPath, entry.name)) - .split(path.sep) - .join('/')); -} - -function legalFilePaths(packageRootPath: string): string[] { - return fs.readdirSync(packageRootPath, { withFileTypes: true }) - .filter(entry => entry.isFile() && /^(?:licen[cs]e|notice)(?:\.|$)/i.test(entry.name)) - .map(entry => entry.name); -} - -function staticPackagePlan( - packageName: string, - relativeFilePaths: readonly string[], - resolvePackageRootPath?: (packageName: string) => string, -): RuntimePackagePlan { - const packageRootPath = (resolvePackageRootPath ?? resolveRuntimePackageRootPath)(packageName); - return { - packageName, - relativeFilePaths: [...relativeFilePaths, ...legalFilePaths(packageRootPath)], - ...(resolvePackageRootPath ? { resolvePackageRootPath } : {}), - }; -} - -function treeSitterGrammarPlan( - packageName: string, - nativePrebuildDirectory: string, -): RuntimePackagePlan { - const packageRootPath = resolveRuntimePackageRootPath(packageName); - const bindingFilePaths = listRelativeFiles(packageRootPath, 'bindings/node') - .filter(filePath => filePath.endsWith('.js') && !filePath.endsWith('_test.js')); - const nodeTypeFilePaths = listRelativeFiles(packageRootPath, '') - .filter(filePath => filePath.endsWith('src/node-types.json')); - const prebuildFilePaths = listRelativeFiles( - packageRootPath, - path.join('prebuilds', nativePrebuildDirectory), - ).filter(filePath => filePath.endsWith('.node')); - const builtFilePaths = packageName === '@driftlog/tree-sitter-dart' - ? listRelativeFiles(packageRootPath, path.join('build', 'Release')) - .filter(filePath => filePath.endsWith('.node')) - : []; - const rootEntrypointFilePaths = packageName === '@driftlog/tree-sitter-dart' - ? ['index.js'] - : []; - const nativeFilePaths = [...prebuildFilePaths, ...builtFilePaths]; - - if (nativeFilePaths.length === 0) { - throw new Error(`No ${nativePrebuildDirectory} native binding found for ${packageName}.`); - } - - return staticPackagePlan(packageName, [ - 'package.json', - ...rootEntrypointFilePaths, - ...bindingFilePaths, - ...nodeTypeFilePaths, - ...nativeFilePaths, - ]); -} - -function createRuntimePackagePlans(target: ExtensionRuntimeTarget): RuntimePackagePlan[] { - const config = RUNTIME_TARGET_CONFIG[target]; - const materialIconThemeRoot = resolveRuntimePackageRootPath('material-icon-theme'); - - return [ - staticPackagePlan('libsql', [ - 'package.json', - 'index.js', - 'auth.js', - 'promise.js', - 'sqlite-error.js', - ]), - staticPackagePlan('@neon-rs/load', ['package.json', 'dist/index.js']), - staticPackagePlan('detect-libc', [ - 'package.json', - 'lib/detect-libc.js', - 'lib/filesystem.js', - 'lib/process.js', - ]), - staticPackagePlan('esbuild', [ - 'package.json', - 'lib/main.js', - ]), - staticPackagePlan(config.esbuildPackageName, [ - 'package.json', - config.platform === 'win32' ? 'esbuild.exe' : 'bin/esbuild', - ], resolveEsbuildBinaryPackageRootPath), - staticPackagePlan(config.libsqlPackageName, ['package.json', 'index.node']), - staticPackagePlan('material-icon-theme', [ - 'package.json', - 'dist/material-icons.json', - ...listRelativeFiles(materialIconThemeRoot, 'icons'), - ]), - staticPackagePlan('node-gyp-build', [ - 'package.json', - 'index.js', - 'node-gyp-build.js', - ]), - staticPackagePlan('tree-sitter', [ - 'package.json', - 'index.js', - path.join( - 'prebuilds', - config.nativePrebuildDirectory, - 'tree-sitter.node', - ), - ]), - ...TREE_SITTER_GRAMMAR_PACKAGE_NAMES.map(packageName => treeSitterGrammarPlan( - packageName, - config.nativePrebuildDirectory, - )), - ]; -} - export function syncExtensionRuntimePackages( outputFilePath: string, target: ExtensionRuntimeTarget = resolveExtensionRuntimeTarget(), diff --git a/packages/extension/scripts/runtimeTarget.ts b/packages/extension/scripts/runtimeTarget.ts new file mode 100644 index 0000000000..ba983b5b40 --- /dev/null +++ b/packages/extension/scripts/runtimeTarget.ts @@ -0,0 +1,94 @@ +export const EXTENSION_RUNTIME_TARGETS = [ + 'linux-x64', + 'darwin-arm64', + 'win32-x64', +] as const; + +export type ExtensionRuntimeTarget = typeof EXTENSION_RUNTIME_TARGETS[number]; + +export type RuntimeTargetConfig = { + platform: NodeJS.Platform; + arch: string; + libsqlPackageName: string; + esbuildPackageName: string; + nativePrebuildDirectory: string; +}; + +export const RUNTIME_TARGET_CONFIG = { + 'linux-x64': { + platform: 'linux', + arch: 'x64', + libsqlPackageName: '@libsql/linux-x64-gnu', + esbuildPackageName: '@esbuild/linux-x64', + nativePrebuildDirectory: 'linux-x64', + }, + 'darwin-arm64': { + platform: 'darwin', + arch: 'arm64', + libsqlPackageName: '@libsql/darwin-arm64', + esbuildPackageName: '@esbuild/darwin-arm64', + nativePrebuildDirectory: 'darwin-arm64', + }, + 'win32-x64': { + platform: 'win32', + arch: 'x64', + libsqlPackageName: '@libsql/win32-x64-msvc', + esbuildPackageName: '@esbuild/win32-x64', + nativePrebuildDirectory: 'win32-x64', + }, +} satisfies Record; + +type RuntimeTargetOptions = { + environment?: Partial>; + platform?: NodeJS.Platform; + arch?: string; +}; + +function isExtensionRuntimeTarget(value: string): value is ExtensionRuntimeTarget { + return EXTENSION_RUNTIME_TARGETS.includes(value as ExtensionRuntimeTarget); +} + +export function resolveExtensionRuntimeTarget({ + environment = process.env, + platform = process.platform, + arch = process.arch, +}: RuntimeTargetOptions = {}): ExtensionRuntimeTarget { + const requestedTargets = environment.CODEGRAPHY_VSIX_TARGETS + ?.split(',') + .map(target => target.trim()) + .filter(Boolean); + + if (requestedTargets && requestedTargets.length !== 1) { + throw new Error('Extension runtime staging requires exactly one VSIX target.'); + } + + const hostTarget = EXTENSION_RUNTIME_TARGETS.find((target) => { + const config = RUNTIME_TARGET_CONFIG[target]; + return config.platform === platform && config.arch === arch; + }); + if (!hostTarget) { + throw new Error( + `Unsupported extension runtime host: ${platform}-${arch}. ` + + `Supported targets are ${EXTENSION_RUNTIME_TARGETS.join(', ')}.`, + ); + } + + const requestedTarget = requestedTargets?.[0]; + if (!requestedTarget) { + return hostTarget; + } + if (!isExtensionRuntimeTarget(requestedTarget)) { + throw new Error( + `Unsupported extension runtime target: ${requestedTarget}. ` + + `Supported targets are ${EXTENSION_RUNTIME_TARGETS.join(', ')}.`, + ); + } + if (requestedTarget !== hostTarget) { + throw new Error( + `Cannot stage ${requestedTarget} native files on ${hostTarget}. ` + + 'Build the VSIX on its matching host.', + ); + } + + return requestedTarget; +} diff --git a/packages/extension/tests/extension/build/runtimePackagePlan.test.ts b/packages/extension/tests/extension/build/runtimePackagePlan.test.ts new file mode 100644 index 0000000000..2ddc61fcd0 --- /dev/null +++ b/packages/extension/tests/extension/build/runtimePackagePlan.test.ts @@ -0,0 +1,55 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { + EXTENSION_RUNTIME_PACKAGE_NAMES, + getExtensionRuntimePackageNames, + resolveRuntimePackageRootPath, +} from '../../../scripts/externalPackages'; + +describe('extension runtime package plan', () => { + it('resolves the installed SQLite package root', () => { + const packageRootPath = resolveRuntimePackageRootPath('libsql'); + + expect(path.basename(packageRootPath)).toBe('libsql'); + expect(fs.existsSync(path.join(packageRootPath, 'package.json'))).toBe(true); + }); + + it('vendors every Tree-sitter grammar needed by the core runtime', () => { + expect(getExtensionRuntimePackageNames('darwin-arm64')).toEqual( + expect.arrayContaining([ + 'libsql', + '@neon-rs/load', + 'detect-libc', + 'esbuild', + '@libsql/darwin-arm64', + '@esbuild/darwin-arm64', + 'material-icon-theme', + 'tree-sitter', + 'tree-sitter-c', + 'tree-sitter-cpp', + 'tree-sitter-c-sharp', + '@driftlog/tree-sitter-dart', + 'tree-sitter-go', + 'tree-sitter-haskell', + 'tree-sitter-java', + 'tree-sitter-javascript', + '@tree-sitter-grammars/tree-sitter-kotlin', + '@tree-sitter-grammars/tree-sitter-lua', + 'tree-sitter-objc', + 'tree-sitter-php', + 'tree-sitter-python', + 'tree-sitter-ruby', + 'tree-sitter-rust', + 'tree-sitter-scala', + 'tree-sitter-swift', + 'tree-sitter-typescript', + ]), + ); + }); + + it('resolves every vendored runtime package from the extension package', () => { + for (const packageName of EXTENSION_RUNTIME_PACKAGE_NAMES) { + expect(() => resolveRuntimePackageRootPath(packageName)).not.toThrow(); + } + }); +}); diff --git a/packages/extension/tests/extension/build/runtimePackages.test.ts b/packages/extension/tests/extension/build/runtimePackages.test.ts index e9fd691dac..21a76e7fbc 100644 --- a/packages/extension/tests/extension/build/runtimePackages.test.ts +++ b/packages/extension/tests/extension/build/runtimePackages.test.ts @@ -5,11 +5,7 @@ import { fileURLToPath } from 'node:url'; import { copyRuntimePackage, EXTENSION_EXTERNAL_PACKAGE_NAMES, - EXTENSION_RUNTIME_PACKAGE_NAMES, - getExtensionRuntimePackageNames, getVendoredPackageRootPath, - resolveExtensionRuntimeTarget, - resolveRuntimePackageRootPath, } from '../../../scripts/externalPackages'; const EXTENSION_PACKAGE_ROOT = path.resolve( @@ -18,13 +14,6 @@ const EXTENSION_PACKAGE_ROOT = path.resolve( ); describe('runtime package build support', () => { - it('resolves the installed SQLite package root', () => { - const packageRootPath = resolveRuntimePackageRootPath('libsql'); - - expect(path.basename(packageRootPath)).toBe('libsql'); - expect(fs.existsSync(path.join(packageRootPath, 'package.json'))).toBe(true); - }); - it('copies only selected runtime files and clears stale package output', () => { const tempDirectoryPath = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraphy-runtime-build-')); const outputFilePath = path.join(tempDirectoryPath, 'dist', 'extension.js'); @@ -84,66 +73,6 @@ describe('runtime package build support', () => { expect(copiedPackageJson.main).toBe('bindings/node/index.js'); }); - it('selects target-specific native runtime packages', () => { - expect(getExtensionRuntimePackageNames('darwin-arm64')).toEqual( - expect.arrayContaining([ - '@libsql/darwin-arm64', - '@esbuild/darwin-arm64', - 'tree-sitter', - ]), - ); - expect(getExtensionRuntimePackageNames('darwin-arm64')).not.toContain( - '@libsql/linux-x64-gnu', - ); - }); - - it('uses one explicit VSIX target when the build provides it', () => { - expect(resolveExtensionRuntimeTarget({ - environment: { CODEGRAPHY_VSIX_TARGETS: 'darwin-arm64' }, - platform: 'darwin', - arch: 'arm64', - })).toBe('darwin-arm64'); - }); - - it('vendors every Tree-sitter grammar needed by the core runtime', () => { - expect(getExtensionRuntimePackageNames('darwin-arm64')).toEqual( - expect.arrayContaining([ - 'libsql', - '@neon-rs/load', - 'detect-libc', - 'esbuild', - '@libsql/darwin-arm64', - '@esbuild/darwin-arm64', - 'material-icon-theme', - 'tree-sitter', - 'tree-sitter-c', - 'tree-sitter-cpp', - 'tree-sitter-c-sharp', - '@driftlog/tree-sitter-dart', - 'tree-sitter-go', - 'tree-sitter-haskell', - 'tree-sitter-java', - 'tree-sitter-javascript', - '@tree-sitter-grammars/tree-sitter-kotlin', - '@tree-sitter-grammars/tree-sitter-lua', - 'tree-sitter-objc', - 'tree-sitter-php', - 'tree-sitter-python', - 'tree-sitter-ruby', - 'tree-sitter-rust', - 'tree-sitter-scala', - 'tree-sitter-swift', - 'tree-sitter-typescript', - ]), - ); - }); - - it('resolves every vendored runtime package from the extension package', () => { - for (const packageName of EXTENSION_RUNTIME_PACKAGE_NAMES) { - expect(() => resolveRuntimePackageRootPath(packageName)).not.toThrow(); - } - }); - it('bundles core packages while externalizing only VS Code and native runtime packages', () => { expect(EXTENSION_EXTERNAL_PACKAGE_NAMES).toEqual( expect.arrayContaining([ diff --git a/packages/extension/tests/extension/build/runtimeTarget.test.ts b/packages/extension/tests/extension/build/runtimeTarget.test.ts new file mode 100644 index 0000000000..4b4d19d660 --- /dev/null +++ b/packages/extension/tests/extension/build/runtimeTarget.test.ts @@ -0,0 +1,27 @@ +import { + getExtensionRuntimePackageNames, + resolveExtensionRuntimeTarget, +} from '../../../scripts/externalPackages'; + +describe('extension runtime target', () => { + it('selects target-specific native runtime packages', () => { + expect(getExtensionRuntimePackageNames('darwin-arm64')).toEqual( + expect.arrayContaining([ + '@libsql/darwin-arm64', + '@esbuild/darwin-arm64', + 'tree-sitter', + ]), + ); + expect(getExtensionRuntimePackageNames('darwin-arm64')).not.toContain( + '@libsql/linux-x64-gnu', + ); + }); + + it('uses one explicit VSIX target when the build provides it', () => { + expect(resolveExtensionRuntimeTarget({ + environment: { CODEGRAPHY_VSIX_TARGETS: 'darwin-arm64' }, + platform: 'darwin', + arch: 'arm64', + })).toBe('darwin-arm64'); + }); +}); From c468e117a06f66ae801edef867823ac9b92d005a Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 16:45:54 -0700 Subject: [PATCH 06/14] fix: align Node runtime requirements --- .changeset/broad-node-runtime-support.md | 17 +++++++++++++++++ .github/workflows/ci.yml | 18 +++++++++--------- .github/workflows/release.yml | 4 ++-- .node-version | 1 - .nvmrc | 1 - CONTRIBUTING.md | 7 +++---- README.md | 6 +++--- apps/web/package.json | 2 +- docs/PLUGINS.md | 2 +- docs/RELEASING.md | 2 +- docs/quality/README.md | 2 +- package.json | 6 +++--- packages/core/README.md | 2 +- packages/core/package.json | 2 +- packages/core/src/cli/doctor/command.ts | 14 ++++++++++---- packages/core/tests/cli/doctor/command.test.ts | 15 ++++++++++++++- packages/extension-plugin-api/package.json | 2 +- packages/extension/docs/README.md | 2 +- packages/extension/package.json | 2 +- packages/extension/scripts/buildExtension.ts | 1 + packages/graph-renderer/package.json | 2 +- packages/plugin-api/package.json | 2 +- packages/plugin-godot/package.json | 4 ++-- packages/plugin-markdown/package.json | 4 ++-- packages/plugin-particles/build.mjs | 2 +- packages/plugin-particles/package.json | 2 +- packages/plugin-svelte/build.mjs | 2 +- packages/plugin-svelte/package.json | 2 +- packages/plugin-typescript/build.mjs | 2 +- packages/plugin-typescript/package.json | 2 +- packages/plugin-unity/package.json | 4 ++-- packages/plugin-vue/build.mjs | 2 +- packages/plugin-vue/package.json | 2 +- packages/tldraw/README.md | 2 +- packages/tldraw/package.json | 2 +- packages/tldraw/scripts/build.ts | 2 +- pnpm-lock.yaml | 2 +- scripts/smoke-installed-vsix.mjs | 2 +- tests/release/corePackage.test.mjs | 2 +- tests/release/pluginApiCompatibility.test.mjs | 2 +- 40 files changed, 94 insertions(+), 60 deletions(-) create mode 100644 .changeset/broad-node-runtime-support.md delete mode 100644 .node-version delete mode 100644 .nvmrc diff --git a/.changeset/broad-node-runtime-support.md b/.changeset/broad-node-runtime-support.md new file mode 100644 index 0000000000..a12cf1db64 --- /dev/null +++ b/.changeset/broad-node-runtime-support.md @@ -0,0 +1,17 @@ +--- +"@codegraphy-dev/core": patch +"@codegraphy-dev/extension": patch +"@codegraphy-dev/extension-plugin-api": patch +"@codegraphy-dev/graph-renderer": patch +"@codegraphy-dev/plugin-api": patch +"@codegraphy-dev/plugin-godot": patch +"@codegraphy-dev/plugin-markdown": patch +"@codegraphy-dev/plugin-particles": patch +"@codegraphy-dev/plugin-svelte": patch +"@codegraphy-dev/plugin-typescript": patch +"@codegraphy-dev/plugin-unity": patch +"@codegraphy-dev/plugin-vue": patch +"@codegraphy-dev/tldraw": patch +--- + +Require Node.js 22.14.0 or newer across CodeGraphy packages without setting a maximum version. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e82844808e..cafbc7cd46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: 'pnpm' - name: Install dependencies @@ -68,7 +68,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: 'pnpm' - name: Install dependencies @@ -124,7 +124,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: 'pnpm' - name: Install dependencies @@ -161,7 +161,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: 'pnpm' - name: Install dependencies @@ -201,7 +201,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: 'pnpm' - name: Install dependencies @@ -376,7 +376,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: 'pnpm' - name: Install dependencies @@ -466,7 +466,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: 'pnpm' - name: Install dependencies @@ -514,7 +514,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: 'pnpm' - name: Install dependencies @@ -558,7 +558,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: 'pnpm' - name: Install dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b6835a823c..a5354d9cad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: pnpm registry-url: https://registry.npmjs.org @@ -101,7 +101,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 22.22.0 + node-version: 'lts/*' cache: pnpm registry-url: https://registry.npmjs.org diff --git a/.node-version b/.node-version deleted file mode 100644 index 85e502778f..0000000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ -22.22.0 diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 85e502778f..0000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -22.22.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 93bed89212..e4efe2ea8b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,16 +6,15 @@ CodeGraphy accepts focused changes through pull requests against `main`. ### Prerequisites -- Node.js 20 or newer; use the repository-pinned Node 22 runtime +- Node.js 22.14.0 or newer; use an active LTS release - pnpm 10+ -- VS Code 1.85+ +- VS Code 1.101+ ### Getting started 1. Fork and clone the repository. 2. Install dependencies: ```bash - nvm use pnpm install ``` 3. Build the project: @@ -112,7 +111,7 @@ pnpm --filter @codegraphy-dev/extension exec playwright install chromium # Brow pnpm --filter @codegraphy-dev/extension exec vitest run --config vitest.config.ts tests/path/to/file.test.ts ``` -The repo pins Node in [`.nvmrc`](./.nvmrc) and [`.node-version`](./.node-version). Use that exact runtime before running release or quality-tool commands. +Use an active Node.js LTS release for repository development, release, and quality-tool commands. Published packages require Node.js 22.14.0 or newer and do not set a maximum version. ## Reporting bugs diff --git a/README.md b/README.md index ec70167c88..5f47a869ba 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ The extension publishes native runtime targets for Linux x64, macOS Apple Silico ### CLI and Plugins -The terminal CLI supports Node.js 20 through 22. Node 22 LTS is recommended. +The terminal CLI supports Node.js 22.14.0 and newer. An active LTS release is recommended. ```bash npm install -g @codegraphy-dev/core @@ -98,8 +98,8 @@ codegraphy -C /path/to/workspace index ### tldraw Offline -The first tldraw interface supports macOS and Node.js 20 through 22. Node.js 22 -LTS is recommended. Install the +The first tldraw interface supports macOS and Node.js 22.14.0 and newer. An active +LTS release is recommended. Install the [tldraw offline desktop app](https://www.tldraw.com/), Core, and the interface. Then run the launcher from the workspace to index: diff --git a/apps/web/package.json b/apps/web/package.json index 5987dab2c2..7bb43e2f33 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -5,7 +5,7 @@ "private": true, "type": "module", "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "scripts": { "dev": "next dev", diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md index 70af132352..98d83d0d4e 100644 --- a/docs/PLUGINS.md +++ b/docs/PLUGINS.md @@ -231,7 +231,7 @@ Keep one package-owned `build` command. A small Core plugin can use: ```json { "scripts": { - "build": "tsc -p tsconfig.build.json && esbuild src/plugin.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/plugin.js" + "build": "tsc -p tsconfig.build.json && esbuild src/plugin.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/plugin.js" } } ``` diff --git a/docs/RELEASING.md b/docs/RELEASING.md index e3ea24f72b..ba90eeebc7 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -17,7 +17,7 @@ The root package version stays pinned as monorepo workspace metadata. `scripts/r ## Prepare a Release -Use a clean checkout of `main` with the repository-pinned Node and pnpm versions. +Use a clean checkout of `main` with an active Node.js LTS release and the repository-pinned pnpm version. ```bash pnpm install --frozen-lockfile diff --git a/docs/quality/README.md b/docs/quality/README.md index 0e50d8c00f..13f903aced 100644 --- a/docs/quality/README.md +++ b/docs/quality/README.md @@ -52,7 +52,7 @@ Implementation now lives in the external `@poleski/quality-tools` package. Extension-specific architecture and lifecycle notes live in `packages/extension/docs/`. -Run these commands with the repository-pinned Node runtime from [`.nvmrc`](../../.nvmrc). `@poleski/quality-tools` uses `path.matchesGlob` and requires Node 20 or newer. +Run these commands with an active Node.js LTS release. `@poleski/quality-tools` requires Node 22.22.0 or newer. ## Workflow diff --git a/package.json b/package.json index b179956948..62dce63c20 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,8 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=20", - "vscode": "^1.85.0" + "node": ">=22.14.0", + "vscode": "^1.101.0" }, "files": [ "dist/extension.js", @@ -238,7 +238,7 @@ "@next/eslint-plugin-next": "^16.2.9", "@poleski/quality-tools": "0.2.1", "@types/node": "^20.11.0", - "@types/vscode": "^1.85.0", + "@types/vscode": "^1.101.0", "@vitest/coverage-istanbul": "^3.2.4", "@vscode/vsce": "^3.7.1", "esbuild": "^0.25.0", diff --git a/packages/core/README.md b/packages/core/README.md index d448463787..7d50597eeb 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -4,7 +4,7 @@ Shared CodeGraphy engine package for workspace indexing, Graph Cache access, plu This package is the headless core used by the VS Code extension and CLI. -The published CLI supports Node.js 20 and newer. An active Long-Term Support release is recommended. +The published CLI supports Node.js 22.14.0 and newer. An active Long-Term Support release is recommended. The VS Code extension bundles this package for extension runtime behavior. Users install `@codegraphy-dev/core` globally only when they want terminal workflows such as Indexing, diagnostics, graph queries, Graph Scope and filter configuration, plugin registration, or workspace plugin enablement. diff --git a/packages/core/package.json b/packages/core/package.json index 1e7cfbbe13..99fea1432d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -28,7 +28,7 @@ } }, "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/core/src/cli/doctor/command.ts b/packages/core/src/cli/doctor/command.ts index 599fd3ea13..8239ad53aa 100644 --- a/packages/core/src/cli/doctor/command.ts +++ b/packages/core/src/cli/doctor/command.ts @@ -27,6 +27,13 @@ function isBooleanRecord(value: unknown): boolean { return isRecord(value) && Object.values(value).every(entry => typeof entry === 'boolean'); } +export function isSupportedNodeRuntime(version: string): boolean { + const [majorText, minorText] = version.split('.'); + const major = Number(majorText); + const minor = Number(minorText); + return major > 22 || (major === 22 && minor >= 14); +} + function validateKnownSettings(value: Record): string | undefined { const stringArrays = ['include', 'filterPatterns', 'disabledCustomFilterPatterns'] as const; for (const key of stringArrays) { @@ -92,8 +99,7 @@ function readSettingsCheck(workspaceRoot: string): Record { export function runDoctorCommand(command: CliCommand): CommandExecutionResult { const workspaceRoot = resolveCodeGraphyWorkspacePath(command.workspacePath, process.cwd()); - const runtimeMajor = Number(process.versions.node.split('.')[0]); - const runtimeOk = runtimeMajor >= 20; + const runtimeOk = isSupportedNodeRuntime(process.versions.node); const settingsCheck = readSettingsCheck(workspaceRoot); const status = readCodeGraphyWorkspaceStatus(workspaceRoot); const meta = readCodeGraphyWorkspaceMeta(workspaceRoot); @@ -108,8 +114,8 @@ export function runDoctorCommand(command: CliCommand): CommandExecutionResult { runtime: { ok: runtimeOk, version: process.version, - supported: '>=20', - ...(runtimeOk ? {} : { action: 'Use Node.js 20 or newer.' }), + supported: '>=22.14.0', + ...(runtimeOk ? {} : { action: 'Use Node.js 22.14.0 or newer.' }), }, settings: settingsCheck, cache: createDoctorCacheCheck({ diff --git a/packages/core/tests/cli/doctor/command.test.ts b/packages/core/tests/cli/doctor/command.test.ts index f05d757297..216e9beabc 100644 --- a/packages/core/tests/cli/doctor/command.test.ts +++ b/packages/core/tests/cli/doctor/command.test.ts @@ -6,6 +6,19 @@ import { describe, expect, it, vi } from 'vitest'; import { runCli } from '../../../src/cli/run'; import { requestCodeGraphyIndexWorkspace } from '../../../src/workspace/requestIndexing'; import { getWorkspaceAnalysisDatabasePath } from '../../../src/graphCache/database/storage'; +import { isSupportedNodeRuntime } from '../../../src/cli/doctor/command'; + +describe('isSupportedNodeRuntime', () => { + it.each([ + ['20.20.1', false], + ['22.13.1', false], + ['22.14.0', true], + ['22.22.0', true], + ['24.0.0', true], + ])('reports whether Node.js %s satisfies the runtime contract', (version, expected) => { + expect(isSupportedNodeRuntime(version)).toBe(expected); + }); +}); describe('cli doctor', () => { it('reports index metadata and normalized graph record counts', async () => { @@ -166,7 +179,7 @@ describe('cli doctor', () => { details: { healthy: false, checks: { - runtime: { ok: true, supported: '>=20' }, + runtime: { ok: true, supported: '>=22.14.0' }, settings: { ok: false, action: 'Run `codegraphy index` to create workspace settings.' }, cache: { ok: false, state: 'missing', action: 'Run `codegraphy index`.' }, plugins: { ok: true, warnings: [] }, diff --git a/packages/extension-plugin-api/package.json b/packages/extension-plugin-api/package.json index e052679db3..6c0324cb92 100644 --- a/packages/extension-plugin-api/package.json +++ b/packages/extension-plugin-api/package.json @@ -3,7 +3,7 @@ "version": "1.1.0", "description": "Type definitions for plugins hosted by the CodeGraphy VS Code extension", "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "repository": { "type": "git", diff --git a/packages/extension/docs/README.md b/packages/extension/docs/README.md index 17374c5ba8..dff0dc083c 100644 --- a/packages/extension/docs/README.md +++ b/packages/extension/docs/README.md @@ -18,7 +18,7 @@ Current extension behavior to keep in mind while reading the package docs: - The extension loads workspace-local CSS Snippets listed under `cssSnippets` in `.codegraphy/settings.json`. They can style stable `data-codegraphy-*` hooks without rebuilding a VS Code theme. - the language plugins in `packages/plugin-*` are now mostly for ecosystem filters and optional semantic enrichment rather than baseline file coloring -The published CLI supports Node 20 and newer. Local tooling and CI use the repository-pinned Node runtime. +The published CLI supports Node.js 22.14.0 and newer. Local tooling and CI use an active Node.js LTS release. The source tree is split by runtime boundary: diff --git a/packages/extension/package.json b/packages/extension/package.json index 4f93b7117f..f940deae00 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -5,7 +5,7 @@ "description": "CodeGraphy VS Code extension core", "license": "MIT", "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "scripts": { "build": "pnpm run build:extension && pnpm run build:webview", diff --git a/packages/extension/scripts/buildExtension.ts b/packages/extension/scripts/buildExtension.ts index a3f0971389..92e3db6f62 100644 --- a/packages/extension/scripts/buildExtension.ts +++ b/packages/extension/scripts/buildExtension.ts @@ -16,6 +16,7 @@ const buildOptions: esbuild.BuildOptions = { external: [...EXTENSION_EXTERNAL_PACKAGE_NAMES], format: 'cjs', platform: 'node', + target: 'node22', }; function syncRuntimePackages(): void { diff --git a/packages/graph-renderer/package.json b/packages/graph-renderer/package.json index 40bb1c2c29..15c07ee9e1 100644 --- a/packages/graph-renderer/package.json +++ b/packages/graph-renderer/package.json @@ -27,7 +27,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "keywords": [ "codegraphy", diff --git a/packages/plugin-api/package.json b/packages/plugin-api/package.json index ee70dbc9ef..b812ddc7db 100644 --- a/packages/plugin-api/package.json +++ b/packages/plugin-api/package.json @@ -3,7 +3,7 @@ "version": "7.0.0", "description": "Type definitions for CodeGraphy plugins", "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "repository": { "type": "git", diff --git a/packages/plugin-godot/package.json b/packages/plugin-godot/package.json index 18d2445de3..d4de3c6bc6 100644 --- a/packages/plugin-godot/package.json +++ b/packages/plugin-godot/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "keywords": [ "codegraphy", @@ -42,7 +42,7 @@ "LICENSE" ], "scripts": { - "build": "tsc -p tsconfig.build.json && esbuild src/plugin.ts src/extension.ts --bundle --platform=node --format=esm --target=node20 --sourcemap --outdir=dist", + "build": "tsc -p tsconfig.build.json && esbuild src/plugin.ts src/extension.ts --bundle --platform=node --format=esm --target=node22 --sourcemap --outdir=dist", "test": "vitest run", "lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"__tests__/**/*.ts\"", "typecheck": "tsc --noEmit -p tsconfig.json" diff --git a/packages/plugin-markdown/package.json b/packages/plugin-markdown/package.json index 89e518749b..3fc80224a9 100644 --- a/packages/plugin-markdown/package.json +++ b/packages/plugin-markdown/package.json @@ -13,7 +13,7 @@ } }, "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "publishConfig": { "access": "public" @@ -25,7 +25,7 @@ "LICENSE" ], "scripts": { - "build": "tsc -p tsconfig.build.json && esbuild src/plugin.ts --bundle --platform=node --format=esm --target=node20 --sourcemap --outfile=dist/plugin.js", + "build": "tsc -p tsconfig.build.json && esbuild src/plugin.ts --bundle --platform=node --format=esm --target=node22 --sourcemap --outfile=dist/plugin.js", "lint": "eslint src", "test": "vitest run", "typecheck": "tsc --noEmit -p tsconfig.json" diff --git a/packages/plugin-particles/build.mjs b/packages/plugin-particles/build.mjs index f36085a720..d9b30a4423 100644 --- a/packages/plugin-particles/build.mjs +++ b/packages/plugin-particles/build.mjs @@ -19,7 +19,7 @@ await Promise.all([ format: 'esm', platform: 'node', sourcemap: true, - target: 'node20', + target: 'node22', }), build({ entryPoints: { diff --git a/packages/plugin-particles/package.json b/packages/plugin-particles/package.json index b83c967caf..c654f03814 100644 --- a/packages/plugin-particles/package.json +++ b/packages/plugin-particles/package.json @@ -25,7 +25,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=20.20.0 <23" + "node": ">=22.14.0" }, "keywords": [ "codegraphy", diff --git a/packages/plugin-svelte/build.mjs b/packages/plugin-svelte/build.mjs index 549c0d4b9c..2066ed8e59 100644 --- a/packages/plugin-svelte/build.mjs +++ b/packages/plugin-svelte/build.mjs @@ -27,5 +27,5 @@ await build({ format: 'esm', platform: 'node', sourcemap: true, - target: 'node20', + target: 'node22', }); diff --git a/packages/plugin-svelte/package.json b/packages/plugin-svelte/package.json index 2b91c039f7..86a5b57b67 100644 --- a/packages/plugin-svelte/package.json +++ b/packages/plugin-svelte/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "keywords": [ "codegraphy", diff --git a/packages/plugin-typescript/build.mjs b/packages/plugin-typescript/build.mjs index 549c0d4b9c..2066ed8e59 100644 --- a/packages/plugin-typescript/build.mjs +++ b/packages/plugin-typescript/build.mjs @@ -27,5 +27,5 @@ await build({ format: 'esm', platform: 'node', sourcemap: true, - target: 'node20', + target: 'node22', }); diff --git a/packages/plugin-typescript/package.json b/packages/plugin-typescript/package.json index 1035e6c2fb..276a72f3a5 100644 --- a/packages/plugin-typescript/package.json +++ b/packages/plugin-typescript/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "keywords": [ "codegraphy", diff --git a/packages/plugin-unity/package.json b/packages/plugin-unity/package.json index 218184b2e5..a91af9c531 100644 --- a/packages/plugin-unity/package.json +++ b/packages/plugin-unity/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "keywords": [ "codegraphy", @@ -41,7 +41,7 @@ "LICENSE" ], "scripts": { - "build": "tsc -p tsconfig.build.json && esbuild src/plugin.ts src/extension.ts --bundle --platform=node --format=esm --target=node20 --sourcemap --outdir=dist", + "build": "tsc -p tsconfig.build.json && esbuild src/plugin.ts src/extension.ts --bundle --platform=node --format=esm --target=node22 --sourcemap --outdir=dist", "test": "vitest run", "lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"__tests__/**/*.ts\"", "typecheck": "tsc --noEmit -p tsconfig.json" diff --git a/packages/plugin-vue/build.mjs b/packages/plugin-vue/build.mjs index 58ec49ac6a..5170c1aaeb 100644 --- a/packages/plugin-vue/build.mjs +++ b/packages/plugin-vue/build.mjs @@ -43,5 +43,5 @@ await build({ }, }], sourcemap: true, - target: 'node20', + target: 'node22', }); diff --git a/packages/plugin-vue/package.json b/packages/plugin-vue/package.json index bcbfd63e5c..a6d515092d 100644 --- a/packages/plugin-vue/package.json +++ b/packages/plugin-vue/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "keywords": [ "codegraphy", diff --git a/packages/tldraw/README.md b/packages/tldraw/README.md index d3978e0576..732e30133c 100644 --- a/packages/tldraw/README.md +++ b/packages/tldraw/README.md @@ -9,7 +9,7 @@ force physics inside the saved `.tldraw` document. ## Requirements - macOS -- Node.js 20 through 22. Node.js 22 LTS is recommended. +- Node.js 22.14.0 or newer. An active LTS release is recommended. - [tldraw offline](https://offline.tldraw.com/) installed in `/Applications` The first launcher release supports macOS because it opens documents through diff --git a/packages/tldraw/package.json b/packages/tldraw/package.json index d540abc6a8..00e909f3a4 100644 --- a/packages/tldraw/package.json +++ b/packages/tldraw/package.json @@ -24,7 +24,7 @@ "codegraphy-tldraw": "./dist/codegraphy-tldraw.js" }, "engines": { - "node": ">=20" + "node": ">=22.14.0" }, "os": [ "darwin" diff --git a/packages/tldraw/scripts/build.ts b/packages/tldraw/scripts/build.ts index 8eded8ca5c..2f38317a47 100644 --- a/packages/tldraw/scripts/build.ts +++ b/packages/tldraw/scripts/build.ts @@ -44,7 +44,7 @@ await Promise.all([ bundle: true, platform: 'node', format: 'esm', - target: 'node20', + target: 'node22', sourcemap: true, external, banner: { js: '#!/usr/bin/env node' }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1334993ff..51156174dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,7 +39,7 @@ importers: specifier: ^20.11.0 version: 20.19.37 '@types/vscode': - specifier: ^1.85.0 + specifier: ^1.101.0 version: 1.110.0 '@vitest/coverage-istanbul': specifier: ^3.2.4 diff --git a/scripts/smoke-installed-vsix.mjs b/scripts/smoke-installed-vsix.mjs index 06536d7717..8d2beba931 100644 --- a/scripts/smoke-installed-vsix.mjs +++ b/scripts/smoke-installed-vsix.mjs @@ -258,7 +258,7 @@ async function writeHarnessExtension(harnessPath) { publisher: 'codegraphy', version: '0.0.0', engines: { - vscode: '^1.85.0', + vscode: '^1.101.0', }, activationEvents: [], main: './extension.js', diff --git a/tests/release/corePackage.test.mjs b/tests/release/corePackage.test.mjs index dd29d8029c..6b909d5794 100644 --- a/tests/release/corePackage.test.mjs +++ b/tests/release/corePackage.test.mjs @@ -31,7 +31,7 @@ test('Core package declares the Node range supported by its native dependencies' fs.readFileSync(path.join(repoRoot, 'packages', 'core', 'package.json'), 'utf8'), ); - assert.equal(manifest.engines.node, '>=20'); + assert.equal(manifest.engines.node, '>=22.14.0'); }); test('Core publishes dependency-free Graph Scope defaults as a focused subpath', () => { diff --git a/tests/release/pluginApiCompatibility.test.mjs b/tests/release/pluginApiCompatibility.test.mjs index cdd791cb14..232011f8c8 100644 --- a/tests/release/pluginApiCompatibility.test.mjs +++ b/tests/release/pluginApiCompatibility.test.mjs @@ -67,7 +67,7 @@ test('public plugin API packages include publish and support metadata', () => { for (const packageName of ['plugin-api', 'extension-plugin-api']) { const packageManifest = readJson(`packages/${packageName}/package.json`); - assert.equal(packageManifest.engines?.node, '>=20', `${packageName} Node.js engine`); + assert.equal(packageManifest.engines?.node, '>=22.14.0', `${packageName} Node.js engine`); assert.equal( packageManifest.repository?.url, 'https://github.com/joesobo/CodeGraphyV4.git', From cf40afe7bbe896fbf5ab881109134703c70daa44 Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 16:54:23 -0700 Subject: [PATCH 07/14] fix: stage Parcel watcher runtime --- examples/example-javascript/package.json | 3 ++ examples/example-svelte/package.json | 3 ++ examples/example-typescript/package.json | 3 ++ examples/example-vue/package.json | 3 ++ .../extension/scripts/runtimePackagePlan.ts | 15 +++++++ packages/extension/scripts/runtimeTarget.ts | 4 ++ .../scripts/validateNativeRuntime.mjs | 18 ++++++++ .../package-graph-view-plugin/package.json | 3 ++ .../build/runtimePackagePlan.test.ts | 11 +++++ .../extension/build/runtimeTarget.test.ts | 1 + .../tests/playwrightVscodeConfig.test.ts | 3 +- scripts/validate-vsix-native-artifacts.mjs | 7 ++++ tests/release/vsixNativeArtifacts.test.mjs | 42 +++++++++++++++++++ 13 files changed, 115 insertions(+), 1 deletion(-) diff --git a/examples/example-javascript/package.json b/examples/example-javascript/package.json index 9f7644dcea..c4ba395baa 100644 --- a/examples/example-javascript/package.json +++ b/examples/example-javascript/package.json @@ -1,5 +1,8 @@ { "name": "codegraphy-javascript-example", "private": true, + "engines": { + "node": ">=22.14.0" + }, "type": "module" } diff --git a/examples/example-svelte/package.json b/examples/example-svelte/package.json index 7349dcf3c5..bce77ccb46 100644 --- a/examples/example-svelte/package.json +++ b/examples/example-svelte/package.json @@ -1,6 +1,9 @@ { "name": "codegraphy-svelte-example", "private": true, + "engines": { + "node": ">=22.14.0" + }, "version": "0.0.0", "type": "module", "scripts": { diff --git a/examples/example-typescript/package.json b/examples/example-typescript/package.json index ae270ada43..9af3c0a19a 100644 --- a/examples/example-typescript/package.json +++ b/examples/example-typescript/package.json @@ -1,6 +1,9 @@ { "name": "codegraphy-typescript-example", "private": true, + "engines": { + "node": ">=22.14.0" + }, "scripts": { "start": "tsx src/index.ts", "build": "tsc --noEmit" diff --git a/examples/example-vue/package.json b/examples/example-vue/package.json index d342140615..8bbe5efc51 100644 --- a/examples/example-vue/package.json +++ b/examples/example-vue/package.json @@ -1,6 +1,9 @@ { "name": "codegraphy-vue-example", "private": true, + "engines": { + "node": ">=22.14.0" + }, "version": "0.0.0", "type": "module", "scripts": { diff --git a/packages/extension/scripts/runtimePackagePlan.ts b/packages/extension/scripts/runtimePackagePlan.ts index 7aa3bcb6a8..f31971a921 100644 --- a/packages/extension/scripts/runtimePackagePlan.ts +++ b/packages/extension/scripts/runtimePackagePlan.ts @@ -7,6 +7,7 @@ import { } from './runtimeTarget'; const require = createRequire(import.meta.url); +const requireFromCore = createRequire(require.resolve('@codegraphy-dev/core')); const TREE_SITTER_GRAMMAR_PACKAGE_NAMES = [ 'tree-sitter-c', @@ -74,6 +75,14 @@ function resolveEsbuildBinaryPackageRootPath(packageName: string): string { return path.dirname(manifestPath); } +function resolveParcelWatcherBinaryPackageRootPath(packageName: string): string { + const watcherEntryPath = requireFromCore.resolve('@parcel/watcher'); + const manifestPath = requireFromCore.resolve(`${packageName}/package.json`, { + paths: [path.dirname(watcherEntryPath)], + }); + return path.dirname(manifestPath); +} + function listRelativeFiles( packageRootPath: string, relativeDirectoryPath: string, @@ -152,6 +161,7 @@ export function getExtensionRuntimePackageNames( ...EXTENSION_RUNTIME_PACKAGE_NAMES, config.libsqlPackageName, config.esbuildPackageName, + config.parcelWatcherPackageName, ]; } @@ -185,6 +195,11 @@ export function createRuntimePackagePlans( config.platform === 'win32' ? 'esbuild.exe' : 'bin/esbuild', ], resolveEsbuildBinaryPackageRootPath), staticPackagePlan(config.libsqlPackageName, ['package.json', 'index.node']), + staticPackagePlan( + config.parcelWatcherPackageName, + ['package.json', 'watcher.node'], + resolveParcelWatcherBinaryPackageRootPath, + ), staticPackagePlan('material-icon-theme', [ 'package.json', 'dist/material-icons.json', diff --git a/packages/extension/scripts/runtimeTarget.ts b/packages/extension/scripts/runtimeTarget.ts index ba983b5b40..7f3d60023a 100644 --- a/packages/extension/scripts/runtimeTarget.ts +++ b/packages/extension/scripts/runtimeTarget.ts @@ -11,6 +11,7 @@ export type RuntimeTargetConfig = { arch: string; libsqlPackageName: string; esbuildPackageName: string; + parcelWatcherPackageName: string; nativePrebuildDirectory: string; }; @@ -20,6 +21,7 @@ export const RUNTIME_TARGET_CONFIG = { arch: 'x64', libsqlPackageName: '@libsql/linux-x64-gnu', esbuildPackageName: '@esbuild/linux-x64', + parcelWatcherPackageName: '@parcel/watcher-linux-x64-glibc', nativePrebuildDirectory: 'linux-x64', }, 'darwin-arm64': { @@ -27,6 +29,7 @@ export const RUNTIME_TARGET_CONFIG = { arch: 'arm64', libsqlPackageName: '@libsql/darwin-arm64', esbuildPackageName: '@esbuild/darwin-arm64', + parcelWatcherPackageName: '@parcel/watcher-darwin-arm64', nativePrebuildDirectory: 'darwin-arm64', }, 'win32-x64': { @@ -34,6 +37,7 @@ export const RUNTIME_TARGET_CONFIG = { arch: 'x64', libsqlPackageName: '@libsql/win32-x64-msvc', esbuildPackageName: '@esbuild/win32-x64', + parcelWatcherPackageName: '@parcel/watcher-win32-x64', nativePrebuildDirectory: 'win32-x64', }, } satisfies Record; diff --git a/packages/extension/scripts/validateNativeRuntime.mjs b/packages/extension/scripts/validateNativeRuntime.mjs index d626e8ca6c..243d725036 100644 --- a/packages/extension/scripts/validateNativeRuntime.mjs +++ b/packages/extension/scripts/validateNativeRuntime.mjs @@ -107,6 +107,24 @@ try { console.log(`Loaded vendored SQLite native runtime from ${vendoredSQLitePath}`); +try { + const watcherPackageName = process.platform === 'linux' + ? '@parcel/watcher-linux-x64-glibc' + : process.platform === 'darwin' + ? '@parcel/watcher-darwin-arm64' + : '@parcel/watcher-win32-x64'; + const watcher = vendoredRequire(watcherPackageName); + if ( + typeof watcher.subscribe !== 'function' + || typeof watcher.unsubscribe !== 'function' + ) { + throw new Error('Parcel watcher binding does not expose subscribe and unsubscribe.'); + } +} catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Unable to load the vendored Parcel watcher runtime: ${message}`); +} + try { const Parser = vendoredRequire('tree-sitter'); diff --git a/packages/extension/src/e2e/fixtures/package-graph-view-plugin/package.json b/packages/extension/src/e2e/fixtures/package-graph-view-plugin/package.json index ee69f4a02f..ef6ba43cb0 100644 --- a/packages/extension/src/e2e/fixtures/package-graph-view-plugin/package.json +++ b/packages/extension/src/e2e/fixtures/package-graph-view-plugin/package.json @@ -1,6 +1,9 @@ { "name": "@codegraphy/e2e-graph-view-plugin", "version": "1.0.0", + "engines": { + "node": ">=22.14.0" + }, "type": "module", "exports": "./plugin.js", "codegraphy": { diff --git a/packages/extension/tests/extension/build/runtimePackagePlan.test.ts b/packages/extension/tests/extension/build/runtimePackagePlan.test.ts index 2ddc61fcd0..78a59f816e 100644 --- a/packages/extension/tests/extension/build/runtimePackagePlan.test.ts +++ b/packages/extension/tests/extension/build/runtimePackagePlan.test.ts @@ -5,6 +5,7 @@ import { getExtensionRuntimePackageNames, resolveRuntimePackageRootPath, } from '../../../scripts/externalPackages'; +import { createRuntimePackagePlans } from '../../../scripts/runtimePackagePlan'; describe('extension runtime package plan', () => { it('resolves the installed SQLite package root', () => { @@ -23,6 +24,7 @@ describe('extension runtime package plan', () => { 'esbuild', '@libsql/darwin-arm64', '@esbuild/darwin-arm64', + '@parcel/watcher-darwin-arm64', 'material-icon-theme', 'tree-sitter', 'tree-sitter-c', @@ -47,6 +49,15 @@ describe('extension runtime package plan', () => { ); }); + it('vendors the target Parcel watcher native binding', () => { + const plan = createRuntimePackagePlans('darwin-arm64') + .find(entry => entry.packageName === '@parcel/watcher-darwin-arm64'); + + expect(plan?.relativeFilePaths).toEqual( + expect.arrayContaining(['package.json', 'watcher.node']), + ); + }); + it('resolves every vendored runtime package from the extension package', () => { for (const packageName of EXTENSION_RUNTIME_PACKAGE_NAMES) { expect(() => resolveRuntimePackageRootPath(packageName)).not.toThrow(); diff --git a/packages/extension/tests/extension/build/runtimeTarget.test.ts b/packages/extension/tests/extension/build/runtimeTarget.test.ts index 4b4d19d660..286f27077d 100644 --- a/packages/extension/tests/extension/build/runtimeTarget.test.ts +++ b/packages/extension/tests/extension/build/runtimeTarget.test.ts @@ -9,6 +9,7 @@ describe('extension runtime target', () => { expect.arrayContaining([ '@libsql/darwin-arm64', '@esbuild/darwin-arm64', + '@parcel/watcher-darwin-arm64', 'tree-sitter', ]), ); diff --git a/packages/extension/tests/playwrightVscodeConfig.test.ts b/packages/extension/tests/playwrightVscodeConfig.test.ts index 584bff8d22..1da1c34980 100644 --- a/packages/extension/tests/playwrightVscodeConfig.test.ts +++ b/packages/extension/tests/playwrightVscodeConfig.test.ts @@ -109,7 +109,8 @@ describe('VS Code Playwright config', () => { ); expect(vsixJob).toContain('CODEGRAPHY_VSCODE_TEST_VERSION: 1.125.1'); - expect(smokeScript).toContain('version: vscodeVersion'); + expect(smokeScript).toContain('downloadAndUnzipVSCode(vscodeVersion)'); + expect(smokeScript).toContain('vscodeExecutablePath,'); }); it('pins the CI VS Code host version into the Playwright Turbo hash', () => { diff --git a/scripts/validate-vsix-native-artifacts.mjs b/scripts/validate-vsix-native-artifacts.mjs index 15712c618e..f275dd4f90 100644 --- a/scripts/validate-vsix-native-artifacts.mjs +++ b/scripts/validate-vsix-native-artifacts.mjs @@ -23,10 +23,17 @@ const treeSitterNativeBinaryPathByTarget = { 'win32-x64': 'extension/dist/node_modules/tree-sitter/prebuilds/win32-x64/tree-sitter.node', }; +const parcelWatcherNativeBinaryPathByTarget = { + 'linux-x64': 'extension/dist/node_modules/@parcel/watcher-linux-x64-glibc/watcher.node', + 'darwin-arm64': 'extension/dist/node_modules/@parcel/watcher-darwin-arm64/watcher.node', + 'win32-x64': 'extension/dist/node_modules/@parcel/watcher-win32-x64/watcher.node', +}; + function nativeBinaryPathsForTarget(target) { return [ libsqlNativeBinaryPathByTarget[target], treeSitterNativeBinaryPathByTarget[target], + parcelWatcherNativeBinaryPathByTarget[target], ]; } diff --git a/tests/release/vsixNativeArtifacts.test.mjs b/tests/release/vsixNativeArtifacts.test.mjs index 22e08a457e..6107ceca11 100644 --- a/tests/release/vsixNativeArtifacts.test.mjs +++ b/tests/release/vsixNativeArtifacts.test.mjs @@ -81,6 +81,7 @@ test('validates only the requested VSIX artifact targets', () => { target: 'linux-x64', sqliteBinary: createElfX64Binary(), treeSitterBinary: createElfX64Binary(), + parcelWatcherBinary: createElfX64Binary(), }); assert.doesNotThrow( @@ -88,12 +89,33 @@ test('validates only the requested VSIX artifact targets', () => { ); }); +test('rejects a VSIX with a Parcel watcher binding for the wrong architecture', () => { + const tempDir = mkdtempSync(path.join(tmpdir(), 'codegraphy-vsix-native-watcher-')); + const artifactsDir = path.join(tempDir, 'artifacts'); + const version = '5.8.0'; + + writeVsixFixture({ + artifactsDir, + version, + target: 'linux-x64', + sqliteBinary: createElfX64Binary(), + treeSitterBinary: createElfX64Binary(), + parcelWatcherBinary: createMachOArm64Binary(), + }); + + assert.throws( + () => validateVsixNativeArtifacts({ artifactsDir, version, targets: ['linux-x64'] }), + /linux-x64\.vsix contains Mach-O arm64 at extension\/dist\/node_modules\/@parcel\/watcher-linux-x64-glibc\/watcher\.node; expected ELF x86-64\./, + ); +}); + function writeVsixFixture({ artifactsDir, version, target, sqliteBinary, treeSitterBinary, + parcelWatcherBinary, }) { const fixtureRoot = mkdtempSync(path.join(tmpdir(), `codegraphy-vsix-${target}-`)); writeFixtureBinary( @@ -106,6 +128,11 @@ function writeVsixFixture({ treeSitterNativeBinaryPath(target), treeSitterBinary, ); + writeFixtureBinary( + fixtureRoot, + parcelWatcherNativeBinaryPath(target), + parcelWatcherBinary ?? nativeBinaryForTarget(target), + ); mkdirSync(artifactsDir, { recursive: true }); const vsixPath = path.join(artifactsDir, `codegraphy.codegraphy-${version}-${target}.vsix`); @@ -137,6 +164,21 @@ function treeSitterNativeBinaryPath(target) { return `extension/dist/node_modules/tree-sitter/prebuilds/${prebuildByTarget[target]}/tree-sitter.node`; } +function parcelWatcherNativeBinaryPath(target) { + const packageByTarget = { + 'linux-x64': 'watcher-linux-x64-glibc', + 'darwin-arm64': 'watcher-darwin-arm64', + 'win32-x64': 'watcher-win32-x64', + }; + return `extension/dist/node_modules/@parcel/${packageByTarget[target]}/watcher.node`; +} + +function nativeBinaryForTarget(target) { + if (target === 'linux-x64') return createElfX64Binary(); + if (target === 'darwin-arm64') return createMachOArm64Binary(); + return createPe32PlusX64Binary(); +} + function writeFixtureBinary(rootDir, relativePath, binary) { const filePath = path.join(rootDir, relativePath); mkdirSync(path.dirname(filePath), { recursive: true }); From 4259af9ff022cb8a6391e3249c1358653a362f9d Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 17:01:46 -0700 Subject: [PATCH 08/14] refactor: simplify runtime staging verification --- packages/extension/scripts/runtimePackages.ts | 8 +-- .../model.ts} | 2 +- .../types.ts} | 0 .../build/runtimePackagePlan.test.ts | 66 ------------------- .../extension/build/runtimeTarget.test.ts | 28 -------- tests/release/vsixNativeArtifacts.test.mjs | 20 ------ 6 files changed, 5 insertions(+), 119 deletions(-) rename packages/extension/scripts/{runtimePackagePlan.ts => runtimeStaging/model.ts} (99%) rename packages/extension/scripts/{runtimeTarget.ts => runtimeStaging/types.ts} (100%) delete mode 100644 packages/extension/tests/extension/build/runtimePackagePlan.test.ts delete mode 100644 packages/extension/tests/extension/build/runtimeTarget.test.ts diff --git a/packages/extension/scripts/runtimePackages.ts b/packages/extension/scripts/runtimePackages.ts index fcac767fd6..5e6f156f67 100644 --- a/packages/extension/scripts/runtimePackages.ts +++ b/packages/extension/scripts/runtimePackages.ts @@ -3,21 +3,21 @@ import * as path from 'node:path'; import { createRuntimePackagePlans, resolveRuntimePackageRootPath, -} from './runtimePackagePlan'; +} from './runtimeStaging/model'; import { resolveExtensionRuntimeTarget, type ExtensionRuntimeTarget, -} from './runtimeTarget'; +} from './runtimeStaging/types'; export { EXTENSION_RUNTIME_PACKAGE_NAMES, getExtensionRuntimePackageNames, resolveRuntimePackageRootPath, -} from './runtimePackagePlan'; +} from './runtimeStaging/model'; export { EXTENSION_RUNTIME_TARGETS, resolveExtensionRuntimeTarget, -} from './runtimeTarget'; +} from './runtimeStaging/types'; export function getVendoredPackageRootPath( outputFilePath: string, diff --git a/packages/extension/scripts/runtimePackagePlan.ts b/packages/extension/scripts/runtimeStaging/model.ts similarity index 99% rename from packages/extension/scripts/runtimePackagePlan.ts rename to packages/extension/scripts/runtimeStaging/model.ts index f31971a921..7b6144541f 100644 --- a/packages/extension/scripts/runtimePackagePlan.ts +++ b/packages/extension/scripts/runtimeStaging/model.ts @@ -4,7 +4,7 @@ import { createRequire } from 'node:module'; import { RUNTIME_TARGET_CONFIG, type ExtensionRuntimeTarget, -} from './runtimeTarget'; +} from './types'; const require = createRequire(import.meta.url); const requireFromCore = createRequire(require.resolve('@codegraphy-dev/core')); diff --git a/packages/extension/scripts/runtimeTarget.ts b/packages/extension/scripts/runtimeStaging/types.ts similarity index 100% rename from packages/extension/scripts/runtimeTarget.ts rename to packages/extension/scripts/runtimeStaging/types.ts diff --git a/packages/extension/tests/extension/build/runtimePackagePlan.test.ts b/packages/extension/tests/extension/build/runtimePackagePlan.test.ts deleted file mode 100644 index 78a59f816e..0000000000 --- a/packages/extension/tests/extension/build/runtimePackagePlan.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import { - EXTENSION_RUNTIME_PACKAGE_NAMES, - getExtensionRuntimePackageNames, - resolveRuntimePackageRootPath, -} from '../../../scripts/externalPackages'; -import { createRuntimePackagePlans } from '../../../scripts/runtimePackagePlan'; - -describe('extension runtime package plan', () => { - it('resolves the installed SQLite package root', () => { - const packageRootPath = resolveRuntimePackageRootPath('libsql'); - - expect(path.basename(packageRootPath)).toBe('libsql'); - expect(fs.existsSync(path.join(packageRootPath, 'package.json'))).toBe(true); - }); - - it('vendors every Tree-sitter grammar needed by the core runtime', () => { - expect(getExtensionRuntimePackageNames('darwin-arm64')).toEqual( - expect.arrayContaining([ - 'libsql', - '@neon-rs/load', - 'detect-libc', - 'esbuild', - '@libsql/darwin-arm64', - '@esbuild/darwin-arm64', - '@parcel/watcher-darwin-arm64', - 'material-icon-theme', - 'tree-sitter', - 'tree-sitter-c', - 'tree-sitter-cpp', - 'tree-sitter-c-sharp', - '@driftlog/tree-sitter-dart', - 'tree-sitter-go', - 'tree-sitter-haskell', - 'tree-sitter-java', - 'tree-sitter-javascript', - '@tree-sitter-grammars/tree-sitter-kotlin', - '@tree-sitter-grammars/tree-sitter-lua', - 'tree-sitter-objc', - 'tree-sitter-php', - 'tree-sitter-python', - 'tree-sitter-ruby', - 'tree-sitter-rust', - 'tree-sitter-scala', - 'tree-sitter-swift', - 'tree-sitter-typescript', - ]), - ); - }); - - it('vendors the target Parcel watcher native binding', () => { - const plan = createRuntimePackagePlans('darwin-arm64') - .find(entry => entry.packageName === '@parcel/watcher-darwin-arm64'); - - expect(plan?.relativeFilePaths).toEqual( - expect.arrayContaining(['package.json', 'watcher.node']), - ); - }); - - it('resolves every vendored runtime package from the extension package', () => { - for (const packageName of EXTENSION_RUNTIME_PACKAGE_NAMES) { - expect(() => resolveRuntimePackageRootPath(packageName)).not.toThrow(); - } - }); -}); diff --git a/packages/extension/tests/extension/build/runtimeTarget.test.ts b/packages/extension/tests/extension/build/runtimeTarget.test.ts deleted file mode 100644 index 286f27077d..0000000000 --- a/packages/extension/tests/extension/build/runtimeTarget.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - getExtensionRuntimePackageNames, - resolveExtensionRuntimeTarget, -} from '../../../scripts/externalPackages'; - -describe('extension runtime target', () => { - it('selects target-specific native runtime packages', () => { - expect(getExtensionRuntimePackageNames('darwin-arm64')).toEqual( - expect.arrayContaining([ - '@libsql/darwin-arm64', - '@esbuild/darwin-arm64', - '@parcel/watcher-darwin-arm64', - 'tree-sitter', - ]), - ); - expect(getExtensionRuntimePackageNames('darwin-arm64')).not.toContain( - '@libsql/linux-x64-gnu', - ); - }); - - it('uses one explicit VSIX target when the build provides it', () => { - expect(resolveExtensionRuntimeTarget({ - environment: { CODEGRAPHY_VSIX_TARGETS: 'darwin-arm64' }, - platform: 'darwin', - arch: 'arm64', - })).toBe('darwin-arm64'); - }); -}); diff --git a/tests/release/vsixNativeArtifacts.test.mjs b/tests/release/vsixNativeArtifacts.test.mjs index 6107ceca11..18ea69fe26 100644 --- a/tests/release/vsixNativeArtifacts.test.mjs +++ b/tests/release/vsixNativeArtifacts.test.mjs @@ -89,26 +89,6 @@ test('validates only the requested VSIX artifact targets', () => { ); }); -test('rejects a VSIX with a Parcel watcher binding for the wrong architecture', () => { - const tempDir = mkdtempSync(path.join(tmpdir(), 'codegraphy-vsix-native-watcher-')); - const artifactsDir = path.join(tempDir, 'artifacts'); - const version = '5.8.0'; - - writeVsixFixture({ - artifactsDir, - version, - target: 'linux-x64', - sqliteBinary: createElfX64Binary(), - treeSitterBinary: createElfX64Binary(), - parcelWatcherBinary: createMachOArm64Binary(), - }); - - assert.throws( - () => validateVsixNativeArtifacts({ artifactsDir, version, targets: ['linux-x64'] }), - /linux-x64\.vsix contains Mach-O arm64 at extension\/dist\/node_modules\/@parcel\/watcher-linux-x64-glibc\/watcher\.node; expected ELF x86-64\./, - ); -}); - function writeVsixFixture({ artifactsDir, version, From f820ae55ac383eafc2f444b2edbd0416d4411236 Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 17:03:47 -0700 Subject: [PATCH 09/14] refactor: keep runtime staging in one tested module --- .../extension/scripts/externalPackages.ts | 4 +- packages/extension/scripts/runtimePackages.ts | 97 ---------- .../extension/scripts/runtimeStaging/model.ts | 180 +++++++++++++++++- .../extension/scripts/runtimeStaging/types.ts | 98 ---------- .../model.test.ts} | 4 +- 5 files changed, 180 insertions(+), 203 deletions(-) delete mode 100644 packages/extension/scripts/runtimePackages.ts delete mode 100644 packages/extension/scripts/runtimeStaging/types.ts rename packages/extension/tests/extension/build/{runtimePackages.test.ts => runtimeStaging/model.test.ts} (98%) diff --git a/packages/extension/scripts/externalPackages.ts b/packages/extension/scripts/externalPackages.ts index 531a09c5d1..9d9ad32abe 100644 --- a/packages/extension/scripts/externalPackages.ts +++ b/packages/extension/scripts/externalPackages.ts @@ -1,4 +1,4 @@ -import { EXTENSION_RUNTIME_PACKAGE_NAMES } from './runtimePackages'; +import { EXTENSION_RUNTIME_PACKAGE_NAMES } from './runtimeStaging/model'; export { copyRuntimePackage, @@ -9,7 +9,7 @@ export { resolveExtensionRuntimeTarget, resolveRuntimePackageRootPath, syncExtensionRuntimePackages, -} from './runtimePackages'; +} from './runtimeStaging/model'; export const EXTENSION_EXTERNAL_PACKAGE_NAMES = [ 'vscode', diff --git a/packages/extension/scripts/runtimePackages.ts b/packages/extension/scripts/runtimePackages.ts deleted file mode 100644 index 5e6f156f67..0000000000 --- a/packages/extension/scripts/runtimePackages.ts +++ /dev/null @@ -1,97 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import { - createRuntimePackagePlans, - resolveRuntimePackageRootPath, -} from './runtimeStaging/model'; -import { - resolveExtensionRuntimeTarget, - type ExtensionRuntimeTarget, -} from './runtimeStaging/types'; - -export { - EXTENSION_RUNTIME_PACKAGE_NAMES, - getExtensionRuntimePackageNames, - resolveRuntimePackageRootPath, -} from './runtimeStaging/model'; -export { - EXTENSION_RUNTIME_TARGETS, - resolveExtensionRuntimeTarget, -} from './runtimeStaging/types'; - -export function getVendoredPackageRootPath( - outputFilePath: string, - packageName: string, -): string { - return path.join(path.dirname(outputFilePath), 'node_modules', ...packageName.split('/')); -} - -function toPackageRelativeEntrypoint(entrypoint: string): string { - return entrypoint.endsWith('/') - ? `${entrypoint}index.js` - : `${entrypoint}/index.js`; -} - -function normalizeVendoredPackageEntrypoint(packageRootPath: string): void { - const manifestPath = path.join(packageRootPath, 'package.json'); - const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as { - main?: unknown; - [key: string]: unknown; - }; - if (typeof manifest.main !== 'string' || path.extname(manifest.main) !== '') { - return; - } - - const normalizedMain = toPackageRelativeEntrypoint(manifest.main); - if (!fs.existsSync(path.join(packageRootPath, normalizedMain))) { - return; - } - - fs.writeFileSync( - manifestPath, - `${JSON.stringify({ ...manifest, main: normalizedMain }, null, 2)}\n`, - ); -} - -export function copyRuntimePackage( - outputFilePath: string, - packageName: string, - relativeFilePaths: readonly string[], - resolvePackageRootPath: (packageName: string) => string = resolveRuntimePackageRootPath, -): string { - const sourcePath = resolvePackageRootPath(packageName); - const targetPath = getVendoredPackageRootPath(outputFilePath, packageName); - - fs.rmSync(targetPath, { recursive: true, force: true }); - - for (const relativeFilePath of [...new Set(relativeFilePaths)]) { - const sourceFilePath = path.join(sourcePath, relativeFilePath); - if (!fs.existsSync(sourceFilePath) || !fs.statSync(sourceFilePath).isFile()) { - throw new Error(`Runtime package file is not a file: ${packageName}/${relativeFilePath}`); - } - - const targetFilePath = path.join(targetPath, relativeFilePath); - fs.mkdirSync(path.dirname(targetFilePath), { recursive: true }); - fs.cpSync(sourceFilePath, targetFilePath, { force: true }); - } - - normalizeVendoredPackageEntrypoint(targetPath); - return targetPath; -} - -export function syncExtensionRuntimePackages( - outputFilePath: string, - target: ExtensionRuntimeTarget = resolveExtensionRuntimeTarget(), -): string[] { - fs.rmSync(path.join(path.dirname(outputFilePath), 'node_modules'), { - recursive: true, - force: true, - }); - - return createRuntimePackagePlans(target).map(plan => copyRuntimePackage( - outputFilePath, - plan.packageName, - plan.relativeFilePaths, - plan.resolvePackageRootPath, - )); -} diff --git a/packages/extension/scripts/runtimeStaging/model.ts b/packages/extension/scripts/runtimeStaging/model.ts index 7b6144541f..4aafca5251 100644 --- a/packages/extension/scripts/runtimeStaging/model.ts +++ b/packages/extension/scripts/runtimeStaging/model.ts @@ -1,14 +1,109 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { createRequire } from 'node:module'; -import { - RUNTIME_TARGET_CONFIG, - type ExtensionRuntimeTarget, -} from './types'; const require = createRequire(import.meta.url); const requireFromCore = createRequire(require.resolve('@codegraphy-dev/core')); +export const EXTENSION_RUNTIME_TARGETS = [ + 'linux-x64', + 'darwin-arm64', + 'win32-x64', +] as const; + +export type ExtensionRuntimeTarget = typeof EXTENSION_RUNTIME_TARGETS[number]; + +type RuntimeTargetConfig = { + platform: NodeJS.Platform; + arch: string; + libsqlPackageName: string; + esbuildPackageName: string; + parcelWatcherPackageName: string; + nativePrebuildDirectory: string; +}; + +export const RUNTIME_TARGET_CONFIG = { + 'linux-x64': { + platform: 'linux', + arch: 'x64', + libsqlPackageName: '@libsql/linux-x64-gnu', + esbuildPackageName: '@esbuild/linux-x64', + parcelWatcherPackageName: '@parcel/watcher-linux-x64-glibc', + nativePrebuildDirectory: 'linux-x64', + }, + 'darwin-arm64': { + platform: 'darwin', + arch: 'arm64', + libsqlPackageName: '@libsql/darwin-arm64', + esbuildPackageName: '@esbuild/darwin-arm64', + parcelWatcherPackageName: '@parcel/watcher-darwin-arm64', + nativePrebuildDirectory: 'darwin-arm64', + }, + 'win32-x64': { + platform: 'win32', + arch: 'x64', + libsqlPackageName: '@libsql/win32-x64-msvc', + esbuildPackageName: '@esbuild/win32-x64', + parcelWatcherPackageName: '@parcel/watcher-win32-x64', + nativePrebuildDirectory: 'win32-x64', + }, +} satisfies Record; + +type RuntimeTargetOptions = { + environment?: Partial>; + platform?: NodeJS.Platform; + arch?: string; +}; + +function isExtensionRuntimeTarget(value: string): value is ExtensionRuntimeTarget { + return EXTENSION_RUNTIME_TARGETS.includes(value as ExtensionRuntimeTarget); +} + +export function resolveExtensionRuntimeTarget({ + environment = process.env, + platform = process.platform, + arch = process.arch, +}: RuntimeTargetOptions = {}): ExtensionRuntimeTarget { + const requestedTargets = environment.CODEGRAPHY_VSIX_TARGETS + ?.split(',') + .map(target => target.trim()) + .filter(Boolean); + + if (requestedTargets && requestedTargets.length !== 1) { + throw new Error('Extension runtime staging requires exactly one VSIX target.'); + } + + const hostTarget = EXTENSION_RUNTIME_TARGETS.find((target) => { + const config = RUNTIME_TARGET_CONFIG[target]; + return config.platform === platform && config.arch === arch; + }); + if (!hostTarget) { + throw new Error( + `Unsupported extension runtime host: ${platform}-${arch}. ` + + `Supported targets are ${EXTENSION_RUNTIME_TARGETS.join(', ')}.`, + ); + } + + const requestedTarget = requestedTargets?.[0]; + if (!requestedTarget) { + return hostTarget; + } + if (!isExtensionRuntimeTarget(requestedTarget)) { + throw new Error( + `Unsupported extension runtime target: ${requestedTarget}. ` + + `Supported targets are ${EXTENSION_RUNTIME_TARGETS.join(', ')}.`, + ); + } + if (requestedTarget !== hostTarget) { + throw new Error( + `Cannot stage ${requestedTarget} native files on ${hostTarget}. ` + + 'Build the VSIX on its matching host.', + ); + } + + return requestedTarget; +} + const TREE_SITTER_GRAMMAR_PACKAGE_NAMES = [ 'tree-sitter-c', 'tree-sitter-cpp', @@ -225,3 +320,80 @@ export function createRuntimePackagePlans( )), ]; } + +export function getVendoredPackageRootPath( + outputFilePath: string, + packageName: string, +): string { + return path.join(path.dirname(outputFilePath), 'node_modules', ...packageName.split('/')); +} + +function toPackageRelativeEntrypoint(entrypoint: string): string { + return entrypoint.endsWith('/') + ? `${entrypoint}index.js` + : `${entrypoint}/index.js`; +} + +function normalizeVendoredPackageEntrypoint(packageRootPath: string): void { + const manifestPath = path.join(packageRootPath, 'package.json'); + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as { + main?: unknown; + [key: string]: unknown; + }; + if (typeof manifest.main !== 'string' || path.extname(manifest.main) !== '') { + return; + } + + const normalizedMain = toPackageRelativeEntrypoint(manifest.main); + if (!fs.existsSync(path.join(packageRootPath, normalizedMain))) { + return; + } + + fs.writeFileSync( + manifestPath, + `${JSON.stringify({ ...manifest, main: normalizedMain }, null, 2)}\n`, + ); +} + +export function copyRuntimePackage( + outputFilePath: string, + packageName: string, + relativeFilePaths: readonly string[], + resolvePackageRootPath: (packageName: string) => string = resolveRuntimePackageRootPath, +): string { + const sourcePath = resolvePackageRootPath(packageName); + const targetPath = getVendoredPackageRootPath(outputFilePath, packageName); + + fs.rmSync(targetPath, { recursive: true, force: true }); + + for (const relativeFilePath of [...new Set(relativeFilePaths)]) { + const sourceFilePath = path.join(sourcePath, relativeFilePath); + if (!fs.existsSync(sourceFilePath) || !fs.statSync(sourceFilePath).isFile()) { + throw new Error(`Runtime package file is not a file: ${packageName}/${relativeFilePath}`); + } + + const targetFilePath = path.join(targetPath, relativeFilePath); + fs.mkdirSync(path.dirname(targetFilePath), { recursive: true }); + fs.cpSync(sourceFilePath, targetFilePath, { force: true }); + } + + normalizeVendoredPackageEntrypoint(targetPath); + return targetPath; +} + +export function syncExtensionRuntimePackages( + outputFilePath: string, + target: ExtensionRuntimeTarget = resolveExtensionRuntimeTarget(), +): string[] { + fs.rmSync(path.join(path.dirname(outputFilePath), 'node_modules'), { + recursive: true, + force: true, + }); + + return createRuntimePackagePlans(target).map(plan => copyRuntimePackage( + outputFilePath, + plan.packageName, + plan.relativeFilePaths, + plan.resolvePackageRootPath, + )); +} diff --git a/packages/extension/scripts/runtimeStaging/types.ts b/packages/extension/scripts/runtimeStaging/types.ts deleted file mode 100644 index 7f3d60023a..0000000000 --- a/packages/extension/scripts/runtimeStaging/types.ts +++ /dev/null @@ -1,98 +0,0 @@ -export const EXTENSION_RUNTIME_TARGETS = [ - 'linux-x64', - 'darwin-arm64', - 'win32-x64', -] as const; - -export type ExtensionRuntimeTarget = typeof EXTENSION_RUNTIME_TARGETS[number]; - -export type RuntimeTargetConfig = { - platform: NodeJS.Platform; - arch: string; - libsqlPackageName: string; - esbuildPackageName: string; - parcelWatcherPackageName: string; - nativePrebuildDirectory: string; -}; - -export const RUNTIME_TARGET_CONFIG = { - 'linux-x64': { - platform: 'linux', - arch: 'x64', - libsqlPackageName: '@libsql/linux-x64-gnu', - esbuildPackageName: '@esbuild/linux-x64', - parcelWatcherPackageName: '@parcel/watcher-linux-x64-glibc', - nativePrebuildDirectory: 'linux-x64', - }, - 'darwin-arm64': { - platform: 'darwin', - arch: 'arm64', - libsqlPackageName: '@libsql/darwin-arm64', - esbuildPackageName: '@esbuild/darwin-arm64', - parcelWatcherPackageName: '@parcel/watcher-darwin-arm64', - nativePrebuildDirectory: 'darwin-arm64', - }, - 'win32-x64': { - platform: 'win32', - arch: 'x64', - libsqlPackageName: '@libsql/win32-x64-msvc', - esbuildPackageName: '@esbuild/win32-x64', - parcelWatcherPackageName: '@parcel/watcher-win32-x64', - nativePrebuildDirectory: 'win32-x64', - }, -} satisfies Record; - -type RuntimeTargetOptions = { - environment?: Partial>; - platform?: NodeJS.Platform; - arch?: string; -}; - -function isExtensionRuntimeTarget(value: string): value is ExtensionRuntimeTarget { - return EXTENSION_RUNTIME_TARGETS.includes(value as ExtensionRuntimeTarget); -} - -export function resolveExtensionRuntimeTarget({ - environment = process.env, - platform = process.platform, - arch = process.arch, -}: RuntimeTargetOptions = {}): ExtensionRuntimeTarget { - const requestedTargets = environment.CODEGRAPHY_VSIX_TARGETS - ?.split(',') - .map(target => target.trim()) - .filter(Boolean); - - if (requestedTargets && requestedTargets.length !== 1) { - throw new Error('Extension runtime staging requires exactly one VSIX target.'); - } - - const hostTarget = EXTENSION_RUNTIME_TARGETS.find((target) => { - const config = RUNTIME_TARGET_CONFIG[target]; - return config.platform === platform && config.arch === arch; - }); - if (!hostTarget) { - throw new Error( - `Unsupported extension runtime host: ${platform}-${arch}. ` - + `Supported targets are ${EXTENSION_RUNTIME_TARGETS.join(', ')}.`, - ); - } - - const requestedTarget = requestedTargets?.[0]; - if (!requestedTarget) { - return hostTarget; - } - if (!isExtensionRuntimeTarget(requestedTarget)) { - throw new Error( - `Unsupported extension runtime target: ${requestedTarget}. ` - + `Supported targets are ${EXTENSION_RUNTIME_TARGETS.join(', ')}.`, - ); - } - if (requestedTarget !== hostTarget) { - throw new Error( - `Cannot stage ${requestedTarget} native files on ${hostTarget}. ` - + 'Build the VSIX on its matching host.', - ); - } - - return requestedTarget; -} diff --git a/packages/extension/tests/extension/build/runtimePackages.test.ts b/packages/extension/tests/extension/build/runtimeStaging/model.test.ts similarity index 98% rename from packages/extension/tests/extension/build/runtimePackages.test.ts rename to packages/extension/tests/extension/build/runtimeStaging/model.test.ts index 21a76e7fbc..9543fd9666 100644 --- a/packages/extension/tests/extension/build/runtimePackages.test.ts +++ b/packages/extension/tests/extension/build/runtimeStaging/model.test.ts @@ -6,11 +6,11 @@ import { copyRuntimePackage, EXTENSION_EXTERNAL_PACKAGE_NAMES, getVendoredPackageRootPath, -} from '../../../scripts/externalPackages'; +} from '../../../../scripts/externalPackages'; const EXTENSION_PACKAGE_ROOT = path.resolve( path.dirname(fileURLToPath(import.meta.url)), - '../../..', + '../../../..', ); describe('runtime package build support', () => { From a0c21d6357739f9e6d5fd96d643cbd57bb6cd1ad Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 17:13:41 -0700 Subject: [PATCH 10/14] fix: defer Core runtime resolution --- packages/extension/scripts/runtimeStaging/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/extension/scripts/runtimeStaging/model.ts b/packages/extension/scripts/runtimeStaging/model.ts index 4aafca5251..18ebc2d4a3 100644 --- a/packages/extension/scripts/runtimeStaging/model.ts +++ b/packages/extension/scripts/runtimeStaging/model.ts @@ -3,7 +3,6 @@ import * as path from 'node:path'; import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); -const requireFromCore = createRequire(require.resolve('@codegraphy-dev/core')); export const EXTENSION_RUNTIME_TARGETS = [ 'linux-x64', @@ -171,6 +170,7 @@ function resolveEsbuildBinaryPackageRootPath(packageName: string): string { } function resolveParcelWatcherBinaryPackageRootPath(packageName: string): string { + const requireFromCore = createRequire(require.resolve('@codegraphy-dev/core')); const watcherEntryPath = requireFromCore.resolve('@parcel/watcher'); const manifestPath = requireFromCore.resolve(`${packageName}/package.json`, { paths: [path.dirname(watcherEntryPath)], From ce6fcc96a2c7e5598bc35d5145a8a77e78f2e0b7 Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 17:31:06 -0700 Subject: [PATCH 11/14] docs: explain runtime upgrade requirements --- .changeset/broad-node-runtime-support.md | 34 ++++++++++++++---------- .changeset/clean-tree-sitter-runtime.md | 4 ++- .changeset/slim-vsix-runtime.md | 4 ++- README.md | 10 ++++++- docs/PLUGINS.md | 4 +++ packages/extension-plugin-api/README.md | 3 +++ packages/extension/docs/README.md | 5 +++- packages/graph-renderer/README.md | 4 +++ packages/plugin-api/README.md | 3 +++ packages/plugin-godot/README.md | 2 ++ packages/plugin-markdown/README.md | 4 +++ packages/plugin-particles/README.md | 2 ++ packages/plugin-svelte/README.md | 2 ++ packages/plugin-typescript/README.md | 2 ++ packages/plugin-unity/README.md | 2 ++ packages/plugin-vue/README.md | 2 ++ 16 files changed, 69 insertions(+), 18 deletions(-) diff --git a/.changeset/broad-node-runtime-support.md b/.changeset/broad-node-runtime-support.md index a12cf1db64..996beb8a0c 100644 --- a/.changeset/broad-node-runtime-support.md +++ b/.changeset/broad-node-runtime-support.md @@ -1,17 +1,23 @@ --- -"@codegraphy-dev/core": patch -"@codegraphy-dev/extension": patch -"@codegraphy-dev/extension-plugin-api": patch -"@codegraphy-dev/graph-renderer": patch -"@codegraphy-dev/plugin-api": patch -"@codegraphy-dev/plugin-godot": patch -"@codegraphy-dev/plugin-markdown": patch -"@codegraphy-dev/plugin-particles": patch -"@codegraphy-dev/plugin-svelte": patch -"@codegraphy-dev/plugin-typescript": patch -"@codegraphy-dev/plugin-unity": patch -"@codegraphy-dev/plugin-vue": patch -"@codegraphy-dev/tldraw": patch +"@codegraphy-dev/core": major +"@codegraphy-dev/extension": minor +"@codegraphy-dev/extension-plugin-api": major +"@codegraphy-dev/graph-renderer": minor +"@codegraphy-dev/plugin-api": major +"@codegraphy-dev/plugin-godot": major +"@codegraphy-dev/plugin-markdown": major +"@codegraphy-dev/plugin-particles": minor +"@codegraphy-dev/plugin-svelte": minor +"@codegraphy-dev/plugin-typescript": major +"@codegraphy-dev/plugin-unity": minor +"@codegraphy-dev/plugin-vue": minor +"@codegraphy-dev/tldraw": minor --- -Require Node.js 22.14.0 or newer across CodeGraphy packages without setting a maximum version. +Node.js 20 is no longer supported. Upgrade to Node.js 22.14.0 or newer before +updating CodeGraphy Core, interfaces, APIs, or plugins. Newer Node.js releases +remain supported because these packages do not set a maximum version. + +The VS Code extension now requires VS Code 1.101 or newer. Extension users do +not need to install Node.js separately because VS Code supplies the Extension +host runtime. diff --git a/.changeset/clean-tree-sitter-runtime.md b/.changeset/clean-tree-sitter-runtime.md index 2fecb05a9e..7c14171909 100644 --- a/.changeset/clean-tree-sitter-runtime.md +++ b/.changeset/clean-tree-sitter-runtime.md @@ -4,4 +4,6 @@ "@codegraphy-dev/tldraw": patch --- -Use the upstream Tree-sitter release with Node 23 and newer support instead of the temporary local native-build patch. +Use Tree-sitter's upstream prebuilt runtime on Node.js 22.14.0 and newer. +Language analysis is unchanged, and CodeGraphy no longer needs its temporary +native-build patch. diff --git a/.changeset/slim-vsix-runtime.md b/.changeset/slim-vsix-runtime.md index 946bd16555..0de4e454c2 100644 --- a/.changeset/slim-vsix-runtime.md +++ b/.changeset/slim-vsix-runtime.md @@ -2,4 +2,6 @@ "@codegraphy-dev/extension": patch --- -Reduce the installed extension size by shipping only the runtime files and native binaries needed by each platform-specific VSIX. +Reduce the platform-specific extension install size by approximately 85% +without removing supported languages, built-in plugins, Graph View assets, or +native runtime capabilities. diff --git a/README.md b/README.md index 9f2b53b211..3571fe7b15 100644 --- a/README.md +++ b/README.md @@ -68,11 +68,19 @@ CodeGraphy indexes a folder and projects its files and declarations into Nodes. 3. Open CodeGraphy from the Activity Bar. 4. Select **Index Workspace** to add semantic Relationships to the initial file graph. +CodeGraphy requires VS Code 1.101 or newer. Extension users do not need to +install Node.js separately because VS Code provides the Extension host runtime. +If you use an older VS Code release, update VS Code before you update +CodeGraphy. + The extension publishes native runtime targets for Linux x64, macOS Apple Silicon, and Windows x64. It bundles Core plus baseline analysis for JavaScript, TypeScript, TSX, Python, Go, Haskell, Java, Kotlin, Lua, PHP, Ruby, Rust, Swift, Dart, C#, C, C++, Objective-C, Scala, and Pascal. Markdown analysis ships as a bundled plugin that starts enabled in new workspaces. ### CLI and Plugins -The terminal CLI supports Node.js 22.14.0 and newer. An active LTS release is recommended. +The CLI, plugins, and interface packages require Node.js 22.14.0 or newer. +Node.js 20 is not supported. Upgrade Node.js before you update these npm +packages. An active LTS release is recommended, and CodeGraphy does not set a +maximum Node.js version. ```bash npm install -g @codegraphy-dev/core diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md index 98d83d0d4e..e47cf49485 100644 --- a/docs/PLUGINS.md +++ b/docs/PLUGINS.md @@ -8,6 +8,10 @@ has a small API for the behavior that it owns. - [`@codegraphy-dev/extension-plugin-api`](../packages/extension-plugin-api/README.md) extends the VS Code extension and Graph View. +Core, Core plugins, Extension plugins, and interface packages require Node.js +22.14.0 or newer. Node.js 20 is not supported. Upgrade Node.js before you +update these npm packages. CodeGraphy does not set a maximum Node.js version. + ## The basic model There are three separate steps: diff --git a/packages/extension-plugin-api/README.md b/packages/extension-plugin-api/README.md index 03b3cc7322..b89564b129 100644 --- a/packages/extension-plugin-api/README.md +++ b/packages/extension-plugin-api/README.md @@ -20,6 +20,9 @@ runtimes separate and let each host load its own descriptor. ## Install +Installing this package requires Node.js 22.14.0 or newer. Node.js 20 is not +supported. + ```bash npm install -D @codegraphy-dev/extension-plugin-api ``` diff --git a/packages/extension/docs/README.md b/packages/extension/docs/README.md index dff0dc083c..c3926ae517 100644 --- a/packages/extension/docs/README.md +++ b/packages/extension/docs/README.md @@ -18,7 +18,10 @@ Current extension behavior to keep in mind while reading the package docs: - The extension loads workspace-local CSS Snippets listed under `cssSnippets` in `.codegraphy/settings.json`. They can style stable `data-codegraphy-*` hooks without rebuilding a VS Code theme. - the language plugins in `packages/plugin-*` are now mostly for ecosystem filters and optional semantic enrichment rather than baseline file coloring -The published CLI supports Node.js 22.14.0 and newer. Local tooling and CI use an active Node.js LTS release. +The Marketplace extension requires VS Code 1.101 or newer and uses the Node.js +runtime supplied by VS Code. Extension users do not install Node.js separately. +The published CLI and npm packages require Node.js 22.14.0 or newer. Local +tooling and CI use an active Node.js LTS release. The source tree is split by runtime boundary: diff --git a/packages/graph-renderer/README.md b/packages/graph-renderer/README.md index ede8066124..09220a7240 100644 --- a/packages/graph-renderer/README.md +++ b/packages/graph-renderer/README.md @@ -10,6 +10,10 @@ The package owns graph rendering and graph physics only. Your application owns i ## Install +Installing this package requires Node.js 22.14.0 or newer. The renderer still +runs in a browser with WebAssembly and WebGPU; the Node.js requirement applies +to package installation and tooling. + ```bash pnpm add @codegraphy-dev/graph-renderer ``` diff --git a/packages/plugin-api/README.md b/packages/plugin-api/README.md index 77d4e7368f..2b11402914 100644 --- a/packages/plugin-api/README.md +++ b/packages/plugin-api/README.md @@ -8,6 +8,9 @@ VS Code Extension and Graph View behavior. ## Install +Installing this package requires Node.js 22.14.0 or newer. Node.js 20 is not +supported. + ```bash npm install -D @codegraphy-dev/plugin-api ``` diff --git a/packages/plugin-godot/README.md b/packages/plugin-godot/README.md index 7d816d4682..b1dbc1a410 100644 --- a/packages/plugin-godot/README.md +++ b/packages/plugin-godot/README.md @@ -12,6 +12,8 @@ colors, shapes, and icons. The CLI loads only the Core entry. ## Install +This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. + Install `@codegraphy-dev/core` first if the `codegraphy` CLI is not already available. ```bash diff --git a/packages/plugin-markdown/README.md b/packages/plugin-markdown/README.md index 80730d87f1..2543a730e4 100644 --- a/packages/plugin-markdown/README.md +++ b/packages/plugin-markdown/README.md @@ -4,6 +4,10 @@ Headless CodeGraphy plugin for Markdown and Obsidian-style wikilinks. This package exposes the Markdown plugin runtime used by `@codegraphy-dev/core`. It detects wikilinks such as `[[Note]]`, `[[folder/Note|Alias]]`, and `![[Embed]]`, then emits Relationship Graph references between workspace files. +Installing this package separately requires Node.js 22.14.0 or newer. Node.js +20 is not supported. The VS Code extension already includes and manages this +plugin, so extension users do not install it separately. + ## CodeGraphy Metadata The package declares `package.json#codegraphy` metadata so CodeGraphy can register and validate the plugin without importing runtime code. Runtime loading happens during explicit Indexing. diff --git a/packages/plugin-particles/README.md b/packages/plugin-particles/README.md index bda5342ca4..cbe6a1459d 100644 --- a/packages/plugin-particles/README.md +++ b/packages/plugin-particles/README.md @@ -6,6 +6,8 @@ The built-in presets are Synapse, Rain, Constellations, Perlin Flow, Leaves, Spa ## Install +This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. + ```bash npm install -g @codegraphy-dev/plugin-particles codegraphy plugins register @codegraphy-dev/plugin-particles diff --git a/packages/plugin-svelte/README.md b/packages/plugin-svelte/README.md index be885412a6..486c4e4ccb 100644 --- a/packages/plugin-svelte/README.md +++ b/packages/plugin-svelte/README.md @@ -7,6 +7,8 @@ Adds Svelte component script import analysis to [CodeGraphy](https://marketplace ## Install +This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. + Install `@codegraphy-dev/core` first if the `codegraphy` CLI is not already available. ```bash diff --git a/packages/plugin-typescript/README.md b/packages/plugin-typescript/README.md index 589f8ec2c8..9fef5940cd 100644 --- a/packages/plugin-typescript/README.md +++ b/packages/plugin-typescript/README.md @@ -7,6 +7,8 @@ Adds TypeScript project-aware alias import relationships and TypeScript/JavaScri ## Install +This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. + Install `@codegraphy-dev/core` first if the `codegraphy` CLI is not already available. ```bash diff --git a/packages/plugin-unity/README.md b/packages/plugin-unity/README.md index beda5795b1..de699ce3f6 100644 --- a/packages/plugin-unity/README.md +++ b/packages/plugin-unity/README.md @@ -12,6 +12,8 @@ colors, shapes, and icons. The CLI loads only the Core entry. ## Install +This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. + Install `@codegraphy-dev/core` first if the `codegraphy` CLI is not already available. ```bash diff --git a/packages/plugin-vue/README.md b/packages/plugin-vue/README.md index 7c6169a6b6..3ddd587884 100644 --- a/packages/plugin-vue/README.md +++ b/packages/plugin-vue/README.md @@ -7,6 +7,8 @@ Adds Vue Single-File Component script import analysis to [CodeGraphy](https://ma ## Install +This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. + Install `@codegraphy-dev/core` first if the `codegraphy` CLI is not already available. ```bash From bd992da7d0c594007088fcf64037dda3da3c814a Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 18:12:09 -0700 Subject: [PATCH 12/14] fix: colocate Particles compiler runtime --- package.json | 1 + .../extension/scripts/runtimeStaging/model.ts | 53 +++++++++++---- .../build/runtimeStaging/model.test.ts | 65 ++++++++++++++++++- 3 files changed, 106 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 62dce63c20..0a98f54117 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "packages/plugin-markdown/LICENSE", "packages/plugin-markdown/package.json", "packages/plugin-particles/dist/plugin.js", + "packages/plugin-particles/dist/node_modules/**", "packages/plugin-particles/dist/webview.js", "packages/plugin-particles/LICENSE", "packages/plugin-particles/package.json", diff --git a/packages/extension/scripts/runtimeStaging/model.ts b/packages/extension/scripts/runtimeStaging/model.ts index 18ebc2d4a3..d5438a556c 100644 --- a/packages/extension/scripts/runtimeStaging/model.ts +++ b/packages/extension/scripts/runtimeStaging/model.ts @@ -128,7 +128,6 @@ export const EXTENSION_RUNTIME_PACKAGE_NAMES = [ 'libsql', '@neon-rs/load', 'detect-libc', - 'esbuild', 'material-icon-theme', 'node-gyp-build', 'tree-sitter', @@ -260,6 +259,22 @@ export function getExtensionRuntimePackageNames( ]; } +function createEsbuildRuntimePackagePlans( + target: ExtensionRuntimeTarget, +): RuntimePackagePlan[] { + const config = RUNTIME_TARGET_CONFIG[target]; + return [ + staticPackagePlan('esbuild', [ + 'package.json', + 'lib/main.js', + ]), + staticPackagePlan(config.esbuildPackageName, [ + 'package.json', + config.platform === 'win32' ? 'esbuild.exe' : 'bin/esbuild', + ], resolveEsbuildBinaryPackageRootPath), + ]; +} + export function createRuntimePackagePlans( target: ExtensionRuntimeTarget, ): RuntimePackagePlan[] { @@ -281,14 +296,6 @@ export function createRuntimePackagePlans( 'lib/filesystem.js', 'lib/process.js', ]), - staticPackagePlan('esbuild', [ - 'package.json', - 'lib/main.js', - ]), - staticPackagePlan(config.esbuildPackageName, [ - 'package.json', - config.platform === 'win32' ? 'esbuild.exe' : 'bin/esbuild', - ], resolveEsbuildBinaryPackageRootPath), staticPackagePlan(config.libsqlPackageName, ['package.json', 'index.node']), staticPackagePlan( config.parcelWatcherPackageName, @@ -381,19 +388,41 @@ export function copyRuntimePackage( return targetPath; } -export function syncExtensionRuntimePackages( +function copyRuntimePackagePlans( outputFilePath: string, - target: ExtensionRuntimeTarget = resolveExtensionRuntimeTarget(), + plans: readonly RuntimePackagePlan[], ): string[] { fs.rmSync(path.join(path.dirname(outputFilePath), 'node_modules'), { recursive: true, force: true, }); - return createRuntimePackagePlans(target).map(plan => copyRuntimePackage( + return plans.map(plan => copyRuntimePackage( outputFilePath, plan.packageName, plan.relativeFilePaths, plan.resolvePackageRootPath, )); } + +export function syncExtensionRuntimePackages( + outputFilePath: string, + target: ExtensionRuntimeTarget = resolveExtensionRuntimeTarget(), +): string[] { + const stageRootPath = path.dirname(path.dirname(outputFilePath)); + const particlesPluginOutputFilePath = path.join( + stageRootPath, + 'packages', + 'plugin-particles', + 'dist', + 'plugin.js', + ); + + return [ + ...copyRuntimePackagePlans(outputFilePath, createRuntimePackagePlans(target)), + ...copyRuntimePackagePlans( + particlesPluginOutputFilePath, + createEsbuildRuntimePackagePlans(target), + ), + ]; +} diff --git a/packages/extension/tests/extension/build/runtimeStaging/model.test.ts b/packages/extension/tests/extension/build/runtimeStaging/model.test.ts index 9543fd9666..68321778e8 100644 --- a/packages/extension/tests/extension/build/runtimeStaging/model.test.ts +++ b/packages/extension/tests/extension/build/runtimeStaging/model.test.ts @@ -1,11 +1,15 @@ +import { execFileSync } from 'node:child_process'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import { fileURLToPath } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { buildSync } from 'esbuild'; import { copyRuntimePackage, EXTENSION_EXTERNAL_PACKAGE_NAMES, getVendoredPackageRootPath, + resolveExtensionRuntimeTarget, + syncExtensionRuntimePackages, } from '../../../../scripts/externalPackages'; const EXTENSION_PACKAGE_ROOT = path.resolve( @@ -73,6 +77,65 @@ describe('runtime package build support', () => { expect(copiedPackageJson.main).toBe('bindings/node/index.js'); }); + it('initializes packaged Particles custom effects from its staged runtime', () => { + const stageRootPath = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraphy-particles-package-')); + const packagedPluginRootPath = path.join(stageRootPath, 'packages', 'plugin-particles'); + const packagedPluginEntryPath = path.join(packagedPluginRootPath, 'dist', 'plugin.js'); + const workspaceRootPath = path.join(stageRootPath, 'workspace'); + const effectSourcePath = path.join( + workspaceRootPath, + '.codegraphy', + 'particles', + 'custom-effect.ts', + ); + + buildSync({ + entryPoints: [path.join( + EXTENSION_PACKAGE_ROOT, + '..', + 'plugin-particles', + 'src', + 'plugin.ts', + )], + outfile: packagedPluginEntryPath, + bundle: true, + external: ['esbuild'], + format: 'esm', + platform: 'node', + target: 'node22', + }); + fs.copyFileSync( + path.join(EXTENSION_PACKAGE_ROOT, '..', 'plugin-particles', 'package.json'), + path.join(packagedPluginRootPath, 'package.json'), + ); + fs.mkdirSync(path.dirname(effectSourcePath), { recursive: true }); + fs.writeFileSync(effectSourcePath, 'export default function customEffect() {}\n'); + + syncExtensionRuntimePackages( + path.join(stageRootPath, 'dist', 'extension.js'), + resolveExtensionRuntimeTarget(), + ); + expect(fs.existsSync(path.join(stageRootPath, 'dist', 'node_modules', 'esbuild'))).toBe(false); + expect(fs.existsSync( + path.join(packagedPluginRootPath, 'dist', 'node_modules', 'esbuild'), + )).toBe(true); + + execFileSync(process.execPath, [ + '--input-type=module', + '--eval', + ` + const packagedModule = await import(${JSON.stringify(pathToFileURL(packagedPluginEntryPath).href)}); + const plugin = packagedModule.default(); + await plugin.initialize(${JSON.stringify(workspaceRootPath)}); + `, + ]); + + expect(fs.readFileSync( + path.join(workspaceRootPath, '.codegraphy', 'cache', 'particles', 'custom-effect.js'), + 'utf8', + )).toContain('customEffect'); + }); + it('bundles core packages while externalizing only VS Code and native runtime packages', () => { expect(EXTENSION_EXTERNAL_PACKAGE_NAMES).toEqual( expect.arrayContaining([ From 54e5c2367581b970552569de35490a3b21f1d6fd Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 18:14:54 -0700 Subject: [PATCH 13/14] fix: exclude unsupported Node 23 runtimes --- .changeset/broad-node-runtime-support.md | 2 +- .changeset/clean-tree-sitter-runtime.md | 2 +- CONTRIBUTING.md | 4 +- README.md | 4 +- apps/web/package.json | 2 +- docs/PLUGINS.md | 2 +- examples/example-javascript/package.json | 2 +- examples/example-svelte/package.json | 2 +- examples/example-typescript/package.json | 2 +- examples/example-vue/package.json | 2 +- package.json | 2 +- packages/core/README.md | 2 +- packages/core/package.json | 2 +- packages/core/src/cli/doctor/command.ts | 12 ++++-- .../core/tests/cli/doctor/command.test.ts | 19 ++++++--- packages/extension-plugin-api/README.md | 2 +- packages/extension-plugin-api/package.json | 2 +- packages/extension/docs/README.md | 2 +- packages/extension/package.json | 2 +- .../package-graph-view-plugin/package.json | 2 +- packages/graph-renderer/README.md | 2 +- packages/graph-renderer/package.json | 2 +- packages/plugin-api/README.md | 2 +- packages/plugin-api/package.json | 2 +- packages/plugin-godot/README.md | 2 +- packages/plugin-godot/package.json | 2 +- packages/plugin-markdown/README.md | 2 +- packages/plugin-markdown/package.json | 2 +- packages/plugin-particles/README.md | 2 +- packages/plugin-particles/package.json | 2 +- packages/plugin-svelte/README.md | 2 +- packages/plugin-svelte/package.json | 2 +- packages/plugin-typescript/README.md | 2 +- packages/plugin-typescript/package.json | 2 +- packages/plugin-unity/README.md | 2 +- packages/plugin-unity/package.json | 2 +- packages/plugin-vue/README.md | 2 +- packages/plugin-vue/package.json | 2 +- packages/tldraw/README.md | 2 +- packages/tldraw/package.json | 2 +- tests/release/corePackage.test.mjs | 2 +- tests/release/nodeRuntime.test.mjs | 39 +++++++++++++++++++ tests/release/pluginApiCompatibility.test.mjs | 6 ++- 43 files changed, 108 insertions(+), 50 deletions(-) create mode 100644 tests/release/nodeRuntime.test.mjs diff --git a/.changeset/broad-node-runtime-support.md b/.changeset/broad-node-runtime-support.md index 996beb8a0c..886a96ecc6 100644 --- a/.changeset/broad-node-runtime-support.md +++ b/.changeset/broad-node-runtime-support.md @@ -14,7 +14,7 @@ "@codegraphy-dev/tldraw": minor --- -Node.js 20 is no longer supported. Upgrade to Node.js 22.14.0 or newer before +Node.js 20 is no longer supported. Upgrade to Node.js `^22.14.0 || >=23.6.0` before updating CodeGraphy Core, interfaces, APIs, or plugins. Newer Node.js releases remain supported because these packages do not set a maximum version. diff --git a/.changeset/clean-tree-sitter-runtime.md b/.changeset/clean-tree-sitter-runtime.md index 7c14171909..f1c8a43c31 100644 --- a/.changeset/clean-tree-sitter-runtime.md +++ b/.changeset/clean-tree-sitter-runtime.md @@ -4,6 +4,6 @@ "@codegraphy-dev/tldraw": patch --- -Use Tree-sitter's upstream prebuilt runtime on Node.js 22.14.0 and newer. +Use Tree-sitter's upstream prebuilt runtime on Node.js `^22.14.0 || >=23.6.0`. Language analysis is unchanged, and CodeGraphy no longer needs its temporary native-build patch. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ed046d0a1..4a29d97088 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ CodeGraphy accepts focused changes through pull requests against `main`. ### Prerequisites -- Node.js 22.14.0 or newer; use an active LTS release +- Node.js `^22.14.0 || >=23.6.0`; use an active LTS release - pnpm 10+ - VS Code 1.101+ @@ -110,7 +110,7 @@ pnpm --filter @codegraphy-dev/extension exec playwright install chromium # Brow pnpm --filter @codegraphy-dev/extension exec vitest run --config vitest.config.ts tests/path/to/file.test.ts ``` -Use an active Node.js LTS release for repository development, release, and quality-tool commands. Published packages require Node.js 22.14.0 or newer and do not set a maximum version. +Use an active Node.js LTS release for repository development, release, and quality-tool commands. Published packages require Node.js `^22.14.0 || >=23.6.0` and do not set a maximum version. ## Reporting bugs diff --git a/README.md b/README.md index 3571fe7b15..bfef1bfbe8 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ The extension publishes native runtime targets for Linux x64, macOS Apple Silico ### CLI and Plugins -The CLI, plugins, and interface packages require Node.js 22.14.0 or newer. +The CLI, plugins, and interface packages require Node.js `^22.14.0 || >=23.6.0`. Node.js 20 is not supported. Upgrade Node.js before you update these npm packages. An active LTS release is recommended, and CodeGraphy does not set a maximum Node.js version. @@ -110,7 +110,7 @@ codegraphy -C /path/to/workspace index ### tldraw Offline -The first tldraw interface supports macOS and Node.js 22.14.0 and newer. An active +The first tldraw interface supports macOS and Node.js `^22.14.0 || >=23.6.0`. An active LTS release is recommended. Install the [tldraw offline desktop app](https://www.tldraw.com/), Core, and the interface. Then run the launcher from the workspace to index: diff --git a/apps/web/package.json b/apps/web/package.json index 7bb43e2f33..154727a236 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -5,7 +5,7 @@ "private": true, "type": "module", "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "scripts": { "dev": "next dev", diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md index e47cf49485..4a5021d89b 100644 --- a/docs/PLUGINS.md +++ b/docs/PLUGINS.md @@ -9,7 +9,7 @@ has a small API for the behavior that it owns. extends the VS Code extension and Graph View. Core, Core plugins, Extension plugins, and interface packages require Node.js -22.14.0 or newer. Node.js 20 is not supported. Upgrade Node.js before you +`^22.14.0 || >=23.6.0`. Node.js 20 is not supported. Upgrade Node.js before you update these npm packages. CodeGraphy does not set a maximum Node.js version. ## The basic model diff --git a/examples/example-javascript/package.json b/examples/example-javascript/package.json index c4ba395baa..73dd318277 100644 --- a/examples/example-javascript/package.json +++ b/examples/example-javascript/package.json @@ -2,7 +2,7 @@ "name": "codegraphy-javascript-example", "private": true, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "type": "module" } diff --git a/examples/example-svelte/package.json b/examples/example-svelte/package.json index bce77ccb46..1cf7e560c8 100644 --- a/examples/example-svelte/package.json +++ b/examples/example-svelte/package.json @@ -2,7 +2,7 @@ "name": "codegraphy-svelte-example", "private": true, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "version": "0.0.0", "type": "module", diff --git a/examples/example-typescript/package.json b/examples/example-typescript/package.json index 9af3c0a19a..1567cc7649 100644 --- a/examples/example-typescript/package.json +++ b/examples/example-typescript/package.json @@ -2,7 +2,7 @@ "name": "codegraphy-typescript-example", "private": true, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "scripts": { "start": "tsx src/index.ts", diff --git a/examples/example-vue/package.json b/examples/example-vue/package.json index 8bbe5efc51..0d25f43339 100644 --- a/examples/example-vue/package.json +++ b/examples/example-vue/package.json @@ -2,7 +2,7 @@ "name": "codegraphy-vue-example", "private": true, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "version": "0.0.0", "type": "module", diff --git a/package.json b/package.json index 0a98f54117..53d82599f6 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=22.14.0", + "node": "^22.14.0 || >=23.6.0", "vscode": "^1.101.0" }, "files": [ diff --git a/packages/core/README.md b/packages/core/README.md index 0dfdcae091..855da38ddc 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -4,7 +4,7 @@ Shared CodeGraphy engine package for workspace indexing, Graph Cache access, plu This package is the headless core used by the VS Code extension and CLI. -The published CLI supports Node.js 22.14.0 and newer. An active Long-Term Support release is recommended. +The published CLI supports Node.js `^22.14.0 || >=23.6.0`. An active Long-Term Support release is recommended. The VS Code extension bundles this package for extension runtime behavior. Users install `@codegraphy-dev/core` globally only when they want terminal workflows such as Indexing, diagnostics, graph queries, Graph Scope and filter configuration, plugin registration, or workspace plugin enablement. diff --git a/packages/core/package.json b/packages/core/package.json index 26c2eb2422..07d7358461 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -28,7 +28,7 @@ } }, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "publishConfig": { "access": "public" diff --git a/packages/core/src/cli/doctor/command.ts b/packages/core/src/cli/doctor/command.ts index af502247fe..6d4e047d22 100644 --- a/packages/core/src/cli/doctor/command.ts +++ b/packages/core/src/cli/doctor/command.ts @@ -16,11 +16,15 @@ import { inspectWorkspaceAnalysisDatabase } from '../../graphCache/database/stor import { readCodeGraphyWorkspaceMeta } from '../../workspace/meta'; import { createDoctorCacheCheck } from './cacheCheck/model'; +export const SUPPORTED_NODE_RUNTIME_RANGE = '^22.14.0 || >=23.6.0'; + export function isSupportedNodeRuntime(version: string): boolean { const [majorText, minorText] = version.split('.'); const major = Number(majorText); const minor = Number(minorText); - return major > 22 || (major === 22 && minor >= 14); + return (major === 22 && minor >= 14) + || (major === 23 && minor >= 6) + || major >= 24; } function readSettingsCheck(workspaceRoot: string): Record { @@ -66,8 +70,10 @@ export function runDoctorCommand(command: CliCommand): CommandExecutionResult { runtime: { ok: runtimeOk, version: process.version, - supported: '>=22.14.0', - ...(runtimeOk ? {} : { action: 'Use Node.js 22.14.0 or newer.' }), + supported: SUPPORTED_NODE_RUNTIME_RANGE, + ...(runtimeOk ? {} : { + action: `Use a Node.js version matching ${SUPPORTED_NODE_RUNTIME_RANGE}.`, + }), }, settings: settingsCheck, cache: createDoctorCacheCheck({ diff --git a/packages/core/tests/cli/doctor/command.test.ts b/packages/core/tests/cli/doctor/command.test.ts index 216e9beabc..ca8c81f350 100644 --- a/packages/core/tests/cli/doctor/command.test.ts +++ b/packages/core/tests/cli/doctor/command.test.ts @@ -6,18 +6,27 @@ import { describe, expect, it, vi } from 'vitest'; import { runCli } from '../../../src/cli/run'; import { requestCodeGraphyIndexWorkspace } from '../../../src/workspace/requestIndexing'; import { getWorkspaceAnalysisDatabasePath } from '../../../src/graphCache/database/storage'; -import { isSupportedNodeRuntime } from '../../../src/cli/doctor/command'; +import { + isSupportedNodeRuntime, + SUPPORTED_NODE_RUNTIME_RANGE, +} from '../../../src/cli/doctor/command'; describe('isSupportedNodeRuntime', () => { it.each([ - ['20.20.1', false], - ['22.13.1', false], + ['22.13.0', false], ['22.14.0', true], - ['22.22.0', true], + ['23.0.0', false], + ['23.5.0', false], + ['23.6.0', true], ['24.0.0', true], + ['25.0.0', true], ])('reports whether Node.js %s satisfies the runtime contract', (version, expected) => { expect(isSupportedNodeRuntime(version)).toBe(expected); }); + + it('publishes the same range used by package manifests', () => { + expect(SUPPORTED_NODE_RUNTIME_RANGE).toBe('^22.14.0 || >=23.6.0'); + }); }); describe('cli doctor', () => { @@ -179,7 +188,7 @@ describe('cli doctor', () => { details: { healthy: false, checks: { - runtime: { ok: true, supported: '>=22.14.0' }, + runtime: { ok: true, supported: '^22.14.0 || >=23.6.0' }, settings: { ok: false, action: 'Run `codegraphy index` to create workspace settings.' }, cache: { ok: false, state: 'missing', action: 'Run `codegraphy index`.' }, plugins: { ok: true, warnings: [] }, diff --git a/packages/extension-plugin-api/README.md b/packages/extension-plugin-api/README.md index b89564b129..91c23314c5 100644 --- a/packages/extension-plugin-api/README.md +++ b/packages/extension-plugin-api/README.md @@ -20,7 +20,7 @@ runtimes separate and let each host load its own descriptor. ## Install -Installing this package requires Node.js 22.14.0 or newer. Node.js 20 is not +Installing this package requires Node.js `^22.14.0 || >=23.6.0`. Node.js 20 is not supported. ```bash diff --git a/packages/extension-plugin-api/package.json b/packages/extension-plugin-api/package.json index 6c0324cb92..ac640fccea 100644 --- a/packages/extension-plugin-api/package.json +++ b/packages/extension-plugin-api/package.json @@ -3,7 +3,7 @@ "version": "1.1.0", "description": "Type definitions for plugins hosted by the CodeGraphy VS Code extension", "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "repository": { "type": "git", diff --git a/packages/extension/docs/README.md b/packages/extension/docs/README.md index c3926ae517..906dd6046b 100644 --- a/packages/extension/docs/README.md +++ b/packages/extension/docs/README.md @@ -20,7 +20,7 @@ Current extension behavior to keep in mind while reading the package docs: The Marketplace extension requires VS Code 1.101 or newer and uses the Node.js runtime supplied by VS Code. Extension users do not install Node.js separately. -The published CLI and npm packages require Node.js 22.14.0 or newer. Local +The published CLI and npm packages require Node.js `^22.14.0 || >=23.6.0`. Local tooling and CI use an active Node.js LTS release. The source tree is split by runtime boundary: diff --git a/packages/extension/package.json b/packages/extension/package.json index f940deae00..4a8d72abcc 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -5,7 +5,7 @@ "description": "CodeGraphy VS Code extension core", "license": "MIT", "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "scripts": { "build": "pnpm run build:extension && pnpm run build:webview", diff --git a/packages/extension/src/e2e/fixtures/package-graph-view-plugin/package.json b/packages/extension/src/e2e/fixtures/package-graph-view-plugin/package.json index ef6ba43cb0..f685a792ea 100644 --- a/packages/extension/src/e2e/fixtures/package-graph-view-plugin/package.json +++ b/packages/extension/src/e2e/fixtures/package-graph-view-plugin/package.json @@ -2,7 +2,7 @@ "name": "@codegraphy/e2e-graph-view-plugin", "version": "1.0.0", "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "type": "module", "exports": "./plugin.js", diff --git a/packages/graph-renderer/README.md b/packages/graph-renderer/README.md index 09220a7240..e8e8c46c82 100644 --- a/packages/graph-renderer/README.md +++ b/packages/graph-renderer/README.md @@ -10,7 +10,7 @@ The package owns graph rendering and graph physics only. Your application owns i ## Install -Installing this package requires Node.js 22.14.0 or newer. The renderer still +Installing this package requires Node.js `^22.14.0 || >=23.6.0`. The renderer still runs in a browser with WebAssembly and WebGPU; the Node.js requirement applies to package installation and tooling. diff --git a/packages/graph-renderer/package.json b/packages/graph-renderer/package.json index 15c07ee9e1..dc1cbf7975 100644 --- a/packages/graph-renderer/package.json +++ b/packages/graph-renderer/package.json @@ -27,7 +27,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "keywords": [ "codegraphy", diff --git a/packages/plugin-api/README.md b/packages/plugin-api/README.md index 2b11402914..31ac52c938 100644 --- a/packages/plugin-api/README.md +++ b/packages/plugin-api/README.md @@ -8,7 +8,7 @@ VS Code Extension and Graph View behavior. ## Install -Installing this package requires Node.js 22.14.0 or newer. Node.js 20 is not +Installing this package requires Node.js `^22.14.0 || >=23.6.0`. Node.js 20 is not supported. ```bash diff --git a/packages/plugin-api/package.json b/packages/plugin-api/package.json index b812ddc7db..b7c9ff45fc 100644 --- a/packages/plugin-api/package.json +++ b/packages/plugin-api/package.json @@ -3,7 +3,7 @@ "version": "7.0.0", "description": "Type definitions for CodeGraphy plugins", "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "repository": { "type": "git", diff --git a/packages/plugin-godot/README.md b/packages/plugin-godot/README.md index b1dbc1a410..34969ccac6 100644 --- a/packages/plugin-godot/README.md +++ b/packages/plugin-godot/README.md @@ -12,7 +12,7 @@ colors, shapes, and icons. The CLI loads only the Core entry. ## Install -This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. +This package requires Node.js `^22.14.0 || >=23.6.0`. Node.js 20 is not supported. Install `@codegraphy-dev/core` first if the `codegraphy` CLI is not already available. diff --git a/packages/plugin-godot/package.json b/packages/plugin-godot/package.json index d4de3c6bc6..f3e90b3f46 100644 --- a/packages/plugin-godot/package.json +++ b/packages/plugin-godot/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "keywords": [ "codegraphy", diff --git a/packages/plugin-markdown/README.md b/packages/plugin-markdown/README.md index 2543a730e4..387b72d9b7 100644 --- a/packages/plugin-markdown/README.md +++ b/packages/plugin-markdown/README.md @@ -4,7 +4,7 @@ Headless CodeGraphy plugin for Markdown and Obsidian-style wikilinks. This package exposes the Markdown plugin runtime used by `@codegraphy-dev/core`. It detects wikilinks such as `[[Note]]`, `[[folder/Note|Alias]]`, and `![[Embed]]`, then emits Relationship Graph references between workspace files. -Installing this package separately requires Node.js 22.14.0 or newer. Node.js +Installing this package separately requires Node.js `^22.14.0 || >=23.6.0`. Node.js 20 is not supported. The VS Code extension already includes and manages this plugin, so extension users do not install it separately. diff --git a/packages/plugin-markdown/package.json b/packages/plugin-markdown/package.json index 3fc80224a9..ef15386431 100644 --- a/packages/plugin-markdown/package.json +++ b/packages/plugin-markdown/package.json @@ -13,7 +13,7 @@ } }, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "publishConfig": { "access": "public" diff --git a/packages/plugin-particles/README.md b/packages/plugin-particles/README.md index cbe6a1459d..ad99571348 100644 --- a/packages/plugin-particles/README.md +++ b/packages/plugin-particles/README.md @@ -6,7 +6,7 @@ The built-in presets are Synapse, Rain, Constellations, Perlin Flow, Leaves, Spa ## Install -This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. +This package requires Node.js `^22.14.0 || >=23.6.0`. Node.js 20 is not supported. ```bash npm install -g @codegraphy-dev/plugin-particles diff --git a/packages/plugin-particles/package.json b/packages/plugin-particles/package.json index c654f03814..cf4a45aabd 100644 --- a/packages/plugin-particles/package.json +++ b/packages/plugin-particles/package.json @@ -25,7 +25,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "keywords": [ "codegraphy", diff --git a/packages/plugin-svelte/README.md b/packages/plugin-svelte/README.md index 486c4e4ccb..dccf58ae81 100644 --- a/packages/plugin-svelte/README.md +++ b/packages/plugin-svelte/README.md @@ -7,7 +7,7 @@ Adds Svelte component script import analysis to [CodeGraphy](https://marketplace ## Install -This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. +This package requires Node.js `^22.14.0 || >=23.6.0`. Node.js 20 is not supported. Install `@codegraphy-dev/core` first if the `codegraphy` CLI is not already available. diff --git a/packages/plugin-svelte/package.json b/packages/plugin-svelte/package.json index 86a5b57b67..421e622660 100644 --- a/packages/plugin-svelte/package.json +++ b/packages/plugin-svelte/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "keywords": [ "codegraphy", diff --git a/packages/plugin-typescript/README.md b/packages/plugin-typescript/README.md index 9fef5940cd..c39c2b729e 100644 --- a/packages/plugin-typescript/README.md +++ b/packages/plugin-typescript/README.md @@ -7,7 +7,7 @@ Adds TypeScript project-aware alias import relationships and TypeScript/JavaScri ## Install -This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. +This package requires Node.js `^22.14.0 || >=23.6.0`. Node.js 20 is not supported. Install `@codegraphy-dev/core` first if the `codegraphy` CLI is not already available. diff --git a/packages/plugin-typescript/package.json b/packages/plugin-typescript/package.json index 276a72f3a5..3ae3a8415a 100644 --- a/packages/plugin-typescript/package.json +++ b/packages/plugin-typescript/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "keywords": [ "codegraphy", diff --git a/packages/plugin-unity/README.md b/packages/plugin-unity/README.md index de699ce3f6..6c1de89bc0 100644 --- a/packages/plugin-unity/README.md +++ b/packages/plugin-unity/README.md @@ -12,7 +12,7 @@ colors, shapes, and icons. The CLI loads only the Core entry. ## Install -This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. +This package requires Node.js `^22.14.0 || >=23.6.0`. Node.js 20 is not supported. Install `@codegraphy-dev/core` first if the `codegraphy` CLI is not already available. diff --git a/packages/plugin-unity/package.json b/packages/plugin-unity/package.json index a91af9c531..f843367480 100644 --- a/packages/plugin-unity/package.json +++ b/packages/plugin-unity/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "keywords": [ "codegraphy", diff --git a/packages/plugin-vue/README.md b/packages/plugin-vue/README.md index 3ddd587884..468785f8e0 100644 --- a/packages/plugin-vue/README.md +++ b/packages/plugin-vue/README.md @@ -7,7 +7,7 @@ Adds Vue Single-File Component script import analysis to [CodeGraphy](https://ma ## Install -This package requires Node.js 22.14.0 or newer. Node.js 20 is not supported. +This package requires Node.js `^22.14.0 || >=23.6.0`. Node.js 20 is not supported. Install `@codegraphy-dev/core` first if the `codegraphy` CLI is not already available. diff --git a/packages/plugin-vue/package.json b/packages/plugin-vue/package.json index a6d515092d..0791499b3b 100644 --- a/packages/plugin-vue/package.json +++ b/packages/plugin-vue/package.json @@ -21,7 +21,7 @@ "url": "https://github.com/joesobo/CodeGraphyV4/issues" }, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "keywords": [ "codegraphy", diff --git a/packages/tldraw/README.md b/packages/tldraw/README.md index 732e30133c..b28a46730d 100644 --- a/packages/tldraw/README.md +++ b/packages/tldraw/README.md @@ -9,7 +9,7 @@ force physics inside the saved `.tldraw` document. ## Requirements - macOS -- Node.js 22.14.0 or newer. An active LTS release is recommended. +- Node.js `^22.14.0 || >=23.6.0`. An active LTS release is recommended. - [tldraw offline](https://offline.tldraw.com/) installed in `/Applications` The first launcher release supports macOS because it opens documents through diff --git a/packages/tldraw/package.json b/packages/tldraw/package.json index 00e909f3a4..3131438bab 100644 --- a/packages/tldraw/package.json +++ b/packages/tldraw/package.json @@ -24,7 +24,7 @@ "codegraphy-tldraw": "./dist/codegraphy-tldraw.js" }, "engines": { - "node": ">=22.14.0" + "node": "^22.14.0 || >=23.6.0" }, "os": [ "darwin" diff --git a/tests/release/corePackage.test.mjs b/tests/release/corePackage.test.mjs index 6b909d5794..cb9322da1b 100644 --- a/tests/release/corePackage.test.mjs +++ b/tests/release/corePackage.test.mjs @@ -31,7 +31,7 @@ test('Core package declares the Node range supported by its native dependencies' fs.readFileSync(path.join(repoRoot, 'packages', 'core', 'package.json'), 'utf8'), ); - assert.equal(manifest.engines.node, '>=22.14.0'); + assert.equal(manifest.engines.node, '^22.14.0 || >=23.6.0'); }); test('Core publishes dependency-free Graph Scope defaults as a focused subpath', () => { diff --git a/tests/release/nodeRuntime.test.mjs b/tests/release/nodeRuntime.test.mjs new file mode 100644 index 0000000000..6fef2e788c --- /dev/null +++ b/tests/release/nodeRuntime.test.mjs @@ -0,0 +1,39 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import test from 'node:test'; + +const NODE_RUNTIME_RANGE = '^22.14.0 || >=23.6.0'; +const MANIFEST_PATHS = [ + 'package.json', + 'apps/web/package.json', + 'examples/example-javascript/package.json', + 'examples/example-svelte/package.json', + 'examples/example-typescript/package.json', + 'examples/example-vue/package.json', + 'packages/core/package.json', + 'packages/extension/package.json', + 'packages/extension-plugin-api/package.json', + 'packages/extension/src/e2e/fixtures/package-graph-view-plugin/package.json', + 'packages/graph-renderer/package.json', + 'packages/plugin-api/package.json', + 'packages/plugin-godot/package.json', + 'packages/plugin-markdown/package.json', + 'packages/plugin-particles/package.json', + 'packages/plugin-svelte/package.json', + 'packages/plugin-typescript/package.json', + 'packages/plugin-unity/package.json', + 'packages/plugin-vue/package.json', + 'packages/tldraw/package.json', +]; + +function readManifest(relativePath) { + return JSON.parse(fs.readFileSync(path.join(process.cwd(), relativePath), 'utf8')); +} + +test('first-party manifests declare the Node-API 10 compatible runtime range', () => { + for (const manifestPath of MANIFEST_PATHS) { + const manifest = readManifest(manifestPath); + assert.equal(manifest.engines?.node, NODE_RUNTIME_RANGE, manifestPath); + } +}); diff --git a/tests/release/pluginApiCompatibility.test.mjs b/tests/release/pluginApiCompatibility.test.mjs index 232011f8c8..4f87bf52d6 100644 --- a/tests/release/pluginApiCompatibility.test.mjs +++ b/tests/release/pluginApiCompatibility.test.mjs @@ -67,7 +67,11 @@ test('public plugin API packages include publish and support metadata', () => { for (const packageName of ['plugin-api', 'extension-plugin-api']) { const packageManifest = readJson(`packages/${packageName}/package.json`); - assert.equal(packageManifest.engines?.node, '>=22.14.0', `${packageName} Node.js engine`); + assert.equal( + packageManifest.engines?.node, + '^22.14.0 || >=23.6.0', + `${packageName} Node.js engine`, + ); assert.equal( packageManifest.repository?.url, 'https://github.com/joesobo/CodeGraphyV4.git', From fe54f6eaffbf5a8d8387e86ac0f39354c4837b8b Mon Sep 17 00:00:00 2001 From: joesobo Date: Wed, 29 Jul 2026 18:49:16 -0700 Subject: [PATCH 14/14] fix: harden packaged runtime validation --- packages/core/src/cli/doctor/command.ts | 10 ++- .../core/tests/cli/doctor/command.test.ts | 11 ++- .../extension/scripts/externalPackages.ts | 1 + .../extension/scripts/runtimeStaging/model.ts | 15 +++- .../build/runtimeStaging/model.test.ts | 6 +- scripts/validate-vsix-native-artifacts.mjs | 45 +++++++++++ tests/release/vsixNativeArtifacts.test.mjs | 80 ++++++++++++++++++- 7 files changed, 152 insertions(+), 16 deletions(-) diff --git a/packages/core/src/cli/doctor/command.ts b/packages/core/src/cli/doctor/command.ts index 6d4e047d22..75e3b9662a 100644 --- a/packages/core/src/cli/doctor/command.ts +++ b/packages/core/src/cli/doctor/command.ts @@ -19,9 +19,13 @@ import { createDoctorCacheCheck } from './cacheCheck/model'; export const SUPPORTED_NODE_RUNTIME_RANGE = '^22.14.0 || >=23.6.0'; export function isSupportedNodeRuntime(version: string): boolean { - const [majorText, minorText] = version.split('.'); - const major = Number(majorText); - const minor = Number(minorText); + const stableVersion = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(version); + if (!stableVersion) { + return false; + } + + const major = Number(stableVersion[1]); + const minor = Number(stableVersion[2]); return (major === 22 && minor >= 14) || (major === 23 && minor >= 6) || major >= 24; diff --git a/packages/core/tests/cli/doctor/command.test.ts b/packages/core/tests/cli/doctor/command.test.ts index ca8c81f350..2b72015ae5 100644 --- a/packages/core/tests/cli/doctor/command.test.ts +++ b/packages/core/tests/cli/doctor/command.test.ts @@ -13,14 +13,17 @@ import { describe('isSupportedNodeRuntime', () => { it.each([ - ['22.13.0', false], + ['22.13.99', false], ['22.14.0', true], - ['23.0.0', false], - ['23.5.0', false], + ['22.99.99', true], + ['23.5.99', false], ['23.6.0', true], ['24.0.0', true], ['25.0.0', true], - ])('reports whether Node.js %s satisfies the runtime contract', (version, expected) => { + ['22.14', false], + ['23.6.0-rc.1', false], + ['not-a-version', false], + ])('reports whether stable Node.js %s satisfies the runtime contract', (version, expected) => { expect(isSupportedNodeRuntime(version)).toBe(expected); }); diff --git a/packages/extension/scripts/externalPackages.ts b/packages/extension/scripts/externalPackages.ts index 9d9ad32abe..1022b23470 100644 --- a/packages/extension/scripts/externalPackages.ts +++ b/packages/extension/scripts/externalPackages.ts @@ -9,6 +9,7 @@ export { resolveExtensionRuntimeTarget, resolveRuntimePackageRootPath, syncExtensionRuntimePackages, + syncParticlesRuntimePackages, } from './runtimeStaging/model'; export const EXTENSION_EXTERNAL_PACKAGE_NAMES = [ diff --git a/packages/extension/scripts/runtimeStaging/model.ts b/packages/extension/scripts/runtimeStaging/model.ts index d5438a556c..9396f3399c 100644 --- a/packages/extension/scripts/runtimeStaging/model.ts +++ b/packages/extension/scripts/runtimeStaging/model.ts @@ -405,6 +405,16 @@ function copyRuntimePackagePlans( )); } +export function syncParticlesRuntimePackages( + particlesPluginOutputFilePath: string, + target: ExtensionRuntimeTarget = resolveExtensionRuntimeTarget(), +): string[] { + return copyRuntimePackagePlans( + particlesPluginOutputFilePath, + createEsbuildRuntimePackagePlans(target), + ); +} + export function syncExtensionRuntimePackages( outputFilePath: string, target: ExtensionRuntimeTarget = resolveExtensionRuntimeTarget(), @@ -420,9 +430,6 @@ export function syncExtensionRuntimePackages( return [ ...copyRuntimePackagePlans(outputFilePath, createRuntimePackagePlans(target)), - ...copyRuntimePackagePlans( - particlesPluginOutputFilePath, - createEsbuildRuntimePackagePlans(target), - ), + ...syncParticlesRuntimePackages(particlesPluginOutputFilePath, target), ]; } diff --git a/packages/extension/tests/extension/build/runtimeStaging/model.test.ts b/packages/extension/tests/extension/build/runtimeStaging/model.test.ts index 68321778e8..feb507b7a2 100644 --- a/packages/extension/tests/extension/build/runtimeStaging/model.test.ts +++ b/packages/extension/tests/extension/build/runtimeStaging/model.test.ts @@ -9,7 +9,7 @@ import { EXTENSION_EXTERNAL_PACKAGE_NAMES, getVendoredPackageRootPath, resolveExtensionRuntimeTarget, - syncExtensionRuntimePackages, + syncParticlesRuntimePackages, } from '../../../../scripts/externalPackages'; const EXTENSION_PACKAGE_ROOT = path.resolve( @@ -111,8 +111,8 @@ describe('runtime package build support', () => { fs.mkdirSync(path.dirname(effectSourcePath), { recursive: true }); fs.writeFileSync(effectSourcePath, 'export default function customEffect() {}\n'); - syncExtensionRuntimePackages( - path.join(stageRootPath, 'dist', 'extension.js'), + syncParticlesRuntimePackages( + packagedPluginEntryPath, resolveExtensionRuntimeTarget(), ); expect(fs.existsSync(path.join(stageRootPath, 'dist', 'node_modules', 'esbuild'))).toBe(false); diff --git a/scripts/validate-vsix-native-artifacts.mjs b/scripts/validate-vsix-native-artifacts.mjs index f275dd4f90..bea24def55 100644 --- a/scripts/validate-vsix-native-artifacts.mjs +++ b/scripts/validate-vsix-native-artifacts.mjs @@ -29,11 +29,31 @@ const parcelWatcherNativeBinaryPathByTarget = { 'win32-x64': 'extension/dist/node_modules/@parcel/watcher-win32-x64/watcher.node', }; +const particlesEsbuildNativeBinaryPathByTarget = { + 'linux-x64': 'extension/packages/plugin-particles/dist/node_modules/@esbuild/linux-x64/bin/esbuild', + 'darwin-arm64': 'extension/packages/plugin-particles/dist/node_modules/@esbuild/darwin-arm64/bin/esbuild', + 'win32-x64': 'extension/packages/plugin-particles/dist/node_modules/@esbuild/win32-x64/esbuild.exe', +}; + +const rootEsbuildNativePackagePathByTarget = { + 'linux-x64': 'extension/dist/node_modules/@esbuild/linux-x64/', + 'darwin-arm64': 'extension/dist/node_modules/@esbuild/darwin-arm64/', + 'win32-x64': 'extension/dist/node_modules/@esbuild/win32-x64/', +}; + function nativeBinaryPathsForTarget(target) { return [ libsqlNativeBinaryPathByTarget[target], treeSitterNativeBinaryPathByTarget[target], parcelWatcherNativeBinaryPathByTarget[target], + particlesEsbuildNativeBinaryPathByTarget[target], + ]; +} + +function forbiddenRootEsbuildPathPrefixesForTarget(target) { + return [ + 'extension/dist/node_modules/esbuild/', + rootEsbuildNativePackagePathByTarget[target], ]; } @@ -128,6 +148,21 @@ function findVsixForTarget({ artifactsDir, version, target }) { .at(0); } +function listVsixPaths(vsixPath) { + const result = spawnSync('unzip', ['-Z1', vsixPath], { + encoding: 'utf8', + maxBuffer: 64 * 1024 * 1024, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + if (result.status !== 0) { + const errorMessage = result.error ? `${result.error.message}\n` : ''; + throw new Error(`Unable to list ${vsixPath}.\n${errorMessage}${result.stderr}`); + } + + return result.stdout.split('\n').filter(Boolean); +} + function extractNativeBinaryFromVsix(vsixPath, nativeBinaryPath) { const result = spawnSync('unzip', ['-p', vsixPath, nativeBinaryPath], { encoding: 'buffer', @@ -167,6 +202,16 @@ export function validateVsixNativeArtifacts({ throw new Error(`Missing VSIX artifact for ${target} in ${artifactsDir}.`); } + const vsixPaths = listVsixPaths(vsixPath); + for (const forbiddenPrefix of forbiddenRootEsbuildPathPrefixesForTarget(target)) { + const oldRootEsbuildPath = vsixPaths.find(entry => entry.startsWith(forbiddenPrefix)); + if (oldRootEsbuildPath) { + throw new Error( + `${path.basename(vsixPath)} must not contain old root esbuild path ${oldRootEsbuildPath}.`, + ); + } + } + for (const nativeBinaryPath of nativeBinaryPathsForTarget(target)) { const binary = extractNativeBinaryFromVsix(vsixPath, nativeBinaryPath); const actualKind = identifyNativeBinary(binary); diff --git a/tests/release/vsixNativeArtifacts.test.mjs b/tests/release/vsixNativeArtifacts.test.mjs index 18ea69fe26..87f27ea88d 100644 --- a/tests/release/vsixNativeArtifacts.test.mjs +++ b/tests/release/vsixNativeArtifacts.test.mjs @@ -70,11 +70,33 @@ test('rejects a linux x64 VSIX with a macOS Apple Silicon Tree-sitter binding', ); }); -test('validates only the requested VSIX artifact targets', () => { +test('validates target-specific plugin-local esbuild executables for every VSIX target', () => { const tempDir = mkdtempSync(path.join(tmpdir(), 'codegraphy-vsix-native-artifacts-target-')); const artifactsDir = path.join(tempDir, 'artifacts'); const version = '5.8.0'; + for (const target of ['linux-x64', 'darwin-arm64', 'win32-x64']) { + writeVsixFixture({ + artifactsDir, + version, + target, + sqliteBinary: nativeBinaryForTarget(target), + treeSitterBinary: nativeBinaryForTarget(target), + parcelWatcherBinary: nativeBinaryForTarget(target), + pluginEsbuildBinary: nativeBinaryForTarget(target), + }); + } + + assert.doesNotThrow( + () => validateVsixNativeArtifacts({ artifactsDir, version }), + ); +}); + +test('rejects a VSIX with the wrong plugin-local esbuild executable', () => { + const tempDir = mkdtempSync(path.join(tmpdir(), 'codegraphy-vsix-native-artifacts-esbuild-')); + const artifactsDir = path.join(tempDir, 'artifacts'); + const version = '5.8.0'; + writeVsixFixture({ artifactsDir, version, @@ -82,13 +104,48 @@ test('validates only the requested VSIX artifact targets', () => { sqliteBinary: createElfX64Binary(), treeSitterBinary: createElfX64Binary(), parcelWatcherBinary: createElfX64Binary(), + pluginEsbuildBinary: createMachOArm64Binary(), }); - assert.doesNotThrow( + assert.throws( () => validateVsixNativeArtifacts({ artifactsDir, version, targets: ['linux-x64'] }), + /plugin-particles\/dist\/node_modules\/@esbuild\/linux-x64\/bin\/esbuild; expected ELF x86-64\./, ); }); +test('rejects old root esbuild wrapper and native package paths', () => { + const version = '5.8.0'; + + for (const [oldRootEsbuildPath, forbiddenPrefix] of [ + [ + 'extension/dist/node_modules/esbuild/lib/main.js', + 'extension/dist/node_modules/esbuild/', + ], + [ + 'extension/dist/node_modules/@esbuild/linux-x64/bin/esbuild', + 'extension/dist/node_modules/@esbuild/linux-x64/', + ], + ]) { + const tempDir = mkdtempSync(path.join(tmpdir(), 'codegraphy-vsix-root-esbuild-')); + const artifactsDir = path.join(tempDir, 'artifacts'); + writeVsixFixture({ + artifactsDir, + version, + target: 'linux-x64', + sqliteBinary: createElfX64Binary(), + treeSitterBinary: createElfX64Binary(), + parcelWatcherBinary: createElfX64Binary(), + pluginEsbuildBinary: createElfX64Binary(), + oldRootEsbuildPath, + }); + + assert.throws( + () => validateVsixNativeArtifacts({ artifactsDir, version, targets: ['linux-x64'] }), + new RegExp(`must not contain old root esbuild path ${forbiddenPrefix}`), + ); + } +}); + function writeVsixFixture({ artifactsDir, version, @@ -96,6 +153,8 @@ function writeVsixFixture({ sqliteBinary, treeSitterBinary, parcelWatcherBinary, + pluginEsbuildBinary, + oldRootEsbuildPath, }) { const fixtureRoot = mkdtempSync(path.join(tmpdir(), `codegraphy-vsix-${target}-`)); writeFixtureBinary( @@ -113,6 +172,14 @@ function writeVsixFixture({ parcelWatcherNativeBinaryPath(target), parcelWatcherBinary ?? nativeBinaryForTarget(target), ); + writeFixtureBinary( + fixtureRoot, + pluginEsbuildNativeBinaryPath(target), + pluginEsbuildBinary ?? nativeBinaryForTarget(target), + ); + if (oldRootEsbuildPath) { + writeFixtureBinary(fixtureRoot, oldRootEsbuildPath, nativeBinaryForTarget(target)); + } mkdirSync(artifactsDir, { recursive: true }); const vsixPath = path.join(artifactsDir, `codegraphy.codegraphy-${version}-${target}.vsix`); @@ -153,6 +220,15 @@ function parcelWatcherNativeBinaryPath(target) { return `extension/dist/node_modules/@parcel/${packageByTarget[target]}/watcher.node`; } +function pluginEsbuildNativeBinaryPath(target) { + const executablePathByTarget = { + 'linux-x64': '@esbuild/linux-x64/bin/esbuild', + 'darwin-arm64': '@esbuild/darwin-arm64/bin/esbuild', + 'win32-x64': '@esbuild/win32-x64/esbuild.exe', + }; + return `extension/packages/plugin-particles/dist/node_modules/${executablePathByTarget[target]}`; +} + function nativeBinaryForTarget(target) { if (target === 'linux-x64') return createElfX64Binary(); if (target === 'darwin-arm64') return createMachOArm64Binary();