diff --git a/packages/plugin-dts/src/utils.ts b/packages/plugin-dts/src/utils.ts index 703e55c3c..c24b1d8d3 100644 --- a/packages/plugin-dts/src/utils.ts +++ b/packages/plugin-dts/src/utils.ts @@ -5,6 +5,7 @@ import path, { basename, dirname, extname, + isAbsolute, join, normalize, relative, @@ -185,10 +186,15 @@ async function addExtension( let redirectPath = path; + // Only add extension if redirectPath is an absolute or relative path + if (!isAbsolute(redirectPath) && !redirectPath.startsWith('.')) { + return redirectPath; + } + // If the import path refers to a directory, it most likely actually refers to a `index.*` file due to Node's module resolution if (await isDirectory(join(dirname(dtsFile), redirectPath))) { // This uses `/` instead of `path.join` here because `join` removes potential "./" prefixes - redirectPath = `${redirectPath}/index`; + redirectPath = `${redirectPath.replace(/\/+$/, '')}/index`; } return `${redirectPath}${extension}`; @@ -277,9 +283,12 @@ export async function redirectDtsImports( let redirectImportPath = importPath; if (absoluteImportPath && redirect.path) { - const isOutsideRootdir = !normalize(absoluteImportPath).startsWith( - normalize(rootDir) + path.sep, + const relativeRootDir = path.relative( + normalize(rootDir), + normalize(absoluteImportPath), ); + const isOutsideRootdir = + relativeRootDir.startsWith('..') || path.isAbsolute(relativeRootDir); if (isOutsideRootdir) { const relativePath = relative(dirname(dtsFile), absoluteImportPath); diff --git a/tests/integration/redirect/dts.test.ts b/tests/integration/redirect/dts.test.ts index 6450bc342..fe3d66d7c 100644 --- a/tests/integration/redirect/dts.test.ts +++ b/tests/integration/redirect/dts.test.ts @@ -7,9 +7,9 @@ let contents: Awaited>['contents']; beforeAll(async () => { const fixturePath = path.resolve(__dirname, './dts'); contents = (await buildAndGetResults({ fixturePath, type: 'dts' })).contents; -}); +}, 15000); -test('redirect.dts default', async () => { +test('redirect.dts.path: true with redirect.dts.extension: false - default', async () => { expect(contents.esm0).toMatchInlineSnapshot(` { "/tests/integration/redirect/dts/dist/default/esm/foo/foo.d.ts": "import { logRequest } from '../logger'; @@ -21,9 +21,10 @@ test('redirect.dts default', async () => { ", "/tests/integration/redirect/dts/dist/default/esm/index.d.ts": "import { logRequest } from './logger'; import { logger } from '../../../compile/rslog'; + import type { Baz } from './'; import type { LoggerOptions } from './types'; import { defaultOptions } from './types.js'; - export { logRequest, logger, type LoggerOptions, defaultOptions }; + export { type Baz as self, logRequest, logger, type LoggerOptions, defaultOptions, }; export type { Foo } from './types'; export type { Bar } from './types'; export * from './foo'; @@ -47,12 +48,15 @@ test('redirect.dts default', async () => { export interface Bar { bar: string; } + export interface Baz { + baz: string; + } ", } `); }); -test('redirect.dts.path false', async () => { +test('redirect.dts.path: false with redirect.dts.extension: false', async () => { expect(contents.esm1).toMatchInlineSnapshot(` { "/tests/integration/redirect/dts/dist/path-false/esm/foo/foo.d.ts": "import { logRequest } from '@src/logger'; @@ -64,9 +68,10 @@ test('redirect.dts.path false', async () => { ", "/tests/integration/redirect/dts/dist/path-false/esm/index.d.ts": "import { logRequest } from '@src/logger'; import { logger } from 'rslog'; + import type { Baz } from 'self-entry'; import type { LoggerOptions } from './types'; import { defaultOptions } from './types.js'; - export { logRequest, logger, type LoggerOptions, defaultOptions }; + export { type Baz as self, logRequest, logger, type LoggerOptions, defaultOptions, }; export type { Foo } from '@src/types'; export type { Bar } from 'types'; export * from './foo'; @@ -90,12 +95,15 @@ test('redirect.dts.path false', async () => { export interface Bar { bar: string; } + export interface Baz { + baz: string; + } ", } `); }); -test('redirect.dts.extension true', async () => { +test('redirect.dts.path: true with redirect.dts.extension: true', async () => { expect(contents.esm2).toMatchInlineSnapshot(` { "/tests/integration/redirect/dts/dist/extension-true/esm/foo/foo.d.ts": "import { logRequest } from '../logger.js'; @@ -107,9 +115,10 @@ test('redirect.dts.extension true', async () => { ", "/tests/integration/redirect/dts/dist/extension-true/esm/index.d.ts": "import { logRequest } from './logger.js'; import { logger } from '../../../compile/rslog'; + import type { Baz } from './index.js'; import type { LoggerOptions } from './types.js'; import { defaultOptions } from './types.js'; - export { logRequest, logger, type LoggerOptions, defaultOptions }; + export { type Baz as self, logRequest, logger, type LoggerOptions, defaultOptions, }; export type { Foo } from './types.js'; export type { Bar } from './types.js'; export * from './foo/index.js'; @@ -133,13 +142,63 @@ test('redirect.dts.extension true', async () => { export interface Bar { bar: string; } + export interface Baz { + baz: string; + } ", } `); }); -test('redirect.dts.extension true with dts.autoExtension true', async () => { +test('redirect.dts.path: false with dts.redirect.extension: true', async () => { expect(contents.esm3).toMatchInlineSnapshot(` + { + "/tests/integration/redirect/dts/dist/path-false-extension-true/esm/foo/foo.d.ts": "import { logRequest } from '@src/logger'; + import { logger } from 'rslog'; + import { logRequest as logRequest2 } from '../logger.js'; + export { logRequest, logRequest2, logger }; + ", + "/tests/integration/redirect/dts/dist/path-false-extension-true/esm/foo/index.d.ts": "export type Barrel = string; + ", + "/tests/integration/redirect/dts/dist/path-false-extension-true/esm/index.d.ts": "import { logRequest } from '@src/logger'; + import { logger } from 'rslog'; + import type { Baz } from 'self-entry'; + import type { LoggerOptions } from './types.js'; + import { defaultOptions } from './types.js'; + export { type Baz as self, logRequest, logger, type LoggerOptions, defaultOptions, }; + export type { Foo } from '@src/types'; + export type { Bar } from 'types'; + export * from './foo/index.js'; + export * from '@src/foo'; + export * from './types.js'; + export * from 'rslog'; + export * from '@src/logger'; + ", + "/tests/integration/redirect/dts/dist/path-false-extension-true/esm/logger.d.ts": "import type { Request } from 'express'; + import type { LoggerOptions } from './types.js'; + export declare function logRequest(req: Request, options: LoggerOptions): void; + ", + "/tests/integration/redirect/dts/dist/path-false-extension-true/esm/types.d.ts": "export interface LoggerOptions { + logLevel: 'info' | 'debug' | 'warn' | 'error'; + logBody: boolean; + } + export declare const defaultOptions: LoggerOptions; + export interface Foo { + foo: string; + } + export interface Bar { + bar: string; + } + export interface Baz { + baz: string; + } + ", + } + `); +}); + +test('redirect.dts.extension: true with dts.autoExtension: true', async () => { + expect(contents.esm4).toMatchInlineSnapshot(` { "/tests/integration/redirect/dts/dist/auto-extension-true/foo/foo.d.mts": "import { logRequest } from '../logger.mjs'; import { logger } from '../../../compile/rslog'; @@ -157,9 +216,10 @@ test('redirect.dts.extension true with dts.autoExtension true', async () => { ", "/tests/integration/redirect/dts/dist/auto-extension-true/index.d.mts": "import { logRequest } from './logger.mjs'; import { logger } from '../../compile/rslog'; + import type { Baz } from './index.mjs'; import type { LoggerOptions } from './types.mjs'; import { defaultOptions } from './types.mjs'; - export { logRequest, logger, type LoggerOptions, defaultOptions }; + export { type Baz as self, logRequest, logger, type LoggerOptions, defaultOptions, }; export type { Foo } from './types.mjs'; export type { Bar } from './types.mjs'; export * from './foo/index.mjs'; @@ -170,9 +230,10 @@ test('redirect.dts.extension true with dts.autoExtension true', async () => { ", "/tests/integration/redirect/dts/dist/auto-extension-true/index.d.ts": "import { logRequest } from './logger.js'; import { logger } from '../../compile/rslog'; + import type { Baz } from './index.js'; import type { LoggerOptions } from './types.js'; import { defaultOptions } from './types.js'; - export { logRequest, logger, type LoggerOptions, defaultOptions }; + export { type Baz as self, logRequest, logger, type LoggerOptions, defaultOptions, }; export type { Foo } from './types.js'; export type { Bar } from './types.js'; export * from './foo/index.js'; @@ -200,6 +261,9 @@ test('redirect.dts.extension true with dts.autoExtension true', async () => { export interface Bar { bar: string; } + export interface Baz { + baz: string; + } ", "/tests/integration/redirect/dts/dist/auto-extension-true/types.d.ts": "export interface LoggerOptions { logLevel: 'info' | 'debug' | 'warn' | 'error'; @@ -212,6 +276,9 @@ test('redirect.dts.extension true with dts.autoExtension true', async () => { export interface Bar { bar: string; } + export interface Baz { + baz: string; + } ", } `); diff --git a/tests/integration/redirect/dts/rslib.config.ts b/tests/integration/redirect/dts/rslib.config.ts index 80114ae48..6cf190290 100644 --- a/tests/integration/redirect/dts/rslib.config.ts +++ b/tests/integration/redirect/dts/rslib.config.ts @@ -3,7 +3,7 @@ import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; export default defineConfig({ lib: [ - // 0 - default + // 0 - default - path: true extension: false generateBundleEsmConfig({ dts: true, output: { @@ -12,7 +12,7 @@ export default defineConfig({ }, }, }), - // 1 - path: false + // 1 - path: false extension: false generateBundleEsmConfig({ dts: true, output: { @@ -26,7 +26,7 @@ export default defineConfig({ }, }, }), - // 2 - extension: true + // 2 - path: true extension: true generateBundleEsmConfig({ dts: true, output: { @@ -40,7 +40,22 @@ export default defineConfig({ }, }, }), - // 3 - extension: true with dts.autoExtension true + // 3 - path: false extension: true + generateBundleEsmConfig({ + dts: true, + output: { + distPath: { + root: './dist/path-false-extension-true/esm', + }, + }, + redirect: { + dts: { + path: false, + extension: true, + }, + }, + }), + // 4 - extension: true with dts.autoExtension true generateBundleEsmConfig({ dts: { autoExtension: true, @@ -56,7 +71,7 @@ export default defineConfig({ }, }, }), - // 4 - extension: true with dts.autoExtension true + // 5 - extension: true with dts.autoExtension true generateBundleCjsConfig({ dts: { autoExtension: true, diff --git a/tests/integration/redirect/dts/src/index.ts b/tests/integration/redirect/dts/src/index.ts index b4402c115..ef376e58a 100644 --- a/tests/integration/redirect/dts/src/index.ts +++ b/tests/integration/redirect/dts/src/index.ts @@ -1,9 +1,16 @@ import { logRequest } from '@src/logger'; import { logger } from 'rslog'; +import type { Baz } from 'self-entry'; import type { LoggerOptions } from './types'; import { defaultOptions } from './types.js'; -export { logRequest, logger, type LoggerOptions, defaultOptions }; +export { + type Baz as self, + logRequest, + logger, + type LoggerOptions, + defaultOptions, +}; export type { Foo } from '@src/types'; export type { Bar } from 'types'; diff --git a/tests/integration/redirect/dts/src/types.ts b/tests/integration/redirect/dts/src/types.ts index cc9ccac52..ef6a9bf2b 100644 --- a/tests/integration/redirect/dts/src/types.ts +++ b/tests/integration/redirect/dts/src/types.ts @@ -15,3 +15,7 @@ export interface Foo { export interface Bar { bar: string; } + +export interface Baz { + baz: string; +} diff --git a/tests/integration/redirect/dts/tsconfig.json b/tests/integration/redirect/dts/tsconfig.json index c86ca44d8..d70d332f2 100644 --- a/tests/integration/redirect/dts/tsconfig.json +++ b/tests/integration/redirect/dts/tsconfig.json @@ -5,7 +5,8 @@ "paths": { "*": ["./src/*"], "@src/*": ["./src/*"], - "rslog": ["./compile/rslog"] + "rslog": ["./compile/rslog"], + "self-entry": ["./src"] } }, "include": ["src/**/*"]