@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.
@@ -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 runFlow(file, config, logger, loggerConfig) {
7
- logger.info(`Loading flow from ${file}`);
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
- const absolutePath = resolve(file);
10
- const flowDir = dirname(absolutePath);
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
- const message = error instanceof Error ? error.message : String(error);
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/resolve-bundle.ts
152
- import { writeFileSync } from "fs";
153
-
154
- // src/core/stdin.ts
155
- function isStdinPiped() {
156
- return !process.stdin.isTTY;
157
- }
158
- async function readStdin() {
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 content = Buffer.concat(chunks).toString("utf-8");
164
- if (!content.trim()) {
165
- throw new Error("No input received on stdin");
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
- `Failed to fetch bundle from ${url}: ${response.status} ${response.statusText}`
322
+ `Config fetch failed: ${response.status} ${response.statusText}`
180
323
  );
181
324
  }
182
- const content = await response.text();
183
- if (!content.trim()) {
184
- throw new Error(`Bundle fetched from ${url} is empty`);
185
- }
186
- writeFileSync(TEMP_BUNDLE_PATH, content, "utf-8");
187
- return TEMP_BUNDLE_PATH;
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
- async function readBundleFromStdin() {
190
- const content = await readStdin();
191
- writeFileSync(TEMP_BUNDLE_PATH, content, "utf-8");
192
- return TEMP_BUNDLE_PATH;
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
- async function resolveBundle(bundleEnv) {
195
- if (isStdinPiped()) {
196
- const path = await readBundleFromStdin();
197
- return { path, source: "stdin" };
198
- }
199
- if (isUrl(bundleEnv)) {
200
- const path = await fetchBundle(bundleEnv);
201
- return { path, source: "url" };
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/register.ts
207
- async function registerRuntime(config) {
208
- const url = `${config.appUrl}/api/projects/${config.projectId}/runtimes/register`;
209
- const response = await fetch(url, {
210
- method: "POST",
211
- headers: {
212
- Authorization: `Bearer ${config.deployToken}`,
213
- "Content-Type": "application/json"
214
- },
215
- body: JSON.stringify({
216
- flowId: config.flowId,
217
- bundlePath: config.bundlePath
218
- })
219
- });
220
- if (!response.ok) {
221
- throw new Error(
222
- `Registration failed: ${response.status} ${response.statusText}`
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
- const data = await response.json();
226
- return { bundleUrl: data.bundleUrl };
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/runtime/main.ts
307
- function adaptLogger(cliLogger) {
308
- return {
309
- error: (message) => {
310
- const msg = message instanceof Error ? message.message : message;
311
- cliLogger.error(msg);
312
- },
313
- info: (message) => {
314
- const msg = message instanceof Error ? message.message : message;
315
- cliLogger.info(msg);
316
- },
317
- debug: (message) => {
318
- const msg = message instanceof Error ? message.message : message;
319
- cliLogger.debug(msg);
320
- },
321
- throw: (message) => {
322
- const msg = message instanceof Error ? message.message : message;
323
- cliLogger.error(msg);
324
- throw message instanceof Error ? message : new Error(msg);
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
- async function main() {
332
- const mode = process.env.MODE || "collect";
333
- let bundleEnv = process.env.BUNDLE || "/app/flow/bundle.mjs";
334
- const port = parseInt(process.env.PORT || "8080", 10);
335
- const cliLogger = createLogger({ silent: false, verbose: true });
336
- const logger = adaptLogger(cliLogger);
337
- cliLogger.log(`Starting walkeros/flow in ${mode} mode`);
338
- const appUrl = process.env.APP_URL;
339
- const deployToken = process.env.DEPLOY_TOKEN;
340
- const projectId = process.env.PROJECT_ID;
341
- const flowId = process.env.FLOW_ID;
342
- const bundlePath = process.env.BUNDLE_PATH;
343
- if (appUrl && deployToken && projectId && flowId && bundlePath) {
344
- try {
345
- cliLogger.log("Registering with app...");
346
- const result = await registerRuntime({
347
- appUrl,
348
- deployToken,
349
- projectId,
350
- flowId,
351
- bundlePath
352
- });
353
- bundleEnv = result.bundleUrl;
354
- cliLogger.log("Registered, bundle URL received");
355
- } catch (error) {
356
- const message = error instanceof Error ? error.message : String(error);
357
- cliLogger.error(`Registration failed: ${message}`);
358
- process.exit(1);
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 resolved = await resolveBundle(bundleEnv);
364
- file = resolved.path;
365
- if (resolved.source === "stdin") {
366
- cliLogger.log("Bundle: received via stdin pipe");
367
- } else if (resolved.source === "url") {
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
- const message = error instanceof Error ? error.message : String(error);
374
- cliLogger.error(`Failed to resolve bundle: ${message}`);
375
- process.exit(1);
600
+ if (error instanceof Error) {
601
+ throw new Error(`Failed to download from URL: ${error.message}`);
602
+ }
603
+ throw error;
376
604
  }
377
- cliLogger.log(`Port: ${port}`);
378
- try {
379
- if (mode === "collect") {
380
- await runFlow(file, { port }, logger);
381
- } else if (mode === "serve") {
382
- await runServeMode({ file, port }, logger);
383
- } else {
384
- cliLogger.error(`Unknown mode: ${mode}. Use 'collect' or 'serve'.`);
385
- process.exit(1);
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
- cliLogger.error(`Failed to start: ${error}`);
389
- process.exit(1);
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();