Fix: use the node: protocol for the bare built-in imports outside core - #550
Open
AmaadMartin wants to merge 2 commits into
Open
Fix: use the node: protocol for the bare built-in imports outside core#550AmaadMartin wants to merge 2 commits into
AmaadMartin wants to merge 2 commits into
Conversation
added 2 commits
August 2, 2026 17:14
Four call sites outside core/src, core/test and tests/e2e imported a Node built-in by its bare name, so an npm package published under the same name could shadow the built-in at resolution time: - vitest.config.ts import path from 'path' - dev/src/cli/cli.ts import * as path from 'path' - core/build.js esbuild ESM banner emitting from 'module' - integrations/build.js esbuild ESM banner emitting from 'module' The last two are emitted text: the banner is prepended verbatim to every file of the esm and web targets, so the bare specifier shipped inside the published artifacts. The cjs target never receives the banner and is unaffected. This does not make the repo free of bare built-in specifiers. 54 remain across 25 files, all inside core/src (3), core/test (8) and tests/e2e (43), which are owned by separate changes. Notably core/src/artifacts/ file_artifact_service.ts still imports fs/promises, path and url bare. No behavioural change: node:path and path resolve to the same built-in, and createRequire from node:module is the same function as from module.
The banner is generated text, so nothing in the source tree stops a future edit to core/build.js or integrations/build.js from reintroducing the bare 'module' specifier into published artifacts. Assert the emitted banner directly on the built output, and assert that the cjs target still carries no banner at all. Verified to fail against the unfixed build scripts: reverting both banners to 'module' and rebuilding fails all four bannered assertions with "expected 'module' to be 'node:module'", while the two cjs assertions correctly keep passing.
AmaadMartin
force-pushed
the
fix/node-protocol-import-prefix-stragglers
branch
from
August 3, 2026 00:17
39407c3 to
0728884
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
N/A — no public issue; this is a repository-convention cleanup.
Problem:
adk-jsprefers Node built-ins to be imported via thenode:scheme so that an npm package published under the same bare name cannot shadow the built-in at resolution time. Four call sites outsidecore/src,core/testandtests/e2ehad not been converted:vitest.config.ts:7import path from 'path';import path from 'node:path';dev/src/cli/cli.ts:18import * as path from 'path';import * as path from 'node:path';integrations/build.js:70from 'module'from 'node:module'core/build.js:76from 'module'from 'node:module'Sites 3 and 4 are not ordinary imports — they are emitted text. esbuild prepends the banner verbatim to every file of the
esmandwebtargets, so the bare specifier shipped inside the published artifacts (402 emitted files). Despite how this is sometimes described, the banner is attached underif (format === 'esm'), not to the CJS build; the CJS output never contained it and does not change. Theformat === 'esm'condition itself andcore/build.js'saliasmap are deliberately untouched.Scope — this does NOT make the repo free of bare built-in specifiers. An earlier draft of this PR claimed to fix "the last" of them; that was wrong and has been corrected. 54 bare specifiers remain across 25 files, all inside the three trees owned by separate changes:
core/src(3),core/test(8),tests/e2e(43). The most significant remainder is shipped library source —core/src/artifacts/file_artifact_service.ts:8-10still importsfs/promises,pathandurlbare, which is the same shadowing exposure this PR cites as motivation, in a more important file than the four fixed here. Deliberately not expanded: touching those trees would collide head-on with the PRs listed below.Solution: Rename the specifier at all four sites.
node:pathandpathresolve to the same built-in, andcreateRequirefromnode:moduleis the same function as frommodule, so there is no behavioural change. The in-repo precedent isdev/src/utils/agent_loader.ts:14, which already doesimport {createRequire} from 'node:module';.Duplication (please read before reviewing). A pre-implementation collision check (
gh pr list --repo AmaadMartin/adk-js --state open --limit 1000, 448 open PRs) plus a follow-up audit found that all 4/4 changed lines are already covered by open PRs:vitest.config.ts:7dev/src/cli/cli.ts:18core/build.js:76integrations/build.js:70#547 makes byte-identical edits to all four files and additionally sweeps
core/src,core/testandtests/e2e. #430 covers exactly the two build banners and also ships a banner regression test. Nothing in this branch is unique. Furthermore, the durable fix is a lint rule —eslint.config.jshas non/prefer-node-protocolor equivalentno-restricted-importsguard, so stragglers must be found by hand and nothing prevents the next one landing; #346, #422 and #548 each add exactly that rule and sweep the violations in one pass. Adding the rule here is out of scope for this branch (it cannot pass until the sibling trees are converted), which is precisely why landing a rule PR and closing the per-call-site PRs, including this one, is the better outcome.Blast radius, stated plainly rather than claimed to be zero: the
node:scheme requires Node >= 14.18 / >= 16. This floor is pre-existing forcoreanddev, whosesrctrees already contain 15 and 31node:imports respectively.integrations/srchas none, but its ESM artifact already carried a bare'module'import, which is equally unresolvable in a browser — so this changes the scheme of an already-Node-only import, not the set of environments the artifact works in. A browser-targeted bundler old enough to lacknode:support (webpack 4, browserify) cannot resolvenode:module, but it could not resolve bare'module'either, so no working configuration regresses; webpack 5, Vite and Rollup with node-resolve handlenode:natively. esbuild'starget: ['node10.4']is unaffected because banner text is prepended verbatim and never parsed or down-levelled. Separately, the ESM banner is also prepended to theweb/browser target, where neithermodulenornode:moduleresolves — pre-existing and out of scope here.Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.
One test added:
tests/integration/build_setup/build_banner_test.ts(+50). The banner is generated text, so nothing in the source tree stops a future edit to either build script from putting the bare'module'specifier straight back into published artifacts. The test asserts the emitted banner on the built output —core/dist/esm/index.js,core/dist/web/index_web.jsand the twointegrationsequivalents must importnode:module— and asserts the twocjsentry points still carry no banner at all, which pins the "CJS is unaffected" claim as a test rather than a promise. It runs in the existingintegrationproject, which CI executes vianpm run test:coverageafternpm run build.No other test was added, and none was modified, skipped, weakened or deleted. The other two sites need no new test: a bad specifier in
vitest.config.tsaborts every vitest run at config load, anddev/src/cli/cli.tsis a module-level import already loaded byunit:dev. A test asserting that a source file contains the literal string'node:path'would test source text rather than behaviour.Commands run from the repo root on the pushed tree:
npm run lintandnpm run format:checkpass clean, including the new test file. No import reordering occurred, soprettier-plugin-organize-importskeeps both changed files in sorted order as-is.npm run ts:checkreports 281 errors, all pre-existing. Verified by stashing the new test file and re-running: the error list is byte-identical with and without it, and none are in any file this PR touches. (These are thecore/testimport-convention errors that Fix: import ADK symbols from source in core/test and pin the convention (281 -> 139 type errors) #514 addresses. The count was 285 beforedevwas built locally; buildingdev/distresolves 4 module-resolution errors and is unrelated to this diff.)npx vitest run --project unit:core --project unit:dev: 2570 passed, 4 failed. The 4 failures are pre-existing and unrelated — 3 incore/test/code_executors/unsafe_local_code_executor_test.tsand 1 indev/test/cli/cli_create_test.ts. Verified by checking out the unmodified base for the changed files and re-running those two files: the same 4 fail identically.Prove-it-can-fail. Four mutations, each confirmed to break a real gate; all reverted afterwards.
'module', rebuilt, re-ranbuild_banner_test.ts:vitest.config.ts->from 'node:path_MUTANT'. Every vitest run dies before a single test executes:dev/src/cli/cli.ts->from 'node:path_MUTANT'.unit:devfails (Test Files 1 failed (1)):core/build.jsbanner ->from 'node:module_MUTANT', rebuild, then load the ESM entry point:Manual End-to-End (E2E) Tests:
Before/after build-output evidence (
coreandintegrationsbuilt from the clean base, then rebuilt after the edits):Postconditions, asserted after the rebuild:
The "CJS is unchanged" claim is verified rather than asserted: I checksummed all 203 files under
core/dist/cjs+integrations/dist/cjs, rebuilt from the unmodified build scripts, checksummed again, and diffed — byte-identical.Smoke-loads of the rebuilt artifacts:
CLI end-to-end, exercising the consumer of the renamed
pathimport (getAbsolutePathatdev/src/cli/cli.ts:54):That last line is the point: the relative path was joined onto
process.cwd()bypath.joinresolved throughnode:path, so the renamed import is exercised in the built binary, not just type-checked.Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas. — the only comment is the JSDoc on the new test's banner regex, explaining why the scheme matters.
[x] I have added tests that prove my fix is effective or that my feature works. —
build_banner_test.ts, shown failing against the unfixed build scripts above.[x] New and existing unit tests pass locally with my changes. — 2570 pass plus the 6 new; the 4 failures and 281
ts:checkerrors are pre-existing and unchanged by this diff.CI
All test jobs green on
0728884c:Two flaky first attempts, both in files this diff does not touch, both passing on
re-run with no code change:
windows-latesttimed out incore/test/code_executors/unsafe_local_code_executor_test.ts.The same job also fails on the unmodified base commit
b390217e(run 30669370416),timing out in a different file,
tests/integration/adk_web/webui_test.ts.macos-latesttimed out intests/integration/app_loader/app_loader_test.ts(3 tests,Test timed out in 20000ms) and the npm-install-heavybuild_setup_test.ts > ts_commonjsfixture. The
app_loadertimeout is a long-standing known flake with seven open fix PRs(Fix: stabilize app_loader integration test timeouts and stop matrix fail-fast #235, Fix: bound AgentLoader bundle imports so app_loader discovery test stops timing out on Windows/macOS CI #247, Fix: raise app_loader integration-test timeout to 60s to stop CI flake #256, Fix: stop Vitest SSR-transforming the compiled agent bundle in app_loader integration tests #499, Fix: stop the app_loader discovery test billing its fixture setup to the first it() (Part 1/2) #506, Fix: make the app_loader integration suite independent of the previous run #521, Fix: stop the app_loader discovery test billing its setup to a test, and make a premature test-server exit self-diagnosing #545). The new
build_banner_test.tspassed onthat same macOS run (
✓ 6 tests, 13ms).