diff --git a/packages/testtools/package.json b/packages/testtools/package.json index c845e37aa..a4cba9a2a 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -55,7 +55,8 @@ }, "peerDependencies": { "better-sqlite3": "catalog:", - "pg": "catalog:" + "pg": "catalog:", + "vitest": "^4.0.14" }, "devDependencies": { "@types/better-sqlite3": "catalog:", @@ -65,7 +66,8 @@ "@zenstackhq/eslint-config": "workspace:*", "@zenstackhq/tsdown-config": "workspace:*", "@zenstackhq/typescript-config": "workspace:*", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "^4.0.14" }, "funding": "https://github.com/sponsors/zenstackhq" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f9f69741..9d153c351 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1020,6 +1020,9 @@ importers: typescript: specifier: 'catalog:' version: 6.0.3 + vitest: + specifier: ^4.0.14 + version: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.8.9)(jiti@2.7.0)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.22.4)(yaml@2.9.0) packages/zod: dependencies: diff --git a/tests/regression/test/issue-2659.test.ts b/tests/regression/test/issue-2659.test.ts new file mode 100644 index 000000000..ef03d6bcd --- /dev/null +++ b/tests/regression/test/issue-2659.test.ts @@ -0,0 +1,33 @@ +import fs from 'node:fs'; +import { builtinModules } from 'node:module'; +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +// https://github.com/zenstackhq/zenstack/issues/2659 +describe('Regression for issue 2659', () => { + it('testtools declares every package it imports at runtime', () => { + const pkgDir = path.resolve(__dirname, '../../../packages/testtools'); + const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf-8')); + const declared = new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.peerDependencies ?? {})]); + + const srcDir = path.join(pkgDir, 'src'); + const imported = new Set(); + for (const file of fs.readdirSync(srcDir)) { + if (!file.endsWith('.ts') || file.endsWith('.d.ts')) { + continue; + } + const content = fs.readFileSync(path.join(srcDir, file), 'utf-8'); + for (const m of content.matchAll(/^import\s+(?!type\s)[^'"]*?from\s+['"]([^'"]+)['"]/gm)) { + const spec = m[1]!; + if (spec.startsWith('.') || spec.startsWith('node:') || builtinModules.includes(spec)) { + continue; + } + const parts = spec.split('/'); + imported.add(spec.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]!); + } + } + + const missing = [...imported].filter((name) => !declared.has(name)); + expect(missing).toEqual([]); + }); +});