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.
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +207 -279
- package/dist/agent.js.map +1 -1
- package/dist/build.js +44 -98
- package/dist/cli/build.js +92 -227
- package/dist/cli/bundle.js +570 -1136
- package/dist/cli/check.js +125 -214
- package/dist/cli/commands.js +63 -177
- package/dist/cli/compiler-host.js +81 -190
- package/dist/cli/dependency-check.js +125 -269
- package/dist/cli/dependency-errors.js +12 -84
- package/dist/cli/fmt.js +1 -13
- package/dist/cli/init-command.js +21 -68
- package/dist/cli/init.js +90 -220
- package/dist/cli/loader.js +95 -361
- package/dist/cli/new-command.js +25 -73
- package/dist/cli/package-manager.js +50 -117
- package/dist/cli/schema.js +89 -245
- package/dist/cli/workspace.js +92 -226
- package/dist/connectors/index.js +1 -7
- package/dist/generated/features.d.ts +5 -0
- package/dist/generated/features.d.ts.map +1 -0
- package/dist/generated/features.js +4 -0
- package/dist/generated/features.js.map +1 -0
- package/dist/index.js +0 -5
- package/dist/schema.js +3 -36
- package/dist/session.js +50 -82
- package/dist/think-builder.d.ts.map +1 -1
- package/dist/think-builder.js +269 -370
- package/dist/think-builder.js.map +1 -1
- package/dist/thought-event.d.ts +1 -0
- package/dist/thought-event.d.ts.map +1 -1
- package/dist/thought-event.js +0 -1
- package/dist/thought-stream.js +60 -96
- package/dist-pkg/acp.cjs +13385 -1876
- package/dist-pkg/cli-build.cjs +171 -369
- package/dist-pkg/cli-bundle.cjs +289 -690
- package/dist-pkg/cli-check.cjs +202 -415
- package/dist-pkg/cli-dependency-check.cjs +39 -82
- package/dist-pkg/cli-dependency-errors.cjs +9 -41
- package/dist-pkg/cli-loader.cjs +90 -173
- package/dist-pkg/protocol.cjs +2 -8
- package/dist-pkg/thinkwell.cjs +876 -1842
- package/package.json +7 -6
package/dist/cli/bundle.js
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
return undefined;
|
|
146
|
-
}
|
|
75
|
+
const pkgPath = join(dir, "package.json");
|
|
76
|
+
if (existsSync(pkgPath))
|
|
147
77
|
try {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
553
|
-
|
|
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
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
603
|
-
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
629
|
-
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
-
|
|
715
|
-
|
|
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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
let
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
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
|
-
|
|
545
|
+
}
|
|
546
|
+
return awaits;
|
|
971
547
|
}
|
|
972
|
-
// ============================================================================
|
|
973
|
-
// Output Helpers
|
|
974
|
-
// ============================================================================
|
|
975
|
-
/** Log output respecting quiet mode */
|
|
976
548
|
function log(ctx, message) {
|
|
977
|
-
|
|
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
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
if (
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
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
|
-
|
|
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() +
|
|
749
|
+
`.trim() + `
|
|
750
|
+
`);
|
|
1316
751
|
}
|
|
1317
|
-
//# sourceMappingURL=bundle.js.map
|