From c0d6d549205efe38d1fb9a18af4785de0bc5f63a Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Sun, 2 Aug 2026 22:10:51 -0700 Subject: [PATCH 1/2] fix(dev): declare @types/express as a runtime dependency 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. --- dev/package.json | 2 +- package-lock.json | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/dev/package.json b/dev/package.json index 6117df13a..00323917a 100644 --- a/dev/package.json +++ b/dev/package.json @@ -41,7 +41,6 @@ }, "devDependencies": { "@types/cors": "^2.8.19", - "@types/express": "^4.17.21", "@types/node": "^20.12.7", "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.7.1", @@ -61,6 +60,7 @@ "@mikro-orm/mysql": "^6.6.6", "@mikro-orm/postgresql": "^6.6.6", "@mikro-orm/sqlite": "^6.6.6", + "@types/express": "^4.17.21", "camelcase-keys": "^6.2.2", "commander": "^14.0.0", "cors": "^2.8.5", diff --git a/package-lock.json b/package-lock.json index e29a4a190..b5e067d60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -101,6 +101,7 @@ "@mikro-orm/mysql": "^6.6.6", "@mikro-orm/postgresql": "^6.6.6", "@mikro-orm/sqlite": "^6.6.6", + "@types/express": "^4.17.21", "camelcase-keys": "^6.2.2", "commander": "^14.0.0", "cors": "^2.8.5", @@ -119,7 +120,6 @@ }, "devDependencies": { "@types/cors": "^2.8.19", - "@types/express": "^4.17.21", "@types/node": "^20.12.7", "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.7.1", @@ -4004,7 +4004,6 @@ "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -4032,7 +4031,6 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4066,7 +4064,6 @@ "version": "4.17.25", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -4079,7 +4076,6 @@ "version": "4.19.8", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -4108,7 +4104,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, "license": "MIT" }, "node_modules/@types/js-yaml": { @@ -4146,7 +4141,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, "license": "MIT" }, "node_modules/@types/minimist": { @@ -4176,14 +4170,12 @@ "version": "6.15.1", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/readable-stream": { @@ -4224,7 +4216,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4234,7 +4225,6 @@ "version": "1.15.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -4246,7 +4236,6 @@ "version": "0.17.6", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", From 3e32238436b9dc17a94c966093761a33f5f61546 Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Sun, 2 Aug 2026 22:10:55 -0700 Subject: [PATCH 2/2] test(dev): pin the @types/express classification in dev/package.json 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. --- dev/test/package_manifest_test.ts | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 dev/test/package_manifest_test.ts diff --git a/dev/test/package_manifest_test.ts b/dev/test/package_manifest_test.ts new file mode 100644 index 000000000..5c9508050 --- /dev/null +++ b/dev/test/package_manifest_test.ts @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {readFileSync} from 'node:fs'; +import {fileURLToPath} from 'node:url'; +import {describe, expect, it} from 'vitest'; + +interface Manifest { + dependencies?: Record; + devDependencies?: Record; +} + +const MANIFEST_PATH = fileURLToPath( + new URL('../package.json', import.meta.url), +); +const manifest: Manifest = JSON.parse(readFileSync(MANIFEST_PATH, 'utf8')); + +/** Extracts the semver major from a range such as `^4.17.21` or `>=4.0.0`. */ +function majorVersion(range: string): string { + return range.replace(/^\D+/, '').split('.')[0]; +} + +describe('dev/package.json', () => { + /** + * `AdkApiServer.app` is typed `express.Application` and the class is exported + * from `dev/src/index.ts`, so the published declarations name the `express` + * module. express@4 bundles no declarations of its own, so a consumer can + * only resolve that import if `@types/express` is installed for them, and a + * devDependency never is. + */ + it('declares @types/express as a runtime dependency', () => { + expect(manifest.dependencies?.['@types/express']).toBeDefined(); + expect(manifest.devDependencies?.['@types/express']).toBeUndefined(); + }); + + it('keeps express and @types/express on the same semver major', () => { + const expressRange = manifest.dependencies?.['express']; + const typesExpressRange = manifest.dependencies?.['@types/express']; + if (expressRange === undefined || typesExpressRange === undefined) { + expect.fail( + 'dev/package.json must declare both express and @types/express as dependencies', + ); + } + expect(majorVersion(typesExpressRange)).toBe(majorVersion(expressRange)); + }); +});