also looking at this
fix(generator): skip glob and relative entrypoints outside the root
#3126
3 files
+74
-2
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -210,6 +210,8 @@ entrypoints: [ | ||
| 210 | 210 | ] |
| 211 | 211 | ``` |
| 212 | 212 | |
| 213 | A file an entry point reaches outside `root` (an absolute path, or a pattern such as `../*.ts`) is skipped with a warning, since its declaration would be written outside `outdir`. With `outputStructure: 'flat'` it is written into `outdir` under its file name. | |
| 214 | ||
| 213 | 215 | ### Output Structure |
| 214 | 216 | |
| 215 | 217 | Choose between mirroring source structure or flat output: |
| @@ -801,9 +801,15 @@ async function findFiles(config: DtsGenerationConfig): Promise<string[]> { | ||
| 801 | 801 | absolute: true, |
| 802 | 802 | onlyFiles: true, |
| 803 | 803 | })) { |
| 804 | // Skip .d.ts files, node_modules, and excluded patterns | |
| 804 | // Skip .d.ts files, node_modules, and excluded patterns. A pattern | |
| 805 | // reaching above the root (`../*.ts`, `../index.ts`) matches files | |
| 806 | // whose declarations would be written outside outdir; they are | |
| 807 | // skipped as an absolute path outside the root is. | |
| 805 | 808 | if (isProcessableSourceFile(file) && !file.includes('node_modules')) { |
| 806 | if (!isExcluded(file, excludePatterns, rootPath)) { | |
| 809 | if (!isWithinRoot(file, rootPath) && config.outputStructure !== 'flat') { | |
| 810 | logger.warn(`Skipping entrypoint outside root: ${relative(config.cwd, file)}`) | |
| 811 | } | |
| 812 | else if (!isExcluded(file, excludePatterns, rootPath)) { | |
| 807 | 813 | files.push(file) |
| 808 | 814 | } |
| 809 | 815 | } |
| @@ -0,0 +1,64 @@ | ||
| 1 | /** | |
| 2 | * An entrypoint pattern reaching above the root wrote its declarations outside | |
| 3 | * outdir: | |
| 4 | * | |
| 5 | * generate({ root: 'src', outdir: 'dist', entrypoints: ['../*.ts'] }) | |
| 6 | * | |
| 7 | * wrote `outside.d.ts` next to `outside.ts`, and `../outside.ts` did the same. | |
| 8 | * An absolute path outside the root was already skipped with a warning; a | |
| 9 | * glob or relative pattern now is too. | |
| 10 | */ | |
| 11 | import { afterEach, describe, expect, it } from 'bun:test' | |
| 12 | import { existsSync } from 'node:fs' | |
| 13 | import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises' | |
| 14 | import { tmpdir } from 'node:os' | |
| 15 | import { join } from 'node:path' | |
| 16 | import { generate } from '../src/generator' | |
| 17 | ||
| 18 | const tempDirectories: string[] = [] | |
| 19 | ||
| 20 | afterEach(async () => { | |
| 21 | await Promise.all(tempDirectories.splice(0).map(dir => rm(dir, { recursive: true, force: true }))) | |
| 22 | }) | |
| 23 | ||
| 24 | /** A project with `src/inside.ts` and `outside.ts` next to `src`. */ | |
| 25 | async function project(): Promise<string> { | |
| 26 | const cwd = await mkdtemp(join(tmpdir(), 'dtsx-outside-root-')) | |
| 27 | tempDirectories.push(cwd) | |
| 28 | await mkdir(join(cwd, 'src')) | |
| 29 | await writeFile(join(cwd, 'src', 'inside.ts'), 'export const inside = 1\n') | |
| 30 | await writeFile(join(cwd, 'outside.ts'), 'export const outside = 2\n') | |
| 31 | return cwd | |
| 32 | } | |
| 33 | ||
| 34 | describe('entrypoints outside the root', () => { | |
| 35 | it.each([ | |
| 36 | ['glob', ['../*.ts']], | |
| 37 | ['relative path', ['../outside.ts']], | |
| 38 | ])('are not written outside outdir: %s', async (_label, entrypoints) => { | |
| 39 | const cwd = await project() | |
| 40 | await expect(generate({ cwd, root: 'src', outdir: 'dist', entrypoints, clean: false })).rejects.toThrow('No source files matched') | |
| 41 | expect(existsSync(join(cwd, 'outside.d.ts'))).toBe(false) | |
| 42 | expect(existsSync(join(cwd, 'dist', 'outside.d.ts'))).toBe(false) | |
| 43 | }) | |
| 44 | ||
| 45 | it('are skipped beside an entrypoint inside the root', async () => { | |
| 46 | const cwd = await project() | |
| 47 | await generate({ cwd, root: 'src', outdir: 'dist', entrypoints: ['*.ts', '../*.ts'], clean: false }) | |
| 48 | expect(existsSync(join(cwd, 'outside.d.ts'))).toBe(false) | |
| 49 | expect(existsSync(join(cwd, 'dist', 'outside.d.ts'))).toBe(false) | |
| 50 | expect(existsSync(join(cwd, 'dist', 'inside.d.ts'))).toBe(true) | |
| 51 | }) | |
| 52 | ||
| 53 | it('are written into outdir with a flat output structure', async () => { | |
| 54 | const cwd = await project() | |
| 55 | await generate({ cwd, root: 'src', outdir: 'dist', entrypoints: ['../*.ts'], outputStructure: 'flat', clean: false }) | |
| 56 | expect(existsSync(join(cwd, 'outside.d.ts'))).toBe(false) | |
| 57 | expect(existsSync(join(cwd, 'dist', 'outside.d.ts'))).toBe(true) | |
| 58 | }) | |
| 59 | ||
| 60 | it('still fail when an exclude leaves nothing', async () => { | |
| 61 | const cwd = await project() | |
| 62 | await expect(generate({ cwd, root: 'src', outdir: 'dist', entrypoints: ['../*.ts'], exclude: ['../*.ts'], outputStructure: 'flat', clean: false })).rejects.toThrow('No source files matched') | |
| 63 | }) | |
| 64 | }) | |