Skip to content

Commit 418d5b6

Browse files
authored
feat(exe): add experimental Node.js SEA executable bundling (#777)
* feat(exe): add experimental Node.js SEA executable bundling Add `exe` option to bundle output as a Node.js Single Executable Application (SEA). When enabled, the bundled code is compiled into a standalone executable using `--build-sea`. - New `--exe` CLI flag and `exe` config option - Auto-configures format (defaults to CJS), disables dts, disables code splitting - Supports custom SEA config (code cache, snapshot, assets, etc.) - macOS ad-hoc codesigning for Gatekeeper compatibility - Refactored ReportPlugin to accept ResolvedConfig for reuse - Added MB formatting support in formatBytes utility - Updated docs (EN/zh-CN CLI reference) and skills * add unit tests * rename * fix: mark as red
1 parent 7949216 commit 418d5b6

File tree

18 files changed

+548
-117
lines changed

18 files changed

+548
-117
lines changed

docs/reference/cli.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,19 @@ All contents of the `public` directory will be copied to your output directory (
246246
An alias for `--copy`.
247247
**Deprecated:** Please use `--copy` instead for better clarity and consistency.
248248

249+
## `--exe`
250+
251+
**[experimental]** Bundle as executable using Node.js SEA (Single Executable Applications).
252+
253+
This will bundle the output into a single executable file using Node.js SEA. Requires Node.js 25.5.0 or later, and is not supported in Bun or Deno.
254+
255+
When `exe` is enabled:
256+
257+
- The default output format changes from `esm` to `cjs` (unless ESM SEA is supported).
258+
- Declaration file generation (`dts`) is disabled by default.
259+
- Code splitting is disabled.
260+
- Only single entry points are supported.
261+
249262
## `--exports`
250263

251264
Generate the `exports`, `main`, `module`, and `types` fields in your `package.json`.

docs/zh-CN/reference/cli.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,19 @@ tsdown --copy public
246246
`--copy` 的别名。
247247
**已废弃:** 为了更清晰和一致,建议使用 `--copy` 选项。
248248

249+
## `--exe`
250+
251+
**[实验性]** 使用 Node.js SEA(单可执行应用)将输出打包为可执行文件。
252+
253+
此选项会使用 Node.js SEA 将输出打包为单个可执行文件。需要 Node.js 25.5.0 或更高版本,不支持 Bun 和 Deno 环境。
254+
255+
启用 `exe` 时:
256+
257+
- 默认输出格式从 `esm` 变更为 `cjs`(除非支持 ESM SEA)。
258+
- 默认禁用声明文件生成(`dts`)。
259+
- 禁用代码分割。
260+
- 仅支持单入口。
261+
249262
## `--exports`
250263

251264
自动生成 `package.json` 中的 `exports``main``module``types` 字段。

dts.snapshot.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"DepsConfig",
2828
"DevtoolsOptions",
2929
"DtsOptions",
30+
"ExeOptions",
3031
"ExportsOptions",
3132
"Format",
3233
"InlineConfig",
@@ -45,6 +46,7 @@
4546
"Rolldown",
4647
"RolldownChunk",
4748
"RolldownContext",
49+
"SeaConfig",
4850
"Sourcemap",
4951
"TreeshakingOptions",
5052
"TsdownBundle",
@@ -101,6 +103,7 @@
101103
"DepPlugin": "declare function DepPlugin(_: ResolvedConfig): Plugin",
102104
"DepsConfig": "interface DepsConfig {\n neverBundle?: ExternalOption\n alwaysBundle?: Arrayable<string | RegExp> | NoExternalFn\n onlyAllowBundle?: Arrayable<string | RegExp> | false\n skipNodeModulesBundle?: boolean\n}",
103105
"DevtoolsOptions": "interface DevtoolsOptions extends NonNullable<InputOptions['devtools']> {\n ui?: boolean | Partial<StartOptions>\n clean?: boolean\n}",
106+
"ExeOptions": "interface ExeOptions {\n seaConfig?: Omit<SeaConfig, 'main' | 'output' | 'mainFormat'>\n fileName?: string | ((_: RolldownChunk) => string)\n}",
104107
"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}",
105108
"Format": "type Format = ModuleFormat",
106109
"globalLogger": "Logger",
@@ -132,16 +135,17 @@
132135
"PackageType": "type PackageType = 'module' | 'commonjs' | undefined",
133136
"PublintOptions": "interface PublintOptions extends Omit<Options, 'pack' | 'pkgDir'> {}",
134137
"ReportOptions": "interface ReportOptions {\n gzip?: boolean\n brotli?: boolean\n maxCompressSize?: number\n}",
135-
"ReportPlugin": "declare function ReportPlugin(_: ReportOptions, _: Logger, _: string, _: boolean, _: string, _: boolean): Plugin",
136-
"ResolvedConfig": "type ResolvedConfig = Overwrite<MarkPartial<Omit<UserConfig, 'workspace' | 'fromVite' | 'publicDir' | 'bundle' | 'removeNodeProtocol' | 'external' | 'noExternal' | 'inlineOnly' | 'skipNodeModulesBundle' | 'logLevel' | 'failOnWarn' | 'customLogger' | 'envFile' | 'envPrefix'>, 'globalName' | 'inputOptions' | 'outputOptions' | 'minify' | 'define' | 'alias' | 'onSuccess' | 'outExtensions' | 'hooks' | 'copy' | 'loader' | 'name' | 'banner' | 'footer' | 'checks'>, { entry: Record<string, string>; nameLabel: string | undefined; format: NormalizedFormat; target?: string[]; clean: string[]; pkg?: PackageJsonWithPath; nodeProtocol: 'strip' | boolean; logger: Logger; ignoreWatch: Array<string | RegExp>; deps: ResolvedDepsConfig; css: Required<CssOptions>; dts: false | DtsOptions; report: false | ReportOptions; tsconfig: false | string; exports: false | ExportsOptions; devtools: false | DevtoolsOptions; publint: false | PublintOptions; attw: false | AttwOptions; unused: false | UnusedOptions }>",
138+
"ReportPlugin": "declare function ReportPlugin(_: ResolvedConfig, _: boolean, _: boolean): Plugin",
139+
"ResolvedConfig": "type ResolvedConfig = Overwrite<MarkPartial<Omit<UserConfig, 'workspace' | 'fromVite' | 'publicDir' | 'bundle' | 'removeNodeProtocol' | 'external' | 'noExternal' | 'inlineOnly' | 'skipNodeModulesBundle' | 'logLevel' | 'failOnWarn' | 'customLogger' | 'envFile' | 'envPrefix'>, 'globalName' | 'inputOptions' | 'outputOptions' | 'minify' | 'define' | 'alias' | 'onSuccess' | 'outExtensions' | 'hooks' | 'copy' | 'loader' | 'name' | 'banner' | 'footer' | 'checks'>, { entry: Record<string, string>; nameLabel: string | undefined; format: NormalizedFormat; target?: string[]; clean: string[]; pkg?: PackageJsonWithPath; nodeProtocol: 'strip' | boolean; logger: Logger; ignoreWatch: Array<string | RegExp>; deps: ResolvedDepsConfig; css: Required<CssOptions>; dts: false | DtsOptions; report: false | ReportOptions; tsconfig: false | string; exports: false | ExportsOptions; devtools: false | DevtoolsOptions; publint: false | PublintOptions; attw: false | AttwOptions; unused: false | UnusedOptions; exe: false | ExeOptions }>",
137140
"ResolvedDepsConfig": "interface ResolvedDepsConfig {\n neverBundle?: ExternalOption\n alwaysBundle?: NoExternalFn\n onlyAllowBundle?: Array<string | RegExp> | false\n skipNodeModulesBundle: boolean\n}",
138141
"RolldownChunk": "type RolldownChunk = (OutputChunk | OutputAsset) & { outDir: string }",
139142
"RolldownContext": "interface RolldownContext {\n buildOptions: BuildOptions\n}",
143+
"SeaConfig": "interface SeaConfig {\n main?: string\n executable?: string\n output?: string\n mainFormat?: 'commonjs' | 'module'\n disableExperimentalSEAWarning?: boolean\n useSnapshot?: boolean\n useCodeCache?: boolean\n execArgv?: string[]\n execArgvExtension?: 'none' | 'env' | 'cli'\n assets?: Record<string, string>\n}",
140144
"Sourcemap": "type Sourcemap = boolean | 'inline' | 'hidden'",
141145
"TsdownBundle": "interface TsdownBundle extends AsyncDisposable {\n chunks: RolldownChunk[]\n config: ResolvedConfig\n}",
142146
"TsdownHooks": "interface TsdownHooks {\n 'build:prepare': (_: BuildContext) => void | Promise<void>\n 'build:before': (_: BuildContext & RolldownContext) => void | Promise<void>\n 'build:done': (_: BuildContext & { chunks: RolldownChunk[] }) => void | Promise<void>\n}",
143147
"TsdownInputOption": "type TsdownInputOption = Arrayable<string | Record<string, Arrayable<string>>>",
144-
"UserConfig": "interface UserConfig {\n entry?: TsdownInputOption\n deps?: DepsConfig\n external?: ExternalOption\n noExternal?: Arrayable<string | RegExp> | NoExternalFn\n inlineOnly?: Arrayable<string | RegExp> | false\n skipNodeModulesBundle?: boolean\n alias?: Record<string, string>\n tsconfig?: string | boolean\n platform?: 'node' | 'neutral' | 'browser'\n target?: string | string[] | false\n env?: Record<string, any>\n envFile?: string\n envPrefix?: string | string[]\n define?: Record<string, string>\n shims?: boolean\n treeshake?: boolean | TreeshakingOptions\n loader?: ModuleTypes\n removeNodeProtocol?: boolean\n nodeProtocol?: 'strip' | boolean\n checks?: ChecksOptions & { legacyCjs?: boolean }\n plugins?: InputOptions['plugins']\n inputOptions?: InputOptions | ((_: InputOptions, _: NormalizedFormat, _: { cjsDts: boolean }) => Awaitable<InputOptions | void | null>)\n format?: Format | Format[] | Partial<Record<Format, Partial<ResolvedConfig>>>\n globalName?: string\n outDir?: string\n write?: boolean\n sourcemap?: Sourcemap\n clean?: boolean | string[]\n minify?: boolean | 'dce-only' | MinifyOptions\n footer?: ChunkAddon\n banner?: ChunkAddon\n unbundle?: boolean\n bundle?: boolean\n fixedExtension?: boolean\n outExtensions?: OutExtensionFactory\n hash?: boolean\n cjsDefault?: boolean\n outputOptions?: OutputOptions | ((_: OutputOptions, _: NormalizedFormat, _: { cjsDts: boolean }) => Awaitable<OutputOptions | void | null>)\n cwd?: string\n name?: string\n logLevel?: LogLevel\n failOnWarn?: boolean | CIOption\n customLogger?: Logger\n fromVite?: boolean | 'vitest'\n watch?: boolean | Arrayable<string>\n ignoreWatch?: Arrayable<string | RegExp>\n devtools?: WithEnabled<DevtoolsOptions>\n onSuccess?: string | ((_: ResolvedConfig, _: AbortSignal) => void | Promise<void>)\n dts?: WithEnabled<DtsOptions>\n unused?: WithEnabled<UnusedOptions>\n publint?: WithEnabled<PublintOptions>\n attw?: WithEnabled<AttwOptions>\n report?: WithEnabled<ReportOptions>\n globImport?: boolean\n exports?: WithEnabled<ExportsOptions>\n css?: CssOptions\n publicDir?: CopyOptions | CopyOptionsFn\n copy?: CopyOptions | CopyOptionsFn\n hooks?: Partial<TsdownHooks> | ((_: Hookable<TsdownHooks>) => Awaitable<void>)\n workspace?: Workspace | Arrayable<string> | true\n}",
148+
"UserConfig": "interface UserConfig {\n entry?: TsdownInputOption\n deps?: DepsConfig\n external?: ExternalOption\n noExternal?: Arrayable<string | RegExp> | NoExternalFn\n inlineOnly?: Arrayable<string | RegExp> | false\n skipNodeModulesBundle?: boolean\n alias?: Record<string, string>\n tsconfig?: string | boolean\n platform?: 'node' | 'neutral' | 'browser'\n target?: string | string[] | false\n env?: Record<string, any>\n envFile?: string\n envPrefix?: string | string[]\n define?: Record<string, string>\n shims?: boolean\n treeshake?: boolean | TreeshakingOptions\n loader?: ModuleTypes\n removeNodeProtocol?: boolean\n nodeProtocol?: 'strip' | boolean\n checks?: ChecksOptions & { legacyCjs?: boolean }\n plugins?: InputOptions['plugins']\n inputOptions?: InputOptions | ((_: InputOptions, _: NormalizedFormat, _: { cjsDts: boolean }) => Awaitable<InputOptions | void | null>)\n format?: Format | Format[] | Partial<Record<Format, Partial<ResolvedConfig>>>\n globalName?: string\n outDir?: string\n write?: boolean\n sourcemap?: Sourcemap\n clean?: boolean | string[]\n minify?: boolean | 'dce-only' | MinifyOptions\n footer?: ChunkAddon\n banner?: ChunkAddon\n unbundle?: boolean\n bundle?: boolean\n fixedExtension?: boolean\n outExtensions?: OutExtensionFactory\n hash?: boolean\n cjsDefault?: boolean\n outputOptions?: OutputOptions | ((_: OutputOptions, _: NormalizedFormat, _: { cjsDts: boolean }) => Awaitable<OutputOptions | void | null>)\n cwd?: string\n name?: string\n logLevel?: LogLevel\n failOnWarn?: boolean | CIOption\n customLogger?: Logger\n fromVite?: boolean | 'vitest'\n watch?: boolean | Arrayable<string>\n ignoreWatch?: Arrayable<string | RegExp>\n devtools?: WithEnabled<DevtoolsOptions>\n onSuccess?: string | ((_: ResolvedConfig, _: AbortSignal) => void | Promise<void>)\n dts?: WithEnabled<DtsOptions>\n unused?: WithEnabled<UnusedOptions>\n publint?: WithEnabled<PublintOptions>\n attw?: WithEnabled<AttwOptions>\n report?: WithEnabled<ReportOptions>\n globImport?: boolean\n exports?: WithEnabled<ExportsOptions>\n css?: CssOptions\n publicDir?: CopyOptions | CopyOptionsFn\n copy?: CopyOptions | CopyOptionsFn\n hooks?: Partial<TsdownHooks> | ((_: Hookable<TsdownHooks>) => Awaitable<void>)\n exe?: WithEnabled<ExeOptions>\n workspace?: Workspace | Arrayable<string> | true\n}",
145149
"UserConfigExport": "type UserConfigExport = Awaitable<Arrayable<UserConfig> | UserConfigFn>",
146150
"UserConfigFn": "type UserConfigFn = (_: InlineConfig, _: { ci: boolean }) => Awaitable<Arrayable<UserConfig>>",
147151
"WithEnabled": "type WithEnabled<T> = boolean | undefined | CIOption | (T & { enabled?: boolean | CIOption })",

skills/GENERATION.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ This document contains information about how these skills were generated and how
55
## Generation Details
66

77
**Generated from documentation at:**
8-
- **Commit SHA**: `301bcd140d180536a1aab922391cafe8843fd1e8`
9-
- **Short SHA**: `301bcd1`
10-
- **Date**: 2026-01-30
11-
- **Commit**: docs: add WASM support guide [ci skip] (#739)
8+
- **Commit SHA**: `7949216eb518f4c83b876f808991cf4b0cbc1561`
9+
- **Short SHA**: `7949216`
10+
- **Date**: 2026-02-22
11+
- **Commit**: feat(sea): add exe option for Node.js SEA executable bundling
1212

1313
**Source documentation:**
1414
- Main docs: `/docs` folder
1515
- Project README: `/README.md`
1616
- CLAUDE.md: `/CLAUDE.md`
1717

18-
**Generation date**: 2026-01-30
18+
**Generation date**: 2026-02-22
1919

2020
## Structure
2121

@@ -72,7 +72,7 @@ When tsdown documentation changes, follow these steps to update the skills:
7272

7373
```bash
7474
# Get changes in docs since generation
75-
git diff 301bcd1..HEAD -- docs/
75+
git diff 7949216..HEAD -- docs/
7676

7777
# List changed files
7878
git diff --name-only 301bcd1..HEAD -- docs/
@@ -116,7 +116,7 @@ Focus on these documentation areas:
116116

117117
```bash
118118
# 1. Check what docs changed
119-
git diff 301bcd1..HEAD -- docs/ > docs_changes.patch
119+
git diff 7949216..HEAD -- docs/ > docs_changes.patch
120120

121121
# 2. Review the changes
122122
cat docs_changes.patch
@@ -179,6 +179,7 @@ Based on the documentation structure, these reference files should be created:
179179
- `option-unbundle.md` - Preserve directory structure
180180
- `option-log-level.md` - Logging configuration
181181
- `option-lint.md` - Package validation (publint, attw)
182+
- `option-exe.md` - Executable bundling (Node.js SEA)
182183

183184
### Advanced Topics (6 files)
184185
- `advanced-plugins.md` - Rolldown, Rollup, Unplugin support
@@ -229,14 +230,15 @@ When updating, maintain style:
229230

230231
| Date | SHA | Changes |
231232
|------------|----------|---------|
233+
| 2026-02-22 | 7949216 | Add `exe` option for Node.js SEA executable bundling |
232234
| 2026-01-30 | 301bcd1 | Add CI environment, package validation (publint/attw), WASM support, update entry globs, sourcemap modes, failOnWarn |
233235
| 2026-01-29 | 0bf92cf | Initial generation from docs |
234236

235237
## Agent Instructions Summary
236238

237239
**For future agents updating these skills:**
238240

239-
1. Run `git diff 301bcd1..HEAD -- docs/` to see all documentation changes
241+
1. Run `git diff 7949216..HEAD -- docs/` to see all documentation changes
240242
2. Read changed files to understand what's new or modified
241243
3. Update `SKILL.md` by:
242244
- Adding new options to appropriate tables
@@ -260,5 +262,5 @@ If you're unsure about whether changes warrant updates:
260262

261263
---
262264

263-
Last updated: 2026-01-30
264-
Current SHA: 301bcd1
265+
Last updated: 2026-02-22
266+
Current SHA: 7949216

skills/tsdown/SKILL.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export default defineConfig({
9898
| Package exports | `exports: true` - Auto-generate exports field | [option-package-exports](references/option-package-exports.md) |
9999
| CSS handling | **[experimental]** Still in development | [option-css](references/option-css.md) |
100100
| Unbundle mode | `unbundle: true` - Preserve directory structure | [option-unbundle](references/option-unbundle.md) |
101+
| Executable (SEA) | **[experimental]** `exe: true` - Bundle as Node.js SEA executable | [option-exe](references/option-exe.md) |
101102
| Package validation | `publint: true`, `attw: true` - Validate package | [option-lint](references/option-lint.md) |
102103

103104
## Framework & Runtime Support
@@ -199,6 +200,15 @@ export default defineConfig({
199200
})
200201
```
201202

203+
### Node.js Executable (SEA)
204+
205+
```ts
206+
export default defineConfig({
207+
entry: ['src/cli.ts'],
208+
exe: true,
209+
})
210+
```
211+
202212
### Advanced with Hooks
203213

204214
```ts
@@ -281,6 +291,7 @@ tsdown --format esm,cjs # Multiple formats
281291
tsdown --outDir lib # Custom output directory
282292
tsdown --minify # Enable minification
283293
tsdown --dts # Generate declarations
294+
tsdown --exe # Bundle as executable (SEA)
284295

285296
# Entry options
286297
tsdown src/index.ts # Single entry
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Executable (SEA) - `exe`
2+
3+
**[experimental]** Bundle as executable using Node.js Single Executable Applications (SEA).
4+
5+
## Requirements
6+
7+
- Node.js >= 25.5.0
8+
- Not supported in Bun or Deno
9+
10+
## Basic Usage
11+
12+
```ts
13+
export default defineConfig({
14+
entry: ['src/cli.ts'],
15+
exe: true,
16+
})
17+
```
18+
19+
## Behavior When Enabled
20+
21+
- Default output format changes from `esm` to `cjs` (unless ESM SEA is supported)
22+
- Declaration file generation (`dts`) is disabled by default
23+
- Code splitting is disabled
24+
- Only single entry points are supported
25+
- Legacy CJS warnings are suppressed
26+
27+
## Advanced Configuration
28+
29+
```ts
30+
export default defineConfig({
31+
entry: ['src/cli.ts'],
32+
exe: {
33+
fileName: 'my-tool',
34+
seaConfig: {
35+
disableExperimentalSEAWarning: true,
36+
useCodeCache: true,
37+
useSnapshot: false,
38+
},
39+
},
40+
})
41+
```
42+
43+
## `ExeOptions`
44+
45+
| Option | Type | Description |
46+
|--------|------|-------------|
47+
| `seaConfig` | `Omit<SeaConfig, 'main' \| 'output'>` | Node.js SEA configuration options |
48+
| `fileName` | `string \| ((chunk) => string)` | Custom output file name for the executable |
49+
50+
## `SeaConfig`
51+
52+
See [Node.js SEA Documentation](https://nodejs.org/api/single-executable-applications.html#generating-single-executable-applications-with---build-sea).
53+
54+
| Option | Type | Default | Description |
55+
|--------|------|---------|-------------|
56+
| `disableExperimentalSEAWarning` | `boolean` | `true` | Disable the experimental SEA warning |
57+
| `useSnapshot` | `boolean` | `false` | Use V8 snapshot |
58+
| `useCodeCache` | `boolean` | `true` | Use V8 code cache |
59+
| `execArgv` | `string[]` | - | Extra Node.js arguments |
60+
| `execArgvExtension` | `'none' \| 'env' \| 'cli'` | `'env'` | How to extend execArgv |
61+
| `assets` | `Record<string, string>` | - | Assets to embed |
62+
63+
## Platform Notes
64+
65+
- On macOS, the executable is automatically codesigned (ad-hoc) for Gatekeeper compatibility
66+
- On Windows, the `.exe` extension is automatically appended
67+
68+
## CLI
69+
70+
```bash
71+
tsdown --exe
72+
tsdown src/cli.ts --exe
73+
```

skills/tsdown/references/reference-cli.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,22 @@ tsdown --copy public
254254
tsdown --copy assets --copy static
255255
```
256256

257+
## Executable
258+
259+
### `--exe`
260+
261+
**[experimental]** Bundle as executable using Node.js SEA (Single Executable Applications). Requires Node.js >= 25.5.0, not supported in Bun or Deno.
262+
263+
```bash
264+
tsdown --exe
265+
```
266+
267+
When enabled:
268+
- Default format changes to `cjs` (unless ESM SEA is supported)
269+
- Declaration file generation (`dts`) is disabled by default
270+
- Code splitting is disabled
271+
- Only single entry points are supported
272+
257273
## Package Management
258274

259275
### `--exports`
@@ -366,6 +382,12 @@ tsdown --format iife --platform browser --minify
366382
tsdown --format esm --platform node --shims
367383
```
368384

385+
### Node.js Executable (SEA)
386+
387+
```bash
388+
tsdown src/cli.ts --exe
389+
```
390+
369391
### Monorepo Package
370392

371393
```bash

src/build.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { warnLegacyCJS } from './features/cjs.ts'
1515
import { cleanChunks, cleanOutDir } from './features/clean.ts'
1616
import { copy } from './features/copy.ts'
1717
import { startDevtoolsUI } from './features/devtools.ts'
18+
import { buildExe } from './features/exe.ts'
1819
import { createHooks, executeOnSuccess } from './features/hooks.ts'
1920
import { bundleDone, initBundleByPkg } from './features/pkg/index.ts'
2021
import {
@@ -150,7 +151,7 @@ async function buildSingle(
150151
postBuild().catch((error) => logger.error(error))
151152
}, 100)
152153

153-
let updated = false
154+
let hasBuilt = false
154155
const bundle: TsdownBundle = {
155156
chunks,
156157
config,
@@ -291,12 +292,13 @@ async function buildSingle(
291292

292293
async function postBuild() {
293294
await copy(config)
294-
if (!updated) {
295+
await buildExe(config, chunks)
296+
if (!hasBuilt) {
295297
await done(bundle)
296298
}
297299

298300
await hooks.callHook('build:done', { ...context, chunks })
299-
updated = true
301+
hasBuilt = true
300302

301303
ab?.abort()
302304
ab = executeOnSuccess(config)

src/cli.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ cli
7070
.option('--public-dir <dir>', 'Alias for --copy, deprecated')
7171
.option('--tsconfig <tsconfig>', 'Set tsconfig path')
7272
.option('--unbundle', 'Unbundle mode')
73+
.option('--exe', 'Bundle as executable')
7374
.option('-W, --workspace [dir]', 'Enable workspace mode')
7475
.option(
7576
'-F, --filter <pattern>',

0 commit comments

Comments
 (0)