@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 +21 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +849 -0
- package/dist/index.js.map +1 -0
- package/package.json +37 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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":";;;;;;;;UAeU,0BAAA;EAAA,SACC,WAAA;EAAA,SACA,aAAA;AAAA;;;AAsBX;;;;;;;;;;;;;AC7BA;;;;iBD6BgB,oBAAA,CAAqB,MAAA,EAAQ,0BAAA,GAA6B,QAAA,CAAS,MAAA;;;;;;UC7BlE,oBAAA;EAAA,SACN,MAAA;EAAA,SACA,MAAA;EAAA,SACA,SAAA;EAAA,SACA,QAAA;AAAA;;;;UAMM,sBAAA;EAAA,SACN,OAAA,WAAkB,aAAA;EAAA,SAClB,IAAA;AAAA;;;;UAMM,qBAAA;EAAA,SACN,KAAA;EAAA,SACA,QAAA;EAAA,SACA,WAAA;EAAA,SACA,aAAA;EAAA,SACA,KAAA,EAAO,oBAAA;EAAA,SACP,OAAA,EAAS,sBAAA;EAAA,SACT,OAAA;EAAA,SACA,GAAA;AAAA;;;AAhBX;KA0BY,aAAA,MAAmB,MAAA,CAAO,CAAA,EAAG,KAAA;;;;KAK7B,kBAAA,MAAwB,WAAA,CAAY,CAAA,EAAG,KAAA;;;;UASlC,WAAA;EAAA,SACN,MAAA;EAAA,SACA,SAAA;AAAA;;;;UAMM,cAAA;EAAA,SACN,MAAA,EAAQ,aAAA;EAAA,SACR,KAAA;EAAA,SACA,IAAA;AAAA;;;;UAMM,aAAA;EAAA,SACN,QAAA,WAAmB,cAAA;AAAA;;;;UAUb,WAAA;EAAA,SACN,MAAA,EAAQ,UAAA;EAAA,SACR,GAAA;AAAA;;;;UAMM,WAAA;EAAA,SACN,MAAA,EAAQ,UAAA;EAAA,SACR,GAAA;EAAA,SACA,SAAA,gBAAyB,OAAA;AAAA;;;;UAMnB,aAAA;EAAA,SACN,MAAA,EAAQ,UAAA;EAAA,SACR,GAAA;EAAA,SACA,aAAA,IAAiB,MAAA,EAAQ,aAAA,YAAyB,OAAA;EAAA,SAClD,gBAAA,IAAoB,MAAA,EAAQ,aAAA,YAAyB,OAAA;AAAA;;;AAjDhE;UA2DiB,WAAA;EAAA,SACN,IAAA;EAAA,SACA,QAAA;AAAA;AArDX;;;AAAA,UA2DiB,UAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EAAA,SACA,KAAA,WAAgB,WAAA;EAAA,SAChB,IAAA,WAAe,UAAA;AAAA;;AAtD1B;;UA4DiB,UAAA;EAAA,SACN,KAAA,WAAgB,WAAA;EAAA,SAChB,IAAA,WAAe,UAAA;AAAA;;;;;;;AD3II;;;;iBEeR,KAAA,CAAM,MAAA,EAAQ,WAAA,GAAc,kBAAA,CAAmB,WAAA;;;;;AFfvC;;;;;AAuC9B;;;iBGKsB,OAAA,CAAQ,MAAA,EAAQ,aAAA,GAAgB,WAAA,CAAY,aAAA;;;;;;;iBAmIlD,kBAAA,CAAmB,MAAA,EAAQ,eAAA;;;;;;UC1KjC,8BAAA;EAAA,SACC,IAAA,EAAM,UAAA;EAAA,SACN,aAAA;AAAA;;;AJgCX;;UIzBiB,qBAAA;EAAA,SACN,OAAA;EAAA,SACA,MAAA;AAAA;;;;;;;;AHNX;;;;iBGoBgB,wBAAA,CAAyB,MAAA,EAAQ,8BAAA;;;;;;AHVjD;;;;;;;;;AAQA;;iBGmCgB,uBAAA,CACd,MAAA,EAAQ,8BAAA,GACP,qBAAA;;;;;;AJjE2B;;;iBKcd,gBAAA,CAAiB,MAAA,EAAQ,qBAAA,GAAwB,YAAA;;ALyBjE;;;;;iBKkBgB,gBAAA,CAAiB,MAAA;EAAA,SACtB,MAAA,EAAQ,qBAAA;EAAA,SACR,SAAA,gBAAyB,OAAA;AAAA,IAChC,YAAA;;;;;;AL5D0B;;;;;AAuC9B;;iBMRgB,uBAAA,CACd,KAAA,YAAiB,gBAAA,eAChB,gBAAA;;;;;;;;;;iBAiBa,aAAA,CAAc,MAAA;EAAA,SACnB,MAAA,EAAQ,YAAA;EAAA,SACR,GAAA;AAAA,IACP,qBAAA;;;;;;;;ALjCJ;;;iBK2EgB,gBAAA,CAAiB,MAAA;;;;;;;AN/FH;;;;;AAuC9B;iBOpBsB,eAAA,CAAgB,GAAA,WAAc,OAAA,CAAQ,UAAA;;;;;;;APnB9B;;;;;iBQgBR,KAAA,CAAM,MAAA,EAAQ,WAAA,GAAc,kBAAA"}
|
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
|
+
}
|