From 6e8628571f853d59f4b839a0d598bbd10bb7811e Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:52:30 +1000 Subject: [PATCH 1/2] fix: resolve dangling directory-barrel imports in published .d.ts Bare imports of utils/tools/middleware barrels were emitted as ../utils.js (etc.), which do not resolve under bundler/node16/nodenext (no /index fallback). With consumer skipLibCheck, those symbols silently became any. Point imports at concrete modules or explicit /index paths, and add pnpm test:dts to scan built declarations so this class cannot regress. --- .changeset/fix-dangling-dts-barrel-imports.md | 14 ++ nx.json | 7 + scripts/scan-dangling-dts.mjs | 137 +++++++++++++----- 3 files changed, 123 insertions(+), 35 deletions(-) create mode 100644 .changeset/fix-dangling-dts-barrel-imports.md diff --git a/.changeset/fix-dangling-dts-barrel-imports.md b/.changeset/fix-dangling-dts-barrel-imports.md new file mode 100644 index 000000000..02c953e92 --- /dev/null +++ b/.changeset/fix-dangling-dts-barrel-imports.md @@ -0,0 +1,14 @@ +--- +'@tanstack/ai': patch +'@tanstack/ai-anthropic': patch +'@tanstack/ai-bedrock': patch +'@tanstack/ai-fal': patch +'@tanstack/ai-gemini': patch +'@tanstack/ai-grok': patch +'@tanstack/ai-groq': patch +'@tanstack/ai-mistral': patch +'@tanstack/ai-ollama': patch +'@tanstack/ai-openrouter': patch +--- + +fix: resolve directory-barrel imports in published `.d.ts` files. Bare imports of `utils`/`tools`/`middleware` barrels were emitted as `../utils.js` (etc.), which do not resolve under bundler/node16/nodenext (no `/index` fallback for explicit `.js`). With consumer `skipLibCheck: true` those symbols silently became `any`. Imports now target concrete modules (e.g. `utils/client`, `middleware/types`) or explicit `/index` paths so public types resolve correctly. diff --git a/nx.json b/nx.json index a4c8f7b86..9b6ed95f6 100644 --- a/nx.json +++ b/nx.json @@ -78,6 +78,13 @@ "test:sherif": { "cache": true, "inputs": ["{workspaceRoot}/**/package.json"] + }, + "test:dts": { + "cache": true, + "inputs": [ + "{workspaceRoot}/scripts/scan-dangling-dts.mjs", + "{workspaceRoot}/packages/*/dist/**/*.d.ts" + ] } } } diff --git a/scripts/scan-dangling-dts.mjs b/scripts/scan-dangling-dts.mjs index 3d5acc771..3601bf6cc 100644 --- a/scripts/scan-dangling-dts.mjs +++ b/scripts/scan-dangling-dts.mjs @@ -1,54 +1,121 @@ -// scan-dangling-dts.mjs — run from repo root after `pnpm build:all` -import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs' -import { dirname, resolve, join } from 'node:path' +#!/usr/bin/env node +/** + * Scan built package declarations for dangling relative imports. + * + * Under bundler/node16/nodenext resolution, an explicit `.js` specifier is + * remapped to a sibling `.d.ts` and does **not** fall back to `/index`. + * Directory-barrel imports emitted as `../utils.js` therefore fail to resolve + * (the real file is `utils/index.d.ts`). Consumers usually set + * `skipLibCheck: true`, so the unresolved import silently becomes `any` + * instead of erroring — degrading public types without a signal. + * + * This check runs on the producer side over built package dist declarations so + * regressions fail CI regardless of consumer tsconfig. + * + * Run after a packages build: + * pnpm build:all && pnpm test:dts + */ +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs' +import { dirname, join, relative, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' -const ROOT = process.cwd() +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const PACKAGES_DIR = join(ROOT, 'packages') -function walk(dir, out = []) { - for (const e of readdirSync(dir)) { - const p = join(dir, e) - const st = statSync(p) +/** @param {string} dir @param {string[]} out */ +function walkDts(dir, out = []) { + for (const entry of readdirSync(dir)) { + const path = join(dir, entry) + const st = statSync(path) if (st.isDirectory()) { - if (e !== 'node_modules') walk(p, out) - } else if (e.endsWith('.d.ts')) { - out.push(p) + if (entry !== 'node_modules') walkDts(path, out) + } else if (entry.endsWith('.d.ts') && !entry.endsWith('.d.ts.map')) { + out.push(path) } } return out } -const RE = /(?:from|import)\s*\(?\s*['"](\.\.?\/[^'"]+)['"]/g +/** + * Whether a relative declaration import resolves to a real file. + * Explicit `.js`/`.mjs`/`.cjs` extensions do **not** fall back to `/index` + * (matching TypeScript bundler/node16/nodenext behavior). + * + * @param {string} fromFile + * @param {string} specifier + */ +function resolves(fromFile, specifier) { + const abs = resolve(dirname(fromFile), specifier) + + if (/\.(js|mjs|cjs)$/.test(specifier)) { + const noext = abs.replace(/\.(js|mjs|cjs)$/, '') + return ['.d.ts', '.d.mts', '.d.cts', '.ts', '.tsx'].some((ext) => + existsSync(noext + ext), + ) + } + + return ( + ['.d.ts', '.ts', '.tsx'].some((ext) => existsSync(abs + ext)) || + ['/index.d.ts', '/index.ts'].some((suffix) => existsSync(abs + suffix)) + ) +} + +const IMPORT_RE = /(?:from|import)\s*\(?\s*['"](\.\.?\/[^'"]+)['"]/g + +const packageNames = existsSync(PACKAGES_DIR) + ? readdirSync(PACKAGES_DIR).filter((name) => { + try { + return statSync(join(PACKAGES_DIR, name)).isDirectory() + } catch { + return false + } + }) + : [] + +const dists = packageNames + .map((name) => join(PACKAGES_DIR, name, 'dist')) + .filter((dir) => existsSync(dir)) + +if (dists.length === 0) { + console.error( + 'scan-dangling-dts: no packages/*/dist directories found. Build packages first (e.g. pnpm build:all).', + ) + process.exit(1) +} + +/** @type {string[]} */ const findings = [] -const dists = readdirSync(join(ROOT, 'packages')) - .map((p) => join(ROOT, 'packages', p, 'dist')) - .filter((d) => existsSync(d)) +let filesScanned = 0 for (const dist of dists) { - for (const file of walk(dist)) { + for (const file of walkDts(dist)) { + filesScanned += 1 const src = readFileSync(file, 'utf8') - let m - while ((m = RE.exec(src))) { - const spec = m[1] - const abs = resolve(dirname(file), spec) - let ok - if (/\.(js|mjs|cjs)$/.test(spec)) { - const noext = abs.replace(/\.(js|mjs|cjs)$/, '') - ok = ['.d.ts', '.d.mts', '.d.cts', '.ts', '.tsx'].some((x) => - existsSync(noext + x), - ) // no /index fallback - } else { - ok = - ['.d.ts', '.ts', '.tsx'].some((x) => existsSync(abs + x)) || - ['/index.d.ts', '/index.ts'].some((x) => existsSync(abs + x)) + IMPORT_RE.lastIndex = 0 + let match + while ((match = IMPORT_RE.exec(src))) { + const specifier = match[1] + if (!resolves(file, specifier)) { + findings.push(`${specifier} <- ${relative(ROOT, file)}`) } - if (!ok) findings.push(`${spec} <- ${file.replace(ROOT + '/', '')}`) } } } -console.log(findings.sort().join('\n') || 'clean') -console.log(`\n${findings.length} dangling specifiers`) +const unique = [...new Set(findings)].sort() -if (findings.length > 0) { - process.exit(1) +if (unique.length === 0) { + console.log( + `scan-dangling-dts: clean (${filesScanned} .d.ts files across ${dists.length} package dist dirs)`, + ) + process.exit(0) } + +console.error( + 'scan-dangling-dts: dangling relative imports in published .d.ts:\n', +) +console.error(unique.join('\n')) +console.error( + `\n${unique.length} dangling specifier(s). Prefer concrete module paths (e.g. '../utils/client') or explicit '/index' so the declaration emit resolves under bundler/node16/nodenext.`, +) +process.exit(1) From 569e768cadbcc8aae7e965665fa440618e70169c Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:54:04 +1000 Subject: [PATCH 2/2] fix: resolve attw InternalResolutionError under node16 ESM Bare '.', extensionless '.generated', and version-dot basenames (model-meta-llama3.1, etc.) emit declaration imports that node16 mis-resolves by stripping the wrong extension. Point them at explicit ./index or .js paths so attw --profile esm-only is clean for ai-anthropic, ai-bedrock, and ai-ollama. --- .../ai-anthropic/src/tools/tool-converter.ts | 2 +- packages/ai-bedrock/src/model-meta.ts | 2 +- packages/ai-ollama/src/model-meta.ts | 40 +++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/ai-anthropic/src/tools/tool-converter.ts b/packages/ai-anthropic/src/tools/tool-converter.ts index 4ca43c389..8ab68c244 100644 --- a/packages/ai-anthropic/src/tools/tool-converter.ts +++ b/packages/ai-anthropic/src/tools/tool-converter.ts @@ -6,7 +6,7 @@ import { convertMemoryToolToAdapterFormat } from './memory-tool' import { convertTextEditorToolToAdapterFormat } from './text-editor-tool' import { convertWebFetchToolToAdapterFormat } from './web-fetch-tool' import { convertWebSearchToolToAdapterFormat } from './web-search-tool' -import type { AnthropicTool } from '.' +import type { AnthropicTool } from './index' import type { Tool } from '@tanstack/ai' /** diff --git a/packages/ai-bedrock/src/model-meta.ts b/packages/ai-bedrock/src/model-meta.ts index dbd02ab3c..fecc28cd9 100644 --- a/packages/ai-bedrock/src/model-meta.ts +++ b/packages/ai-bedrock/src/model-meta.ts @@ -1,4 +1,4 @@ -import { GENERATED_BEDROCK_MODELS } from './model-catalog.generated' +import { GENERATED_BEDROCK_MODELS } from './model-catalog.generated.js' import type { BedrockTextProviderOptions } from './text/text-provider-options' import type { BedrockConverseProviderOptions } from './converse/provider-options' diff --git a/packages/ai-ollama/src/model-meta.ts b/packages/ai-ollama/src/model-meta.ts index 6ab88da79..3674fdf12 100644 --- a/packages/ai-ollama/src/model-meta.ts +++ b/packages/ai-ollama/src/model-meta.ts @@ -9,10 +9,10 @@ import { COMMAND_R_7b_MODELS } from './meta/model-meta-command-r7b' import { DEEPSEEK_CODER_V2_MODELS } from './meta/model-meta-deepseek-coder-v2' import { DEEPSEEK_OCR_MODELS } from './meta/model-meta-deepseek-ocr' import { DEEPSEEK_R1_MODELS } from './meta/model-meta-deepseek-r1' -import { DEEPSEEK_V3_1_MODELS } from './meta/model-meta-deepseek-v3.1' +import { DEEPSEEK_V3_1_MODELS } from './meta/model-meta-deepseek-v3.1.js' import { DEVSTRAL_MODELS } from './meta/model-meta-devstral' import { DOLPHIN3_MODELS } from './meta/model-meta-dolphin3' -import { EXAONE3_5MODELS } from './meta/model-meta-exaone3.5' +import { EXAONE3_5MODELS } from './meta/model-meta-exaone3.5.js' import { FALCON2_MODELS } from './meta/model-meta-falcon2' import { FALCON3_MODELS } from './meta/model-meta-falcon3' import { FIREFUNCTION_V2_MODELS } from './meta/model-meta-firefunction-v2' @@ -23,17 +23,17 @@ import { GPT_OSS_MODELS } from './meta/model-meta-gpt-oss' import { GRANITE3_DENSE_MODELS } from './meta/model-meta-granite3-dense' import { GRANITE3_GUARDIAN_MODELS } from './meta/model-meta-granite3-guardian' import { GRANITE3_MOE_MODELS } from './meta/model-meta-granite3-moe' -import { GRANITE3_1_DENSE_MODELS } from './meta/model-meta-granite3.1-dense' -import { GRANITE3_1_MOE_MODELS } from './meta/model-meta-granite3.1-moe' +import { GRANITE3_1_DENSE_MODELS } from './meta/model-meta-granite3.1-dense.js' +import { GRANITE3_1_MOE_MODELS } from './meta/model-meta-granite3.1-moe.js' import { LLAMA_GUARD3_MODELS } from './meta/model-meta-llama-guard3' import { LLAMA2_MODELS } from './meta/model-meta-llama2' import { LLAMA3_MODELS } from './meta/model-meta-llama3' import { LLAMA3_CHATQA_MODELS } from './meta/model-meta-llama3-chatqa' import { LLAMA3_GRADIENT_MODELS } from './meta/model-meta-llama3-gradient' -import { LLAMA3_1_MODELS } from './meta/model-meta-llama3.1' -import { LLAMA3_2_MODELS } from './meta/model-meta-llama3.2' -import { LLAMA3_2_VISION_MODELS } from './meta/model-meta-llama3.2-vision' -import { LLAMA3_3_MODELS } from './meta/model-meta-llama3.3' +import { LLAMA3_1_MODELS } from './meta/model-meta-llama3.1.js' +import { LLAMA3_2_MODELS } from './meta/model-meta-llama3.2.js' +import { LLAMA3_2_VISION_MODELS } from './meta/model-meta-llama3.2-vision.js' +import { LLAMA3_3_MODELS } from './meta/model-meta-llama3.3.js' import { LLAMA4_MODELS } from './meta/model-meta-llama4' import { LLAVA_MODELS } from './meta/model-meta-llava' import { LLAVA_LLAMA3_MODELS } from './meta/model-meta-llava-llama3' @@ -54,8 +54,8 @@ import { PHI3_MODELS } from './meta/model-meta-phi3' import { PHI4_MODELS } from './meta/model-meta-phi4' import { QWEN_MODELS } from './meta/model-meta-qwen' import { QWEN2_MODELS } from './meta/model-meta-qwen2' -import { QWEN2_5_MODELS } from './meta/model-meta-qwen2.5' -import { QWEN2_5_CODER_MODELS } from './meta/model-meta-qwen2.5-coder' +import { QWEN2_5_MODELS } from './meta/model-meta-qwen2.5.js' +import { QWEN2_5_CODER_MODELS } from './meta/model-meta-qwen2.5-coder.js' import { QWEN3_MODELS } from './meta/model-meta-qwen3' import { QWQ_MODELS } from './meta/model-meta-qwq' import { SAILOR2_MODELS } from './meta/model-meta-sailor2' @@ -109,7 +109,7 @@ import type { import type { Deepseekv3_1ChatModelProviderOptionsByName, Deepseekv3_1ModelInputModalitiesByName, -} from './meta/model-meta-deepseek-v3.1' +} from './meta/model-meta-deepseek-v3.1.js' import type { DevstralChatModelProviderOptionsByName, DevstralModelInputModalitiesByName, @@ -121,7 +121,7 @@ import type { import type { Exaone3_5ChatModelProviderOptionsByName, Exaone3_5ModelInputModalitiesByName, -} from './meta/model-meta-exaone3.5' +} from './meta/model-meta-exaone3.5.js' import type { Falcon2ChatModelProviderOptionsByName, Falcon2ModelInputModalitiesByName, @@ -165,11 +165,11 @@ import type { import type { Granite3_1DenseChatModelProviderOptionsByName, Granite3_1DenseModelInputModalitiesByName, -} from './meta/model-meta-granite3.1-dense' +} from './meta/model-meta-granite3.1-dense.js' import type { Granite3_1MoeChatModelProviderOptionsByName, Granite3_1MoeModelInputModalitiesByName, -} from './meta/model-meta-granite3.1-moe' +} from './meta/model-meta-granite3.1-moe.js' import type { LlamaGuard3ChatModelProviderOptionsByName, LlamaGuard3ModelInputModalitiesByName, @@ -193,19 +193,19 @@ import type { import type { Llama3_1ChatModelProviderOptionsByName, Llama3_1ModelInputModalitiesByName, -} from './meta/model-meta-llama3.1' +} from './meta/model-meta-llama3.1.js' import type { Llama3_2ChatModelProviderOptionsByName, Llama3_2ModelInputModalitiesByName, -} from './meta/model-meta-llama3.2' +} from './meta/model-meta-llama3.2.js' import type { Llama3_2VisionChatModelProviderOptionsByName, Llama3_2VisionModelInputModalitiesByName, -} from './meta/model-meta-llama3.2-vision' +} from './meta/model-meta-llama3.2-vision.js' import type { Llama3_3ChatModelProviderOptionsByName, Llama3_3ModelInputModalitiesByName, -} from './meta/model-meta-llama3.3' +} from './meta/model-meta-llama3.3.js' import type { Llama4ChatModelProviderOptionsByName, Llama4ModelInputModalitiesByName, @@ -289,11 +289,11 @@ import type { import type { Qwen2_5ChatModelProviderOptionsByName, Qwen2_5ModelInputModalitiesByName, -} from './meta/model-meta-qwen2.5' +} from './meta/model-meta-qwen2.5.js' import type { Qwen2_5CoderChatModelProviderOptionsByName, Qwen2_5CoderModelInputModalitiesByName, -} from './meta/model-meta-qwen2.5-coder' +} from './meta/model-meta-qwen2.5-coder.js' import type { Qwen3ChatModelProviderOptionsByName, Qwen3ModelInputModalitiesByName,