@kidd-cli/bundler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Joggr, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,300 @@
1
+ import { AsyncResult, Result } from "@kidd-cli/utils/fp";
2
+ import { InlineConfig, Rolldown } from "tsdown";
3
+ import { BuildOptions, CompileOptions, CompileOptions as CompileOptions$1, CompileTarget, CompileTarget as CompileTarget$1, KiddConfig, KiddConfig as KiddConfig$1 } from "@kidd-cli/config";
4
+
5
+ //#region src/autoload-plugin.d.ts
6
+ /**
7
+ * Parameters for creating the autoload plugin.
8
+ */
9
+ interface CreateAutoloadPluginParams {
10
+ readonly commandsDir: string;
11
+ readonly tagModulePath: string;
12
+ }
13
+ /**
14
+ * Create a rolldown plugin that replaces the runtime autoloader with a static version.
15
+ *
16
+ * Uses a three-hook approach to break the circular dependency between kidd's
17
+ * dist and user command files (which `import { command } from 'kidd'`):
18
+ *
19
+ * 1. `transform` — detects kidd's pre-bundled dist and replaces the autoloader
20
+ * region with a dynamic `import()` to a virtual module
21
+ * 2. `resolveId` — resolves the virtual module identifier
22
+ * 3. `load` — scans the commands directory and generates a static autoloader
23
+ * module with all command imports pre-resolved
24
+ *
25
+ * The dynamic import ensures command files execute after kidd's code is fully
26
+ * initialized, avoiding `ReferenceError` from accessing `TAG` before its
27
+ * declaration.
28
+ *
29
+ * @param params - The commands directory and tag module path.
30
+ * @returns A rolldown plugin for static autoloading.
31
+ */
32
+ declare function createAutoloadPlugin(params: CreateAutoloadPluginParams): Rolldown.Plugin;
33
+ //#endregion
34
+ //#region src/types.d.ts
35
+ /**
36
+ * Fully resolved build options with all defaults applied.
37
+ */
38
+ interface ResolvedBuildOptions {
39
+ readonly target: string;
40
+ readonly minify: boolean;
41
+ readonly sourcemap: boolean;
42
+ readonly external: readonly string[];
43
+ }
44
+ /**
45
+ * Fully resolved compile options with all defaults applied.
46
+ */
47
+ interface ResolvedCompileOptions {
48
+ readonly targets: readonly CompileTarget[];
49
+ readonly name: string;
50
+ }
51
+ /**
52
+ * Fully resolved bundler configuration with absolute paths and all defaults filled.
53
+ */
54
+ interface ResolvedBundlerConfig {
55
+ readonly entry: string;
56
+ readonly commands: string;
57
+ readonly buildOutDir: string;
58
+ readonly compileOutDir: string;
59
+ readonly build: ResolvedBuildOptions;
60
+ readonly compile: ResolvedCompileOptions;
61
+ readonly include: readonly string[];
62
+ readonly cwd: string;
63
+ }
64
+ /**
65
+ * Synchronous result from a bundler operation.
66
+ */
67
+ type BundlerResult<T> = Result<T, Error>;
68
+ /**
69
+ * Asynchronous result from a bundler operation.
70
+ */
71
+ type AsyncBundlerResult<T> = AsyncResult<T, Error>;
72
+ /**
73
+ * Output of a successful build operation.
74
+ */
75
+ interface BuildOutput {
76
+ readonly outDir: string;
77
+ readonly entryFile: string;
78
+ }
79
+ /**
80
+ * A single compiled binary for a specific target platform.
81
+ */
82
+ interface CompiledBinary {
83
+ readonly target: CompileTarget;
84
+ readonly label: string;
85
+ readonly path: string;
86
+ }
87
+ /**
88
+ * Output of a successful compile operation.
89
+ */
90
+ interface CompileOutput {
91
+ readonly binaries: readonly CompiledBinary[];
92
+ }
93
+ /**
94
+ * Parameters for the build function.
95
+ */
96
+ interface BuildParams {
97
+ readonly config: KiddConfig;
98
+ readonly cwd: string;
99
+ }
100
+ /**
101
+ * Parameters for the watch function.
102
+ */
103
+ interface WatchParams {
104
+ readonly config: KiddConfig;
105
+ readonly cwd: string;
106
+ readonly onSuccess?: () => void | Promise<void>;
107
+ }
108
+ /**
109
+ * Parameters for the compile function.
110
+ */
111
+ interface CompileParams {
112
+ readonly config: KiddConfig;
113
+ readonly cwd: string;
114
+ readonly onTargetStart?: (target: CompileTarget) => void | Promise<void>;
115
+ readonly onTargetComplete?: (target: CompileTarget) => void | Promise<void>;
116
+ }
117
+ /**
118
+ * A single command file discovered during a directory scan.
119
+ */
120
+ interface ScannedFile {
121
+ readonly name: string;
122
+ readonly filePath: string;
123
+ }
124
+ /**
125
+ * A subdirectory of commands discovered during a directory scan.
126
+ */
127
+ interface ScannedDir {
128
+ readonly name: string;
129
+ readonly index?: string;
130
+ readonly files: readonly ScannedFile[];
131
+ readonly dirs: readonly ScannedDir[];
132
+ }
133
+ /**
134
+ * The result of scanning a commands directory tree.
135
+ */
136
+ interface ScanResult {
137
+ readonly files: readonly ScannedFile[];
138
+ readonly dirs: readonly ScannedDir[];
139
+ }
140
+ //#endregion
141
+ //#region src/build.d.ts
142
+ /**
143
+ * Build a kidd CLI tool using tsdown.
144
+ *
145
+ * Resolves defaults, maps the config to tsdown's InlineConfig, and invokes the build.
146
+ *
147
+ * @param params - The build parameters including config and working directory.
148
+ * @returns A result tuple with build output on success or an Error on failure.
149
+ */
150
+ declare function build(params: BuildParams): AsyncBundlerResult<BuildOutput>;
151
+ //#endregion
152
+ //#region src/compile.d.ts
153
+ /**
154
+ * Compile a kidd CLI tool into standalone binaries using `bun build --compile`.
155
+ *
156
+ * Expects the bundled entry to already exist in `outDir` (i.e., `build()` must
157
+ * be run first). For each requested target (or the current platform if none
158
+ * specified), spawns `bun build --compile` to produce a self-contained binary.
159
+ *
160
+ * @param params - The compile parameters including config and working directory.
161
+ * @returns A result tuple with compile output on success or an Error on failure.
162
+ */
163
+ declare function compile(params: CompileParams): AsyncResult<CompileOutput>;
164
+ /**
165
+ * Look up the human-readable label for a compile target.
166
+ *
167
+ * @param target - The compile target identifier.
168
+ * @returns A descriptive label (e.g., "macOS Apple Silicon").
169
+ */
170
+ declare function resolveTargetLabel(target: CompileTarget$1): string;
171
+ //#endregion
172
+ //#region src/generate-autoloader.d.ts
173
+ /**
174
+ * Parameters for generating a static autoloader module.
175
+ */
176
+ interface GenerateStaticAutoloaderParams {
177
+ readonly scan: ScanResult;
178
+ readonly tagModulePath: string;
179
+ }
180
+ /**
181
+ * The two parts of a static autoloader transform: import statements to
182
+ * prepend and the replacement code for the autoloader region.
183
+ */
184
+ interface StaticAutoloaderParts {
185
+ readonly imports: string;
186
+ readonly region: string;
187
+ }
188
+ /**
189
+ * Generate JavaScript source code for a static autoloader virtual module.
190
+ *
191
+ * The generated module statically imports every discovered command file and
192
+ * exports an `autoload()` function that returns the pre-built CommandMap.
193
+ * Directory commands that merge a parent handler with subcommands are re-tagged
194
+ * via `withTag` because the TAG symbol is non-enumerable and lost on spread.
195
+ *
196
+ * @param params - The scan result and path to the tag utility module.
197
+ * @returns JavaScript source code for the static autoloader.
198
+ */
199
+ declare function generateStaticAutoloader(params: GenerateStaticAutoloaderParams): string;
200
+ /**
201
+ * Generate the two parts needed to transform kidd's bundled dist.
202
+ *
203
+ * Returns an empty imports string (no prepended imports needed) and a
204
+ * replacement autoloader region that uses dynamic `import()` calls inside
205
+ * the async `autoload()` function. This avoids circular dependency issues:
206
+ * command files import `command` from `kidd`, so static imports would be
207
+ * hoisted above kidd's own initialization code, causing `TAG` to be
208
+ * accessed before initialization.
209
+ *
210
+ * By deferring to dynamic imports, kidd fully initializes first, then
211
+ * command files are loaded when `autoload()` is called at CLI startup.
212
+ *
213
+ * @param params - The scan result and path to the tag utility module.
214
+ * @returns Import statements (empty) and the replacement autoloader region.
215
+ */
216
+ declare function generateAutoloaderParts(params: GenerateStaticAutoloaderParams): StaticAutoloaderParts;
217
+ //#endregion
218
+ //#region src/map-config.d.ts
219
+ /**
220
+ * Map a resolved bundler config to a tsdown InlineConfig for production builds.
221
+ *
222
+ * @param config - The fully resolved bundler config.
223
+ * @returns A tsdown InlineConfig ready for `build()`.
224
+ */
225
+ declare function mapToBuildConfig(config: ResolvedBundlerConfig): InlineConfig;
226
+ /**
227
+ * Map a resolved bundler config to a tsdown InlineConfig for watch mode.
228
+ *
229
+ * @param params - The resolved config and optional success callback.
230
+ * @returns A tsdown InlineConfig with `watch: true`.
231
+ */
232
+ declare function mapToWatchConfig(params: {
233
+ readonly config: ResolvedBundlerConfig;
234
+ readonly onSuccess?: () => void | Promise<void>;
235
+ }): InlineConfig;
236
+ //#endregion
237
+ //#region src/resolve-config.d.ts
238
+ /**
239
+ * Normalize the `compile` config field from `boolean | CompileOptions | undefined` to `CompileOptions`.
240
+ *
241
+ * - `true` → `{}` (compile with defaults)
242
+ * - `false` / `undefined` → `{}` (no explicit options, caller decides whether to compile)
243
+ * - object → pass through
244
+ *
245
+ * @param value - The raw compile config value.
246
+ * @returns A normalized CompileOptions object.
247
+ */
248
+ declare function normalizeCompileOptions(value: boolean | CompileOptions$1 | undefined): CompileOptions$1;
249
+ /**
250
+ * Fill defaults and resolve relative paths against `cwd`.
251
+ *
252
+ * This is a pure function — the incoming config is already validated by `@kidd-cli/config`.
253
+ * It only fills missing optional fields with defaults and resolves paths to absolute.
254
+ *
255
+ * @param params - The raw config and working directory.
256
+ * @returns A fully resolved bundler configuration.
257
+ */
258
+ declare function resolveConfig(params: {
259
+ readonly config: KiddConfig$1;
260
+ readonly cwd: string;
261
+ }): ResolvedBundlerConfig;
262
+ /**
263
+ * Detect the bundled entry file in a build output directory.
264
+ *
265
+ * tsdown may produce `index.mjs` or `index.js` depending on the project's
266
+ * `package.json` `type` field and tsdown configuration. This function checks
267
+ * for both candidates and returns the first one that exists on disk.
268
+ *
269
+ * @param outDir - Absolute path to the build output directory.
270
+ * @returns The absolute path to the entry file, or `undefined` when none is found.
271
+ */
272
+ declare function detectBuildEntry(outDir: string): string | undefined;
273
+ //#endregion
274
+ //#region src/scan-commands.d.ts
275
+ /**
276
+ * Scan a commands directory and produce a tree structure for static code generation.
277
+ *
278
+ * Mirrors the runtime autoloader's rules: valid extensions are `.ts`, `.js`, `.mjs`;
279
+ * files and directories starting with `_` or `.` are skipped; `index` files in
280
+ * subdirectories become parent command handlers.
281
+ *
282
+ * @param dir - Absolute path to the commands directory.
283
+ * @returns A tree of scanned files and directories.
284
+ */
285
+ declare function scanCommandsDir(dir: string): Promise<ScanResult>;
286
+ //#endregion
287
+ //#region src/watch.d.ts
288
+ /**
289
+ * Start a watch-mode build for a kidd CLI tool using tsdown.
290
+ *
291
+ * The returned promise resolves only when tsdown's watch terminates (typically on process exit).
292
+ * tsdown's `build()` with `watch: true` runs indefinitely.
293
+ *
294
+ * @param params - The watch parameters including config, working directory, and optional success callback.
295
+ * @returns A result tuple with void on success or an Error on failure.
296
+ */
297
+ declare function watch(params: WatchParams): AsyncBundlerResult<void>;
298
+ //#endregion
299
+ export { type AsyncBundlerResult, type BuildOptions, type BuildOutput, type BuildParams, type BundlerResult, type CompileOptions, type CompileOutput, type CompileParams, type CompileTarget, type CompiledBinary, type KiddConfig, type ResolvedBuildOptions, type ResolvedBundlerConfig, type ResolvedCompileOptions, type ScanResult, type ScannedDir, type ScannedFile, type StaticAutoloaderParts, type WatchParams, build, compile, createAutoloadPlugin, detectBuildEntry, generateAutoloaderParts, generateStaticAutoloader, mapToBuildConfig, mapToWatchConfig, normalizeCompileOptions, resolveConfig, resolveTargetLabel, scanCommandsDir, watch };
300
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/autoload-plugin.ts","../src/types.ts","../src/build.ts","../src/compile.ts","../src/generate-autoloader.ts","../src/map-config.ts","../src/resolve-config.ts","../src/scan-commands.ts","../src/watch.ts"],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,849 @@
1
+ import { builtinModules, createRequire } from "node:module";
2
+ import { readdir } from "node:fs/promises";
3
+ import { basename, extname, join, resolve } from "node:path";
4
+ import { err, ok } from "@kidd-cli/utils/fp";
5
+ import { build as build$1 } from "tsdown";
6
+ import { existsSync, readdirSync, unlinkSync } from "node:fs";
7
+ import { execFile } from "node:child_process";
8
+
9
+ //#region src/generate-autoloader.ts
10
+ /**
11
+ * Generate JavaScript source code for a static autoloader virtual module.
12
+ *
13
+ * The generated module statically imports every discovered command file and
14
+ * exports an `autoload()` function that returns the pre-built CommandMap.
15
+ * Directory commands that merge a parent handler with subcommands are re-tagged
16
+ * via `withTag` because the TAG symbol is non-enumerable and lost on spread.
17
+ *
18
+ * @param params - The scan result and path to the tag utility module.
19
+ * @returns JavaScript source code for the static autoloader.
20
+ */
21
+ function generateStaticAutoloader(params) {
22
+ const importLines = buildImportStatements(collectImports(params.scan), params.tagModulePath);
23
+ const commandsObject = buildCommandsObject(params.scan);
24
+ return [
25
+ ...importLines,
26
+ "",
27
+ `const commands = ${commandsObject}`,
28
+ "",
29
+ "export async function autoload() {",
30
+ " return commands",
31
+ "}",
32
+ ""
33
+ ].join("\n");
34
+ }
35
+ /**
36
+ * Generate the two parts needed to transform kidd's bundled dist.
37
+ *
38
+ * Returns an empty imports string (no prepended imports needed) and a
39
+ * replacement autoloader region that uses dynamic `import()` calls inside
40
+ * the async `autoload()` function. This avoids circular dependency issues:
41
+ * command files import `command` from `kidd`, so static imports would be
42
+ * hoisted above kidd's own initialization code, causing `TAG` to be
43
+ * accessed before initialization.
44
+ *
45
+ * By deferring to dynamic imports, kidd fully initializes first, then
46
+ * command files are loaded when `autoload()` is called at CLI startup.
47
+ *
48
+ * @param params - The scan result and path to the tag utility module.
49
+ * @returns Import statements (empty) and the replacement autoloader region.
50
+ */
51
+ function generateAutoloaderParts(params) {
52
+ const imports = collectImports(params.scan);
53
+ if (imports.length === 0) return {
54
+ imports: "",
55
+ region: buildEmptyAutoloaderRegion()
56
+ };
57
+ return {
58
+ imports: "",
59
+ region: buildDynamicAutoloaderRegion(imports, buildCommandsObject(params.scan))
60
+ };
61
+ }
62
+ /**
63
+ * Collect all import entries from a scan result.
64
+ *
65
+ * @private
66
+ * @param scan - The scan result to collect imports from.
67
+ * @returns A flat array of all import entries.
68
+ */
69
+ function collectImports(scan) {
70
+ return [...scan.files.map((file) => fileToImport(file, [])), ...scan.dirs.flatMap((dir) => collectDirImports(dir, []))];
71
+ }
72
+ /**
73
+ * Recursively collect import entries from a scanned directory.
74
+ *
75
+ * @private
76
+ * @param dir - The scanned directory.
77
+ * @param parentPath - The path segments leading to this directory.
78
+ * @returns A flat array of import entries for the directory and its children.
79
+ */
80
+ function collectDirImports(dir, parentPath) {
81
+ const currentPath = [...parentPath, dir.name];
82
+ return [
83
+ ...buildIndexImport(dir.index, currentPath),
84
+ ...dir.files.map((file) => fileToImport(file, currentPath)),
85
+ ...dir.dirs.flatMap((sub) => collectDirImports(sub, currentPath))
86
+ ];
87
+ }
88
+ /**
89
+ * Create an import entry for a leaf command file.
90
+ *
91
+ * @private
92
+ * @param file - The scanned file.
93
+ * @param parentPath - The path segments leading to the file's parent directory.
94
+ * @returns An import entry with a generated identifier.
95
+ */
96
+ function fileToImport(file, parentPath) {
97
+ return {
98
+ filePath: file.filePath,
99
+ identifier: toIdentifier([...parentPath, file.name])
100
+ };
101
+ }
102
+ /**
103
+ * Convert a path segment array to a valid JavaScript identifier.
104
+ *
105
+ * Joins segments with underscores and prefixes with `_`.
106
+ * Example: `['deploy', 'preview']` becomes `_deploy_preview`.
107
+ *
108
+ * @private
109
+ * @param segments - The path segments to convert.
110
+ * @returns A valid JavaScript identifier string.
111
+ */
112
+ function toIdentifier(segments) {
113
+ return `_${segments.map((s) => s.replaceAll("-", "$")).join("_")}`;
114
+ }
115
+ /**
116
+ * Build the array of import statement lines.
117
+ *
118
+ * @private
119
+ * @param imports - All collected import entries.
120
+ * @param tagModulePath - Absolute path to the tag utility module.
121
+ * @returns An array of import statement strings.
122
+ */
123
+ function buildImportStatements(imports, tagModulePath) {
124
+ const tagLine = buildTagImportLine(imports, tagModulePath);
125
+ const importLines = imports.map((entry) => `import ${entry.identifier} from '${entry.filePath}'`);
126
+ return [
127
+ ...tagLine,
128
+ "",
129
+ ...importLines
130
+ ];
131
+ }
132
+ /**
133
+ * Build the JavaScript object literal string for the top-level commands map.
134
+ *
135
+ * @private
136
+ * @param scan - The scan result.
137
+ * @returns A string representation of the commands object literal.
138
+ */
139
+ function buildCommandsObject(scan) {
140
+ return formatObject([...scan.files.map((file) => buildFileEntry(file, [])), ...scan.dirs.map((dir) => buildDirEntry(dir, []))]);
141
+ }
142
+ /**
143
+ * Build an object entry string for a leaf command file.
144
+ *
145
+ * @private
146
+ * @param file - The scanned file.
147
+ * @param parentPath - Path segments to the file's parent.
148
+ * @returns A string like `'status': _status`.
149
+ */
150
+ function buildFileEntry(file, parentPath) {
151
+ const identifier = toIdentifier([...parentPath, file.name]);
152
+ return `'${file.name}': ${identifier}`;
153
+ }
154
+ /**
155
+ * Build an object entry string for a directory command (possibly with subcommands).
156
+ *
157
+ * @private
158
+ * @param dir - The scanned directory.
159
+ * @param parentPath - Path segments to the directory's parent.
160
+ * @returns A string representing the directory command with withTag wrapping.
161
+ */
162
+ function buildDirEntry(dir, parentPath) {
163
+ const currentPath = [...parentPath, dir.name];
164
+ const commandsObj = formatObject([...dir.files.map((file) => buildFileEntry(file, currentPath)), ...dir.dirs.map((sub) => buildDirEntry(sub, currentPath))]);
165
+ if (dir.index) {
166
+ const indexIdentifier = toIdentifier(currentPath);
167
+ return `'${dir.name}': withTag({ ...${indexIdentifier}, commands: ${commandsObj} }, 'Command')`;
168
+ }
169
+ return `'${dir.name}': withTag({ commands: ${commandsObj} }, 'Command')`;
170
+ }
171
+ /**
172
+ * Build the import entry for an index file, if present.
173
+ *
174
+ * @private
175
+ * @param index - The absolute path to the index file, or undefined.
176
+ * @param currentPath - The current path segments for identifier generation.
177
+ * @returns An array with zero or one import entries.
178
+ */
179
+ function buildIndexImport(index, currentPath) {
180
+ if (!index) return [];
181
+ return [{
182
+ filePath: index,
183
+ identifier: toIdentifier(currentPath)
184
+ }];
185
+ }
186
+ /**
187
+ * Build the tag import line if any imports exist.
188
+ *
189
+ * @private
190
+ * @param imports - The collected import entries.
191
+ * @param tagModulePath - The absolute path to the tag module.
192
+ * @returns An array with zero or one import statement strings.
193
+ */
194
+ function buildTagImportLine(imports, tagModulePath) {
195
+ if (imports.length === 0) return [];
196
+ return [`import { withTag } from '${tagModulePath}'`];
197
+ }
198
+ /**
199
+ * Build the autoloader region for an empty scan result.
200
+ *
201
+ * @private
202
+ * @returns A region string with an autoloader that returns an empty object.
203
+ */
204
+ function buildEmptyAutoloaderRegion() {
205
+ return [
206
+ "//#region src/autoloader.ts (static)",
207
+ "async function autoload() {",
208
+ " return {}",
209
+ "}",
210
+ "//#endregion"
211
+ ].join("\n");
212
+ }
213
+ /**
214
+ * Build the autoloader region using dynamic `import()` calls.
215
+ *
216
+ * Uses `Promise.all` with array destructuring to load all command files
217
+ * in parallel. The dynamic imports defer execution until `autoload()` is
218
+ * called, avoiding circular dependency issues with kidd's own initialization.
219
+ *
220
+ * @private
221
+ * @param imports - The collected import entries.
222
+ * @param commandsObject - The commands object literal string.
223
+ * @returns A region string with the full dynamic autoloader.
224
+ */
225
+ function buildDynamicAutoloaderRegion(imports, commandsObject) {
226
+ return [
227
+ "//#region src/autoloader.ts (static)",
228
+ "async function autoload() {",
229
+ " const [",
230
+ imports.map((entry) => ` { default: ${entry.identifier} },`).join("\n"),
231
+ " ] = await Promise.all([",
232
+ imports.map((entry) => ` import('${entry.filePath}'),`).join("\n"),
233
+ " ])",
234
+ ` return ${commandsObject}`,
235
+ "}",
236
+ "//#endregion"
237
+ ].join("\n");
238
+ }
239
+ /**
240
+ * Format an array of key-value strings as a JavaScript object literal.
241
+ *
242
+ * @private
243
+ * @param entries - The key-value pair strings.
244
+ * @returns A formatted object literal string.
245
+ */
246
+ function formatObject(entries) {
247
+ if (entries.length === 0) return "{}";
248
+ return `{\n${entries.map((entry) => ` ${entry},`).join("\n")}\n}`;
249
+ }
250
+
251
+ //#endregion
252
+ //#region src/scan-commands.ts
253
+ const VALID_EXTENSIONS = new Set([
254
+ ".ts",
255
+ ".js",
256
+ ".mjs"
257
+ ]);
258
+ const INDEX_NAME = "index";
259
+ /**
260
+ * Scan a commands directory and produce a tree structure for static code generation.
261
+ *
262
+ * Mirrors the runtime autoloader's rules: valid extensions are `.ts`, `.js`, `.mjs`;
263
+ * files and directories starting with `_` or `.` are skipped; `index` files in
264
+ * subdirectories become parent command handlers.
265
+ *
266
+ * @param dir - Absolute path to the commands directory.
267
+ * @returns A tree of scanned files and directories.
268
+ */
269
+ async function scanCommandsDir(dir) {
270
+ const entries = await readdir(dir, { withFileTypes: true });
271
+ const files = entries.filter(isCommandFile).map((entry) => toScannedFile(dir, entry));
272
+ return {
273
+ dirs: await Promise.all(entries.filter(isCommandDir).map((entry) => scanSubDir(join(dir, entry.name)))),
274
+ files
275
+ };
276
+ }
277
+ /**
278
+ * Recursively scan a subdirectory into a ScannedDir.
279
+ *
280
+ * @private
281
+ * @param dir - Absolute path to the subdirectory.
282
+ * @returns A ScannedDir representing the directory and its contents.
283
+ */
284
+ async function scanSubDir(dir) {
285
+ const name = basename(dir);
286
+ const entries = await readdir(dir, { withFileTypes: true });
287
+ const indexEntry = findIndexEntry(entries);
288
+ const files = entries.filter(isCommandFile).map((entry) => toScannedFile(dir, entry));
289
+ return {
290
+ dirs: await Promise.all(entries.filter(isCommandDir).map((entry) => scanSubDir(join(dir, entry.name)))),
291
+ files,
292
+ index: resolveIndexPath(dir, indexEntry),
293
+ name
294
+ };
295
+ }
296
+ /**
297
+ * Convert a directory entry into a ScannedFile.
298
+ *
299
+ * @private
300
+ * @param dir - Parent directory absolute path.
301
+ * @param entry - The directory entry for the file.
302
+ * @returns A ScannedFile with name and absolute file path.
303
+ */
304
+ function toScannedFile(dir, entry) {
305
+ return {
306
+ filePath: join(dir, entry.name),
307
+ name: basename(entry.name, extname(entry.name))
308
+ };
309
+ }
310
+ /**
311
+ * Find the index file entry among a list of directory entries.
312
+ *
313
+ * @private
314
+ * @param entries - The directory entries to search.
315
+ * @returns The Dirent for the index file, or undefined.
316
+ */
317
+ function findIndexEntry(entries) {
318
+ return entries.find((entry) => entry.isFile() && VALID_EXTENSIONS.has(extname(entry.name)) && basename(entry.name, extname(entry.name)) === INDEX_NAME);
319
+ }
320
+ /**
321
+ * Predicate: entry is a valid command file (not index, not hidden/private).
322
+ *
323
+ * @private
324
+ * @param entry - The directory entry to check.
325
+ * @returns True when the entry is a scannable command file.
326
+ */
327
+ function isCommandFile(entry) {
328
+ if (!entry.isFile()) return false;
329
+ if (entry.name.startsWith("_") || entry.name.startsWith(".")) return false;
330
+ if (!VALID_EXTENSIONS.has(extname(entry.name))) return false;
331
+ return basename(entry.name, extname(entry.name)) !== INDEX_NAME;
332
+ }
333
+ /**
334
+ * Predicate: entry is a scannable command directory (not hidden/private).
335
+ *
336
+ * @private
337
+ * @param entry - The directory entry to check.
338
+ * @returns True when the entry is a scannable command directory.
339
+ */
340
+ function isCommandDir(entry) {
341
+ if (!entry.isDirectory()) return false;
342
+ return !entry.name.startsWith("_") && !entry.name.startsWith(".");
343
+ }
344
+ /**
345
+ * Resolve the absolute path to an index file entry, or undefined.
346
+ *
347
+ * @private
348
+ * @param dir - The parent directory absolute path.
349
+ * @param entry - The index file Dirent, or undefined.
350
+ * @returns The absolute path to the index file, or undefined.
351
+ */
352
+ function resolveIndexPath(dir, entry) {
353
+ if (!entry) return;
354
+ return join(dir, entry.name);
355
+ }
356
+
357
+ //#endregion
358
+ //#region src/autoload-plugin.ts
359
+ const VIRTUAL_MODULE_ID = "virtual:kidd-static-commands";
360
+ const RESOLVED_VIRTUAL_ID = `\0${VIRTUAL_MODULE_ID}`;
361
+ const AUTOLOADER_REGION_START = "//#region src/autoloader.ts";
362
+ const AUTOLOADER_REGION_END = "//#endregion";
363
+ const KIDD_DIST_PATTERN = /\/kidd\/dist\/index\.js$/;
364
+ /**
365
+ * Create a rolldown plugin that replaces the runtime autoloader with a static version.
366
+ *
367
+ * Uses a three-hook approach to break the circular dependency between kidd's
368
+ * dist and user command files (which `import { command } from 'kidd'`):
369
+ *
370
+ * 1. `transform` — detects kidd's pre-bundled dist and replaces the autoloader
371
+ * region with a dynamic `import()` to a virtual module
372
+ * 2. `resolveId` — resolves the virtual module identifier
373
+ * 3. `load` — scans the commands directory and generates a static autoloader
374
+ * module with all command imports pre-resolved
375
+ *
376
+ * The dynamic import ensures command files execute after kidd's code is fully
377
+ * initialized, avoiding `ReferenceError` from accessing `TAG` before its
378
+ * declaration.
379
+ *
380
+ * @param params - The commands directory and tag module path.
381
+ * @returns A rolldown plugin for static autoloading.
382
+ */
383
+ function createAutoloadPlugin(params) {
384
+ return {
385
+ async load(id) {
386
+ if (id !== RESOLVED_VIRTUAL_ID) return null;
387
+ return generateStaticAutoloader({
388
+ scan: await scanCommandsDir(params.commandsDir),
389
+ tagModulePath: params.tagModulePath
390
+ });
391
+ },
392
+ name: "kidd-static-autoloader",
393
+ resolveId(source) {
394
+ if (source === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_ID;
395
+ return null;
396
+ },
397
+ transform(code, id) {
398
+ if (!KIDD_DIST_PATTERN.test(id)) return null;
399
+ const regionStart = code.indexOf(AUTOLOADER_REGION_START);
400
+ if (regionStart === -1) return null;
401
+ const regionEnd = code.indexOf(AUTOLOADER_REGION_END, regionStart);
402
+ if (regionEnd === -1) return null;
403
+ const before = code.slice(0, regionStart);
404
+ const after = code.slice(regionEnd + 12);
405
+ return `${before}${buildStaticRegion()}${after}`;
406
+ }
407
+ };
408
+ }
409
+ /**
410
+ * Build the replacement autoloader region that delegates to the virtual module.
411
+ *
412
+ * @private
413
+ * @returns The replacement region string with dynamic import.
414
+ */
415
+ function buildStaticRegion() {
416
+ return [
417
+ "//#region src/autoloader.ts (static)",
418
+ "async function autoload() {",
419
+ ` const mod = await import('${VIRTUAL_MODULE_ID}')`,
420
+ " return mod.autoload()",
421
+ "}",
422
+ "//#endregion"
423
+ ].join("\n");
424
+ }
425
+
426
+ //#endregion
427
+ //#region src/constants.ts
428
+ /**
429
+ * Shebang line prepended to CLI entry files.
430
+ */
431
+ const SHEBANG = "#!/usr/bin/env node\n";
432
+ /**
433
+ * Default entry point for the CLI source.
434
+ */
435
+ const DEFAULT_ENTRY = "./src/index.ts";
436
+ /**
437
+ * Default directory for CLI commands.
438
+ */
439
+ const DEFAULT_COMMANDS = "./commands";
440
+ /**
441
+ * Default build output directory.
442
+ */
443
+ const DEFAULT_OUT_DIR = "./dist";
444
+ /**
445
+ * Default Node.js target version for builds.
446
+ */
447
+ const DEFAULT_TARGET = "node18";
448
+ /**
449
+ * Default minification setting.
450
+ */
451
+ const DEFAULT_MINIFY = false;
452
+ /**
453
+ * Default source map generation setting.
454
+ */
455
+ const DEFAULT_SOURCEMAP = true;
456
+ /**
457
+ * Default binary name for compiled SEA output.
458
+ */
459
+ const DEFAULT_BINARY_NAME = "cli";
460
+ /**
461
+ * Default compile targets when none are explicitly configured.
462
+ *
463
+ * Covers Linux servers/CI, modern and Intel Macs, and Windows — roughly 95%
464
+ * of developer environments.
465
+ */
466
+ const DEFAULT_COMPILE_TARGETS = [
467
+ "darwin-arm64",
468
+ "darwin-x64",
469
+ "linux-x64",
470
+ "windows-x64"
471
+ ];
472
+ /**
473
+ * Packages that must always be bundled into the output.
474
+ *
475
+ * The `kidd` framework and its internal `@kidd-cli/*` packages must be inlined
476
+ * so the autoload plugin can intercept and replace the runtime autoloader
477
+ * with a static version for compiled binaries.
478
+ */
479
+ const ALWAYS_BUNDLE = [/^@?kidd/];
480
+ /**
481
+ * Node.js builtin modules in both bare and `node:` prefixed forms.
482
+ */
483
+ const NODE_BUILTINS = [...builtinModules, ...builtinModules.map((m) => `node:${m}`)];
484
+
485
+ //#endregion
486
+ //#region src/map-config.ts
487
+ /**
488
+ * Map a resolved bundler config to a tsdown InlineConfig for production builds.
489
+ *
490
+ * @param config - The fully resolved bundler config.
491
+ * @returns A tsdown InlineConfig ready for `build()`.
492
+ */
493
+ function mapToBuildConfig(config) {
494
+ return {
495
+ banner: SHEBANG,
496
+ clean: true,
497
+ config: false,
498
+ cwd: config.cwd,
499
+ deps: {
500
+ alwaysBundle: ALWAYS_BUNDLE,
501
+ neverBundle: buildExternals(config.build.external)
502
+ },
503
+ dts: false,
504
+ entry: { index: config.entry },
505
+ format: "esm",
506
+ inputOptions: { resolve: { mainFields: ["module", "main"] } },
507
+ logLevel: "info",
508
+ minify: config.build.minify,
509
+ outDir: config.buildOutDir,
510
+ outputOptions: { codeSplitting: false },
511
+ platform: "node",
512
+ plugins: [createAutoloadPlugin({
513
+ commandsDir: config.commands,
514
+ tagModulePath: resolveTagModulePath()
515
+ })],
516
+ sourcemap: config.build.sourcemap,
517
+ target: config.build.target,
518
+ treeshake: true
519
+ };
520
+ }
521
+ /**
522
+ * Map a resolved bundler config to a tsdown InlineConfig for watch mode.
523
+ *
524
+ * @param params - The resolved config and optional success callback.
525
+ * @returns A tsdown InlineConfig with `watch: true`.
526
+ */
527
+ function mapToWatchConfig(params) {
528
+ return {
529
+ ...mapToBuildConfig(params.config),
530
+ onSuccess: params.onSuccess,
531
+ watch: true
532
+ };
533
+ }
534
+ /**
535
+ * Combine Node.js builtins with user-specified externals.
536
+ *
537
+ * @private
538
+ * @param userExternals - Additional packages to mark as external.
539
+ * @returns Combined array of externals for tsdown's `deps.neverBundle`.
540
+ */
541
+ function buildExternals(userExternals) {
542
+ return [...NODE_BUILTINS, ...userExternals];
543
+ }
544
+ /**
545
+ * Resolve the absolute file path to the `@kidd-cli/utils/tag` module.
546
+ *
547
+ * The static autoloader virtual module imports `withTag` via this path.
548
+ * Using an absolute path ensures rolldown can resolve the import from
549
+ * inside the virtual module without relying on tsdown's `alwaysBundle`
550
+ * heuristic, which virtual modules may bypass.
551
+ *
552
+ * @private
553
+ * @returns The absolute file path to the tag module.
554
+ */
555
+ function resolveTagModulePath() {
556
+ return createRequire(import.meta.url).resolve("@kidd-cli/utils/tag");
557
+ }
558
+
559
+ //#endregion
560
+ //#region src/resolve-config.ts
561
+ /**
562
+ * Known entry file names produced by tsdown for ESM builds, in preference order.
563
+ */
564
+ const ENTRY_CANDIDATES = ["index.mjs", "index.js"];
565
+ /**
566
+ * Normalize the `compile` config field from `boolean | CompileOptions | undefined` to `CompileOptions`.
567
+ *
568
+ * - `true` → `{}` (compile with defaults)
569
+ * - `false` / `undefined` → `{}` (no explicit options, caller decides whether to compile)
570
+ * - object → pass through
571
+ *
572
+ * @param value - The raw compile config value.
573
+ * @returns A normalized CompileOptions object.
574
+ */
575
+ function normalizeCompileOptions(value) {
576
+ if (typeof value === "object") return value;
577
+ return {};
578
+ }
579
+ /**
580
+ * Fill defaults and resolve relative paths against `cwd`.
581
+ *
582
+ * This is a pure function — the incoming config is already validated by `@kidd-cli/config`.
583
+ * It only fills missing optional fields with defaults and resolves paths to absolute.
584
+ *
585
+ * @param params - The raw config and working directory.
586
+ * @returns A fully resolved bundler configuration.
587
+ */
588
+ function resolveConfig(params) {
589
+ const { config, cwd } = params;
590
+ const entry = resolve(cwd, config.entry ?? DEFAULT_ENTRY);
591
+ const commands = resolve(cwd, config.commands ?? DEFAULT_COMMANDS);
592
+ const buildOpts = config.build ?? {};
593
+ const compileOpts = normalizeCompileOptions(config.compile);
594
+ const buildOutDir = resolve(cwd, buildOpts.out ?? DEFAULT_OUT_DIR);
595
+ const compileOutDir = resolve(cwd, compileOpts.out ?? DEFAULT_OUT_DIR);
596
+ return {
597
+ build: {
598
+ external: buildOpts.external ?? [],
599
+ minify: buildOpts.minify ?? DEFAULT_MINIFY,
600
+ sourcemap: buildOpts.sourcemap ?? DEFAULT_SOURCEMAP,
601
+ target: buildOpts.target ?? DEFAULT_TARGET
602
+ },
603
+ buildOutDir,
604
+ commands,
605
+ compile: {
606
+ name: compileOpts.name ?? DEFAULT_BINARY_NAME,
607
+ targets: compileOpts.targets ?? []
608
+ },
609
+ compileOutDir,
610
+ cwd,
611
+ entry,
612
+ include: config.include ?? []
613
+ };
614
+ }
615
+ /**
616
+ * Detect the bundled entry file in a build output directory.
617
+ *
618
+ * tsdown may produce `index.mjs` or `index.js` depending on the project's
619
+ * `package.json` `type` field and tsdown configuration. This function checks
620
+ * for both candidates and returns the first one that exists on disk.
621
+ *
622
+ * @param outDir - Absolute path to the build output directory.
623
+ * @returns The absolute path to the entry file, or `undefined` when none is found.
624
+ */
625
+ function detectBuildEntry(outDir) {
626
+ return ENTRY_CANDIDATES.map((name) => join(outDir, name)).find(existsSync);
627
+ }
628
+
629
+ //#endregion
630
+ //#region src/build.ts
631
+ /**
632
+ * Build a kidd CLI tool using tsdown.
633
+ *
634
+ * Resolves defaults, maps the config to tsdown's InlineConfig, and invokes the build.
635
+ *
636
+ * @param params - The build parameters including config and working directory.
637
+ * @returns A result tuple with build output on success or an Error on failure.
638
+ */
639
+ async function build(params) {
640
+ const resolved = resolveConfig(params);
641
+ const inlineConfig = mapToBuildConfig(resolved);
642
+ try {
643
+ await build$1(inlineConfig);
644
+ } catch (error) {
645
+ console.error("[kidd-bundler] build error:", error);
646
+ return err(new Error("tsdown build failed", { cause: error }));
647
+ }
648
+ const entryFile = detectBuildEntry(resolved.buildOutDir);
649
+ if (!entryFile) return err(/* @__PURE__ */ new Error(`build produced no entry file in ${resolved.buildOutDir}`));
650
+ return ok({
651
+ entryFile,
652
+ outDir: resolved.buildOutDir
653
+ });
654
+ }
655
+
656
+ //#endregion
657
+ //#region src/compile.ts
658
+ /**
659
+ * Packages to externalize during `bun build --compile`.
660
+ *
661
+ * These are optional peer dependencies of `c12` (the config loader) that bun
662
+ * eagerly tries to resolve even though they are behind dynamic `import()` calls
663
+ * that never execute at runtime in a compiled CLI.
664
+ */
665
+ const COMPILE_EXTERNALS = [
666
+ "chokidar",
667
+ "magicast",
668
+ "giget"
669
+ ];
670
+ /**
671
+ * Human-readable labels for each compile target.
672
+ */
673
+ const COMPILE_TARGET_LABELS = {
674
+ "darwin-arm64": "macOS Apple Silicon",
675
+ "darwin-x64": "macOS Intel",
676
+ "linux-arm64": "Linux ARM64",
677
+ "linux-x64": "Linux x64",
678
+ "linux-x64-musl": "Linux x64 (musl)",
679
+ "windows-arm64": "Windows ARM64",
680
+ "windows-x64": "Windows x64"
681
+ };
682
+ /**
683
+ * Compile a kidd CLI tool into standalone binaries using `bun build --compile`.
684
+ *
685
+ * Expects the bundled entry to already exist in `outDir` (i.e., `build()` must
686
+ * be run first). For each requested target (or the current platform if none
687
+ * specified), spawns `bun build --compile` to produce a self-contained binary.
688
+ *
689
+ * @param params - The compile parameters including config and working directory.
690
+ * @returns A result tuple with compile output on success or an Error on failure.
691
+ */
692
+ async function compile(params) {
693
+ const resolved = resolveConfig(params);
694
+ const bundledEntry = detectBuildEntry(resolved.buildOutDir);
695
+ if (!bundledEntry) return err(/* @__PURE__ */ new Error(`bundled entry not found in ${resolved.buildOutDir} — run build() first`));
696
+ const targets = resolveTargets(resolved.compile.targets);
697
+ const isMultiTarget = targets.length > 1;
698
+ const results = await Promise.all(targets.map(async (target) => {
699
+ if (params.onTargetStart) await params.onTargetStart(target);
700
+ const result = await compileSingleTarget({
701
+ bundledEntry,
702
+ isMultiTarget,
703
+ name: resolved.compile.name,
704
+ outDir: resolved.compileOutDir,
705
+ target
706
+ });
707
+ if (params.onTargetComplete) await params.onTargetComplete(target);
708
+ return result;
709
+ }));
710
+ cleanBunBuildArtifacts(resolved.cwd);
711
+ const failedResult = results.find((r) => r[0] !== null);
712
+ if (failedResult) {
713
+ const [failedError] = failedResult;
714
+ if (failedError) return err(failedError);
715
+ }
716
+ return ok({ binaries: results.filter((r) => r[1] !== null).map(([, binary]) => binary) });
717
+ }
718
+ /**
719
+ * Compile a single target via `bun build --compile`.
720
+ *
721
+ * @private
722
+ * @param params - Target compilation parameters.
723
+ * @returns A result tuple with the compiled binary info or an error.
724
+ */
725
+ async function compileSingleTarget(params) {
726
+ const binaryName = resolveBinaryName(params.name, params.target, params.isMultiTarget);
727
+ const outfile = join(params.outDir, binaryName);
728
+ const [execError] = await execBunBuild([
729
+ "build",
730
+ "--compile",
731
+ params.bundledEntry,
732
+ "--outfile",
733
+ outfile,
734
+ "--target",
735
+ mapCompileTarget(params.target),
736
+ ...COMPILE_EXTERNALS.flatMap((pkg) => ["--external", pkg])
737
+ ]);
738
+ if (execError) return err(new Error(`bun build --compile failed for target ${params.target}`, { cause: execError }));
739
+ return ok({
740
+ label: resolveTargetLabel(params.target),
741
+ path: outfile,
742
+ target: params.target
743
+ });
744
+ }
745
+ /**
746
+ * Resolve the list of compile targets, falling back to the default set.
747
+ *
748
+ * When no targets are explicitly configured, defaults to linux-x64,
749
+ * darwin-arm64, darwin-x64, and windows-x64 to cover ~95% of developers.
750
+ *
751
+ * @private
752
+ * @param explicit - User-specified targets (may be empty).
753
+ * @returns The targets to compile for.
754
+ */
755
+ function resolveTargets(explicit) {
756
+ if (explicit.length > 0) return explicit;
757
+ return DEFAULT_COMPILE_TARGETS;
758
+ }
759
+ /**
760
+ * Build the output binary name, appending the target suffix for multi-target builds.
761
+ *
762
+ * @private
763
+ * @param name - Base binary name.
764
+ * @param target - The compile target.
765
+ * @param isMultiTarget - Whether multiple targets are being compiled.
766
+ * @returns The resolved binary file name.
767
+ */
768
+ function resolveBinaryName(name, target, isMultiTarget) {
769
+ if (isMultiTarget) return `${name}-${target}`;
770
+ return name;
771
+ }
772
+ /**
773
+ * Look up the human-readable label for a compile target.
774
+ *
775
+ * @param target - The compile target identifier.
776
+ * @returns A descriptive label (e.g., "macOS Apple Silicon").
777
+ */
778
+ function resolveTargetLabel(target) {
779
+ return COMPILE_TARGET_LABELS[target];
780
+ }
781
+ /**
782
+ * Map a `CompileTarget` to Bun's `--target` string.
783
+ *
784
+ * Note: `linux-x64-musl` maps to `bun-linux-x64` because Bun's Linux
785
+ * builds natively handle musl — there is no separate musl target.
786
+ *
787
+ * @private
788
+ * @param target - The kidd compile target.
789
+ * @returns The Bun target string (e.g., `'bun-darwin-arm64'`).
790
+ */
791
+ function mapCompileTarget(target) {
792
+ if (target === "linux-x64-musl") return "bun-linux-x64";
793
+ return `bun-${target}`;
794
+ }
795
+ /**
796
+ * Promisified wrapper around `execFile` to invoke `bun build`.
797
+ *
798
+ * @private
799
+ * @param args - Arguments to pass to `bun`.
800
+ * @returns A result tuple with stdout on success or an Error on failure.
801
+ */
802
+ function execBunBuild(args) {
803
+ return new Promise((resolve) => {
804
+ execFile("bun", [...args], (error, stdout) => {
805
+ if (error) {
806
+ resolve(err(error));
807
+ return;
808
+ }
809
+ resolve(ok(stdout));
810
+ });
811
+ });
812
+ }
813
+ /**
814
+ * Remove temporary `.bun-build` files that `bun build --compile` leaves behind.
815
+ *
816
+ * @private
817
+ * @param cwd - The working directory to clean.
818
+ */
819
+ function cleanBunBuildArtifacts(cwd) {
820
+ readdirSync(cwd).filter((name) => name.endsWith(".bun-build")).map((name) => join(cwd, name)).map(unlinkSync);
821
+ }
822
+
823
+ //#endregion
824
+ //#region src/watch.ts
825
+ /**
826
+ * Start a watch-mode build for a kidd CLI tool using tsdown.
827
+ *
828
+ * The returned promise resolves only when tsdown's watch terminates (typically on process exit).
829
+ * tsdown's `build()` with `watch: true` runs indefinitely.
830
+ *
831
+ * @param params - The watch parameters including config, working directory, and optional success callback.
832
+ * @returns A result tuple with void on success or an Error on failure.
833
+ */
834
+ async function watch(params) {
835
+ const watchConfig = mapToWatchConfig({
836
+ config: resolveConfig(params),
837
+ onSuccess: params.onSuccess
838
+ });
839
+ try {
840
+ await build$1(watchConfig);
841
+ } catch (error) {
842
+ return err(new Error("tsdown watch failed", { cause: error }));
843
+ }
844
+ return ok();
845
+ }
846
+
847
+ //#endregion
848
+ export { build, compile, createAutoloadPlugin, detectBuildEntry, generateAutoloaderParts, generateStaticAutoloader, mapToBuildConfig, mapToWatchConfig, normalizeCompileOptions, resolveConfig, resolveTargetLabel, scanCommandsDir, watch };
849
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["tsdownBuild","tsdownBuild"],"sources":["../src/generate-autoloader.ts","../src/scan-commands.ts","../src/autoload-plugin.ts","../src/constants.ts","../src/map-config.ts","../src/resolve-config.ts","../src/build.ts","../src/compile.ts","../src/watch.ts"],"sourcesContent":["import type { ScanResult, ScannedDir, ScannedFile } from './types.js'\n\n/**\n * Parameters for generating a static autoloader module.\n */\ninterface GenerateStaticAutoloaderParams {\n readonly scan: ScanResult\n readonly tagModulePath: string\n}\n\n/**\n * The two parts of a static autoloader transform: import statements to\n * prepend and the replacement code for the autoloader region.\n */\nexport interface StaticAutoloaderParts {\n readonly imports: string\n readonly region: string\n}\n\n/**\n * Generate JavaScript source code for a static autoloader virtual module.\n *\n * The generated module statically imports every discovered command file and\n * exports an `autoload()` function that returns the pre-built CommandMap.\n * Directory commands that merge a parent handler with subcommands are re-tagged\n * via `withTag` because the TAG symbol is non-enumerable and lost on spread.\n *\n * @param params - The scan result and path to the tag utility module.\n * @returns JavaScript source code for the static autoloader.\n */\nexport function generateStaticAutoloader(params: GenerateStaticAutoloaderParams): string {\n const imports = collectImports(params.scan)\n const importLines = buildImportStatements(imports, params.tagModulePath)\n const commandsObject = buildCommandsObject(params.scan)\n\n return [\n ...importLines,\n '',\n `const commands = ${commandsObject}`,\n '',\n 'export async function autoload() {',\n ' return commands',\n '}',\n '',\n ].join('\\n')\n}\n\n/**\n * Generate the two parts needed to transform kidd's bundled dist.\n *\n * Returns an empty imports string (no prepended imports needed) and a\n * replacement autoloader region that uses dynamic `import()` calls inside\n * the async `autoload()` function. This avoids circular dependency issues:\n * command files import `command` from `kidd`, so static imports would be\n * hoisted above kidd's own initialization code, causing `TAG` to be\n * accessed before initialization.\n *\n * By deferring to dynamic imports, kidd fully initializes first, then\n * command files are loaded when `autoload()` is called at CLI startup.\n *\n * @param params - The scan result and path to the tag utility module.\n * @returns Import statements (empty) and the replacement autoloader region.\n */\nexport function generateAutoloaderParts(\n params: GenerateStaticAutoloaderParams\n): StaticAutoloaderParts {\n const imports = collectImports(params.scan)\n\n if (imports.length === 0) {\n return {\n imports: '',\n region: buildEmptyAutoloaderRegion(),\n }\n }\n\n const commandsObject = buildCommandsObject(params.scan)\n const region = buildDynamicAutoloaderRegion(imports, commandsObject)\n\n return { imports: '', region }\n}\n\n// ---------------------------------------------------------------------------\n\n/**\n * A collected import entry with its identifier and absolute file path.\n *\n * @private\n */\ninterface ImportEntry {\n readonly identifier: string\n readonly filePath: string\n}\n\n/**\n * Collect all import entries from a scan result.\n *\n * @private\n * @param scan - The scan result to collect imports from.\n * @returns A flat array of all import entries.\n */\nfunction collectImports(scan: ScanResult): readonly ImportEntry[] {\n return [\n ...scan.files.map((file) => fileToImport(file, [])),\n ...scan.dirs.flatMap((dir) => collectDirImports(dir, [])),\n ]\n}\n\n/**\n * Recursively collect import entries from a scanned directory.\n *\n * @private\n * @param dir - The scanned directory.\n * @param parentPath - The path segments leading to this directory.\n * @returns A flat array of import entries for the directory and its children.\n */\nfunction collectDirImports(dir: ScannedDir, parentPath: readonly string[]): readonly ImportEntry[] {\n const currentPath = [...parentPath, dir.name]\n const indexImport: readonly ImportEntry[] = buildIndexImport(dir.index, currentPath)\n\n return [\n ...indexImport,\n ...dir.files.map((file) => fileToImport(file, currentPath)),\n ...dir.dirs.flatMap((sub) => collectDirImports(sub, currentPath)),\n ]\n}\n\n/**\n * Create an import entry for a leaf command file.\n *\n * @private\n * @param file - The scanned file.\n * @param parentPath - The path segments leading to the file's parent directory.\n * @returns An import entry with a generated identifier.\n */\nfunction fileToImport(file: ScannedFile, parentPath: readonly string[]): ImportEntry {\n return {\n filePath: file.filePath,\n identifier: toIdentifier([...parentPath, file.name]),\n }\n}\n\n/**\n * Convert a path segment array to a valid JavaScript identifier.\n *\n * Joins segments with underscores and prefixes with `_`.\n * Example: `['deploy', 'preview']` becomes `_deploy_preview`.\n *\n * @private\n * @param segments - The path segments to convert.\n * @returns A valid JavaScript identifier string.\n */\nfunction toIdentifier(segments: readonly string[]): string {\n return `_${segments.map((s) => s.replaceAll('-', '$')).join('_')}`\n}\n\n/**\n * Build the array of import statement lines.\n *\n * @private\n * @param imports - All collected import entries.\n * @param tagModulePath - Absolute path to the tag utility module.\n * @returns An array of import statement strings.\n */\nfunction buildImportStatements(\n imports: readonly ImportEntry[],\n tagModulePath: string\n): readonly string[] {\n const tagLine = buildTagImportLine(imports, tagModulePath)\n\n const importLines = imports.map((entry) => `import ${entry.identifier} from '${entry.filePath}'`)\n\n return [...tagLine, '', ...importLines]\n}\n\n/**\n * Build the JavaScript object literal string for the top-level commands map.\n *\n * @private\n * @param scan - The scan result.\n * @returns A string representation of the commands object literal.\n */\nfunction buildCommandsObject(scan: ScanResult): string {\n const entries = [\n ...scan.files.map((file) => buildFileEntry(file, [])),\n ...scan.dirs.map((dir) => buildDirEntry(dir, [])),\n ]\n\n return formatObject(entries)\n}\n\n/**\n * Build an object entry string for a leaf command file.\n *\n * @private\n * @param file - The scanned file.\n * @param parentPath - Path segments to the file's parent.\n * @returns A string like `'status': _status`.\n */\nfunction buildFileEntry(file: ScannedFile, parentPath: readonly string[]): string {\n const identifier = toIdentifier([...parentPath, file.name])\n return `'${file.name}': ${identifier}`\n}\n\n/**\n * Build an object entry string for a directory command (possibly with subcommands).\n *\n * @private\n * @param dir - The scanned directory.\n * @param parentPath - Path segments to the directory's parent.\n * @returns A string representing the directory command with withTag wrapping.\n */\nfunction buildDirEntry(dir: ScannedDir, parentPath: readonly string[]): string {\n const currentPath = [...parentPath, dir.name]\n const subEntries = [\n ...dir.files.map((file) => buildFileEntry(file, currentPath)),\n ...dir.dirs.map((sub) => buildDirEntry(sub, currentPath)),\n ]\n const commandsObj = formatObject(subEntries)\n\n if (dir.index) {\n const indexIdentifier = toIdentifier(currentPath)\n\n return `'${dir.name}': withTag({ ...${indexIdentifier}, commands: ${commandsObj} }, 'Command')`\n }\n\n return `'${dir.name}': withTag({ commands: ${commandsObj} }, 'Command')`\n}\n\n/**\n * Build the import entry for an index file, if present.\n *\n * @private\n * @param index - The absolute path to the index file, or undefined.\n * @param currentPath - The current path segments for identifier generation.\n * @returns An array with zero or one import entries.\n */\nfunction buildIndexImport(\n index: string | undefined,\n currentPath: readonly string[]\n): readonly ImportEntry[] {\n if (!index) {\n return []\n }\n return [{ filePath: index, identifier: toIdentifier(currentPath) }]\n}\n\n/**\n * Build the tag import line if any imports exist.\n *\n * @private\n * @param imports - The collected import entries.\n * @param tagModulePath - The absolute path to the tag module.\n * @returns An array with zero or one import statement strings.\n */\nfunction buildTagImportLine(\n imports: readonly ImportEntry[],\n tagModulePath: string\n): readonly string[] {\n if (imports.length === 0) {\n return []\n }\n return [`import { withTag } from '${tagModulePath}'`]\n}\n\n/**\n * Build the autoloader region for an empty scan result.\n *\n * @private\n * @returns A region string with an autoloader that returns an empty object.\n */\nfunction buildEmptyAutoloaderRegion(): string {\n return [\n '//#region src/autoloader.ts (static)',\n 'async function autoload() {',\n ' return {}',\n '}',\n '//#endregion',\n ].join('\\n')\n}\n\n/**\n * Build the autoloader region using dynamic `import()` calls.\n *\n * Uses `Promise.all` with array destructuring to load all command files\n * in parallel. The dynamic imports defer execution until `autoload()` is\n * called, avoiding circular dependency issues with kidd's own initialization.\n *\n * @private\n * @param imports - The collected import entries.\n * @param commandsObject - The commands object literal string.\n * @returns A region string with the full dynamic autoloader.\n */\nfunction buildDynamicAutoloaderRegion(\n imports: readonly ImportEntry[],\n commandsObject: string\n): string {\n const destructuring = imports.map((entry) => ` { default: ${entry.identifier} },`).join('\\n')\n\n const importCalls = imports.map((entry) => ` import('${entry.filePath}'),`).join('\\n')\n\n return [\n '//#region src/autoloader.ts (static)',\n 'async function autoload() {',\n ' const [',\n destructuring,\n ' ] = await Promise.all([',\n importCalls,\n ' ])',\n ` return ${commandsObject}`,\n '}',\n '//#endregion',\n ].join('\\n')\n}\n\n/**\n * Format an array of key-value strings as a JavaScript object literal.\n *\n * @private\n * @param entries - The key-value pair strings.\n * @returns A formatted object literal string.\n */\nfunction formatObject(entries: readonly string[]): string {\n if (entries.length === 0) {\n return '{}'\n }\n\n const body = entries.map((entry) => ` ${entry},`).join('\\n')\n return `{\\n${body}\\n}`\n}\n","import type { Dirent } from 'node:fs'\nimport { readdir } from 'node:fs/promises'\nimport { basename, extname, join } from 'node:path'\n\nimport type { ScanResult, ScannedDir, ScannedFile } from './types.js'\n\nconst VALID_EXTENSIONS = new Set(['.ts', '.js', '.mjs'])\nconst INDEX_NAME = 'index'\n\n/**\n * Scan a commands directory and produce a tree structure for static code generation.\n *\n * Mirrors the runtime autoloader's rules: valid extensions are `.ts`, `.js`, `.mjs`;\n * files and directories starting with `_` or `.` are skipped; `index` files in\n * subdirectories become parent command handlers.\n *\n * @param dir - Absolute path to the commands directory.\n * @returns A tree of scanned files and directories.\n */\nexport async function scanCommandsDir(dir: string): Promise<ScanResult> {\n const entries = await readdir(dir, { withFileTypes: true })\n\n const files = entries.filter(isCommandFile).map((entry) => toScannedFile(dir, entry))\n\n const dirs = await Promise.all(\n entries.filter(isCommandDir).map((entry) => scanSubDir(join(dir, entry.name)))\n )\n\n return { dirs, files }\n}\n\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively scan a subdirectory into a ScannedDir.\n *\n * @private\n * @param dir - Absolute path to the subdirectory.\n * @returns A ScannedDir representing the directory and its contents.\n */\nasync function scanSubDir(dir: string): Promise<ScannedDir> {\n const name = basename(dir)\n const entries = await readdir(dir, { withFileTypes: true })\n const indexEntry = findIndexEntry(entries)\n\n const files = entries.filter(isCommandFile).map((entry) => toScannedFile(dir, entry))\n\n const dirs = await Promise.all(\n entries.filter(isCommandDir).map((entry) => scanSubDir(join(dir, entry.name)))\n )\n\n return {\n dirs,\n files,\n index: resolveIndexPath(dir, indexEntry),\n name,\n }\n}\n\n/**\n * Convert a directory entry into a ScannedFile.\n *\n * @private\n * @param dir - Parent directory absolute path.\n * @param entry - The directory entry for the file.\n * @returns A ScannedFile with name and absolute file path.\n */\nfunction toScannedFile(dir: string, entry: Dirent): ScannedFile {\n return {\n filePath: join(dir, entry.name),\n name: basename(entry.name, extname(entry.name)),\n }\n}\n\n/**\n * Find the index file entry among a list of directory entries.\n *\n * @private\n * @param entries - The directory entries to search.\n * @returns The Dirent for the index file, or undefined.\n */\nfunction findIndexEntry(entries: readonly Dirent[]): Dirent | undefined {\n return entries.find(\n (entry) =>\n entry.isFile() &&\n VALID_EXTENSIONS.has(extname(entry.name)) &&\n basename(entry.name, extname(entry.name)) === INDEX_NAME\n )\n}\n\n/**\n * Predicate: entry is a valid command file (not index, not hidden/private).\n *\n * @private\n * @param entry - The directory entry to check.\n * @returns True when the entry is a scannable command file.\n */\nfunction isCommandFile(entry: Dirent): boolean {\n if (!entry.isFile()) {\n return false\n }\n if (entry.name.startsWith('_') || entry.name.startsWith('.')) {\n return false\n }\n if (!VALID_EXTENSIONS.has(extname(entry.name))) {\n return false\n }\n return basename(entry.name, extname(entry.name)) !== INDEX_NAME\n}\n\n/**\n * Predicate: entry is a scannable command directory (not hidden/private).\n *\n * @private\n * @param entry - The directory entry to check.\n * @returns True when the entry is a scannable command directory.\n */\nfunction isCommandDir(entry: Dirent): boolean {\n if (!entry.isDirectory()) {\n return false\n }\n return !entry.name.startsWith('_') && !entry.name.startsWith('.')\n}\n\n/**\n * Resolve the absolute path to an index file entry, or undefined.\n *\n * @private\n * @param dir - The parent directory absolute path.\n * @param entry - The index file Dirent, or undefined.\n * @returns The absolute path to the index file, or undefined.\n */\nfunction resolveIndexPath(dir: string, entry: Dirent | undefined): string | undefined {\n if (!entry) {\n return undefined\n }\n return join(dir, entry.name)\n}\n","import type { Rolldown } from 'tsdown'\n\nimport { generateStaticAutoloader } from './generate-autoloader.js'\nimport { scanCommandsDir } from './scan-commands.js'\n\nconst VIRTUAL_MODULE_ID = 'virtual:kidd-static-commands'\nconst RESOLVED_VIRTUAL_ID = `\\0${VIRTUAL_MODULE_ID}`\n\nconst AUTOLOADER_REGION_START = '//#region src/autoloader.ts'\nconst AUTOLOADER_REGION_END = '//#endregion'\nconst KIDD_DIST_PATTERN = /\\/kidd\\/dist\\/index\\.js$/\n\n/**\n * Parameters for creating the autoload plugin.\n */\ninterface CreateAutoloadPluginParams {\n readonly commandsDir: string\n readonly tagModulePath: string\n}\n\n/**\n * Create a rolldown plugin that replaces the runtime autoloader with a static version.\n *\n * Uses a three-hook approach to break the circular dependency between kidd's\n * dist and user command files (which `import { command } from 'kidd'`):\n *\n * 1. `transform` — detects kidd's pre-bundled dist and replaces the autoloader\n * region with a dynamic `import()` to a virtual module\n * 2. `resolveId` — resolves the virtual module identifier\n * 3. `load` — scans the commands directory and generates a static autoloader\n * module with all command imports pre-resolved\n *\n * The dynamic import ensures command files execute after kidd's code is fully\n * initialized, avoiding `ReferenceError` from accessing `TAG` before its\n * declaration.\n *\n * @param params - The commands directory and tag module path.\n * @returns A rolldown plugin for static autoloading.\n */\nexport function createAutoloadPlugin(params: CreateAutoloadPluginParams): Rolldown.Plugin {\n return {\n async load(id) {\n if (id !== RESOLVED_VIRTUAL_ID) {\n return null\n }\n\n const scan = await scanCommandsDir(params.commandsDir)\n\n return generateStaticAutoloader({\n scan,\n tagModulePath: params.tagModulePath,\n })\n },\n name: 'kidd-static-autoloader',\n resolveId(source) {\n if (source === VIRTUAL_MODULE_ID) {\n return RESOLVED_VIRTUAL_ID\n }\n\n return null\n },\n transform(code, id) {\n if (!KIDD_DIST_PATTERN.test(id)) {\n return null\n }\n\n const regionStart = code.indexOf(AUTOLOADER_REGION_START)\n if (regionStart === -1) {\n return null\n }\n\n const regionEnd = code.indexOf(AUTOLOADER_REGION_END, regionStart)\n if (regionEnd === -1) {\n return null\n }\n\n const before = code.slice(0, regionStart)\n const after = code.slice(regionEnd + AUTOLOADER_REGION_END.length)\n const staticRegion = buildStaticRegion()\n\n return `${before}${staticRegion}${after}`\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n\n/**\n * Build the replacement autoloader region that delegates to the virtual module.\n *\n * @private\n * @returns The replacement region string with dynamic import.\n */\nfunction buildStaticRegion(): string {\n return [\n '//#region src/autoloader.ts (static)',\n 'async function autoload() {',\n ` const mod = await import('${VIRTUAL_MODULE_ID}')`,\n ' return mod.autoload()',\n '}',\n '//#endregion',\n ].join('\\n')\n}\n","import { builtinModules } from 'node:module'\n\nimport type { CompileTarget } from '@kidd-cli/config'\n\n/**\n * Shebang line prepended to CLI entry files.\n */\nexport const SHEBANG = '#!/usr/bin/env node\\n'\n\n/**\n * Default entry point for the CLI source.\n */\nexport const DEFAULT_ENTRY = './src/index.ts'\n\n/**\n * Default directory for CLI commands.\n */\nexport const DEFAULT_COMMANDS = './commands'\n\n/**\n * Default build output directory.\n */\nexport const DEFAULT_OUT_DIR = './dist'\n\n/**\n * Default Node.js target version for builds.\n */\nexport const DEFAULT_TARGET = 'node18'\n\n/**\n * Default minification setting.\n */\nexport const DEFAULT_MINIFY = false\n\n/**\n * Default source map generation setting.\n */\nexport const DEFAULT_SOURCEMAP = true\n\n/**\n * Default binary name for compiled SEA output.\n */\nexport const DEFAULT_BINARY_NAME = 'cli'\n\n/**\n * Default compile targets when none are explicitly configured.\n *\n * Covers Linux servers/CI, modern and Intel Macs, and Windows — roughly 95%\n * of developer environments.\n */\nexport const DEFAULT_COMPILE_TARGETS: readonly CompileTarget[] = [\n 'darwin-arm64',\n 'darwin-x64',\n 'linux-x64',\n 'windows-x64',\n]\n\n/**\n * Packages that must always be bundled into the output.\n *\n * The `kidd` framework and its internal `@kidd-cli/*` packages must be inlined\n * so the autoload plugin can intercept and replace the runtime autoloader\n * with a static version for compiled binaries.\n */\nexport const ALWAYS_BUNDLE: RegExp[] = [/^@?kidd/]\n\n/**\n * Node.js builtin modules in both bare and `node:` prefixed forms.\n */\nexport const NODE_BUILTINS: readonly string[] = [\n ...builtinModules,\n ...builtinModules.map((m) => `node:${m}`),\n]\n","import { createRequire } from 'node:module'\n\nimport type { InlineConfig } from 'tsdown'\n\nimport { createAutoloadPlugin } from './autoload-plugin.js'\nimport { ALWAYS_BUNDLE, NODE_BUILTINS, SHEBANG } from './constants.js'\nimport type { ResolvedBundlerConfig } from './types.js'\n\n/**\n * Map a resolved bundler config to a tsdown InlineConfig for production builds.\n *\n * @param config - The fully resolved bundler config.\n * @returns A tsdown InlineConfig ready for `build()`.\n */\nexport function mapToBuildConfig(config: ResolvedBundlerConfig): InlineConfig {\n return {\n banner: SHEBANG,\n clean: true,\n config: false,\n cwd: config.cwd,\n deps: {\n alwaysBundle: ALWAYS_BUNDLE,\n neverBundle: buildExternals(config.build.external),\n },\n dts: false,\n entry: { index: config.entry },\n format: 'esm',\n inputOptions: {\n resolve: {\n mainFields: ['module', 'main'],\n },\n },\n logLevel: 'info',\n minify: config.build.minify,\n outDir: config.buildOutDir,\n outputOptions: {\n codeSplitting: false,\n },\n platform: 'node',\n plugins: [\n createAutoloadPlugin({\n commandsDir: config.commands,\n tagModulePath: resolveTagModulePath(),\n }),\n ],\n sourcemap: config.build.sourcemap,\n target: config.build.target,\n treeshake: true,\n }\n}\n\n/**\n * Map a resolved bundler config to a tsdown InlineConfig for watch mode.\n *\n * @param params - The resolved config and optional success callback.\n * @returns A tsdown InlineConfig with `watch: true`.\n */\nexport function mapToWatchConfig(params: {\n readonly config: ResolvedBundlerConfig\n readonly onSuccess?: () => void | Promise<void>\n}): InlineConfig {\n const buildConfig = mapToBuildConfig(params.config)\n\n return {\n ...buildConfig,\n onSuccess: params.onSuccess,\n watch: true,\n }\n}\n\n// ---------------------------------------------------------------------------\n\n/**\n * Combine Node.js builtins with user-specified externals.\n *\n * @private\n * @param userExternals - Additional packages to mark as external.\n * @returns Combined array of externals for tsdown's `deps.neverBundle`.\n */\nfunction buildExternals(userExternals: readonly string[]): (string | RegExp)[] {\n return [...NODE_BUILTINS, ...userExternals]\n}\n\n/**\n * Resolve the absolute file path to the `@kidd-cli/utils/tag` module.\n *\n * The static autoloader virtual module imports `withTag` via this path.\n * Using an absolute path ensures rolldown can resolve the import from\n * inside the virtual module without relying on tsdown's `alwaysBundle`\n * heuristic, which virtual modules may bypass.\n *\n * @private\n * @returns The absolute file path to the tag module.\n */\nfunction resolveTagModulePath(): string {\n const require = createRequire(import.meta.url)\n return require.resolve('@kidd-cli/utils/tag')\n}\n","import { existsSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\n\nimport type { CompileOptions, KiddConfig } from '@kidd-cli/config'\n\nimport {\n DEFAULT_BINARY_NAME,\n DEFAULT_COMMANDS,\n DEFAULT_ENTRY,\n DEFAULT_MINIFY,\n DEFAULT_OUT_DIR,\n DEFAULT_SOURCEMAP,\n DEFAULT_TARGET,\n} from './constants.js'\nimport type { ResolvedBundlerConfig } from './types.js'\n\n/**\n * Known entry file names produced by tsdown for ESM builds, in preference order.\n */\nconst ENTRY_CANDIDATES = ['index.mjs', 'index.js'] as const\n\n/**\n * Normalize the `compile` config field from `boolean | CompileOptions | undefined` to `CompileOptions`.\n *\n * - `true` → `{}` (compile with defaults)\n * - `false` / `undefined` → `{}` (no explicit options, caller decides whether to compile)\n * - object → pass through\n *\n * @param value - The raw compile config value.\n * @returns A normalized CompileOptions object.\n */\nexport function normalizeCompileOptions(\n value: boolean | CompileOptions | undefined\n): CompileOptions {\n if (typeof value === 'object') {\n return value\n }\n\n return {}\n}\n\n/**\n * Fill defaults and resolve relative paths against `cwd`.\n *\n * This is a pure function — the incoming config is already validated by `@kidd-cli/config`.\n * It only fills missing optional fields with defaults and resolves paths to absolute.\n *\n * @param params - The raw config and working directory.\n * @returns A fully resolved bundler configuration.\n */\nexport function resolveConfig(params: {\n readonly config: KiddConfig\n readonly cwd: string\n}): ResolvedBundlerConfig {\n const { config, cwd } = params\n\n const entry = resolve(cwd, config.entry ?? DEFAULT_ENTRY)\n const commands = resolve(cwd, config.commands ?? DEFAULT_COMMANDS)\n\n const buildOpts = config.build ?? {}\n const compileOpts = normalizeCompileOptions(config.compile)\n\n const buildOutDir = resolve(cwd, buildOpts.out ?? DEFAULT_OUT_DIR)\n const compileOutDir = resolve(cwd, compileOpts.out ?? DEFAULT_OUT_DIR)\n\n return {\n build: {\n external: buildOpts.external ?? [],\n minify: buildOpts.minify ?? DEFAULT_MINIFY,\n sourcemap: buildOpts.sourcemap ?? DEFAULT_SOURCEMAP,\n target: buildOpts.target ?? DEFAULT_TARGET,\n },\n buildOutDir,\n commands,\n compile: {\n name: compileOpts.name ?? DEFAULT_BINARY_NAME,\n targets: compileOpts.targets ?? [],\n },\n compileOutDir,\n cwd,\n entry,\n include: config.include ?? [],\n }\n}\n\n/**\n * Detect the bundled entry file in a build output directory.\n *\n * tsdown may produce `index.mjs` or `index.js` depending on the project's\n * `package.json` `type` field and tsdown configuration. This function checks\n * for both candidates and returns the first one that exists on disk.\n *\n * @param outDir - Absolute path to the build output directory.\n * @returns The absolute path to the entry file, or `undefined` when none is found.\n */\nexport function detectBuildEntry(outDir: string): string | undefined {\n return ENTRY_CANDIDATES.map((name) => join(outDir, name)).find(existsSync)\n}\n","import { err, ok } from '@kidd-cli/utils/fp'\nimport { build as tsdownBuild } from 'tsdown'\n\nimport { mapToBuildConfig } from './map-config.js'\nimport { detectBuildEntry, resolveConfig } from './resolve-config.js'\nimport type { AsyncBundlerResult, BuildOutput, BuildParams } from './types.js'\n\n/**\n * Build a kidd CLI tool using tsdown.\n *\n * Resolves defaults, maps the config to tsdown's InlineConfig, and invokes the build.\n *\n * @param params - The build parameters including config and working directory.\n * @returns A result tuple with build output on success or an Error on failure.\n */\nexport async function build(params: BuildParams): AsyncBundlerResult<BuildOutput> {\n const resolved = resolveConfig(params)\n const inlineConfig = mapToBuildConfig(resolved)\n\n try {\n await tsdownBuild(inlineConfig)\n } catch (error: unknown) {\n console.error('[kidd-bundler] build error:', error)\n return err(new Error('tsdown build failed', { cause: error }))\n }\n\n const entryFile = detectBuildEntry(resolved.buildOutDir)\n\n if (!entryFile) {\n return err(new Error(`build produced no entry file in ${resolved.buildOutDir}`))\n }\n\n return ok({\n entryFile,\n outDir: resolved.buildOutDir,\n })\n}\n","import { execFile as execFileCb } from 'node:child_process'\nimport { readdirSync, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport type { CompileTarget } from '@kidd-cli/config'\nimport { err, ok } from '@kidd-cli/utils/fp'\nimport type { AsyncResult } from '@kidd-cli/utils/fp'\n\nimport { DEFAULT_COMPILE_TARGETS } from './constants.js'\nimport { detectBuildEntry, resolveConfig } from './resolve-config.js'\nimport type { CompileOutput, CompileParams, CompiledBinary } from './types.js'\n\n/**\n * Packages to externalize during `bun build --compile`.\n *\n * These are optional peer dependencies of `c12` (the config loader) that bun\n * eagerly tries to resolve even though they are behind dynamic `import()` calls\n * that never execute at runtime in a compiled CLI.\n */\nconst COMPILE_EXTERNALS: readonly string[] = ['chokidar', 'magicast', 'giget']\n\n/**\n * Human-readable labels for each compile target.\n */\nconst COMPILE_TARGET_LABELS: Readonly<Record<CompileTarget, string>> = {\n 'darwin-arm64': 'macOS Apple Silicon',\n 'darwin-x64': 'macOS Intel',\n 'linux-arm64': 'Linux ARM64',\n 'linux-x64': 'Linux x64',\n 'linux-x64-musl': 'Linux x64 (musl)',\n 'windows-arm64': 'Windows ARM64',\n 'windows-x64': 'Windows x64',\n}\n\n/**\n * Compile a kidd CLI tool into standalone binaries using `bun build --compile`.\n *\n * Expects the bundled entry to already exist in `outDir` (i.e., `build()` must\n * be run first). For each requested target (or the current platform if none\n * specified), spawns `bun build --compile` to produce a self-contained binary.\n *\n * @param params - The compile parameters including config and working directory.\n * @returns A result tuple with compile output on success or an Error on failure.\n */\nexport async function compile(params: CompileParams): AsyncResult<CompileOutput> {\n const resolved = resolveConfig(params)\n const bundledEntry = detectBuildEntry(resolved.buildOutDir)\n\n if (!bundledEntry) {\n return err(new Error(`bundled entry not found in ${resolved.buildOutDir} — run build() first`))\n }\n\n const targets: readonly CompileTarget[] = resolveTargets(resolved.compile.targets)\n const isMultiTarget = targets.length > 1\n\n const results = await Promise.all(\n targets.map(async (target) => {\n if (params.onTargetStart) {\n await params.onTargetStart(target)\n }\n\n const result = await compileSingleTarget({\n bundledEntry,\n isMultiTarget,\n name: resolved.compile.name,\n outDir: resolved.compileOutDir,\n target,\n })\n\n if (params.onTargetComplete) {\n await params.onTargetComplete(target)\n }\n\n return result\n })\n )\n\n cleanBunBuildArtifacts(resolved.cwd)\n\n const failedResult = results.find((r) => r[0] !== null)\n if (failedResult) {\n const [failedError] = failedResult\n if (failedError) {\n return err(failedError)\n }\n }\n\n const binaries: readonly CompiledBinary[] = results\n .filter((r): r is readonly [null, CompiledBinary] => r[1] !== null)\n .map(([, binary]) => binary)\n\n return ok({ binaries })\n}\n\n// ---------------------------------------------------------------------------\n\n/**\n * Compile a single target via `bun build --compile`.\n *\n * @private\n * @param params - Target compilation parameters.\n * @returns A result tuple with the compiled binary info or an error.\n */\nasync function compileSingleTarget(params: {\n readonly bundledEntry: string\n readonly outDir: string\n readonly name: string\n readonly target: CompileTarget\n readonly isMultiTarget: boolean\n}): AsyncResult<CompiledBinary> {\n const binaryName = resolveBinaryName(params.name, params.target, params.isMultiTarget)\n const outfile = join(params.outDir, binaryName)\n\n const args = [\n 'build',\n '--compile',\n params.bundledEntry,\n '--outfile',\n outfile,\n '--target',\n mapCompileTarget(params.target),\n ...COMPILE_EXTERNALS.flatMap((pkg) => ['--external', pkg]),\n ]\n\n const [execError] = await execBunBuild(args)\n if (execError) {\n return err(\n new Error(`bun build --compile failed for target ${params.target}`, { cause: execError })\n )\n }\n\n return ok({ label: resolveTargetLabel(params.target), path: outfile, target: params.target })\n}\n\n/**\n * Resolve the list of compile targets, falling back to the default set.\n *\n * When no targets are explicitly configured, defaults to linux-x64,\n * darwin-arm64, darwin-x64, and windows-x64 to cover ~95% of developers.\n *\n * @private\n * @param explicit - User-specified targets (may be empty).\n * @returns The targets to compile for.\n */\nfunction resolveTargets(explicit: readonly CompileTarget[]): readonly CompileTarget[] {\n if (explicit.length > 0) {\n return explicit\n }\n\n return DEFAULT_COMPILE_TARGETS\n}\n\n/**\n * Build the output binary name, appending the target suffix for multi-target builds.\n *\n * @private\n * @param name - Base binary name.\n * @param target - The compile target.\n * @param isMultiTarget - Whether multiple targets are being compiled.\n * @returns The resolved binary file name.\n */\nfunction resolveBinaryName(name: string, target: CompileTarget, isMultiTarget: boolean): string {\n if (isMultiTarget) {\n return `${name}-${target}`\n }\n\n return name\n}\n\n/**\n * Look up the human-readable label for a compile target.\n *\n * @param target - The compile target identifier.\n * @returns A descriptive label (e.g., \"macOS Apple Silicon\").\n */\nexport function resolveTargetLabel(target: CompileTarget): string {\n return COMPILE_TARGET_LABELS[target]\n}\n\n/**\n * Map a `CompileTarget` to Bun's `--target` string.\n *\n * Note: `linux-x64-musl` maps to `bun-linux-x64` because Bun's Linux\n * builds natively handle musl — there is no separate musl target.\n *\n * @private\n * @param target - The kidd compile target.\n * @returns The Bun target string (e.g., `'bun-darwin-arm64'`).\n */\nfunction mapCompileTarget(target: CompileTarget): string {\n if (target === 'linux-x64-musl') {\n return 'bun-linux-x64'\n }\n\n return `bun-${target}`\n}\n\n/**\n * Promisified wrapper around `execFile` to invoke `bun build`.\n *\n * @private\n * @param args - Arguments to pass to `bun`.\n * @returns A result tuple with stdout on success or an Error on failure.\n */\nfunction execBunBuild(args: readonly string[]): AsyncResult<string> {\n return new Promise((resolve) => {\n execFileCb('bun', [...args], (error, stdout) => {\n if (error) {\n resolve(err(error))\n return\n }\n\n resolve(ok(stdout))\n })\n })\n}\n\n/**\n * Remove temporary `.bun-build` files that `bun build --compile` leaves behind.\n *\n * @private\n * @param cwd - The working directory to clean.\n */\nfunction cleanBunBuildArtifacts(cwd: string): void {\n readdirSync(cwd)\n .filter((name) => name.endsWith('.bun-build'))\n .map((name) => join(cwd, name))\n .map(unlinkSync)\n}\n","import { err, ok } from '@kidd-cli/utils/fp'\nimport { build as tsdownBuild } from 'tsdown'\n\nimport { mapToWatchConfig } from './map-config.js'\nimport { resolveConfig } from './resolve-config.js'\nimport type { AsyncBundlerResult, WatchParams } from './types.js'\n\n/**\n * Start a watch-mode build for a kidd CLI tool using tsdown.\n *\n * The returned promise resolves only when tsdown's watch terminates (typically on process exit).\n * tsdown's `build()` with `watch: true` runs indefinitely.\n *\n * @param params - The watch parameters including config, working directory, and optional success callback.\n * @returns A result tuple with void on success or an Error on failure.\n */\nexport async function watch(params: WatchParams): AsyncBundlerResult<void> {\n const resolved = resolveConfig(params)\n const watchConfig = mapToWatchConfig({\n config: resolved,\n onSuccess: params.onSuccess,\n })\n\n try {\n await tsdownBuild(watchConfig)\n } catch (error: unknown) {\n return err(new Error('tsdown watch failed', { cause: error }))\n }\n\n return ok()\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@kidd-cli/bundler",
3
+ "version": "0.1.0",
4
+ "description": "Programmatic bundler for kidd CLI tools powered by tsdown",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "dependencies": {
17
+ "es-toolkit": "^1.45.0",
18
+ "ts-pattern": "^5.9.0",
19
+ "tsdown": "0.21.0-beta.2",
20
+ "zod": "^4.3.6",
21
+ "@kidd-cli/config": "0.1.0",
22
+ "@kidd-cli/utils": "0.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^25.3.3",
26
+ "typescript": "^5.9.3",
27
+ "vitest": "^4.0.18"
28
+ },
29
+ "scripts": {
30
+ "build": "tsdown",
31
+ "typecheck": "tsgo --noEmit",
32
+ "lint": "oxlint --ignore-pattern node_modules",
33
+ "lint:fix": "oxlint --fix --ignore-pattern node_modules",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest"
36
+ }
37
+ }