thinkwell 0.5.4 → 0.5.6

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.
Files changed (44) hide show
  1. package/dist/agent.d.ts.map +1 -1
  2. package/dist/agent.js +207 -279
  3. package/dist/agent.js.map +1 -1
  4. package/dist/build.js +44 -98
  5. package/dist/cli/build.js +92 -227
  6. package/dist/cli/bundle.js +570 -1136
  7. package/dist/cli/check.js +125 -214
  8. package/dist/cli/commands.js +63 -177
  9. package/dist/cli/compiler-host.js +81 -190
  10. package/dist/cli/dependency-check.js +125 -269
  11. package/dist/cli/dependency-errors.js +12 -84
  12. package/dist/cli/fmt.js +1 -13
  13. package/dist/cli/init-command.js +21 -68
  14. package/dist/cli/init.js +90 -220
  15. package/dist/cli/loader.js +95 -361
  16. package/dist/cli/new-command.js +25 -73
  17. package/dist/cli/package-manager.js +50 -117
  18. package/dist/cli/schema.js +89 -245
  19. package/dist/cli/workspace.js +92 -226
  20. package/dist/connectors/index.js +1 -7
  21. package/dist/generated/features.d.ts +5 -0
  22. package/dist/generated/features.d.ts.map +1 -0
  23. package/dist/generated/features.js +4 -0
  24. package/dist/generated/features.js.map +1 -0
  25. package/dist/index.js +0 -5
  26. package/dist/schema.js +3 -36
  27. package/dist/session.js +50 -82
  28. package/dist/think-builder.d.ts.map +1 -1
  29. package/dist/think-builder.js +269 -370
  30. package/dist/think-builder.js.map +1 -1
  31. package/dist/thought-event.d.ts +1 -0
  32. package/dist/thought-event.d.ts.map +1 -1
  33. package/dist/thought-event.js +0 -1
  34. package/dist/thought-stream.js +60 -96
  35. package/dist-pkg/acp.cjs +13385 -1876
  36. package/dist-pkg/cli-build.cjs +171 -369
  37. package/dist-pkg/cli-bundle.cjs +289 -690
  38. package/dist-pkg/cli-check.cjs +202 -415
  39. package/dist-pkg/cli-dependency-check.cjs +39 -82
  40. package/dist-pkg/cli-dependency-errors.cjs +9 -41
  41. package/dist-pkg/cli-loader.cjs +90 -173
  42. package/dist-pkg/protocol.cjs +2 -8
  43. package/dist-pkg/thinkwell.cjs +876 -1842
  44. package/package.json +7 -6
@@ -1,14 +1,4 @@
1
- /**
2
- * Bundle command for creating self-contained executables from user scripts.
3
- *
4
- * This module provides the `thinkwell bundle` command that compiles user scripts
5
- * into standalone binaries using the same pkg-based tooling as the thinkwell CLI.
6
- *
7
- * The bundle process follows a two-stage pipeline:
8
- * 1. **Pre-bundle with esbuild** - Bundle user script + thinkwell packages into CJS
9
- * 2. **Compile with pkg** - Create self-contained binary with Node.js runtime
10
- */
11
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync, rmSync, copyFileSync, chmodSync, createWriteStream, watch as fsWatch, } from "node:fs";
1
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync, rmSync, copyFileSync, chmodSync, createWriteStream, watch as fsWatch } from "node:fs";
12
2
  import { dirname, resolve, basename, join, isAbsolute } from "node:path";
13
3
  import { fileURLToPath } from "node:url";
14
4
  import { styleText } from "node:util";
@@ -20,355 +10,185 @@ import * as esbuild from "esbuild";
20
10
  import { transformJsonSchemas, hasJsonSchemaMarkers } from "./schema.js";
21
11
  import { findProjectRoot, checkDependencies } from "./dependency-check.js";
22
12
  import { hasMissingDeps, formatMissingDepsError } from "./dependency-errors.js";
23
- const SPINNER_FRAMES = ["", "", "", "", "", "", "", "", "", ""];
24
- const SPINNER_INTERVAL = 80;
13
+ const SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"], SPINNER_INTERVAL = 80;
25
14
  function createSpinnerImpl(options) {
26
- let text = options.text;
27
- let interval;
28
- let frameIndex = 0;
29
- const isSilent = options.isSilent ?? false;
30
- // Check TTY lazily - only when we actually try to render
31
- const isTTY = () => process.stderr.isTTY === true;
32
- const clearLine = () => {
33
- if (isTTY()) {
34
- process.stderr.write("\r\x1b[K");
35
- }
36
- };
37
- const render = () => {
38
- if (isSilent)
39
- return;
40
- if (isTTY()) {
41
- const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length];
42
- process.stderr.write(`\r${frame} ${text}`);
43
- frameIndex++;
44
- }
45
- };
46
- const spinner = {
47
- get text() {
48
- return text;
49
- },
50
- set text(value) {
51
- text = value;
52
- },
53
- start(newText) {
54
- if (newText)
55
- text = newText;
56
- if (isSilent)
57
- return this;
58
- if (isTTY()) {
59
- render();
60
- interval = setInterval(render, SPINNER_INTERVAL);
61
- }
62
- else {
63
- // Non-TTY: just print the text with a dash prefix
64
- process.stderr.write(`- ${text}\n`);
65
- }
66
- return this;
67
- },
68
- stop() {
69
- if (interval) {
70
- clearInterval(interval);
71
- interval = undefined;
72
- }
73
- clearLine();
74
- return this;
75
- },
76
- succeed(successText) {
77
- if (interval) {
78
- clearInterval(interval);
79
- interval = undefined;
80
- }
81
- if (isSilent)
82
- return this;
83
- const finalText = successText ?? text;
84
- if (isTTY()) {
85
- process.stderr.write(`\r\x1b[K✔ ${finalText}\n`);
86
- }
87
- else {
88
- process.stderr.write(`✔ ${finalText}\n`);
89
- }
90
- return this;
91
- },
92
- fail(failText) {
93
- if (interval) {
94
- clearInterval(interval);
95
- interval = undefined;
96
- }
97
- if (isSilent)
98
- return this;
99
- const finalText = failText ?? text;
100
- if (isTTY()) {
101
- process.stderr.write(`\r\x1b[K✖ ${finalText}\n`);
102
- }
103
- else {
104
- process.stderr.write(`✖ ${finalText}\n`);
105
- }
106
- return this;
107
- },
108
- };
109
- return spinner;
15
+ let text = options.text, interval, frameIndex = 0;
16
+ const isSilent = options.isSilent ?? !1, isTTY = () => process.stderr.isTTY === !0, clearLine = () => {
17
+ isTTY() && process.stderr.write("\r\x1B[K");
18
+ }, render = () => {
19
+ if (!isSilent && isTTY()) {
20
+ const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length];
21
+ process.stderr.write(`\r${frame} ${text}`), frameIndex++;
22
+ }
23
+ };
24
+ return {
25
+ get text() {
26
+ return text;
27
+ },
28
+ set text(value) {
29
+ text = value;
30
+ },
31
+ start(newText) {
32
+ return newText && (text = newText), isSilent ? this : (isTTY() ? (render(), interval = setInterval(render, SPINNER_INTERVAL)) : process.stderr.write(`- ${text}
33
+ `), this);
34
+ },
35
+ stop() {
36
+ return interval && (clearInterval(interval), interval = void 0), clearLine(), this;
37
+ },
38
+ succeed(successText) {
39
+ if (interval && (clearInterval(interval), interval = void 0), isSilent)
40
+ return this;
41
+ const finalText = successText ?? text;
42
+ return isTTY() ? process.stderr.write(`\r\x1B[K\u2714 ${finalText}
43
+ `) : process.stderr.write(`\u2714 ${finalText}
44
+ `), this;
45
+ },
46
+ fail(failText) {
47
+ if (interval && (clearInterval(interval), interval = void 0), isSilent)
48
+ return this;
49
+ const finalText = failText ?? text;
50
+ return isTTY() ? process.stderr.write(`\r\x1B[K\u2716 ${finalText}
51
+ `) : process.stderr.write(`\u2716 ${finalText}
52
+ `), this;
53
+ }
54
+ };
110
55
  }
111
- // Handle both ESM and CJS contexts for __dirname
112
- // When bundled to CJS, import.meta.url won't work, but global __dirname will
113
- const __dirname = typeof import.meta?.url === "string"
114
- ? dirname(fileURLToPath(import.meta.url))
115
- : globalThis.__dirname || dirname(process.argv[1]);
116
- // Map user-friendly target names to pkg target names
117
- const TARGET_MAP = {
118
- "darwin-arm64": "node24-macos-arm64",
119
- "darwin-x64": "node24-macos-x64",
120
- "linux-x64": "node24-linux-x64",
121
- "linux-arm64": "node24-linux-arm64",
56
+ const __dirname = typeof import.meta?.url == "string" ? dirname(fileURLToPath(import.meta.url)) : globalThis.__dirname || dirname(process.argv[1]), TARGET_MAP = {
57
+ "darwin-arm64": "node24-macos-arm64",
58
+ "darwin-x64": "node24-macos-x64",
59
+ "linux-x64": "node24-linux-x64",
60
+ "linux-arm64": "node24-linux-arm64"
122
61
  };
123
- // Detect the current host platform
124
62
  function detectHostTarget() {
125
- const platform = process.platform;
126
- const arch = process.arch;
127
- if (platform === "darwin" && arch === "arm64")
128
- return "darwin-arm64";
129
- if (platform === "darwin" && arch === "x64")
130
- return "darwin-x64";
131
- if (platform === "linux" && arch === "x64")
132
- return "linux-x64";
133
- if (platform === "linux" && arch === "arm64")
134
- return "linux-arm64";
135
- throw new Error(`Unsupported platform: ${platform}-${arch}. ` +
136
- `Supported platforms: darwin-arm64, darwin-x64, linux-x64, linux-arm64`);
63
+ const platform = process.platform, arch = process.arch;
64
+ if (platform === "darwin" && arch === "arm64")
65
+ return "darwin-arm64";
66
+ if (platform === "darwin" && arch === "x64")
67
+ return "darwin-x64";
68
+ if (platform === "linux" && arch === "x64")
69
+ return "linux-x64";
70
+ if (platform === "linux" && arch === "arm64")
71
+ return "linux-arm64";
72
+ throw new Error(`Unsupported platform: ${platform}-${arch}. Supported platforms: darwin-arm64, darwin-x64, linux-x64, linux-arm64`);
137
73
  }
138
- /**
139
- * Read build configuration from package.json in the given directory.
140
- * Returns undefined if no configuration is found.
141
- */
142
74
  function readPackageJsonConfig(dir) {
143
- const pkgPath = join(dir, "package.json");
144
- if (!existsSync(pkgPath)) {
145
- return undefined;
146
- }
75
+ const pkgPath = join(dir, "package.json");
76
+ if (existsSync(pkgPath))
147
77
  try {
148
- const content = readFileSync(pkgPath, "utf-8");
149
- const pkg = JSON.parse(content);
150
- // Look for "thinkwell.bundle" configuration
151
- const config = pkg?.thinkwell?.bundle;
152
- if (!config || typeof config !== "object") {
153
- return undefined;
154
- }
155
- // Validate and extract configuration
156
- const result = {};
157
- if (typeof config.output === "string") {
158
- result.output = config.output;
159
- }
160
- if (Array.isArray(config.targets)) {
161
- const validTargets = ["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64", "host"];
162
- result.targets = config.targets.filter((t) => typeof t === "string" && validTargets.includes(t));
163
- }
164
- if (Array.isArray(config.include)) {
165
- result.include = config.include.filter((i) => typeof i === "string");
166
- }
167
- if (Array.isArray(config.external)) {
168
- result.external = config.external.filter((e) => typeof e === "string");
169
- }
170
- if (typeof config.minify === "boolean") {
171
- result.minify = config.minify;
172
- }
173
- return result;
174
- }
175
- catch {
176
- // Ignore JSON parse errors
177
- return undefined;
78
+ const content = readFileSync(pkgPath, "utf-8"), config = JSON.parse(content)?.thinkwell?.bundle;
79
+ if (!config || typeof config != "object")
80
+ return;
81
+ const result = {};
82
+ if (typeof config.output == "string" && (result.output = config.output), Array.isArray(config.targets)) {
83
+ const validTargets = ["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64", "host"];
84
+ result.targets = config.targets.filter((t) => typeof t == "string" && validTargets.includes(t));
85
+ }
86
+ return Array.isArray(config.include) && (result.include = config.include.filter((i) => typeof i == "string")), Array.isArray(config.external) && (result.external = config.external.filter((e) => typeof e == "string")), typeof config.minify == "boolean" && (result.minify = config.minify), result;
87
+ } catch {
88
+ return;
178
89
  }
179
90
  }
180
- /**
181
- * Merge package.json configuration with CLI options.
182
- * CLI options take precedence over package.json configuration.
183
- */
184
91
  function mergeWithPackageConfig(options, entryDir) {
185
- const pkgConfig = readPackageJsonConfig(entryDir);
186
- if (!pkgConfig) {
187
- return options;
188
- }
189
- // CLI options take precedence - only use package.json defaults for unset values
190
- return {
191
- ...options,
192
- output: options.output ?? pkgConfig.output,
193
- targets: options.targets && options.targets.length > 0
194
- ? options.targets
195
- : pkgConfig.targets ?? options.targets,
196
- include: [
197
- ...(pkgConfig.include || []),
198
- ...(options.include || []),
199
- ],
200
- external: [
201
- ...(pkgConfig.external || []),
202
- ...(options.external || []),
203
- ],
204
- minify: options.minify ?? pkgConfig.minify,
205
- };
92
+ const pkgConfig = readPackageJsonConfig(entryDir);
93
+ return pkgConfig ? {
94
+ ...options,
95
+ output: options.output ?? pkgConfig.output,
96
+ targets: options.targets && options.targets.length > 0 ? options.targets : pkgConfig.targets ?? options.targets,
97
+ include: [
98
+ ...pkgConfig.include || [],
99
+ ...options.include || []
100
+ ],
101
+ external: [
102
+ ...pkgConfig.external || [],
103
+ ...options.external || []
104
+ ],
105
+ minify: options.minify ?? pkgConfig.minify
106
+ } : options;
206
107
  }
207
- /**
208
- * Parse and validate build options from command-line arguments.
209
- */
210
108
  export function parseBundleArgs(args) {
211
- const options = {
212
- entry: "",
213
- targets: [],
214
- include: [],
215
- external: [],
216
- };
217
- let i = 0;
218
- while (i < args.length) {
219
- const arg = args[i];
220
- if (arg === "-o" || arg === "--output") {
221
- i++;
222
- if (i >= args.length) {
223
- throw new Error("Missing value for --output");
224
- }
225
- options.output = args[i];
226
- }
227
- else if (arg === "-t" || arg === "--target") {
228
- i++;
229
- if (i >= args.length) {
230
- throw new Error("Missing value for --target");
231
- }
232
- const target = args[i];
233
- const validTargets = ["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64", "host"];
234
- if (!validTargets.includes(target)) {
235
- throw new Error(`Invalid target '${target}'. Valid targets: ${validTargets.join(", ")}`);
236
- }
237
- options.targets.push(target);
238
- }
239
- else if (arg === "--include") {
240
- i++;
241
- if (i >= args.length) {
242
- throw new Error("Missing value for --include");
243
- }
244
- options.include.push(args[i]);
245
- }
246
- else if (arg === "--external" || arg === "-e") {
247
- i++;
248
- if (i >= args.length) {
249
- throw new Error("Missing value for --external");
250
- }
251
- options.external.push(args[i]);
252
- }
253
- else if (arg === "--verbose" || arg === "-v") {
254
- options.verbose = true;
255
- }
256
- else if (arg === "--quiet" || arg === "-q") {
257
- options.quiet = true;
258
- }
259
- else if (arg === "--dry-run" || arg === "-n") {
260
- options.dryRun = true;
261
- }
262
- else if (arg === "--minify" || arg === "-m") {
263
- options.minify = true;
264
- }
265
- else if (arg === "--watch" || arg === "-w") {
266
- options.watch = true;
267
- }
268
- else if (arg.startsWith("-")) {
269
- throw new Error(`Unknown option: ${arg}`);
270
- }
271
- else {
272
- // Positional argument - entry file
273
- if (options.entry) {
274
- throw new Error(`Unexpected argument: ${arg}`);
275
- }
276
- options.entry = arg;
277
- }
278
- i++;
279
- }
280
- // Validate entry
281
- if (!options.entry) {
282
- throw new Error("No entry file specified");
283
- }
284
- // Default target is host
285
- if (options.targets.length === 0) {
286
- options.targets = ["host"];
109
+ const options = {
110
+ entry: "",
111
+ targets: [],
112
+ include: [],
113
+ external: []
114
+ };
115
+ let i = 0;
116
+ for (; i < args.length; ) {
117
+ const arg = args[i];
118
+ if (arg === "-o" || arg === "--output") {
119
+ if (i++, i >= args.length)
120
+ throw new Error("Missing value for --output");
121
+ options.output = args[i];
122
+ } else if (arg === "-t" || arg === "--target") {
123
+ if (i++, i >= args.length)
124
+ throw new Error("Missing value for --target");
125
+ const target = args[i], validTargets = ["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64", "host"];
126
+ if (!validTargets.includes(target))
127
+ throw new Error(`Invalid target '${target}'. Valid targets: ${validTargets.join(", ")}`);
128
+ options.targets.push(target);
129
+ } else if (arg === "--include") {
130
+ if (i++, i >= args.length)
131
+ throw new Error("Missing value for --include");
132
+ options.include.push(args[i]);
133
+ } else if (arg === "--external" || arg === "-e") {
134
+ if (i++, i >= args.length)
135
+ throw new Error("Missing value for --external");
136
+ options.external.push(args[i]);
137
+ } else if (arg === "--verbose" || arg === "-v")
138
+ options.verbose = !0;
139
+ else if (arg === "--quiet" || arg === "-q")
140
+ options.quiet = !0;
141
+ else if (arg === "--dry-run" || arg === "-n")
142
+ options.dryRun = !0;
143
+ else if (arg === "--minify" || arg === "-m")
144
+ options.minify = !0;
145
+ else if (arg === "--watch" || arg === "-w")
146
+ options.watch = !0;
147
+ else {
148
+ if (arg.startsWith("-"))
149
+ throw new Error(`Unknown option: ${arg}`);
150
+ if (options.entry)
151
+ throw new Error(`Unexpected argument: ${arg}`);
152
+ options.entry = arg;
287
153
  }
288
- return options;
154
+ i++;
155
+ }
156
+ if (!options.entry)
157
+ throw new Error("No entry file specified");
158
+ return options.targets.length === 0 && (options.targets = ["host"]), options;
289
159
  }
290
- /**
291
- * Initialize the build context with resolved paths and validated inputs.
292
- */
293
160
  function initBundleContext(options) {
294
- // Resolve entry path
295
- const entryPath = isAbsolute(options.entry)
296
- ? options.entry
297
- : resolve(process.cwd(), options.entry);
298
- if (!existsSync(entryPath)) {
299
- const suggestion = options.entry.endsWith(".ts") || options.entry.endsWith(".js")
300
- ? ""
301
- : "\n Did you mean to add a .ts or .js extension?";
302
- throw new Error(`Entry file not found: ${options.entry}${suggestion}\n` +
303
- ` Working directory: ${process.cwd()}`);
304
- }
305
- const entryBasename = basename(entryPath).replace(/\.(ts|js|mts|mjs|cts|cjs)$/, "");
306
- const entryDir = dirname(entryPath);
307
- // Merge CLI options with package.json configuration
308
- // Check both entry directory and current working directory for package.json
309
- let mergedOptions = mergeWithPackageConfig(options, entryDir);
310
- if (entryDir !== process.cwd()) {
311
- mergedOptions = mergeWithPackageConfig(mergedOptions, process.cwd());
312
- }
313
- // Create build directory in system temp directory using mkdtempSync for atomicity
314
- const buildDir = mkdtempSync(join(tmpdir(), `thinkwell-bundle-${entryBasename}-`));
315
- // Find the thinkwell dist-pkg directory
316
- // When running from npm install: node_modules/thinkwell/dist-pkg
317
- // When running from source: packages/thinkwell/dist-pkg
318
- const thinkwellDistPkg = resolve(__dirname, "../../dist-pkg");
319
- if (!existsSync(thinkwellDistPkg)) {
320
- throw new Error(`Thinkwell dist-pkg not found at ${thinkwellDistPkg}.\n` +
321
- ` This may indicate a corrupted installation.\n` +
322
- ` Try reinstalling thinkwell: npm install thinkwell`);
323
- }
324
- // Resolve "host" targets to actual platform
325
- const resolvedTargets = mergedOptions.targets.map((t) => t === "host" ? detectHostTarget() : t);
326
- // Deduplicate targets
327
- const uniqueTargets = [...new Set(resolvedTargets)];
328
- return {
329
- entryPath,
330
- entryBasename,
331
- entryDir,
332
- buildDir,
333
- thinkwellDistPkg,
334
- resolvedTargets: uniqueTargets,
335
- options: mergedOptions,
336
- };
161
+ const entryPath = isAbsolute(options.entry) ? options.entry : resolve(process.cwd(), options.entry);
162
+ if (!existsSync(entryPath)) {
163
+ const suggestion = options.entry.endsWith(".ts") || options.entry.endsWith(".js") ? "" : `
164
+ Did you mean to add a .ts or .js extension?`;
165
+ throw new Error(`Entry file not found: ${options.entry}${suggestion}
166
+ Working directory: ${process.cwd()}`);
167
+ }
168
+ const entryBasename = basename(entryPath).replace(/\.(ts|js|mts|mjs|cts|cjs)$/, ""), entryDir = dirname(entryPath);
169
+ let mergedOptions = mergeWithPackageConfig(options, entryDir);
170
+ entryDir !== process.cwd() && (mergedOptions = mergeWithPackageConfig(mergedOptions, process.cwd()));
171
+ const buildDir = mkdtempSync(join(tmpdir(), `thinkwell-bundle-${entryBasename}-`)), thinkwellDistPkg = resolve(__dirname, "../../dist-pkg");
172
+ if (!existsSync(thinkwellDistPkg))
173
+ throw new Error(`Thinkwell dist-pkg not found at ${thinkwellDistPkg}.
174
+ This may indicate a corrupted installation.
175
+ Try reinstalling thinkwell: npm install thinkwell`);
176
+ const resolvedTargets = mergedOptions.targets.map((t) => t === "host" ? detectHostTarget() : t), uniqueTargets = [...new Set(resolvedTargets)];
177
+ return {
178
+ entryPath,
179
+ entryBasename,
180
+ entryDir,
181
+ buildDir,
182
+ thinkwellDistPkg,
183
+ resolvedTargets: uniqueTargets,
184
+ options: mergedOptions
185
+ };
337
186
  }
338
- /**
339
- * Generate the output path for a given target.
340
- */
341
187
  function getOutputPath(ctx, target) {
342
- if (ctx.options.output) {
343
- if (ctx.resolvedTargets.length === 1) {
344
- // Single target: use exact output path
345
- return isAbsolute(ctx.options.output)
346
- ? ctx.options.output
347
- : resolve(process.cwd(), ctx.options.output);
348
- }
349
- else {
350
- // Multiple targets: append target suffix
351
- const base = isAbsolute(ctx.options.output)
352
- ? ctx.options.output
353
- : resolve(process.cwd(), ctx.options.output);
354
- return `${base}-${target}`;
355
- }
356
- }
357
- else {
358
- // Default: <entry-basename>-<target> in current directory
359
- return resolve(process.cwd(), `${ctx.entryBasename}-${target}`);
360
- }
188
+ return ctx.options.output ? ctx.resolvedTargets.length === 1 ? isAbsolute(ctx.options.output) ? ctx.options.output : resolve(process.cwd(), ctx.options.output) : `${isAbsolute(ctx.options.output) ? ctx.options.output : resolve(process.cwd(), ctx.options.output)}-${target}` : resolve(process.cwd(), `${ctx.entryBasename}-${target}`);
361
189
  }
362
- /**
363
- * Generate the wrapper entry point that sets up global.__bundled__.
364
- *
365
- * This creates a CJS file that:
366
- * 1. Loads the pre-bundled thinkwell packages
367
- * 2. Registers them in global.__bundled__
368
- * 3. Loads and runs the user's bundled code
369
- */
370
190
  function generateWrapperSource(userBundlePath) {
371
- return `#!/usr/bin/env node
191
+ return `#!/usr/bin/env node
372
192
  /**
373
193
  * Generated wrapper for thinkwell bundle.
374
194
  * This file is auto-generated - do not edit.
@@ -389,36 +209,24 @@ global.__bundled__ = {
389
209
  require('./${basename(userBundlePath)}');
390
210
  `;
391
211
  }
392
- /**
393
- * Stage 1: Bundle user script with esbuild.
394
- *
395
- * This bundles the user's entry point along with all its dependencies
396
- * into a single CJS file. The thinkwell packages are marked as external
397
- * since they'll be provided via global.__bundled__.
398
- */
399
212
  async function bundleUserScript(ctx) {
400
- const outputFile = join(ctx.buildDir, `${ctx.entryBasename}-bundle.cjs`);
401
- if (ctx.options.verbose) {
402
- console.log(` Bundling ${ctx.entryPath}...`);
403
- }
404
- // Note: When running from a compiled binary, ESBUILD_BINARY_PATH is set
405
- // by main-pkg.cjs before this module loads.
406
- try {
407
- // Combine Node built-ins with user-specified external packages
408
- const externalPackages = ["node:*", ...(ctx.options.external || [])];
409
- await esbuild.build({
410
- entryPoints: [ctx.entryPath],
411
- bundle: true,
412
- platform: "node",
413
- format: "cjs",
414
- outfile: outputFile,
415
- // External: Node built-ins and user-specified packages
416
- external: externalPackages,
417
- // Mark thinkwell packages as external - they're provided via global.__bundled__
418
- // But actually, we need to transform the imports, so let's bundle them
419
- // and use a banner to set up the module aliases
420
- banner: {
421
- js: `
213
+ const outputFile = join(ctx.buildDir, `${ctx.entryBasename}-bundle.cjs`);
214
+ ctx.options.verbose && console.log(` Bundling ${ctx.entryPath}...`);
215
+ try {
216
+ const externalPackages = ["node:*", ...ctx.options.external || []];
217
+ await esbuild.build({
218
+ entryPoints: [ctx.entryPath],
219
+ bundle: !0,
220
+ platform: "node",
221
+ format: "cjs",
222
+ outfile: outputFile,
223
+ // External: Node built-ins and user-specified packages
224
+ external: externalPackages,
225
+ // Mark thinkwell packages as external - they're provided via global.__bundled__
226
+ // But actually, we need to transform the imports, so let's bundle them
227
+ // and use a banner to set up the module aliases
228
+ banner: {
229
+ js: `
422
230
  // Alias thinkwell packages to global.__bundled__
423
231
  const __origRequire = require;
424
232
  require = function(id) {
@@ -437,821 +245,447 @@ require.resolve = __origRequire.resolve;
437
245
  require.cache = __origRequire.cache;
438
246
  require.extensions = __origRequire.extensions;
439
247
  require.main = __origRequire.main;
440
- `,
441
- },
442
- // Resolve thinkwell imports to bundled versions during bundle time
443
- plugins: [
444
- // Transform @JSONSchema types into namespace declarations with schema providers
445
- {
446
- name: "jsonschema-transformer",
447
- setup(build) {
448
- build.onLoad({ filter: /\.(ts|tsx|mts|cts)$/ }, async (args) => {
449
- // Skip node_modules
450
- if (args.path.includes("node_modules")) {
451
- return null;
452
- }
453
- const source = readFileSync(args.path, "utf-8");
454
- // Fast path: skip files without @JSONSchema markers
455
- if (!hasJsonSchemaMarkers(source)) {
456
- return null;
457
- }
458
- // Transform the source to inject schema namespaces
459
- const transformed = transformJsonSchemas(args.path, source, ctx.projectDir);
460
- return {
461
- contents: transformed,
462
- loader: args.path.endsWith(".tsx") ? "tsx" : "ts",
463
- };
464
- });
465
- },
466
- },
467
- {
468
- name: "thinkwell-resolver",
469
- setup(build) {
470
- // Mark thinkwell packages as external - provided via global.__bundled__ at runtime
471
- build.onResolve({ filter: /^(thinkwell|@thinkwell\/(acp|protocol))$/ }, (args) => {
472
- return { path: args.path, external: true };
473
- });
474
- },
475
- },
476
- ],
477
- sourcemap: false,
478
- minify: ctx.options.minify ?? false,
479
- keepNames: !ctx.options.minify, // Keep names unless minifying
480
- target: "node24",
481
- logLevel: ctx.options.verbose ? "info" : "silent",
482
- });
483
- }
484
- catch (error) {
485
- // Provide helpful error messages for common failures
486
- const message = error instanceof Error ? error.message : String(error);
487
- if (message.includes("Could not resolve")) {
488
- const match = message.match(/Could not resolve "([^"]+)"/);
489
- const moduleName = match ? match[1] : "unknown module";
490
- throw new Error(`Could not resolve dependency "${moduleName}".\n` +
491
- ` Make sure all dependencies are installed: npm install\n` +
492
- ` If this is a dev dependency, it may need to be a regular dependency.`);
493
- }
494
- if (message.includes("No loader is configured")) {
495
- throw new Error(`Unsupported file type in import.\n` +
496
- ` esbuild cannot bundle this file type by default.\n` +
497
- ` Consider using --include to embed the file as an asset instead.`);
498
- }
499
- throw error;
500
- }
501
- return outputFile;
248
+ `
249
+ },
250
+ // Resolve thinkwell imports to bundled versions during bundle time
251
+ plugins: [
252
+ // Transform @JSONSchema types into namespace declarations with schema providers
253
+ {
254
+ name: "jsonschema-transformer",
255
+ setup(build) {
256
+ build.onLoad({ filter: /\.(ts|tsx|mts|cts)$/ }, async (args) => {
257
+ if (args.path.includes("node_modules"))
258
+ return null;
259
+ const source = readFileSync(args.path, "utf-8");
260
+ return hasJsonSchemaMarkers(source) ? {
261
+ contents: transformJsonSchemas(args.path, source, ctx.projectDir),
262
+ loader: args.path.endsWith(".tsx") ? "tsx" : "ts"
263
+ } : null;
264
+ });
265
+ }
266
+ },
267
+ {
268
+ name: "thinkwell-resolver",
269
+ setup(build) {
270
+ build.onResolve({ filter: /^(thinkwell|@thinkwell\/(acp|protocol))$/ }, (args) => ({ path: args.path, external: !0 }));
271
+ }
272
+ }
273
+ ],
274
+ sourcemap: !1,
275
+ minify: ctx.options.minify ?? !1,
276
+ keepNames: !ctx.options.minify,
277
+ // Keep names unless minifying
278
+ target: "node24",
279
+ logLevel: ctx.options.verbose ? "info" : "silent"
280
+ });
281
+ } catch (error) {
282
+ const message = error instanceof Error ? error.message : String(error);
283
+ if (message.includes("Could not resolve")) {
284
+ const match = message.match(/Could not resolve "([^"]+)"/), moduleName = match ? match[1] : "unknown module";
285
+ throw new Error(`Could not resolve dependency "${moduleName}".
286
+ Make sure all dependencies are installed: npm install
287
+ If this is a dev dependency, it may need to be a regular dependency.`);
288
+ }
289
+ throw message.includes("No loader is configured") ? new Error(`Unsupported file type in import.
290
+ esbuild cannot bundle this file type by default.
291
+ Consider using --include to embed the file as an asset instead.`) : error;
292
+ }
293
+ return outputFile;
502
294
  }
503
- /**
504
- * Copy thinkwell pre-bundled packages to build directory.
505
- */
506
295
  function copyThinkwellBundles(ctx) {
507
- const bundles = ["thinkwell.cjs", "acp.cjs", "protocol.cjs"];
508
- for (const bundle of bundles) {
509
- const src = join(ctx.thinkwellDistPkg, bundle);
510
- const dest = join(ctx.buildDir, bundle);
511
- if (!existsSync(src)) {
512
- throw new Error(`Thinkwell bundle not found: ${src}`);
513
- }
514
- const content = readFileSync(src);
515
- writeFileSync(dest, content);
516
- if (ctx.options.verbose) {
517
- console.log(` Copied ${bundle}`);
518
- }
519
- }
296
+ const bundles = ["thinkwell.cjs", "acp.cjs", "protocol.cjs"];
297
+ for (const bundle of bundles) {
298
+ const src = join(ctx.thinkwellDistPkg, bundle), dest = join(ctx.buildDir, bundle);
299
+ if (!existsSync(src))
300
+ throw new Error(`Thinkwell bundle not found: ${src}`);
301
+ const content = readFileSync(src);
302
+ writeFileSync(dest, content), ctx.options.verbose && console.log(` Copied ${bundle}`);
303
+ }
520
304
  }
521
- /**
522
- * Check if running from a pkg-compiled binary.
523
- */
524
305
  function isRunningFromCompiledBinary() {
525
- // @ts-expect-error process.pkg is set by pkg at runtime
526
- return typeof process.pkg !== "undefined";
306
+ return typeof process.pkg < "u";
527
307
  }
528
- // ============================================================================
529
- // Portable Node.js Download (for compiled binary builds)
530
- // ============================================================================
531
- /** Pinned Node.js version for portable runtime */
532
308
  const PORTABLE_NODE_VERSION = "24.1.0";
533
- /** Get the thinkwell cache directory */
534
309
  function getCacheDir() {
535
- return process.env.THINKWELL_CACHE_DIR || join(homedir(), ".cache", "thinkwell");
310
+ return process.env.THINKWELL_CACHE_DIR || join(homedir(), ".cache", "thinkwell");
536
311
  }
537
- /** Get the thinkwell version from package.json */
538
312
  function getThinkwellVersion() {
539
- try {
540
- const pkgPath = resolve(__dirname, "../../package.json");
541
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
542
- return pkg.version || "unknown";
543
- }
544
- catch {
545
- return "unknown";
546
- }
313
+ try {
314
+ const pkgPath = resolve(__dirname, "../../package.json");
315
+ return JSON.parse(readFileSync(pkgPath, "utf-8")).version || "unknown";
316
+ } catch {
317
+ return "unknown";
318
+ }
547
319
  }
548
- /**
549
- * Map process.platform/arch to Node.js download format.
550
- */
551
320
  function getNodePlatformArch() {
552
- const platform = process.platform === "darwin" ? "darwin" : "linux";
553
- const arch = process.arch; // x64 or arm64
554
- return { platform, arch };
321
+ const platform = process.platform === "darwin" ? "darwin" : "linux", arch = process.arch;
322
+ return { platform, arch };
555
323
  }
556
- /**
557
- * Download a file from a URL with progress reporting.
558
- */
559
324
  async function downloadFile(url, destPath, spinner) {
560
- const response = await fetch(url);
561
- if (!response.ok) {
562
- throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
563
- }
564
- const contentLength = response.headers.get("content-length");
565
- const totalBytes = contentLength ? parseInt(contentLength, 10) : 0;
566
- // Ensure directory exists
567
- mkdirSync(dirname(destPath), { recursive: true });
568
- const fileStream = createWriteStream(destPath);
569
- const reader = response.body?.getReader();
570
- if (!reader) {
571
- throw new Error("No response body");
572
- }
573
- let downloadedBytes = 0;
574
- try {
575
- while (true) {
576
- const { done, value } = await reader.read();
577
- if (done)
578
- break;
579
- fileStream.write(Buffer.from(value));
580
- downloadedBytes += value.length;
581
- if (spinner && totalBytes > 0) {
582
- const percent = Math.round((downloadedBytes / totalBytes) * 100);
583
- const downloadedMB = (downloadedBytes / 1024 / 1024).toFixed(1);
584
- const totalMB = (totalBytes / 1024 / 1024).toFixed(1);
585
- spinner.text = `Downloading Node.js runtime... ${downloadedMB} MB / ${totalMB} MB (${percent}%)`;
586
- }
587
- }
588
- }
589
- finally {
590
- fileStream.end();
325
+ const response = await fetch(url);
326
+ if (!response.ok)
327
+ throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
328
+ const contentLength = response.headers.get("content-length"), totalBytes = contentLength ? parseInt(contentLength, 10) : 0;
329
+ mkdirSync(dirname(destPath), { recursive: !0 });
330
+ const fileStream = createWriteStream(destPath), reader = response.body?.getReader();
331
+ if (!reader)
332
+ throw new Error("No response body");
333
+ let downloadedBytes = 0;
334
+ try {
335
+ for (; ; ) {
336
+ const { done, value } = await reader.read();
337
+ if (done)
338
+ break;
339
+ if (fileStream.write(Buffer.from(value)), downloadedBytes += value.length, spinner && totalBytes > 0) {
340
+ const percent = Math.round(downloadedBytes / totalBytes * 100), downloadedMB = (downloadedBytes / 1024 / 1024).toFixed(1), totalMB = (totalBytes / 1024 / 1024).toFixed(1);
341
+ spinner.text = `Downloading Node.js runtime... ${downloadedMB} MB / ${totalMB} MB (${percent}%)`;
342
+ }
591
343
  }
592
- // Wait for file to be fully written
593
- await new Promise((resolve, reject) => {
594
- fileStream.on("finish", resolve);
595
- fileStream.on("error", reject);
596
- });
344
+ } finally {
345
+ fileStream.end();
346
+ }
347
+ await new Promise((resolve2, reject) => {
348
+ fileStream.on("finish", resolve2), fileStream.on("error", reject);
349
+ });
597
350
  }
598
- /**
599
- * Compute SHA-256 hash of a file.
600
- */
601
351
  function hashFile(filePath) {
602
- const content = readFileSync(filePath);
603
- return createHash("sha256").update(content).digest("hex");
352
+ const content = readFileSync(filePath);
353
+ return createHash("sha256").update(content).digest("hex");
604
354
  }
605
- /**
606
- * Fetch the expected SHA-256 checksum for a Node.js download.
607
- */
608
355
  async function fetchExpectedChecksum(version, filename) {
609
- const url = `https://nodejs.org/dist/v${version}/SHASUMS256.txt`;
610
- const response = await fetch(url);
611
- if (!response.ok) {
612
- throw new Error(`Failed to fetch checksums: ${response.status}`);
613
- }
614
- const text = await response.text();
615
- for (const line of text.split("\n")) {
616
- // Format: "hash filename"
617
- const parts = line.trim().split(/\s+/);
618
- if (parts.length === 2 && parts[1] === filename) {
619
- return parts[0];
620
- }
621
- }
622
- throw new Error(`Checksum not found for ${filename}`);
356
+ const url = `https://nodejs.org/dist/v${version}/SHASUMS256.txt`, response = await fetch(url);
357
+ if (!response.ok)
358
+ throw new Error(`Failed to fetch checksums: ${response.status}`);
359
+ const text = await response.text();
360
+ for (const line of text.split(`
361
+ `)) {
362
+ const parts = line.trim().split(/\s+/);
363
+ if (parts.length === 2 && parts[1] === filename)
364
+ return parts[0];
365
+ }
366
+ throw new Error(`Checksum not found for ${filename}`);
623
367
  }
624
- /**
625
- * Extract a .tar.gz archive using the system tar command.
626
- */
627
368
  function extractTarGz(archivePath, destDir) {
628
- execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, {
629
- stdio: "pipe",
630
- });
369
+ execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, {
370
+ stdio: "pipe"
371
+ });
631
372
  }
632
- /**
633
- * Ensure portable Node.js is available in the cache.
634
- *
635
- * Downloads from nodejs.org if not cached, verifies checksum, and extracts.
636
- * Returns the path to the node binary.
637
- */
638
373
  async function ensurePortableNode(spinner) {
639
- const version = PORTABLE_NODE_VERSION;
640
- const { platform, arch } = getNodePlatformArch();
641
- const cacheDir = join(getCacheDir(), "node", `v${version}`);
642
- const nodeBinary = process.platform === "win32" ? "node.exe" : "node";
643
- const nodePath = join(cacheDir, nodeBinary);
644
- // Check if already cached
645
- if (existsSync(nodePath)) {
646
- return nodePath;
647
- }
648
- const filename = `node-v${version}-${platform}-${arch}.tar.gz`;
649
- const url = `https://nodejs.org/dist/v${version}/${filename}`;
650
- const archivePath = join(cacheDir, filename);
651
- spinner?.start("Downloading Node.js runtime (first time only)...");
652
- try {
653
- // Ensure cache directory exists
654
- mkdirSync(cacheDir, { recursive: true });
655
- // Download
656
- await downloadFile(url, archivePath, spinner);
657
- // Verify checksum
658
- spinner?.start("Verifying download integrity...");
659
- const expectedHash = await fetchExpectedChecksum(version, filename);
660
- const actualHash = hashFile(archivePath);
661
- if (actualHash !== expectedHash) {
662
- // Clean up the corrupted download
663
- rmSync(archivePath, { force: true });
664
- throw new Error(`Node.js download verification failed.\n\n` +
665
- ` Expected: ${expectedHash}\n` +
666
- ` Actual: ${actualHash}\n\n` +
667
- `This may indicate a corrupted download or network interference.\n` +
668
- `Please retry or report this issue.`);
669
- }
670
- // Extract
671
- spinner?.start("Extracting Node.js...");
672
- extractTarGz(archivePath, cacheDir);
673
- // Move node binary to cache root
674
- // The tarball extracts to node-v{version}-{platform}-{arch}/bin/node
675
- const extractedDir = join(cacheDir, `node-v${version}-${platform}-${arch}`);
676
- const extractedBin = join(extractedDir, "bin", nodeBinary);
677
- copyFileSync(extractedBin, nodePath);
678
- chmodSync(nodePath, 0o755);
679
- // Cleanup: remove extracted directory and archive
680
- rmSync(extractedDir, { recursive: true, force: true });
681
- rmSync(archivePath, { force: true });
682
- spinner?.succeed(`Node.js v${version} cached to ${cacheDir}`);
683
- return nodePath;
684
- }
685
- catch (error) {
686
- // Cleanup on error
687
- rmSync(cacheDir, { recursive: true, force: true });
688
- const message = error instanceof Error ? error.message : String(error);
689
- // Provide helpful error messages
690
- if (message.includes("ETIMEDOUT") || message.includes("ENOTFOUND")) {
691
- throw new Error(`Failed to download Node.js runtime.\n\n` +
692
- ` URL: ${url}\n` +
693
- ` Error: ${message}\n\n` +
694
- `Check your network connection and try again.\n` +
695
- `If behind a proxy, set HTTPS_PROXY environment variable.`);
696
- }
697
- throw error;
698
- }
374
+ const version = PORTABLE_NODE_VERSION, { platform, arch } = getNodePlatformArch(), cacheDir = join(getCacheDir(), "node", `v${version}`), nodeBinary = process.platform === "win32" ? "node.exe" : "node", nodePath = join(cacheDir, nodeBinary);
375
+ if (existsSync(nodePath))
376
+ return nodePath;
377
+ const filename = `node-v${version}-${platform}-${arch}.tar.gz`, url = `https://nodejs.org/dist/v${version}/${filename}`, archivePath = join(cacheDir, filename);
378
+ spinner?.start("Downloading Node.js runtime (first time only)...");
379
+ try {
380
+ mkdirSync(cacheDir, { recursive: !0 }), await downloadFile(url, archivePath, spinner), spinner?.start("Verifying download integrity...");
381
+ const expectedHash = await fetchExpectedChecksum(version, filename), actualHash = hashFile(archivePath);
382
+ if (actualHash !== expectedHash)
383
+ throw rmSync(archivePath, { force: !0 }), new Error(`Node.js download verification failed.
384
+
385
+ Expected: ${expectedHash}
386
+ Actual: ${actualHash}
387
+
388
+ This may indicate a corrupted download or network interference.
389
+ Please retry or report this issue.`);
390
+ spinner?.start("Extracting Node.js..."), extractTarGz(archivePath, cacheDir);
391
+ const extractedDir = join(cacheDir, `node-v${version}-${platform}-${arch}`), extractedBin = join(extractedDir, "bin", nodeBinary);
392
+ return copyFileSync(extractedBin, nodePath), chmodSync(nodePath, 493), rmSync(extractedDir, { recursive: !0, force: !0 }), rmSync(archivePath, { force: !0 }), spinner?.succeed(`Node.js v${version} cached to ${cacheDir}`), nodePath;
393
+ } catch (error) {
394
+ rmSync(cacheDir, { recursive: !0, force: !0 });
395
+ const message = error instanceof Error ? error.message : String(error);
396
+ throw message.includes("ETIMEDOUT") || message.includes("ENOTFOUND") ? new Error(`Failed to download Node.js runtime.
397
+
398
+ URL: ${url}
399
+ Error: ${message}
400
+
401
+ Check your network connection and try again.
402
+ If behind a proxy, set HTTPS_PROXY environment variable.`) : error;
403
+ }
699
404
  }
700
- /**
701
- * Ensure the pkg CLI bundle and its auxiliary files are extracted from the
702
- * compiled binary's assets.
703
- *
704
- * pkg requires several auxiliary files at runtime:
705
- * - pkg-cli.cjs - The main bundled CLI
706
- * - package.json - pkg's version info (read as ../package.json from cacheDir)
707
- * - pkg-prelude/ - JavaScript files injected into compiled binaries
708
- * - pkg-dictionary/ - Compression dictionaries for bytecode
709
- * - pkg-common.cjs - Common utilities
710
- *
711
- * Returns the path to the extracted pkg-cli.cjs file.
712
- */
713
405
  function ensurePkgCli() {
714
- const version = getThinkwellVersion();
715
- const pkgCliBaseDir = join(getCacheDir(), "pkg-cli");
716
- const cacheDir = join(pkgCliBaseDir, version);
717
- const pkgCliPath = join(cacheDir, "pkg-cli.cjs");
718
- // Check if already cached (check for main file and a prelude file)
719
- const preludeCheck = join(cacheDir, "pkg-prelude", "bootstrap.js");
720
- if (existsSync(pkgCliPath) && existsSync(preludeCheck)) {
721
- return pkgCliPath;
722
- }
723
- // Base path for pkg assets in the compiled binary's snapshot
724
- const distPkgPath = resolve(__dirname, "../../dist-pkg");
725
- // Extract main CLI bundle
726
- const cliSrc = join(distPkgPath, "pkg-cli.cjs");
727
- if (!existsSync(cliSrc)) {
728
- throw new Error(`pkg CLI not found in compiled binary assets.\n` +
729
- ` Expected at: ${cliSrc}\n\n` +
730
- `This may indicate a build issue. Please report this.`);
731
- }
732
- mkdirSync(cacheDir, { recursive: true });
733
- copyFileSync(cliSrc, pkgCliPath);
734
- // Extract pkg's package.json (for version info)
735
- // pkg reads ../package.json relative to __dirname (which is cacheDir)
736
- // So we place it in the parent directory (pkgCliBaseDir)
737
- const pkgJsonSrc = join(distPkgPath, "package.json");
738
- if (existsSync(pkgJsonSrc)) {
739
- copyFileSync(pkgJsonSrc, join(pkgCliBaseDir, "package.json"));
740
- }
741
- // Extract prelude files
742
- const preludeDir = join(cacheDir, "pkg-prelude");
743
- mkdirSync(preludeDir, { recursive: true });
744
- for (const file of ["bootstrap.js", "diagnostic.js"]) {
745
- const src = join(distPkgPath, "pkg-prelude", file);
746
- if (existsSync(src)) {
747
- copyFileSync(src, join(preludeDir, file));
748
- }
749
- }
750
- // Extract common.js
751
- const commonSrc = join(distPkgPath, "pkg-common.cjs");
752
- if (existsSync(commonSrc)) {
753
- copyFileSync(commonSrc, join(cacheDir, "pkg-common.cjs"));
754
- }
755
- // Extract dictionary files
756
- // pkg reads ../dictionary relative to __dirname (which is cacheDir)
757
- // So we place it in the parent directory (pkgCliBaseDir/dictionary/)
758
- const dictionaryDir = join(pkgCliBaseDir, "dictionary");
759
- mkdirSync(dictionaryDir, { recursive: true });
760
- for (const file of ["v8-7.8.js", "v8-8.4.js", "v8-12.4.js"]) {
761
- const src = join(distPkgPath, "pkg-dictionary", file);
762
- if (existsSync(src)) {
763
- copyFileSync(src, join(dictionaryDir, file));
764
- }
765
- }
406
+ const version = getThinkwellVersion(), pkgCliBaseDir = join(getCacheDir(), "pkg-cli"), cacheDir = join(pkgCliBaseDir, version), pkgCliPath = join(cacheDir, "pkg-cli.cjs"), preludeCheck = join(cacheDir, "pkg-prelude", "bootstrap.js");
407
+ if (existsSync(pkgCliPath) && existsSync(preludeCheck))
766
408
  return pkgCliPath;
409
+ const distPkgPath = resolve(__dirname, "../../dist-pkg"), cliSrc = join(distPkgPath, "pkg-cli.cjs");
410
+ if (!existsSync(cliSrc))
411
+ throw new Error(`pkg CLI not found in compiled binary assets.
412
+ Expected at: ${cliSrc}
413
+
414
+ This may indicate a build issue. Please report this.`);
415
+ mkdirSync(cacheDir, { recursive: !0 }), copyFileSync(cliSrc, pkgCliPath);
416
+ const pkgJsonSrc = join(distPkgPath, "package.json");
417
+ existsSync(pkgJsonSrc) && copyFileSync(pkgJsonSrc, join(pkgCliBaseDir, "package.json"));
418
+ const preludeDir = join(cacheDir, "pkg-prelude");
419
+ mkdirSync(preludeDir, { recursive: !0 });
420
+ for (const file of ["bootstrap.js", "diagnostic.js"]) {
421
+ const src = join(distPkgPath, "pkg-prelude", file);
422
+ existsSync(src) && copyFileSync(src, join(preludeDir, file));
423
+ }
424
+ const commonSrc = join(distPkgPath, "pkg-common.cjs");
425
+ existsSync(commonSrc) && copyFileSync(commonSrc, join(cacheDir, "pkg-common.cjs"));
426
+ const dictionaryDir = join(pkgCliBaseDir, "dictionary");
427
+ mkdirSync(dictionaryDir, { recursive: !0 });
428
+ for (const file of ["v8-7.8.js", "v8-8.4.js", "v8-12.4.js"]) {
429
+ const src = join(distPkgPath, "pkg-dictionary", file);
430
+ existsSync(src) && copyFileSync(src, join(dictionaryDir, file));
431
+ }
432
+ return pkgCliPath;
767
433
  }
768
- /**
769
- * Spawn a subprocess and wait for completion.
770
- */
771
434
  function spawnAsync(command, args, options = {}) {
772
- return new Promise((resolve) => {
773
- const proc = spawn(command, args, {
774
- cwd: options.cwd,
775
- env: options.env || process.env,
776
- stdio: options.verbose ? "inherit" : "pipe",
777
- });
778
- let stdout = "";
779
- let stderr = "";
780
- if (!options.verbose) {
781
- proc.stdout?.on("data", (data) => {
782
- stdout += data.toString();
783
- });
784
- proc.stderr?.on("data", (data) => {
785
- stderr += data.toString();
786
- });
787
- }
788
- proc.on("close", (code) => {
789
- resolve({
790
- exitCode: code ?? 1,
791
- stdout,
792
- stderr,
793
- });
794
- });
795
- proc.on("error", (error) => {
796
- resolve({
797
- exitCode: 1,
798
- stdout,
799
- stderr: error.message,
800
- });
801
- });
435
+ return new Promise((resolve2) => {
436
+ const proc = spawn(command, args, {
437
+ cwd: options.cwd,
438
+ env: options.env || process.env,
439
+ stdio: options.verbose ? "inherit" : "pipe"
802
440
  });
441
+ let stdout = "", stderr = "";
442
+ options.verbose || (proc.stdout?.on("data", (data) => {
443
+ stdout += data.toString();
444
+ }), proc.stderr?.on("data", (data) => {
445
+ stderr += data.toString();
446
+ })), proc.on("close", (code) => {
447
+ resolve2({
448
+ exitCode: code ?? 1,
449
+ stdout,
450
+ stderr
451
+ });
452
+ }), proc.on("error", (error) => {
453
+ resolve2({
454
+ exitCode: 1,
455
+ stdout,
456
+ stderr: error.message
457
+ });
458
+ });
459
+ });
803
460
  }
804
- /**
805
- * Compile using pkg via subprocess (for compiled binary environment).
806
- *
807
- * This function is called when running from a compiled thinkwell binary.
808
- * It downloads a portable Node.js runtime and uses the bundled pkg CLI
809
- * to perform the compilation as a subprocess.
810
- */
811
461
  async function compileWithPkgSubprocess(ctx, wrapperPath, target, outputPath, spinner) {
812
- // Ensure portable Node.js is available
813
- const nodePath = await ensurePortableNode(spinner);
814
- // Extract pkg CLI from snapshot
815
- const pkgCliPath = ensurePkgCli();
816
- const pkgTarget = TARGET_MAP[target];
817
- // Ensure output directory exists
818
- const outputDir = dirname(outputPath);
819
- if (!existsSync(outputDir)) {
820
- mkdirSync(outputDir, { recursive: true });
821
- }
822
- // Build pkg CLI arguments
823
- const args = [
824
- pkgCliPath,
825
- wrapperPath,
826
- "--targets",
827
- pkgTarget,
828
- "--output",
829
- outputPath,
830
- "--options",
831
- "experimental-transform-types,disable-warning=ExperimentalWarning",
832
- "--public",
833
- ];
834
- // Add assets if specified
835
- if (ctx.options.include && ctx.options.include.length > 0) {
836
- for (const pattern of ctx.options.include) {
837
- args.push("--assets", pattern);
838
- }
839
- }
840
- spinner?.start(`Compiling for ${target}...`);
841
- const result = await spawnAsync(nodePath, args, {
842
- cwd: ctx.buildDir,
843
- env: {
844
- ...process.env,
845
- // Set pkg cache path for pkg-fetch downloads
846
- PKG_CACHE_PATH: join(getCacheDir(), "pkg-cache"),
847
- },
848
- verbose: ctx.options.verbose,
849
- });
850
- if (result.exitCode !== 0) {
851
- const errorOutput = result.stderr || result.stdout;
852
- throw new Error(`pkg compilation failed for ${target}.\n\n` +
853
- `Exit code: ${result.exitCode}\n` +
854
- (errorOutput ? `Output:\n${errorOutput}` : ""));
855
- }
462
+ const nodePath = await ensurePortableNode(spinner), pkgCliPath = ensurePkgCli(), pkgTarget = TARGET_MAP[target], outputDir = dirname(outputPath);
463
+ existsSync(outputDir) || mkdirSync(outputDir, { recursive: !0 });
464
+ const args = [
465
+ pkgCliPath,
466
+ wrapperPath,
467
+ "--targets",
468
+ pkgTarget,
469
+ "--output",
470
+ outputPath,
471
+ "--options",
472
+ "experimental-transform-types,disable-warning=ExperimentalWarning",
473
+ "--public"
474
+ ];
475
+ if (ctx.options.include && ctx.options.include.length > 0)
476
+ for (const pattern of ctx.options.include)
477
+ args.push("--assets", pattern);
478
+ spinner?.start(`Compiling for ${target}...`);
479
+ const result = await spawnAsync(nodePath, args, {
480
+ cwd: ctx.buildDir,
481
+ env: {
482
+ ...process.env,
483
+ // Set pkg cache path for pkg-fetch downloads
484
+ PKG_CACHE_PATH: join(getCacheDir(), "pkg-cache")
485
+ },
486
+ verbose: ctx.options.verbose
487
+ });
488
+ if (result.exitCode !== 0) {
489
+ const errorOutput = result.stderr || result.stdout;
490
+ throw new Error(`pkg compilation failed for ${target}.
491
+
492
+ Exit code: ${result.exitCode}
493
+ ` + (errorOutput ? `Output:
494
+ ${errorOutput}` : ""));
495
+ }
856
496
  }
857
- /**
858
- * Stage 2: Compile with pkg.
859
- *
860
- * Uses @yao-pkg/pkg to create a self-contained binary.
861
- *
862
- * When running from a compiled thinkwell binary, this function uses a
863
- * subprocess approach: downloading a portable Node.js runtime and executing
864
- * the bundled pkg CLI as a child process. This works around pkg's dynamic
865
- * import limitations in the virtual filesystem.
866
- *
867
- * When running from npm/source, this function uses @yao-pkg/pkg programmatically.
868
- */
869
497
  async function compileWithPkg(ctx, wrapperPath, target, outputPath, spinner) {
870
- // When running from a compiled binary, use subprocess approach
871
- if (isRunningFromCompiledBinary()) {
872
- await compileWithPkgSubprocess(ctx, wrapperPath, target, outputPath, spinner);
873
- return;
874
- }
875
- // Normal path: use pkg programmatically
876
- const { exec } = await import("@yao-pkg/pkg");
877
- const pkgTarget = TARGET_MAP[target];
878
- // Ensure output directory exists
879
- const outputDir = dirname(outputPath);
880
- if (!existsSync(outputDir)) {
881
- mkdirSync(outputDir, { recursive: true });
882
- }
883
- // Build pkg configuration
884
- const pkgConfig = [
885
- wrapperPath,
886
- "--targets",
887
- pkgTarget,
888
- "--output",
889
- outputPath,
890
- "--options",
891
- "experimental-transform-types,disable-warning=ExperimentalWarning",
892
- "--public", // Include source instead of bytecode (required for cross-compilation)
893
- ];
894
- // Add assets if specified
895
- if (ctx.options.include && ctx.options.include.length > 0) {
896
- for (const pattern of ctx.options.include) {
897
- pkgConfig.push("--assets", pattern);
898
- }
899
- }
900
- await exec(pkgConfig);
498
+ if (isRunningFromCompiledBinary()) {
499
+ await compileWithPkgSubprocess(ctx, wrapperPath, target, outputPath, spinner);
500
+ return;
501
+ }
502
+ const { exec } = await import("@yao-pkg/pkg"), pkgTarget = TARGET_MAP[target], outputDir = dirname(outputPath);
503
+ existsSync(outputDir) || mkdirSync(outputDir, { recursive: !0 });
504
+ const pkgConfig = [
505
+ wrapperPath,
506
+ "--targets",
507
+ pkgTarget,
508
+ "--output",
509
+ outputPath,
510
+ "--options",
511
+ "experimental-transform-types,disable-warning=ExperimentalWarning",
512
+ "--public"
513
+ // Include source instead of bytecode (required for cross-compilation)
514
+ ];
515
+ if (ctx.options.include && ctx.options.include.length > 0)
516
+ for (const pattern of ctx.options.include)
517
+ pkgConfig.push("--assets", pattern);
518
+ await exec(pkgConfig);
901
519
  }
902
- // ============================================================================
903
- // Top-Level Await Detection
904
- // ============================================================================
905
- /**
906
- * Detect top-level await usage in the entry file.
907
- * Returns an array of line numbers where top-level await is found.
908
- */
909
520
  function detectTopLevelAwait(filePath) {
910
- const content = readFileSync(filePath, "utf-8");
911
- const lines = content.split("\n");
912
- const awaits = [];
913
- // Track nesting depth of functions/classes
914
- let depth = 0;
915
- let inMultiLineComment = false;
916
- for (let i = 0; i < lines.length; i++) {
917
- let line = lines[i];
918
- // Handle multi-line comments
919
- if (inMultiLineComment) {
920
- const endIdx = line.indexOf("*/");
921
- if (endIdx !== -1) {
922
- line = line.slice(endIdx + 2);
923
- inMultiLineComment = false;
924
- }
925
- else {
926
- continue;
927
- }
928
- }
929
- // Remove single-line comments
930
- const singleLineCommentIdx = line.indexOf("//");
931
- if (singleLineCommentIdx !== -1) {
932
- line = line.slice(0, singleLineCommentIdx);
933
- }
934
- // Handle multi-line comment start
935
- const multiLineStart = line.indexOf("/*");
936
- if (multiLineStart !== -1) {
937
- const multiLineEnd = line.indexOf("*/", multiLineStart);
938
- if (multiLineEnd !== -1) {
939
- line = line.slice(0, multiLineStart) + line.slice(multiLineEnd + 2);
940
- }
941
- else {
942
- line = line.slice(0, multiLineStart);
943
- inMultiLineComment = true;
944
- }
945
- }
946
- // Count function/class/arrow function depth changes
947
- // This is a simplified heuristic - not a full parser
948
- const openBraces = (line.match(/\{/g) || []).length;
949
- const closeBraces = (line.match(/\}/g) || []).length;
950
- // Check for function/class/arrow declarations that increase depth
951
- if (/\b(function|class|async\s+function)\b/.test(line) && line.includes("{")) {
952
- depth += 1;
953
- }
954
- else if (/=>\s*\{/.test(line)) {
955
- depth += 1;
956
- }
957
- // Adjust depth for brace changes (simplified)
958
- depth += openBraces - closeBraces;
959
- if (depth < 0)
960
- depth = 0;
961
- // Check for await at top level (depth 0)
962
- if (depth === 0 && /\bawait\b/.test(line)) {
963
- // Make sure it's not inside a string
964
- const withoutStrings = line.replace(/(["'`])(?:(?!\1)[^\\]|\\.)*\1/g, "");
965
- if (/\bawait\b/.test(withoutStrings)) {
966
- awaits.push(i + 1); // 1-indexed line numbers
967
- }
968
- }
521
+ const lines = readFileSync(filePath, "utf-8").split(`
522
+ `), awaits = [];
523
+ let depth = 0, inMultiLineComment = !1;
524
+ for (let i = 0; i < lines.length; i++) {
525
+ let line = lines[i];
526
+ if (inMultiLineComment) {
527
+ const endIdx = line.indexOf("*/");
528
+ if (endIdx !== -1)
529
+ line = line.slice(endIdx + 2), inMultiLineComment = !1;
530
+ else
531
+ continue;
532
+ }
533
+ const singleLineCommentIdx = line.indexOf("//");
534
+ singleLineCommentIdx !== -1 && (line = line.slice(0, singleLineCommentIdx));
535
+ const multiLineStart = line.indexOf("/*");
536
+ if (multiLineStart !== -1) {
537
+ const multiLineEnd = line.indexOf("*/", multiLineStart);
538
+ multiLineEnd !== -1 ? line = line.slice(0, multiLineStart) + line.slice(multiLineEnd + 2) : (line = line.slice(0, multiLineStart), inMultiLineComment = !0);
539
+ }
540
+ const openBraces = (line.match(/\{/g) || []).length, closeBraces = (line.match(/\}/g) || []).length;
541
+ if ((/\b(function|class|async\s+function)\b/.test(line) && line.includes("{") || /=>\s*\{/.test(line)) && (depth += 1), depth += openBraces - closeBraces, depth < 0 && (depth = 0), depth === 0 && /\bawait\b/.test(line)) {
542
+ const withoutStrings = line.replace(/(["'`])(?:(?!\1)[^\\]|\\.)*\1/g, "");
543
+ /\bawait\b/.test(withoutStrings) && awaits.push(i + 1);
969
544
  }
970
- return awaits;
545
+ }
546
+ return awaits;
971
547
  }
972
- // ============================================================================
973
- // Output Helpers
974
- // ============================================================================
975
- /** Log output respecting quiet mode */
976
548
  function log(ctx, message) {
977
- if (!ctx.options.quiet) {
978
- console.log(message);
979
- }
549
+ ctx.options.quiet || console.log(message);
980
550
  }
981
- /** Create a spinner respecting quiet mode */
982
551
  function createSpinner(ctx, text) {
983
- return createSpinnerImpl({
984
- text,
985
- isSilent: ctx.options.quiet,
986
- });
552
+ return createSpinnerImpl({
553
+ text,
554
+ isSilent: ctx.options.quiet
555
+ });
987
556
  }
988
- /**
989
- * Run a dry-run build that shows what would be built without actually building.
990
- */
991
557
  function runDryRun(ctx) {
992
- console.log(styleText("bold", "Dry run mode - no files will be created\n"));
993
- console.log(styleText("bold", "Entry point:"));
994
- console.log(` ${ctx.entryPath}\n`);
995
- console.log(styleText("bold", "Targets:"));
996
- for (const target of ctx.resolvedTargets) {
997
- const outputPath = getOutputPath(ctx, target);
998
- console.log(` ${target} → ${outputPath}`);
999
- }
558
+ console.log(styleText("bold", `Dry run mode - no files will be created
559
+ `)), console.log(styleText("bold", "Entry point:")), console.log(` ${ctx.entryPath}
560
+ `), console.log(styleText("bold", "Targets:"));
561
+ for (const target of ctx.resolvedTargets) {
562
+ const outputPath = getOutputPath(ctx, target);
563
+ console.log(` ${target} \u2192 ${outputPath}`);
564
+ }
565
+ if (console.log(), ctx.options.include && ctx.options.include.length > 0) {
566
+ console.log(styleText("bold", "Assets to include:"));
567
+ for (const pattern of ctx.options.include)
568
+ console.log(` ${pattern}`);
1000
569
  console.log();
1001
- if (ctx.options.include && ctx.options.include.length > 0) {
1002
- console.log(styleText("bold", "Assets to include:"));
1003
- for (const pattern of ctx.options.include) {
1004
- console.log(` ${pattern}`);
1005
- }
1006
- console.log();
1007
- }
1008
- if (ctx.options.external && ctx.options.external.length > 0) {
1009
- console.log(styleText("bold", "External packages (not bundled):"));
1010
- for (const pkg of ctx.options.external) {
1011
- console.log(` ${pkg}`);
1012
- }
1013
- console.log();
1014
- }
1015
- if (ctx.options.minify) {
1016
- console.log(styleText("bold", "Minification:"), "enabled");
1017
- console.log();
1018
- }
1019
- console.log(styleText("bold", "Build steps:"));
1020
- console.log(" 1. Bundle user script with esbuild");
1021
- console.log(" 2. Copy thinkwell packages");
1022
- console.log(" 3. Generate wrapper entry point");
1023
- console.log(` 4. Compile with pkg for ${ctx.resolvedTargets.length} target(s)`);
570
+ }
571
+ if (ctx.options.external && ctx.options.external.length > 0) {
572
+ console.log(styleText("bold", "External packages (not bundled):"));
573
+ for (const pkg of ctx.options.external)
574
+ console.log(` ${pkg}`);
1024
575
  console.log();
1025
- // Check for potential issues
1026
- const topLevelAwaits = detectTopLevelAwait(ctx.entryPath);
1027
- if (topLevelAwaits.length > 0) {
1028
- console.log(styleText("yellow", "Warning: Top-level await detected"));
1029
- console.log(" Top-level await is not supported in compiled binaries.");
1030
- console.log(` Found at line(s): ${topLevelAwaits.join(", ")}`);
1031
- console.log(" Wrap async code in an async main() function instead.\n");
1032
- }
1033
- console.log(styleText("dim", "Run without --dry-run to build."));
576
+ }
577
+ ctx.options.minify && (console.log(styleText("bold", "Minification:"), "enabled"), console.log()), console.log(styleText("bold", "Build steps:")), console.log(" 1. Bundle user script with esbuild"), console.log(" 2. Copy thinkwell packages"), console.log(" 3. Generate wrapper entry point"), console.log(` 4. Compile with pkg for ${ctx.resolvedTargets.length} target(s)`), console.log();
578
+ const topLevelAwaits = detectTopLevelAwait(ctx.entryPath);
579
+ topLevelAwaits.length > 0 && (console.log(styleText("yellow", "Warning: Top-level await detected")), console.log(" Top-level await is not supported in compiled binaries."), console.log(` Found at line(s): ${topLevelAwaits.join(", ")}`), console.log(` Wrap async code in an async main() function instead.
580
+ `)), console.log(styleText("dim", "Run without --dry-run to build."));
1034
581
  }
1035
- /**
1036
- * Main build function.
1037
- */
1038
582
  export async function runBundle(options) {
1039
- // Check for project-level dependencies when a package.json exists
1040
- const entryPath = isAbsolute(options.entry)
1041
- ? options.entry
1042
- : resolve(process.cwd(), options.entry);
1043
- const projectRoot = findProjectRoot(dirname(entryPath));
1044
- if (projectRoot && existsSync(entryPath)) {
1045
- const source = readFileSync(entryPath, "utf-8");
1046
- const requireTypescript = hasJsonSchemaMarkers(source);
1047
- const depCheck = await checkDependencies(projectRoot);
1048
- if (hasMissingDeps(depCheck, { requireTypescript })) {
1049
- process.stderr.write(formatMissingDepsError(depCheck, { requireTypescript }) + "\n");
1050
- process.exit(2);
1051
- }
1052
- }
1053
- // Handle watch mode separately
1054
- if (options.watch) {
1055
- await runWatchMode(options, projectRoot);
1056
- return;
1057
- }
1058
- const ctx = initBundleContext(options);
1059
- ctx.projectDir = projectRoot;
1060
- // Check for top-level await and warn
1061
- const topLevelAwaits = detectTopLevelAwait(ctx.entryPath);
1062
- if (topLevelAwaits.length > 0) {
1063
- console.log(styleText("yellow", "Warning: Top-level await detected"));
1064
- console.log(" Top-level await is not supported in compiled binaries.");
1065
- console.log(` Found at line(s): ${topLevelAwaits.join(", ")}`);
1066
- console.log(" Wrap async code in an async main() function instead.\n");
1067
- }
1068
- // Handle dry-run mode
1069
- if (options.dryRun) {
1070
- runDryRun(ctx);
1071
- return;
1072
- }
1073
- log(ctx, `Building ${styleText("bold", ctx.entryBasename)}...\n`);
1074
- // Create build directory
1075
- if (existsSync(ctx.buildDir)) {
1076
- rmSync(ctx.buildDir, { recursive: true });
1077
- }
1078
- mkdirSync(ctx.buildDir, { recursive: true });
1079
- try {
1080
- // Stage 1: Bundle user script
1081
- let spinner = createSpinner(ctx, "Bundling with esbuild...");
1082
- spinner.start();
1083
- const userBundlePath = await bundleUserScript(ctx);
1084
- spinner.succeed("User script bundled");
1085
- // Stage 2: Copy thinkwell bundles
1086
- spinner = createSpinner(ctx, "Preparing thinkwell packages...");
1087
- spinner.start();
1088
- copyThinkwellBundles(ctx);
1089
- spinner.succeed("Thinkwell packages ready");
1090
- // Generate wrapper
1091
- const wrapperPath = join(ctx.buildDir, "wrapper.cjs");
1092
- const wrapperSource = generateWrapperSource(userBundlePath);
1093
- writeFileSync(wrapperPath, wrapperSource);
1094
- if (ctx.options.verbose) {
1095
- log(ctx, " Generated wrapper entry point");
1096
- }
1097
- // Stage 3: Compile with pkg for each target
1098
- const outputs = [];
1099
- for (const target of ctx.resolvedTargets) {
1100
- const outputPath = getOutputPath(ctx, target);
1101
- spinner = createSpinner(ctx, `Compiling for ${target}...`);
1102
- spinner.start();
1103
- await compileWithPkg(ctx, wrapperPath, target, outputPath, spinner);
1104
- outputs.push(outputPath);
1105
- spinner.succeed(`Built ${basename(outputPath)}`);
1106
- }
1107
- log(ctx, "");
1108
- log(ctx, styleText("green", "Build complete!"));
1109
- log(ctx, "");
1110
- log(ctx, styleText("bold", "Output:"));
1111
- for (const output of outputs) {
1112
- log(ctx, ` ${output}`);
1113
- }
1114
- }
1115
- finally {
1116
- // Clean up build directory
1117
- if (!ctx.options.verbose) {
1118
- try {
1119
- rmSync(ctx.buildDir, { recursive: true });
1120
- }
1121
- catch {
1122
- // Ignore cleanup errors
1123
- }
1124
- }
1125
- else {
1126
- log(ctx, `\nBuild artifacts preserved in: ${ctx.buildDir}`);
1127
- }
1128
- }
583
+ const entryPath = isAbsolute(options.entry) ? options.entry : resolve(process.cwd(), options.entry), projectRoot = findProjectRoot(dirname(entryPath));
584
+ if (projectRoot && existsSync(entryPath)) {
585
+ const source = readFileSync(entryPath, "utf-8"), requireTypescript = hasJsonSchemaMarkers(source), depCheck = await checkDependencies(projectRoot);
586
+ hasMissingDeps(depCheck, { requireTypescript }) && (process.stderr.write(formatMissingDepsError(depCheck, { requireTypescript }) + `
587
+ `), process.exit(2));
588
+ }
589
+ if (options.watch) {
590
+ await runWatchMode(options, projectRoot);
591
+ return;
592
+ }
593
+ const ctx = initBundleContext(options);
594
+ ctx.projectDir = projectRoot;
595
+ const topLevelAwaits = detectTopLevelAwait(ctx.entryPath);
596
+ if (topLevelAwaits.length > 0 && (console.log(styleText("yellow", "Warning: Top-level await detected")), console.log(" Top-level await is not supported in compiled binaries."), console.log(` Found at line(s): ${topLevelAwaits.join(", ")}`), console.log(` Wrap async code in an async main() function instead.
597
+ `)), options.dryRun) {
598
+ runDryRun(ctx);
599
+ return;
600
+ }
601
+ log(ctx, `Building ${styleText("bold", ctx.entryBasename)}...
602
+ `), existsSync(ctx.buildDir) && rmSync(ctx.buildDir, { recursive: !0 }), mkdirSync(ctx.buildDir, { recursive: !0 });
603
+ try {
604
+ let spinner = createSpinner(ctx, "Bundling with esbuild...");
605
+ spinner.start();
606
+ const userBundlePath = await bundleUserScript(ctx);
607
+ spinner.succeed("User script bundled"), spinner = createSpinner(ctx, "Preparing thinkwell packages..."), spinner.start(), copyThinkwellBundles(ctx), spinner.succeed("Thinkwell packages ready");
608
+ const wrapperPath = join(ctx.buildDir, "wrapper.cjs"), wrapperSource = generateWrapperSource(userBundlePath);
609
+ writeFileSync(wrapperPath, wrapperSource), ctx.options.verbose && log(ctx, " Generated wrapper entry point");
610
+ const outputs = [];
611
+ for (const target of ctx.resolvedTargets) {
612
+ const outputPath = getOutputPath(ctx, target);
613
+ spinner = createSpinner(ctx, `Compiling for ${target}...`), spinner.start(), await compileWithPkg(ctx, wrapperPath, target, outputPath, spinner), outputs.push(outputPath), spinner.succeed(`Built ${basename(outputPath)}`);
614
+ }
615
+ log(ctx, ""), log(ctx, styleText("green", "Build complete!")), log(ctx, ""), log(ctx, styleText("bold", "Output:"));
616
+ for (const output of outputs)
617
+ log(ctx, ` ${output}`);
618
+ } finally {
619
+ if (ctx.options.verbose)
620
+ log(ctx, `
621
+ Build artifacts preserved in: ${ctx.buildDir}`);
622
+ else
623
+ try {
624
+ rmSync(ctx.buildDir, { recursive: !0 });
625
+ } catch {
626
+ }
627
+ }
1129
628
  }
1130
- /**
1131
- * Run the build in watch mode, rebuilding on file changes.
1132
- */
1133
629
  async function runWatchMode(options, projectDir) {
1134
- const ctx = initBundleContext(options);
1135
- ctx.projectDir = projectDir;
1136
- console.log(styleText("bold", `Watching ${ctx.entryBasename} for changes...`));
1137
- console.log(styleText("dim", "Press Ctrl+C to stop.\n"));
1138
- // Track if a build is currently in progress
1139
- let buildInProgress = false;
1140
- let rebuildQueued = false;
1141
- // Debounce timer
1142
- let debounceTimer = null;
1143
- const DEBOUNCE_MS = 100;
1144
- async function doBuild() {
1145
- if (buildInProgress) {
1146
- rebuildQueued = true;
1147
- return;
1148
- }
1149
- buildInProgress = true;
1150
- rebuildQueued = false;
1151
- const startTime = Date.now();
1152
- console.log(styleText("dim", `[${new Date().toLocaleTimeString()}] Building...`));
630
+ const ctx = initBundleContext(options);
631
+ ctx.projectDir = projectDir, console.log(styleText("bold", `Watching ${ctx.entryBasename} for changes...`)), console.log(styleText("dim", `Press Ctrl+C to stop.
632
+ `));
633
+ let buildInProgress = !1, rebuildQueued = !1, debounceTimer = null;
634
+ const DEBOUNCE_MS = 100;
635
+ async function doBuild() {
636
+ if (buildInProgress) {
637
+ rebuildQueued = !0;
638
+ return;
639
+ }
640
+ buildInProgress = !0, rebuildQueued = !1;
641
+ const startTime = Date.now();
642
+ console.log(styleText("dim", `[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Building...`));
643
+ try {
644
+ const freshCtx = initBundleContext(options);
645
+ existsSync(freshCtx.buildDir) && rmSync(freshCtx.buildDir, { recursive: !0 }), mkdirSync(freshCtx.buildDir, { recursive: !0 });
646
+ const userBundlePath = await bundleUserScript(freshCtx);
647
+ copyThinkwellBundles(freshCtx);
648
+ const wrapperPath = join(freshCtx.buildDir, "wrapper.cjs"), wrapperSource = generateWrapperSource(userBundlePath);
649
+ writeFileSync(wrapperPath, wrapperSource);
650
+ const outputs = [];
651
+ for (const target of freshCtx.resolvedTargets) {
652
+ const outputPath = getOutputPath(freshCtx, target);
653
+ await compileWithPkg(freshCtx, wrapperPath, target, outputPath), outputs.push(outputPath);
654
+ }
655
+ if (!freshCtx.options.verbose)
1153
656
  try {
1154
- // Re-initialize context to pick up any config changes
1155
- const freshCtx = initBundleContext(options);
1156
- // Create build directory
1157
- if (existsSync(freshCtx.buildDir)) {
1158
- rmSync(freshCtx.buildDir, { recursive: true });
1159
- }
1160
- mkdirSync(freshCtx.buildDir, { recursive: true });
1161
- // Bundle user script
1162
- const userBundlePath = await bundleUserScript(freshCtx);
1163
- // Copy thinkwell bundles
1164
- copyThinkwellBundles(freshCtx);
1165
- // Generate wrapper
1166
- const wrapperPath = join(freshCtx.buildDir, "wrapper.cjs");
1167
- const wrapperSource = generateWrapperSource(userBundlePath);
1168
- writeFileSync(wrapperPath, wrapperSource);
1169
- // Compile with pkg for each target
1170
- const outputs = [];
1171
- for (const target of freshCtx.resolvedTargets) {
1172
- const outputPath = getOutputPath(freshCtx, target);
1173
- await compileWithPkg(freshCtx, wrapperPath, target, outputPath);
1174
- outputs.push(outputPath);
1175
- }
1176
- // Clean up build directory
1177
- if (!freshCtx.options.verbose) {
1178
- try {
1179
- rmSync(freshCtx.buildDir, { recursive: true });
1180
- }
1181
- catch {
1182
- // Ignore cleanup errors
1183
- }
1184
- }
1185
- const elapsed = Date.now() - startTime;
1186
- console.log(styleText("green", `✓ Built in ${elapsed}ms`));
1187
- for (const output of outputs) {
1188
- console.log(styleText("dim", ` ${basename(output)}`));
1189
- }
1190
- console.log();
1191
- }
1192
- catch (error) {
1193
- const message = error instanceof Error ? error.message : String(error);
1194
- console.log(styleText("red", `✗ Build failed: ${message}`));
1195
- console.log();
1196
- }
1197
- finally {
1198
- buildInProgress = false;
1199
- // If a rebuild was queued while building, start another build
1200
- if (rebuildQueued) {
1201
- doBuild();
1202
- }
1203
- }
1204
- }
1205
- function scheduleRebuild() {
1206
- if (debounceTimer) {
1207
- clearTimeout(debounceTimer);
1208
- }
1209
- debounceTimer = setTimeout(() => {
1210
- debounceTimer = null;
1211
- doBuild();
1212
- }, DEBOUNCE_MS);
657
+ rmSync(freshCtx.buildDir, { recursive: !0 });
658
+ } catch {
659
+ }
660
+ const elapsed = Date.now() - startTime;
661
+ console.log(styleText("green", `\u2713 Built in ${elapsed}ms`));
662
+ for (const output of outputs)
663
+ console.log(styleText("dim", ` ${basename(output)}`));
664
+ console.log();
665
+ } catch (error) {
666
+ const message = error instanceof Error ? error.message : String(error);
667
+ console.log(styleText("red", `\u2717 Build failed: ${message}`)), console.log();
668
+ } finally {
669
+ buildInProgress = !1, rebuildQueued && doBuild();
1213
670
  }
1214
- // Do initial build
1215
- await doBuild();
1216
- // Watch the entry file's directory for changes
1217
- const watchDir = ctx.entryDir;
1218
- const watcher = fsWatch(watchDir, { recursive: true }, (_eventType, filename) => {
1219
- if (!filename)
1220
- return;
1221
- // Ignore common non-source files
1222
- if (filename.includes("node_modules") ||
1223
- filename.startsWith(".") ||
1224
- filename.endsWith(".d.ts")) {
1225
- return;
1226
- }
1227
- // Only watch TypeScript and JavaScript files
1228
- if (!/\.(ts|tsx|js|jsx|mts|mjs|cts|cjs|json)$/.test(filename)) {
1229
- return;
1230
- }
1231
- if (ctx.options.verbose) {
1232
- console.log(styleText("dim", ` Changed: ${filename}`));
1233
- }
1234
- scheduleRebuild();
1235
- });
1236
- // Handle graceful shutdown
1237
- const cleanup = () => {
1238
- watcher.close();
1239
- if (debounceTimer) {
1240
- clearTimeout(debounceTimer);
1241
- }
1242
- console.log("\nStopped watching.");
1243
- process.exit(0);
1244
- };
1245
- process.on("SIGINT", cleanup);
1246
- process.on("SIGTERM", cleanup);
1247
- // Keep process alive
1248
- await new Promise(() => { });
671
+ }
672
+ function scheduleRebuild() {
673
+ debounceTimer && clearTimeout(debounceTimer), debounceTimer = setTimeout(() => {
674
+ debounceTimer = null, doBuild();
675
+ }, DEBOUNCE_MS);
676
+ }
677
+ await doBuild();
678
+ const watchDir = ctx.entryDir, watcher = fsWatch(watchDir, { recursive: !0 }, (_eventType, filename) => {
679
+ filename && (filename.includes("node_modules") || filename.startsWith(".") || filename.endsWith(".d.ts") || /\.(ts|tsx|js|jsx|mts|mjs|cts|cjs|json)$/.test(filename) && (ctx.options.verbose && console.log(styleText("dim", ` Changed: ${filename}`)), scheduleRebuild()));
680
+ }), cleanup = () => {
681
+ watcher.close(), debounceTimer && clearTimeout(debounceTimer), console.log(`
682
+ Stopped watching.`), process.exit(0);
683
+ };
684
+ process.on("SIGINT", cleanup), process.on("SIGTERM", cleanup), await new Promise(() => {
685
+ });
1249
686
  }
1250
- /**
1251
- * Show help for the build command.
1252
- */
1253
687
  export function showBundleHelp() {
1254
- console.log(`
688
+ console.log(`
1255
689
  ${cyanBold("thinkwell bundle")} - ${whiteBold("Compile TypeScript scripts into standalone executables")}
1256
690
 
1257
691
  ${greenBold("Usage:")}
@@ -1312,6 +746,6 @@ ${greenBold("Configuration via package.json:")}
1312
746
 
1313
747
  ${dim("Note: Binaries are ~70-90 MB due to the embedded Node.js runtime.")}
1314
748
  ${dim(" Use --minify to reduce bundle size (though Node.js runtime dominates).")}
1315
- `.trim() + "\n");
749
+ `.trim() + `
750
+ `);
1316
751
  }
1317
- //# sourceMappingURL=bundle.js.map