Skip to content

Fix: declare the root workspace's test dependencies in package.json - #360

Open
AmaadMartin wants to merge 1 commit into
mainfrom
fix/root-phantom-test-dependencies
Open

Fix: declare the root workspace's test dependencies in package.json#360
AmaadMartin wants to merge 1 commit into
mainfrom
fix/root-phantom-test-dependencies

Conversation

@AmaadMartin

@AmaadMartin AmaadMartin commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

  1. Link to an existing issue (if applicable):
    Closes: #issue_number
    Related: #issue_number
  2. Or, if no issue exists, describe the change:

Problem: Files under tests/** are owned by the workspace-root package — they are not inside core/, dev/ or integrations/ — so the root package.json is the nearest manifest on their resolution path. Five third-party packages they import are declared in no manifest on that path:

Package Imported by Resolves today only because
@google-cloud/vertexai 3 files under tests/integration/ core/package.json dependencies
@mikro-orm/core 4 tests/integration/build_setup/*/db_init_check.* fixtures core/package.json dependencies
dotenv 20 files under tests/e2e/ dev/package.json dependencies
openapi-types tests/e2e/tools/rest_api_tool_auth_e2e_test.ts:11 core/package.json devDependencies
zod 8 files under tests/e2e/ and tests/integration/ core/package.json dependencies

npm hoists each workspace's dependencies into the repo-root node_modules/, so Node, tsc, ESLint and vitest all resolve these specifiers by walking up from tests/** and finding a copy installed on behalf of a different workspace. Nothing in the root manifest records that relationship, which makes the coupling silent and non-local: the moment a workspace drops or renames one of these, the whole tests/ suite breaks with no signal that tests/ was the consumer. openapi-types is the sharpest case — it is only a devDependency of core, i.e. something core itself considers disposable, yet a root e2e test imports it. The build_setup fixtures are the second sharpest: they are self-contained npm projects that get a real npm install, declare only @google/adk and @google/adk-devtools, and still resolve @mikro-orm/core by escaping to the repo root.

Severity is workspace hygiene, not a consumer-facing break: the root package (name: "adk") is never published, so no installer of @google/adk or @google/adk-devtools is affected.

Solution: Declare all five in the root devDependencies, each at the exact range the owning workspace already declares (not a range re-derived from the lockfile), and let npm install regenerate package-lock.json. Mirroring the owner's range keeps the two manifests textually consistent and means the range can never drift from its owner for free:

"@google-cloud/vertexai": "^1.12.0"   // core   dependencies
"@mikro-orm/core":        "^6.6.10"   // core   dependencies
"dotenv":                 "^17.2.3"   // dev    dependencies
"openapi-types":          "^12.1.3"   // core   devDependencies
"zod":                    "^4.2.1"    // core   dependencies

Every range is already satisfied by the copy npm hoists today, so this documents the dependency graph without altering it. This is deliberately the smallest possible shape for the fix: no new script, no CI step, no source or test file touched.

Two packages deliberately left out

  • @google/genai meets the same criterion (50 references from tests/**) but is out of scope: upstream main already declares it at the workspace root (package.json:42, "@google/genai": "^2.9.0"), added by commit 00f37755Fix: hoist @google/genai 2.x to the workspace root to end the version duality google/adk-js#564, "fix(deps): hoist @google/genai 2.x to the workspace root". This branch is based on 1210acc7, which predates that commit (git merge-base --is-ancestor 00f37755 HEAD → false), so the package looks phantom only because of where this branch sits, not because the project has an open gap. Re-adding it would duplicate an already-merged upstream change and conflict on the next rebase forward.
  • onnxruntime-node is not a phantom. Both native-addon fixtures declare it themselves as "onnxruntime-node": "file:./fake-onnxruntime-node" against a checked-in stub ({"name":"onnxruntime-node","version":"1.0.0","main":"index.cjs"}) and each ships an ambient onnxruntime-node.d.ts for tsc. Consistent with that, it appears nowhere in package-lock.json (grep -c onnxruntime-node package-lock.json0). Declaring it at the root would download a real multi-hundred-MB native addon that nothing resolves to. Verified after this change that no node_modules/onnxruntime-node appears at the repo root.

vi.mock('@modelcontextprotocol/sdk/client/index.js', …) and vi.mock('google-auth-library', …) in tests/integration/agent_registry/agent_registry_test.ts are not treated as root dependencies. Those specifiers name modules that @google/adk imports, intercepted in the dependency's own graph; the root workspace never loads them, and declaring them at the root would protect nothing (if core dropped either, core itself would be broken first). The rule applied throughout is: static import/export … from, dynamic import(), and require() — i.e. modules the root-owned code actually loads.

Collision check (performed before any code was written)

gh pr list --repo AmaadMartin/adk-js --state open --limit 300 → 262 open PRs; the changed-file list of every one was fetched and 21 touch the root package.json. Their root-manifest patches were read individually. No open PR declares any of these five packages at the root, so there is no competing implementation. Adjacent-but-not-colliding work, recorded for the reviewer:

Disclosure: one incidental line in the lockfile

The package-lock.json diff is 6 added lines (the packages[""].devDependencies mirror) plus one deletion: "dev": true is dropped from the adm-zip entry. That is pre-existing drift on main, not a product of this change — running a bare npm install on an otherwise unmodified checkout of main produces exactly that line and nothing else. It is correct: adm-zip is a production dependency of core, so the dev flag was stale, and hand-reverting it would leave the committed lockfile no longer a fixed point of npm install. Hand-editing the lockfile was avoided entirely. #345 fixes this same line directly; if it merges first, this hunk simply disappears on rebase.

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.

No test was added, and that is deliberate. This change adds zero executable lines — it is a documentation change expressed in JSON — so there is no new line or branch to cover. vitest.config.ts:109-113 scopes coverage to core/src/**, dev/src/** and integrations/src/**, none of which is touched, so the configured thresholds (statements 86 / branches 87 / functions 88 / lines 86) are unaffected by construction. A test that walked the filesystem asserting manifest completeness would be the separately-tracked guard task (see #250 / #323), not this one.

Falsifiability check — the direct analogue of "prove the test can fail". npm explain <pkg> reports the requiring parents of an installed package, and it is the check that fails without this edit and passes only with it. Captured on the same checkout by stashing and restoring the change:

$ npm explain <pkg> | grep 'from the root project'      # BEFORE (fix reverted)
@google-cloud/vertexai   root-requires: <none>
@mikro-orm/core          root-requires: <none>
dotenv                   root-requires: <none>
openapi-types            root-requires: <none>
zod                      root-requires: <none>

                                                        # AFTER (fix applied)
@google-cloud/vertexai   dev @google-cloud/vertexai@"^1.12.0" from the root project
@mikro-orm/core          dev @mikro-orm/core@"^6.6.10"        from the root project
dotenv                   dev dotenv@"^17.2.3"                 from the root project
openapi-types            dev openapi-types@"^12.1.3"          from the root project
zod                      dev zod@"^4.2.1"                     from the root project

Proof the installed tree is unchanged. A sha256 over name@version for every package.json in node_modules/ is byte-identical before and after: 53f757341af6d16ce256e7b5e0febe9b6a4eeb06f0cab23c22ac11a5bad54fd2. npm install reported no package added, removed, or moved, and npm ls resolves the same versions as on main@google-cloud/vertexai@1.12.0, @mikro-orm/core@6.6.14, dotenv@17.4.2, openapi-types@12.1.3, zod@4.4.3. npm install is idempotent on the committed lockfile (re-running it produces a byte-identical file), and no ERESOLVE or peer conflict occurred.

Local gate, run on the exact pushed commit:

Command Result
npm install clean; no package added/removed/moved
npm run build pass
npm run lint pass (exit 0)
npm run format:check pass — "All matched files use Prettier code style!"
npx prettier --check package.json package-lock.json pass (the lint-staged hook will not reformat the manifests)
npm run ts:check fails identically to main — see below
npx vitest run --project integration tests/integration/agent_registry/… tests/integration/memory/vertex_ai_memory_bank_service_test.ts 2 files / 2 tests passed
npx vitest run --project integration tests/integration/skills 3 passed, 1 pre-existing hook timeout (skills/script_js)

npm run ts:check exits 2 on this base before and after this change. Its output was captured in both states and diffed: byte-identical. The errors are the known pre-existing test-tree type errors that #178 / #207 address; this change neither adds nor fixes any of them.

Two suites could not be validated in this sandbox, in the same way on main and on this branch:

  • tests/integration/build_setup/build_setup_test.ts (the @mikro-orm/core and onnxruntime-node-stub consumer) runs a real npm install per fixture against a 10s beforeAll hook, which this environment cannot meet, and npx @google/adk-devtools --version fails here with "could not determine executable to run". Run from a clean fixture state on both sides, it fails with the same two failure modes either way; the pass/fail split varies run to run purely by how far each run gets before the timeout. Given the node_modules tree is provably identical, this change cannot affect it.
  • The e2e project needs live credentials; tests/e2e/tools/rest_api_tool_auth_e2e_test.ts (the openapi-types + dotenv consumer) fails with API key must be provided … GOOGLE_GENAI_API_KEY. Note this failure occurs inside the test body, which confirms the module graph — including openapi-types and dotenv — resolved and loaded successfully.

Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.

# 1. The check that fails without this change and passes with it.
#    On main, none of these name the root project as a requiring parent.
npm install
for p in @google-cloud/vertexai @mikro-orm/core dotenv openapi-types zod; do
  npm explain "$p" | grep 'from the root project' | grep -v workspace
done
# Expect one `dev <pkg>@"<range>" from the root project` line per package.

# 2. The dependency graph must be unchanged, not merely working.
npm ls @google-cloud/vertexai @mikro-orm/core dotenv openapi-types zod
# Expect 1.12.0, 6.6.14, 17.4.2, 12.1.3, 4.4.3 -- the same as on main.

# 3. No real native addon was pulled in by mistake.
ls node_modules/onnxruntime-node          # expect: No such file or directory
grep -c onnxruntime-node package-lock.json # expect: 0

# 4. The lockfile is a fixed point (nothing further to regenerate).
npm install && git diff --exit-code package-lock.json && echo "idempotent"

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.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.

CI

All three run-tests legs pass on the pushed commit: ubuntu-latest, macos-latest, windows-latest.

The first windows-latest attempt failed and was re-run on the identical commit, where it passed. Both failures were in files this PR does not touch and are known Windows flakes, not regressions:

  • tests/integration/tools/run_skill_script_tool_test.ts:190"successfully executes a real PowerShell skill script", Test timed out in 5000ms. Upstream addressed exactly this test in Fix: add explicit per-test timeout to real-PowerShell skill-script integration test google/adk-js#550, "add explicit per-test timeout to real-PowerShell skill-script integration test", a commit this branch's base predates.
  • tests/integration/a2a/stream/stream_test.tsCLI exited prematurely with code 1, a child-process startup race.

Neither can be caused by this change: it adds only devDependencies declarations at ranges already satisfied by the installed copies, and the resulting node_modules/ tree is provably byte-identical (same sha256 name@version signature before and after).

…json

Files under tests/** are owned by the workspace-root package, but the
third-party packages they import are declared in no manifest on their
resolution path. They resolve today only because npm hoists core/'s and
dev/'s dependencies into the repo-root node_modules/, so the tests/ suite
is silently coupled to manifests that never mention it -- openapi-types is
merely a devDependency of core, yet a root e2e test imports it.

Declare them in the root devDependencies at the exact range the owning
workspace already declares, so the coupling is recorded where it lives:

  @google-cloud/vertexai  ^1.12.0  (core dependencies)
  @mikro-orm/core         ^6.6.10  (core dependencies)
  dotenv                  ^17.2.3  (dev dependencies)
  openapi-types           ^12.1.3  (core devDependencies)
  zod                     ^4.2.1   (core dependencies)

One further package, @google/genai, meets the same criterion but is
deliberately left out: upstream already hoisted it to the root in
google#564, which this branch's base predates. Re-adding it here
would duplicate a change that is already merged upstream.

onnxruntime-node is not in scope either: the build_setup native-addon
fixtures declare it themselves as "file:./fake-onnxruntime-node" against a
checked-in stub and ship an ambient .d.ts for it, and it appears nowhere in
package-lock.json. Declaring it at the root would pull a real native addon
that nothing resolves to.

Every range is already satisfied by the copy npm hoists today, so the
installed tree is unchanged: a name@version signature over the whole
node_modules/ tree is identical before and after.

package-lock.json is regenerated by npm install, never hand-edited.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant