Chore: fail lint on deep @google/adk* subpath imports (stacked on #380) - #435
Open
AmaadMartin wants to merge 4 commits into
Open
Chore: fail lint on deep @google/adk* subpath imports (stacked on #380)#435AmaadMartin wants to merge 4 commits into
AmaadMartin wants to merge 4 commits into
Conversation
added 4 commits
July 31, 2026 20:53
The repo's lint-staged hook runs `prettier --write` over `**/*.{js,ts}`, but
`npm run format` only targets `**/*.ts`, so this file had never been
formatted and any commit touching it drags a whole-file reformat along.
Doing it on its own keeps the next commit reviewable. No behavior change.
The three published packages export only "." from their exports maps, so `@google/adk/<anything>` is unresolvable for a consumer, for tsc under nodenext, and for node. The four call sites that used one are removed on the base branch; this rule stops them coming back. Package-root specifiers stay allowed; every subpath under the three packages is an error.
Lints an in-memory import statement with the repo's own config, so deleting the rule or widening its group to cover the package roots fails the suite instead of quietly leaving the guard inert.
One glob replaces the three enumerated package names and picks up any future @google/adk-* workspace package instead of letting it escape the rule. The test moves next to the other repo-tooling suites.
This was referenced Aug 1, 2026
Chore: remove the dead dev/src/browser ignore entries from eslint.config.js and .prettierignore
#454
Open
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
Link to an existing issue (if applicable):
Related: Fix: anchor the vitest workspace aliases so deep @google/adk/* specifiers fail at test time #380
Or, if no issue exists, describe the change:
Collision check (done before writing any code). The task assigned to this
branch was "remove the four unresolvable deep-subpath
@google/adk*imports".gh pr list --repo AmaadMartin/adk-js --state open --limit 100plusgh pr diff --name-onlyover every plausibly adjacent PR found that #380(
fix/vitest-alias-exact-match) already rewrites all four of those imports,in the same two files, to the same targets. Rather than ship a competing
implementation, this PR is stacked on #380 and contributes only the part
#380 does not have: the regression guard. No open PR adds a
no-restricted-importsrule (checked #426, #422, #346, #332, #333 — the otherPRs that touch
eslint.config.js; they add thenode:protocol rule andtype-aware linting). Stacking is also load-bearing rather than merely polite:
on
mainthe four deep imports still exist, so this rule would failnpm run lintif it landed there on its own.Problem:
core/package.json,dev/package.jsonandintegrations/package.jsoneach declare anexportsmap with only a"."entry, so
@google/adk/<anything>is not a resolvable specifier for aconsumer, for
tscundermoduleResolution: nodenext, or fornode. Fourcall sites imported one anyway. Nothing in the repo failed, because every gate
looks away:
vitest.config.tsaliased'@google/adk'as a stringfind, and Vitetreats a string alias as matching the specifier or anything starting with
find + '/'— so@google/adk/sessions/session.jswas silently rewritten tocore/src/sessions/session.jsand the exports map was never consulted.(Fix: anchor the vitest workspace aliases so deep @google/adk/* specifiers fail at test time #380 anchors those aliases.)
.github/workflows/validation.yamldoes not runnpm run ts:check; the onlycompilation in CI is per-package
tsc --emitDeclarationOnlyoversrc/**,which never sees
core/test/**ortests/**.npm run docs:check(TypeDoc,entryPoints: ["./core/src/index.ts"],exclude: ["**/*_test.ts", "**/test"]) andnpm run lint(non-type-checkedtypescript-eslintrecommended) do not resolve module specifiers at all.Removing the four call sites without a guard leaves nothing to stop the fifth.
Solution: Direction (a) — deep subpath imports into
@google/adk*arenot public API, and lint now says so. One
no-restricted-importsgroup,'@google/adk*/**', rejects any subpath of@google/adk,@google/adk-devtools,@google/adk-integrationsand any future@google/adk-*workspace package, with a message that names the cause and bothsupported alternatives. Package roots stay allowed.
Why (a) rather than adding a
"./*"export condition:those four lines, and they have never worked outside Vitest.
"./*"condition would make every file path underdist/part of thepackages' semver surface — the whole internal module tree, for three
packages, effectively forever — to serve four test-only imports.
quoteFilterLiteralis an AIP-160literal-escaping helper,
isVertexAiConnectionStringis a URI predicate usedonly by
core/src/sessions/registry.ts,responseProcessormirrors amodule-private singleton in
google/adk-python(
src/google/adk/flows/llm_flows/_code_execution.py), andloggeralreadyhas public accessors (
getLogger,setLogger,setLogLevel,LogLevel,Logger) exported fromcore/src/common.ts.The public API surface is byte-identical. Nothing was added to
core/src/index.tsorcore/src/common.ts;npm run docs:checkpassesunchanged. No package manifest, no
dist/layout, no runtime behaviour istouched by this PR — the entire diff is
eslint.config.jsplus one test.On the plan's glob claim — corrected by measurement, not assumed. The brief
asserted that ESLint's
patterns.groupmatching "does not cross/with asingle
*", making a trailing**mandatory. ESLint 9 builds anignore()matcher, i.e. gitignore semantics: a
*does not itself cross/, but a matchon a parent path also covers everything below it, so
@google/adk/*blocks@google/adk/utils/logger.jsall the same. Verified by mutating the config andre-running the suite. The trailing
**is kept because it states the intentunambiguously and because
a/**does not match barea— that is what keepsthe package roots allowed.
Review round 1 (complexity). Two findings, both applied: the three
enumerated package globs collapsed into one
'@google/adk*/**'(−4 lines, anda future
@google/adk-<new>package is now covered instead of silentlyescaping), and the new suite moved to
tests/integration/repo_config/, whererepo-tooling tests belong. The reviewer also asked for
tests/integration/vitest_alias_test.tsto move alongside it; that file belongsto #380, this PR's base, so moving it here would put a rename of another PR's
file into this diff and conflict with any revision of it. It should move when
#380 lands or in its own change.
Whole-file Prettier reformat, isolated in its own commit.
lint-stagedruns
prettier --writeover**/*.{js,ts}, butnpm run formatonly targets**/*.ts, soeslint.config.jshad never been formatted and any committouching it drags a 17-line quote-style reformat along. Commit
Chore: format eslint.config.js ...is that reformat with no behaviour change;the rule itself is a clean +16 on top of it.
Out of scope, deliberately (stated rather than silently dropped):
LlmAgentdefaultsresponseProcessorsto[], which is why the sandboxintegration test wires
responseProcessorby hand. That gap is trackedseparately and is not addressed here.
npm run ts:checkreports 277 pre-existing errors on this branch, none ofthem
TS2307. One is a direct consequence of Fix: anchor the vitest workspace aliases so deep @google/adk/* specifiers fail at test time #380's rewrite and is worthflagging: at
tests/integration/agents/agent_with_sandbox_executor_test.ts:95the previously-unresolvable import used to yield
TS2307, and now yieldsTS2322—LlmAgentcomes from@google/adk(whichtscresolves tocore/dist/types/**) whileresponseProcessornow comes fromcore/src/**, andBaseSessionServicehas a private member, so the twodeclarations are nominally distinct. The root cause is the root type check
resolving workspace packages to
distrather than to source; that is exactlywhat Fix: gate CI on a root type check that resolves workspace packages to source #414 changes, so it is not duplicated 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.
tests/integration/repo_config/eslint_restricted_imports_test.ts(new) lints an in-memoryimport statement with the repo's own config and asserts the package roots are
accepted while a subpath at every depth of all three packages is rejected.
Targeted runs (no whole-repo suite):
Whole-repo gates (these are what prove the rule does not false-positive on the
126 legitimate
@google/adkroot imports incore/testalone):Proof the new test can fail (mutations applied to
eslint.config.js, eachreverted afterwards):
no-restricted-importsrule× rejects a subpath at any depth of any published package—expected [] to have a length of 1 but got +0'@google/adk*'(dropping/**, so it also covers the roots)× allows the package root specifiers—expected [ Array(1) ] to deeply equal []'@google/adk/**'(so the other two packages escape)× rejects a subpath at any depth of any published package—expected [] to have a length of 1 but got +0'/**'to'/*'ts:checkbefore/after. With the packages built,npm run ts:checkwas runagainst the pre-fix versions of the two test files and against this branch. The
four
TS2307s are gone; the remaining errors are pre-existing and untouched(the error-count delta also includes line-number shifts, because #380's base
predates the
ttl/expireTimetests onmain):Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.
Vitest cannot prove anything here, because its alias was the bug. The check
below uses
nodeagainst a genuinely installed package — thets_esmbuild_setup fixture depends on
"@google/adk": "file:../../../../core":That is the behaviour the lint rule now encodes: the package root resolves, any
subpath does not. No permanent build_setup fixture assertion was added — it
would buy a seventh
npm installcycle in CI to restate what the exports mapalready enforces.
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 status: absent (validated locally)
.github/workflows/validation.yamltriggers onpull_request: branches: [main]only, so a stacked PR based on
fix/vitest-alias-exact-matchnever starts thetest workflow —
gh pr checksshows justauto-assign. Reporting that as"green" would be false. Everything below was therefore run locally against the
exact pushed commit
a7a92ea7:npm run buildnpm run lintnpm run format:checknpm run docs:checknpx vitest run --project unit:core core/test/sessions/vertex_ai_session_service_test.tsnpx vitest run --project integration tests/integration/{agents/agent_with_sandbox_executor,vitest_alias,repo_config/eslint_restricted_imports}_test.tsOnce #380 merges, retargeting this PR at
mainwill let the full workflow run.