Fix: declare @types/express as a runtime dependency of @google/adk-devtools - #562
Open
AmaadMartin wants to merge 2 commits into
Open
Fix: declare @types/express as a runtime dependency of @google/adk-devtools#562AmaadMartin wants to merge 2 commits into
AmaadMartin wants to merge 2 commits into
Conversation
added 2 commits
August 2, 2026 22:10
AdkApiServer exposes `readonly app: express.Application` and is exported from dev/src/index.ts, so `tsc --emitDeclarationOnly` writes a top-level `import express from 'express'` into dev/dist/types/server/adk_api_server.d.ts, which is reachable from the package entry point. express@4 ships no bundled declarations, so those types can only come from @types/express -- and a devDependency is never installed for a consumer of the published package. Consumers therefore saw either a TS7016 raised from inside the shipped declaration (skipLibCheck: false) or a silent collapse of AdkApiServer['app'] to `any` (skipLibCheck: true). In-repo builds were masked by npm workspace hoisting plus skipLibCheck. Move @types/express from devDependencies to dependencies, keeping the range at ^4.17.21 so it stays on the same semver major as express ^4.21.2. The emitted declarations are byte-identical; only whether a consumer can resolve the express import changes.
Guards the fix against a future manifest edit silently demoting @types/express back to a devDependency, and pins express and @types/express to a shared semver major so a one-sided bump fails while a coordinated one keeps passing.
This was referenced Aug 3, 2026
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
No existing issue; described below.
Problem:
@google/adk-devtoolsships declarations that name a module whose types it does not install for consumers.AdkApiServerputs express on its public API surface, and the class is reachable from the package entry point:dev/src/server/adk_api_server.ts:92—readonly app: express.Application;dev/src/index.ts:8—export {AdkApiServer} from './server/adk_api_server.js';so
tsc --emitDeclarationOnlymust name the namespace, and the shippeddev/dist/types/server/adk_api_server.d.ts:8carries a top-levelimport express from 'express';reachable fromdist/types/index.d.ts(whattypings/exports["."].typespoint at).express@4 ships no bundled declarations, so those types can only come from
@types/express— whichdev/package.jsondeclared underdevDependencies. devDependencies are not installed for consumers of a published package. Two consumer-visible symptoms follow:skipLibCheck: false— a hardTS7016raised from inside the shipped declaration;skipLibCheck: true(the common default) — no error, butAdkApiServer['app']silently collapses toany, so everyserver.app.use(...)call in consumer code is unchecked.Neither is visible from inside this repo: npm workspace hoisting puts a single
@types/expressat the monorepo root so resolution walks up and finds it, andtsconfig.json:9setsskipLibCheck: true. Both maskings disappear in a consumer's tree.Solution: move
@types/expressfromdevDependenciestodependenciesindev/package.json, keeping the range at^4.17.21so it stays on the same semver major asexpress: "^4.21.2"(shipping@types/express@5types forexpress@4would be a worse bug than the one being fixed). This is the standard remedy for a package whose published declarations reference a@types/*package.Scope is deliberately three files:
dev/package.json, the regenerated rootpackage-lock.json, and one new regression test.Collision check against open PRs on this fork
Ran before writing any code:
gh pr list --repo AmaadMartin/adk-js --state open --limit 1000(461 open PRs), filtered for express/types/dependency/manifest/devtools/lockfile keywords, thengh pr diff --name-onlyand thedev/package.jsonhunks of every adjacent candidate.fix/publish-public-type-deps) contains a byte-identicaldev/package.jsonhunk plus a newscripts/check_published_types.mjsCI gate and a.github/workflows/validation.yamlchange. It is stacked on Fix: declare @types/express as a runtime dependency of @google/adk #292 (the separatecore@types/expresspromotion), so it is not mergeable againstmainon its own. This PR is the minimal,main-based version of the same manifest fix. If Fix: declare @types/express as a runtime dependency of @google/adk #292 and Fix: declare @types/express in dev, and gate the published type closure in CI #382 land first, this PR becomes a no-op and should be closed — it is not intended to compete with the CI gate, only to make the one-line manifest fix landable independently.@google/genai,@opentelemetry/*,lodash-es,@google-cloud/vertexai,@types/lodash-es), Fix: declare @types/express as a runtime dependency of @google/adk #292 (thecoreworkspace@types/expresspromotion), Fix: declare openapi-types as a runtime dependency of @google/adk #273 (coreopenapi-types), Fix: declare hoisted runtime imports in dev/core and enforce with import/no-extraneous-dependencies #541 (lint-enforced declaration sweep).dev/test/package_manifest_test.tsis also used by #485/#484/#244 for their own assertions; those are additiveit()cases in the same file and will merge as an ordinary textual conflict, not a semantic one.Why the lockfile diff is 13 changed lines
Regenerated with
npm install --registry=https://registry.npmjs.org/(pinned so a mirror cannot rewriteresolvedURLs) — never hand-edited. The complete delta is 2 lines for the manifest move insidepackages["dev"], plus the removal of"dev": truefrom the 11 entries that enter the production closure:Verified there is no other churn — every changed
+/-line is one of those 13:No version bumps, no
resolved/integrityrewrites.npm audit fix,npm updateandnpm dedupewere not run.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.
New file
dev/test/package_manifest_test.ts(picked up by theunit:devvitest project). It readsdev/package.jsonrelative to the test file and pins two facts:@types/expressis declared independenciesand not indevDependencies; andexpressand@types/expressshare a semver major (derived from each range, not hardcoded to4, so a coordinated bump to major 5 keeps passing while a one-sided bump fails).Mutation proof — each assertion was shown to fail. Three independent mutations, each reverted afterwards:
Mutation 1 — revert the
dev/package.jsonhunk (put@types/expressback underdevDependencies). Both cases fail:Mutation 2 — keep the fix but bump
@types/expressto^5.0.0(major skew). Only the major-parity case fails, proving it is not vacuous:Mutation 3 — declare
@types/expressin both blocks. Only the classification case fails, proving thedevDependencieshalf of it carries weight:Coverage. This change adds zero lines under any
src/tree, andvitest.config.tsrestricts coverage to*/src/**/*.ts, so the new-code coverage bar is vacuously met and the thresholds cannot move. The meaningful evidence is the mutation proof above and the clean-room proof below.No automated integration test was added, deliberately. An automated version of the clean-room repro needs
npm packplus a full out-of-treenpm install(the tarball drags in@google/adk, five MikroORM drivers and esbuild) plus atscrun, on all three OSes in the CI matrix, for a change that adds no code paths. The existingtests/integration/build_setupfixtures cannot host it either: they live inside the repo and install withfile:, and they setskipLibCheck: true— i.e. they reproduce exactly the two maskings this bug hides behind, so the test would pass before and after. Flipping those fixtures toskipLibCheck: falsewould not detect this bug (hoisting still masks it) and would surface unrelated pre-existing declaration errors across the suite.Other CI gates, run locally on the pushed commit:
CI on this PR is green on all three matrix OSes (
run-testsubuntu / macOS / windows). Two intermittent failures were hit on the first attempt and cleared on re-run; both are pre-existing harness flakes with zero individual test failures, unaffected by a manifest-only change:tests/integration/app_loader/app_loader_test.ts > should discover apps vs agents across directories and standalone files—Test timed out in 40000ms(2679 passed / 1 failed).tests/integration/a2a/stream/stream_test.ts > A2A: RemoteAgent Streaming—Error: CLI exited prematurely with code 1attests/integration/test_case_utils.ts:341(2680 passed, 0 failed; the failure is at suite level, in the spawned test server). The same harness error fails the windows job on this repo'smainbranch itself, intests/integration/adk_web/webui_test.ts.Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.
The defect is only observable outside the monorepo, so the proof is a packed tarball installed into a scratch project outside the repo tree, type-checked with
skipLibCheck: false:Before (tarball packed from the base commit) —
node_modules/@types/expressis absent, and the shipped declaration raises the error itself:After (tarball packed from this branch) —
node_modules/@types/expressis present purely as a transitive install, and everyTS7016for express is gone:The acceptance criterion is scoped by file path: zero diagnostics under
node_modules/@google/adk-devtools/. The residual sixTS2307: Cannot find module 'openapi-types'come from insidenode_modules/@google/adk(installed from the registry) and are the subject of a separate change tocore/package.json; they are present identically before and after and were deliberately not touched here. Addingnpm i --save-dev openapi-typesto the scratch consumer makes the run fully green. The@google/adkand@a2a-js/sdkexpress diagnostics also clear in the "after" run, because npm hoists the now-transitive@types/expressto the consumer'snode_modulesroot where it is globally visible — incidental to this fix, not a substitute for the separatecore/package.jsonchange.Second symptom (
skipLibCheck: true) — the silentany. Re-checking the same two consumers withskipLibCheck: trueandexport const app: AdkApiServer['app'] = 42;:That error in the "after" run is the desired outcome:
AdkApiServer['app']is genuinelyexpress.Applicationfor consumers instead of collapsing toany.No runtime or declaration output changed.
dev/dist/typesis byte-identical across the two tarballs (sha256over the sorted file digests:d4df2eb5cc78059f77cc3f5118c954a4abae9dad86a9b469392652f902b91ccein both), and the two tarballs'dependenciesblocks differ by exactly the one added line. Only whether a consumer can resolve the express import changes.Not a breaking change, with one behaviour worth naming: a consumer type-checking with
skipLibCheck: falsewho had been tolerating or suppressing the oldTS7016will now findAdkApiServer['app']genuinely typed, which can surface pre-existing type errors in their own code. That is the fix working. Consumers who already installed@types/expressthemselves are unaffected — npm dedupes to one copy when the ranges overlap.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.