Skip to content

Commit a58c605

Browse files
authored
fix: don't shift output when a dependency resolves to a .ts file (#502)
1 parent 245c653 commit a58c605

4 files changed

Lines changed: 56 additions & 0 deletions

File tree

mod.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ export async function build(options: BuildOptions): Promise<void> {
342342
const esmOutDir = path.join(options.outDir, "esm");
343343
const scriptOutDir = path.join(options.outDir, "script");
344344
const typesOutDir = path.join(options.outDir, "types");
345+
const srcOutDir = path.join(options.outDir, "src");
345346
const compilerScriptTarget = getCompilerScriptTarget(scriptTarget);
346347
// TypeScript 6.0 no longer automatically discovers the `@types` packages
347348
// installed in the output's node_modules, so resolve and include them
@@ -352,6 +353,10 @@ export async function build(options: BuildOptions): Promise<void> {
352353
const project = createProjectSync({
353354
compilerOptions: {
354355
outDir: typesOutDir,
356+
// pin the root so that a dependency resolving to a file outside the
357+
// sources (ex. a package that ships a .ts file) can't shift the output
358+
// down a directory and leave the package.json paths dangling
359+
rootDir: srcOutDir,
355360
allowJs: true,
356361
alwaysStrict: true,
357362
stripInternal: options.compilerOptions?.stripInternal,
@@ -588,10 +593,20 @@ export async function build(options: BuildOptions): Promise<void> {
588593
d.code !== 1343 &&
589594
// 1470: The_import_meta_meta_property_is_not_allowed_in_files_which_will_build_into_CommonJS_output
590595
d.code !== 1470 &&
596+
!isInNodeModules(d) &&
591597
(options.filterDiagnostic?.(d) ?? true)
592598
);
593599
}
594600

601+
/** A dependency shipping a .ts file that resolution lands on is compiled
602+
* as a source rather than a declaration file, so `skipLibCheck` doesn't
603+
* cover it. Its code is no more the package author's problem than a .d.ts
604+
* would be, so ignore it all the same. */
605+
function isInNodeModules(diagnostic: ts.Diagnostic) {
606+
const fileName = diagnostic.file?.fileName;
607+
return fileName != null && fileName.includes("/node_modules/");
608+
}
609+
595610
function shouldTypeCheck() {
596611
const typeCheck = options.typeCheck!;
597612
switch (typeCheck) {

tests/integration.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,34 @@ Deno.test("should build undici project", async () => {
11801180
});
11811181
});
11821182

1183+
Deno.test("should build a project with a dependency that resolves to a .ts file", async () => {
1184+
await runTest("node_modules_ts_project", {
1185+
entryPoints: ["mod.ts", "nested/other.ts"],
1186+
outDir: "./npm",
1187+
scriptModule: false,
1188+
declaration: "separate",
1189+
test: false,
1190+
shims: {},
1191+
package: {
1192+
name: "node-modules-ts-project",
1193+
version: "1.0.0",
1194+
},
1195+
}, (output) => {
1196+
// see issue #460 -- the dependency's own .ts file must not shift the
1197+
// output down a directory, which would leave the package.json paths
1198+
// pointing at files that don't exist
1199+
output.assertExists("esm/mod.js");
1200+
output.assertExists("esm/nested/other.js");
1201+
output.assertNotExists("esm/src/mod.js");
1202+
output.assertNotExists("esm/node_modules");
1203+
assertEquals(output.packageJson.module, "./esm/mod.js");
1204+
assertEquals(
1205+
output.packageJson.exports["."].import.default,
1206+
"./esm/mod.js",
1207+
);
1208+
});
1209+
});
1210+
11831211
Deno.test("should run the test preload module", async () => {
11841212
await runTest("test_preload_project", {
11851213
entryPoints: ["mod.ts"],
@@ -1453,6 +1481,7 @@ async function runTest(
14531481
| "polyfill_promise_with_resolvers_project"
14541482
| "polyfill_import_meta_project"
14551483
| "module_mappings_project"
1484+
| "node_modules_ts_project"
14561485
| "node_types_project"
14571486
| "undici_project"
14581487
| "shim_project"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2018-2024 the Deno authors. MIT license.
2+
3+
// `type-detect` ships an `index.ts` that TypeScript's resolution lands on, so
4+
// it ends up in the program as a source file rather than a declaration file
5+
import typeDetect from "npm:type-detect@^4.1.0";
6+
7+
export function detect(value: unknown): string {
8+
return typeDetect(value);
9+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Copyright 2018-2024 the Deno authors. MIT license.
2+
3+
export const other = "other";

0 commit comments

Comments
 (0)