From d476f5d7e1d4e383983893883ae4336ceed31b41 Mon Sep 17 00:00:00 2001
From: ryoppippi <1560508+ryoppippi@users.noreply.github.com>
Date: Fri, 27 Mar 2026 08:13:07 +0000
Subject: [PATCH] fix(cli): generate proxy .d.ts files for TypeScript
compilerOptions.types resolution
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
TypeScript's compilerOptions.types field resolves package subpaths via the
filesystem directly, without consulting package.json exports. This means
"types": ["vite-plus/test/globals"] looks for node_modules/vite-plus/test/globals.d.ts
on disk, but that file didn't exist — the actual shim was at dist/test/globals.d.ts,
only reachable via the exports map.
vitest works because it places globals.d.ts at the package root, matching the
filesystem path TypeScript expects.
Fix: syncTestPackageExports() now also generates proxy .d.ts files at test/{name}.d.ts
for type-only exports (those with only a "types" field and no runtime JS). Each proxy
contains a triple-slash reference directive:
///
Triple-slash reference types directives DO go through the exports field, so this
correctly delegates to the underlying package while being discoverable at the path
TypeScript expects for compilerOptions.types.
The generated test/ directory is gitignored and added to package.json files at
build time, so it is included in published packages.
---
packages/cli/.gitignore | 1 +
packages/cli/build.ts | 40 ++++++++++++++++++++++++++++++++++++++--
2 files changed, 39 insertions(+), 2 deletions(-)
diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore
index 1ff8aa7bb3..5e1e780c15 100644
--- a/packages/cli/.gitignore
+++ b/packages/cli/.gitignore
@@ -2,3 +2,4 @@
/artifacts
/LICENSE
/skills/vite-plus/docs
+/test
diff --git a/packages/cli/build.ts b/packages/cli/build.ts
index c595cadc7d..e5ad56c40f 100644
--- a/packages/cli/build.ts
+++ b/packages/cli/build.ts
@@ -21,7 +21,7 @@
import { execSync } from 'node:child_process';
import { existsSync, globSync, readFileSync, readdirSync, statSync } from 'node:fs';
import { copyFile, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
-import { dirname, join } from 'node:path';
+import { basename, dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { parseArgs } from 'node:util';
@@ -391,6 +391,11 @@ async function syncTestPackageExports() {
const testPkgPath = join(projectDir, '../test/package.json');
const cliPkgPath = join(projectDir, 'package.json');
const testDistDir = join(projectDir, 'dist/test');
+ // Proxy files for TypeScript compilerOptions.types resolution.
+ // TypeScript resolves "vite-plus/test/globals" via the filesystem directly
+ // (without consulting package.json exports), so we need actual files at
+ // test/globals.d.ts that TypeScript can find for type-only exports.
+ const testTypesDir = join(projectDir, 'test');
// Read test package.json
const testPkg = JSON.parse(await readFile(testPkgPath, 'utf-8'));
@@ -399,6 +404,8 @@ async function syncTestPackageExports() {
// Clean up previous build
await rm(testDistDir, { recursive: true, force: true });
await mkdir(testDistDir, { recursive: true });
+ await rm(testTypesDir, { recursive: true, force: true });
+ await mkdir(testTypesDir, { recursive: true });
const generatedExports: Record = {};
@@ -416,6 +423,24 @@ async function syncTestPackageExports() {
if (shimExport) {
generatedExports[cliExportPath] = shimExport;
console.log(` Created ${cliExportPath}`);
+
+ // For type-only exports, also create a proxy file at test/{name}.d.ts so
+ // TypeScript can resolve "vite-plus/test/globals" via compilerOptions.types.
+ // compilerOptions.types uses filesystem resolution (not exports field), so
+ // it looks for node_modules/vite-plus/test/globals.d.ts directly.
+ if (isTypeOnlyExport(shimExport)) {
+ const testImportSpecifier =
+ exportPath === '.' ? TEST_PACKAGE_NAME : `${TEST_PACKAGE_NAME}${exportPath.slice(1)}`;
+ const shimBaseName = exportPath === '.' ? 'index' : exportPath.slice(2);
+ const proxyRelDir = dirname(shimBaseName);
+ const proxyDir = proxyRelDir === '.' ? testTypesDir : join(testTypesDir, proxyRelDir);
+ await mkdir(proxyDir, { recursive: true });
+ const baseFileName = basename(shimBaseName);
+ await writeFile(
+ join(proxyDir, `${baseFileName}.d.ts`),
+ `/// \n`,
+ );
+ }
}
}
@@ -425,6 +450,14 @@ async function syncTestPackageExports() {
console.log(`\nSynced ${Object.keys(generatedExports).length} exports from test package`);
}
+function isTypeOnlyExport(exportValue: ExportValue): boolean {
+ if (typeof exportValue === 'string') {
+ return false;
+ }
+ const keys = Object.keys(exportValue);
+ return keys.length === 1 && keys[0] === 'types';
+}
+
/**
* Copy markdown doc files from the monorepo docs/ directory into skills/vite-plus/docs/,
* preserving the relative directory structure. This keeps stable file paths for
@@ -713,10 +746,13 @@ async function updateCliPackageJson(pkgPath: string, generatedExports: Record