Skip to content

feat(types): migrate test suite and config files to TypeScript (Phase 6)#588

Merged
carlos-alm merged 16 commits intomainfrom
feat/ts-migrate-tests
Mar 25, 2026
Merged

feat(types): migrate test suite and config files to TypeScript (Phase 6)#588
carlos-alm merged 16 commits intomainfrom
feat/ts-migrate-tests

Conversation

@carlos-alm
Copy link
Copy Markdown
Contributor

@carlos-alm carlos-alm commented Mar 24, 2026

Summary

  • Rename all 114 test files + 2 helpers from .js to .ts
  • Rename vitest.config.js and commitlint.config.js to .ts
  • Rename 16 scripts + 1 docs example from .js to .ts
  • Update all node scripts/X.js invocations in package.json, CI workflows, and shell scripts to node --experimental-strip-types scripts/X.ts
  • Add type annotations to all bare let declarations to fix noImplicitAnyLet lint errors (0 lint errors)
  • Remove jsToTsResolver vite plugin from vitest config (no longer needed)
  • Drop allowJs/checkJs migration flags from tsconfig.json

Remaining JS files (must stay JS for Node 20 / bootstrap reasons):

  • scripts/test.js + scripts/build-wasm.js — Node 20 bootstrap (no strip-types)
  • 4 ESM loader hooks (ts-resolve-*.js, ts-resolver-*.js) — bootstrap .ts support
  • scripts/gen-deps.cjs + src/index.cjs — CommonJS by design

Test plan

  • Full test suite passes: 114 files, 2098 tests, 0 failures
  • tsc --noEmit clean
  • biome check — 0 errors
  • node --experimental-strip-types scripts/verify-imports.ts passes
  • CI passes on all Node versions (20, 22)

Rename all 114 test files and 2 helpers from .js to .ts, plus
vitest.config and commitlint.config. Add type annotations to fix
noImplicitAnyLet lint errors. Remove jsToTsResolver vite plugin
(no longer needed). Drop allowJs/checkJs from tsconfig.
@claude
Copy link
Copy Markdown

claude bot commented Mar 24, 2026

Claude finished @carlos-alm's task —— View job


PR Review: TypeScript Migration Phase 6 ✅

Review Todo List:

  • Phase 1: Root cause analysis
  • Phase 2: Configuration change detection
  • Phase 3: Test integrity verification
  • Phase 4: Code quality assessment
  • Phase 5: Backlog compliance evaluation
  • Final recommendation

Phase 1: Root Cause Analysis ✅

Problem Being Solved: Final phase of TypeScript migration - converting test suite (.js → .ts) to complete the codebase migration and enable full TypeScript strict mode checking.

Why This Approach: Logical completion of the migration phases. The approach is sound:

  • Renames test files to .ts (no functionality changes)
  • Removes temporary migration scaffolding (jsToTsResolver plugin, allowJs/checkJs flags)
  • Fixes remaining noImplicitAnyLet violations with explicit type annotations

Risk Assessment: Low risk - this is cleanup/completion work, not new functionality.

Phase 2: Configuration Change Detection ✅

Changes Reviewed:

  • tsconfig.json: Appropriately removes migration flags (allowJs: true, checkJs: false) - these were temporary scaffolding
  • vitest.config.ts: Removes jsToTsResolver plugin - no longer needed since all importers are now .ts
  • commitlint.config.js → .ts: Simple rename, identical content
  • .npmignore: Updates reference to renamed config file
  • biome.json: No changes - already supports both .js and .ts

Assessment: All configuration changes are justified cleanup after migration completion. No rules were relaxed or weakened.

Phase 3: Test Integrity Verification ✅

File Renames: All 114 test files + 2 helpers renamed from .js to .ts with no logic changes.

Type Annotations Added: Systematic addition of explicit types to let declarations to fix noImplicitAnyLet violations:

  • let tmpDir, dbPath;let tmpDir: string, dbPath: string;
  • let parsers;let parsers: any;
  • let cache;let cache: any;

Assessment: Test logic is preserved. Type annotations are conservative (using any where precise typing is unnecessary for tests). No assertions were weakened or removed.

Phase 4: Code Quality Assessment ✅

Approach Quality: Conservative and methodical - only adds types where required by the linter, uses any for complex types that don't need precision in tests.

Consistency: All changes follow the same pattern - minimal annotations to satisfy TypeScript without over-engineering test types.

Maintainability: Removes temporary scaffolding that was supporting the migration, leaving cleaner configuration.

Phase 5: Backlog Compliance Evaluation ✅

Root Cause Analysis

Problem Being Solved: Completing TypeScript migration by converting the test suite to enable full strict mode checking and removing temporary migration scaffolding.
Why This Approach: Systematic, minimal changes that preserve functionality while enabling better tooling.
Risk Assessment: Very low risk - this is pure infrastructure cleanup with no behavior changes.

Backlog Compliance

  • Zero-dep: ✅ — No new runtime dependencies
  • Foundation-aligned: ✅ — Maintains all core principles, improves type safety
  • Problem-fit: 3 — Useful foundation work that improves tooling for agents
  • Breaking: No — All interfaces remain unchanged
  • Tier: 1

Critical Concerns

None identified. This is excellent, methodical completion work.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: High-quality completion of TypeScript migration. Conservative approach with no functionality changes, proper cleanup of temporary scaffolding, and systematic type annotations. This completes a multi-phase migration effort and enables better tooling support. Only minor point: could have been more ambitious with precise typing in some test scenarios, but the conservative approach is appropriate and maintainable.

Rename 16 scripts and 1 docs example from .js to .ts. Update all
invocations in package.json, CI workflows, and shell scripts to use
node --experimental-strip-types.

Remaining .js files (must stay JS):
- scripts/test.js, scripts/build-wasm.js (Node 20 bootstrap)
- 4 ESM loader hooks (bootstrap .ts support)
- scripts/gen-deps.cjs, src/index.cjs (CJS by design)
- Bump engines.node from >=20 to >=22
- Remove Node 20 from CI test matrix
- Bump native build Node from 20 to 22
- Replace old JS ESM loader hooks with 2 TypeScript hooks
  (ts-resolve-loader.ts + ts-resolve-hooks.ts) that resolve .js→.ts
  import specifiers at runtime
- Remove canStripTypes helper and version guards from tests
- Update src/index.cjs to import('./index.ts') for dev; build step
  produces dist/index.cjs with ./index.js for published package
- Simplify vitest.config.ts to use --experimental-strip-types only
- Update child-process test invocations to use --import loader flag
- Rewrite scripts/gen-deps.cjs as ESM TypeScript
- Rename scripts/build-wasm.js → .ts

Impact: 1 functions changed, 1 affected
…ile (#588)

The test file was renamed from .js to .ts in the Phase 6 migration
but the CI workflow was not updated, causing test discovery to fail.
@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 24, 2026

Greptile Summary

This is Phase 6 of the JS→TS migration, renaming 114 test files, 16 scripts, and 2 config files from .js to .ts, dropping allowJs/checkJs from tsconfig.json, and updating every invocation site in package.json, CI workflows, and shell scripts accordingly. All previously raised review concerns (minor-version guard for --experimental-strip-types, dynamic STRIP_FLAG in CI, replaceAll in the build script, the .ts loader files being live test code) have been addressed.

Key findings:

  • CLAUDE.md was updated for the Node version constraint but line 62 still references tests/parsers/javascript.test.js — that file is now .ts and the example command will silently fail for anyone following it.
  • docs/examples/claude-code-hooks/pre-commit-checks.ts declares context: [] without a type annotation; TypeScript infers never[], making all subsequent .push(string) calls a type error in any TypeScript-aware editor or toolchain.
  • scripts/lib/fork-engine.ts and scripts/lib/bench-config.ts were converted to TypeScript but their exported functions retain untyped parameters and use err.message without narrowing the unknown catch variable — these files are excluded from tsconfig.json so the build stays clean, but the implicit-any parameters are inconsistent with the migration's stated goal.
  • The removal of the jsToTsResolver Vite plugin and the --import loader injection from vitest.config.ts NODE_OPTIONS is intentional and correct: all test files are now .ts, and tests that spawn child processes explicitly pass the loader themselves.

Confidence Score: 4/5

  • Safe to merge — all runtime-affecting concerns from previous review rounds have been addressed; remaining issues are documentation and example-file type safety.
  • All 2098 tests pass, tsc --noEmit is clean, and biome reports zero errors. The issues found are a stale command in CLAUDE.md, a never[] inference in a documentation example file, and missing type annotations in scripts excluded from the compiler. None of these affect the production build or the running test suite.
  • CLAUDE.md (stale .js example command) and docs/examples/claude-code-hooks/pre-commit-checks.ts (never[] context field)

Important Files Changed

Filename Overview
vitest.config.ts New TypeScript config replacing vitest.config.js — correctly restores minor-version guard for --experimental-strip-types (supportsStripTypes), removes the now-unnecessary jsToTsResolver Vite plugin, and propagates the strip flag to child process NODE_OPTIONS. The --import loader injection was intentionally removed since all test files are now .ts and tests that spawn child processes explicitly pass the loader.
CLAUDE.md Updated Node version requirement to >=22.6, but the single-file vitest example on line 62 still references the old .js extension (tests/parsers/javascript.test.js) which no longer exists — will produce a "no tests found" error for anyone following the guide.
docs/examples/claude-code-hooks/pre-commit-checks.ts New TypeScript example file (renamed from .js). The output object's context field is initialised as [] which TypeScript infers as never[], causing a type error when strings are pushed into it (lines 80, 170). All other logic — cycle checks, signature warnings, dead-export blocking, diff-impact reporting — is sound.
scripts/lib/fork-engine.ts Renamed from .js to .ts. forkWorker and other exported functions are missing TypeScript parameter type annotations (implicitly any), and catch-clause err variables are accessed as .message without narrowing. Safe at runtime, but inconsistent with the TypeScript migration goals.
scripts/lib/bench-config.ts Renamed from .js to .ts. resolveBenchmarkSource is fully typed, but srcImport(srcDir, file) has no parameter types. Multiple catch blocks access err.message without narrowing. Scripts are excluded from tsconfig so no build failures, but these are inconsistencies in a TypeScript file.
package.json engines constraint tightened to >=22.6, build script updated to use replaceAll for index.ts→index.js, test scripts simplified to direct vitest invocations. The --experimental-strip-types hardcoding in npm scripts is tracked in #590 and acknowledged as a forward-compatibility concern for Node 23+.
.github/workflows/benchmark.yml All eight node invocations (four benchmark runners + four update-report scripts) now use a dynamic STRIP_FLAG variable, and the four benchmark runners correctly include --import ./scripts/ts-resolve-loader.js for .js→.ts specifier resolution.
tsconfig.json allowJs and checkJs migration flags removed cleanly — correct now that all src/ files are TypeScript. No other compiler option changes.
scripts/ts-resolve-loader.ts New .ts loader that registers ts-resolve-hooks.ts via module.register(). Used by batch.test.ts, cli.test.ts, and registry.test.ts when spawning child Node.js processes, which is distinct from the .js version used by CI workflows directly.
scripts/ts-resolve-hooks.ts New .ts ESM resolve hook with properly typed nextResolve callback signature. Implements .js→.ts fallback only on ERR_MODULE_NOT_FOUND, which is the correct narrow condition.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Node.js process] --> B{Node version?}
    B -->|">= 23"| C["--strip-types"]
    B -->|"22.6 – 22.x"| D["--experimental-strip-types"]

    subgraph "vitest test runner"
        E[vitest.config.ts] -->|"injects strip flag into NODE_OPTIONS"| F[Child test processes]
        F --> G{needs .js→.ts resolver?}
        G -->|"batch / cli / registry tests"| H["--import ts-resolve-loader.ts\n(explicit in test code)"]
        G -->|"all other tests"| I[No loader needed\nall imports already .ts]
    end

    subgraph "CI / npm scripts"
        J["package.json scripts\n(--experimental-strip-types hardcoded)"] --> K[build:wasm / verify-imports\ngen-deps / sync-native-versions]
        L["benchmark.yml\n(dynamic STRIP_FLAG)"] -->|"+ --import ts-resolve-loader.js"| M[benchmark scripts\nimport from lib/*.js]
        N["ci.yml / publish.yml\n(dynamic STRIP_FLAG)"] --> O[verify-imports\nsync-native-versions]
    end

    subgraph "Bootstrap .js files (stay as .js)"
        P[ts-resolve-loader.js]
        Q[ts-resolve-hooks.js]
        R[ts-resolver-*.js]
    end

    H --> P
    P -->|"register()"| Q
Loading

Comments Outside Diff (4)

  1. CLAUDE.md, line 62 (link)

    P2 Stale .js extension in example command

    This line still references tests/parsers/javascript.test.js, but that file was renamed to .test.ts in this PR. Anyone following the example in CLAUDE.md will get either an "ERR_MODULE_NOT_FOUND" or a vitest "no tests found" error.

  2. docs/examples/claude-code-hooks/pre-commit-checks.ts, line 29 (link)

    P1 context: [] infers as never[]

    TypeScript infers the empty array literal [] as never[] — any subsequent output.context.push(string) (lines 80, 170) will fail type-checking with "Argument of type 'string' is not assignable to parameter of type 'never'". This file lives in docs/ and is excluded from the main tsconfig.json, so the project build won't catch it — but users who copy this example and run tsc on it will see the error immediately.

  3. scripts/lib/fork-engine.ts, line 67 (link)

    P2 Untyped function parameters in converted TypeScript file

    forkWorker has no parameter type annotations, so all five parameters are implicitly any. Because scripts/ is excluded from tsconfig.json the compiler won't flag this, but it defeats the purpose of converting the file to TypeScript. The JSDoc block immediately above already documents the intended types:

    The same pattern affects scripts/lib/bench-config.ts's srcImport(srcDir, file) (line 185) — both parameters should be string.

  4. scripts/lib/fork-engine.ts, line 115-116 (link)

    P2 catch (err) used as Error without narrowing

    In TypeScript strict mode, err in a catch clause is typed unknown. Accessing err.message directly will be a type error if the scripts directory is ever added to tsconfig.json, and silently produces undefined at runtime if something other than an Error is thrown. The same pattern appears in scripts/lib/bench-config.ts (multiple catch blocks).

Reviews (7): Last reviewed commit: "fix: remove dead scripts/test.js (#588)" | Re-trigger Greptile

Comment on lines 28 to 30
"engines": {
"node": ">=20"
"node": ">=22"
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 engines constraint too broad for --experimental-strip-types

--experimental-strip-types was only introduced in Node 22.6.0. Declaring >=22 here means users on Node 22.0–22.5 will hit errors when running any script that uses node --experimental-strip-types (build:wasm, deps:tree, verify-imports, version, etc.) — even though the engines field technically allows those versions.

The same issue affects vitest.config.ts, which injects --experimental-strip-types into NODE_OPTIONS unconditionally for any Node 22.x (see below).

Suggested change
"engines": {
"node": ">=20"
"node": ">=22"
},
"engines": {
"node": ">=22.6"
},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — tightened engines constraint to >=22.6 since --experimental-strip-types requires Node 22.6+.

vitest.config.ts Outdated
Comment on lines +3 to +5
const [major] = process.versions.node.split('.').map(Number);
const existing = process.env.NODE_OPTIONS || '';
const stripFlag = major >= 23 ? '--strip-types' : '--experimental-strip-types';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing minor-version guard for --experimental-strip-types

The old vitest.config.js explicitly gated on major > 22 || (major === 22 && minor >= 6) before injecting the flag into NODE_OPTIONS. The new config only destructures major and unconditionally assigns --experimental-strip-types for any Node 22.x. Child processes spawned by tests (e.g. watcher tests, CLI integration tests) inherit NODE_OPTIONS; on Node 22.0–22.5 that invalid flag will crash them immediately.

Suggested change
const [major] = process.versions.node.split('.').map(Number);
const existing = process.env.NODE_OPTIONS || '';
const stripFlag = major >= 23 ? '--strip-types' : '--experimental-strip-types';
const [major, minor] = process.versions.node.split('.').map(Number);
const existing = process.env.NODE_OPTIONS || '';
const supportsStripTypes = major > 22 || (major === 22 && minor >= 6);
const stripFlag = major >= 23 ? '--strip-types' : '--experimental-strip-types';

And in the env block, guard the injection:

!supportsStripTypes || (existing.includes('--experimental-strip-types') || existing.includes('--strip-types'))
  ? ''
  : stripFlag,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — restored the minor-version guard (supportsStripTypes = major > 22 || (major === 22 && minor >= 6)) and gated the strip-types injection on it, matching the old config's behavior.

package.json Outdated
"scripts": {
"build": "tsc",
"build:wasm": "node scripts/build-wasm.js",
"build": "tsc && node -e \"require('fs').writeFileSync('dist/index.cjs',require('fs').readFileSync('src/index.cjs','utf8').replace('./index.ts','./index.js'))\"",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 String.replace() only replaces first occurrence

String.prototype.replace() with a string pattern replaces only the first match. If src/index.cjs ever gains a second ./index.ts reference, later occurrences will be silently left behind in dist/index.cjs, causing a runtime failure for CJS consumers. Prefer replaceAll:

Suggested change
"build": "tsc && node -e \"require('fs').writeFileSync('dist/index.cjs',require('fs').readFileSync('src/index.cjs','utf8').replace('./index.ts','./index.js'))\"",
"build": "tsc && node -e \"require('fs').writeFileSync('dist/index.cjs',require('fs').readFileSync('src/index.cjs','utf8').replaceAll('./index.ts','./index.js'))\"",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — switched to replaceAll for the ./index.ts to ./index.js replacement in the build script.

Comment on lines +14 to +17
specifier: string,
context: { parentURL?: string; conditions: string[] },
nextResolve: Function,
): Promise<{ url: string; shortCircuit?: boolean }> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Avoid the Function type — use a specific callback signature

Function is explicitly banned by most TypeScript style guides (equivalent to any for callables). The Node ESM nextResolve hook has a known shape:

Suggested change
specifier: string,
context: { parentURL?: string; conditions: string[] },
nextResolve: Function,
): Promise<{ url: string; shortCircuit?: boolean }> {
export async function resolve(
specifier: string,
context: { parentURL?: string; conditions: string[] },
nextResolve: (specifier: string, context?: { parentURL?: string; conditions: string[] }) => Promise<{ url: string; shortCircuit?: boolean }>,
): Promise<{ url: string; shortCircuit?: boolean }> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — replaced bare Function type with a specific callback signature: (specifier: string, context?: { parentURL?: string; conditions: string[] }) => Promise<{ url: string; shortCircuit?: boolean }>.

…eplaceAll (#588)

Impact: 1 functions changed, 0 affected
@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

# Run all checks in a single Node.js process
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
RESULT=$(node "$HOOK_DIR/pre-commit-checks.js" "$WORK_ROOT" "$EDITED_FILES" "$STAGED" 2>/dev/null) || true
RESULT=$(node --experimental-strip-types "$HOOK_DIR/pre-commit-checks.ts" "$WORK_ROOT" "$EDITED_FILES" "$STAGED" 2>/dev/null) || true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded --experimental-strip-types is deprecated on Node 23+

This shell script hardcodes --experimental-strip-types, but vitest.config.ts correctly selects between --experimental-strip-types (Node 22.x) and --strip-types (Node >=23) at runtime. Users running Node 23+ who follow this documentation example will get deprecation warnings, even though they're suppressed here via 2>/dev/null.

Since the package now requires Node >=22.6, consider a version-aware invocation:

Suggested change
RESULT=$(node --experimental-strip-types "$HOOK_DIR/pre-commit-checks.ts" "$WORK_ROOT" "$EDITED_FILES" "$STAGED" 2>/dev/null) || true
STRIP_FLAG=$(node -e "const [M,m]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
RESULT=$(node $STRIP_FLAG "$HOOK_DIR/pre-commit-checks.ts" "$WORK_ROOT" "$EDITED_FILES" "$STAGED" 2>/dev/null) || true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the pre-commit.sh example now dynamically selects between --strip-types (Node >=23) and --experimental-strip-types (Node 22.x) at runtime, matching the approach already used in vitest.config.ts.

…588)

Detect Node version at runtime and select --strip-types (>=23) vs
--experimental-strip-types (22.x), matching the approach in vitest.config.ts.
Replace JSDoc-only annotations in fixtures.ts with proper TypeScript
interfaces and typed parameters. Add interfaces and typed parameters
to resolution-benchmark.test.ts helper functions. Eliminates all
noExplicitAny warnings in both files.

Impact: 22 functions changed, 0 affected
@carlos-alm
Copy link
Copy Markdown
Contributor Author

Addressed the remaining review feedback from Greptile's second review:

  1. docs/examples/claude-code-hooks/pre-commit.sh — Now dynamically selects between --strip-types (Node >=23) and --experimental-strip-types (Node 22.x) at runtime, consistent with vitest.config.ts.

  2. tests/helpers/fixtures.ts — Replaced JSDoc-only annotations with proper TypeScript interfaces (PendingNode, PendingEdge, PendingComplexity) and typed all method parameters and return types. Eliminated all noExplicitAny warnings.

  3. tests/benchmarks/resolution/resolution-benchmark.test.ts — Added ResolvedEdge, ExpectedEdge, ModeMetrics, and BenchmarkMetrics interfaces. Typed all helper function parameters and return types. Eliminated all noExplicitAny warnings.

All tests pass (546/546), TypeScript type check passes, and lint is clean.

@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

ARGS="$ARGS --npm"
fi
node scripts/benchmark.js $ARGS 2>/dev/null > benchmark-result.json
node --experimental-strip-types scripts/benchmark.ts $ARGS 2>/dev/null > benchmark-result.json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing --import loader — benchmark scripts will fail on Node 22

scripts/benchmark.ts (and embedding-benchmark.ts, query-benchmark.ts, incremental-benchmark.ts) contain cross-script imports using .js specifiers:

import { resolveBenchmarkSource, srcImport } from './lib/bench-config.js';
import { isWorker, workerEngine, workerTargets, forkEngines } from './lib/fork-engine.js';

Both scripts/lib/bench-config.js and scripts/lib/fork-engine.js do not exist on disk — only their .ts counterparts do. Node 22's --experimental-strip-types strips type annotations but does not rewrite .js specifiers to .ts (that resolution was only added in Node 23+). Without the --import ESM loader hook, these scripts will fail immediately with ERR_MODULE_NOT_FOUND.

The fix is to add --import ./scripts/ts-resolve-loader.js (the .js version is still present and fully functional) before the script path. The same fix applies at lines 247, 384, and 521:

Suggested change
node --experimental-strip-types scripts/benchmark.ts $ARGS 2>/dev/null > benchmark-result.json
node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/benchmark.ts $ARGS 2>/dev/null > benchmark-result.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added --import ./scripts/ts-resolve-loader.js to the benchmark.ts invocation so .js specifiers resolve to their .ts counterparts on Node 22.

ARGS="$ARGS --npm"
fi
node scripts/embedding-benchmark.js $ARGS 2>/dev/null > embedding-benchmark-result.json
node --experimental-strip-types scripts/embedding-benchmark.ts $ARGS 2>/dev/null > embedding-benchmark-result.json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing --import loader — same resolution failure as line 96

scripts/embedding-benchmark.ts also imports ./lib/bench-config.js and ./lib/fork-engine.js (which exist only as .ts files). Same fix applies:

Suggested change
node --experimental-strip-types scripts/embedding-benchmark.ts $ARGS 2>/dev/null > embedding-benchmark-result.json
node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/embedding-benchmark.ts $ARGS 2>/dev/null > embedding-benchmark-result.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — same --import ./scripts/ts-resolve-loader.js flag added to the embedding-benchmark.ts invocation.

ARGS="$ARGS --npm"
fi
node scripts/query-benchmark.js $ARGS 2>/dev/null > query-benchmark-result.json
node --experimental-strip-types scripts/query-benchmark.ts $ARGS 2>/dev/null > query-benchmark-result.json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing --import loader — same resolution failure as line 96

scripts/query-benchmark.ts also imports ./lib/bench-config.js and ./lib/fork-engine.js (which exist only as .ts files).

Suggested change
node --experimental-strip-types scripts/query-benchmark.ts $ARGS 2>/dev/null > query-benchmark-result.json
node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/query-benchmark.ts $ARGS 2>/dev/null > query-benchmark-result.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — same --import ./scripts/ts-resolve-loader.js flag added to the query-benchmark.ts invocation.

ARGS="$ARGS --npm"
fi
node scripts/incremental-benchmark.js $ARGS 2>/dev/null > incremental-benchmark-result.json
node --experimental-strip-types scripts/incremental-benchmark.ts $ARGS 2>/dev/null > incremental-benchmark-result.json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing --import loader — same resolution failure as line 96

scripts/incremental-benchmark.ts also imports ./lib/bench-config.js and ./lib/fork-engine.js (which exist only as .ts files).

Suggested change
node --experimental-strip-types scripts/incremental-benchmark.ts $ARGS 2>/dev/null > incremental-benchmark-result.json
node --experimental-strip-types --import ./scripts/ts-resolve-loader.js scripts/incremental-benchmark.ts $ARGS 2>/dev/null > incremental-benchmark-result.json

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — same --import ./scripts/ts-resolve-loader.js flag added to the incremental-benchmark.ts invocation.

…ports (#588)

The file was renamed from .js to .ts but retained CommonJS require() calls.
Since the repo has "type": "module", Node loads .ts files as ESM under
--strip-types, where require is not defined. This caused a silent
ReferenceError swallowed by 2>/dev/null || true in pre-commit.sh,
making all pre-commit checks (cycles, dead exports, diff-impact)
non-functional.

Convert to ESM imports with createRequire for dynamic runtime requires.
Also update the file header comment to reference .ts instead of .js.
…ibility (#588)

Benchmark scripts import ./lib/bench-config.js and ./lib/fork-engine.js
but only .ts files exist on disk. Node 22's --experimental-strip-types
does not auto-resolve .js to .ts (that was added in Node 23+). Add
--import ./scripts/ts-resolve-loader.js to all four benchmark invocations
so the ESM loader resolves .js specifiers to their .ts counterparts.
@carlos-alm
Copy link
Copy Markdown
Contributor Author

Addressed the two remaining Greptile findings from the third review:

  1. P0: pre-commit-checks.ts uses CJS require() in ESM context — Converted to ESM imports (import fs from 'node:fs', import path from 'node:path') with createRequire(import.meta.url) for the dynamic runtime requires of project modules. Also updated the file header comment from .js to .ts. This fixes the silent hook failure where ReferenceError: require is not defined was swallowed by 2>/dev/null || true.

  2. P1: Missing --import loader in benchmark.yml — Added --import ./scripts/ts-resolve-loader.js to all four benchmark script invocations (benchmark.ts, embedding-benchmark.ts, query-benchmark.ts, incremental-benchmark.ts). These scripts import .js specifiers that only exist as .ts files, and Node 22's --experimental-strip-types does not auto-resolve .js to .ts.

All 2098 tests pass, lint is clean.

@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

Comment on lines +1 to +9
/**
* Registers the .js → .ts ESM resolve hook.
*
* Usage: node --experimental-strip-types --import ./scripts/ts-resolve-loader.ts src/cli.ts
*/

import { register } from 'node:module';

register(new URL('./ts-resolve-hooks.ts', import.meta.url));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 New .ts loader duplicates existing .js loader but is unused

scripts/ts-resolve-loader.ts and scripts/ts-resolve-hooks.ts are newly added files, but neither is referenced anywhere in CI workflows, package.json scripts, or vitest.config.ts. Every callsite that needs the resolver (benchmark.yml lines 96, 247, 384, 521) still explicitly uses --import ./scripts/ts-resolve-loader.js.

The PR description notes that ESM loader hooks must stay .js for bootstrap reasons, which is consistent — but it's unclear what purpose these new .ts duplicates serve. If they're intended for documentation or future use, a comment explaining that would help. If they're not needed, they introduce confusion about which version to use and create an extra maintenance burden.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — removed both scripts/ts-resolve-loader.ts and scripts/ts-resolve-hooks.ts. All callsites correctly use the .js versions (ESM loader hooks must stay .js for bootstrap reasons), so the .ts duplicates were dead code adding maintenance confusion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction on my earlier reply: I initially removed these files, but CI revealed they ARE used by the test suite. Three test files (batch.test.ts, cli.test.ts, registry.test.ts) reference scripts/ts-resolve-loader.ts via new URL('../../scripts/ts-resolve-loader.ts', import.meta.url) when spawning child node processes that need the loader.

The .ts versions serve a different purpose than the .js versions: the .js files are used by CI workflows and package.json scripts (where Node runs the loader directly), while the .ts files are used by the test suite's child process spawning (where the test resolver handles .ts imports).

Reverted the deletion. These files are correctly part of the PR.

- name: Update build report
if: steps.existing.outputs.skip != 'true'
run: node scripts/update-benchmark-report.js benchmark-result.json
run: node --experimental-strip-types scripts/update-benchmark-report.ts benchmark-result.json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded --experimental-strip-types in update-report invocations

The four update-*-report.ts invocations (benchmark, embedding, query, incremental) use node --experimental-strip-types without --strip-types on Node 23+. The benchmark.yml workflow isn't pinned to Node 22 — if it's run on a Node 23+ runner (or when the matrix is eventually updated), these will emit deprecation warnings about the obsolete flag.

The main benchmark runners (lines 96, 247, 384, 521) received the same fix in this PR and correctly keep --experimental-strip-types, but the equivalent vitest.config.ts dynamically selects between --experimental-strip-types and --strip-types based on the major version. Consider applying the same dynamic selection here, or at minimum noting this as a forward-compatibility concern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — all eight node invocations in benchmark.yml (four benchmark runners + four update-report scripts) now use a dynamic STRIP_FLAG variable that selects --strip-types on Node 23+ and --experimental-strip-types on Node 22.x, matching the approach already used in vitest.config.ts and pre-commit.sh.

- Use dynamic STRIP_FLAG selection in all benchmark.yml node invocations
  for forward compatibility with Node 23+ (--strip-types vs
  --experimental-strip-types)
- Update token-benchmark.ts usage docs to include --import loader flag
- Update CLAUDE.md Node version from >= 20 to >= 22.6
ESM loader hooks must stay as .js files for bootstrap reasons. The .ts
duplicates were never referenced by any CI workflow or package script
and only created confusion about which version to use.
@carlos-alm
Copy link
Copy Markdown
Contributor Author

Addressed the remaining findings from the latest Greptile review:

  1. Unused .ts loader duplicates — Removed scripts/ts-resolve-loader.ts and scripts/ts-resolve-hooks.ts. ESM loader hooks must stay as .js files for bootstrap reasons; the .ts duplicates were unreferenced dead code.

  2. Hardcoded --experimental-strip-types in benchmark.yml update-report scripts — All eight node invocations in benchmark.yml now use a dynamic STRIP_FLAG variable that selects between --strip-types (Node 23+) and --experimental-strip-types (Node 22.x) for forward compatibility.

  3. token-benchmark.ts missing --import loader — Updated the usage docs in the file header to include the required --import ./scripts/ts-resolve-loader.js flag. (This script has no CI job, so the doc fix ensures manual invocations include the loader.)

  4. Stale Node version in CLAUDE.md — Updated from "Requires Node >= 20" to "Requires Node >= 22.6" to match the package.json engines constraint.

All tests pass, lint is clean.

@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

Comment on lines +33 to +48
"build:wasm": "node --experimental-strip-types scripts/build-wasm.ts",
"typecheck": "tsc --noEmit",
"verify-imports": "node scripts/verify-imports.js",
"test": "node scripts/test.js run",
"test:watch": "node scripts/test.js",
"test:coverage": "node scripts/test.js run --coverage",
"verify-imports": "node --experimental-strip-types scripts/verify-imports.ts",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"lint": "biome check src/ tests/",
"lint:fix": "biome check --write src/ tests/",
"format": "biome format --write src/ tests/",
"prepack": "npm run build",
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('.tsbuildinfo',{force:true})\"",
"prepare": "npm run build:wasm && npm run build && husky && npm run deps:tree",
"deps:tree": "node scripts/gen-deps.cjs",
"deps:tree": "node --experimental-strip-types scripts/gen-deps.ts",
"release": "commit-and-tag-version",
"release:dry-run": "commit-and-tag-version --dry-run",
"version": "node scripts/sync-native-versions.js && git add package.json crates/codegraph-core/Cargo.toml"
"version": "node --experimental-strip-types scripts/sync-native-versions.ts && git add package.json crates/codegraph-core/Cargo.toml"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded --experimental-strip-types across all npm scripts

Every user-facing npm script that invokes a .ts file still hardcodes --experimental-strip-types, which is deprecated on Node 23+. The same pattern was flagged and fixed in benchmark.yml, pre-commit.sh, and vitest.config.ts, but package.json was left behind.

Affected scripts:

  • build:wasm (line 33) — runs via prepare, i.e. on every npm install
  • verify-imports (line 35)
  • deps:tree (line 45)
  • version (line 48)

Users on Node 23+ will see a deprecation warning for every invocation. Because build:wasm is part of the prepare lifecycle, the warning appears on npm install, which is the noisiest possible place.

package.json scripts can't use shell conditionals inline, but a tiny wrapper (similar to the now-unused scripts/test.js, which already did version-aware flag selection) or an env-var trick works:

Suggested change
"build:wasm": "node --experimental-strip-types scripts/build-wasm.ts",
"typecheck": "tsc --noEmit",
"verify-imports": "node scripts/verify-imports.js",
"test": "node scripts/test.js run",
"test:watch": "node scripts/test.js",
"test:coverage": "node scripts/test.js run --coverage",
"verify-imports": "node --experimental-strip-types scripts/verify-imports.ts",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"lint": "biome check src/ tests/",
"lint:fix": "biome check --write src/ tests/",
"format": "biome format --write src/ tests/",
"prepack": "npm run build",
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('.tsbuildinfo',{force:true})\"",
"prepare": "npm run build:wasm && npm run build && husky && npm run deps:tree",
"deps:tree": "node scripts/gen-deps.cjs",
"deps:tree": "node --experimental-strip-types scripts/gen-deps.ts",
"release": "commit-and-tag-version",
"release:dry-run": "commit-and-tag-version --dry-run",
"version": "node scripts/sync-native-versions.js && git add package.json crates/codegraph-core/Cargo.toml"
"version": "node --experimental-strip-types scripts/sync-native-versions.ts && git add package.json crates/codegraph-core/Cargo.toml"
"build:wasm": "node -e \"const v=process.versions.node.split('.').map(Number);const f=v[0]>=23?'--strip-types':'--experimental-strip-types';require('child_process').execFileSync(process.execPath,[f,'scripts/build-wasm.ts'],{stdio:'inherit'})\"",
"typecheck": "tsc --noEmit",
"verify-imports": "node -e \"const v=process.versions.node.split('.').map(Number);const f=v[0]>=23?'--strip-types':'--experimental-strip-types';require('child_process').execFileSync(process.execPath,[f,'scripts/verify-imports.ts'],{stdio:'inherit'})\"",

Alternatively, since scripts/test.js already contains version-aware flag selection logic and is now dead code (package.json replaced node scripts/test.js run with vitest run directly), that pattern could be extracted into a small scripts/node-ts.js launcher that accepts a script path as its first argument and applies the correct flag.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed this is a real forward-compatibility concern. However, fixing this in package.json requires creating a new launcher script and changing all four script invocations, which is a meaningful refactor beyond this PR's scope.

Created #590 to track this. The engines constraint (>=22.6) means this will surface as deprecation warnings (not errors) when users adopt Node 23+, and build:wasm during npm install is indeed the noisiest place for it.

Comment on lines +115 to +116
- name: Verify all dynamic imports resolve
run: node scripts/verify-imports.js
run: node --experimental-strip-types scripts/verify-imports.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded --experimental-strip-types in verify-imports step

This step runs node --experimental-strip-types scripts/verify-imports.ts with a hardcoded flag. The CI matrix is currently pinned to Node 22, so this works today, but if Node 23+ is ever added to the matrix (or the runner's default Node version shifts), users will see deprecation warnings.

benchmark.yml was updated to use a dynamic STRIP_FLAG variable for all eight invocations. Applying the same pattern here keeps the workflows consistent:

Suggested change
- name: Verify all dynamic imports resolve
run: node scripts/verify-imports.js
run: node --experimental-strip-types scripts/verify-imports.ts
run: |
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG scripts/verify-imports.ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — applied the same dynamic STRIP_FLAG pattern used in benchmark.yml to the verify-imports step in ci.yml.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — applied the dynamic STRIP_FLAG pattern to both sync-native-versions.ts invocations in publish.yml (lines 227 and 409), matching the approach already used in benchmark.yml.

@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

run: |
npm version "$VERSION" --no-git-tag-version --allow-same-version
node scripts/sync-native-versions.js --strip
node --experimental-strip-types scripts/sync-native-versions.ts --strip
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded --experimental-strip-types in publish workflow

Both sync-native-versions.ts invocations in this workflow still hardcode --experimental-strip-types (also at line 408). The same pattern was flagged and fixed in benchmark.yml (all eight invocations) and ci.yml (the verify-imports step) with a dynamic STRIP_FLAG variable. The jobs are currently pinned to Node 22, so this is functional today, but it creates an inconsistency and will silently emit deprecation warnings if the node-version is ever bumped to 23+.

Applying the same dynamic selection used elsewhere:

Suggested change
node --experimental-strip-types scripts/sync-native-versions.ts --strip
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG scripts/sync-native-versions.ts --strip

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — both sync-native-versions.ts invocations in publish.yml now use the dynamic STRIP_FLAG pattern, pushed in commit d5f30c7.

git checkout -- package-lock.json
npm version "$VERSION" --no-git-tag-version --allow-same-version
node scripts/sync-native-versions.js
node --experimental-strip-types scripts/sync-native-versions.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Same hardcoded --experimental-strip-types issue as line 227

Same forward-compatibility concern applies here. Apply the same dynamic STRIP_FLAG pattern:

Suggested change
node --experimental-strip-types scripts/sync-native-versions.ts
STRIP_FLAG=$(node -e "const [M]=process.versions.node.split('.').map(Number); console.log(M>=23?'--strip-types':'--experimental-strip-types')")
node $STRIP_FLAG scripts/sync-native-versions.ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — same dynamic STRIP_FLAG applied at this invocation too.

@carlos-alm
Copy link
Copy Markdown
Contributor Author

Addressed the remaining findings from the latest Greptile review:

  1. publish.yml hardcoded --experimental-strip-types — Both sync-native-versions.ts invocations (lines 227 and 409) now use the dynamic STRIP_FLAG pattern, consistent with benchmark.yml, vitest.config.ts, and pre-commit.sh.

  2. scripts/test.js dead code — Removed. The file was no longer referenced anywhere after package.json switched to direct vitest invocations.

@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit 924306e into main Mar 25, 2026
26 checks passed
@carlos-alm carlos-alm deleted the feat/ts-migrate-tests branch March 25, 2026 06:36
@github-actions github-actions bot locked and limited conversation to collaborators Mar 25, 2026
@carlos-alm
Copy link
Copy Markdown
Contributor Author

Addressed the 4 remaining "Comments Outside Diff" from the latest Greptile review:

  1. CLAUDE.md line 62 — stale .js extension — Updated example command from tests/parsers/javascript.test.js to tests/parsers/javascript.test.ts.

  2. pre-commit-checks.ts line 29 — context: [] infers as never[] — Added explicit type annotation: const output: { action: string; reason?: string; context: string[] } = { action: 'allow', context: [] }. This prevents the never[] inference that would cause type errors on .push(string) calls at lines 100 and 187.

  3. fork-engine.ts — untyped function parameters — Added TypeScript parameter types and return types to all exported functions (isWorker, workerEngine, workerTargets, forkWorker, forkEngines). Replaced JSDoc-only type documentation with actual TS annotations.

  4. fork-engine.ts + bench-config.ts — catch (err) without narrowing — All err.message accesses now use err instanceof Error ? err.message : err to properly narrow the unknown catch variable.

@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant