Skip to content

Commit d7389a9

Browse files
committed
test: add issue #621 regression test for declared type dependencies
1 parent fcb5b5b commit d7389a9

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* Regression test for GitHub issue #621
3+
* https://github.com/OpenRouterTeam/ai-sdk-provider/issues/621
4+
*
5+
* Reported error: TS2307: Cannot find module '@ai-sdk/provider' (pnpm
6+
* isolated with hoist=false, skipLibCheck: false); under other layouts the
7+
* provider types silently became `any` or bound to an arbitrary hoisted copy.
8+
* SDK version: @openrouter/ai-sdk-provider 3.1.0
9+
*
10+
* This test verifies that every package imported by the library source (and
11+
* by the built `dist/**\/*.d.ts`, when present) is declared in `dependencies`
12+
* or `peerDependencies`. Layout-level coverage against a packed tarball lives
13+
* in `scripts/check-consumer-types.mjs` (`pnpm check:consumer-types`).
14+
*/
15+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
16+
import { join } from 'node:path';
17+
import { describe, expect, it } from 'vitest';
18+
19+
const repoRoot = join(import.meta.dirname, '..', '..');
20+
const pkg: {
21+
dependencies?: Record<string, string>;
22+
peerDependencies?: Record<string, string>;
23+
} = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8'));
24+
const declared = new Set([
25+
...Object.keys(pkg.dependencies ?? {}),
26+
...Object.keys(pkg.peerDependencies ?? {}),
27+
]);
28+
29+
function listFiles(dir: string, accept: (file: string) => boolean): string[] {
30+
return readdirSync(dir).flatMap((entry) => {
31+
const full = join(dir, entry);
32+
if (statSync(full).isDirectory()) {
33+
return listFiles(full, accept);
34+
}
35+
return accept(full) ? [full] : [];
36+
});
37+
}
38+
39+
function importedPackages(files: string[]): Set<string> {
40+
const specifier = /(?:from|import)\s*\(?\s*['"]([^'"]+)['"]/g;
41+
const packages = new Set<string>();
42+
for (const file of files) {
43+
for (const [, spec] of readFileSync(file, 'utf8').matchAll(specifier)) {
44+
if (
45+
!spec ||
46+
spec.startsWith('.') ||
47+
spec.startsWith('@/') ||
48+
spec.startsWith('node:')
49+
) {
50+
continue;
51+
}
52+
const parts = spec.split('/');
53+
packages.add(
54+
spec.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]!,
55+
);
56+
}
57+
}
58+
return packages;
59+
}
60+
61+
const distDir = join(repoRoot, 'dist');
62+
63+
describe('Issue #621: type declarations import undeclared packages', () => {
64+
it('declares @ai-sdk/provider and @ai-sdk/provider-utils as dependencies', () => {
65+
expect(pkg.dependencies).toHaveProperty('@ai-sdk/provider');
66+
expect(pkg.dependencies).toHaveProperty('@ai-sdk/provider-utils');
67+
});
68+
69+
it('declares every package imported by library source', () => {
70+
const files = listFiles(
71+
join(repoRoot, 'src'),
72+
(f) => f.endsWith('.ts') && !f.endsWith('.test.ts'),
73+
);
74+
const undeclared = [...importedPackages(files)].filter(
75+
(name) => !declared.has(name),
76+
);
77+
expect(undeclared).toEqual([]);
78+
});
79+
80+
it.skipIf(!existsSync(distDir))(
81+
'declares every package imported by built type declarations',
82+
() => {
83+
const files = listFiles(distDir, (f) => f.endsWith('.d.ts'));
84+
expect(files.length).toBeGreaterThan(0);
85+
const undeclared = [...importedPackages(files)].filter(
86+
(name) => !declared.has(name),
87+
);
88+
expect(undeclared).toEqual([]);
89+
},
90+
);
91+
});

0 commit comments

Comments
 (0)