From ee9ad8748115730ad41de38bbd85c3d7e9afbd50 Mon Sep 17 00:00:00 2001 From: Kevin Deng Date: Tue, 14 Apr 2026 17:21:07 +0900 Subject: [PATCH 1/2] feat(exports): add bin to publishConfig when devExports is enabled When `devExports` is enabled, `exports` puts source paths in the main field and dist paths in `publishConfig.exports`. However, `bin` only wrote dist paths directly without the same dev/publish split. Add a `devExports` parameter to `generateBin`. When true, it returns source paths instead of dist paths. Call `generateBin` twice in `generateExports` (mirroring the `genSubExport` pattern): once with `devExports` for the main `bin` field, and once with `false` for `publishBin`. Write `publishBin` to `publishConfig.bin` in `writeExports`. Closes #908 --- src/features/pkg/exports.test.ts | 94 ++++++++++++++++++++++++++++++++ src/features/pkg/exports.ts | 49 +++++++++++++---- 2 files changed, 131 insertions(+), 12 deletions(-) diff --git a/src/features/pkg/exports.test.ts b/src/features/pkg/exports.test.ts index d4903adc1..9dc104966 100644 --- a/src/features/pkg/exports.test.ts +++ b/src/features/pkg/exports.test.ts @@ -43,6 +43,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -66,6 +67,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -87,6 +89,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -108,6 +111,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -129,6 +133,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -153,6 +158,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": "./foo.cjs", "module": "./foo.js", + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -177,6 +183,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": "./foo.cjs", "module": "./foo.js", + "publishBin": undefined, "publishExports": undefined, "types": "./foo.d.cts", } @@ -201,6 +208,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": "./index.cjs", "module": "./index.mjs", + "publishBin": undefined, "publishExports": undefined, "types": "./index.d.cts", } @@ -255,6 +263,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": "./index.cjs", "module": "./index.js", + "publishBin": undefined, "publishExports": { ".": { "import": "./index.js", @@ -351,6 +360,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -378,6 +388,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -401,6 +412,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -427,6 +439,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": "./index.cjs", "module": "./index.js", + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -453,6 +466,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -481,6 +495,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -509,6 +524,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -538,6 +554,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -586,6 +603,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": "./index.cjs", "module": "./index.js", + "publishBin": undefined, "publishExports": undefined, "types": "./index.d.cts", } @@ -611,6 +629,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -637,6 +656,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -662,6 +682,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -687,6 +708,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -866,6 +888,73 @@ describe('generateExports', () => { }) }) + test('bin: object form with devExports', async ({ expect }) => { + const results = generateExports( + { + es: [ + genChunk( + 'cli.js', + true, + path.resolve('./src/cli.ts'), + '#!/usr/bin/env node\n', + ), + ], + }, + { + exports: { + devExports: true, + bin: { + mycli: './src/cli.ts', + }, + }, + }, + ) + await expect(results).resolves.toMatchObject({ + bin: { mycli: './src/cli.ts' }, + publishBin: { mycli: './cli.js' }, + }) + }) + + test('bin: true with devExports', async ({ expect }) => { + const results = generateExports( + { + es: [ + genChunk( + 'cli.js', + true, + path.resolve('./src/cli.ts'), + '#!/usr/bin/env node\nconsole.log("hello")', + ), + ], + }, + { exports: { devExports: true, bin: true } }, + ) + await expect(results).resolves.toMatchObject({ + bin: { 'fake-pkg': './src/cli.ts' }, + publishBin: { 'fake-pkg': './cli.js' }, + }) + }) + + test('bin: string form with devExports', async ({ expect }) => { + const results = generateExports( + { + es: [ + genChunk( + 'cli.js', + true, + path.resolve('./src/cli.ts'), + '#!/usr/bin/env node\n', + ), + ], + }, + { exports: { devExports: true, bin: './src/cli.ts' } }, + ) + await expect(results).resolves.toMatchObject({ + bin: { 'fake-pkg': './src/cli.ts' }, + publishBin: { 'fake-pkg': './cli.js' }, + }) + }) + test('extensions adds .js to subpath export keys', async ({ expect }) => { const results = generateExports( { @@ -887,6 +976,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -913,6 +1003,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -946,6 +1037,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": "./index.cjs", "module": "./index.js", + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -971,6 +1063,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": undefined, "types": undefined, } @@ -1044,6 +1137,7 @@ describe('generateExports', () => { "inlinedDependencies": undefined, "main": undefined, "module": undefined, + "publishBin": undefined, "publishExports": { ".": "./index.js", "./package.json": "./package.json", diff --git a/src/features/pkg/exports.ts b/src/features/pkg/exports.ts index a3a94cbc7..706bcc741 100644 --- a/src/features/pkg/exports.ts +++ b/src/features/pkg/exports.ts @@ -135,12 +135,8 @@ export async function writeExports( typeAssert(options.pkg) const { pkg } = options - const { publishExports, bin, ...generated } = await generateExports( - pkg, - chunks, - options, - inlinedDeps, - ) + const { publishExports, publishBin, bin, ...generated } = + await generateExports(pkg, chunks, options, inlinedDeps) const updatedPkg = { ...pkg, @@ -149,9 +145,14 @@ export async function writeExports( packageJsonPath: undefined, } - if (publishExports) { + if (publishExports || publishBin) { updatedPkg.publishConfig ||= {} - updatedPkg.publishConfig.exports = publishExports + if (publishExports) { + updatedPkg.publishConfig.exports = publishExports + } + if (publishBin) { + updatedPkg.publishConfig.bin = publishBin + } } const original = readFileSync(pkg.packageJsonPath, 'utf8') @@ -186,6 +187,7 @@ export async function generateExports( types: string | undefined exports: Record bin?: string | Record + publishBin?: string | Record inlinedDependencies?: Record publishExports?: Record }> { @@ -347,12 +349,27 @@ export async function generateExports( } } + const binResult = generateBin( + bin, + !!devExports, + pkg, + chunks, + pkgRoot, + logger, + cwd, + ) + const publishBin = + devExports && binResult + ? generateBin(bin, false, pkg, chunks, pkgRoot, logger, cwd) + : undefined + return { main: legacy ? main || module || pkg.main : undefined, module: legacy ? module || pkg.module : undefined, types: legacy ? cjsTypes || esmTypes || pkg.types : pkg.types, exports, - bin: generateBin(bin, pkg, chunks, pkgRoot, logger, cwd), + bin: binResult, + publishBin, inlinedDependencies: emitInlinedDeps ? inlinedDeps : undefined, publishExports, } @@ -447,6 +464,7 @@ const RE_SHEBANG = /^#!.*/ function generateBin( bin: ExportsOptions['bin'], + devExports: boolean, pkg: PackageJson, chunks: ChunksByFormat, pkgRoot: string, @@ -482,7 +500,9 @@ function generateBin( 'Multiple entry chunks with shebangs found. Use `exports.bin: { name: "./src/file.ts" }` to specify which one to use.', ) } - detected = join(pkgRoot, chunk.outDir, slash(chunk.fileName)) + detected = devExports + ? `./${slash(path.relative(pkgRoot, chunk.facadeModuleId))}` + : join(pkgRoot, chunk.outDir, slash(chunk.fileName)) } } @@ -500,7 +520,7 @@ function generateBin( if (!match) { throw new Error(`Could not find output chunk for bin entry "${bin}"`) } - return { [binName]: match } + return { [binName]: devExports ? normalizeSource(bin) : match } } } @@ -513,10 +533,15 @@ function generateBin( `Could not find output chunk for bin entry "${cmdName}": "${sourcePath}"`, ) } - result[cmdName] = match + result[cmdName] = devExports ? normalizeSource(sourcePath) : match } return result + function normalizeSource(sourcePath: string): string { + const resolved = path.resolve(cwd, sourcePath) + return `./${slash(path.relative(pkgRoot, resolved))}` + } + function findChunkBySource(sourcePath: string): string | undefined { const resolved = path.resolve(cwd, sourcePath) From 6a93d31b486502f70dd35e8c399f8009ed07475e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 08:22:40 +0000 Subject: [PATCH 2/2] [autofix.ci] apply automated fixes --- package.json | 5 ++++- packages/create-tsdown/package.json | 5 ++++- packages/migrate/package.json | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6780036a0..557f7f244 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ } }, "bin": { - "tsdown": "./dist/run.mjs" + "tsdown": "./src/run.ts" }, "files": [ "client.d.ts", @@ -66,6 +66,9 @@ "./run": "./dist/run.mjs", "./package.json": "./package.json", "./client": "./client.d.ts" + }, + "bin": { + "tsdown": "./dist/run.mjs" } }, "engines": { diff --git a/packages/create-tsdown/package.json b/packages/create-tsdown/package.json index 1549b06fe..00eccf59d 100644 --- a/packages/create-tsdown/package.json +++ b/packages/create-tsdown/package.json @@ -36,7 +36,7 @@ } }, "bin": { - "create-tsdown": "./dist/run.mjs" + "create-tsdown": "./src/run.ts" }, "files": [ "dist" @@ -47,6 +47,9 @@ ".": "./dist/index.mjs", "./run": "./dist/run.mjs", "./package.json": "./package.json" + }, + "bin": { + "create-tsdown": "./dist/run.mjs" } }, "engines": { diff --git a/packages/migrate/package.json b/packages/migrate/package.json index 15d7c58a7..c930a768f 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -36,7 +36,7 @@ } }, "bin": { - "tsdown-migrate": "./dist/run.mjs" + "tsdown-migrate": "./src/run.ts" }, "files": [ "dist" @@ -47,6 +47,9 @@ ".": "./dist/index.mjs", "./run": "./dist/run.mjs", "./package.json": "./package.json" + }, + "bin": { + "tsdown-migrate": "./dist/run.mjs" } }, "engines": {