@walkeros/cli 1.4.0-next-1771334965900 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -3
- package/README.md +62 -29
- package/dist/cli.js +209 -68
- package/dist/examples/docker-compose.runner.yml +42 -0
- package/dist/index.d.ts +23 -41
- package/dist/index.js +211 -70
- package/dist/index.js.map +1 -1
- package/dist/runtime/main.js +1919 -184
- package/examples/docker-compose.runner.yml +42 -0
- package/package.json +8 -4
- package/dist/walker.js +0 -1
package/dist/runtime/main.js
CHANGED
|
@@ -1,57 +1,118 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/runtime/env.ts
|
|
4
|
+
function validateEnv(env) {
|
|
5
|
+
const token = env.WALKEROS_TOKEN;
|
|
6
|
+
const projectId = env.PROJECT_ID;
|
|
7
|
+
const flowId = env.FLOW_ID;
|
|
8
|
+
if (flowId && (!token || !projectId)) {
|
|
9
|
+
throw new Error("FLOW_ID requires WALKEROS_TOKEN and PROJECT_ID");
|
|
10
|
+
}
|
|
11
|
+
if (token && !projectId) {
|
|
12
|
+
throw new Error("WALKEROS_TOKEN requires PROJECT_ID");
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
mode: env.MODE === "serve" ? "serve" : "collect",
|
|
16
|
+
port: parseInt(env.PORT || "8080", 10),
|
|
17
|
+
bundlePath: env.BUNDLE || "/app/flow/bundle.mjs",
|
|
18
|
+
cacheDir: env.CACHE_DIR || "/app/cache",
|
|
19
|
+
pollInterval: parseInt(env.POLL_INTERVAL || "30", 10),
|
|
20
|
+
heartbeatInterval: parseInt(env.HEARTBEAT_INTERVAL || "60", 10),
|
|
21
|
+
apiEnabled: !!(token && projectId),
|
|
22
|
+
remoteConfig: !!(token && projectId && flowId),
|
|
23
|
+
token,
|
|
24
|
+
projectId,
|
|
25
|
+
flowId
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/runtime/resolve-bundle.ts
|
|
30
|
+
import { writeFileSync } from "fs";
|
|
31
|
+
|
|
32
|
+
// src/core/stdin.ts
|
|
33
|
+
function isStdinPiped() {
|
|
34
|
+
return !process.stdin.isTTY;
|
|
35
|
+
}
|
|
36
|
+
async function readStdin() {
|
|
37
|
+
const chunks = [];
|
|
38
|
+
for await (const chunk of process.stdin) {
|
|
39
|
+
chunks.push(chunk);
|
|
40
|
+
}
|
|
41
|
+
const content = Buffer.concat(chunks).toString("utf-8");
|
|
42
|
+
if (!content.trim()) {
|
|
43
|
+
throw new Error("No input received on stdin");
|
|
44
|
+
}
|
|
45
|
+
return content;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/runtime/resolve-bundle.ts
|
|
49
|
+
var TEMP_BUNDLE_PATH = "/tmp/walkeros-bundle.mjs";
|
|
50
|
+
function isUrl(value) {
|
|
51
|
+
return value.startsWith("http://") || value.startsWith("https://");
|
|
52
|
+
}
|
|
53
|
+
async function fetchBundle(url) {
|
|
54
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Failed to fetch bundle from ${url}: ${response.status} ${response.statusText}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const content = await response.text();
|
|
61
|
+
if (!content.trim()) {
|
|
62
|
+
throw new Error(`Bundle fetched from ${url} is empty`);
|
|
63
|
+
}
|
|
64
|
+
writeFileSync(TEMP_BUNDLE_PATH, content, "utf-8");
|
|
65
|
+
return TEMP_BUNDLE_PATH;
|
|
66
|
+
}
|
|
67
|
+
async function readBundleFromStdin() {
|
|
68
|
+
const content = await readStdin();
|
|
69
|
+
writeFileSync(TEMP_BUNDLE_PATH, content, "utf-8");
|
|
70
|
+
return TEMP_BUNDLE_PATH;
|
|
71
|
+
}
|
|
72
|
+
async function resolveBundle(bundleEnv) {
|
|
73
|
+
if (isStdinPiped()) {
|
|
74
|
+
const path11 = await readBundleFromStdin();
|
|
75
|
+
return { path: path11, source: "stdin" };
|
|
76
|
+
}
|
|
77
|
+
if (isUrl(bundleEnv)) {
|
|
78
|
+
const path11 = await fetchBundle(bundleEnv);
|
|
79
|
+
return { path: path11, source: "url" };
|
|
80
|
+
}
|
|
81
|
+
return { path: bundleEnv, source: "file" };
|
|
82
|
+
}
|
|
83
|
+
|
|
3
84
|
// src/runtime/runner.ts
|
|
4
85
|
import { pathToFileURL } from "url";
|
|
5
86
|
import { resolve, dirname } from "path";
|
|
6
|
-
async function
|
|
7
|
-
|
|
87
|
+
async function loadFlow(file, config, logger, loggerConfig) {
|
|
88
|
+
const absolutePath = resolve(file);
|
|
89
|
+
const flowDir = dirname(absolutePath);
|
|
90
|
+
process.chdir(flowDir);
|
|
91
|
+
const fileUrl = pathToFileURL(absolutePath).href;
|
|
92
|
+
const module = await import(`${fileUrl}?t=${Date.now()}`);
|
|
93
|
+
if (!module.default || typeof module.default !== "function") {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Invalid flow bundle: ${file} must export a default function`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const flowContext = loggerConfig ? { ...config, logger: loggerConfig } : config;
|
|
99
|
+
const result = await module.default(flowContext);
|
|
100
|
+
if (!result || !result.collector) {
|
|
101
|
+
throw new Error(`Invalid flow bundle: ${file} must return { collector }`);
|
|
102
|
+
}
|
|
103
|
+
return { collector: result.collector, file };
|
|
104
|
+
}
|
|
105
|
+
async function swapFlow(currentHandle, newFile, config, logger, loggerConfig) {
|
|
106
|
+
const newHandle = await loadFlow(newFile, config, logger, loggerConfig);
|
|
8
107
|
try {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
process.chdir(flowDir);
|
|
12
|
-
const fileUrl = pathToFileURL(absolutePath).href;
|
|
13
|
-
const module = await import(fileUrl);
|
|
14
|
-
if (!module.default || typeof module.default !== "function") {
|
|
15
|
-
logger.throw(
|
|
16
|
-
`Invalid flow bundle: ${file} must export a default function`
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
const flowContext = loggerConfig ? { ...config, logger: loggerConfig } : config;
|
|
20
|
-
const result = await module.default(flowContext);
|
|
21
|
-
if (!result || !result.collector) {
|
|
22
|
-
logger.throw(`Invalid flow bundle: ${file} must return { collector }`);
|
|
108
|
+
if (currentHandle.collector.command) {
|
|
109
|
+
await currentHandle.collector.command("shutdown");
|
|
23
110
|
}
|
|
24
|
-
const { collector } = result;
|
|
25
|
-
logger.info("Flow running");
|
|
26
|
-
if (config?.port) {
|
|
27
|
-
logger.info(`Port: ${config.port}`);
|
|
28
|
-
}
|
|
29
|
-
const shutdown = async (signal) => {
|
|
30
|
-
logger.info(`Received ${signal}, shutting down gracefully...`);
|
|
31
|
-
try {
|
|
32
|
-
if (collector.command) {
|
|
33
|
-
await collector.command("shutdown");
|
|
34
|
-
}
|
|
35
|
-
logger.info("Shutdown complete");
|
|
36
|
-
process.exit(0);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
-
logger.error(`Error during shutdown: ${message}`);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
44
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
45
|
-
await new Promise(() => {
|
|
46
|
-
});
|
|
47
111
|
} catch (error) {
|
|
48
|
-
|
|
49
|
-
logger.error(`Failed to run flow: ${message}`);
|
|
50
|
-
if (error instanceof Error && error.stack) {
|
|
51
|
-
logger.debug("Stack trace:", { stack: error.stack });
|
|
52
|
-
}
|
|
53
|
-
throw error;
|
|
112
|
+
logger.debug(`Old flow shutdown warning: ${error}`);
|
|
54
113
|
}
|
|
114
|
+
logger.info("Flow swapped successfully");
|
|
115
|
+
return newHandle;
|
|
55
116
|
}
|
|
56
117
|
|
|
57
118
|
// src/runtime/serve.ts
|
|
@@ -81,6 +142,95 @@ function findPackageJson() {
|
|
|
81
142
|
}
|
|
82
143
|
var VERSION = JSON.parse(findPackageJson()).version;
|
|
83
144
|
|
|
145
|
+
// src/runtime/heartbeat.ts
|
|
146
|
+
import { randomBytes } from "crypto";
|
|
147
|
+
var instanceId = randomBytes(8).toString("hex");
|
|
148
|
+
function getInstanceId() {
|
|
149
|
+
return instanceId;
|
|
150
|
+
}
|
|
151
|
+
function createHeartbeat(config, logger) {
|
|
152
|
+
let timer = null;
|
|
153
|
+
const startTime2 = Date.now();
|
|
154
|
+
let configVersion = config.configVersion;
|
|
155
|
+
async function sendOnce() {
|
|
156
|
+
try {
|
|
157
|
+
await fetch(
|
|
158
|
+
`${config.appUrl}/api/projects/${config.projectId}/runners/heartbeat`,
|
|
159
|
+
{
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers: {
|
|
162
|
+
Authorization: `Bearer ${config.token}`,
|
|
163
|
+
"Content-Type": "application/json"
|
|
164
|
+
},
|
|
165
|
+
body: JSON.stringify({
|
|
166
|
+
instanceId,
|
|
167
|
+
flowId: config.flowId,
|
|
168
|
+
configVersion,
|
|
169
|
+
mode: config.mode,
|
|
170
|
+
cliVersion: VERSION,
|
|
171
|
+
uptime: Math.floor((Date.now() - startTime2) / 1e3)
|
|
172
|
+
}),
|
|
173
|
+
signal: AbortSignal.timeout(1e4)
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
178
|
+
logger.debug(`Heartbeat failed: ${message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function start() {
|
|
182
|
+
sendOnce();
|
|
183
|
+
const jitter = config.intervalMs * 0.1 * (Math.random() * 2 - 1);
|
|
184
|
+
timer = setInterval(() => sendOnce(), config.intervalMs + jitter);
|
|
185
|
+
}
|
|
186
|
+
function stop() {
|
|
187
|
+
if (timer) {
|
|
188
|
+
clearInterval(timer);
|
|
189
|
+
timer = null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function updateConfigVersion2(version) {
|
|
193
|
+
configVersion = version;
|
|
194
|
+
}
|
|
195
|
+
return { start, stop, sendOnce, updateConfigVersion: updateConfigVersion2 };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/runtime/status.ts
|
|
199
|
+
var state = null;
|
|
200
|
+
var startTime = Date.now();
|
|
201
|
+
var currentStatus = "starting";
|
|
202
|
+
var lastPollTime;
|
|
203
|
+
var lastHeartbeatTime;
|
|
204
|
+
function initStatus(initial) {
|
|
205
|
+
state = initial;
|
|
206
|
+
startTime = Date.now();
|
|
207
|
+
currentStatus = "starting";
|
|
208
|
+
}
|
|
209
|
+
function setRunning() {
|
|
210
|
+
currentStatus = "running";
|
|
211
|
+
}
|
|
212
|
+
function updateLastPoll() {
|
|
213
|
+
lastPollTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
214
|
+
}
|
|
215
|
+
function updateConfigVersion(version) {
|
|
216
|
+
if (state) state.configVersion = version;
|
|
217
|
+
}
|
|
218
|
+
function getStatus() {
|
|
219
|
+
return {
|
|
220
|
+
status: currentStatus,
|
|
221
|
+
version: VERSION,
|
|
222
|
+
mode: state?.mode ?? "collect",
|
|
223
|
+
instanceId: getInstanceId(),
|
|
224
|
+
configVersion: state?.configVersion,
|
|
225
|
+
configSource: state?.configSource ?? "local",
|
|
226
|
+
apiEnabled: state?.apiEnabled ?? false,
|
|
227
|
+
lastPoll: lastPollTime,
|
|
228
|
+
lastHeartbeat: lastHeartbeatTime,
|
|
229
|
+
uptime: Math.floor((Date.now() - startTime) / 1e3),
|
|
230
|
+
port: state?.port ?? 8080
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
84
234
|
// src/runtime/serve.ts
|
|
85
235
|
async function runServeMode(config, logger) {
|
|
86
236
|
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : config?.port || 8080;
|
|
@@ -106,6 +256,9 @@ async function runServeMode(config, logger) {
|
|
|
106
256
|
url: urlPath
|
|
107
257
|
});
|
|
108
258
|
});
|
|
259
|
+
app.get("/status", (req, res) => {
|
|
260
|
+
res.json(getStatus());
|
|
261
|
+
});
|
|
109
262
|
app.get(urlPath, (req, res) => {
|
|
110
263
|
res.type("application/javascript");
|
|
111
264
|
res.sendFile(file, { dotfiles: "allow" }, (err) => {
|
|
@@ -148,82 +301,104 @@ async function runServeMode(config, logger) {
|
|
|
148
301
|
}
|
|
149
302
|
}
|
|
150
303
|
|
|
151
|
-
// src/runtime/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const chunks = [];
|
|
160
|
-
for await (const chunk of process.stdin) {
|
|
161
|
-
chunks.push(chunk);
|
|
304
|
+
// src/runtime/config-fetcher.ts
|
|
305
|
+
async function fetchConfig(options) {
|
|
306
|
+
const url = `${options.appUrl}/api/projects/${options.projectId}/flows/${options.flowId}`;
|
|
307
|
+
const headers = {
|
|
308
|
+
Authorization: `Bearer ${options.token}`
|
|
309
|
+
};
|
|
310
|
+
if (options.lastEtag) {
|
|
311
|
+
headers["If-None-Match"] = options.lastEtag;
|
|
162
312
|
}
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
313
|
+
const response = await fetch(url, {
|
|
314
|
+
headers,
|
|
315
|
+
signal: AbortSignal.timeout(3e4)
|
|
316
|
+
});
|
|
317
|
+
if (response.status === 304) {
|
|
318
|
+
return { changed: false };
|
|
166
319
|
}
|
|
167
|
-
return content;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// src/runtime/resolve-bundle.ts
|
|
171
|
-
var TEMP_BUNDLE_PATH = "/tmp/walkeros-bundle.mjs";
|
|
172
|
-
function isUrl(value) {
|
|
173
|
-
return value.startsWith("http://") || value.startsWith("https://");
|
|
174
|
-
}
|
|
175
|
-
async function fetchBundle(url) {
|
|
176
|
-
const response = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
177
320
|
if (!response.ok) {
|
|
178
321
|
throw new Error(
|
|
179
|
-
`
|
|
322
|
+
`Config fetch failed: ${response.status} ${response.statusText}`
|
|
180
323
|
);
|
|
181
324
|
}
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
325
|
+
const data = await response.json();
|
|
326
|
+
const etag = response.headers.get("etag") || "";
|
|
327
|
+
const version = etag.replace(/"/g, "");
|
|
328
|
+
return {
|
|
329
|
+
content: data.content,
|
|
330
|
+
version,
|
|
331
|
+
etag,
|
|
332
|
+
changed: true
|
|
333
|
+
};
|
|
188
334
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
335
|
+
|
|
336
|
+
// src/runtime/cache.ts
|
|
337
|
+
import {
|
|
338
|
+
existsSync,
|
|
339
|
+
mkdirSync,
|
|
340
|
+
copyFileSync,
|
|
341
|
+
writeFileSync as writeFileSync2,
|
|
342
|
+
readFileSync as readFileSync2
|
|
343
|
+
} from "fs";
|
|
344
|
+
import { join as join2 } from "path";
|
|
345
|
+
function writeCache(cacheDir, bundlePath, configContent, version) {
|
|
346
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
347
|
+
copyFileSync(bundlePath, join2(cacheDir, "bundle.mjs"));
|
|
348
|
+
writeFileSync2(join2(cacheDir, "config.json"), configContent, "utf-8");
|
|
349
|
+
const meta = { version, timestamp: Date.now() };
|
|
350
|
+
writeFileSync2(join2(cacheDir, "meta.json"), JSON.stringify(meta), "utf-8");
|
|
193
351
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
352
|
+
function readCache(cacheDir) {
|
|
353
|
+
try {
|
|
354
|
+
const metaPath = join2(cacheDir, "meta.json");
|
|
355
|
+
const bundlePath = join2(cacheDir, "bundle.mjs");
|
|
356
|
+
if (!existsSync(metaPath) || !existsSync(bundlePath)) return null;
|
|
357
|
+
const meta = JSON.parse(readFileSync2(metaPath, "utf-8"));
|
|
358
|
+
return { bundlePath, version: meta.version };
|
|
359
|
+
} catch {
|
|
360
|
+
return null;
|
|
202
361
|
}
|
|
203
|
-
return { path: bundleEnv, source: "file" };
|
|
204
362
|
}
|
|
205
363
|
|
|
206
|
-
// src/runtime/
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
364
|
+
// src/runtime/poller.ts
|
|
365
|
+
function createPoller(config, logger) {
|
|
366
|
+
let timer = null;
|
|
367
|
+
let lastEtag;
|
|
368
|
+
async function pollOnce() {
|
|
369
|
+
try {
|
|
370
|
+
const result = await fetchConfig({
|
|
371
|
+
...config.fetchOptions,
|
|
372
|
+
lastEtag
|
|
373
|
+
});
|
|
374
|
+
if (!result.changed) {
|
|
375
|
+
logger.debug("Config unchanged");
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
logger.info(`New config version: ${result.version}`);
|
|
379
|
+
lastEtag = result.etag;
|
|
380
|
+
await config.onUpdate(result.content, result.version);
|
|
381
|
+
logger.info("Config updated successfully");
|
|
382
|
+
} catch (error) {
|
|
383
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
384
|
+
logger.error(`Poll error: ${message}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function start() {
|
|
388
|
+
lastEtag = void 0;
|
|
389
|
+
const jitter = config.intervalMs * 0.15 * (Math.random() * 2 - 1);
|
|
390
|
+
timer = setInterval(() => pollOnce(), config.intervalMs + jitter);
|
|
391
|
+
logger.info(
|
|
392
|
+
`Polling every ${Math.round((config.intervalMs + jitter) / 1e3)}s`
|
|
223
393
|
);
|
|
224
394
|
}
|
|
225
|
-
|
|
226
|
-
|
|
395
|
+
function stop() {
|
|
396
|
+
if (timer) {
|
|
397
|
+
clearInterval(timer);
|
|
398
|
+
timer = null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return { start, stop, pollOnce };
|
|
227
402
|
}
|
|
228
403
|
|
|
229
404
|
// src/core/logger.ts
|
|
@@ -302,91 +477,1651 @@ function createLogger(options = {}) {
|
|
|
302
477
|
}
|
|
303
478
|
};
|
|
304
479
|
}
|
|
480
|
+
function createCommandLogger(options) {
|
|
481
|
+
return createLogger({
|
|
482
|
+
verbose: options.verbose,
|
|
483
|
+
silent: options.silent ?? false,
|
|
484
|
+
json: options.json,
|
|
485
|
+
stderr: options.stderr
|
|
486
|
+
});
|
|
487
|
+
}
|
|
305
488
|
|
|
306
|
-
// src/
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
},
|
|
326
|
-
scope: (name) => {
|
|
327
|
-
return adaptLogger(cliLogger);
|
|
328
|
-
}
|
|
329
|
-
};
|
|
489
|
+
// src/commands/run/utils.ts
|
|
490
|
+
import path10 from "path";
|
|
491
|
+
import fs10 from "fs-extra";
|
|
492
|
+
|
|
493
|
+
// src/commands/bundle/index.ts
|
|
494
|
+
import path9 from "path";
|
|
495
|
+
import fs9 from "fs-extra";
|
|
496
|
+
import { getPlatform as getPlatform2 } from "@walkeros/core";
|
|
497
|
+
|
|
498
|
+
// src/core/output.ts
|
|
499
|
+
import fs from "fs-extra";
|
|
500
|
+
|
|
501
|
+
// src/core/tmp.ts
|
|
502
|
+
import path from "path";
|
|
503
|
+
var DEFAULT_TMP_ROOT = ".tmp";
|
|
504
|
+
function getTmpPath(tmpDir, ...segments) {
|
|
505
|
+
const root = tmpDir || DEFAULT_TMP_ROOT;
|
|
506
|
+
const absoluteRoot = path.isAbsolute(root) ? root : path.resolve(root);
|
|
507
|
+
return path.join(absoluteRoot, ...segments);
|
|
330
508
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
509
|
+
|
|
510
|
+
// src/core/asset-resolver.ts
|
|
511
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
512
|
+
import { existsSync as existsSync3 } from "fs";
|
|
513
|
+
import path3 from "path";
|
|
514
|
+
|
|
515
|
+
// src/config/utils.ts
|
|
516
|
+
import fs2 from "fs-extra";
|
|
517
|
+
import path2 from "path";
|
|
518
|
+
|
|
519
|
+
// src/lib/config-file.ts
|
|
520
|
+
import {
|
|
521
|
+
readFileSync as readFileSync3,
|
|
522
|
+
writeFileSync as writeFileSync3,
|
|
523
|
+
mkdirSync as mkdirSync2,
|
|
524
|
+
unlinkSync,
|
|
525
|
+
existsSync as existsSync2
|
|
526
|
+
} from "fs";
|
|
527
|
+
import { join as join3 } from "path";
|
|
528
|
+
import { homedir } from "os";
|
|
529
|
+
function getConfigDir() {
|
|
530
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
531
|
+
const base = xdgConfig || join3(homedir(), ".config");
|
|
532
|
+
return join3(base, "walkeros");
|
|
533
|
+
}
|
|
534
|
+
function getConfigPath() {
|
|
535
|
+
return join3(getConfigDir(), "config.json");
|
|
536
|
+
}
|
|
537
|
+
function readConfig() {
|
|
538
|
+
const configPath = getConfigPath();
|
|
539
|
+
try {
|
|
540
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
541
|
+
return JSON.parse(content);
|
|
542
|
+
} catch {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function resolveToken() {
|
|
547
|
+
const envToken = process.env.WALKEROS_TOKEN;
|
|
548
|
+
if (envToken) return { token: envToken, source: "env" };
|
|
549
|
+
const config = readConfig();
|
|
550
|
+
if (config?.token) return { token: config.token, source: "config" };
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/core/auth.ts
|
|
555
|
+
function getToken() {
|
|
556
|
+
const result = resolveToken();
|
|
557
|
+
return result?.token;
|
|
558
|
+
}
|
|
559
|
+
function getAuthHeaders() {
|
|
560
|
+
const token = getToken();
|
|
561
|
+
if (!token) return {};
|
|
562
|
+
return { Authorization: `Bearer ${token}` };
|
|
563
|
+
}
|
|
564
|
+
async function authenticatedFetch(url, init) {
|
|
565
|
+
const authHeaders = getAuthHeaders();
|
|
566
|
+
const existingHeaders = init?.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : Array.isArray(init?.headers) ? Object.fromEntries(init.headers) : init?.headers ?? {};
|
|
567
|
+
return fetch(url, {
|
|
568
|
+
...init,
|
|
569
|
+
headers: { ...existingHeaders, ...authHeaders }
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/config/utils.ts
|
|
574
|
+
function isUrl2(str) {
|
|
575
|
+
try {
|
|
576
|
+
const url = new URL(str);
|
|
577
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
578
|
+
} catch {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async function downloadFromUrl(url) {
|
|
583
|
+
if (!isUrl2(url)) {
|
|
584
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
360
585
|
}
|
|
361
|
-
let file;
|
|
362
586
|
try {
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
cliLogger.log(`Bundle: fetched from ${bundleEnv}`);
|
|
369
|
-
} else {
|
|
370
|
-
cliLogger.log(`Bundle: ${file}`);
|
|
587
|
+
const response = await authenticatedFetch(url);
|
|
588
|
+
if (!response.ok) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
`Failed to download ${url}: ${response.status} ${response.statusText}`
|
|
591
|
+
);
|
|
371
592
|
}
|
|
593
|
+
const content = await response.text();
|
|
594
|
+
const downloadsDir = getTmpPath(void 0, "downloads");
|
|
595
|
+
await fs2.ensureDir(downloadsDir);
|
|
596
|
+
const tempPath = path2.join(downloadsDir, "flow.json");
|
|
597
|
+
await fs2.writeFile(tempPath, content, "utf-8");
|
|
598
|
+
return tempPath;
|
|
372
599
|
} catch (error) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
600
|
+
if (error instanceof Error) {
|
|
601
|
+
throw new Error(`Failed to download from URL: ${error.message}`);
|
|
602
|
+
}
|
|
603
|
+
throw error;
|
|
376
604
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
605
|
+
}
|
|
606
|
+
async function loadJsonConfig(configPath) {
|
|
607
|
+
let absolutePath;
|
|
608
|
+
let isTemporary = false;
|
|
609
|
+
if (isUrl2(configPath)) {
|
|
610
|
+
absolutePath = await downloadFromUrl(configPath);
|
|
611
|
+
isTemporary = true;
|
|
612
|
+
} else {
|
|
613
|
+
absolutePath = path2.resolve(configPath);
|
|
614
|
+
if (!await fs2.pathExists(absolutePath)) {
|
|
615
|
+
throw new Error(`Configuration file not found: ${absolutePath}`);
|
|
386
616
|
}
|
|
617
|
+
}
|
|
618
|
+
try {
|
|
619
|
+
const rawConfig = await fs2.readJson(absolutePath);
|
|
620
|
+
return rawConfig;
|
|
387
621
|
} catch (error) {
|
|
388
|
-
|
|
389
|
-
|
|
622
|
+
throw new Error(
|
|
623
|
+
`Invalid JSON in config file: ${configPath}. ${error instanceof Error ? error.message : error}`
|
|
624
|
+
);
|
|
625
|
+
} finally {
|
|
626
|
+
if (isTemporary) {
|
|
627
|
+
try {
|
|
628
|
+
await fs2.remove(absolutePath);
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// src/core/asset-resolver.ts
|
|
636
|
+
var cachedAssetDir;
|
|
637
|
+
function getAssetDir() {
|
|
638
|
+
if (cachedAssetDir) return cachedAssetDir;
|
|
639
|
+
const currentFile = fileURLToPath2(import.meta.url);
|
|
640
|
+
let dir = path3.dirname(currentFile);
|
|
641
|
+
while (dir !== path3.dirname(dir)) {
|
|
642
|
+
if (existsSync3(path3.join(dir, "examples"))) {
|
|
643
|
+
cachedAssetDir = dir;
|
|
644
|
+
return dir;
|
|
645
|
+
}
|
|
646
|
+
dir = path3.dirname(dir);
|
|
647
|
+
}
|
|
648
|
+
cachedAssetDir = path3.dirname(currentFile);
|
|
649
|
+
return cachedAssetDir;
|
|
650
|
+
}
|
|
651
|
+
function resolveAsset(assetPath, assetType, baseDir) {
|
|
652
|
+
if (isUrl2(assetPath)) {
|
|
653
|
+
return assetPath;
|
|
390
654
|
}
|
|
655
|
+
if (!assetPath.includes("/") && !assetPath.includes("\\")) {
|
|
656
|
+
const assetDir = getAssetDir();
|
|
657
|
+
return path3.join(assetDir, "examples", assetPath);
|
|
658
|
+
}
|
|
659
|
+
if (path3.isAbsolute(assetPath)) {
|
|
660
|
+
return assetPath;
|
|
661
|
+
}
|
|
662
|
+
return path3.resolve(baseDir || process.cwd(), assetPath);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/core/local-packages.ts
|
|
666
|
+
import path4 from "path";
|
|
667
|
+
import fs3 from "fs-extra";
|
|
668
|
+
async function resolveLocalPackage(packageName, localPath, configDir, logger) {
|
|
669
|
+
const absolutePath = path4.isAbsolute(localPath) ? localPath : path4.resolve(configDir, localPath);
|
|
670
|
+
if (!await fs3.pathExists(absolutePath)) {
|
|
671
|
+
throw new Error(
|
|
672
|
+
`Local package path not found: ${localPath} (resolved to ${absolutePath})`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
const pkgJsonPath = path4.join(absolutePath, "package.json");
|
|
676
|
+
if (!await fs3.pathExists(pkgJsonPath)) {
|
|
677
|
+
throw new Error(
|
|
678
|
+
`No package.json found at ${absolutePath}. Is this a valid package directory?`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
const distPath = path4.join(absolutePath, "dist");
|
|
682
|
+
const hasDistFolder = await fs3.pathExists(distPath);
|
|
683
|
+
if (!hasDistFolder) {
|
|
684
|
+
logger.warn(
|
|
685
|
+
`\u26A0\uFE0F ${packageName}: No dist/ folder found. Using package root.`
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
name: packageName,
|
|
690
|
+
absolutePath,
|
|
691
|
+
distPath: hasDistFolder ? distPath : absolutePath,
|
|
692
|
+
hasDistFolder
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
async function copyLocalPackage(localPkg, targetDir, logger) {
|
|
696
|
+
const packageDir = path4.join(targetDir, "node_modules", localPkg.name);
|
|
697
|
+
await fs3.ensureDir(path4.dirname(packageDir));
|
|
698
|
+
await fs3.copy(
|
|
699
|
+
path4.join(localPkg.absolutePath, "package.json"),
|
|
700
|
+
path4.join(packageDir, "package.json")
|
|
701
|
+
);
|
|
702
|
+
if (localPkg.hasDistFolder) {
|
|
703
|
+
await fs3.copy(localPkg.distPath, path4.join(packageDir, "dist"));
|
|
704
|
+
} else {
|
|
705
|
+
const entries = await fs3.readdir(localPkg.absolutePath);
|
|
706
|
+
for (const entry of entries) {
|
|
707
|
+
if (!["node_modules", ".turbo", ".git"].includes(entry)) {
|
|
708
|
+
await fs3.copy(
|
|
709
|
+
path4.join(localPkg.absolutePath, entry),
|
|
710
|
+
path4.join(packageDir, entry)
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
logger.info(`\u{1F4E6} Using local: ${localPkg.name} from ${localPkg.absolutePath}`);
|
|
716
|
+
return packageDir;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/core/input-detector.ts
|
|
720
|
+
import fs4 from "fs-extra";
|
|
721
|
+
|
|
722
|
+
// src/config/validators.ts
|
|
723
|
+
import { schemas } from "@walkeros/core/dev";
|
|
724
|
+
var { safeParseSetup } = schemas;
|
|
725
|
+
function validateFlowSetup(data) {
|
|
726
|
+
const result = safeParseSetup(data);
|
|
727
|
+
if (!result.success) {
|
|
728
|
+
const errors = result.error.issues.map((issue) => {
|
|
729
|
+
const path11 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
|
|
730
|
+
return ` - ${path11}: ${issue.message}`;
|
|
731
|
+
}).join("\n");
|
|
732
|
+
throw new Error(`Invalid configuration:
|
|
733
|
+
${errors}`);
|
|
734
|
+
}
|
|
735
|
+
return result.data;
|
|
736
|
+
}
|
|
737
|
+
function getAvailableFlows(setup) {
|
|
738
|
+
return Object.keys(setup.flows);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/config/build-defaults.ts
|
|
742
|
+
var WEB_BUILD_DEFAULTS = {
|
|
743
|
+
format: "iife",
|
|
744
|
+
platform: "browser",
|
|
745
|
+
target: "es2020",
|
|
746
|
+
minify: true,
|
|
747
|
+
sourcemap: false,
|
|
748
|
+
cache: true,
|
|
749
|
+
windowCollector: "collector",
|
|
750
|
+
windowElb: "elb"
|
|
751
|
+
};
|
|
752
|
+
var SERVER_BUILD_DEFAULTS = {
|
|
753
|
+
format: "esm",
|
|
754
|
+
platform: "node",
|
|
755
|
+
target: "node20",
|
|
756
|
+
minify: true,
|
|
757
|
+
sourcemap: false,
|
|
758
|
+
cache: true
|
|
759
|
+
};
|
|
760
|
+
var DEFAULT_OUTPUT_PATHS = {
|
|
761
|
+
web: "./dist/walker.js",
|
|
762
|
+
server: "./dist/bundle.mjs"
|
|
763
|
+
};
|
|
764
|
+
function getBuildDefaults(platform) {
|
|
765
|
+
return platform === "web" ? WEB_BUILD_DEFAULTS : SERVER_BUILD_DEFAULTS;
|
|
766
|
+
}
|
|
767
|
+
function getDefaultOutput(platform) {
|
|
768
|
+
return DEFAULT_OUTPUT_PATHS[platform];
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/config/loader.ts
|
|
772
|
+
import path5 from "path";
|
|
773
|
+
import fs5 from "fs-extra";
|
|
774
|
+
import { getFlowConfig, getPlatform } from "@walkeros/core";
|
|
775
|
+
var DEFAULT_INCLUDE_FOLDER = "./shared";
|
|
776
|
+
function loadBundleConfig(rawConfig, options) {
|
|
777
|
+
const setup = validateFlowSetup(rawConfig);
|
|
778
|
+
const availableFlows = getAvailableFlows(setup);
|
|
779
|
+
const flowName = resolveFlow(setup, options.flowName, availableFlows);
|
|
780
|
+
const flowConfig = getFlowConfig(setup, flowName);
|
|
781
|
+
const platform = getPlatform(flowConfig);
|
|
782
|
+
if (!platform) {
|
|
783
|
+
throw new Error(
|
|
784
|
+
`Invalid configuration: flow "${flowName}" must have a "web" or "server" key.`
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
const buildDefaults = getBuildDefaults(platform);
|
|
788
|
+
const packages = flowConfig.packages || {};
|
|
789
|
+
const output = options.buildOverrides?.output || getDefaultOutput(platform);
|
|
790
|
+
const configDir = isUrl2(options.configPath) ? process.cwd() : path5.dirname(options.configPath);
|
|
791
|
+
let includes = setup.include;
|
|
792
|
+
if (!includes) {
|
|
793
|
+
const defaultIncludePath = path5.resolve(configDir, DEFAULT_INCLUDE_FOLDER);
|
|
794
|
+
if (fs5.pathExistsSync(defaultIncludePath)) {
|
|
795
|
+
includes = [DEFAULT_INCLUDE_FOLDER];
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const buildOptions = {
|
|
799
|
+
...buildDefaults,
|
|
800
|
+
packages,
|
|
801
|
+
output,
|
|
802
|
+
include: includes,
|
|
803
|
+
configDir,
|
|
804
|
+
...options.buildOverrides
|
|
805
|
+
};
|
|
806
|
+
const isMultiFlow = availableFlows.length > 1;
|
|
807
|
+
if (isMultiFlow && options.logger) {
|
|
808
|
+
options.logger.info(
|
|
809
|
+
`\u{1F4E6} Using flow: ${flowName} (${availableFlows.length} total)`
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
return {
|
|
813
|
+
flowConfig,
|
|
814
|
+
buildOptions,
|
|
815
|
+
flowName,
|
|
816
|
+
isMultiFlow,
|
|
817
|
+
availableFlows
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function resolveFlow(setup, requestedFlow, available) {
|
|
821
|
+
if (available.length === 1) {
|
|
822
|
+
return available[0];
|
|
823
|
+
}
|
|
824
|
+
if (!requestedFlow) {
|
|
825
|
+
throw new Error(
|
|
826
|
+
`Multiple flows found. Please specify a flow using --flow flag.
|
|
827
|
+
Available flows: ${available.join(", ")}`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
if (!available.includes(requestedFlow)) {
|
|
831
|
+
throw new Error(
|
|
832
|
+
`Flow "${requestedFlow}" not found in configuration.
|
|
833
|
+
Available flows: ${available.join(", ")}`
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
return requestedFlow;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/commands/bundle/bundler.ts
|
|
840
|
+
import esbuild from "esbuild";
|
|
841
|
+
import path8 from "path";
|
|
842
|
+
import fs8 from "fs-extra";
|
|
843
|
+
import { packageNameToVariable } from "@walkeros/core";
|
|
844
|
+
|
|
845
|
+
// src/commands/bundle/package-manager.ts
|
|
846
|
+
import pacote from "pacote";
|
|
847
|
+
import path6 from "path";
|
|
848
|
+
import fs6 from "fs-extra";
|
|
849
|
+
|
|
850
|
+
// src/core/cache-utils.ts
|
|
851
|
+
import { getHashServer } from "@walkeros/server-core";
|
|
852
|
+
var HASH_LENGTH = 12;
|
|
853
|
+
function isMutableVersion(version) {
|
|
854
|
+
return version === "latest" || version.includes("^") || version.includes("~") || version.includes("*") || version.includes("x");
|
|
855
|
+
}
|
|
856
|
+
function getTodayDate() {
|
|
857
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
858
|
+
}
|
|
859
|
+
async function getPackageCacheKey(packageName, version, date) {
|
|
860
|
+
const safeName = packageName.replace(/\//g, "-").replace(/@/g, "");
|
|
861
|
+
if (isMutableVersion(version)) {
|
|
862
|
+
const dateStr = date ?? getTodayDate();
|
|
863
|
+
const input2 = `${safeName}@${version}:${dateStr}`;
|
|
864
|
+
return getHashServer(input2, HASH_LENGTH);
|
|
865
|
+
}
|
|
866
|
+
const input = `${safeName}@${version}`;
|
|
867
|
+
return getHashServer(input, HASH_LENGTH);
|
|
868
|
+
}
|
|
869
|
+
function normalizeJson(content) {
|
|
870
|
+
const parsed = JSON.parse(content);
|
|
871
|
+
return JSON.stringify(parsed);
|
|
872
|
+
}
|
|
873
|
+
async function getFlowConfigCacheKey(content, date) {
|
|
874
|
+
const dateStr = date ?? getTodayDate();
|
|
875
|
+
const normalized = normalizeJson(content);
|
|
876
|
+
const input = `${normalized}:${dateStr}`;
|
|
877
|
+
return getHashServer(input, HASH_LENGTH);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/commands/bundle/package-manager.ts
|
|
881
|
+
var PACKAGE_DOWNLOAD_TIMEOUT_MS = 6e4;
|
|
882
|
+
async function withTimeout(promise, ms, errorMessage) {
|
|
883
|
+
const timeout = new Promise(
|
|
884
|
+
(_, reject) => setTimeout(() => reject(new Error(errorMessage)), ms)
|
|
885
|
+
);
|
|
886
|
+
return Promise.race([promise, timeout]);
|
|
887
|
+
}
|
|
888
|
+
function getPackageDirectory(baseDir, packageName, version) {
|
|
889
|
+
return path6.join(baseDir, "node_modules", packageName);
|
|
890
|
+
}
|
|
891
|
+
async function getCachedPackagePath(pkg, tmpDir) {
|
|
892
|
+
const cacheDir = getTmpPath(tmpDir, "cache", "packages");
|
|
893
|
+
const cacheKey = await getPackageCacheKey(pkg.name, pkg.version);
|
|
894
|
+
return path6.join(cacheDir, cacheKey);
|
|
895
|
+
}
|
|
896
|
+
async function isPackageCached(pkg, tmpDir) {
|
|
897
|
+
const cachedPath = await getCachedPackagePath(pkg, tmpDir);
|
|
898
|
+
return fs6.pathExists(cachedPath);
|
|
899
|
+
}
|
|
900
|
+
function validateNoDuplicatePackages(packages) {
|
|
901
|
+
const packageMap = /* @__PURE__ */ new Map();
|
|
902
|
+
for (const pkg of packages) {
|
|
903
|
+
if (!packageMap.has(pkg.name)) {
|
|
904
|
+
packageMap.set(pkg.name, []);
|
|
905
|
+
}
|
|
906
|
+
packageMap.get(pkg.name).push(pkg.version);
|
|
907
|
+
}
|
|
908
|
+
const conflicts = [];
|
|
909
|
+
for (const [name, versions] of packageMap.entries()) {
|
|
910
|
+
const uniqueVersions = [...new Set(versions)];
|
|
911
|
+
if (uniqueVersions.length > 1) {
|
|
912
|
+
conflicts.push(`${name}: [${uniqueVersions.join(", ")}]`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if (conflicts.length > 0) {
|
|
916
|
+
throw new Error(
|
|
917
|
+
`Version conflicts detected:
|
|
918
|
+
${conflicts.map((c) => ` - ${c}`).join("\n")}
|
|
919
|
+
|
|
920
|
+
Each package must use the same version across all declarations. Please update your configuration to use consistent versions.`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
async function resolveDependencies(pkg, packageDir, logger, visited = /* @__PURE__ */ new Set()) {
|
|
925
|
+
const dependencies = [];
|
|
926
|
+
const pkgKey = `${pkg.name}@${pkg.version}`;
|
|
927
|
+
if (visited.has(pkgKey)) {
|
|
928
|
+
return dependencies;
|
|
929
|
+
}
|
|
930
|
+
visited.add(pkgKey);
|
|
931
|
+
try {
|
|
932
|
+
const packageJsonPath = path6.join(packageDir, "package.json");
|
|
933
|
+
if (await fs6.pathExists(packageJsonPath)) {
|
|
934
|
+
const packageJson = await fs6.readJson(packageJsonPath);
|
|
935
|
+
const deps = {
|
|
936
|
+
...packageJson.dependencies,
|
|
937
|
+
...packageJson.peerDependencies
|
|
938
|
+
};
|
|
939
|
+
for (const [name, versionSpec] of Object.entries(deps)) {
|
|
940
|
+
if (typeof versionSpec === "string") {
|
|
941
|
+
dependencies.push({ name, version: versionSpec });
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
} catch (error) {
|
|
946
|
+
logger.debug(`Failed to read dependencies for ${pkgKey}: ${error}`);
|
|
947
|
+
}
|
|
948
|
+
return dependencies;
|
|
949
|
+
}
|
|
950
|
+
async function downloadPackages(packages, targetDir, logger, useCache = true, configDir, tmpDir) {
|
|
951
|
+
const packagePaths = /* @__PURE__ */ new Map();
|
|
952
|
+
const downloadQueue = [...packages];
|
|
953
|
+
const processed = /* @__PURE__ */ new Set();
|
|
954
|
+
const userSpecifiedPackages = new Set(packages.map((p) => p.name));
|
|
955
|
+
const localPackageMap = /* @__PURE__ */ new Map();
|
|
956
|
+
for (const pkg of packages) {
|
|
957
|
+
if (pkg.path) {
|
|
958
|
+
localPackageMap.set(pkg.name, pkg.path);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
validateNoDuplicatePackages(packages);
|
|
962
|
+
await fs6.ensureDir(targetDir);
|
|
963
|
+
while (downloadQueue.length > 0) {
|
|
964
|
+
const pkg = downloadQueue.shift();
|
|
965
|
+
const pkgKey = `${pkg.name}@${pkg.version}`;
|
|
966
|
+
if (processed.has(pkgKey)) {
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
processed.add(pkgKey);
|
|
970
|
+
if (!pkg.path && localPackageMap.has(pkg.name)) {
|
|
971
|
+
pkg.path = localPackageMap.get(pkg.name);
|
|
972
|
+
}
|
|
973
|
+
if (pkg.path) {
|
|
974
|
+
const localPkg = await resolveLocalPackage(
|
|
975
|
+
pkg.name,
|
|
976
|
+
pkg.path,
|
|
977
|
+
configDir || process.cwd(),
|
|
978
|
+
logger
|
|
979
|
+
);
|
|
980
|
+
const installedPath = await copyLocalPackage(localPkg, targetDir, logger);
|
|
981
|
+
packagePaths.set(pkg.name, installedPath);
|
|
982
|
+
const deps = await resolveDependencies(pkg, installedPath, logger);
|
|
983
|
+
for (const dep of deps) {
|
|
984
|
+
const depKey = `${dep.name}@${dep.version}`;
|
|
985
|
+
if (!processed.has(depKey)) {
|
|
986
|
+
downloadQueue.push(dep);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
const packageSpec = `${pkg.name}@${pkg.version}`;
|
|
992
|
+
const packageDir = getPackageDirectory(targetDir, pkg.name, pkg.version);
|
|
993
|
+
const cachedPath = await getCachedPackagePath(pkg, tmpDir);
|
|
994
|
+
if (useCache && await isPackageCached(pkg, tmpDir)) {
|
|
995
|
+
if (userSpecifiedPackages.has(pkg.name)) {
|
|
996
|
+
logger.debug(`Downloading ${packageSpec} (cached)`);
|
|
997
|
+
}
|
|
998
|
+
try {
|
|
999
|
+
await fs6.ensureDir(path6.dirname(packageDir));
|
|
1000
|
+
await fs6.copy(cachedPath, packageDir);
|
|
1001
|
+
packagePaths.set(pkg.name, packageDir);
|
|
1002
|
+
const deps = await resolveDependencies(pkg, packageDir, logger);
|
|
1003
|
+
for (const dep of deps) {
|
|
1004
|
+
const depKey = `${dep.name}@${dep.version}`;
|
|
1005
|
+
if (!processed.has(depKey)) {
|
|
1006
|
+
downloadQueue.push(dep);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
continue;
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
logger.debug(
|
|
1012
|
+
`Failed to use cache for ${packageSpec}, downloading fresh: ${error}`
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
try {
|
|
1017
|
+
await fs6.ensureDir(path6.dirname(packageDir));
|
|
1018
|
+
const cacheDir = process.env.NPM_CACHE_DIR || getTmpPath(tmpDir, "cache", "npm");
|
|
1019
|
+
await withTimeout(
|
|
1020
|
+
pacote.extract(packageSpec, packageDir, {
|
|
1021
|
+
// Force npm registry download, prevent workspace resolution
|
|
1022
|
+
registry: "https://registry.npmjs.org",
|
|
1023
|
+
// Force online fetching from registry (don't use cached workspace packages)
|
|
1024
|
+
preferOnline: true,
|
|
1025
|
+
// Cache for performance
|
|
1026
|
+
cache: cacheDir,
|
|
1027
|
+
// Don't resolve relative to workspace context
|
|
1028
|
+
where: void 0
|
|
1029
|
+
}),
|
|
1030
|
+
PACKAGE_DOWNLOAD_TIMEOUT_MS,
|
|
1031
|
+
`Package download timed out after ${PACKAGE_DOWNLOAD_TIMEOUT_MS / 1e3}s: ${packageSpec}`
|
|
1032
|
+
);
|
|
1033
|
+
if (userSpecifiedPackages.has(pkg.name)) {
|
|
1034
|
+
const pkgStats = await fs6.stat(path6.join(packageDir, "package.json"));
|
|
1035
|
+
const pkgJsonSize = pkgStats.size;
|
|
1036
|
+
const sizeKB = (pkgJsonSize / 1024).toFixed(1);
|
|
1037
|
+
logger.debug(`Downloading ${packageSpec} (${sizeKB} KB)`);
|
|
1038
|
+
}
|
|
1039
|
+
if (useCache) {
|
|
1040
|
+
try {
|
|
1041
|
+
await fs6.ensureDir(path6.dirname(cachedPath));
|
|
1042
|
+
await fs6.copy(packageDir, cachedPath);
|
|
1043
|
+
} catch (cacheError) {
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
packagePaths.set(pkg.name, packageDir);
|
|
1047
|
+
const deps = await resolveDependencies(pkg, packageDir, logger);
|
|
1048
|
+
for (const dep of deps) {
|
|
1049
|
+
const depKey = `${dep.name}@${dep.version}`;
|
|
1050
|
+
if (!processed.has(depKey)) {
|
|
1051
|
+
downloadQueue.push(dep);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
throw new Error(`Failed to download ${packageSpec}: ${error}`);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return packagePaths;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// src/core/build-cache.ts
|
|
1062
|
+
import fs7 from "fs-extra";
|
|
1063
|
+
import path7 from "path";
|
|
1064
|
+
async function getBuildCachePath(configContent, tmpDir) {
|
|
1065
|
+
const cacheDir = getTmpPath(tmpDir, "cache", "builds");
|
|
1066
|
+
const cacheKey = await getFlowConfigCacheKey(configContent);
|
|
1067
|
+
return path7.join(cacheDir, `${cacheKey}.js`);
|
|
1068
|
+
}
|
|
1069
|
+
async function isBuildCached(configContent, tmpDir) {
|
|
1070
|
+
const cachePath = await getBuildCachePath(configContent, tmpDir);
|
|
1071
|
+
return fs7.pathExists(cachePath);
|
|
1072
|
+
}
|
|
1073
|
+
async function cacheBuild(configContent, buildOutput, tmpDir) {
|
|
1074
|
+
const cachePath = await getBuildCachePath(configContent, tmpDir);
|
|
1075
|
+
await fs7.ensureDir(path7.dirname(cachePath));
|
|
1076
|
+
await fs7.writeFile(cachePath, buildOutput, "utf-8");
|
|
1077
|
+
}
|
|
1078
|
+
async function getCachedBuild(configContent, tmpDir) {
|
|
1079
|
+
const cachePath = await getBuildCachePath(configContent, tmpDir);
|
|
1080
|
+
if (await fs7.pathExists(cachePath)) {
|
|
1081
|
+
return await fs7.readFile(cachePath, "utf-8");
|
|
1082
|
+
}
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/commands/bundle/bundler.ts
|
|
1087
|
+
function isInlineCode(code) {
|
|
1088
|
+
return code !== null && typeof code === "object" && !Array.isArray(code) && "push" in code;
|
|
1089
|
+
}
|
|
1090
|
+
function validateReference(type, name, ref) {
|
|
1091
|
+
const hasPackage = !!ref.package;
|
|
1092
|
+
const hasCode = isInlineCode(ref.code);
|
|
1093
|
+
if (hasPackage && hasCode) {
|
|
1094
|
+
throw new Error(
|
|
1095
|
+
`${type} "${name}": Cannot specify both package and code. Use one or the other.`
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
if (!hasPackage && !hasCode) {
|
|
1099
|
+
throw new Error(`${type} "${name}": Must specify either package or code.`);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
function generateInlineCode(inline, config, env, chain, chainPropertyName, isDestination) {
|
|
1103
|
+
const pushFn = inline.push.replace("$code:", "");
|
|
1104
|
+
const initFn = inline.init ? inline.init.replace("$code:", "") : void 0;
|
|
1105
|
+
const typeLine = inline.type ? `type: '${inline.type}',` : "";
|
|
1106
|
+
const chainLine = chain && chainPropertyName ? `${chainPropertyName}: ${JSON.stringify(chain)},` : "";
|
|
1107
|
+
if (isDestination) {
|
|
1108
|
+
return `{
|
|
1109
|
+
code: {
|
|
1110
|
+
${typeLine}
|
|
1111
|
+
config: ${JSON.stringify(config || {})},
|
|
1112
|
+
${initFn ? `init: ${initFn},` : ""}
|
|
1113
|
+
push: ${pushFn}
|
|
1114
|
+
},
|
|
1115
|
+
config: ${JSON.stringify(config || {})},
|
|
1116
|
+
env: ${JSON.stringify(env || {})}${chain ? `,
|
|
1117
|
+
${chainLine.slice(0, -1)}` : ""}
|
|
1118
|
+
}`;
|
|
1119
|
+
}
|
|
1120
|
+
return `{
|
|
1121
|
+
code: async (context) => ({
|
|
1122
|
+
${typeLine}
|
|
1123
|
+
config: context.config,
|
|
1124
|
+
${initFn ? `init: ${initFn},` : ""}
|
|
1125
|
+
push: ${pushFn}
|
|
1126
|
+
}),
|
|
1127
|
+
config: ${JSON.stringify(config || {})},
|
|
1128
|
+
env: ${JSON.stringify(env || {})}${chain ? `,
|
|
1129
|
+
${chainLine.slice(0, -1)}` : ""}
|
|
1130
|
+
}`;
|
|
1131
|
+
}
|
|
1132
|
+
async function copyIncludes(includes, sourceDir, outputDir, logger) {
|
|
1133
|
+
for (const include of includes) {
|
|
1134
|
+
const sourcePath = path8.resolve(sourceDir, include);
|
|
1135
|
+
const folderName = path8.basename(include);
|
|
1136
|
+
const destPath = path8.join(outputDir, folderName);
|
|
1137
|
+
if (await fs8.pathExists(sourcePath)) {
|
|
1138
|
+
await fs8.copy(sourcePath, destPath);
|
|
1139
|
+
logger.debug(`Copied ${include} to output`);
|
|
1140
|
+
} else {
|
|
1141
|
+
logger.debug(`Include folder not found: ${include}`);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
function generateCacheKeyContent(flowConfig, buildOptions) {
|
|
1146
|
+
const configForCache = {
|
|
1147
|
+
flow: flowConfig,
|
|
1148
|
+
build: {
|
|
1149
|
+
...buildOptions,
|
|
1150
|
+
// Exclude non-deterministic fields from cache key
|
|
1151
|
+
tempDir: void 0,
|
|
1152
|
+
output: void 0
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
return JSON.stringify(configForCache);
|
|
1156
|
+
}
|
|
1157
|
+
function validateFlowConfig(flowConfig, logger) {
|
|
1158
|
+
let hasDeprecatedCodeTrue = false;
|
|
1159
|
+
const sources = flowConfig.sources || {};
|
|
1160
|
+
for (const [sourceId, source] of Object.entries(sources)) {
|
|
1161
|
+
if (source && typeof source === "object" && source.code === true) {
|
|
1162
|
+
logger.warning(
|
|
1163
|
+
`DEPRECATED: Source "${sourceId}" uses code: true which is no longer supported. Use $code: prefix in config values or create a source package instead.`
|
|
1164
|
+
);
|
|
1165
|
+
hasDeprecatedCodeTrue = true;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const destinations = flowConfig.destinations || {};
|
|
1169
|
+
for (const [destId, dest] of Object.entries(destinations)) {
|
|
1170
|
+
if (dest && typeof dest === "object" && dest.code === true) {
|
|
1171
|
+
logger.warning(
|
|
1172
|
+
`DEPRECATED: Destination "${destId}" uses code: true which is no longer supported. Use $code: prefix in config values or create a destination package instead.`
|
|
1173
|
+
);
|
|
1174
|
+
hasDeprecatedCodeTrue = true;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
const transformers = flowConfig.transformers || {};
|
|
1178
|
+
for (const [transformerId, transformer] of Object.entries(transformers)) {
|
|
1179
|
+
if (transformer && typeof transformer === "object" && transformer.code === true) {
|
|
1180
|
+
logger.warning(
|
|
1181
|
+
`DEPRECATED: Transformer "${transformerId}" uses code: true which is no longer supported. Use $code: prefix in config values or create a transformer package instead.`
|
|
1182
|
+
);
|
|
1183
|
+
hasDeprecatedCodeTrue = true;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (hasDeprecatedCodeTrue) {
|
|
1187
|
+
logger.warning(
|
|
1188
|
+
`See https://www.elbwalker.com/docs/walkeros/getting-started/flow for migration guide.`
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
return hasDeprecatedCodeTrue;
|
|
1192
|
+
}
|
|
1193
|
+
async function bundleCore(flowConfig, buildOptions, logger, showStats = false) {
|
|
1194
|
+
const bundleStartTime = Date.now();
|
|
1195
|
+
const hasDeprecatedFeatures = validateFlowConfig(flowConfig, logger);
|
|
1196
|
+
if (hasDeprecatedFeatures) {
|
|
1197
|
+
logger.warning("Skipping deprecated code: true entries from bundle.");
|
|
1198
|
+
}
|
|
1199
|
+
const TEMP_DIR = buildOptions.tempDir || getTmpPath();
|
|
1200
|
+
if (buildOptions.cache !== false) {
|
|
1201
|
+
const configContent = generateCacheKeyContent(flowConfig, buildOptions);
|
|
1202
|
+
const cached = await isBuildCached(configContent, TEMP_DIR);
|
|
1203
|
+
if (cached) {
|
|
1204
|
+
const cachedBuild = await getCachedBuild(configContent, TEMP_DIR);
|
|
1205
|
+
if (cachedBuild) {
|
|
1206
|
+
logger.debug("Using cached build");
|
|
1207
|
+
const outputPath = path8.resolve(buildOptions.output);
|
|
1208
|
+
await fs8.ensureDir(path8.dirname(outputPath));
|
|
1209
|
+
await fs8.writeFile(outputPath, cachedBuild);
|
|
1210
|
+
const stats = await fs8.stat(outputPath);
|
|
1211
|
+
const sizeKB = (stats.size / 1024).toFixed(1);
|
|
1212
|
+
logger.log(`Output: ${outputPath} (${sizeKB} KB, cached)`);
|
|
1213
|
+
if (showStats) {
|
|
1214
|
+
const stats2 = await fs8.stat(outputPath);
|
|
1215
|
+
const packageStats = Object.entries(buildOptions.packages).map(
|
|
1216
|
+
([name, pkg]) => ({
|
|
1217
|
+
name: `${name}@${pkg.version || "latest"}`,
|
|
1218
|
+
size: 0
|
|
1219
|
+
// Size estimation not available for cached builds
|
|
1220
|
+
})
|
|
1221
|
+
);
|
|
1222
|
+
return {
|
|
1223
|
+
totalSize: stats2.size,
|
|
1224
|
+
packages: packageStats,
|
|
1225
|
+
buildTime: Date.now() - bundleStartTime,
|
|
1226
|
+
treeshakingEffective: true
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
try {
|
|
1234
|
+
await fs8.ensureDir(TEMP_DIR);
|
|
1235
|
+
const hasSourcesOrDests = Object.keys(
|
|
1236
|
+
flowConfig.sources || {}
|
|
1237
|
+
).length > 0 || Object.keys(
|
|
1238
|
+
flowConfig.destinations || {}
|
|
1239
|
+
).length > 0;
|
|
1240
|
+
if (hasSourcesOrDests && !buildOptions.packages["@walkeros/collector"]) {
|
|
1241
|
+
buildOptions.packages["@walkeros/collector"] = {};
|
|
1242
|
+
}
|
|
1243
|
+
logger.debug("Downloading packages");
|
|
1244
|
+
const packagesArray = Object.entries(buildOptions.packages).map(
|
|
1245
|
+
([name, packageConfig]) => ({
|
|
1246
|
+
name,
|
|
1247
|
+
version: packageConfig.version || "latest",
|
|
1248
|
+
path: packageConfig.path
|
|
1249
|
+
// Pass local path if defined
|
|
1250
|
+
})
|
|
1251
|
+
);
|
|
1252
|
+
const packagePaths = await downloadPackages(
|
|
1253
|
+
packagesArray,
|
|
1254
|
+
TEMP_DIR,
|
|
1255
|
+
logger,
|
|
1256
|
+
buildOptions.cache,
|
|
1257
|
+
buildOptions.configDir,
|
|
1258
|
+
// For resolving relative local paths
|
|
1259
|
+
TEMP_DIR
|
|
1260
|
+
);
|
|
1261
|
+
for (const [pkgName, pkgPath] of packagePaths.entries()) {
|
|
1262
|
+
if (pkgName.startsWith("@walkeros/")) {
|
|
1263
|
+
const pkgJsonPath = path8.join(pkgPath, "package.json");
|
|
1264
|
+
const pkgJson = await fs8.readJSON(pkgJsonPath);
|
|
1265
|
+
if (!pkgJson.exports && pkgJson.module) {
|
|
1266
|
+
pkgJson.exports = {
|
|
1267
|
+
".": {
|
|
1268
|
+
import: pkgJson.module,
|
|
1269
|
+
require: pkgJson.main
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
await fs8.writeJSON(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
const packageJsonPath = path8.join(TEMP_DIR, "package.json");
|
|
1277
|
+
await fs8.writeFile(
|
|
1278
|
+
packageJsonPath,
|
|
1279
|
+
JSON.stringify({ type: "module" }, null, 2)
|
|
1280
|
+
);
|
|
1281
|
+
logger.debug("Creating entry point");
|
|
1282
|
+
const entryContent = await createEntryPoint(
|
|
1283
|
+
flowConfig,
|
|
1284
|
+
buildOptions,
|
|
1285
|
+
packagePaths
|
|
1286
|
+
);
|
|
1287
|
+
const entryPath = path8.join(TEMP_DIR, "entry.js");
|
|
1288
|
+
await fs8.writeFile(entryPath, entryContent);
|
|
1289
|
+
logger.debug(
|
|
1290
|
+
`Running esbuild (target: ${buildOptions.target || "es2018"}, format: ${buildOptions.format})`
|
|
1291
|
+
);
|
|
1292
|
+
const outputPath = path8.resolve(buildOptions.output);
|
|
1293
|
+
await fs8.ensureDir(path8.dirname(outputPath));
|
|
1294
|
+
const esbuildOptions = createEsbuildOptions(
|
|
1295
|
+
buildOptions,
|
|
1296
|
+
entryPath,
|
|
1297
|
+
outputPath,
|
|
1298
|
+
TEMP_DIR,
|
|
1299
|
+
packagePaths,
|
|
1300
|
+
logger
|
|
1301
|
+
);
|
|
1302
|
+
try {
|
|
1303
|
+
await esbuild.build(esbuildOptions);
|
|
1304
|
+
} catch (buildError) {
|
|
1305
|
+
throw createBuildError(
|
|
1306
|
+
buildError,
|
|
1307
|
+
buildOptions.code || ""
|
|
1308
|
+
);
|
|
1309
|
+
} finally {
|
|
1310
|
+
await esbuild.stop();
|
|
1311
|
+
}
|
|
1312
|
+
const outputStats = await fs8.stat(outputPath);
|
|
1313
|
+
const sizeKB = (outputStats.size / 1024).toFixed(1);
|
|
1314
|
+
const buildTime = ((Date.now() - bundleStartTime) / 1e3).toFixed(1);
|
|
1315
|
+
logger.log(`Output: ${outputPath} (${sizeKB} KB, ${buildTime}s)`);
|
|
1316
|
+
if (buildOptions.cache !== false) {
|
|
1317
|
+
const configContent = generateCacheKeyContent(flowConfig, buildOptions);
|
|
1318
|
+
const buildOutput = await fs8.readFile(outputPath, "utf-8");
|
|
1319
|
+
await cacheBuild(configContent, buildOutput, TEMP_DIR);
|
|
1320
|
+
logger.debug("Build cached for future use");
|
|
1321
|
+
}
|
|
1322
|
+
let stats;
|
|
1323
|
+
if (showStats) {
|
|
1324
|
+
stats = await collectBundleStats(
|
|
1325
|
+
outputPath,
|
|
1326
|
+
buildOptions.packages,
|
|
1327
|
+
bundleStartTime,
|
|
1328
|
+
entryContent
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
if (buildOptions.include && buildOptions.include.length > 0) {
|
|
1332
|
+
const outputDir = path8.dirname(outputPath);
|
|
1333
|
+
await copyIncludes(
|
|
1334
|
+
buildOptions.include,
|
|
1335
|
+
buildOptions.configDir || process.cwd(),
|
|
1336
|
+
outputDir,
|
|
1337
|
+
logger
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
return stats;
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
throw error;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
async function collectBundleStats(outputPath, packages, startTime2, entryContent) {
|
|
1346
|
+
const stats = await fs8.stat(outputPath);
|
|
1347
|
+
const totalSize = stats.size;
|
|
1348
|
+
const buildTime = Date.now() - startTime2;
|
|
1349
|
+
const packageStats = Object.entries(packages).map(([name, pkg]) => {
|
|
1350
|
+
const importPattern = new RegExp(`from\\s+['"]${name}['"]`, "g");
|
|
1351
|
+
const namedImportPattern = new RegExp(
|
|
1352
|
+
`import\\s+\\{[^}]*\\}\\s+from\\s+['"]${name}['"]`,
|
|
1353
|
+
"g"
|
|
1354
|
+
);
|
|
1355
|
+
const hasImports = importPattern.test(entryContent) || namedImportPattern.test(entryContent);
|
|
1356
|
+
const packagesCount = Object.keys(packages).length;
|
|
1357
|
+
const estimatedSize = hasImports ? Math.floor(totalSize / packagesCount) : 0;
|
|
1358
|
+
return {
|
|
1359
|
+
name: `${name}@${pkg.version || "latest"}`,
|
|
1360
|
+
size: estimatedSize
|
|
1361
|
+
};
|
|
1362
|
+
});
|
|
1363
|
+
const hasWildcardImports = /import\s+\*\s+as\s+\w+\s+from/.test(entryContent);
|
|
1364
|
+
const treeshakingEffective = !hasWildcardImports;
|
|
1365
|
+
return {
|
|
1366
|
+
totalSize,
|
|
1367
|
+
packages: packageStats,
|
|
1368
|
+
buildTime,
|
|
1369
|
+
treeshakingEffective
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function createEsbuildOptions(buildOptions, entryPath, outputPath, tempDir, packagePaths, logger) {
|
|
1373
|
+
const alias = {};
|
|
1374
|
+
const baseOptions = {
|
|
1375
|
+
entryPoints: [entryPath],
|
|
1376
|
+
bundle: true,
|
|
1377
|
+
format: buildOptions.format,
|
|
1378
|
+
platform: buildOptions.platform,
|
|
1379
|
+
outfile: outputPath,
|
|
1380
|
+
absWorkingDir: tempDir,
|
|
1381
|
+
// Resolve modules from temp directory
|
|
1382
|
+
// alias removed - not needed with absWorkingDir
|
|
1383
|
+
mainFields: ["module", "main"],
|
|
1384
|
+
// Prefer ESM over CJS
|
|
1385
|
+
treeShaking: true,
|
|
1386
|
+
logLevel: "error",
|
|
1387
|
+
minify: buildOptions.minify,
|
|
1388
|
+
sourcemap: buildOptions.sourcemap,
|
|
1389
|
+
resolveExtensions: [".mjs", ".js", ".ts", ".json"],
|
|
1390
|
+
// Prefer .mjs
|
|
1391
|
+
// Enhanced minification options when minify is enabled
|
|
1392
|
+
...buildOptions.minify && {
|
|
1393
|
+
minifyWhitespace: buildOptions.minifyOptions?.whitespace ?? true,
|
|
1394
|
+
minifyIdentifiers: buildOptions.minifyOptions?.identifiers ?? true,
|
|
1395
|
+
minifySyntax: buildOptions.minifyOptions?.syntax ?? true,
|
|
1396
|
+
legalComments: buildOptions.minifyOptions?.legalComments ?? "none",
|
|
1397
|
+
keepNames: buildOptions.minifyOptions?.keepNames ?? false,
|
|
1398
|
+
charset: "utf8"
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
if (buildOptions.platform === "browser") {
|
|
1402
|
+
baseOptions.define = {
|
|
1403
|
+
"process.env.NODE_ENV": '"production"',
|
|
1404
|
+
global: "globalThis"
|
|
1405
|
+
};
|
|
1406
|
+
baseOptions.external = buildOptions.external || [];
|
|
1407
|
+
} else if (buildOptions.platform === "node") {
|
|
1408
|
+
const nodeBuiltins = [
|
|
1409
|
+
"crypto",
|
|
1410
|
+
"fs",
|
|
1411
|
+
"path",
|
|
1412
|
+
"os",
|
|
1413
|
+
"util",
|
|
1414
|
+
"stream",
|
|
1415
|
+
"buffer",
|
|
1416
|
+
"events",
|
|
1417
|
+
"http",
|
|
1418
|
+
"https",
|
|
1419
|
+
"url",
|
|
1420
|
+
"querystring",
|
|
1421
|
+
"zlib"
|
|
1422
|
+
];
|
|
1423
|
+
const npmPackages = ["express", "express/*", "cors", "cors/*"];
|
|
1424
|
+
baseOptions.external = buildOptions.external ? [...nodeBuiltins, ...npmPackages, ...buildOptions.external] : [...nodeBuiltins, ...npmPackages];
|
|
1425
|
+
if (buildOptions.format === "esm") {
|
|
1426
|
+
baseOptions.banner = {
|
|
1427
|
+
js: `import { createRequire } from 'module';const require = createRequire(import.meta.url);`
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
if (buildOptions.target) {
|
|
1432
|
+
baseOptions.target = buildOptions.target;
|
|
1433
|
+
} else if (buildOptions.platform === "node") {
|
|
1434
|
+
baseOptions.target = "node18";
|
|
1435
|
+
} else {
|
|
1436
|
+
baseOptions.target = "es2018";
|
|
1437
|
+
}
|
|
1438
|
+
return baseOptions;
|
|
1439
|
+
}
|
|
1440
|
+
function detectDestinationPackages(flowConfig) {
|
|
1441
|
+
const destinationPackages = /* @__PURE__ */ new Set();
|
|
1442
|
+
const destinations = flowConfig.destinations;
|
|
1443
|
+
if (destinations) {
|
|
1444
|
+
for (const [destKey, destConfig] of Object.entries(destinations)) {
|
|
1445
|
+
if (typeof destConfig === "object" && destConfig !== null && "code" in destConfig && destConfig.code === true) {
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
if (typeof destConfig === "object" && destConfig !== null && "package" in destConfig && typeof destConfig.package === "string") {
|
|
1449
|
+
destinationPackages.add(destConfig.package);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return destinationPackages;
|
|
1454
|
+
}
|
|
1455
|
+
function detectSourcePackages(flowConfig) {
|
|
1456
|
+
const sourcePackages = /* @__PURE__ */ new Set();
|
|
1457
|
+
const sources = flowConfig.sources;
|
|
1458
|
+
if (sources) {
|
|
1459
|
+
for (const [sourceKey, sourceConfig] of Object.entries(sources)) {
|
|
1460
|
+
if (typeof sourceConfig === "object" && sourceConfig !== null && "code" in sourceConfig && sourceConfig.code === true) {
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
if (typeof sourceConfig === "object" && sourceConfig !== null && "package" in sourceConfig && typeof sourceConfig.package === "string") {
|
|
1464
|
+
sourcePackages.add(sourceConfig.package);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return sourcePackages;
|
|
1469
|
+
}
|
|
1470
|
+
function detectTransformerPackages(flowConfig) {
|
|
1471
|
+
const transformerPackages = /* @__PURE__ */ new Set();
|
|
1472
|
+
const transformers = flowConfig.transformers;
|
|
1473
|
+
if (transformers) {
|
|
1474
|
+
for (const [transformerKey, transformerConfig] of Object.entries(
|
|
1475
|
+
transformers
|
|
1476
|
+
)) {
|
|
1477
|
+
if (typeof transformerConfig === "object" && transformerConfig !== null && "code" in transformerConfig && transformerConfig.code === true) {
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
if (typeof transformerConfig === "object" && transformerConfig !== null && "package" in transformerConfig && typeof transformerConfig.package === "string") {
|
|
1481
|
+
transformerPackages.add(transformerConfig.package);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
return transformerPackages;
|
|
1486
|
+
}
|
|
1487
|
+
function detectExplicitCodeImports(flowConfig) {
|
|
1488
|
+
const explicitCodeImports = /* @__PURE__ */ new Map();
|
|
1489
|
+
const destinations = flowConfig.destinations;
|
|
1490
|
+
if (destinations) {
|
|
1491
|
+
for (const [destKey, destConfig] of Object.entries(destinations)) {
|
|
1492
|
+
if (typeof destConfig === "object" && destConfig !== null && "code" in destConfig && destConfig.code === true) {
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1495
|
+
if (typeof destConfig === "object" && destConfig !== null && "package" in destConfig && typeof destConfig.package === "string" && "code" in destConfig && typeof destConfig.code === "string") {
|
|
1496
|
+
const isAutoGenerated = destConfig.code.startsWith("_");
|
|
1497
|
+
if (!isAutoGenerated) {
|
|
1498
|
+
if (!explicitCodeImports.has(destConfig.package)) {
|
|
1499
|
+
explicitCodeImports.set(destConfig.package, /* @__PURE__ */ new Set());
|
|
1500
|
+
}
|
|
1501
|
+
explicitCodeImports.get(destConfig.package).add(destConfig.code);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
const sources = flowConfig.sources;
|
|
1507
|
+
if (sources) {
|
|
1508
|
+
for (const [sourceKey, sourceConfig] of Object.entries(sources)) {
|
|
1509
|
+
if (typeof sourceConfig === "object" && sourceConfig !== null && "code" in sourceConfig && sourceConfig.code === true) {
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
if (typeof sourceConfig === "object" && sourceConfig !== null && "package" in sourceConfig && typeof sourceConfig.package === "string" && "code" in sourceConfig && typeof sourceConfig.code === "string") {
|
|
1513
|
+
const isAutoGenerated = sourceConfig.code.startsWith("_");
|
|
1514
|
+
if (!isAutoGenerated) {
|
|
1515
|
+
if (!explicitCodeImports.has(sourceConfig.package)) {
|
|
1516
|
+
explicitCodeImports.set(sourceConfig.package, /* @__PURE__ */ new Set());
|
|
1517
|
+
}
|
|
1518
|
+
explicitCodeImports.get(sourceConfig.package).add(sourceConfig.code);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
const transformers = flowConfig.transformers;
|
|
1524
|
+
if (transformers) {
|
|
1525
|
+
for (const [transformerKey, transformerConfig] of Object.entries(
|
|
1526
|
+
transformers
|
|
1527
|
+
)) {
|
|
1528
|
+
if (typeof transformerConfig === "object" && transformerConfig !== null && "code" in transformerConfig && transformerConfig.code === true) {
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1531
|
+
if (typeof transformerConfig === "object" && transformerConfig !== null && "package" in transformerConfig && typeof transformerConfig.package === "string" && "code" in transformerConfig && typeof transformerConfig.code === "string") {
|
|
1532
|
+
const isAutoGenerated = transformerConfig.code.startsWith("_");
|
|
1533
|
+
if (!isAutoGenerated) {
|
|
1534
|
+
if (!explicitCodeImports.has(transformerConfig.package)) {
|
|
1535
|
+
explicitCodeImports.set(transformerConfig.package, /* @__PURE__ */ new Set());
|
|
1536
|
+
}
|
|
1537
|
+
explicitCodeImports.get(transformerConfig.package).add(transformerConfig.code);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return explicitCodeImports;
|
|
1543
|
+
}
|
|
1544
|
+
function generateImportStatements(packages, destinationPackages, sourcePackages, transformerPackages, explicitCodeImports) {
|
|
1545
|
+
const importStatements = [];
|
|
1546
|
+
const examplesMappings = [];
|
|
1547
|
+
const usedPackages = /* @__PURE__ */ new Set([
|
|
1548
|
+
...destinationPackages,
|
|
1549
|
+
...sourcePackages,
|
|
1550
|
+
...transformerPackages
|
|
1551
|
+
]);
|
|
1552
|
+
for (const [packageName, packageConfig] of Object.entries(packages)) {
|
|
1553
|
+
const isUsedByDestOrSource = usedPackages.has(packageName);
|
|
1554
|
+
const hasExplicitCode = explicitCodeImports.has(packageName);
|
|
1555
|
+
const namedImportsToGenerate = [];
|
|
1556
|
+
if (isUsedByDestOrSource && !hasExplicitCode) {
|
|
1557
|
+
const varName = packageNameToVariable(packageName);
|
|
1558
|
+
importStatements.push(`import ${varName} from '${packageName}';`);
|
|
1559
|
+
}
|
|
1560
|
+
if (hasExplicitCode) {
|
|
1561
|
+
const codes = Array.from(explicitCodeImports.get(packageName));
|
|
1562
|
+
namedImportsToGenerate.push(...codes);
|
|
1563
|
+
}
|
|
1564
|
+
if (packageConfig.imports && packageConfig.imports.length > 0) {
|
|
1565
|
+
const uniqueImports = [...new Set(packageConfig.imports)];
|
|
1566
|
+
for (const imp of uniqueImports) {
|
|
1567
|
+
if (imp.startsWith("default as ")) {
|
|
1568
|
+
if (!isUsedByDestOrSource || hasExplicitCode) {
|
|
1569
|
+
const defaultImportName = imp.replace("default as ", "");
|
|
1570
|
+
importStatements.push(
|
|
1571
|
+
`import ${defaultImportName} from '${packageName}';`
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
} else {
|
|
1575
|
+
if (!namedImportsToGenerate.includes(imp)) {
|
|
1576
|
+
namedImportsToGenerate.push(imp);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
const examplesImport = uniqueImports.find(
|
|
1581
|
+
(imp) => imp.includes("examples as ")
|
|
1582
|
+
);
|
|
1583
|
+
if (examplesImport) {
|
|
1584
|
+
const examplesVarName = examplesImport.split(" as ")[1];
|
|
1585
|
+
const destinationMatch = packageName.match(
|
|
1586
|
+
/@walkeros\/web-destination-(.+)$/
|
|
1587
|
+
);
|
|
1588
|
+
if (destinationMatch) {
|
|
1589
|
+
const destinationName = destinationMatch[1];
|
|
1590
|
+
examplesMappings.push(
|
|
1591
|
+
` ${destinationName}: typeof ${examplesVarName} !== 'undefined' ? ${examplesVarName} : undefined`
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
if (packageName === "@walkeros/collector" && !namedImportsToGenerate.includes("startFlow")) {
|
|
1597
|
+
namedImportsToGenerate.push("startFlow");
|
|
1598
|
+
}
|
|
1599
|
+
if (namedImportsToGenerate.length > 0) {
|
|
1600
|
+
const importList = namedImportsToGenerate.join(", ");
|
|
1601
|
+
importStatements.push(`import { ${importList} } from '${packageName}';`);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
return { importStatements, examplesMappings };
|
|
1605
|
+
}
|
|
1606
|
+
async function createEntryPoint(flowConfig, buildOptions, packagePaths) {
|
|
1607
|
+
const destinationPackages = detectDestinationPackages(flowConfig);
|
|
1608
|
+
const sourcePackages = detectSourcePackages(flowConfig);
|
|
1609
|
+
const transformerPackages = detectTransformerPackages(flowConfig);
|
|
1610
|
+
const explicitCodeImports = detectExplicitCodeImports(flowConfig);
|
|
1611
|
+
const { importStatements } = generateImportStatements(
|
|
1612
|
+
buildOptions.packages,
|
|
1613
|
+
destinationPackages,
|
|
1614
|
+
sourcePackages,
|
|
1615
|
+
transformerPackages,
|
|
1616
|
+
explicitCodeImports
|
|
1617
|
+
);
|
|
1618
|
+
const importsCode = importStatements.join("\n");
|
|
1619
|
+
const hasFlow = Object.values(flowConfig.sources || {}).some(
|
|
1620
|
+
(s) => s.package || isInlineCode(s.code)
|
|
1621
|
+
) || Object.values(flowConfig.destinations || {}).some(
|
|
1622
|
+
(d) => d.package || isInlineCode(d.code)
|
|
1623
|
+
);
|
|
1624
|
+
if (!hasFlow) {
|
|
1625
|
+
const userCode = buildOptions.code || "";
|
|
1626
|
+
return importsCode ? `${importsCode}
|
|
1627
|
+
|
|
1628
|
+
${userCode}` : userCode;
|
|
1629
|
+
}
|
|
1630
|
+
const configObject = buildConfigObject(flowConfig, explicitCodeImports);
|
|
1631
|
+
const wrappedCode = generatePlatformWrapper(
|
|
1632
|
+
configObject,
|
|
1633
|
+
buildOptions.code || "",
|
|
1634
|
+
buildOptions
|
|
1635
|
+
);
|
|
1636
|
+
return importsCode ? `${importsCode}
|
|
1637
|
+
|
|
1638
|
+
${wrappedCode}` : wrappedCode;
|
|
1639
|
+
}
|
|
1640
|
+
function createBuildError(buildError, code) {
|
|
1641
|
+
if (!buildError.errors || buildError.errors.length === 0) {
|
|
1642
|
+
return new Error(`Build failed: ${buildError.message || buildError}`);
|
|
1643
|
+
}
|
|
1644
|
+
const firstError = buildError.errors[0];
|
|
1645
|
+
const location = firstError.location;
|
|
1646
|
+
if (location && location.file && location.file.includes("entry.js")) {
|
|
1647
|
+
const line = location.line;
|
|
1648
|
+
const column = location.column;
|
|
1649
|
+
const codeLines = code.split("\n");
|
|
1650
|
+
const errorLine = codeLines[line - 1] || "";
|
|
1651
|
+
return new Error(
|
|
1652
|
+
`Code syntax error at line ${line}, column ${column}:
|
|
1653
|
+
${errorLine}
|
|
1654
|
+
${" ".repeat(column - 1)}^
|
|
1655
|
+
${firstError.text}`
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
return new Error(
|
|
1659
|
+
`Build error: ${firstError.text}
|
|
1660
|
+
` + (location ? ` at ${location.file}:${location.line}:${location.column}` : "")
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
function buildConfigObject(flowConfig, explicitCodeImports) {
|
|
1664
|
+
const flowWithProps = flowConfig;
|
|
1665
|
+
const sources = flowWithProps.sources || {};
|
|
1666
|
+
const destinations = flowWithProps.destinations || {};
|
|
1667
|
+
const transformers = flowWithProps.transformers || {};
|
|
1668
|
+
Object.entries(sources).forEach(([name, source]) => {
|
|
1669
|
+
if (source.code !== true) {
|
|
1670
|
+
validateReference("Source", name, source);
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
Object.entries(destinations).forEach(([name, dest]) => {
|
|
1674
|
+
if (dest.code !== true) {
|
|
1675
|
+
validateReference("Destination", name, dest);
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
Object.entries(transformers).forEach(([name, transformer]) => {
|
|
1679
|
+
if (transformer.code !== true) {
|
|
1680
|
+
validateReference("Transformer", name, transformer);
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
const sourcesEntries = Object.entries(sources).filter(
|
|
1684
|
+
([, source]) => source.code !== true && (source.package || isInlineCode(source.code))
|
|
1685
|
+
).map(([key, source]) => {
|
|
1686
|
+
if (isInlineCode(source.code)) {
|
|
1687
|
+
return ` ${key}: ${generateInlineCode(source.code, source.config || {}, source.env, source.next, "next")}`;
|
|
1688
|
+
}
|
|
1689
|
+
let codeVar;
|
|
1690
|
+
if (source.code && typeof source.code === "string" && explicitCodeImports.has(source.package)) {
|
|
1691
|
+
codeVar = source.code;
|
|
1692
|
+
} else {
|
|
1693
|
+
codeVar = packageNameToVariable(source.package);
|
|
1694
|
+
}
|
|
1695
|
+
const configStr = source.config ? processConfigValue(source.config) : "{}";
|
|
1696
|
+
const envStr = source.env ? `,
|
|
1697
|
+
env: ${processConfigValue(source.env)}` : "";
|
|
1698
|
+
const nextStr = source.next ? `,
|
|
1699
|
+
next: ${JSON.stringify(source.next)}` : "";
|
|
1700
|
+
return ` ${key}: {
|
|
1701
|
+
code: ${codeVar},
|
|
1702
|
+
config: ${configStr}${envStr}${nextStr}
|
|
1703
|
+
}`;
|
|
1704
|
+
});
|
|
1705
|
+
const destinationsEntries = Object.entries(destinations).filter(
|
|
1706
|
+
([, dest]) => dest.code !== true && (dest.package || isInlineCode(dest.code))
|
|
1707
|
+
).map(([key, dest]) => {
|
|
1708
|
+
if (isInlineCode(dest.code)) {
|
|
1709
|
+
return ` ${key}: ${generateInlineCode(dest.code, dest.config || {}, dest.env, dest.before, "before", true)}`;
|
|
1710
|
+
}
|
|
1711
|
+
let codeVar;
|
|
1712
|
+
if (dest.code && typeof dest.code === "string" && explicitCodeImports.has(dest.package)) {
|
|
1713
|
+
codeVar = dest.code;
|
|
1714
|
+
} else {
|
|
1715
|
+
codeVar = packageNameToVariable(dest.package);
|
|
1716
|
+
}
|
|
1717
|
+
const configStr = dest.config ? processConfigValue(dest.config) : "{}";
|
|
1718
|
+
const envStr = dest.env ? `,
|
|
1719
|
+
env: ${processConfigValue(dest.env)}` : "";
|
|
1720
|
+
const beforeStr = dest.before ? `,
|
|
1721
|
+
before: ${JSON.stringify(dest.before)}` : "";
|
|
1722
|
+
return ` ${key}: {
|
|
1723
|
+
code: ${codeVar},
|
|
1724
|
+
config: ${configStr}${envStr}${beforeStr}
|
|
1725
|
+
}`;
|
|
1726
|
+
});
|
|
1727
|
+
const transformersEntries = Object.entries(transformers).filter(
|
|
1728
|
+
([, transformer]) => transformer.code !== true && (transformer.package || isInlineCode(transformer.code))
|
|
1729
|
+
).map(([key, transformer]) => {
|
|
1730
|
+
if (isInlineCode(transformer.code)) {
|
|
1731
|
+
return ` ${key}: ${generateInlineCode(transformer.code, transformer.config || {}, transformer.env, transformer.next, "next")}`;
|
|
1732
|
+
}
|
|
1733
|
+
let codeVar;
|
|
1734
|
+
if (transformer.code && typeof transformer.code === "string" && explicitCodeImports.has(transformer.package)) {
|
|
1735
|
+
codeVar = transformer.code;
|
|
1736
|
+
} else {
|
|
1737
|
+
codeVar = packageNameToVariable(transformer.package);
|
|
1738
|
+
}
|
|
1739
|
+
const configStr = transformer.config ? processConfigValue(transformer.config) : "{}";
|
|
1740
|
+
const envStr = transformer.env ? `,
|
|
1741
|
+
env: ${processConfigValue(transformer.env)}` : "";
|
|
1742
|
+
const nextStr = transformer.next ? `,
|
|
1743
|
+
next: ${JSON.stringify(transformer.next)}` : "";
|
|
1744
|
+
return ` ${key}: {
|
|
1745
|
+
code: ${codeVar},
|
|
1746
|
+
config: ${configStr}${envStr}${nextStr}
|
|
1747
|
+
}`;
|
|
1748
|
+
});
|
|
1749
|
+
const collectorStr = flowWithProps.collector ? `,
|
|
1750
|
+
...${processConfigValue(flowWithProps.collector)}` : "";
|
|
1751
|
+
const transformersStr = transformersEntries.length > 0 ? `,
|
|
1752
|
+
transformers: {
|
|
1753
|
+
${transformersEntries.join(",\n")}
|
|
1754
|
+
}` : "";
|
|
1755
|
+
return `{
|
|
1756
|
+
sources: {
|
|
1757
|
+
${sourcesEntries.join(",\n")}
|
|
1758
|
+
},
|
|
1759
|
+
destinations: {
|
|
1760
|
+
${destinationsEntries.join(",\n")}
|
|
1761
|
+
}${transformersStr}${collectorStr}
|
|
1762
|
+
}`;
|
|
1763
|
+
}
|
|
1764
|
+
function processConfigValue(value) {
|
|
1765
|
+
return serializeWithCode(value, 0);
|
|
1766
|
+
}
|
|
1767
|
+
function serializeWithCode(value, indent) {
|
|
1768
|
+
const spaces = " ".repeat(indent);
|
|
1769
|
+
const nextSpaces = " ".repeat(indent + 1);
|
|
1770
|
+
if (typeof value === "string") {
|
|
1771
|
+
if (value.startsWith("$code:")) {
|
|
1772
|
+
return value.slice(6);
|
|
1773
|
+
}
|
|
1774
|
+
return JSON.stringify(value);
|
|
1775
|
+
}
|
|
1776
|
+
if (Array.isArray(value)) {
|
|
1777
|
+
if (value.length === 0) return "[]";
|
|
1778
|
+
const items = value.map(
|
|
1779
|
+
(v) => nextSpaces + serializeWithCode(v, indent + 1)
|
|
1780
|
+
);
|
|
1781
|
+
return `[
|
|
1782
|
+
${items.join(",\n")}
|
|
1783
|
+
${spaces}]`;
|
|
1784
|
+
}
|
|
1785
|
+
if (value !== null && typeof value === "object") {
|
|
1786
|
+
const entries = Object.entries(value);
|
|
1787
|
+
if (entries.length === 0) return "{}";
|
|
1788
|
+
const props = entries.map(
|
|
1789
|
+
([k, v]) => `${nextSpaces}${JSON.stringify(k)}: ${serializeWithCode(v, indent + 1)}`
|
|
1790
|
+
);
|
|
1791
|
+
return `{
|
|
1792
|
+
${props.join(",\n")}
|
|
1793
|
+
${spaces}}`;
|
|
1794
|
+
}
|
|
1795
|
+
return JSON.stringify(value);
|
|
1796
|
+
}
|
|
1797
|
+
function generatePlatformWrapper(configObject, userCode, buildOptions) {
|
|
1798
|
+
if (buildOptions.platform === "browser") {
|
|
1799
|
+
const windowAssignments = [];
|
|
1800
|
+
if (buildOptions.windowCollector) {
|
|
1801
|
+
windowAssignments.push(
|
|
1802
|
+
` if (typeof window !== 'undefined') window['${buildOptions.windowCollector}'] = collector;`
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
if (buildOptions.windowElb) {
|
|
1806
|
+
windowAssignments.push(
|
|
1807
|
+
` if (typeof window !== 'undefined') window['${buildOptions.windowElb}'] = elb;`
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
const assignments = windowAssignments.length > 0 ? "\n" + windowAssignments.join("\n") : "";
|
|
1811
|
+
return `(async () => {
|
|
1812
|
+
const config = ${configObject};
|
|
1813
|
+
|
|
1814
|
+
${userCode}
|
|
1815
|
+
|
|
1816
|
+
const { collector, elb } = await startFlow(config);${assignments}
|
|
1817
|
+
})();`;
|
|
1818
|
+
} else {
|
|
1819
|
+
const codeSection = userCode ? `
|
|
1820
|
+
${userCode}
|
|
1821
|
+
` : "";
|
|
1822
|
+
return `export default async function(context = {}) {
|
|
1823
|
+
const config = ${configObject};${codeSection}
|
|
1824
|
+
// Apply context overrides (e.g., logger config from CLI)
|
|
1825
|
+
if (context.logger) {
|
|
1826
|
+
config.logger = { ...config.logger, ...context.logger };
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
return await startFlow(config);
|
|
1830
|
+
}`;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/core/api-client.ts
|
|
1835
|
+
import createClient from "openapi-fetch";
|
|
1836
|
+
|
|
1837
|
+
// src/commands/bundle/index.ts
|
|
1838
|
+
async function bundle(configOrPath, options = {}) {
|
|
1839
|
+
let rawConfig;
|
|
1840
|
+
let configPath = path9.resolve(process.cwd(), "walkeros.config.json");
|
|
1841
|
+
if (typeof configOrPath === "string") {
|
|
1842
|
+
configPath = resolveAsset(configOrPath, "config");
|
|
1843
|
+
rawConfig = await loadJsonConfig(configPath);
|
|
1844
|
+
} else {
|
|
1845
|
+
rawConfig = configOrPath;
|
|
1846
|
+
}
|
|
1847
|
+
const { flowConfig, buildOptions } = loadBundleConfig(rawConfig, {
|
|
1848
|
+
configPath,
|
|
1849
|
+
flowName: options.flowName,
|
|
1850
|
+
buildOverrides: options.buildOverrides
|
|
1851
|
+
});
|
|
1852
|
+
if (options.cache !== void 0) {
|
|
1853
|
+
buildOptions.cache = options.cache;
|
|
1854
|
+
}
|
|
1855
|
+
const logger = createCommandLogger(options);
|
|
1856
|
+
return await bundleCore(
|
|
1857
|
+
flowConfig,
|
|
1858
|
+
buildOptions,
|
|
1859
|
+
logger,
|
|
1860
|
+
options.stats ?? false
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// src/commands/run/utils.ts
|
|
1865
|
+
async function prepareBundleForRun(configPath, options) {
|
|
1866
|
+
const tempDir = getTmpPath(
|
|
1867
|
+
void 0,
|
|
1868
|
+
`run-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
|
1869
|
+
);
|
|
1870
|
+
await fs10.ensureDir(tempDir);
|
|
1871
|
+
const tempPath = path10.join(tempDir, "bundle.mjs");
|
|
1872
|
+
await bundle(configPath, {
|
|
1873
|
+
cache: true,
|
|
1874
|
+
verbose: options.verbose,
|
|
1875
|
+
silent: options.silent,
|
|
1876
|
+
buildOverrides: {
|
|
1877
|
+
output: tempPath,
|
|
1878
|
+
format: "esm",
|
|
1879
|
+
platform: "node"
|
|
1880
|
+
}
|
|
1881
|
+
});
|
|
1882
|
+
return tempPath;
|
|
1883
|
+
}
|
|
1884
|
+
function isPreBuiltConfig(configPath) {
|
|
1885
|
+
return configPath.endsWith(".mjs") || configPath.endsWith(".js") || configPath.endsWith(".cjs");
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
// src/runtime/main.ts
|
|
1889
|
+
import { writeFileSync as writeFileSync4, readFileSync as readFileSync4 } from "fs";
|
|
1890
|
+
function adaptLogger(cliLogger) {
|
|
1891
|
+
return {
|
|
1892
|
+
error: (message) => {
|
|
1893
|
+
cliLogger.error(message instanceof Error ? message.message : message);
|
|
1894
|
+
},
|
|
1895
|
+
info: (message) => {
|
|
1896
|
+
cliLogger.info(message instanceof Error ? message.message : message);
|
|
1897
|
+
},
|
|
1898
|
+
debug: (message) => {
|
|
1899
|
+
cliLogger.debug(message instanceof Error ? message.message : message);
|
|
1900
|
+
},
|
|
1901
|
+
throw: (message) => {
|
|
1902
|
+
const msg = message instanceof Error ? message.message : message;
|
|
1903
|
+
cliLogger.error(msg);
|
|
1904
|
+
throw message instanceof Error ? message : new Error(msg);
|
|
1905
|
+
},
|
|
1906
|
+
scope: (_name) => adaptLogger(cliLogger)
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
function resolveAppUrl2() {
|
|
1910
|
+
return process.env.APP_URL || process.env.WALKEROS_APP_URL || "https://app.walkeros.io";
|
|
1911
|
+
}
|
|
1912
|
+
async function main() {
|
|
1913
|
+
const cliLogger = createLogger({ silent: false, verbose: true });
|
|
1914
|
+
const logger = adaptLogger(cliLogger);
|
|
1915
|
+
let env;
|
|
1916
|
+
try {
|
|
1917
|
+
env = validateEnv(process.env);
|
|
1918
|
+
} catch (error) {
|
|
1919
|
+
cliLogger.error(
|
|
1920
|
+
`Configuration error: ${error instanceof Error ? error.message : error}`
|
|
1921
|
+
);
|
|
1922
|
+
process.exit(1);
|
|
1923
|
+
}
|
|
1924
|
+
cliLogger.log(`walkeros/flow v${VERSION} \u2014 ${env.mode} mode`);
|
|
1925
|
+
cliLogger.log(`Instance: ${getInstanceId()}`);
|
|
1926
|
+
initStatus({
|
|
1927
|
+
mode: env.mode,
|
|
1928
|
+
port: env.port,
|
|
1929
|
+
configSource: env.remoteConfig ? "api" : "local",
|
|
1930
|
+
apiEnabled: env.apiEnabled
|
|
1931
|
+
});
|
|
1932
|
+
if (env.mode === "serve") {
|
|
1933
|
+
setRunning();
|
|
1934
|
+
await runServeMode({ port: env.port, file: env.bundlePath }, logger);
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
let configPath = null;
|
|
1938
|
+
let configVersion;
|
|
1939
|
+
if (env.remoteConfig) {
|
|
1940
|
+
cliLogger.log("Fetching config from API...");
|
|
1941
|
+
try {
|
|
1942
|
+
const result = await fetchConfig({
|
|
1943
|
+
appUrl: resolveAppUrl2(),
|
|
1944
|
+
token: env.token,
|
|
1945
|
+
projectId: env.projectId,
|
|
1946
|
+
flowId: env.flowId
|
|
1947
|
+
});
|
|
1948
|
+
if (result.changed) {
|
|
1949
|
+
const tmpConfigPath = `/tmp/walkeros-flow-${Date.now()}.json`;
|
|
1950
|
+
writeFileSync4(
|
|
1951
|
+
tmpConfigPath,
|
|
1952
|
+
JSON.stringify(result.content, null, 2),
|
|
1953
|
+
"utf-8"
|
|
1954
|
+
);
|
|
1955
|
+
configPath = tmpConfigPath;
|
|
1956
|
+
configVersion = result.version;
|
|
1957
|
+
cliLogger.log(`Config version: ${result.version}`);
|
|
1958
|
+
}
|
|
1959
|
+
} catch (error) {
|
|
1960
|
+
cliLogger.error(
|
|
1961
|
+
`API fetch failed: ${error instanceof Error ? error.message : error}`
|
|
1962
|
+
);
|
|
1963
|
+
const cached = readCache(env.cacheDir);
|
|
1964
|
+
if (cached) {
|
|
1965
|
+
cliLogger.log(`Using cached bundle (version: ${cached.version})`);
|
|
1966
|
+
await runWithBundle(
|
|
1967
|
+
cached.bundlePath,
|
|
1968
|
+
env,
|
|
1969
|
+
logger,
|
|
1970
|
+
cliLogger,
|
|
1971
|
+
cached.version
|
|
1972
|
+
);
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
cliLogger.error("No cached bundle available. Cannot start.");
|
|
1976
|
+
process.exit(1);
|
|
1977
|
+
}
|
|
1978
|
+
} else {
|
|
1979
|
+
const resolved = await resolveBundle(env.bundlePath);
|
|
1980
|
+
if (resolved.source === "stdin") {
|
|
1981
|
+
cliLogger.log("Bundle: received via stdin");
|
|
1982
|
+
} else if (resolved.source === "url") {
|
|
1983
|
+
cliLogger.log("Bundle: fetched from URL");
|
|
1984
|
+
} else {
|
|
1985
|
+
cliLogger.log(`Bundle: ${resolved.path}`);
|
|
1986
|
+
}
|
|
1987
|
+
if (isPreBuiltConfig(resolved.path)) {
|
|
1988
|
+
await runWithBundle(resolved.path, env, logger, cliLogger, void 0);
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
configPath = resolved.path;
|
|
1992
|
+
}
|
|
1993
|
+
if (!configPath) {
|
|
1994
|
+
cliLogger.error("No config resolved");
|
|
1995
|
+
process.exit(1);
|
|
1996
|
+
}
|
|
1997
|
+
cliLogger.log("Building flow...");
|
|
1998
|
+
let bundlePath;
|
|
1999
|
+
try {
|
|
2000
|
+
bundlePath = await prepareBundleForRun(configPath, {
|
|
2001
|
+
verbose: false,
|
|
2002
|
+
silent: true
|
|
2003
|
+
});
|
|
2004
|
+
} catch (error) {
|
|
2005
|
+
cliLogger.error(
|
|
2006
|
+
`Bundle failed: ${error instanceof Error ? error.message : error}`
|
|
2007
|
+
);
|
|
2008
|
+
if (env.remoteConfig) {
|
|
2009
|
+
const cached = readCache(env.cacheDir);
|
|
2010
|
+
if (cached) {
|
|
2011
|
+
cliLogger.log(`Using cached bundle (version: ${cached.version})`);
|
|
2012
|
+
await runWithBundle(
|
|
2013
|
+
cached.bundlePath,
|
|
2014
|
+
env,
|
|
2015
|
+
logger,
|
|
2016
|
+
cliLogger,
|
|
2017
|
+
cached.version
|
|
2018
|
+
);
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
process.exit(1);
|
|
2023
|
+
}
|
|
2024
|
+
cliLogger.log("Bundle ready");
|
|
2025
|
+
try {
|
|
2026
|
+
const configContent = readFileSync4(configPath, "utf-8");
|
|
2027
|
+
writeCache(
|
|
2028
|
+
env.cacheDir,
|
|
2029
|
+
bundlePath,
|
|
2030
|
+
configContent,
|
|
2031
|
+
configVersion || "local"
|
|
2032
|
+
);
|
|
2033
|
+
} catch {
|
|
2034
|
+
cliLogger.debug("Cache write failed (non-critical)");
|
|
2035
|
+
}
|
|
2036
|
+
await runWithBundle(bundlePath, env, logger, cliLogger, configVersion);
|
|
2037
|
+
}
|
|
2038
|
+
async function runWithBundle(bundlePath, env, logger, cliLogger, configVersion) {
|
|
2039
|
+
let handle;
|
|
2040
|
+
try {
|
|
2041
|
+
handle = await loadFlow(bundlePath, { port: env.port }, logger);
|
|
2042
|
+
} catch (error) {
|
|
2043
|
+
cliLogger.error(
|
|
2044
|
+
`Failed to load flow: ${error instanceof Error ? error.message : error}`
|
|
2045
|
+
);
|
|
2046
|
+
process.exit(1);
|
|
2047
|
+
}
|
|
2048
|
+
setRunning();
|
|
2049
|
+
cliLogger.log("Flow running");
|
|
2050
|
+
cliLogger.log(`Port: ${env.port}`);
|
|
2051
|
+
let heartbeat = null;
|
|
2052
|
+
let poller = null;
|
|
2053
|
+
if (env.apiEnabled) {
|
|
2054
|
+
heartbeat = createHeartbeat(
|
|
2055
|
+
{
|
|
2056
|
+
appUrl: resolveAppUrl2(),
|
|
2057
|
+
token: env.token,
|
|
2058
|
+
projectId: env.projectId,
|
|
2059
|
+
flowId: env.flowId,
|
|
2060
|
+
configVersion,
|
|
2061
|
+
mode: env.mode,
|
|
2062
|
+
intervalMs: env.heartbeatInterval * 1e3
|
|
2063
|
+
},
|
|
2064
|
+
logger
|
|
2065
|
+
);
|
|
2066
|
+
heartbeat.start();
|
|
2067
|
+
cliLogger.log(`Heartbeat: active (every ${env.heartbeatInterval}s)`);
|
|
2068
|
+
}
|
|
2069
|
+
if (env.remoteConfig) {
|
|
2070
|
+
poller = createPoller(
|
|
2071
|
+
{
|
|
2072
|
+
fetchOptions: {
|
|
2073
|
+
appUrl: resolveAppUrl2(),
|
|
2074
|
+
token: env.token,
|
|
2075
|
+
projectId: env.projectId,
|
|
2076
|
+
flowId: env.flowId
|
|
2077
|
+
},
|
|
2078
|
+
intervalMs: env.pollInterval * 1e3,
|
|
2079
|
+
onUpdate: async (content, version) => {
|
|
2080
|
+
const tmpConfigPath = `/tmp/walkeros-flow-${Date.now()}.json`;
|
|
2081
|
+
writeFileSync4(
|
|
2082
|
+
tmpConfigPath,
|
|
2083
|
+
JSON.stringify(content, null, 2),
|
|
2084
|
+
"utf-8"
|
|
2085
|
+
);
|
|
2086
|
+
const newBundle = await prepareBundleForRun(tmpConfigPath, {
|
|
2087
|
+
verbose: false,
|
|
2088
|
+
silent: true
|
|
2089
|
+
});
|
|
2090
|
+
handle = await swapFlow(
|
|
2091
|
+
handle,
|
|
2092
|
+
newBundle,
|
|
2093
|
+
{ port: env.port },
|
|
2094
|
+
logger
|
|
2095
|
+
);
|
|
2096
|
+
writeCache(env.cacheDir, newBundle, JSON.stringify(content), version);
|
|
2097
|
+
configVersion = version;
|
|
2098
|
+
if (heartbeat) heartbeat.updateConfigVersion(version);
|
|
2099
|
+
updateConfigVersion(version);
|
|
2100
|
+
updateLastPoll();
|
|
2101
|
+
cliLogger.log(`Hot-swapped to version ${version}`);
|
|
2102
|
+
}
|
|
2103
|
+
},
|
|
2104
|
+
logger
|
|
2105
|
+
);
|
|
2106
|
+
poller.start();
|
|
2107
|
+
cliLogger.log(`Polling: active (every ${env.pollInterval}s)`);
|
|
2108
|
+
}
|
|
2109
|
+
const shutdown = async (signal) => {
|
|
2110
|
+
cliLogger.log(`Received ${signal}, shutting down...`);
|
|
2111
|
+
if (poller) poller.stop();
|
|
2112
|
+
if (heartbeat) heartbeat.stop();
|
|
2113
|
+
try {
|
|
2114
|
+
if (handle.collector.command) {
|
|
2115
|
+
await handle.collector.command("shutdown");
|
|
2116
|
+
}
|
|
2117
|
+
} catch {
|
|
2118
|
+
}
|
|
2119
|
+
cliLogger.log("Shutdown complete");
|
|
2120
|
+
process.exit(0);
|
|
2121
|
+
};
|
|
2122
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
2123
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
2124
|
+
await new Promise(() => {
|
|
2125
|
+
});
|
|
391
2126
|
}
|
|
392
2127
|
main();
|