Skip to content

Commit d49ef37

Browse files
committed
feat(exe)!: add exe.outDir for separate executable output dir, defaults to build
1 parent 8869180 commit d49ef37

File tree

4 files changed

+36
-7
lines changed

4 files changed

+36
-7
lines changed

dts.snapshot.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@
127127
"DepPlugin": "declare function DepPlugin(_: ResolvedConfig, _: TsdownBundle): Plugin",
128128
"DepsConfig": "interface DepsConfig {\n neverBundle?: ExternalOption\n alwaysBundle?: Arrayable<string | RegExp> | NoExternalFn\n onlyAllowBundle?: Arrayable<string | RegExp> | false\n skipNodeModulesBundle?: boolean\n}",
129129
"DevtoolsOptions": "interface DevtoolsOptions extends NonNullable<InputOptions['devtools']> {\n ui?: boolean | Partial<StartOptions>\n clean?: boolean\n}",
130-
"ExeOptions": "interface ExeOptions extends ExeExtensionOptions {\n seaConfig?: Omit<SeaConfig, 'main' | 'output' | 'mainFormat'>\n fileName?: string | ((_: RolldownChunk) => string)\n}",
130+
"ExeOptions": "interface ExeOptions extends ExeExtensionOptions {\n seaConfig?: Omit<SeaConfig, 'main' | 'output' | 'mainFormat'>\n fileName?: string | ((_: RolldownChunk) => string)\n outDir?: string\n}",
131131
"ExportsOptions": "interface ExportsOptions {\n devExports?: boolean | string\n packageJson?: boolean\n all?: boolean\n exclude?: (RegExp | string)[]\n legacy?: boolean\n customExports?: Record<string, any> | ((_: Record<string, any>, _: { pkg: PackageJson; chunks: ChunksByFormat; isPublish: boolean }) => Awaitable<Record<string, any>>)\n inlinedDependencies?: boolean\n}",
132132
"Format": "type Format = ModuleFormat",
133133
"InlineConfig": "interface InlineConfig extends UserConfig {\n config?: boolean | string\n configLoader?: 'auto' | 'native' | 'unrun'\n filter?: RegExp | Arrayable<string>\n}",

src/features/clean.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export async function cleanOutDir(configs: ResolvedConfig[]): Promise<void> {
1919
config.clean.push('node_modules/.rolldown')
2020
}
2121

22+
if (config.exe) {
23+
const exeOutDir = path.resolve(config.cwd, config.exe.outDir || 'build')
24+
config.clean.push(exeOutDir)
25+
}
26+
2227
if (!config.clean.length) continue
2328
const files = await glob(config.clean, {
2429
cwd: config.cwd,

src/features/exe.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mkdtemp, writeFile } from 'node:fs/promises'
1+
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
22
import { tmpdir } from 'node:os'
33
import path from 'node:path'
44
import process from 'node:process'
@@ -9,7 +9,7 @@ import satisfies from 'semver/functions/satisfies.js'
99
import { x } from 'tinyexec'
1010
import { formatBytes } from '../utils/format.ts'
1111
import { fsRemove, fsStat } from '../utils/fs.ts'
12-
import { importWithError } from '../utils/general.ts'
12+
import { importWithError, typeAssert } from '../utils/general.ts'
1313
import type { ResolvedConfig, RolldownChunk } from '../config/types.ts'
1414
import type { ExeExtensionOptions, ExeTarget } from '@tsdown/exe'
1515

@@ -20,6 +20,11 @@ export interface ExeOptions extends ExeExtensionOptions {
2020
* For example, do not include `.exe`, platform suffixes, or architecture suffixes.
2121
*/
2222
fileName?: string | ((chunk: RolldownChunk) => string)
23+
/**
24+
* Output directory for executables.
25+
* @default 'build'
26+
*/
27+
outDir?: string
2328
}
2429

2530
/**
@@ -176,8 +181,12 @@ async function buildSingleExe(
176181
executable?: string,
177182
target?: ExeTarget,
178183
): Promise<void> {
179-
const exe = config.exe as ExeOptions
180-
const outputPath = path.join(config.outDir, outputFile)
184+
typeAssert(config.exe)
185+
const exe = config.exe
186+
const exeOutDir = path.resolve(config.cwd, exe.outDir || 'build')
187+
188+
await mkdir(exeOutDir, { recursive: true })
189+
const outputPath = path.join(exeOutDir, outputFile)
181190
debug('Building SEA executable: %s -> %s', bundledFile, outputPath)
182191

183192
const t = performance.now()

tests/exe.test.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,26 @@ describe.runIf(nodeSupportsBuiltinSea)('exe', () => {
3838
snapshot: false,
3939
})
4040

41-
const exePath = path.join(testDir, `dist/index${suffix}`)
41+
const exePath = path.join(testDir, `build/index${suffix}`)
4242
const { stdout } = await x(exePath)
4343
expect(stdout.trim()).toBe('hello from sea')
4444
})
4545

46+
test('exe.outDir outputs executable to custom directory', async (context) => {
47+
const { testDir } = await testBuild({
48+
context,
49+
files: {
50+
'index.ts': 'console.log("hello from custom outdir")',
51+
},
52+
options: { exe: true },
53+
snapshot: false,
54+
})
55+
56+
const exePath = path.join(testDir, `build/index${suffix}`)
57+
const { stdout } = await x(exePath)
58+
expect(stdout.trim()).toBe('hello from custom outdir')
59+
})
60+
4661
test('bundles dynamic import() and executes correctly', async (context) => {
4762
const { testDir } = await testBuild({
4863
context,
@@ -64,7 +79,7 @@ describe.runIf(nodeSupportsBuiltinSea)('exe', () => {
6479
snapshot: false,
6580
})
6681

67-
const exePath = path.join(testDir, `dist/index${suffix}`)
82+
const exePath = path.join(testDir, `build/index${suffix}`)
6883
const { stdout } = await x(exePath)
6984
expect(stdout.trim()).toBe('hello from dynamic import')
7085
})

0 commit comments

Comments
 (0)