@walkeros/cli 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,182 +1,58 @@
1
- // src/commands/bundle/index.ts
2
- import path10 from "path";
3
- import fs9 from "fs-extra";
4
- import { getPlatform as getPlatform2 } from "@walkeros/core";
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x2) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x2, {
4
+ get: (a2, b2) => (typeof require !== "undefined" ? require : a2)[b2]
5
+ }) : x2)(function(x2) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x2 + '" is not supported');
8
+ });
9
+ var __esm = (fn2, res) => function __init() {
10
+ return fn2 && (res = (0, fn2[__getOwnPropNames(fn2)[0]])(fn2 = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
5
16
 
6
- // src/core/logger.ts
17
+ // src/core/cli-logger.ts
7
18
  import chalk from "chalk";
8
- var BRAND_COLOR = "#01b5e2";
9
- function createLogger(options = {}) {
19
+ import { createLogger, Level } from "@walkeros/core";
20
+ function createCLILogger(options = {}) {
10
21
  const {
11
22
  verbose = false,
12
23
  silent = false,
13
24
  json = false,
14
25
  stderr = false
15
26
  } = options;
16
- const shouldLog = !silent && !json;
17
- const shouldDebug = verbose && !silent && !json;
18
27
  const out = stderr ? console.error : console.log;
19
- return {
20
- log: (...args) => {
21
- if (shouldLog) {
22
- const message = args.map((arg) => String(arg)).join(" ");
23
- out(message);
24
- }
25
- },
26
- brand: (...args) => {
27
- if (shouldLog) {
28
- const message = args.map((arg) => String(arg)).join(" ");
29
- out(chalk.hex(BRAND_COLOR)(message));
30
- }
31
- },
32
- error: (...args) => {
33
- if (!json) {
34
- const message = args.map((arg) => String(arg)).join(" ");
35
- console.error(chalk.red(message));
36
- }
37
- },
38
- debug: (...args) => {
39
- if (shouldDebug) {
40
- const message = args.map((arg) => String(arg)).join(" ");
41
- out(` ${message}`);
42
- }
43
- },
44
- json: (data) => {
45
- if (!silent) {
46
- out(JSON.stringify(data, null, 2));
47
- }
48
- },
49
- // Backward-compatible methods (all use default terminal color per design)
50
- info: (...args) => {
51
- if (shouldLog) {
52
- const message = args.map((arg) => String(arg)).join(" ");
53
- out(message);
54
- }
55
- },
56
- success: (...args) => {
57
- if (shouldLog) {
58
- const message = args.map((arg) => String(arg)).join(" ");
59
- out(message);
60
- }
61
- },
62
- warning: (...args) => {
63
- if (shouldLog) {
64
- const message = args.map((arg) => String(arg)).join(" ");
65
- out(message);
66
- }
67
- },
68
- warn: (...args) => {
69
- if (shouldLog) {
70
- const message = args.map((arg) => String(arg)).join(" ");
71
- out(message);
72
- }
73
- },
74
- gray: (...args) => {
75
- if (shouldLog) {
76
- const message = args.map((arg) => String(arg)).join(" ");
77
- out(message);
78
- }
79
- }
80
- };
81
- }
82
- function createCommandLogger(options) {
83
28
  return createLogger({
84
- verbose: options.verbose,
85
- silent: options.silent ?? false,
86
- json: options.json,
87
- stderr: options.stderr
88
- });
89
- }
90
-
91
- // src/core/collector-logger.ts
92
- function createCollectorLoggerConfig(cliLogger, verbose) {
93
- return {
94
- level: verbose ? "DEBUG" : "ERROR",
95
- handler: (level, message, context, scope) => {
29
+ // Let handler control visibility — pass everything through
30
+ level: Level.DEBUG,
31
+ handler: (level, message, _context, scope) => {
96
32
  const scopePath = scope.length > 0 ? `[${scope.join(":")}] ` : "";
97
- const hasContext = Object.keys(context).length > 0;
98
- const contextStr = hasContext ? ` ${JSON.stringify(context)}` : "";
99
- if (level === 0) {
100
- cliLogger.error(`${scopePath}${message}${contextStr}`);
101
- } else if (verbose) {
102
- cliLogger.debug(`${scopePath}${message}${contextStr}`);
33
+ const fullMessage = `${scopePath}${message}`;
34
+ if (level === Level.ERROR) {
35
+ if (!json) console.error(chalk.red(fullMessage));
36
+ return;
103
37
  }
104
- }
105
- };
106
- }
107
-
108
- // src/core/timer.ts
109
- function createTimer() {
110
- let startTime2 = 0;
111
- let endTime = 0;
112
- return {
113
- start() {
114
- startTime2 = Date.now();
115
- endTime = 0;
116
- },
117
- end() {
118
- endTime = Date.now();
119
- return endTime - startTime2;
120
- },
121
- getElapsed() {
122
- const currentTime = endTime || Date.now();
123
- return currentTime - startTime2;
38
+ if (silent || json) return;
39
+ if (level === Level.DEBUG) {
40
+ if (!verbose) return;
41
+ out(` ${fullMessage}`);
42
+ return;
43
+ }
44
+ out(fullMessage);
124
45
  },
125
- format() {
126
- const elapsed = this.getElapsed();
127
- return (elapsed / 1e3).toFixed(2) + "s";
46
+ jsonHandler: (data) => {
47
+ if (!silent) out(JSON.stringify(data, null, 2));
128
48
  }
129
- };
49
+ });
130
50
  }
131
-
132
- // src/core/output.ts
133
- import fs from "fs-extra";
134
- import path from "path";
135
- async function writeResult(content, options) {
136
- if (options.output) {
137
- const outputPath = path.resolve(options.output);
138
- await fs.ensureDir(path.dirname(outputPath));
139
- await fs.writeFile(outputPath, content);
140
- } else {
141
- process.stdout.write(content);
142
- process.stdout.write("\n");
51
+ var init_cli_logger = __esm({
52
+ "src/core/cli-logger.ts"() {
53
+ "use strict";
143
54
  }
144
- }
145
- function createJsonOutput(success, data, error, duration) {
146
- return {
147
- success,
148
- ...data && { data },
149
- ...error && { error },
150
- ...duration && { duration }
151
- };
152
- }
153
- function createSuccessOutput(data, duration) {
154
- return createJsonOutput(true, data, void 0, duration);
155
- }
156
- function createErrorOutput(error, duration) {
157
- return createJsonOutput(false, void 0, error, duration);
158
- }
159
- function formatBytes(bytes) {
160
- return (bytes / 1024).toFixed(2);
161
- }
162
-
163
- // src/core/tmp.ts
164
- import path2 from "path";
165
- var DEFAULT_TMP_ROOT = ".tmp";
166
- function getTmpPath(tmpDir, ...segments) {
167
- const root = tmpDir || DEFAULT_TMP_ROOT;
168
- const absoluteRoot = path2.isAbsolute(root) ? root : path2.resolve(root);
169
- return path2.join(absoluteRoot, ...segments);
170
- }
171
-
172
- // src/core/asset-resolver.ts
173
- import { fileURLToPath } from "url";
174
- import { existsSync as existsSync2 } from "fs";
175
- import path4 from "path";
176
-
177
- // src/config/utils.ts
178
- import fs2 from "fs-extra";
179
- import path3 from "path";
55
+ });
180
56
 
181
57
  // src/lib/config-file.ts
182
58
  import {
@@ -226,6 +102,9 @@ function resolveToken() {
226
102
  if (config?.token) return { token: config.token, source: "config" };
227
103
  return null;
228
104
  }
105
+ function resolveDeployToken() {
106
+ return process.env.WALKEROS_DEPLOY_TOKEN ?? null;
107
+ }
229
108
  function resolveAppUrl() {
230
109
  const envUrl = process.env.WALKEROS_APP_URL;
231
110
  if (envUrl) return envUrl;
@@ -233,6 +112,11 @@ function resolveAppUrl() {
233
112
  if (config?.appUrl) return config.appUrl;
234
113
  return "https://app.walkeros.io";
235
114
  }
115
+ var init_config_file = __esm({
116
+ "src/lib/config-file.ts"() {
117
+ "use strict";
118
+ }
119
+ });
236
120
 
237
121
  // src/core/auth.ts
238
122
  function getToken() {
@@ -252,6 +136,19 @@ async function authenticatedFetch(url, init) {
252
136
  headers: { ...existingHeaders, ...authHeaders }
253
137
  });
254
138
  }
139
+ async function deployAuthenticatedFetch(url, init) {
140
+ const deployToken = resolveDeployToken();
141
+ const token = deployToken ?? getToken();
142
+ if (!token)
143
+ throw new Error(
144
+ "No authentication token available. Set WALKEROS_DEPLOY_TOKEN or run walkeros auth login."
145
+ );
146
+ const existingHeaders = init?.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : Array.isArray(init?.headers) ? Object.fromEntries(init.headers) : init?.headers ?? {};
147
+ return fetch(url, {
148
+ ...init,
149
+ headers: { ...existingHeaders, Authorization: `Bearer ${token}` }
150
+ });
151
+ }
255
152
  function resolveBaseUrl() {
256
153
  return resolveAppUrl();
257
154
  }
@@ -260,8 +157,227 @@ function requireProjectId() {
260
157
  if (!projectId) throw new Error("WALKEROS_PROJECT_ID not set.");
261
158
  return projectId;
262
159
  }
160
+ var init_auth = __esm({
161
+ "src/core/auth.ts"() {
162
+ "use strict";
163
+ init_config_file();
164
+ }
165
+ });
166
+
167
+ // src/version.ts
168
+ import { readFileSync as readFileSync2 } from "fs";
169
+ import { fileURLToPath as fileURLToPath2 } from "url";
170
+ import { dirname as dirname2, join as join2 } from "path";
171
+ function findPackageJson() {
172
+ const paths = [
173
+ join2(versionDirname, "../package.json"),
174
+ // dist/ or src/
175
+ join2(versionDirname, "../../package.json")
176
+ // src/core/ (not used, but safe)
177
+ ];
178
+ for (const p2 of paths) {
179
+ try {
180
+ return readFileSync2(p2, "utf-8");
181
+ } catch {
182
+ }
183
+ }
184
+ return JSON.stringify({ version: "0.0.0" });
185
+ }
186
+ var versionFilename, versionDirname, VERSION;
187
+ var init_version = __esm({
188
+ "src/version.ts"() {
189
+ "use strict";
190
+ versionFilename = fileURLToPath2(import.meta.url);
191
+ versionDirname = dirname2(versionFilename);
192
+ VERSION = JSON.parse(findPackageJson()).version;
193
+ }
194
+ });
195
+
196
+ // src/commands/run/heartbeat.ts
197
+ var heartbeat_exports = {};
198
+ __export(heartbeat_exports, {
199
+ startHeartbeat: () => startHeartbeat
200
+ });
201
+ import { randomUUID } from "crypto";
202
+ async function startHeartbeat(options) {
203
+ const projectId = options.projectId ?? requireProjectId();
204
+ const base = resolveBaseUrl();
205
+ const instanceId2 = randomUUID();
206
+ const healthEndpoint = options.healthEndpoint ?? "/health";
207
+ const intervalSec = options.heartbeatInterval ?? 60;
208
+ const log = createCLILogger();
209
+ const startTime = Date.now();
210
+ const heartbeatUrl = `${base}/api/projects/${projectId}/deployments/${options.deployment}/heartbeat`;
211
+ const initResponse = await deployAuthenticatedFetch(heartbeatUrl, {
212
+ method: "POST",
213
+ headers: { "Content-Type": "application/json" },
214
+ body: JSON.stringify({
215
+ url: options.url,
216
+ healthEndpoint,
217
+ instanceId: instanceId2,
218
+ cliVersion: VERSION
219
+ })
220
+ });
221
+ if (!initResponse.ok) {
222
+ const err = await initResponse.json().catch(() => ({}));
223
+ throw new Error(
224
+ err.error?.message || `Initial heartbeat failed (${initResponse.status})`
225
+ );
226
+ }
227
+ const initData = await initResponse.json();
228
+ log.info(
229
+ `Registered as ${instanceId2} on deployment ${options.deployment} (${initData.deploymentId})`
230
+ );
231
+ const heartbeatTimer = setInterval(async () => {
232
+ try {
233
+ const resp = await deployAuthenticatedFetch(heartbeatUrl, {
234
+ method: "POST",
235
+ headers: { "Content-Type": "application/json" },
236
+ body: JSON.stringify({
237
+ instanceId: instanceId2,
238
+ uptime: Math.floor((Date.now() - startTime) / 1e3),
239
+ cliVersion: VERSION,
240
+ metadata: {
241
+ nodeVersion: process.version,
242
+ platform: process.platform
243
+ }
244
+ })
245
+ });
246
+ if (resp.ok) {
247
+ const data = await resp.json();
248
+ if (data.action === "update" && data.bundleUrl) {
249
+ log.info(
250
+ `Update available: version ${data.versionNumber}, downloading from ${data.bundleUrl}`
251
+ );
252
+ } else if (data.action === "stop") {
253
+ log.info("Received stop signal from server, shutting down...");
254
+ await cleanup();
255
+ process.exit(0);
256
+ }
257
+ }
258
+ } catch (err) {
259
+ log.error(
260
+ `Heartbeat failed: ${err instanceof Error ? err.message : "Unknown error"}`
261
+ );
262
+ }
263
+ }, intervalSec * 1e3);
264
+ const cleanup = async () => {
265
+ clearInterval(heartbeatTimer);
266
+ try {
267
+ await deployAuthenticatedFetch(heartbeatUrl, {
268
+ method: "POST",
269
+ headers: { "Content-Type": "application/json" },
270
+ body: JSON.stringify({
271
+ instanceId: instanceId2,
272
+ uptime: Math.floor((Date.now() - startTime) / 1e3),
273
+ shutting_down: true
274
+ })
275
+ });
276
+ } catch {
277
+ }
278
+ };
279
+ process.on("SIGTERM", async () => {
280
+ await cleanup();
281
+ process.exit(0);
282
+ });
283
+ process.on("SIGINT", async () => {
284
+ await cleanup();
285
+ process.exit(0);
286
+ });
287
+ return { instanceId: instanceId2, deploymentId: initData.deploymentId, cleanup };
288
+ }
289
+ var init_heartbeat = __esm({
290
+ "src/commands/run/heartbeat.ts"() {
291
+ "use strict";
292
+ init_auth();
293
+ init_cli_logger();
294
+ init_version();
295
+ }
296
+ });
297
+
298
+ // src/commands/bundle/index.ts
299
+ init_cli_logger();
300
+ import path10 from "path";
301
+ import fs10 from "fs-extra";
302
+ import { getPlatform as getPlatform2 } from "@walkeros/core";
303
+
304
+ // src/core/index.ts
305
+ init_cli_logger();
306
+
307
+ // src/core/timer.ts
308
+ function createTimer() {
309
+ let startTime = 0;
310
+ let endTime = 0;
311
+ return {
312
+ start() {
313
+ startTime = Date.now();
314
+ endTime = 0;
315
+ },
316
+ end() {
317
+ endTime = Date.now();
318
+ return endTime - startTime;
319
+ },
320
+ getElapsed() {
321
+ const currentTime = endTime || Date.now();
322
+ return currentTime - startTime;
323
+ },
324
+ format() {
325
+ const elapsed = this.getElapsed();
326
+ return (elapsed / 1e3).toFixed(2) + "s";
327
+ }
328
+ };
329
+ }
330
+
331
+ // src/core/output.ts
332
+ import fs from "fs-extra";
333
+ import path from "path";
334
+ async function writeResult(content, options) {
335
+ if (options.output) {
336
+ const outputPath = path.resolve(options.output);
337
+ await fs.ensureDir(path.dirname(outputPath));
338
+ await fs.writeFile(outputPath, content);
339
+ } else {
340
+ process.stdout.write(content);
341
+ process.stdout.write("\n");
342
+ }
343
+ }
344
+ function createJsonOutput(success, data, error, duration) {
345
+ return {
346
+ success,
347
+ ...data && { data },
348
+ ...error && { error },
349
+ ...duration && { duration }
350
+ };
351
+ }
352
+ function createSuccessOutput(data, duration) {
353
+ return createJsonOutput(true, data, void 0, duration);
354
+ }
355
+ function createErrorOutput(error, duration) {
356
+ return createJsonOutput(false, void 0, error, duration);
357
+ }
358
+ function formatBytes(bytes) {
359
+ return (bytes / 1024).toFixed(2);
360
+ }
361
+
362
+ // src/core/tmp.ts
363
+ import os from "os";
364
+ import path2 from "path";
365
+ var DEFAULT_TMP_ROOT = os.tmpdir();
366
+ function getTmpPath(tmpDir, ...segments) {
367
+ const root = tmpDir || DEFAULT_TMP_ROOT;
368
+ const absoluteRoot = path2.isAbsolute(root) ? root : path2.resolve(root);
369
+ return path2.join(absoluteRoot, ...segments);
370
+ }
371
+
372
+ // src/core/asset-resolver.ts
373
+ import { fileURLToPath } from "url";
374
+ import { existsSync as existsSync2 } from "fs";
375
+ import path4 from "path";
263
376
 
264
377
  // src/config/utils.ts
378
+ import fs2 from "fs-extra";
379
+ import path3 from "path";
380
+ init_auth();
265
381
  function isUrl(str) {
266
382
  try {
267
383
  const url = new URL(str);
@@ -467,6 +583,7 @@ async function copyLocalPackage(localPkg, targetDir, logger2) {
467
583
 
468
584
  // src/core/input-detector.ts
469
585
  import fs4 from "fs-extra";
586
+ init_auth();
470
587
  async function detectInput(inputPath, platformOverride) {
471
588
  const content = await loadContent(inputPath);
472
589
  try {
@@ -508,6 +625,32 @@ async function readStdin() {
508
625
  return content;
509
626
  }
510
627
 
628
+ // src/core/index.ts
629
+ init_auth();
630
+
631
+ // src/core/sse.ts
632
+ function parseSSEEvents(buffer) {
633
+ const events = [];
634
+ const blocks = buffer.split("\n\n");
635
+ const remainder = blocks.pop() || "";
636
+ for (const block of blocks) {
637
+ if (!block.trim()) continue;
638
+ let eventType = "message";
639
+ const dataLines = [];
640
+ for (const line of block.split("\n")) {
641
+ if (line.startsWith("event:")) {
642
+ eventType = line.slice(6).trim();
643
+ } else if (line.startsWith("data:")) {
644
+ dataLines.push(line.slice(5).trimStart());
645
+ }
646
+ }
647
+ if (dataLines.length > 0) {
648
+ events.push({ type: eventType, data: dataLines.join("\n") });
649
+ }
650
+ }
651
+ return { parsed: events, remainder };
652
+ }
653
+
511
654
  // src/config/validators.ts
512
655
  import { schemas } from "@walkeros/core/dev";
513
656
  var { safeParseSetup } = schemas;
@@ -518,8 +661,8 @@ function validateFlowSetup(data) {
518
661
  const result = safeParseSetup(data);
519
662
  if (!result.success) {
520
663
  const errors = result.error.issues.map((issue) => {
521
- const path15 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
522
- return ` - ${path15}: ${issue.message}`;
664
+ const path14 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
665
+ return ` - ${path14}: ${issue.message}`;
523
666
  }).join("\n");
524
667
  throw new Error(`Invalid configuration:
525
668
  ${errors}`);
@@ -569,13 +712,16 @@ function loadBundleConfig(rawConfig, options) {
569
712
  const setup = validateFlowSetup(rawConfig);
570
713
  const availableFlows = getAvailableFlows(setup);
571
714
  const flowName = resolveFlow(setup, options.flowName, availableFlows);
572
- const flowConfig = getFlowConfig(setup, flowName);
715
+ let flowConfig = getFlowConfig(setup, flowName, { deferred: true });
573
716
  const platform = getPlatform(flowConfig);
574
717
  if (!platform) {
575
718
  throw new Error(
576
719
  `Invalid configuration: flow "${flowName}" must have a "web" or "server" key.`
577
720
  );
578
721
  }
722
+ if (platform === "web") {
723
+ flowConfig = getFlowConfig(setup, flowName);
724
+ }
579
725
  const buildDefaults = getBuildDefaults(platform);
580
726
  const packages = flowConfig.packages || {};
581
727
  const output = options.buildOverrides?.output || getDefaultOutput(platform);
@@ -651,7 +797,7 @@ async function loadFlowConfig(configPath, options) {
651
797
  import esbuild from "esbuild";
652
798
  import path9 from "path";
653
799
  import fs8 from "fs-extra";
654
- import { packageNameToVariable } from "@walkeros/core";
800
+ import { packageNameToVariable, ENV_MARKER_PREFIX } from "@walkeros/core";
655
801
 
656
802
  // src/commands/bundle/package-manager.ts
657
803
  import pacote from "pacote";
@@ -691,10 +837,15 @@ async function getFlowConfigCacheKey(content, date) {
691
837
  // src/commands/bundle/package-manager.ts
692
838
  var PACKAGE_DOWNLOAD_TIMEOUT_MS = 6e4;
693
839
  async function withTimeout(promise, ms, errorMessage) {
694
- const timeout = new Promise(
695
- (_, reject) => setTimeout(() => reject(new Error(errorMessage)), ms)
696
- );
697
- return Promise.race([promise, timeout]);
840
+ let timer;
841
+ const timeout = new Promise((_2, reject) => {
842
+ timer = setTimeout(() => reject(new Error(errorMessage)), ms);
843
+ });
844
+ try {
845
+ return await Promise.race([promise, timeout]);
846
+ } finally {
847
+ clearTimeout(timer);
848
+ }
698
849
  }
699
850
  function getPackageDirectory(baseDir, packageName, version) {
700
851
  return path7.join(baseDir, "node_modules", packageName);
@@ -726,7 +877,7 @@ function validateNoDuplicatePackages(packages) {
726
877
  if (conflicts.length > 0) {
727
878
  throw new Error(
728
879
  `Version conflicts detected:
729
- ${conflicts.map((c) => ` - ${c}`).join("\n")}
880
+ ${conflicts.map((c2) => ` - ${c2}`).join("\n")}
730
881
 
731
882
  Each package must use the same version across all declarations. Please update your configuration to use consistent versions.`
732
883
  );
@@ -762,7 +913,7 @@ async function downloadPackages(packages, targetDir, logger2, useCache = true, c
762
913
  const packagePaths = /* @__PURE__ */ new Map();
763
914
  const downloadQueue = [...packages];
764
915
  const processed = /* @__PURE__ */ new Set();
765
- const userSpecifiedPackages = new Set(packages.map((p) => p.name));
916
+ const userSpecifiedPackages = new Set(packages.map((p2) => p2.name));
766
917
  const localPackageMap = /* @__PURE__ */ new Map();
767
918
  for (const pkg of packages) {
768
919
  if (pkg.path) {
@@ -970,7 +1121,7 @@ function validateFlowConfig(flowConfig, logger2) {
970
1121
  const sources = flowConfig.sources || {};
971
1122
  for (const [sourceId, source] of Object.entries(sources)) {
972
1123
  if (source && typeof source === "object" && source.code === true) {
973
- logger2.warning(
1124
+ logger2.warn(
974
1125
  `DEPRECATED: Source "${sourceId}" uses code: true which is no longer supported. Use $code: prefix in config values or create a source package instead.`
975
1126
  );
976
1127
  hasDeprecatedCodeTrue = true;
@@ -979,7 +1130,7 @@ function validateFlowConfig(flowConfig, logger2) {
979
1130
  const destinations = flowConfig.destinations || {};
980
1131
  for (const [destId, dest] of Object.entries(destinations)) {
981
1132
  if (dest && typeof dest === "object" && dest.code === true) {
982
- logger2.warning(
1133
+ logger2.warn(
983
1134
  `DEPRECATED: Destination "${destId}" uses code: true which is no longer supported. Use $code: prefix in config values or create a destination package instead.`
984
1135
  );
985
1136
  hasDeprecatedCodeTrue = true;
@@ -988,14 +1139,14 @@ function validateFlowConfig(flowConfig, logger2) {
988
1139
  const transformers = flowConfig.transformers || {};
989
1140
  for (const [transformerId, transformer] of Object.entries(transformers)) {
990
1141
  if (transformer && typeof transformer === "object" && transformer.code === true) {
991
- logger2.warning(
1142
+ logger2.warn(
992
1143
  `DEPRECATED: Transformer "${transformerId}" uses code: true which is no longer supported. Use $code: prefix in config values or create a transformer package instead.`
993
1144
  );
994
1145
  hasDeprecatedCodeTrue = true;
995
1146
  }
996
1147
  }
997
1148
  if (hasDeprecatedCodeTrue) {
998
- logger2.warning(
1149
+ logger2.warn(
999
1150
  `See https://www.elbwalker.com/docs/walkeros/getting-started/flow for migration guide.`
1000
1151
  );
1001
1152
  }
@@ -1005,7 +1156,7 @@ async function bundleCore(flowConfig, buildOptions, logger2, showStats = false)
1005
1156
  const bundleStartTime = Date.now();
1006
1157
  const hasDeprecatedFeatures = validateFlowConfig(flowConfig, logger2);
1007
1158
  if (hasDeprecatedFeatures) {
1008
- logger2.warning("Skipping deprecated code: true entries from bundle.");
1159
+ logger2.warn("Skipping deprecated code: true entries from bundle.");
1009
1160
  }
1010
1161
  const TEMP_DIR = buildOptions.tempDir || getTmpPath();
1011
1162
  if (buildOptions.cache !== false) {
@@ -1020,7 +1171,7 @@ async function bundleCore(flowConfig, buildOptions, logger2, showStats = false)
1020
1171
  await fs8.writeFile(outputPath, cachedBuild);
1021
1172
  const stats = await fs8.stat(outputPath);
1022
1173
  const sizeKB = (stats.size / 1024).toFixed(1);
1023
- logger2.log(`Output: ${outputPath} (${sizeKB} KB, cached)`);
1174
+ logger2.info(`Output: ${outputPath} (${sizeKB} KB, cached)`);
1024
1175
  if (showStats) {
1025
1176
  const stats2 = await fs8.stat(outputPath);
1026
1177
  const packageStats = Object.entries(buildOptions.packages).map(
@@ -1030,11 +1181,14 @@ async function bundleCore(flowConfig, buildOptions, logger2, showStats = false)
1030
1181
  // Size estimation not available for cached builds
1031
1182
  })
1032
1183
  );
1184
+ const hasWildcardImports = /import\s+\*\s+as\s+\w+\s+from/.test(
1185
+ buildOptions.code || ""
1186
+ );
1033
1187
  return {
1034
1188
  totalSize: stats2.size,
1035
1189
  packages: packageStats,
1036
1190
  buildTime: Date.now() - bundleStartTime,
1037
- treeshakingEffective: true
1191
+ treeshakingEffective: !hasWildcardImports
1038
1192
  };
1039
1193
  }
1040
1194
  return;
@@ -1123,7 +1277,7 @@ async function bundleCore(flowConfig, buildOptions, logger2, showStats = false)
1123
1277
  const outputStats = await fs8.stat(outputPath);
1124
1278
  const sizeKB = (outputStats.size / 1024).toFixed(1);
1125
1279
  const buildTime = ((Date.now() - bundleStartTime) / 1e3).toFixed(1);
1126
- logger2.log(`Output: ${outputPath} (${sizeKB} KB, ${buildTime}s)`);
1280
+ logger2.info(`Output: ${outputPath} (${sizeKB} KB, ${buildTime}s)`);
1127
1281
  if (buildOptions.cache !== false) {
1128
1282
  const configContent = generateCacheKeyContent(flowConfig, buildOptions);
1129
1283
  const buildOutput = await fs8.readFile(outputPath, "utf-8");
@@ -1153,10 +1307,10 @@ async function bundleCore(flowConfig, buildOptions, logger2, showStats = false)
1153
1307
  throw error;
1154
1308
  }
1155
1309
  }
1156
- async function collectBundleStats(outputPath, packages, startTime2, entryContent) {
1310
+ async function collectBundleStats(outputPath, packages, startTime, entryContent) {
1157
1311
  const stats = await fs8.stat(outputPath);
1158
1312
  const totalSize = stats.size;
1159
- const buildTime = Date.now() - startTime2;
1313
+ const buildTime = Date.now() - startTime;
1160
1314
  const packageStats = Object.entries(packages).map(([name, pkg]) => {
1161
1315
  const importPattern = new RegExp(`from\\s+['"]${name}['"]`, "g");
1162
1316
  const namedImportPattern = new RegExp(
@@ -1428,9 +1582,9 @@ async function createEntryPoint(flowConfig, buildOptions, packagePaths) {
1428
1582
  );
1429
1583
  const importsCode = importStatements.join("\n");
1430
1584
  const hasFlow = Object.values(flowConfig.sources || {}).some(
1431
- (s) => s.package || isInlineCode(s.code)
1585
+ (s2) => s2.package || isInlineCode(s2.code)
1432
1586
  ) || Object.values(flowConfig.destinations || {}).some(
1433
- (d) => d.package || isInlineCode(d.code)
1587
+ (d2) => d2.package || isInlineCode(d2.code)
1434
1588
  );
1435
1589
  if (!hasFlow) {
1436
1590
  const userCode = buildOptions.code || "";
@@ -1582,12 +1736,42 @@ function serializeWithCode(value, indent) {
1582
1736
  if (value.startsWith("$code:")) {
1583
1737
  return value.slice(6);
1584
1738
  }
1739
+ const esc = ENV_MARKER_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1740
+ const markerRe = new RegExp(
1741
+ esc + "([a-zA-Z_][a-zA-Z0-9_]*)(?::((?:(?!" + esc + `)[^\\s"'])*))?`,
1742
+ "g"
1743
+ );
1744
+ if (markerRe.test(value)) {
1745
+ markerRe.lastIndex = 0;
1746
+ const pureRe = new RegExp(
1747
+ "^" + esc + "([a-zA-Z_][a-zA-Z0-9_]*)(?::((?:(?!" + esc + `)[^\\s"'])*))?$`
1748
+ );
1749
+ const pureMatch = value.match(pureRe);
1750
+ if (pureMatch) {
1751
+ const [, name, defaultValue] = pureMatch;
1752
+ return defaultValue !== void 0 ? `process.env[${JSON.stringify(name)}] ?? ${JSON.stringify(defaultValue)}` : `process.env[${JSON.stringify(name)}]`;
1753
+ }
1754
+ const segments = [];
1755
+ let lastIndex = 0;
1756
+ markerRe.lastIndex = 0;
1757
+ let m2;
1758
+ while ((m2 = markerRe.exec(value)) !== null) {
1759
+ const staticPart = value.slice(lastIndex, m2.index).replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$(?!{)/g, "\\$");
1760
+ const [, name, defaultValue] = m2;
1761
+ const envExpr = defaultValue !== void 0 ? `\${process.env[${JSON.stringify(name)}] ?? ${JSON.stringify(defaultValue)}}` : `\${process.env[${JSON.stringify(name)}]}`;
1762
+ segments.push(staticPart + envExpr);
1763
+ lastIndex = m2.index + m2[0].length;
1764
+ }
1765
+ const trailing = value.slice(lastIndex).replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$(?!{)/g, "\\$");
1766
+ segments.push(trailing);
1767
+ return "`" + segments.join("") + "`";
1768
+ }
1585
1769
  return JSON.stringify(value);
1586
1770
  }
1587
1771
  if (Array.isArray(value)) {
1588
1772
  if (value.length === 0) return "[]";
1589
1773
  const items = value.map(
1590
- (v) => nextSpaces + serializeWithCode(v, indent + 1)
1774
+ (v2) => nextSpaces + serializeWithCode(v2, indent + 1)
1591
1775
  );
1592
1776
  return `[
1593
1777
  ${items.join(",\n")}
@@ -1597,7 +1781,7 @@ ${spaces}]`;
1597
1781
  const entries = Object.entries(value);
1598
1782
  if (entries.length === 0) return "{}";
1599
1783
  const props = entries.map(
1600
- ([k, v]) => `${nextSpaces}${JSON.stringify(k)}: ${serializeWithCode(v, indent + 1)}`
1784
+ ([k2, v2]) => `${nextSpaces}${JSON.stringify(k2)}: ${serializeWithCode(v2, indent + 1)}`
1601
1785
  );
1602
1786
  return `{
1603
1787
  ${props.join(",\n")}
@@ -1637,11 +1821,56 @@ function generatePlatformWrapper(configObject, userCode, buildOptions) {
1637
1821
  config.logger = { ...config.logger, ...context.logger };
1638
1822
  }
1639
1823
 
1640
- return await startFlow(config);
1824
+ // When runner provides external server, strip port from sources
1825
+ // so they don't self-listen (runner owns the port)
1826
+ if (context.externalServer && config.sources) {
1827
+ for (const src of Object.values(config.sources)) {
1828
+ if (src.config && src.config.settings && 'port' in src.config.settings) {
1829
+ delete src.config.settings.port;
1830
+ }
1831
+ }
1832
+ }
1833
+
1834
+ const result = await startFlow(config);
1835
+
1836
+ // Discover httpHandler from initialized sources (duck-typing, no source-type coupling)
1837
+ const httpSource = Object.values(result.collector.sources || {})
1838
+ .find(s => 'httpHandler' in s && typeof s.httpHandler === 'function');
1839
+
1840
+ return { ...result, httpHandler: httpSource ? httpSource.httpHandler : undefined };
1641
1841
  }`;
1642
1842
  }
1643
1843
  }
1644
1844
 
1845
+ // src/commands/bundle/upload.ts
1846
+ import fs9 from "fs-extra";
1847
+ function sanitizeUrl(url) {
1848
+ return url.split("?")[0];
1849
+ }
1850
+ async function uploadBundleToUrl(filePath, url, timeoutMs = 3e4) {
1851
+ const bundleContent = await fs9.readFile(filePath);
1852
+ const doUpload = async (attempt) => {
1853
+ const response = await fetch(url, {
1854
+ method: "PUT",
1855
+ body: bundleContent,
1856
+ headers: {
1857
+ "Content-Type": "application/javascript",
1858
+ "Content-Length": String(bundleContent.length)
1859
+ },
1860
+ signal: AbortSignal.timeout(timeoutMs)
1861
+ });
1862
+ if (response.status >= 500 && attempt === 1) {
1863
+ return doUpload(2);
1864
+ }
1865
+ if (!response.ok) {
1866
+ throw new Error(
1867
+ `Upload failed: ${response.status} ${response.statusText}`
1868
+ );
1869
+ }
1870
+ };
1871
+ await doUpload(1);
1872
+ }
1873
+
1645
1874
  // src/commands/bundle/stats.ts
1646
1875
  function displayStats(stats, logger2) {
1647
1876
  logger2.info("\n\u{1F4CA} Bundle Statistics");
@@ -1666,6 +1895,7 @@ Package Breakdown:`);
1666
1895
  }
1667
1896
 
1668
1897
  // src/core/api-client.ts
1898
+ init_auth();
1669
1899
  import createClient from "openapi-fetch";
1670
1900
  function createApiClient() {
1671
1901
  const token = getToken();
@@ -1693,7 +1923,7 @@ async function bundleCommand(options) {
1693
1923
  const timer = createTimer();
1694
1924
  timer.start();
1695
1925
  const writingToStdout = !options.output;
1696
- const logger2 = createCommandLogger({
1926
+ const logger2 = createCLILogger({
1697
1927
  ...options,
1698
1928
  stderr: writingToStdout
1699
1929
  });
@@ -1739,16 +1969,24 @@ async function bundleCommand(options) {
1739
1969
  if (options.cache !== void 0) {
1740
1970
  buildOptions.cache = options.cache;
1741
1971
  }
1742
- if (options.output) {
1972
+ const outputIsUrl = options.output ? isUrl(options.output) : false;
1973
+ const uploadUrl = outputIsUrl ? options.output : void 0;
1974
+ if (outputIsUrl) {
1975
+ const ext = buildOptions.platform === "browser" ? ".js" : ".mjs";
1976
+ buildOptions.output = getTmpPath(
1977
+ void 0,
1978
+ `url-bundle-${Date.now()}${ext}`
1979
+ );
1980
+ } else if (options.output) {
1743
1981
  buildOptions.output = resolveOutputPath(options.output, buildOptions);
1744
1982
  } else {
1745
1983
  const ext = buildOptions.platform === "browser" ? ".js" : ".mjs";
1746
1984
  buildOptions.output = getTmpPath(void 0, "stdout-bundle" + ext);
1747
1985
  }
1748
1986
  if (isMultiFlow || options.all) {
1749
- logger2.log(`Bundling flow: ${flowName}...`);
1987
+ logger2.info(`Bundling flow: ${flowName}...`);
1750
1988
  } else {
1751
- logger2.log("Bundling...");
1989
+ logger2.info("Bundling...");
1752
1990
  }
1753
1991
  const shouldCollectStats = options.stats || options.json;
1754
1992
  const stats = await bundleCore(
@@ -1758,11 +1996,16 @@ async function bundleCommand(options) {
1758
1996
  shouldCollectStats
1759
1997
  );
1760
1998
  results.push({ flowName, success: true, stats });
1999
+ if (uploadUrl) {
2000
+ await uploadBundleToUrl(buildOptions.output, uploadUrl);
2001
+ logger2.info(`Uploaded to: ${sanitizeUrl(uploadUrl)}`);
2002
+ await fs10.remove(buildOptions.output);
2003
+ }
1761
2004
  if (!options.json && !options.all && options.stats && stats) {
1762
2005
  displayStats(stats, logger2);
1763
2006
  }
1764
2007
  if (writingToStdout && !options.json) {
1765
- const bundleContent = await fs9.readFile(buildOptions.output);
2008
+ const bundleContent = await fs10.readFile(buildOptions.output);
1766
2009
  await writeResult(bundleContent, {});
1767
2010
  }
1768
2011
  if (options.dockerfile && options.output) {
@@ -1782,8 +2025,8 @@ async function bundleCommand(options) {
1782
2025
  }
1783
2026
  }
1784
2027
  const duration = timer.end() / 1e3;
1785
- const successCount = results.filter((r) => r.success).length;
1786
- const failureCount = results.filter((r) => !r.success).length;
2028
+ const successCount = results.filter((r2) => r2.success).length;
2029
+ const failureCount = results.filter((r2) => !r2.success).length;
1787
2030
  if (options.json) {
1788
2031
  const jsonResult = failureCount === 0 ? createSuccessOutput(
1789
2032
  {
@@ -1804,7 +2047,7 @@ async function bundleCommand(options) {
1804
2047
  });
1805
2048
  } else {
1806
2049
  if (options.all) {
1807
- logger2.log(
2050
+ logger2.info(
1808
2051
  `
1809
2052
  Build Summary: ${successCount}/${results.length} succeeded`
1810
2053
  );
@@ -1848,7 +2091,7 @@ async function bundle(configOrPath, options = {}) {
1848
2091
  if (options.cache !== void 0) {
1849
2092
  buildOptions.cache = options.cache;
1850
2093
  }
1851
- const logger2 = createCommandLogger(options);
2094
+ const logger2 = createCLILogger(options);
1852
2095
  return await bundleCore(
1853
2096
  flowConfig,
1854
2097
  buildOptions,
@@ -1858,26 +2101,24 @@ async function bundle(configOrPath, options = {}) {
1858
2101
  }
1859
2102
  async function generateDockerfile(outputDir, platform, logger2, customFile) {
1860
2103
  const destPath = path10.join(outputDir, "Dockerfile");
1861
- if (customFile && await fs9.pathExists(customFile)) {
1862
- await fs9.copy(customFile, destPath);
1863
- logger2.log(`Dockerfile: ${destPath} (copied from ${customFile})`);
2104
+ if (customFile && await fs10.pathExists(customFile)) {
2105
+ await fs10.copy(customFile, destPath);
2106
+ logger2.info(`Dockerfile: ${destPath} (copied from ${customFile})`);
1864
2107
  return;
1865
2108
  }
1866
2109
  const isWeb = platform === "web";
1867
2110
  const bundleFile = isWeb ? "walker.js" : "bundle.mjs";
1868
- const mode = isWeb ? "serve" : "collect";
1869
2111
  const dockerfile = `# Generated by walkeros CLI
1870
2112
  FROM walkeros/flow:latest
1871
2113
 
1872
2114
  COPY ${bundleFile} /app/flow/${bundleFile}
1873
2115
 
1874
- ENV MODE=${mode}
1875
2116
  ENV BUNDLE=/app/flow/${bundleFile}
1876
2117
 
1877
2118
  EXPOSE 8080
1878
2119
  `;
1879
- await fs9.writeFile(destPath, dockerfile);
1880
- logger2.log(`Dockerfile: ${destPath}`);
2120
+ await fs10.writeFile(destPath, dockerfile);
2121
+ logger2.info(`Dockerfile: ${destPath}`);
1881
2122
  }
1882
2123
  async function bundleRemote(options) {
1883
2124
  const client = createApiClient();
@@ -1899,279 +2140,545 @@ async function bundleRemote(options) {
1899
2140
  }
1900
2141
 
1901
2142
  // src/commands/simulate/simulator.ts
1902
- import path11 from "path";
1903
2143
  import fs11 from "fs-extra";
1904
- import { getPlatform as getPlatform3 } from "@walkeros/core";
2144
+ import { Level as Level2 } from "@walkeros/core";
1905
2145
 
1906
- // src/commands/simulate/tracker.ts
1907
- var CallTracker = class {
1908
- calls = /* @__PURE__ */ new Map();
1909
- /**
1910
- * Wrap a function to track its calls
1911
- */
1912
- wrapFunction(name, fn) {
1913
- const self = this;
1914
- const targetFn = fn || (() => {
1915
- });
1916
- return new Proxy(targetFn, {
1917
- apply(_target, thisArg, args) {
1918
- self.logCall(name, args);
1919
- return targetFn.apply(thisArg, args);
1920
- }
1921
- });
1922
- }
1923
- /**
1924
- * Wrap an environment object, tracking specified paths
1925
- *
1926
- * @param env - Environment object (from destination's examples/env.ts)
1927
- * @param paths - Paths to track (e.g., ['gtag:window.gtag', 'gtag:window.dataLayer.push'])
1928
- */
1929
- wrapEnv(env, paths) {
1930
- const wrapped = {};
1931
- for (const [key, value] of Object.entries(env)) {
1932
- if (typeof value === "object" && value !== null) {
1933
- wrapped[key] = Array.isArray(value) ? [...value] : { ...value };
1934
- } else {
1935
- wrapped[key] = value;
1936
- }
1937
- }
1938
- for (const fullPath of paths) {
1939
- const [destKey, ...pathParts] = fullPath.split(":");
1940
- const path15 = pathParts.join(":");
1941
- if (!path15) continue;
1942
- const cleanPath = path15.replace(/^call:/, "");
1943
- const parts = cleanPath.split(".");
1944
- let current = wrapped;
1945
- let source = env;
1946
- for (let i = 0; i < parts.length - 1; i++) {
1947
- const part = parts[i];
1948
- if (!current[part]) {
1949
- current[part] = {};
1950
- }
1951
- current = current[part];
1952
- source = source && typeof source[part] === "object" && source[part] !== null ? source[part] : void 0;
1953
- }
1954
- const finalKey = parts[parts.length - 1];
1955
- const originalFn = source?.[finalKey];
1956
- current[finalKey] = this.wrapFunction(
1957
- `${destKey}:${cleanPath}`,
1958
- typeof originalFn === "function" ? originalFn : void 0
1959
- );
1960
- }
1961
- return wrapped;
1962
- }
1963
- logCall(fullPath, args) {
1964
- const [destKey, ...pathParts] = fullPath.split(":");
1965
- const apiPath = pathParts.join(":");
1966
- if (!this.calls.has(destKey)) {
1967
- this.calls.set(destKey, []);
2146
+ // ../collector/dist/index.mjs
2147
+ import { assign as o } from "@walkeros/core";
2148
+ import { assign as r, createLogger as i } from "@walkeros/core";
2149
+ import { assign as a, clone as c, debounce as u, getId as f, getGrantedConsent as l, isDefined as d, isFunction as g, isObject as m, processEventMapping as p, tryCatchAsync as h, useHooks as w } from "@walkeros/core";
2150
+ import { isArray as y } from "@walkeros/core";
2151
+ import { tryCatch as b, tryCatchAsync as v } from "@walkeros/core";
2152
+ import { getMappingValue as k, tryCatchAsync as C } from "@walkeros/core";
2153
+ import { isObject as O, tryCatchAsync as q, useHooks as j } from "@walkeros/core";
2154
+ import { assign as M, getId as Q, isFunction as V, isString as K } from "@walkeros/core";
2155
+ import { isObject as X } from "@walkeros/core";
2156
+ import { getGrantedConsent as tn, processEventMapping as on, tryCatchAsync as sn, useHooks as rn } from "@walkeros/core";
2157
+ import { useHooks as cn, tryCatchAsync as un } from "@walkeros/core";
2158
+ var e = { Action: "action", Actions: "actions", Config: "config", Consent: "consent", Context: "context", Custom: "custom", Destination: "destination", Elb: "elb", Globals: "globals", Hook: "hook", Init: "init", Link: "link", On: "on", Prefix: "data-elb", Ready: "ready", Run: "run", Session: "session", Shutdown: "shutdown", User: "user", Walker: "walker" };
2159
+ var t = { Commands: e, Utils: { Storage: { Cookie: "cookie", Local: "local", Session: "session" } } };
2160
+ function s(n, e2) {
2161
+ let t2 = false;
2162
+ const s2 = {};
2163
+ return Object.entries(e2).forEach(([n2, e3]) => {
2164
+ const o2 = !!e3;
2165
+ s2[n2] = o2, t2 = t2 || o2;
2166
+ }), n.consent = o(n.consent, s2), { update: s2, runQueue: t2 };
2167
+ }
2168
+ function D(n) {
2169
+ return null != n && false !== n && "object" == typeof n && true === n.__branch;
2170
+ }
2171
+ function A(n) {
2172
+ const e2 = {};
2173
+ for (const [t2, o2] of Object.entries(n)) o2.config?.next ? e2[t2] = { next: o2.config.next } : e2[t2] = {};
2174
+ return e2;
2175
+ }
2176
+ function E(n, e2) {
2177
+ const t2 = n.config || {}, o2 = n[e2];
2178
+ return void 0 !== o2 ? { config: { ...t2, [e2]: o2 }, chainValue: o2 } : { config: t2, chainValue: void 0 };
2179
+ }
2180
+ function P(n, e2 = {}) {
2181
+ if (!n) return [];
2182
+ if (Array.isArray(n)) return n;
2183
+ const t2 = [], o2 = /* @__PURE__ */ new Set();
2184
+ let s2 = n;
2185
+ for (; s2 && e2[s2] && !o2.has(s2); ) {
2186
+ o2.add(s2), t2.push(s2);
2187
+ const n2 = e2[s2].next;
2188
+ if (Array.isArray(n2)) {
2189
+ t2.push(...n2);
2190
+ break;
1968
2191
  }
1969
- this.calls.get(destKey).push({
1970
- type: "call",
1971
- path: apiPath,
1972
- args,
1973
- timestamp: Date.now()
1974
- });
1975
- }
1976
- getCalls() {
1977
- return Object.fromEntries(this.calls);
2192
+ s2 = n2;
1978
2193
  }
1979
- reset() {
1980
- this.calls.clear();
2194
+ return t2;
2195
+ }
2196
+ async function x(n, e2, t2) {
2197
+ if (e2.init && !e2.config.init) {
2198
+ const o2 = e2.type || "unknown", s2 = n.logger.scope(`transformer:${o2}`), r2 = { collector: n, logger: s2, id: t2, config: e2.config, env: R(e2.config.env) };
2199
+ s2.debug("init");
2200
+ const i2 = await j(e2.init, "TransformerInit", n.hooks)(r2);
2201
+ if (false === i2) return false;
2202
+ e2.config = { ...i2 || e2.config, init: true }, s2.debug("init done");
1981
2203
  }
1982
- };
1983
-
1984
- // src/commands/simulate/jsdom-executor.ts
1985
- import { JSDOM, VirtualConsole } from "jsdom";
1986
- import fs10 from "fs-extra";
1987
- function buildSandboxFromEnvs(envs, destinations, tracker) {
1988
- const baseBrowserMocks = {
1989
- Image: class MockImage {
1990
- src = "";
1991
- onload = (() => {
1992
- });
1993
- onerror = (() => {
1994
- });
1995
- },
1996
- fetch: async () => ({ ok: true, json: async () => ({}) }),
1997
- location: { href: "http://localhost" },
1998
- navigator: { userAgent: "Mozilla/5.0 (walkerOS Simulation)" }
1999
- };
2000
- const sandbox = {
2001
- window: { ...baseBrowserMocks },
2002
- document: {}
2003
- };
2004
- for (const [destKey, destConfig] of Object.entries(destinations)) {
2005
- const destEnv = envs[destKey];
2006
- if (!destEnv?.push) continue;
2007
- const mockEnv = destEnv.push;
2008
- const trackPaths = destEnv.simulation || [];
2009
- const trackedEnv = tracker.wrapEnv(
2010
- mockEnv,
2011
- trackPaths.map((p) => `${destKey}:${p}`)
2012
- );
2013
- if (trackedEnv.window && typeof trackedEnv.window === "object") {
2014
- Object.assign(sandbox.window, trackedEnv.window);
2204
+ return true;
2205
+ }
2206
+ async function S(n, e2, t2, o2, s2, r2) {
2207
+ const i2 = e2.type || "unknown", a2 = n.logger.scope(`transformer:${i2}`), c2 = { collector: n, logger: a2, id: t2, ingest: s2, config: e2.config, env: { ...R(e2.config.env), ...r2 ? { respond: r2 } : {} } };
2208
+ a2.debug("push", { event: o2.name });
2209
+ const u2 = await j(e2.push, "TransformerPush", n.hooks)(o2, c2);
2210
+ return a2.debug("push done"), u2;
2211
+ }
2212
+ async function $(n, e2, t2, o2, s2, r2) {
2213
+ let i2 = o2;
2214
+ for (const o3 of t2) {
2215
+ const t3 = e2[o3];
2216
+ if (!t3) {
2217
+ n.logger.warn(`Transformer not found: ${o3}`);
2218
+ continue;
2015
2219
  }
2016
- if (trackedEnv.document && typeof trackedEnv.document === "object") {
2017
- Object.assign(sandbox.document, trackedEnv.document);
2220
+ if (!await q(x)(n, t3, o3)) return n.logger.error(`Transformer init failed: ${o3}`), null;
2221
+ const a2 = await q(S, (e3) => (n.logger.scope(`transformer:${t3.type || "unknown"}`).error("Push failed", { error: e3 }), false))(n, t3, o3, i2, s2, r2);
2222
+ if (false === a2) return null;
2223
+ if (D(a2)) {
2224
+ const t4 = P(a2.next, A(e2));
2225
+ return t4.length > 0 ? $(n, e2, t4, a2.event, s2, r2) : (n.logger.warn(`Branch target not found: ${JSON.stringify(a2.next)}`), null);
2226
+ }
2227
+ void 0 !== a2 && (i2 = a2);
2228
+ }
2229
+ return i2;
2230
+ }
2231
+ function R(n) {
2232
+ return n && O(n) ? n : {};
2233
+ }
2234
+ async function T(n, e2, t2) {
2235
+ const { code: o2, config: s2 = {}, env: r2 = {}, primary: i2, next: a2 } = t2;
2236
+ let c2, u2;
2237
+ const f2 = P(a2, A(n.transformers)), l2 = n.logger.scope("source").scope(e2), d2 = { push: (t3, o3 = {}) => n.push(t3, { ...o3, id: e2, ingest: c2, respond: u2, mapping: s2, preChain: f2 }), command: n.command, sources: n.sources, elb: n.sources.elb.push, logger: l2, ...r2 }, g2 = { collector: n, logger: l2, id: e2, config: s2, env: d2, setIngest: async (e3) => {
2238
+ c2 = s2.ingest ? await k(e3, s2.ingest, { collector: n }) : void 0;
2239
+ }, setRespond: (n2) => {
2240
+ u2 = n2;
2241
+ } }, m2 = await C(o2)(g2);
2242
+ if (!m2) return;
2243
+ const p2 = m2.type || "unknown", h2 = n.logger.scope(p2).scope(e2);
2244
+ return d2.logger = h2, i2 && (m2.config = { ...m2.config, primary: i2 }), m2;
2245
+ }
2246
+ async function I(n, e2 = {}) {
2247
+ const t2 = {};
2248
+ for (const [o2, s2] of Object.entries(e2)) {
2249
+ const { config: e3 = {} } = s2;
2250
+ if (e3.require && e3.require.length > 0) {
2251
+ n.pending.sources[o2] = s2;
2252
+ continue;
2018
2253
  }
2254
+ const r2 = await T(n, o2, s2);
2255
+ r2 && (t2[o2] = r2);
2019
2256
  }
2020
- return sandbox;
2257
+ return t2;
2021
2258
  }
2022
- function waitForWindowProperty(window, prop, timeout = 5e3) {
2023
- return new Promise((resolve3, reject) => {
2024
- const start = Date.now();
2025
- const check = () => {
2026
- if (window[prop] !== void 0) {
2027
- resolve3();
2028
- } else if (Date.now() - start > timeout) {
2029
- reject(
2030
- new Error(
2031
- `Timeout waiting for window.${prop}. IIFE may have failed to execute or assign to window.`
2032
- )
2033
- );
2034
- } else {
2035
- setImmediate(check);
2036
- }
2037
- };
2038
- check();
2039
- });
2259
+ async function B(n, e2, t2) {
2260
+ const o2 = n.on, s2 = o2[e2] || [], r2 = y(t2) ? t2 : [t2];
2261
+ r2.forEach((n2) => {
2262
+ s2.push(n2);
2263
+ }), o2[e2] = s2, await H(n, e2, r2);
2040
2264
  }
2041
- async function executeInJSDOM(bundlePath, destinations, event, tracker, envs, timeout = 1e4) {
2042
- const start = Date.now();
2043
- const virtualConsole = new VirtualConsole();
2044
- const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>", {
2045
- url: "http://localhost",
2046
- runScripts: "dangerously",
2047
- // Allow script execution
2048
- resources: "usable",
2049
- virtualConsole
2050
- });
2051
- const { window } = dom;
2052
- const sandbox = buildSandboxFromEnvs(envs, destinations, tracker);
2053
- Object.assign(window, sandbox.window);
2054
- Object.assign(window.document, sandbox.document);
2055
- const bundleCode = await fs10.readFile(bundlePath, "utf8");
2056
- try {
2057
- window.eval(bundleCode);
2058
- } catch (error) {
2059
- throw new Error(`Bundle execution failed: ${getErrorMessage(error)}`);
2060
- }
2061
- try {
2062
- await waitForWindowProperty(
2063
- window,
2064
- "collector",
2065
- timeout
2066
- );
2067
- await waitForWindowProperty(
2068
- window,
2069
- "elb",
2070
- timeout
2071
- );
2072
- } catch (error) {
2073
- throw new Error(
2074
- `Window property assignment failed: ${getErrorMessage(error)}`
2075
- );
2076
- }
2077
- const { collector, elb } = window;
2078
- let elbResult;
2079
- try {
2080
- elbResult = await elb(event.name, event.data);
2081
- } catch (error) {
2082
- throw new Error(`Event execution failed: ${getErrorMessage(error)}`);
2083
- }
2084
- return {
2085
- collector,
2086
- elb,
2087
- elbResult,
2088
- usage: tracker.getCalls(),
2089
- duration: Date.now() - start
2090
- };
2265
+ function G(n, e2, t2, o2, s2) {
2266
+ if (!e2.on) return;
2267
+ const r2 = e2.type || "unknown", i2 = n.logger.scope(r2).scope("on").scope(o2), a2 = { collector: n, logger: i2, id: t2, config: e2.config, data: s2, env: L(e2.env, e2.config.env) };
2268
+ b(e2.on)(o2, a2);
2091
2269
  }
2092
-
2093
- // src/commands/simulate/node-executor.ts
2094
- import { pathToFileURL } from "url";
2095
- function buildGlobalMocksFromEnvs(envs, destinations, tracker) {
2096
- const globalMocks = {};
2097
- for (const [destKey] of Object.entries(destinations)) {
2098
- const destEnv = envs[destKey];
2099
- if (!destEnv?.push) continue;
2100
- const mockEnv = destEnv.push;
2101
- const trackPaths = destEnv.simulation || [];
2102
- const trackedEnv = tracker.wrapEnv(
2103
- mockEnv,
2104
- trackPaths.map((p) => `${destKey}:${p}`)
2105
- );
2106
- Object.assign(globalMocks, trackedEnv);
2107
- }
2108
- return globalMocks;
2109
- }
2110
- function injectGlobalMocks(mocks) {
2111
- const originalValues = {};
2112
- for (const [key, value] of Object.entries(mocks)) {
2113
- originalValues[key] = globalThis[key];
2114
- globalThis[key] = value;
2270
+ async function H(n, e2, o2, s2) {
2271
+ let r2, i2 = o2 || [];
2272
+ switch (o2 || (i2 = n.on[e2] || []), e2) {
2273
+ case t.Commands.Consent:
2274
+ r2 = s2 || n.consent;
2275
+ break;
2276
+ case t.Commands.Session:
2277
+ r2 = n.session;
2278
+ break;
2279
+ case t.Commands.User:
2280
+ r2 = s2 || n.user;
2281
+ break;
2282
+ case t.Commands.Custom:
2283
+ r2 = s2 || n.custom;
2284
+ break;
2285
+ case t.Commands.Globals:
2286
+ r2 = s2 || n.globals;
2287
+ break;
2288
+ case t.Commands.Config:
2289
+ r2 = s2 || n.config;
2290
+ break;
2291
+ case t.Commands.Ready:
2292
+ case t.Commands.Run:
2293
+ default:
2294
+ r2 = void 0;
2295
+ }
2296
+ let a2 = false;
2297
+ for (const t2 of Object.values(n.sources)) if (t2.on) {
2298
+ false === await v(t2.on)(e2, r2) && (a2 = true);
2299
+ }
2300
+ if (Object.entries(n.destinations).forEach(([t2, o3]) => {
2301
+ if (o3.on) {
2302
+ if (!o3.config.init) return o3.queueOn = o3.queueOn || [], void o3.queueOn.push({ type: e2, data: r2 });
2303
+ G(n, o3, t2, e2, r2);
2304
+ }
2305
+ }), (Object.keys(n.pending.sources).length > 0 || Object.keys(n.pending.destinations).length > 0) && await (async function(n2, e3) {
2306
+ for (const [t2, o3] of Object.entries(n2.pending.sources)) {
2307
+ if (!n2.pending.sources[t2] || n2.sources[t2]) continue;
2308
+ const s3 = o3.config?.require;
2309
+ if (!s3) continue;
2310
+ const r3 = s3.indexOf(e3);
2311
+ if (-1 === r3) continue;
2312
+ if (s3.splice(r3, 1), s3.length > 0) continue;
2313
+ delete n2.pending.sources[t2];
2314
+ const i3 = await T(n2, t2, o3);
2315
+ i3 && (n2.sources[t2] = i3);
2316
+ }
2317
+ for (const [t2, o3] of Object.entries(n2.pending.destinations)) {
2318
+ if (!n2.pending.destinations[t2] || n2.destinations[t2]) continue;
2319
+ const s3 = o3.config?.require;
2320
+ if (!s3) continue;
2321
+ const r3 = s3.indexOf(e3);
2322
+ if (-1 === r3) continue;
2323
+ if (s3.splice(r3, 1), s3.length > 0) continue;
2324
+ delete n2.pending.destinations[t2];
2325
+ const i3 = W(o3);
2326
+ false !== i3.config.queue && (i3.queuePush = [...n2.queue]), n2.destinations[t2] = i3;
2327
+ }
2328
+ })(n, e2), !i2.length) return !a2;
2329
+ switch (e2) {
2330
+ case t.Commands.Consent:
2331
+ !(function(n2, e3, t2) {
2332
+ const o3 = t2 || n2.consent;
2333
+ e3.forEach((e4) => {
2334
+ Object.keys(o3).filter((n3) => n3 in e4).forEach((t3) => {
2335
+ b(e4[t3])(n2, o3);
2336
+ });
2337
+ });
2338
+ })(n, i2, s2);
2339
+ break;
2340
+ case t.Commands.Ready:
2341
+ case t.Commands.Run:
2342
+ !(function(n2, e3) {
2343
+ n2.allowed && e3.forEach((e4) => {
2344
+ b(e4)(n2);
2345
+ });
2346
+ })(n, i2);
2347
+ break;
2348
+ case t.Commands.Session:
2349
+ !(function(n2, e3) {
2350
+ if (!n2.session) return;
2351
+ e3.forEach((e4) => {
2352
+ b(e4)(n2, n2.session);
2353
+ });
2354
+ })(n, i2);
2355
+ break;
2356
+ default:
2357
+ i2.forEach((e3) => {
2358
+ "function" == typeof e3 && b(e3)(n, r2);
2359
+ });
2115
2360
  }
2116
- return () => {
2117
- for (const [key, value] of Object.entries(originalValues)) {
2118
- if (value === void 0) {
2119
- delete globalThis[key];
2120
- } else {
2121
- globalThis[key] = value;
2361
+ return !a2;
2362
+ }
2363
+ async function U(n, e2, t2) {
2364
+ const { code: o2, config: s2 = {}, env: r2 = {}, before: i2 } = e2;
2365
+ if (!g(o2.push)) return N({ ok: false, failed: { invalid: { type: "invalid", error: "Destination code must have a push method" } } });
2366
+ const a2 = t2 || s2 || { init: false }, c2 = i2 ? { ...a2, before: i2 } : a2, u2 = { ...o2, config: c2, env: L(o2.env, r2) };
2367
+ let l2 = u2.config.id;
2368
+ if (!l2) do {
2369
+ l2 = f(4);
2370
+ } while (n.destinations[l2]);
2371
+ return n.destinations[l2] = u2, false !== u2.config.queue && (u2.queuePush = [...n.queue]), _(n, void 0, {}, { [l2]: u2 });
2372
+ }
2373
+ async function _(n, e2, t2 = {}, o2) {
2374
+ const { allowed: s2, consent: r2, globals: i2, user: u2 } = n;
2375
+ if (!s2) return N({ ok: false });
2376
+ e2 && (n.queue.push(e2), n.status.in++), o2 || (o2 = n.destinations);
2377
+ const f2 = await Promise.all(Object.entries(o2 || {}).map(async ([o3, s3]) => {
2378
+ let f3 = (s3.queuePush || []).map((n2) => ({ ...n2, consent: r2 }));
2379
+ if (s3.queuePush = [], e2) {
2380
+ const n2 = c(e2);
2381
+ f3.push(n2);
2382
+ }
2383
+ if (!f3.length && !s3.queueOn?.length) return { id: o3, destination: s3, skipped: true };
2384
+ if (!f3.length && s3.queueOn?.length) {
2385
+ const e3 = await h(F)(n, s3, o3);
2386
+ return { id: o3, destination: s3, skipped: !e3 };
2387
+ }
2388
+ const d3 = [], g3 = f3.filter((n2) => {
2389
+ const e3 = l(s3.config.consent, r2, n2.consent);
2390
+ return !e3 || (n2.consent = e3, d3.push(n2), false);
2391
+ });
2392
+ if (s3.queuePush.push(...g3), !d3.length) return { id: o3, destination: s3, queue: f3 };
2393
+ if (!await h(F)(n, s3, o3)) return { id: o3, destination: s3, queue: f3 };
2394
+ let m3, p2;
2395
+ s3.dlq || (s3.dlq = []);
2396
+ const w2 = (function(n2, e3) {
2397
+ const t3 = n2.config.before;
2398
+ return t3 ? P(t3, A(e3)) : [];
2399
+ })(s3, n.transformers);
2400
+ let y2 = 0;
2401
+ return await Promise.all(d3.map(async (e3) => {
2402
+ e3.globals = a(i2, e3.globals), e3.user = a(u2, e3.user);
2403
+ let r3 = e3;
2404
+ if (w2.length > 0 && n.transformers && Object.keys(n.transformers).length > 0) {
2405
+ const o4 = await $(n, n.transformers, w2, e3, t2.ingest, t2.respond);
2406
+ if (null === o4) return e3;
2407
+ r3 = o4;
2122
2408
  }
2409
+ const c2 = Date.now(), f4 = await h(J, (e4) => {
2410
+ const t3 = s3.type || "unknown";
2411
+ n.logger.scope(t3).error("Push failed", { error: e4, event: r3.name }), m3 = e4, s3.dlq.push([r3, e4]);
2412
+ })(n, s3, o3, r3, t2.ingest, t2.respond);
2413
+ return y2 += Date.now() - c2, void 0 !== f4 && (p2 = f4), e3;
2414
+ })), { id: o3, destination: s3, error: m3, response: p2, totalDuration: y2 };
2415
+ })), d2 = {}, g2 = {}, m2 = {};
2416
+ for (const e3 of f2) {
2417
+ if (e3.skipped) continue;
2418
+ const t3 = e3.destination, o3 = { type: t3.type || "unknown", data: e3.response };
2419
+ n.status.destinations[e3.id] || (n.status.destinations[e3.id] = { count: 0, failed: 0, duration: 0 });
2420
+ const s3 = n.status.destinations[e3.id], r3 = Date.now();
2421
+ e3.error ? (o3.error = e3.error, m2[e3.id] = o3, s3.failed++, s3.lastAt = r3, s3.duration += e3.totalDuration || 0, n.status.failed++) : e3.queue && e3.queue.length ? (t3.queuePush = (t3.queuePush || []).concat(e3.queue), g2[e3.id] = o3) : (d2[e3.id] = o3, s3.count++, s3.lastAt = r3, s3.duration += e3.totalDuration || 0, n.status.out++);
2422
+ }
2423
+ return N({ event: e2, ...Object.keys(d2).length && { done: d2 }, ...Object.keys(g2).length && { queued: g2 }, ...Object.keys(m2).length && { failed: m2 } });
2424
+ }
2425
+ async function F(n, e2, t2) {
2426
+ if (e2.init && !e2.config.init) {
2427
+ const o2 = e2.type || "unknown", s2 = n.logger.scope(o2), r2 = { collector: n, logger: s2, id: t2, config: e2.config, env: L(e2.env, e2.config.env) };
2428
+ s2.debug("init");
2429
+ const i2 = await w(e2.init, "DestinationInit", n.hooks)(r2);
2430
+ if (false === i2) return i2;
2431
+ if (e2.config = { ...i2 || e2.config, init: true }, e2.queueOn?.length) {
2432
+ const o3 = e2.queueOn;
2433
+ e2.queueOn = [];
2434
+ for (const { type: s3, data: r3 } of o3) G(n, e2, t2, s3, r3);
2435
+ }
2436
+ s2.debug("init done");
2437
+ }
2438
+ return true;
2439
+ }
2440
+ async function J(n, e2, t2, o2, s2, r2) {
2441
+ const { config: i2 } = e2, a2 = await p(o2, i2, n);
2442
+ if (a2.ignore) return false;
2443
+ const c2 = e2.type || "unknown", f2 = n.logger.scope(c2), l2 = { collector: n, logger: f2, id: t2, config: i2, data: a2.data, rule: a2.mapping, ingest: s2, env: { ...L(e2.env, i2.env), ...r2 ? { respond: r2 } : {} } }, g2 = a2.mapping, m2 = a2.mappingKey || "* *";
2444
+ if (!g2?.batch || !e2.pushBatch) {
2445
+ f2.debug("push", { event: a2.event.name });
2446
+ const t3 = await w(e2.push, "DestinationPush", n.hooks)(a2.event, l2);
2447
+ return f2.debug("push done"), t3;
2448
+ }
2449
+ {
2450
+ if (e2.batches = e2.batches || {}, !e2.batches[m2]) {
2451
+ const o4 = { key: m2, events: [], data: [] };
2452
+ e2.batches[m2] = { batched: o4, batchFn: u(() => {
2453
+ const o5 = e2.batches[m2].batched, a3 = { collector: n, logger: f2, id: t2, config: i2, data: void 0, rule: g2, ingest: s2, env: { ...L(e2.env, i2.env), ...r2 ? { respond: r2 } : {} } };
2454
+ f2.debug("push batch", { events: o5.events.length }), w(e2.pushBatch, "DestinationPushBatch", n.hooks)(o5, a3), f2.debug("push batch done"), o5.events = [], o5.data = [];
2455
+ }, g2.batch) };
2456
+ }
2457
+ const o3 = e2.batches[m2];
2458
+ o3.batched.events.push(a2.event), d(a2.data) && o3.batched.data.push(a2.data), o3.batchFn();
2459
+ }
2460
+ return true;
2461
+ }
2462
+ function N(n) {
2463
+ return { ok: !n?.failed, ...n };
2464
+ }
2465
+ function W(n) {
2466
+ const { code: e2, config: t2 = {}, env: o2 = {} } = n, { config: s2 } = E(n, "before"), r2 = { ...e2.config, ...t2, ...s2 }, i2 = L(e2.env, o2);
2467
+ return { ...e2, config: r2, env: i2 };
2468
+ }
2469
+ async function z(n, e2 = {}) {
2470
+ const t2 = {};
2471
+ for (const [o2, s2] of Object.entries(e2)) s2.config?.require?.length ? n.pending.destinations[o2] = s2 : t2[o2] = W(s2);
2472
+ return t2;
2473
+ }
2474
+ function L(n, e2) {
2475
+ return n || e2 ? e2 ? n && m(n) && m(e2) ? { ...n, ...e2 } : e2 : n : {};
2476
+ }
2477
+ async function Y(n, e2, t2) {
2478
+ const o2 = Object.entries(n).map(async ([n2, o3]) => {
2479
+ const s2 = o3.destroy;
2480
+ if (!s2) return;
2481
+ const r2 = o3.type || "unknown", i2 = t2.scope(r2), a2 = { id: n2, config: o3.config, env: o3.env ?? {}, logger: i2 };
2482
+ try {
2483
+ await Promise.race([s2(a2), new Promise((t3, o4) => setTimeout(() => o4(new Error(`${e2} '${n2}' destroy timed out`)), 5e3))]);
2484
+ } catch (t3) {
2485
+ i2.error(`${e2} '${n2}' destroy failed: ${t3}`);
2123
2486
  }
2124
- };
2487
+ });
2488
+ await Promise.allSettled(o2);
2125
2489
  }
2126
- async function executeInNode(bundlePath, destinations, event, tracker, envs, timeout = 3e4, context = {}) {
2127
- const start = Date.now();
2128
- const globalMocks = buildGlobalMocksFromEnvs(envs, destinations, tracker);
2129
- const cleanupMocks = injectGlobalMocks(globalMocks);
2130
- try {
2131
- const executeWithTimeout = async () => {
2132
- const importUrl = process.env.JEST_WORKER_ID ? bundlePath : `${pathToFileURL(bundlePath).href}?t=${Date.now()}`;
2133
- const module = await import(importUrl);
2134
- if (!module.default || typeof module.default !== "function") {
2135
- throw new Error("Bundle does not export default factory function");
2136
- }
2137
- const result = await module.default(context);
2138
- if (!result || !result.collector || typeof result.collector.push !== "function") {
2139
- throw new Error(
2140
- "Factory function did not return valid result with collector"
2141
- );
2490
+ async function Z(n, e2, o2, r2) {
2491
+ let i2, a2, c2 = false, u2 = false;
2492
+ switch (e2) {
2493
+ case t.Commands.Config:
2494
+ X(o2) && (M(n.config, o2, { shallow: false }), a2 = o2, c2 = true);
2495
+ break;
2496
+ case t.Commands.Consent:
2497
+ if (X(o2)) {
2498
+ const { update: e3, runQueue: t2 } = s(n, o2);
2499
+ a2 = e3, c2 = true, u2 = t2;
2142
2500
  }
2143
- const { collector, elb } = result;
2144
- let elbResult;
2145
- try {
2146
- elbResult = await collector.push({
2147
- name: event.name,
2148
- data: event.data
2149
- });
2150
- } catch (error) {
2151
- throw new Error(`Event execution failed: ${getErrorMessage(error)}`);
2501
+ break;
2502
+ case t.Commands.Custom:
2503
+ X(o2) && (n.custom = M(n.custom, o2), a2 = o2, c2 = true);
2504
+ break;
2505
+ case t.Commands.Destination:
2506
+ X(o2) && ("code" in o2 && X(o2.code) ? i2 = await U(n, o2, r2) : V(o2.push) && (i2 = await U(n, { code: o2 }, r2)));
2507
+ break;
2508
+ case t.Commands.Globals:
2509
+ X(o2) && (n.globals = M(n.globals, o2), a2 = o2, c2 = true);
2510
+ break;
2511
+ case t.Commands.On:
2512
+ K(o2) && await B(n, o2, r2);
2513
+ break;
2514
+ case t.Commands.Ready:
2515
+ c2 = true;
2516
+ break;
2517
+ case t.Commands.Run:
2518
+ i2 = await en(n, o2), c2 = true;
2519
+ break;
2520
+ case t.Commands.Session:
2521
+ c2 = true;
2522
+ break;
2523
+ case t.Commands.Shutdown:
2524
+ await (async function(n2) {
2525
+ const e3 = n2.logger;
2526
+ await Y(n2.sources, "source", e3), await Y(n2.destinations, "destination", e3), await Y(n2.transformers, "transformer", e3);
2527
+ })(n);
2528
+ break;
2529
+ case t.Commands.User:
2530
+ X(o2) && (M(n.user, o2, { shallow: false }), a2 = o2, c2 = true);
2531
+ }
2532
+ return c2 && await H(n, e2, void 0, a2), u2 && (i2 = await _(n)), i2 || N({ ok: true });
2533
+ }
2534
+ function nn(n, e2) {
2535
+ if (!e2.name) throw new Error("Event name is required");
2536
+ const [t2, o2] = e2.name.split(" ");
2537
+ if (!t2 || !o2) throw new Error("Event name is invalid");
2538
+ ++n.count;
2539
+ const { timestamp: s2 = Date.now(), group: r2 = n.group, count: i2 = n.count } = e2, { name: a2 = `${t2} ${o2}`, data: c2 = {}, context: u2 = {}, globals: f2 = n.globals, custom: l2 = {}, user: d2 = n.user, nested: g2 = [], consent: m2 = n.consent, id: p2 = `${s2}-${r2}-${i2}`, trigger: h2 = "", entity: w2 = t2, action: y2 = o2, timing: b2 = 0, version: v2 = { source: n.version, tagging: n.config.tagging || 0 }, source: k2 = { type: "collector", id: "", previous_id: "" } } = e2;
2540
+ return { name: a2, data: c2, context: u2, globals: f2, custom: l2, user: d2, nested: g2, consent: m2, id: p2, trigger: h2, entity: w2, action: y2, timestamp: s2, timing: b2, group: r2, count: i2, version: v2, source: k2 };
2541
+ }
2542
+ async function en(n, e2) {
2543
+ n.allowed = true, n.count = 0, n.group = Q(), n.timing = Date.now(), e2 && (e2.consent && (n.consent = M(n.consent, e2.consent)), e2.user && (n.user = M(n.user, e2.user)), e2.globals && (n.globals = M(n.config.globalsStatic || {}, e2.globals)), e2.custom && (n.custom = M(n.custom, e2.custom))), Object.values(n.destinations).forEach((n2) => {
2544
+ n2.queuePush = [];
2545
+ }), n.queue = [], n.round++;
2546
+ return await _(n);
2547
+ }
2548
+ function an(n, e2) {
2549
+ return rn(async (t2, o2 = {}) => await sn(async () => {
2550
+ const s2 = Date.now(), { id: r2, ingest: i2, respond: a2, mapping: c2, preChain: u2 } = o2;
2551
+ let f2 = t2;
2552
+ const l2 = i2 ? Object.freeze(i2) : void 0;
2553
+ if (c2) {
2554
+ const e3 = await on(f2, c2, n);
2555
+ if (e3.ignore) return N({ ok: true });
2556
+ if (c2.consent) {
2557
+ if (!tn(c2.consent, n.consent, e3.event.consent)) return N({ ok: true });
2152
2558
  }
2153
- return {
2154
- collector,
2155
- elb,
2156
- elbResult,
2157
- usage: tracker.getCalls(),
2158
- duration: Date.now() - start
2159
- };
2160
- };
2161
- const timeoutPromise = new Promise((_, reject) => {
2162
- setTimeout(
2163
- () => reject(new Error(`Server simulation timeout after ${timeout}ms`)),
2164
- timeout
2165
- );
2559
+ f2 = e3.event;
2560
+ }
2561
+ if (u2?.length && n.transformers && Object.keys(n.transformers).length > 0) {
2562
+ const e3 = await $(n, n.transformers, u2, f2, l2, a2);
2563
+ if (null === e3) return N({ ok: true });
2564
+ f2 = e3;
2565
+ }
2566
+ const d2 = e2(f2), g2 = nn(n, d2), m2 = await _(n, g2, { id: r2, ingest: l2, respond: a2 });
2567
+ if (r2) {
2568
+ n.status.sources[r2] || (n.status.sources[r2] = { count: 0, duration: 0 });
2569
+ const e3 = n.status.sources[r2];
2570
+ e3.count++, e3.lastAt = Date.now(), e3.duration += Date.now() - s2;
2571
+ }
2572
+ return m2;
2573
+ }, () => N({ ok: false }))(), "Push", n.hooks);
2574
+ }
2575
+ async function fn(n) {
2576
+ const e2 = r({ globalsStatic: {}, sessionStatic: {}, tagging: 0, run: true }, n, { merge: false, extend: false }), t2 = { level: n.logger?.level, handler: n.logger?.handler }, o2 = i(t2), s2 = { ...e2.globalsStatic, ...n.globals }, a2 = { allowed: false, config: e2, consent: n.consent || {}, count: 0, custom: n.custom || {}, destinations: {}, transformers: {}, globals: s2, group: "", hooks: {}, logger: o2, on: {}, queue: [], round: 0, session: void 0, status: { startedAt: Date.now(), in: 0, out: 0, failed: 0, sources: {}, destinations: {} }, timing: Date.now(), user: n.user || {}, version: "2.0.1", sources: {}, pending: { sources: {}, destinations: {} }, push: void 0, command: void 0 };
2577
+ return a2.push = an(a2, (n2) => ({ timing: Math.round((Date.now() - a2.timing) / 10) / 100, source: { type: "collector", id: "", previous_id: "" }, ...n2 })), a2.command = (function(n2, e3) {
2578
+ return cn(async (t3, o3, s3) => await un(async () => await e3(n2, t3, o3, s3), () => N({ ok: false }))(), "Command", n2.hooks);
2579
+ })(a2, Z), a2.destinations = await z(a2, n.destinations || {}), a2.transformers = await (async function(n2, e3 = {}) {
2580
+ const t3 = {};
2581
+ for (const [o3, s3] of Object.entries(e3)) {
2582
+ const { code: e4, env: r2 = {} } = s3, { config: i2 } = E(s3, "next"), a3 = n2.logger.scope("transformer").scope(o3), c2 = { collector: n2, logger: a3, id: o3, config: i2, env: r2 }, u2 = await e4(c2);
2583
+ t3[o3] = u2;
2584
+ }
2585
+ return t3;
2586
+ })(a2, n.transformers || {}), a2;
2587
+ }
2588
+ async function ln(n) {
2589
+ n = n || {};
2590
+ const e2 = await fn(n), t2 = (o2 = e2, { type: "elb", config: {}, push: async (n2, e3, t3, s3, r3, i3) => {
2591
+ if ("string" == typeof n2 && n2.startsWith("walker ")) {
2592
+ const s4 = n2.replace("walker ", "");
2593
+ return o2.command(s4, e3, t3);
2594
+ }
2595
+ let a3;
2596
+ if ("string" == typeof n2) a3 = { name: n2 }, e3 && "object" == typeof e3 && !Array.isArray(e3) && (a3.data = e3);
2597
+ else {
2598
+ if (!n2 || "object" != typeof n2) return N({ ok: false });
2599
+ a3 = n2, e3 && "object" == typeof e3 && !Array.isArray(e3) && (a3.data = { ...a3.data || {}, ...e3 });
2600
+ }
2601
+ return s3 && "object" == typeof s3 && (a3.context = s3), r3 && Array.isArray(r3) && (a3.nested = r3), i3 && "object" == typeof i3 && (a3.custom = i3), o2.push(a3);
2602
+ } });
2603
+ var o2;
2604
+ e2.sources.elb = t2;
2605
+ const s2 = await I(e2, n.sources || {});
2606
+ Object.assign(e2.sources, s2);
2607
+ const { consent: r2, user: i2, globals: a2, custom: c2 } = n;
2608
+ r2 && await e2.command("consent", r2), i2 && await e2.command("user", i2), a2 && Object.assign(e2.globals, a2), c2 && Object.assign(e2.custom, c2), e2.config.run && await e2.command("run");
2609
+ let u2 = t2.push;
2610
+ const f2 = Object.values(e2.sources).filter((n2) => "elb" !== n2.type), l2 = f2.find((n2) => n2.config.primary);
2611
+ return l2 ? u2 = l2.push : f2.length > 0 && (u2 = f2[0].push), { collector: e2, elb: u2 };
2612
+ }
2613
+ function dn(n) {
2614
+ if (null === n || "object" != typeof n) return n;
2615
+ if (Array.isArray(n)) return n.map(dn);
2616
+ const e2 = {};
2617
+ for (const [t2, o2] of Object.entries(n)) e2[t2] = "function" == typeof o2 ? o2 : dn(o2);
2618
+ return e2;
2619
+ }
2620
+ function gn(n) {
2621
+ const e2 = [], { simulation: t2, ...o2 } = n, s2 = dn(o2);
2622
+ for (const n2 of t2) {
2623
+ const t3 = n2.startsWith("call:") ? n2.slice(5) : n2, o3 = t3.split(".");
2624
+ let r2 = s2;
2625
+ for (let n3 = 0; n3 < o3.length - 1 && null != r2[o3[n3]]; n3++) r2 = r2[o3[n3]];
2626
+ const i2 = o3[o3.length - 1];
2627
+ if (null == r2 || !(i2 in r2)) continue;
2628
+ const a2 = r2[i2];
2629
+ "function" == typeof a2 && (r2[i2] = function(...n3) {
2630
+ return e2.push({ fn: t3, args: n3, ts: Date.now() }), a2.apply(this, n3);
2166
2631
  });
2167
- return await Promise.race([executeWithTimeout(), timeoutPromise]);
2168
- } catch (error) {
2169
- throw new Error(`Node execution failed: ${getErrorMessage(error)}`);
2170
- } finally {
2171
- cleanupMocks();
2632
+ }
2633
+ return { wrappedEnv: s2, calls: e2 };
2634
+ }
2635
+ async function mn(n) {
2636
+ const e2 = Date.now();
2637
+ try {
2638
+ switch (n.step) {
2639
+ case "transformer":
2640
+ return await (async function(n2, e3) {
2641
+ const { code: t2, config: o2 = {}, event: s2 } = n2, { collector: r2 } = await ln({ transformers: { sim: { code: t2, config: o2 } } }), i2 = r2.transformers?.sim;
2642
+ if (!i2) throw new Error("Transformer failed to initialize");
2643
+ const a2 = await i2.push(s2, { collector: r2, logger: r2.logger.scope("transformer").scope("sim"), id: "sim", config: i2.config, env: i2.config?.env || {} });
2644
+ let c2;
2645
+ c2 = false === a2 ? [] : null == a2 ? [s2] : [a2];
2646
+ return { step: "transformer", name: n2.name, events: c2, calls: [], duration: Date.now() - e3 };
2647
+ })(n, e2);
2648
+ case "source":
2649
+ return await (async function(n2, e3) {
2650
+ const { code: t2, config: o2 = {}, setup: s2, input: r2, env: i2, consent: a2 } = n2, c2 = { functional: true, marketing: true, analytics: true };
2651
+ let u2;
2652
+ if (s2) {
2653
+ const n3 = s2(r2, i2);
2654
+ "function" == typeof n3 && (u2 = n3);
2655
+ }
2656
+ const f2 = [], { collector: l2 } = await ln({ consent: a2 || c2, sources: { sim: { code: t2, config: o2, env: i2, next: "spy" } }, transformers: { spy: { code: () => ({ type: "spy", config: {}, push: (n3) => (f2.push(JSON.parse(JSON.stringify(n3))), n3) }) } } });
2657
+ u2 && u2();
2658
+ return { step: "source", name: n2.name, events: f2, calls: [], duration: Date.now() - e3 };
2659
+ })(n, e2);
2660
+ case "destination":
2661
+ return await (async function(n2, e3) {
2662
+ const { code: t2, config: o2 = {}, event: s2, consent: r2, env: i2, track: a2 } = n2, c2 = { functional: true, marketing: true, analytics: true };
2663
+ let u2 = [], f2 = i2;
2664
+ if (i2 && a2 && a2.length > 0) {
2665
+ const n3 = gn({ ...i2, simulation: a2 });
2666
+ f2 = n3.wrappedEnv, u2 = n3.calls;
2667
+ }
2668
+ const l2 = { ...o2 };
2669
+ f2 && (l2.env = f2);
2670
+ const { collector: d2 } = await ln({ consent: r2 || c2, destinations: { sim: { code: t2, config: l2 } } });
2671
+ return await d2.push(s2), { step: "destination", name: n2.name, events: [], calls: u2, duration: Date.now() - e3 };
2672
+ })(n, e2);
2673
+ }
2674
+ } catch (t2) {
2675
+ return { step: n.step, name: n.name, events: [], calls: [], duration: Date.now() - e2, error: t2 instanceof Error ? t2 : new Error(String(t2)) };
2172
2676
  }
2173
2677
  }
2174
2678
 
2679
+ // src/commands/simulate/simulator.ts
2680
+ init_cli_logger();
2681
+
2175
2682
  // src/commands/simulate/env-loader.ts
2176
2683
  async function loadDestinationEnvs(destinations) {
2177
2684
  const envs = {};
@@ -2201,11 +2708,34 @@ async function loadDestinationEnvs(destinations) {
2201
2708
  }
2202
2709
 
2203
2710
  // src/commands/simulate/simulator.ts
2204
- function generateId() {
2205
- return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
2711
+ function createCollectorLoggerConfigInline(logger2, verbose) {
2712
+ return {
2713
+ level: verbose ? Level2.DEBUG : Level2.ERROR,
2714
+ handler: (level, message, context, scope) => {
2715
+ const scopePath = scope.length > 0 ? `[${scope.join(":")}] ` : "";
2716
+ const hasContext = Object.keys(context).length > 0;
2717
+ const contextStr = hasContext ? ` ${JSON.stringify(context)}` : "";
2718
+ if (level === Level2.ERROR) {
2719
+ logger2.error(`${scopePath}${message}${contextStr}`);
2720
+ } else {
2721
+ logger2.debug(`${scopePath}${message}${contextStr}`);
2722
+ }
2723
+ }
2724
+ };
2725
+ }
2726
+ function callsToUsage(destName, calls) {
2727
+ if (!calls.length) return {};
2728
+ return {
2729
+ [destName]: calls.map((c2) => ({
2730
+ type: "call",
2731
+ path: c2.fn,
2732
+ args: c2.args,
2733
+ timestamp: c2.ts
2734
+ }))
2735
+ };
2206
2736
  }
2207
2737
  async function simulateCore(inputPath, event, options = {}) {
2208
- const logger2 = createLogger({
2738
+ const logger2 = createCLILogger({
2209
2739
  verbose: options.verbose || false,
2210
2740
  silent: options.silent || false,
2211
2741
  json: options.json || false
@@ -2214,6 +2744,7 @@ async function simulateCore(inputPath, event, options = {}) {
2214
2744
  logger2.debug(`Simulating event: ${JSON.stringify(event)}`);
2215
2745
  const result = await executeSimulation(event, inputPath, options.platform, {
2216
2746
  flow: options.flow,
2747
+ step: options.step,
2217
2748
  logger: logger2,
2218
2749
  verbose: options.verbose
2219
2750
  });
@@ -2233,18 +2764,43 @@ function formatSimulationResult(result, options = {}) {
2233
2764
  usage: result.usage,
2234
2765
  duration: result.duration
2235
2766
  };
2767
+ if (result.capturedEvents) {
2768
+ output.capturedEvents = result.capturedEvents;
2769
+ }
2770
+ if (result.exampleMatch) {
2771
+ output.exampleMatch = result.exampleMatch;
2772
+ }
2236
2773
  return JSON.stringify(output, null, 2);
2237
2774
  }
2775
+ const lines = [];
2238
2776
  if (result.success) {
2239
- return "Simulation completed";
2777
+ lines.push("Simulation completed");
2240
2778
  } else {
2241
- return `Simulation failed: ${result.error}`;
2779
+ lines.push(`Simulation failed: ${result.error}`);
2780
+ }
2781
+ if (result.capturedEvents) {
2782
+ lines.push(`Captured ${result.capturedEvents.length} event(s)`);
2783
+ for (const evt of result.capturedEvents) {
2784
+ lines.push(` - ${evt.name || "unknown"}`);
2785
+ }
2242
2786
  }
2787
+ if (result.exampleMatch) {
2788
+ const em = result.exampleMatch;
2789
+ if (em.match) {
2790
+ lines.push(`Example "${em.name}" (${em.step}): PASS`);
2791
+ } else {
2792
+ lines.push(`Example "${em.name}" (${em.step}): FAIL`);
2793
+ if (em.diff) {
2794
+ lines.push(em.diff);
2795
+ }
2796
+ }
2797
+ }
2798
+ return lines.join("\n");
2243
2799
  }
2244
2800
  async function executeSimulation(event, inputPath, platformOverride, options = {}) {
2245
- const startTime2 = Date.now();
2801
+ const startTime = Date.now();
2246
2802
  const tempDir = getTmpPath();
2247
- const collectorLoggerConfig = options.logger ? createCollectorLoggerConfig(options.logger, options.verbose) : void 0;
2803
+ const collectorLoggerConfig = options.logger ? createCollectorLoggerConfigInline(options.logger, options.verbose) : void 0;
2248
2804
  try {
2249
2805
  await fs11.ensureDir(tempDir);
2250
2806
  const detected = await detectInput(inputPath, platformOverride);
@@ -2254,176 +2810,436 @@ async function executeSimulation(event, inputPath, platformOverride, options = {
2254
2810
  );
2255
2811
  }
2256
2812
  const typedEvent = event;
2257
- if (detected.type === "config") {
2258
- return await executeConfigSimulation(
2259
- detected.content,
2260
- inputPath,
2261
- typedEvent,
2262
- tempDir,
2263
- startTime2,
2264
- collectorLoggerConfig,
2265
- options.flow
2813
+ if (detected.type !== "config") {
2814
+ throw new Error(
2815
+ `Input "${inputPath}" is not valid JSON config. simulate only accepts Flow.Setup config files.`
2266
2816
  );
2267
- } else {
2268
- return await executeBundleSimulation(
2269
- detected.content,
2270
- detected.platform,
2271
- typedEvent,
2272
- tempDir,
2273
- startTime2,
2274
- collectorLoggerConfig
2817
+ }
2818
+ return await executeConfigSimulation(
2819
+ detected.content,
2820
+ inputPath,
2821
+ typedEvent,
2822
+ tempDir,
2823
+ startTime,
2824
+ collectorLoggerConfig,
2825
+ options.flow,
2826
+ options.step
2827
+ );
2828
+ } catch (error) {
2829
+ const duration = Date.now() - startTime;
2830
+ return {
2831
+ success: false,
2832
+ error: getErrorMessage(error),
2833
+ duration
2834
+ };
2835
+ } finally {
2836
+ if (tempDir) {
2837
+ await fs11.remove(tempDir).catch(() => {
2838
+ });
2839
+ }
2840
+ }
2841
+ }
2842
+ function parseStepTarget(stepTarget, flowConfig) {
2843
+ if (stepTarget) {
2844
+ const dotIndex = stepTarget.indexOf(".");
2845
+ if (dotIndex > -1) {
2846
+ const type = stepTarget.substring(0, dotIndex);
2847
+ const name = stepTarget.substring(dotIndex + 1);
2848
+ const section = flowConfig[type + "s"];
2849
+ if (!section?.[name]) {
2850
+ throw new Error(`Step "${stepTarget}" not found in flow config`);
2851
+ }
2852
+ return { type, name, config: section[name] };
2853
+ }
2854
+ }
2855
+ const destinations = flowConfig.destinations;
2856
+ if (destinations) {
2857
+ const [name, config] = Object.entries(destinations)[0];
2858
+ return {
2859
+ type: "destination",
2860
+ name,
2861
+ config
2862
+ };
2863
+ }
2864
+ throw new Error("No destination found in flow config");
2865
+ }
2866
+ async function executeConfigSimulation(_content, configPath, typedEvent, tempDir, startTime, loggerConfig2, flowName, stepTarget) {
2867
+ const { flowConfig } = await loadFlowConfig(configPath, {
2868
+ flowName
2869
+ });
2870
+ const step = parseStepTarget(
2871
+ stepTarget,
2872
+ flowConfig
2873
+ );
2874
+ if (step.type === "destination") {
2875
+ const packageName = step.config.package;
2876
+ if (!packageName) {
2877
+ throw new Error(`Destination "${step.name}" has no package field`);
2878
+ }
2879
+ const destModule = await import(packageName);
2880
+ const code = destModule.default || Object.values(destModule)[0];
2881
+ const destinations = flowConfig.destinations;
2882
+ const envs = await loadDestinationEnvs(destinations || {});
2883
+ const destEnv = envs[step.name];
2884
+ const result = await mn({
2885
+ step: "destination",
2886
+ name: step.name,
2887
+ code,
2888
+ event: typedEvent,
2889
+ config: step.config.config,
2890
+ env: destEnv?.push,
2891
+ track: destEnv?.simulation
2892
+ });
2893
+ const duration = Date.now() - startTime;
2894
+ return {
2895
+ success: !result.error,
2896
+ error: result.error?.message,
2897
+ usage: callsToUsage(step.name, result.calls),
2898
+ duration,
2899
+ logs: []
2900
+ };
2901
+ }
2902
+ if (step.type === "transformer") {
2903
+ const packageName = step.config.package;
2904
+ if (!packageName) {
2905
+ throw new Error(`Transformer "${step.name}" has no package field`);
2906
+ }
2907
+ const mod = await import(packageName);
2908
+ const code = mod.default || Object.values(mod)[0];
2909
+ const result = await mn({
2910
+ step: "transformer",
2911
+ name: step.name,
2912
+ code,
2913
+ event: typedEvent,
2914
+ config: step.config.config
2915
+ });
2916
+ const duration = Date.now() - startTime;
2917
+ return {
2918
+ success: !result.error,
2919
+ error: result.error?.message,
2920
+ capturedEvents: result.events,
2921
+ duration,
2922
+ usage: {},
2923
+ logs: []
2924
+ };
2925
+ }
2926
+ throw new Error(`Unknown step type: ${step.type}`);
2927
+ }
2928
+
2929
+ // src/commands/simulate/source-simulator.ts
2930
+ import { JSDOM, VirtualConsole } from "jsdom";
2931
+ async function loadSourcePackage(packageName) {
2932
+ const mainModule = await import(packageName);
2933
+ const code = mainModule.default || Object.values(mainModule)[0];
2934
+ if (!code || typeof code !== "function") {
2935
+ throw new Error(`Package ${packageName} missing source init function`);
2936
+ }
2937
+ let setup;
2938
+ try {
2939
+ const devModule = await import(`${packageName}/dev`);
2940
+ const examples = devModule.examples || devModule.default?.examples;
2941
+ if (examples?.setup && typeof examples.setup === "function") {
2942
+ setup = examples.setup;
2943
+ }
2944
+ } catch {
2945
+ }
2946
+ return { code, setup };
2947
+ }
2948
+ async function simulateSourceCLI(flowConfig, setupInput, options) {
2949
+ const startTime = Date.now();
2950
+ try {
2951
+ const sources = flowConfig.sources;
2952
+ if (!sources) {
2953
+ throw new Error("Flow config has no sources");
2954
+ }
2955
+ const sourceConfig = sources[options.sourceStep];
2956
+ if (!sourceConfig) {
2957
+ const available = Object.keys(sources).join(", ");
2958
+ throw new Error(
2959
+ `Source "${options.sourceStep}" not found. Available: ${available}`
2275
2960
  );
2276
2961
  }
2962
+ if (!sourceConfig.package) {
2963
+ throw new Error(`Source "${options.sourceStep}" has no package field`);
2964
+ }
2965
+ const { code, setup } = await loadSourcePackage(sourceConfig.package);
2966
+ const virtualConsole = new VirtualConsole();
2967
+ const dom = new JSDOM(
2968
+ "<!DOCTYPE html><html><head></head><body></body></html>",
2969
+ {
2970
+ url: "http://localhost",
2971
+ runScripts: "dangerously",
2972
+ pretendToBeVisual: true,
2973
+ virtualConsole
2974
+ }
2975
+ );
2976
+ const env = {
2977
+ window: dom.window,
2978
+ document: dom.window.document,
2979
+ localStorage: dom.window.localStorage
2980
+ };
2981
+ const result = await mn({
2982
+ step: "source",
2983
+ name: options.sourceStep,
2984
+ code,
2985
+ config: sourceConfig.config || {},
2986
+ setup,
2987
+ input: setupInput,
2988
+ env
2989
+ });
2990
+ dom.window.close();
2991
+ return {
2992
+ success: !result.error,
2993
+ error: result.error?.message,
2994
+ capturedEvents: result.events,
2995
+ duration: Date.now() - startTime
2996
+ };
2277
2997
  } catch (error) {
2278
- const duration = Date.now() - startTime2;
2279
2998
  return {
2280
2999
  success: false,
2281
3000
  error: getErrorMessage(error),
2282
- duration
3001
+ duration: Date.now() - startTime
2283
3002
  };
2284
- } finally {
2285
- if (tempDir) {
2286
- await fs11.remove(tempDir).catch(() => {
2287
- });
2288
- }
2289
3003
  }
2290
3004
  }
2291
- async function executeConfigSimulation(_content, configPath, typedEvent, tempDir, startTime2, loggerConfig2, flowName) {
2292
- const { flowConfig, buildOptions } = await loadFlowConfig(configPath, {
2293
- flowName
2294
- });
2295
- const platform = getPlatform3(flowConfig);
2296
- const tracker = new CallTracker();
2297
- const tempOutput = path11.join(
2298
- tempDir,
2299
- `simulation-bundle-${generateId()}.${platform === "web" ? "js" : "mjs"}`
2300
- );
2301
- const destinations = flowConfig.destinations;
2302
- const simulationBuildOptions = {
2303
- ...buildOptions,
2304
- code: buildOptions.code || "",
2305
- output: tempOutput,
2306
- tempDir,
2307
- ...platform === "web" ? {
2308
- format: "iife",
2309
- platform: "browser",
2310
- windowCollector: "collector",
2311
- windowElb: "elb"
2312
- } : {
2313
- format: "esm",
2314
- platform: "node"
2315
- }
2316
- };
2317
- await bundleCore(
2318
- flowConfig,
2319
- simulationBuildOptions,
2320
- createLogger({ silent: true }),
2321
- false
2322
- );
2323
- const envs = await loadDestinationEnvs(destinations || {});
2324
- let result;
2325
- if (platform === "web") {
2326
- result = await executeInJSDOM(
2327
- tempOutput,
2328
- destinations || {},
2329
- typedEvent,
2330
- tracker,
2331
- envs,
2332
- 1e4
3005
+
3006
+ // src/commands/simulate/index.ts
3007
+ init_cli_logger();
3008
+
3009
+ // src/commands/simulate/example-loader.ts
3010
+ function findExample(config, exampleName, stepTarget) {
3011
+ if (stepTarget) {
3012
+ return findExampleInStep(config, exampleName, stepTarget);
3013
+ }
3014
+ return findExampleAcrossSteps(config, exampleName);
3015
+ }
3016
+ function findExampleInStep(config, exampleName, stepTarget) {
3017
+ const dotIndex = stepTarget.indexOf(".");
3018
+ if (dotIndex === -1) {
3019
+ throw new Error(
3020
+ `Invalid --step format: "${stepTarget}". Expected "type.name" (e.g. "destination.gtag")`
2333
3021
  );
2334
- } else {
2335
- result = await executeInNode(
2336
- tempOutput,
2337
- destinations || {},
2338
- typedEvent,
2339
- tracker,
2340
- envs,
2341
- 3e4,
2342
- loggerConfig2 ? { logger: loggerConfig2 } : {}
3022
+ }
3023
+ const type = stepTarget.substring(0, dotIndex);
3024
+ const name = stepTarget.substring(dotIndex + 1);
3025
+ const stepMap = getStepMap(config, type);
3026
+ if (!stepMap) {
3027
+ throw new Error(`No ${type}s found in flow config`);
3028
+ }
3029
+ const step = stepMap[name];
3030
+ if (!step) {
3031
+ const available = Object.keys(stepMap).join(", ");
3032
+ throw new Error(`${type} "${name}" not found. Available: ${available}`);
3033
+ }
3034
+ const examples = step.examples;
3035
+ if (!examples || !examples[exampleName]) {
3036
+ const available = examples ? Object.keys(examples).join(", ") : "none";
3037
+ throw new Error(
3038
+ `Example "${exampleName}" not found in ${type} "${name}". Available: ${available}`
2343
3039
  );
2344
3040
  }
2345
- const duration = Date.now() - startTime2;
2346
3041
  return {
2347
- success: true,
2348
- elbResult: result.elbResult,
2349
- usage: result.usage,
2350
- duration,
2351
- logs: []
3042
+ stepType: type,
3043
+ stepName: name,
3044
+ exampleName,
3045
+ example: examples[exampleName]
2352
3046
  };
2353
3047
  }
2354
- async function executeBundleSimulation(bundleContent, platform, typedEvent, tempDir, startTime2, loggerConfig2) {
2355
- const tempOutput = path11.join(
2356
- tempDir,
2357
- `bundle-${generateId()}.${platform === "server" ? "mjs" : "js"}`
2358
- );
2359
- await fs11.writeFile(tempOutput, bundleContent, "utf8");
2360
- const tracker = new CallTracker();
2361
- let result;
2362
- if (platform === "web") {
2363
- result = await executeInJSDOM(
2364
- tempOutput,
2365
- {},
2366
- typedEvent,
2367
- tracker,
2368
- {},
2369
- 1e4
2370
- );
2371
- } else {
2372
- result = await executeInNode(
2373
- tempOutput,
2374
- {},
2375
- typedEvent,
2376
- tracker,
2377
- {},
2378
- 3e4,
2379
- loggerConfig2 ? { logger: loggerConfig2 } : {}
3048
+ function findExampleAcrossSteps(config, exampleName) {
3049
+ const matches = [];
3050
+ const stepTypes = ["source", "transformer", "destination"];
3051
+ for (const type of stepTypes) {
3052
+ const stepMap = getStepMap(config, type);
3053
+ if (!stepMap) continue;
3054
+ for (const [name, step] of Object.entries(stepMap)) {
3055
+ const examples = step.examples;
3056
+ if (examples && examples[exampleName]) {
3057
+ matches.push({
3058
+ stepType: type,
3059
+ stepName: name,
3060
+ exampleName,
3061
+ example: examples[exampleName]
3062
+ });
3063
+ }
3064
+ }
3065
+ }
3066
+ if (matches.length === 0) {
3067
+ throw new Error(`Example "${exampleName}" not found in any step`);
3068
+ }
3069
+ if (matches.length > 1) {
3070
+ const locations = matches.map((m2) => `${m2.stepType}.${m2.stepName}`).join(", ");
3071
+ throw new Error(
3072
+ `Example "${exampleName}" found in multiple steps: ${locations}. Use --step to disambiguate.`
2380
3073
  );
2381
3074
  }
2382
- const duration = Date.now() - startTime2;
3075
+ return matches[0];
3076
+ }
3077
+ function getStepMap(config, type) {
3078
+ switch (type) {
3079
+ case "source":
3080
+ return config.sources;
3081
+ case "transformer":
3082
+ return config.transformers;
3083
+ case "destination":
3084
+ return config.destinations;
3085
+ default:
3086
+ throw new Error(
3087
+ `Invalid step type: "${type}". Must be "source", "transformer", or "destination"`
3088
+ );
3089
+ }
3090
+ }
3091
+
3092
+ // src/commands/simulate/compare.ts
3093
+ function compareOutput(expected, actual) {
3094
+ const expectedStr = JSON.stringify(expected, null, 2);
3095
+ const actualStr = JSON.stringify(actual, null, 2);
3096
+ if (expectedStr === actualStr) {
3097
+ return { expected, actual, match: true };
3098
+ }
2383
3099
  return {
2384
- success: true,
2385
- elbResult: result.elbResult,
2386
- usage: result.usage,
2387
- duration,
2388
- logs: []
3100
+ expected,
3101
+ actual,
3102
+ match: false,
3103
+ diff: `Expected:
3104
+ ${expectedStr}
3105
+
3106
+ Actual:
3107
+ ${actualStr}`
2389
3108
  };
2390
3109
  }
2391
3110
 
2392
3111
  // src/commands/simulate/index.ts
2393
3112
  async function simulateCommand(options) {
2394
- const logger2 = createCommandLogger({ ...options, stderr: true });
2395
- const startTime2 = Date.now();
3113
+ const logger2 = createCLILogger({ ...options, stderr: true });
3114
+ const startTime = Date.now();
2396
3115
  try {
2397
3116
  let config;
2398
3117
  if (isStdinPiped() && !options.config) {
2399
3118
  const stdinContent = await readStdin();
2400
3119
  const fs14 = await import("fs-extra");
2401
- const path15 = await import("path");
3120
+ const path14 = await import("path");
2402
3121
  const tmpPath = getTmpPath(void 0, "stdin-simulate.json");
2403
- await fs14.default.ensureDir(path15.default.dirname(tmpPath));
3122
+ await fs14.default.ensureDir(path14.default.dirname(tmpPath));
2404
3123
  await fs14.default.writeFile(tmpPath, stdinContent, "utf-8");
2405
3124
  config = tmpPath;
2406
3125
  } else {
2407
3126
  config = options.config || "bundle.config.json";
2408
3127
  }
2409
- const event = await loadJsonFromSource(options.event, {
2410
- name: "event"
2411
- });
2412
- const result = await simulateCore(config, event, {
2413
- flow: options.flow,
2414
- json: options.json,
2415
- verbose: options.verbose,
2416
- silent: options.silent
2417
- });
3128
+ let event;
3129
+ let exampleContext;
3130
+ if (options.example) {
3131
+ const rawConfig = await loadJsonConfig(config);
3132
+ const setup = validateFlowSetup(rawConfig);
3133
+ const flowNames = Object.keys(setup.flows);
3134
+ let flowName = options.flow;
3135
+ if (!flowName) {
3136
+ if (flowNames.length === 1) {
3137
+ flowName = flowNames[0];
3138
+ } else {
3139
+ throw new Error(
3140
+ `Multiple flows found. Use --flow to specify which flow contains the example.
3141
+ Available flows: ${flowNames.join(", ")}`
3142
+ );
3143
+ }
3144
+ }
3145
+ const flowConfig = setup.flows[flowName];
3146
+ if (!flowConfig) {
3147
+ throw new Error(
3148
+ `Flow "${flowName}" not found. Available: ${flowNames.join(", ")}`
3149
+ );
3150
+ }
3151
+ const found = findExample(flowConfig, options.example, options.step);
3152
+ if (found.example.in === void 0) {
3153
+ throw new Error(
3154
+ `Example "${options.example}" in ${found.stepType}.${found.stepName} has no "in" value`
3155
+ );
3156
+ }
3157
+ event = found.example.in;
3158
+ exampleContext = {
3159
+ stepType: found.stepType,
3160
+ stepName: found.stepName,
3161
+ expected: found.example.out
3162
+ };
3163
+ } else {
3164
+ event = await loadJsonFromSource(options.event, {
3165
+ name: "event"
3166
+ });
3167
+ }
3168
+ const isSourceSimulation = exampleContext?.stepType === "source" || options.step?.startsWith("source.");
3169
+ let result;
3170
+ if (isSourceSimulation) {
3171
+ const rawConfig = await loadJsonConfig(config);
3172
+ const setup = validateFlowSetup(rawConfig);
3173
+ const flowNames = Object.keys(setup.flows);
3174
+ const flowName = options.flow || (flowNames.length === 1 ? flowNames[0] : void 0);
3175
+ if (!flowName) {
3176
+ throw new Error(
3177
+ `Multiple flows found. Use --flow to specify which flow.
3178
+ Available: ${flowNames.join(", ")}`
3179
+ );
3180
+ }
3181
+ const flowConfig = setup.flows[flowName];
3182
+ if (!flowConfig) {
3183
+ throw new Error(
3184
+ `Flow "${flowName}" not found. Available: ${flowNames.join(", ")}`
3185
+ );
3186
+ }
3187
+ const sourceStep = exampleContext?.stepName || options.step.substring("source.".length);
3188
+ result = await simulateSourceCLI(
3189
+ flowConfig,
3190
+ event,
3191
+ {
3192
+ flow: options.flow,
3193
+ sourceStep,
3194
+ json: options.json,
3195
+ verbose: options.verbose,
3196
+ silent: options.silent
3197
+ }
3198
+ );
3199
+ } else {
3200
+ const stepTarget = exampleContext ? `${exampleContext.stepType}.${exampleContext.stepName}` : options.step;
3201
+ result = await simulateCore(config, event, {
3202
+ flow: options.flow,
3203
+ json: options.json,
3204
+ verbose: options.verbose,
3205
+ silent: options.silent,
3206
+ step: stepTarget
3207
+ });
3208
+ }
3209
+ let exampleMatch;
3210
+ if (exampleContext && result.success) {
3211
+ const stepKey = `${exampleContext.stepType}.${exampleContext.stepName}`;
3212
+ if (exampleContext.expected === false) {
3213
+ const calls = result.usage?.[exampleContext.stepName];
3214
+ const wasFiltered = !calls || calls.length === 0;
3215
+ exampleMatch = {
3216
+ name: options.example,
3217
+ step: stepKey,
3218
+ expected: false,
3219
+ actual: wasFiltered ? false : calls,
3220
+ match: wasFiltered,
3221
+ diff: wasFiltered ? void 0 : `Expected event to be filtered, but ${calls.length} API call(s) were made`
3222
+ };
3223
+ } else if (exampleContext.expected !== void 0) {
3224
+ const actual = result.usage?.[exampleContext.stepName] ?? [];
3225
+ exampleMatch = {
3226
+ name: options.example,
3227
+ step: stepKey,
3228
+ ...compareOutput(exampleContext.expected, actual)
3229
+ };
3230
+ }
3231
+ }
2418
3232
  const resultWithDuration = {
2419
3233
  ...result,
2420
- duration: (Date.now() - startTime2) / 1e3
3234
+ duration: (Date.now() - startTime) / 1e3,
3235
+ ...exampleMatch ? { exampleMatch } : {}
2421
3236
  };
2422
3237
  const formatted = formatSimulationResult(resultWithDuration, {
2423
3238
  json: options.json
2424
3239
  });
2425
3240
  await writeResult(formatted + "\n", { output: options.output });
2426
- process.exit(result.success ? 0 : 1);
3241
+ const exitCode = !result.success || exampleMatch && !exampleMatch.match ? 1 : 0;
3242
+ process.exit(exitCode);
2427
3243
  } catch (error) {
2428
3244
  const errorMessage = getErrorMessage(error);
2429
3245
  if (options.json) {
@@ -2431,7 +3247,7 @@ async function simulateCommand(options) {
2431
3247
  {
2432
3248
  success: false,
2433
3249
  error: errorMessage,
2434
- duration: (Date.now() - startTime2) / 1e3
3250
+ duration: (Date.now() - startTime) / 1e3
2435
3251
  },
2436
3252
  null,
2437
3253
  2
@@ -2449,28 +3265,102 @@ async function simulate(configOrPath, event, options = {}) {
2449
3265
  "simulate() currently only supports config file paths. Config object support will be added in a future version. Please provide a path to a configuration file."
2450
3266
  );
2451
3267
  }
2452
- return await simulateCore(configOrPath, event, {
3268
+ let resolvedEvent = event;
3269
+ let exampleContext;
3270
+ if (options.example) {
3271
+ const rawConfig = await loadJsonConfig(configOrPath);
3272
+ const setup = validateFlowSetup(rawConfig);
3273
+ const flowNames = Object.keys(setup.flows);
3274
+ let flowName = options.flow;
3275
+ if (!flowName) {
3276
+ if (flowNames.length === 1) {
3277
+ flowName = flowNames[0];
3278
+ } else {
3279
+ throw new Error(
3280
+ `Multiple flows found. Use --flow to specify which flow contains the example.
3281
+ Available flows: ${flowNames.join(", ")}`
3282
+ );
3283
+ }
3284
+ }
3285
+ const flowConfig = setup.flows[flowName];
3286
+ if (!flowConfig) {
3287
+ throw new Error(
3288
+ `Flow "${flowName}" not found. Available: ${flowNames.join(", ")}`
3289
+ );
3290
+ }
3291
+ const found = findExample(flowConfig, options.example, options.step);
3292
+ if (found.example.in === void 0) {
3293
+ throw new Error(
3294
+ `Example "${options.example}" in ${found.stepType}.${found.stepName} has no "in" value`
3295
+ );
3296
+ }
3297
+ resolvedEvent = found.example.in;
3298
+ exampleContext = {
3299
+ stepType: found.stepType,
3300
+ stepName: found.stepName,
3301
+ expected: found.example.out
3302
+ };
3303
+ }
3304
+ const result = await simulateCore(configOrPath, resolvedEvent, {
2453
3305
  json: options.json ?? false,
2454
3306
  verbose: options.verbose ?? false,
2455
3307
  flow: options.flow,
2456
3308
  platform: options.platform
2457
3309
  });
3310
+ if (exampleContext && result.success) {
3311
+ const stepKey = `${exampleContext.stepType}.${exampleContext.stepName}`;
3312
+ if (exampleContext.expected === false) {
3313
+ const calls = result.usage?.[exampleContext.stepName];
3314
+ const wasFiltered = !calls || calls.length === 0;
3315
+ result.exampleMatch = {
3316
+ name: options.example,
3317
+ step: stepKey,
3318
+ expected: false,
3319
+ actual: wasFiltered ? false : calls,
3320
+ match: wasFiltered,
3321
+ diff: wasFiltered ? void 0 : `Expected event to be filtered, but ${calls.length} API call(s) were made`
3322
+ };
3323
+ } else if (exampleContext.expected !== void 0) {
3324
+ const actual = result.usage?.[exampleContext.stepName] ?? [];
3325
+ result.exampleMatch = {
3326
+ name: options.example,
3327
+ step: stepKey,
3328
+ ...compareOutput(exampleContext.expected, actual)
3329
+ };
3330
+ }
3331
+ }
3332
+ return result;
2458
3333
  }
2459
3334
 
2460
3335
  // src/commands/push/index.ts
2461
- import path12 from "path";
3336
+ init_cli_logger();
3337
+ import path11 from "path";
2462
3338
  import { JSDOM as JSDOM2, VirtualConsole as VirtualConsole2 } from "jsdom";
2463
3339
  import fs12 from "fs-extra";
2464
- import {
2465
- getPlatform as getPlatform4
2466
- } from "@walkeros/core";
3340
+ import { getPlatform as getPlatform3 } from "@walkeros/core";
2467
3341
  import { schemas as schemas2 } from "@walkeros/core/dev";
3342
+ import { Level as Level3 } from "@walkeros/core";
3343
+ function createCollectorLoggerConfig(logger2, verbose) {
3344
+ return {
3345
+ level: verbose ? Level3.DEBUG : Level3.ERROR,
3346
+ handler: (level, message, context, scope) => {
3347
+ const scopePath = scope.length > 0 ? `[${scope.join(":")}] ` : "";
3348
+ const hasContext = Object.keys(context).length > 0;
3349
+ const contextStr = hasContext ? ` ${JSON.stringify(context)}` : "";
3350
+ if (level === Level3.ERROR) {
3351
+ logger2.error(`${scopePath}${message}${contextStr}`);
3352
+ } else {
3353
+ logger2.debug(`${scopePath}${message}${contextStr}`);
3354
+ }
3355
+ }
3356
+ };
3357
+ }
2468
3358
  async function pushCore(inputPath, event, options = {}) {
2469
- const logger2 = createCommandLogger({
3359
+ const logger2 = createCLILogger({
2470
3360
  silent: options.silent,
2471
3361
  verbose: options.verbose
2472
3362
  });
2473
- const startTime2 = Date.now();
3363
+ const startTime = Date.now();
2474
3364
  let tempDir;
2475
3365
  try {
2476
3366
  let loadedEvent = event;
@@ -2491,7 +3381,7 @@ async function pushCore(inputPath, event, options = {}) {
2491
3381
  data: parsedEvent.data || {}
2492
3382
  };
2493
3383
  if (!validatedEvent.name.includes(" ")) {
2494
- logger2.log(
3384
+ logger2.info(
2495
3385
  `Warning: Event name "${validatedEvent.name}" should follow "ENTITY ACTION" format (e.g., "page view")`
2496
3386
  );
2497
3387
  }
@@ -2515,10 +3405,6 @@ async function pushCore(inputPath, event, options = {}) {
2515
3405
  }
2516
3406
  );
2517
3407
  } else {
2518
- const collectorLoggerConfig = createCollectorLoggerConfig(
2519
- logger2,
2520
- options.verbose
2521
- );
2522
3408
  result = await executeBundlePush(
2523
3409
  detected.content,
2524
3410
  detected.platform,
@@ -2527,14 +3413,14 @@ async function pushCore(inputPath, event, options = {}) {
2527
3413
  (dir) => {
2528
3414
  tempDir = dir;
2529
3415
  },
2530
- { logger: collectorLoggerConfig }
3416
+ { logger: createCollectorLoggerConfig(logger2, options.verbose) }
2531
3417
  );
2532
3418
  }
2533
3419
  return result;
2534
3420
  } catch (error) {
2535
3421
  return {
2536
3422
  success: false,
2537
- duration: Date.now() - startTime2,
3423
+ duration: Date.now() - startTime,
2538
3424
  error: getErrorMessage(error)
2539
3425
  };
2540
3426
  } finally {
@@ -2545,14 +3431,14 @@ async function pushCore(inputPath, event, options = {}) {
2545
3431
  }
2546
3432
  }
2547
3433
  async function pushCommand(options) {
2548
- const logger2 = createCommandLogger({ ...options, stderr: true });
2549
- const startTime2 = Date.now();
3434
+ const logger2 = createCLILogger({ ...options, stderr: true });
3435
+ const startTime = Date.now();
2550
3436
  try {
2551
3437
  let config;
2552
3438
  if (isStdinPiped() && !options.config) {
2553
3439
  const stdinContent = await readStdin();
2554
3440
  const tmpPath = getTmpPath(void 0, "stdin-push.json");
2555
- await fs12.ensureDir(path12.dirname(tmpPath));
3441
+ await fs12.ensureDir(path11.dirname(tmpPath));
2556
3442
  await fs12.writeFile(tmpPath, stdinContent, "utf-8");
2557
3443
  config = tmpPath;
2558
3444
  } else {
@@ -2566,7 +3452,7 @@ async function pushCommand(options) {
2566
3452
  silent: options.silent,
2567
3453
  platform: options.platform
2568
3454
  });
2569
- const duration = Date.now() - startTime2;
3455
+ const duration = Date.now() - startTime;
2570
3456
  let output;
2571
3457
  if (options.json) {
2572
3458
  output = JSON.stringify(
@@ -2600,7 +3486,7 @@ async function pushCommand(options) {
2600
3486
  await writeResult(output + "\n", { output: options.output });
2601
3487
  process.exit(result.success ? 0 : 1);
2602
3488
  } catch (error) {
2603
- const duration = Date.now() - startTime2;
3489
+ const duration = Date.now() - startTime;
2604
3490
  const errorMessage = getErrorMessage(error);
2605
3491
  if (options.json) {
2606
3492
  const errorOutput = JSON.stringify(
@@ -2634,7 +3520,7 @@ async function executeConfigPush(options, validatedEvent, logger2, setTempDir) {
2634
3520
  flowName: options.flow,
2635
3521
  logger: logger2
2636
3522
  });
2637
- const platform = getPlatform4(flowConfig);
3523
+ const platform = getPlatform3(flowConfig);
2638
3524
  logger2.debug("Bundling flow configuration");
2639
3525
  const configDir = buildOptions.configDir || process.cwd();
2640
3526
  const tempDir = getTmpPath(
@@ -2643,7 +3529,7 @@ async function executeConfigPush(options, validatedEvent, logger2, setTempDir) {
2643
3529
  );
2644
3530
  setTempDir(tempDir);
2645
3531
  await fs12.ensureDir(tempDir);
2646
- const tempPath = path12.join(
3532
+ const tempPath = path11.join(
2647
3533
  tempDir,
2648
3534
  `bundle.${platform === "web" ? "js" : "mjs"}`
2649
3535
  );
@@ -2664,12 +3550,8 @@ async function executeConfigPush(options, validatedEvent, logger2, setTempDir) {
2664
3550
  return executeWebPush(tempPath, validatedEvent, logger2);
2665
3551
  } else if (platform === "server") {
2666
3552
  logger2.debug("Executing in server environment (Node.js)");
2667
- const collectorLoggerConfig = createCollectorLoggerConfig(
2668
- logger2,
2669
- options.verbose
2670
- );
2671
3553
  return executeServerPush(tempPath, validatedEvent, logger2, 6e4, {
2672
- logger: collectorLoggerConfig
3554
+ logger: createCollectorLoggerConfig(logger2, options.verbose)
2673
3555
  });
2674
3556
  } else {
2675
3557
  throw new Error(`Unsupported platform: ${platform}`);
@@ -2682,7 +3564,7 @@ async function executeBundlePush(bundleContent, platform, validatedEvent, logger
2682
3564
  );
2683
3565
  setTempDir(tempDir);
2684
3566
  await fs12.ensureDir(tempDir);
2685
- const tempPath = path12.join(
3567
+ const tempPath = path11.join(
2686
3568
  tempDir,
2687
3569
  `bundle.${platform === "server" ? "mjs" : "js"}`
2688
3570
  );
@@ -2697,7 +3579,7 @@ async function executeBundlePush(bundleContent, platform, validatedEvent, logger
2697
3579
  }
2698
3580
  }
2699
3581
  async function executeWebPush(bundlePath, event, logger2) {
2700
- const startTime2 = Date.now();
3582
+ const startTime = Date.now();
2701
3583
  try {
2702
3584
  const virtualConsole = new VirtualConsole2();
2703
3585
  const dom = new JSDOM2("<!DOCTYPE html><html><body></body></html>", {
@@ -2711,14 +3593,14 @@ async function executeWebPush(bundlePath, event, logger2) {
2711
3593
  const bundleCode = await fs12.readFile(bundlePath, "utf8");
2712
3594
  window.eval(bundleCode);
2713
3595
  logger2.debug("Waiting for collector...");
2714
- await waitForWindowProperty2(
3596
+ await waitForWindowProperty(
2715
3597
  window,
2716
3598
  "collector",
2717
3599
  5e3
2718
3600
  );
2719
3601
  const windowObj = window;
2720
3602
  const collector = windowObj.collector;
2721
- logger2.log(`Pushing event: ${event.name}`);
3603
+ logger2.info(`Pushing event: ${event.name}`);
2722
3604
  const elbResult = await collector.push({
2723
3605
  name: event.name,
2724
3606
  data: event.data
@@ -2726,21 +3608,22 @@ async function executeWebPush(bundlePath, event, logger2) {
2726
3608
  return {
2727
3609
  success: true,
2728
3610
  elbResult,
2729
- duration: Date.now() - startTime2
3611
+ duration: Date.now() - startTime
2730
3612
  };
2731
3613
  } catch (error) {
2732
3614
  return {
2733
3615
  success: false,
2734
- duration: Date.now() - startTime2,
3616
+ duration: Date.now() - startTime,
2735
3617
  error: getErrorMessage(error)
2736
3618
  };
2737
3619
  }
2738
3620
  }
2739
3621
  async function executeServerPush(bundlePath, event, logger2, timeout = 6e4, context = {}) {
2740
- const startTime2 = Date.now();
3622
+ const startTime = Date.now();
3623
+ let timer;
2741
3624
  try {
2742
- const timeoutPromise = new Promise((_, reject) => {
2743
- setTimeout(
3625
+ const timeoutPromise = new Promise((_2, reject) => {
3626
+ timer = setTimeout(
2744
3627
  () => reject(new Error(`Server push timeout after ${timeout}ms`)),
2745
3628
  timeout
2746
3629
  );
@@ -2759,7 +3642,7 @@ async function executeServerPush(bundlePath, event, logger2, timeout = 6e4, cont
2759
3642
  );
2760
3643
  }
2761
3644
  const { collector } = result;
2762
- logger2.log(`Pushing event: ${event.name}`);
3645
+ logger2.info(`Pushing event: ${event.name}`);
2763
3646
  const elbResult = await collector.push({
2764
3647
  name: event.name,
2765
3648
  data: event.data
@@ -2767,24 +3650,26 @@ async function executeServerPush(bundlePath, event, logger2, timeout = 6e4, cont
2767
3650
  return {
2768
3651
  success: true,
2769
3652
  elbResult,
2770
- duration: Date.now() - startTime2
3653
+ duration: Date.now() - startTime
2771
3654
  };
2772
3655
  })();
2773
3656
  return await Promise.race([executePromise, timeoutPromise]);
2774
3657
  } catch (error) {
2775
3658
  return {
2776
3659
  success: false,
2777
- duration: Date.now() - startTime2,
3660
+ duration: Date.now() - startTime,
2778
3661
  error: getErrorMessage(error)
2779
3662
  };
3663
+ } finally {
3664
+ clearTimeout(timer);
2780
3665
  }
2781
3666
  }
2782
- function waitForWindowProperty2(window, prop, timeout = 5e3) {
2783
- return new Promise((resolve3, reject) => {
3667
+ function waitForWindowProperty(window, prop, timeout = 5e3) {
3668
+ return new Promise((resolve2, reject) => {
2784
3669
  const start = Date.now();
2785
3670
  const check = () => {
2786
3671
  if (window[prop] !== void 0) {
2787
- resolve3();
3672
+ resolve2();
2788
3673
  } else if (Date.now() - start > timeout) {
2789
3674
  reject(
2790
3675
  new Error(
@@ -2800,105 +3685,103 @@ function waitForWindowProperty2(window, prop, timeout = 5e3) {
2800
3685
  }
2801
3686
 
2802
3687
  // src/commands/run/index.ts
2803
- import path14 from "path";
3688
+ init_cli_logger();
3689
+ import path13 from "path";
2804
3690
 
2805
3691
  // src/commands/run/validators.ts
2806
3692
  import { existsSync as existsSync3 } from "fs";
2807
3693
 
2808
3694
  // src/schemas/primitives.ts
2809
- import { z } from "@walkeros/core/dev";
2810
- var RunModeSchema = z.enum(["collect", "serve"]).describe("CLI run mode: collect events or serve HTTP");
2811
- var PortSchema = z.number().int("Port must be an integer").min(1, "Port must be at least 1").max(65535, "Port must be at most 65535").describe("HTTP server port number");
2812
- var FilePathSchema = z.string().min(1, "File path cannot be empty").describe("Path to configuration file");
3695
+ import { z as z2 } from "@walkeros/core/dev";
3696
+ var PortSchema = z2.number().int("Port must be an integer").min(1, "Port must be at least 1").max(65535, "Port must be at most 65535").describe("HTTP server port number");
3697
+ var FilePathSchema = z2.string().min(1, "File path cannot be empty").describe("Path to configuration file");
2813
3698
 
2814
3699
  // src/schemas/run.ts
2815
- import { z as z2 } from "@walkeros/core/dev";
2816
- var RunOptionsSchema = z2.object({
2817
- mode: RunModeSchema,
3700
+ import { z as z3 } from "@walkeros/core/dev";
3701
+ var RunOptionsSchema = z3.object({
2818
3702
  flow: FilePathSchema,
2819
3703
  port: PortSchema.default(8080),
2820
- flowName: z2.string().optional().describe("Specific flow name to run")
3704
+ flowName: z3.string().optional().describe("Specific flow name to run")
2821
3705
  });
2822
3706
 
2823
3707
  // src/schemas/validate.ts
2824
- import { z as z3 } from "@walkeros/core/dev";
2825
- var ValidationTypeSchema = z3.union([
2826
- z3.enum(["event", "flow", "mapping"]),
2827
- z3.string().regex(/^(destinations|sources|transformers)\.\w+$|^\w+$/)
2828
- ]).describe(
2829
- 'Validation type: "event", "flow", "mapping", or dot-notation path (e.g., "destinations.snowplow") to validate a specific entry against its package schema'
2830
- );
2831
- var ValidateOptionsSchema = z3.object({
2832
- flow: z3.string().optional().describe("Flow name for multi-flow configs")
3708
+ import { z as z4 } from "@walkeros/core/dev";
3709
+ var ValidationTypeSchema = z4.enum(["contract", "event", "flow", "mapping"]).describe('Validation type: "event", "flow", "mapping", or "contract"');
3710
+ var ValidateOptionsSchema = z4.object({
3711
+ flow: z4.string().optional().describe("Flow name for multi-flow configs"),
3712
+ path: z4.string().optional().describe(
3713
+ 'Entry path for package schema validation (e.g., "destinations.snowplow", "sources.browser")'
3714
+ )
2833
3715
  });
2834
3716
  var ValidateInputShape = {
2835
3717
  type: ValidationTypeSchema,
2836
- input: z3.string().min(1).describe("JSON string, file path, or URL to validate"),
2837
- flow: z3.string().optional().describe("Flow name for multi-flow configs")
3718
+ input: z4.string().min(1).describe("JSON string, file path, or URL to validate"),
3719
+ flow: z4.string().optional().describe("Flow name for multi-flow configs"),
3720
+ path: z4.string().optional().describe(
3721
+ 'Entry path for package schema validation (e.g., "destinations.snowplow"). When provided, validates the entry against its package JSON Schema instead of using --type.'
3722
+ )
2838
3723
  };
2839
- var ValidateInputSchema = z3.object(ValidateInputShape);
3724
+ var ValidateInputSchema = z4.object(ValidateInputShape);
2840
3725
 
2841
3726
  // src/schemas/bundle.ts
2842
- import { z as z4 } from "@walkeros/core/dev";
2843
- var BundleOptionsSchema = z4.object({
2844
- silent: z4.boolean().optional().describe("Suppress all output"),
2845
- verbose: z4.boolean().optional().describe("Enable verbose logging"),
2846
- stats: z4.boolean().optional().default(true).describe("Return bundle statistics"),
2847
- cache: z4.boolean().optional().default(true).describe("Enable package caching"),
2848
- flowName: z4.string().optional().describe("Flow name for multi-flow configs")
3727
+ import { z as z5 } from "@walkeros/core/dev";
3728
+ var BundleOptionsSchema = z5.object({
3729
+ silent: z5.boolean().optional().describe("Suppress all output"),
3730
+ verbose: z5.boolean().optional().describe("Enable verbose logging"),
3731
+ stats: z5.boolean().optional().default(true).describe("Return bundle statistics"),
3732
+ cache: z5.boolean().optional().default(true).describe("Enable package caching"),
3733
+ flowName: z5.string().optional().describe("Flow name for multi-flow configs")
2849
3734
  });
2850
3735
  var BundleInputShape = {
2851
3736
  configPath: FilePathSchema.describe(
2852
3737
  "Path to flow configuration file (JSON or JavaScript)"
2853
3738
  ),
2854
- flow: z4.string().optional().describe("Flow name for multi-flow configs"),
2855
- stats: z4.boolean().optional().default(true).describe("Return bundle statistics"),
2856
- output: z4.string().optional().describe("Output file path (defaults to config-defined)")
3739
+ flow: z5.string().optional().describe("Flow name for multi-flow configs"),
3740
+ stats: z5.boolean().optional().default(true).describe("Return bundle statistics"),
3741
+ output: z5.string().optional().describe("Output file path (defaults to config-defined)")
2857
3742
  };
2858
- var BundleInputSchema = z4.object(BundleInputShape);
3743
+ var BundleInputSchema = z5.object(BundleInputShape);
2859
3744
 
2860
3745
  // src/schemas/simulate.ts
2861
- import { z as z5 } from "@walkeros/core/dev";
2862
- var PlatformSchema = z5.enum(["web", "server"]).describe("Platform type for event processing");
2863
- var SimulateOptionsSchema = z5.object({
2864
- silent: z5.boolean().optional().describe("Suppress all output"),
2865
- verbose: z5.boolean().optional().describe("Enable verbose logging"),
2866
- json: z5.boolean().optional().describe("Format output as JSON")
3746
+ import { z as z6 } from "@walkeros/core/dev";
3747
+ var PlatformSchema = z6.enum(["web", "server"]).describe("Platform type for event processing");
3748
+ var SimulateOptionsSchema = z6.object({
3749
+ silent: z6.boolean().optional().describe("Suppress all output"),
3750
+ verbose: z6.boolean().optional().describe("Enable verbose logging"),
3751
+ json: z6.boolean().optional().describe("Format output as JSON")
2867
3752
  });
2868
3753
  var SimulateInputShape = {
2869
3754
  configPath: FilePathSchema.describe("Path to flow configuration file"),
2870
- event: z5.string().min(1).describe("Event as JSON string, file path, or URL"),
2871
- flow: z5.string().optional().describe("Flow name for multi-flow configs"),
2872
- platform: PlatformSchema.optional().describe("Override platform detection")
3755
+ event: z6.string().min(1).optional().describe(
3756
+ "Event as JSON string, file path, or URL. Optional when example is provided."
3757
+ ),
3758
+ flow: z6.string().optional().describe("Flow name for multi-flow configs"),
3759
+ platform: PlatformSchema.optional().describe("Override platform detection"),
3760
+ example: z6.string().optional().describe(
3761
+ 'Name of a step example to use as event input (uses its "in" value)'
3762
+ ),
3763
+ step: z6.string().optional().describe(
3764
+ 'Step target in type.name format (e.g. "destination.gtag") to narrow example lookup'
3765
+ )
2873
3766
  };
2874
- var SimulateInputSchema = z5.object(SimulateInputShape);
3767
+ var SimulateInputSchema = z6.object(SimulateInputShape);
2875
3768
 
2876
3769
  // src/schemas/push.ts
2877
- import { z as z6 } from "@walkeros/core/dev";
2878
- var PushOptionsSchema = z6.object({
2879
- silent: z6.boolean().optional().describe("Suppress all output"),
2880
- verbose: z6.boolean().optional().describe("Enable verbose logging"),
2881
- json: z6.boolean().optional().describe("Format output as JSON")
3770
+ import { z as z7 } from "@walkeros/core/dev";
3771
+ var PushOptionsSchema = z7.object({
3772
+ silent: z7.boolean().optional().describe("Suppress all output"),
3773
+ verbose: z7.boolean().optional().describe("Enable verbose logging"),
3774
+ json: z7.boolean().optional().describe("Format output as JSON")
2882
3775
  });
2883
3776
  var PushInputShape = {
2884
3777
  configPath: FilePathSchema.describe("Path to flow configuration file"),
2885
- event: z6.string().min(1).describe("Event as JSON string, file path, or URL"),
2886
- flow: z6.string().optional().describe("Flow name for multi-flow configs"),
3778
+ event: z7.string().min(1).describe("Event as JSON string, file path, or URL"),
3779
+ flow: z7.string().optional().describe("Flow name for multi-flow configs"),
2887
3780
  platform: PlatformSchema.optional().describe("Override platform detection")
2888
3781
  };
2889
- var PushInputSchema = z6.object(PushInputShape);
3782
+ var PushInputSchema = z7.object(PushInputShape);
2890
3783
 
2891
3784
  // src/commands/run/validators.ts
2892
- function validateMode(mode) {
2893
- const result = RunModeSchema.safeParse(mode);
2894
- if (!result.success) {
2895
- throw new Error(
2896
- `Invalid mode: "${mode}"
2897
- Valid modes: collect, serve
2898
- Example: walkeros run collect ./flow.json`
2899
- );
2900
- }
2901
- }
2902
3785
  function validateFlowFile(filePath) {
2903
3786
  const absolutePath = resolveAsset(filePath, "bundle");
2904
3787
  if (!existsSync3(absolutePath)) {
@@ -2922,7 +3805,7 @@ function validatePort(port) {
2922
3805
  }
2923
3806
 
2924
3807
  // src/commands/run/utils.ts
2925
- import path13 from "path";
3808
+ import path12 from "path";
2926
3809
  import fs13 from "fs-extra";
2927
3810
  async function prepareBundleForRun(configPath, options) {
2928
3811
  const tempDir = getTmpPath(
@@ -2930,11 +3813,12 @@ async function prepareBundleForRun(configPath, options) {
2930
3813
  `run-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
2931
3814
  );
2932
3815
  await fs13.ensureDir(tempDir);
2933
- const tempPath = path13.join(tempDir, "bundle.mjs");
3816
+ const tempPath = path12.join(tempDir, "bundle.mjs");
2934
3817
  await bundle(configPath, {
2935
3818
  cache: true,
2936
3819
  verbose: options.verbose,
2937
3820
  silent: options.silent,
3821
+ flowName: options.flowName,
2938
3822
  buildOverrides: {
2939
3823
  output: tempPath,
2940
3824
  format: "esm",
@@ -2948,28 +3832,42 @@ function isPreBuiltConfig(configPath) {
2948
3832
  }
2949
3833
 
2950
3834
  // src/commands/run/execution.ts
2951
- import { createLogger as createLogger2, Level } from "@walkeros/core";
3835
+ import { createLogger as createLogger2, Level as Level4 } from "@walkeros/core";
2952
3836
 
2953
3837
  // src/runtime/runner.ts
2954
- import { pathToFileURL as pathToFileURL2 } from "url";
3838
+ import { pathToFileURL } from "url";
2955
3839
  import { resolve, dirname } from "path";
2956
- async function loadFlow(file, config, logger2, loggerConfig2) {
3840
+ async function loadFlow(file, config, logger2, loggerConfig2, healthServer) {
2957
3841
  const absolutePath = resolve(file);
2958
3842
  const flowDir = dirname(absolutePath);
2959
3843
  process.chdir(flowDir);
2960
- const fileUrl = pathToFileURL2(absolutePath).href;
3844
+ const fileUrl = pathToFileURL(absolutePath).href;
2961
3845
  const module = await import(`${fileUrl}?t=${Date.now()}`);
2962
3846
  if (!module.default || typeof module.default !== "function") {
2963
3847
  throw new Error(
2964
3848
  `Invalid flow bundle: ${file} must export a default function`
2965
3849
  );
2966
3850
  }
2967
- const flowContext = loggerConfig2 ? { ...config, logger: loggerConfig2 } : config;
3851
+ const flowContext = {
3852
+ ...config,
3853
+ ...loggerConfig2 ? { logger: loggerConfig2 } : {},
3854
+ ...healthServer ? { externalServer: true } : {}
3855
+ };
2968
3856
  const result = await module.default(flowContext);
2969
3857
  if (!result || !result.collector) {
2970
3858
  throw new Error(`Invalid flow bundle: ${file} must return { collector }`);
2971
3859
  }
2972
- return { collector: result.collector, file };
3860
+ if (healthServer && typeof result.httpHandler === "function") {
3861
+ healthServer.setFlowHandler(result.httpHandler);
3862
+ }
3863
+ return {
3864
+ collector: {
3865
+ command: result.collector.command,
3866
+ status: result.collector.status
3867
+ },
3868
+ file,
3869
+ httpHandler: result.httpHandler
3870
+ };
2973
3871
  }
2974
3872
  async function runFlow(file, config, logger2, loggerConfig2) {
2975
3873
  logger2.info(`Loading flow from ${file}`);
@@ -2981,13 +3879,19 @@ async function runFlow(file, config, logger2, loggerConfig2) {
2981
3879
  }
2982
3880
  const shutdown = async (signal) => {
2983
3881
  logger2.info(`Received ${signal}, shutting down gracefully...`);
3882
+ const forceTimer = setTimeout(() => {
3883
+ logger2.error("Shutdown timed out, forcing exit");
3884
+ process.exit(1);
3885
+ }, 15e3);
2984
3886
  try {
2985
3887
  if (handle.collector.command) {
2986
3888
  await handle.collector.command("shutdown");
2987
3889
  }
2988
3890
  logger2.info("Shutdown complete");
3891
+ clearTimeout(forceTimer);
2989
3892
  process.exit(0);
2990
3893
  } catch (error) {
3894
+ clearTimeout(forceTimer);
2991
3895
  const message = error instanceof Error ? error.message : String(error);
2992
3896
  logger2.error(`Error during shutdown: ${message}`);
2993
3897
  process.exit(1);
@@ -3007,198 +3911,73 @@ async function runFlow(file, config, logger2, loggerConfig2) {
3007
3911
  }
3008
3912
  }
3009
3913
 
3010
- // src/runtime/serve.ts
3011
- import express from "express";
3012
- import { resolve as resolve2 } from "path";
3013
-
3014
- // src/version.ts
3015
- import { readFileSync as readFileSync2 } from "fs";
3016
- import { fileURLToPath as fileURLToPath2 } from "url";
3017
- import { dirname as dirname2, join as join2 } from "path";
3018
- var versionFilename = fileURLToPath2(import.meta.url);
3019
- var versionDirname = dirname2(versionFilename);
3020
- function findPackageJson() {
3021
- const paths = [
3022
- join2(versionDirname, "../package.json"),
3023
- // dist/ or src/
3024
- join2(versionDirname, "../../package.json")
3025
- // src/core/ (not used, but safe)
3026
- ];
3027
- for (const p of paths) {
3028
- try {
3029
- return readFileSync2(p, "utf-8");
3030
- } catch {
3031
- }
3032
- }
3033
- return JSON.stringify({ version: "0.0.0" });
3034
- }
3035
- var VERSION = JSON.parse(findPackageJson()).version;
3036
-
3037
3914
  // src/runtime/heartbeat.ts
3915
+ init_version();
3038
3916
  import { randomBytes } from "crypto";
3039
3917
  var instanceId = randomBytes(8).toString("hex");
3040
- function getInstanceId() {
3041
- return instanceId;
3042
- }
3043
-
3044
- // src/runtime/status.ts
3045
- var state = null;
3046
- var startTime = Date.now();
3047
- var currentStatus = "starting";
3048
- var lastPollTime;
3049
- var lastHeartbeatTime;
3050
- function getStatus() {
3051
- return {
3052
- status: currentStatus,
3053
- version: VERSION,
3054
- mode: state?.mode ?? "collect",
3055
- instanceId: getInstanceId(),
3056
- configVersion: state?.configVersion,
3057
- configSource: state?.configSource ?? "local",
3058
- apiEnabled: state?.apiEnabled ?? false,
3059
- lastPoll: lastPollTime,
3060
- lastHeartbeat: lastHeartbeatTime,
3061
- uptime: Math.floor((Date.now() - startTime) / 1e3),
3062
- port: state?.port ?? 8080
3063
- };
3064
- }
3065
-
3066
- // src/runtime/serve.ts
3067
- async function runServeMode(config, logger2) {
3068
- const port = process.env.PORT ? parseInt(process.env.PORT, 10) : config?.port || 8080;
3069
- const host = process.env.HOST || config?.host || "0.0.0.0";
3070
- const file = resolve2(
3071
- process.env.BUNDLE || config?.file || "./dist/walker.js"
3072
- );
3073
- const serveName = process.env.SERVE_NAME || config?.serveName || "walker.js";
3074
- const servePath = process.env.SERVE_PATH || config?.servePath || "";
3075
- const urlPath = servePath ? `/${servePath}/${serveName}` : `/${serveName}`;
3076
- logger2.info("Starting single-file server...");
3077
- logger2.info(`File: ${file}`);
3078
- logger2.info(`URL: http://${host}:${port}${urlPath}`);
3079
- try {
3080
- const app = express();
3081
- app.get("/health", (req, res) => {
3082
- res.json({
3083
- status: "ok",
3084
- version: VERSION,
3085
- timestamp: Date.now(),
3086
- mode: "serve",
3087
- file,
3088
- url: urlPath
3089
- });
3090
- });
3091
- app.get("/status", (req, res) => {
3092
- res.json(getStatus());
3093
- });
3094
- app.get(urlPath, (req, res) => {
3095
- res.type("application/javascript");
3096
- res.sendFile(file, { dotfiles: "allow" }, (err) => {
3097
- if (err && !res.headersSent) {
3098
- const errCode = err.code;
3099
- const errStatus = err.status || err.statusCode;
3100
- if (errCode !== "ECONNABORTED") {
3101
- logger2.error(
3102
- `sendFile error for ${file}: ${err.message} (code: ${errCode}, status: ${errStatus})`
3103
- );
3104
- }
3105
- if (errStatus === 404 || errCode === "ENOENT" || errCode === "EISDIR" || errCode === "ENOTDIR") {
3106
- res.status(404).send("File not found");
3107
- } else if (errCode !== "ECONNABORTED") {
3108
- res.status(500).send("Internal server error");
3109
- }
3110
- }
3111
- });
3112
- });
3113
- const server = app.listen(port, host, () => {
3114
- logger2.info(`Server listening on http://${host}:${port}`);
3115
- logger2.info(`GET ${urlPath} - Bundle file`);
3116
- logger2.info(`GET /health - Health check`);
3117
- });
3118
- const shutdownHandler = (signal) => {
3119
- logger2.info(`Received ${signal}, shutting down...`);
3120
- server.close(() => {
3121
- logger2.info("Server closed");
3122
- process.exit(0);
3123
- });
3124
- };
3125
- process.on("SIGTERM", () => shutdownHandler("SIGTERM"));
3126
- process.on("SIGINT", () => shutdownHandler("SIGINT"));
3127
- await new Promise(() => {
3128
- });
3129
- } catch (error) {
3130
- const message = error instanceof Error ? error.message : String(error);
3131
- logger2.error(`Server failed: ${message}`);
3132
- process.exit(1);
3133
- }
3134
- }
3135
3918
 
3136
3919
  // src/commands/run/execution.ts
3137
- var logLevel = process.env.VERBOSE === "true" ? Level.DEBUG : Level.INFO;
3920
+ var logLevel = process.env.VERBOSE === "true" ? Level4.DEBUG : Level4.INFO;
3138
3921
  var loggerConfig = { level: logLevel };
3139
3922
  var logger = createLogger2(loggerConfig);
3140
- async function executeRunLocal(mode, flowPath, options) {
3141
- switch (mode) {
3142
- case "collect": {
3143
- if (!flowPath) {
3144
- throw new Error("Flow path is required for collect mode");
3145
- }
3146
- const config = {
3147
- port: options.port,
3148
- host: options.host
3149
- };
3150
- await runFlow(flowPath, config, logger.scope("runner"), loggerConfig);
3151
- break;
3152
- }
3153
- case "serve": {
3154
- const config = {
3155
- port: options.port,
3156
- host: options.host,
3157
- serveName: options.serveName,
3158
- servePath: options.servePath,
3159
- file: flowPath || void 0
3160
- };
3161
- await runServeMode(config, logger.scope("serve"));
3162
- break;
3163
- }
3164
- default:
3165
- throw new Error(`Unknown mode: ${mode}`);
3166
- }
3923
+ async function executeRunLocal(flowPath, options) {
3924
+ const config = {
3925
+ port: options.port,
3926
+ host: options.host
3927
+ };
3928
+ await runFlow(flowPath, config, logger.scope("runner"), loggerConfig);
3167
3929
  }
3168
3930
 
3169
3931
  // src/commands/run/index.ts
3170
- async function runCommand(mode, options) {
3932
+ async function runCommand(options) {
3171
3933
  const timer = createTimer();
3172
3934
  timer.start();
3173
- const logger2 = createCommandLogger(options);
3935
+ const logger2 = createCLILogger(options);
3174
3936
  try {
3175
- validateMode(mode);
3176
3937
  const configPath = validateFlowFile(options.config);
3177
3938
  if (options.port !== void 0) {
3178
3939
  validatePort(options.port);
3179
3940
  }
3180
- const isPreBuilt = isPreBuiltConfig(configPath);
3181
- let flowPath = null;
3182
- if (mode === "collect") {
3183
- if (isPreBuilt) {
3184
- flowPath = path14.resolve(configPath);
3185
- logger2.debug(`Using pre-built flow: ${path14.basename(flowPath)}`);
3186
- } else {
3187
- logger2.debug("Building flow bundle");
3188
- flowPath = await prepareBundleForRun(configPath, {
3189
- verbose: options.verbose,
3190
- silent: options.json || options.silent
3191
- });
3192
- logger2.debug("Bundle ready");
3941
+ const runtimeDeps = ["express", "cors"];
3942
+ for (const dep of runtimeDeps) {
3943
+ try {
3944
+ __require.resolve(dep);
3945
+ } catch {
3946
+ logger2.error(
3947
+ `Missing runtime dependency "${dep}"
3948
+ Server flows require express and cors when running outside Docker.
3949
+ Run: npm install express cors`
3950
+ );
3951
+ process.exit(1);
3193
3952
  }
3194
3953
  }
3195
- const modeLabel = mode === "collect" ? "Collector" : "Server";
3196
- logger2.log(`Starting ${modeLabel}...`);
3197
- await executeRunLocal(mode, flowPath, {
3954
+ const isPreBuilt = isPreBuiltConfig(configPath);
3955
+ let flowPath;
3956
+ if (isPreBuilt) {
3957
+ flowPath = path13.resolve(configPath);
3958
+ logger2.debug(`Using pre-built flow: ${path13.basename(flowPath)}`);
3959
+ } else {
3960
+ logger2.debug("Building flow bundle");
3961
+ flowPath = await prepareBundleForRun(configPath, {
3962
+ verbose: options.verbose,
3963
+ silent: options.json || options.silent
3964
+ });
3965
+ logger2.debug("Bundle ready");
3966
+ }
3967
+ if (options.deployment) {
3968
+ const { startHeartbeat: startHeartbeat2 } = await Promise.resolve().then(() => (init_heartbeat(), heartbeat_exports));
3969
+ await startHeartbeat2({
3970
+ deployment: options.deployment,
3971
+ projectId: options.project,
3972
+ url: options.url || `http://localhost:${options.port || 8080}`,
3973
+ healthEndpoint: options.healthEndpoint,
3974
+ heartbeatInterval: options.heartbeatInterval
3975
+ });
3976
+ }
3977
+ logger2.info("Starting flow...");
3978
+ await executeRunLocal(flowPath, {
3198
3979
  port: options.port,
3199
- host: options.host,
3200
- serveName: options.serveName,
3201
- servePath: options.servePath
3980
+ host: options.host
3202
3981
  });
3203
3982
  } catch (error) {
3204
3983
  const duration = timer.getElapsed() / 1e3;
@@ -3206,7 +3985,6 @@ async function runCommand(mode, options) {
3206
3985
  if (options.json) {
3207
3986
  logger2.json({
3208
3987
  success: false,
3209
- mode,
3210
3988
  error: errorMessage,
3211
3989
  duration
3212
3990
  });
@@ -3216,10 +3994,9 @@ async function runCommand(mode, options) {
3216
3994
  process.exit(1);
3217
3995
  }
3218
3996
  }
3219
- async function run(mode, options) {
3220
- const startTime2 = Date.now();
3997
+ async function run(options) {
3998
+ const startTime = Date.now();
3221
3999
  try {
3222
- validateMode(mode);
3223
4000
  let flowFile;
3224
4001
  if (typeof options.config === "string") {
3225
4002
  flowFile = validateFlowFile(options.config);
@@ -3229,40 +4006,131 @@ async function run(mode, options) {
3229
4006
  if (options.port !== void 0) {
3230
4007
  validatePort(options.port);
3231
4008
  }
4009
+ const runtimeDeps = ["express", "cors"];
4010
+ for (const dep of runtimeDeps) {
4011
+ try {
4012
+ __require.resolve(dep);
4013
+ } catch {
4014
+ throw new Error(
4015
+ `Missing runtime dependency "${dep}". Server flows require express and cors when running outside Docker. Run: npm install express cors`
4016
+ );
4017
+ }
4018
+ }
3232
4019
  const isPreBuilt = isPreBuiltConfig(flowFile);
3233
4020
  let flowPath;
3234
4021
  if (isPreBuilt) {
3235
- flowPath = path14.resolve(flowFile);
4022
+ flowPath = path13.resolve(flowFile);
3236
4023
  } else {
3237
4024
  flowPath = await prepareBundleForRun(flowFile, {
3238
4025
  verbose: options.verbose,
3239
4026
  silent: true
3240
4027
  });
3241
4028
  }
3242
- await executeRunLocal(mode, flowPath, {
4029
+ await executeRunLocal(flowPath, {
3243
4030
  port: options.port,
3244
- host: options.host,
3245
- serveName: options.serveName,
3246
- servePath: options.servePath
4031
+ host: options.host
3247
4032
  });
3248
4033
  return {
3249
4034
  success: true,
3250
4035
  exitCode: 0,
3251
- duration: Date.now() - startTime2
4036
+ duration: Date.now() - startTime
3252
4037
  };
3253
4038
  } catch (error) {
3254
4039
  return {
3255
4040
  success: false,
3256
4041
  exitCode: 1,
3257
- duration: Date.now() - startTime2,
4042
+ duration: Date.now() - startTime,
3258
4043
  error: getErrorMessage(error)
3259
4044
  };
3260
4045
  }
3261
4046
  }
3262
4047
 
3263
4048
  // src/commands/validate/index.ts
4049
+ init_cli_logger();
3264
4050
  import chalk2 from "chalk";
3265
4051
 
4052
+ // src/commands/validate/validators/contract.ts
4053
+ function validateContract(input) {
4054
+ const errors = [];
4055
+ const warnings = [];
4056
+ const details = {};
4057
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
4058
+ errors.push({
4059
+ path: "root",
4060
+ message: "Contract must be an object",
4061
+ code: "INVALID_CONTRACT"
4062
+ });
4063
+ return { valid: false, type: "contract", errors, warnings, details };
4064
+ }
4065
+ const contract = input;
4066
+ let entityCount = 0;
4067
+ let actionCount = 0;
4068
+ if ("$tagging" in contract) {
4069
+ const tagging = contract.$tagging;
4070
+ if (typeof tagging !== "number" || !Number.isInteger(tagging) || tagging < 0) {
4071
+ errors.push({
4072
+ path: "$tagging",
4073
+ message: "$tagging must be a non-negative integer",
4074
+ value: tagging,
4075
+ code: "INVALID_TAGGING"
4076
+ });
4077
+ } else {
4078
+ details.tagging = tagging;
4079
+ }
4080
+ }
4081
+ for (const [entityKey, entityValue] of Object.entries(contract)) {
4082
+ if (entityKey.startsWith("$")) continue;
4083
+ if (entityKey.trim() === "") {
4084
+ errors.push({
4085
+ path: entityKey,
4086
+ message: "Entity key cannot be empty",
4087
+ code: "INVALID_ENTITY_KEY"
4088
+ });
4089
+ continue;
4090
+ }
4091
+ if (typeof entityValue !== "object" || entityValue === null) {
4092
+ errors.push({
4093
+ path: entityKey,
4094
+ message: `Entity "${entityKey}" must be an object of action entries`,
4095
+ value: entityValue,
4096
+ code: "INVALID_ENTITY"
4097
+ });
4098
+ continue;
4099
+ }
4100
+ entityCount++;
4101
+ const actions = entityValue;
4102
+ for (const [actionKey, actionValue] of Object.entries(actions)) {
4103
+ if (actionKey.trim() === "") {
4104
+ errors.push({
4105
+ path: `${entityKey}.${actionKey}`,
4106
+ message: "Action key cannot be empty",
4107
+ code: "INVALID_ACTION_KEY"
4108
+ });
4109
+ continue;
4110
+ }
4111
+ if (typeof actionValue !== "object" || actionValue === null || Array.isArray(actionValue)) {
4112
+ errors.push({
4113
+ path: `${entityKey}.${actionKey}`,
4114
+ message: `Contract entry must be a JSON Schema object`,
4115
+ value: typeof actionValue,
4116
+ code: "INVALID_SCHEMA_ENTRY"
4117
+ });
4118
+ continue;
4119
+ }
4120
+ actionCount++;
4121
+ }
4122
+ }
4123
+ details.entityCount = entityCount;
4124
+ details.actionCount = actionCount;
4125
+ return {
4126
+ valid: errors.length === 0,
4127
+ type: "contract",
4128
+ errors,
4129
+ warnings,
4130
+ details
4131
+ };
4132
+ }
4133
+
3266
4134
  // src/commands/validate/validators/event.ts
3267
4135
  import { schemas as schemas3 } from "@walkeros/core/dev";
3268
4136
  var { PartialEventSchema } = schemas3;
@@ -3306,10 +4174,10 @@ function validateEvent(input) {
3306
4174
  const zodResult = PartialEventSchema.safeParse(input);
3307
4175
  if (!zodResult.success) {
3308
4176
  for (const issue of zodResult.error.issues) {
3309
- const path15 = issue.path.join(".");
3310
- if (path15 === "name") continue;
4177
+ const path14 = issue.path.join(".");
4178
+ if (path14 === "name") continue;
3311
4179
  errors.push({
3312
- path: path15 || "root",
4180
+ path: path14 || "root",
3313
4181
  message: issue.message,
3314
4182
  code: "SCHEMA_VALIDATION"
3315
4183
  });
@@ -3336,23 +4204,37 @@ function validateEvent(input) {
3336
4204
 
3337
4205
  // src/commands/validate/validators/flow.ts
3338
4206
  import { schemas as schemas4 } from "@walkeros/core/dev";
3339
- var { SetupSchema } = schemas4;
4207
+ var { validateFlowSetup: validateFlowSetup2 } = schemas4;
3340
4208
  function validateFlow(input, options = {}) {
3341
4209
  const errors = [];
3342
4210
  const warnings = [];
3343
4211
  const details = {};
3344
- const config = typeof input === "object" && input !== null ? input : {};
3345
- const zodResult = SetupSchema.safeParse(input);
3346
- if (!zodResult.success) {
3347
- for (const issue of zodResult.error.issues) {
3348
- const path15 = issue.path.join(".");
3349
- errors.push({
3350
- path: path15 || "root",
3351
- message: issue.message,
3352
- code: "SCHEMA_VALIDATION"
3353
- });
3354
- }
4212
+ let json;
4213
+ try {
4214
+ json = JSON.stringify(input, null, 2);
4215
+ } catch {
4216
+ errors.push({
4217
+ path: "root",
4218
+ message: "Input cannot be serialized to JSON",
4219
+ code: "SERIALIZATION_ERROR"
4220
+ });
4221
+ return { valid: false, type: "flow", errors, warnings, details };
4222
+ }
4223
+ const coreResult = validateFlowSetup2(json);
4224
+ for (const issue of coreResult.errors) {
4225
+ errors.push({
4226
+ path: issue.path || "root",
4227
+ message: issue.message,
4228
+ code: "SCHEMA_VALIDATION"
4229
+ });
4230
+ }
4231
+ for (const issue of coreResult.warnings) {
4232
+ warnings.push({
4233
+ path: issue.path || "root",
4234
+ message: issue.message
4235
+ });
3355
4236
  }
4237
+ const config = typeof input === "object" && input !== null ? input : {};
3356
4238
  const flows = config.flows;
3357
4239
  if (flows && typeof flows === "object" && Object.keys(flows).length === 0) {
3358
4240
  errors.push({
@@ -3390,6 +4272,34 @@ function validateFlow(input, options = {}) {
3390
4272
  }
3391
4273
  details.packageCount = Object.keys(packages).length;
3392
4274
  }
4275
+ if (coreResult.context) {
4276
+ details.context = coreResult.context;
4277
+ }
4278
+ if (flows && typeof flows === "object" && errors.length === 0) {
4279
+ const flowNames = Object.keys(flows);
4280
+ const flowsToCheck = options.flow ? [options.flow] : flowNames;
4281
+ let totalConnections = 0;
4282
+ for (const name of flowsToCheck) {
4283
+ const flowConfig = flows[name];
4284
+ if (!flowConfig) continue;
4285
+ checkExampleCoverage(flowConfig, warnings);
4286
+ const connections = buildConnectionGraph(flowConfig);
4287
+ for (const conn of connections) {
4288
+ checkCompatibility(conn, errors, warnings);
4289
+ }
4290
+ totalConnections += connections.length;
4291
+ const setupContract = config.contract;
4292
+ if (setupContract || flowConfig.contract) {
4293
+ checkContractCompliance(
4294
+ flowConfig,
4295
+ setupContract,
4296
+ flowConfig.contract,
4297
+ warnings
4298
+ );
4299
+ }
4300
+ }
4301
+ details.connectionsChecked = totalConnections;
4302
+ }
3393
4303
  return {
3394
4304
  valid: errors.length === 0,
3395
4305
  type: "flow",
@@ -3398,6 +4308,147 @@ function validateFlow(input, options = {}) {
3398
4308
  details
3399
4309
  };
3400
4310
  }
4311
+ function checkExampleCoverage(config, warnings) {
4312
+ const stepTypes = [
4313
+ { key: "sources", type: "source" },
4314
+ { key: "transformers", type: "transformer" },
4315
+ { key: "destinations", type: "destination" }
4316
+ ];
4317
+ for (const { key, type } of stepTypes) {
4318
+ const refs = config[key];
4319
+ if (!refs) continue;
4320
+ for (const [name, ref] of Object.entries(refs)) {
4321
+ if (!ref.examples || Object.keys(ref.examples).length === 0) {
4322
+ warnings.push({
4323
+ path: `${type}.${name}`,
4324
+ message: `Step has no examples`,
4325
+ suggestion: `Add examples to ${type}.${name} for testing and documentation`
4326
+ });
4327
+ }
4328
+ }
4329
+ }
4330
+ }
4331
+ function buildConnectionGraph(config) {
4332
+ const connections = [];
4333
+ for (const [name, source] of Object.entries(config.sources || {})) {
4334
+ if (!source.next || !source.examples) continue;
4335
+ const nextNames = Array.isArray(source.next) ? source.next : [source.next];
4336
+ for (const nextName of nextNames) {
4337
+ const transformer = config.transformers?.[nextName];
4338
+ if (transformer?.examples) {
4339
+ connections.push({
4340
+ from: { type: "source", name, examples: source.examples },
4341
+ to: {
4342
+ type: "transformer",
4343
+ name: nextName,
4344
+ examples: transformer.examples
4345
+ }
4346
+ });
4347
+ }
4348
+ }
4349
+ }
4350
+ for (const [name, transformer] of Object.entries(config.transformers || {})) {
4351
+ if (!transformer.next || !transformer.examples) continue;
4352
+ const nextNames = Array.isArray(transformer.next) ? transformer.next : [transformer.next];
4353
+ for (const nextName of nextNames) {
4354
+ const nextTransformer = config.transformers?.[nextName];
4355
+ if (nextTransformer?.examples) {
4356
+ connections.push({
4357
+ from: {
4358
+ type: "transformer",
4359
+ name,
4360
+ examples: transformer.examples
4361
+ },
4362
+ to: {
4363
+ type: "transformer",
4364
+ name: nextName,
4365
+ examples: nextTransformer.examples
4366
+ }
4367
+ });
4368
+ }
4369
+ }
4370
+ }
4371
+ for (const [name, dest] of Object.entries(config.destinations || {})) {
4372
+ if (!dest.before || !dest.examples) continue;
4373
+ const beforeNames = Array.isArray(dest.before) ? dest.before : [dest.before];
4374
+ for (const beforeName of beforeNames) {
4375
+ const transformer = config.transformers?.[beforeName];
4376
+ if (transformer?.examples) {
4377
+ connections.push({
4378
+ from: {
4379
+ type: "transformer",
4380
+ name: beforeName,
4381
+ examples: transformer.examples
4382
+ },
4383
+ to: { type: "destination", name, examples: dest.examples }
4384
+ });
4385
+ }
4386
+ }
4387
+ }
4388
+ return connections;
4389
+ }
4390
+ function checkCompatibility(conn, errors, warnings) {
4391
+ const fromOuts = Object.entries(conn.from.examples).filter(([, ex]) => ex.out !== void 0 && ex.out !== false).map(([name, ex]) => ({ name, value: ex.out }));
4392
+ const toIns = Object.entries(conn.to.examples).filter(([, ex]) => ex.in !== void 0).map(([name, ex]) => ({ name, value: ex.in }));
4393
+ const path14 = `${conn.from.type}.${conn.from.name} \u2192 ${conn.to.type}.${conn.to.name}`;
4394
+ if (fromOuts.length === 0 || toIns.length === 0) {
4395
+ warnings.push({
4396
+ path: path14,
4397
+ message: "Cannot check compatibility: missing out or in examples",
4398
+ suggestion: "Add out examples to the source step or in examples to the target step"
4399
+ });
4400
+ return;
4401
+ }
4402
+ let hasMatch = false;
4403
+ for (const out of fromOuts) {
4404
+ for (const inp of toIns) {
4405
+ if (isStructurallyCompatible(out.value, inp.value)) {
4406
+ hasMatch = true;
4407
+ break;
4408
+ }
4409
+ }
4410
+ if (hasMatch) break;
4411
+ }
4412
+ if (!hasMatch) {
4413
+ errors.push({
4414
+ path: path14,
4415
+ message: "No compatible out/in pair found between connected steps",
4416
+ code: "INCOMPATIBLE_EXAMPLES"
4417
+ });
4418
+ }
4419
+ }
4420
+ function isStructurallyCompatible(a2, b2) {
4421
+ if (typeof a2 !== typeof b2) return false;
4422
+ if (a2 === null || b2 === null) return a2 === b2;
4423
+ if (Array.isArray(a2) && Array.isArray(b2)) return true;
4424
+ if (typeof a2 === "object" && typeof b2 === "object") {
4425
+ const keysA = Object.keys(a2);
4426
+ const keysB = Object.keys(b2);
4427
+ const shared = keysA.filter((k2) => keysB.includes(k2));
4428
+ return shared.length >= Math.min(keysA.length, keysB.length) * 0.5;
4429
+ }
4430
+ return true;
4431
+ }
4432
+ function checkContractCompliance(config, setupContract, flowContract, warnings) {
4433
+ for (const [name, dest] of Object.entries(config.destinations || {})) {
4434
+ if (!dest.examples) continue;
4435
+ for (const [exName, example] of Object.entries(dest.examples)) {
4436
+ if (!example.in || typeof example.in !== "object") continue;
4437
+ const event = example.in;
4438
+ if (!event.entity || !event.action) continue;
4439
+ const contract = flowContract?.[event.entity] || setupContract?.[event.entity];
4440
+ if (!contract || typeof contract !== "object") continue;
4441
+ const actionSchema = contract[event.action] || contract["*"];
4442
+ if (actionSchema) {
4443
+ warnings.push({
4444
+ path: `destination.${name}.examples.${exName}`,
4445
+ message: `Example has contract for ${event.entity}.${event.action}`,
4446
+ suggestion: "Verify example data matches contract schema"
4447
+ });
4448
+ }
4449
+ }
4450
+ }
4451
+ }
3401
4452
 
3402
4453
  // src/commands/validate/validators/mapping.ts
3403
4454
  function validateMapping(input) {
@@ -3434,7 +4485,7 @@ function validateMapping(input) {
3434
4485
  });
3435
4486
  }
3436
4487
  const rule = mapping[pattern];
3437
- const isValidRule = Array.isArray(rule) ? rule.every((r) => typeof r === "object" && r !== null) : typeof rule === "object" && rule !== null;
4488
+ const isValidRule = Array.isArray(rule) ? rule.every((r2) => typeof r2 === "object" && r2 !== null) : typeof rule === "object" && rule !== null;
3438
4489
  if (!isValidRule) {
3439
4490
  errors.push({
3440
4491
  path: pattern,
@@ -3456,13 +4507,13 @@ function validateMapping(input) {
3456
4507
  import Ajv from "ajv";
3457
4508
  import { fetchPackageSchema } from "@walkeros/core";
3458
4509
  var SECTIONS = ["destinations", "sources", "transformers"];
3459
- function resolveEntry(path15, flowConfig) {
4510
+ function resolveEntry(path14, flowConfig) {
3460
4511
  const flows = flowConfig.flows;
3461
4512
  if (!flows || typeof flows !== "object") return "No flows found in config";
3462
4513
  const flowName = Object.keys(flows)[0];
3463
4514
  const flow = flows[flowName];
3464
4515
  if (!flow) return `Flow "${flowName}" is empty`;
3465
- const parts = path15.split(".");
4516
+ const parts = path14.split(".");
3466
4517
  if (parts.length === 2) {
3467
4518
  const [section, key] = parts;
3468
4519
  if (!SECTIONS.includes(section)) {
@@ -3494,20 +4545,20 @@ function resolveEntry(path15, flowConfig) {
3494
4545
  return `Entry "${key}" not found in any section`;
3495
4546
  }
3496
4547
  if (matches.length > 1) {
3497
- const sections = matches.map((m) => m.section).join(", ");
4548
+ const sections = matches.map((m2) => m2.section).join(", ");
3498
4549
  return `Ambiguous key "${key}" found in multiple sections: ${sections}. Use dot-notation (e.g., destinations.${key})`;
3499
4550
  }
3500
4551
  return { section: matches[0].section, key, entry: matches[0].entry };
3501
4552
  }
3502
- return `Invalid path "${path15}". Use "section.key" or just "key"`;
4553
+ return `Invalid path "${path14}". Use "section.key" or just "key"`;
3503
4554
  }
3504
- async function validateEntry(path15, flowConfig) {
3505
- const resolved = resolveEntry(path15, flowConfig);
4555
+ async function validateEntry(path14, flowConfig) {
4556
+ const resolved = resolveEntry(path14, flowConfig);
3506
4557
  if (typeof resolved === "string") {
3507
4558
  return {
3508
4559
  valid: false,
3509
4560
  type: "entry",
3510
- errors: [{ path: path15, message: resolved, code: "ENTRY_VALIDATION" }],
4561
+ errors: [{ path: path14, message: resolved, code: "ENTRY_VALIDATION" }],
3511
4562
  warnings: [],
3512
4563
  details: {}
3513
4564
  };
@@ -3538,7 +4589,7 @@ async function validateEntry(path15, flowConfig) {
3538
4589
  type: "entry",
3539
4590
  errors: [
3540
4591
  {
3541
- path: path15,
4592
+ path: path14,
3542
4593
  message: error instanceof Error ? error.message : "Unknown error",
3543
4594
  code: "ENTRY_VALIDATION"
3544
4595
  }
@@ -3563,10 +4614,10 @@ async function validateEntry(path15, flowConfig) {
3563
4614
  const validate2 = ajv.compile(settingsSchema);
3564
4615
  const isValid = validate2(settings || {});
3565
4616
  if (!isValid) {
3566
- const errors = (validate2.errors || []).map((e) => ({
3567
- path: e.instancePath || "/",
3568
- message: e.message || "Unknown error",
3569
- code: e.keyword
4617
+ const errors = (validate2.errors || []).map((e2) => ({
4618
+ path: e2.instancePath || "/",
4619
+ message: e2.message || "Unknown error",
4620
+ code: e2.keyword
3570
4621
  }));
3571
4622
  return {
3572
4623
  valid: false,
@@ -3587,10 +4638,12 @@ async function validateEntry(path15, flowConfig) {
3587
4638
 
3588
4639
  // src/commands/validate/index.ts
3589
4640
  async function validate(type, input, options = {}) {
3590
- if (type.includes(".") || !["event", "flow", "mapping"].includes(type)) {
3591
- return validateEntry(type, input);
4641
+ if (options.path) {
4642
+ return validateEntry(options.path, input);
3592
4643
  }
3593
4644
  switch (type) {
4645
+ case "contract":
4646
+ return validateContract(input);
3594
4647
  case "event":
3595
4648
  return validateEvent(input);
3596
4649
  case "flow":
@@ -3636,7 +4689,7 @@ function formatResult(result, options) {
3636
4689
  return lines.join("\n");
3637
4690
  }
3638
4691
  async function validateCommand(options) {
3639
- const logger2 = createCommandLogger({ ...options, stderr: true });
4692
+ const logger2 = createCLILogger({ ...options, stderr: true });
3640
4693
  try {
3641
4694
  let input;
3642
4695
  if (isStdinPiped() && !options.input) {
@@ -3653,7 +4706,8 @@ async function validateCommand(options) {
3653
4706
  });
3654
4707
  }
3655
4708
  const result = await validate(options.type, input, {
3656
- flow: options.flow
4709
+ flow: options.flow,
4710
+ path: options.path
3657
4711
  });
3658
4712
  const formatted = formatResult(result, {
3659
4713
  json: options.json,
@@ -3692,6 +4746,8 @@ async function validateCommand(options) {
3692
4746
  }
3693
4747
 
3694
4748
  // src/commands/login/index.ts
4749
+ init_cli_logger();
4750
+ init_config_file();
3695
4751
  import { hostname } from "os";
3696
4752
  var POLL_TIMEOUT_BUFFER_MS = 5e3;
3697
4753
  async function openInBrowser(url) {
@@ -3699,18 +4755,14 @@ async function openInBrowser(url) {
3699
4755
  await open(url);
3700
4756
  }
3701
4757
  async function loginCommand(options) {
3702
- const logger2 = createLogger({
3703
- verbose: options.verbose,
3704
- silent: options.silent,
3705
- json: options.json
3706
- });
4758
+ const logger2 = createCLILogger(options);
3707
4759
  try {
3708
4760
  const result = await login({ url: options.url });
3709
4761
  if (options.json) {
3710
4762
  logger2.json(result);
3711
4763
  } else if (result.success) {
3712
- logger2.success(`Logged in as ${result.email}`);
3713
- logger2.log(`Token stored in ${result.configPath}`);
4764
+ logger2.info(`Logged in as ${result.email}`);
4765
+ logger2.info(`Token stored in ${result.configPath}`);
3714
4766
  }
3715
4767
  process.exit(result.success ? 0 : 1);
3716
4768
  } catch (error) {
@@ -3725,8 +4777,8 @@ async function loginCommand(options) {
3725
4777
  }
3726
4778
  async function login(options = {}) {
3727
4779
  const appUrl = options.url || resolveAppUrl();
3728
- const f = options.fetch ?? globalThis.fetch;
3729
- const codeResponse = await f(`${appUrl}/api/auth/device/code`, {
4780
+ const f2 = options.fetch ?? globalThis.fetch;
4781
+ const codeResponse = await f2(`${appUrl}/api/auth/device/code`, {
3730
4782
  method: "POST",
3731
4783
  headers: { "Content-Type": "application/json" },
3732
4784
  body: JSON.stringify({})
@@ -3745,7 +4797,7 @@ async function login(options = {}) {
3745
4797
  const prompt = (msg) => process.stderr.write(msg + "\n");
3746
4798
  prompt(`
3747
4799
  ! Your one-time code: ${userCode}`);
3748
- prompt(` Authorize here: ${verificationUri}
4800
+ prompt(` Authorize here: ${verificationUriComplete || verificationUri}
3749
4801
  `);
3750
4802
  const opener = options.openUrl ?? openInBrowser;
3751
4803
  try {
@@ -3761,8 +4813,8 @@ async function login(options = {}) {
3761
4813
  let attempts = 0;
3762
4814
  while (Date.now() < deadline && attempts < maxAttempts) {
3763
4815
  attempts++;
3764
- await new Promise((r) => setTimeout(r, pollInterval));
3765
- const tokenResponse = await f(`${appUrl}/api/auth/device/token`, {
4816
+ await new Promise((r2) => setTimeout(r2, pollInterval));
4817
+ const tokenResponse = await f2(`${appUrl}/api/auth/device/token`, {
3766
4818
  method: "POST",
3767
4819
  headers: { "Content-Type": "application/json" },
3768
4820
  body: JSON.stringify({ deviceCode, hostname: hostname() })
@@ -3787,25 +4839,24 @@ async function login(options = {}) {
3787
4839
  }
3788
4840
 
3789
4841
  // src/commands/logout/index.ts
4842
+ init_cli_logger();
4843
+ init_config_file();
3790
4844
  async function logoutCommand(options) {
3791
- const logger2 = createLogger({
3792
- verbose: options.verbose,
3793
- silent: options.silent,
3794
- json: options.json
3795
- });
4845
+ const logger2 = createCLILogger(options);
3796
4846
  const deleted = deleteConfig();
3797
4847
  const configPath = getConfigPath();
3798
4848
  if (options.json) {
3799
4849
  logger2.json({ success: true, deleted });
3800
4850
  } else if (deleted) {
3801
- logger2.success(`Logged out. Token removed from ${configPath}`);
4851
+ logger2.info(`Logged out. Token removed from ${configPath}`);
3802
4852
  } else {
3803
- logger2.log("No stored credentials found.");
4853
+ logger2.info("No stored credentials found.");
3804
4854
  }
3805
4855
  process.exit(0);
3806
4856
  }
3807
4857
 
3808
4858
  // src/commands/auth/index.ts
4859
+ init_cli_logger();
3809
4860
  async function whoami() {
3810
4861
  const client = createApiClient();
3811
4862
  const { data, error } = await client.GET("/api/auth/whoami");
@@ -3813,16 +4864,16 @@ async function whoami() {
3813
4864
  return data;
3814
4865
  }
3815
4866
  async function whoamiCommand(options) {
3816
- const logger2 = createCommandLogger(options);
4867
+ const logger2 = createCLILogger(options);
3817
4868
  try {
3818
4869
  const result = await whoami();
3819
4870
  if (options.json) {
3820
4871
  await writeResult(JSON.stringify(result, null, 2), options);
3821
4872
  } else {
3822
4873
  const data = result;
3823
- if (data.email) logger2.log(`${data.email}`);
3824
- if (data.userId) logger2.log(`User: ${data.userId}`);
3825
- if (data.projectId) logger2.log(`Project: ${data.projectId}`);
4874
+ if (data.email) logger2.info(`${data.email}`);
4875
+ if (data.userId) logger2.info(`User: ${data.userId}`);
4876
+ if (data.projectId) logger2.info(`Project: ${data.projectId}`);
3826
4877
  }
3827
4878
  } catch (error) {
3828
4879
  logger2.error(error instanceof Error ? error.message : String(error));
@@ -3831,6 +4882,8 @@ async function whoamiCommand(options) {
3831
4882
  }
3832
4883
 
3833
4884
  // src/commands/projects/index.ts
4885
+ init_auth();
4886
+ init_cli_logger();
3834
4887
  async function listProjects() {
3835
4888
  const client = createApiClient();
3836
4889
  const { data, error } = await client.GET("/api/projects");
@@ -3876,10 +4929,10 @@ async function deleteProject(options = {}) {
3876
4929
  throw new Error(error.error?.message || "Failed to delete project");
3877
4930
  return data ?? { success: true };
3878
4931
  }
3879
- async function handleResult(fn, options) {
3880
- const logger2 = createCommandLogger(options);
4932
+ async function handleResult(fn2, options) {
4933
+ const logger2 = createCLILogger(options);
3881
4934
  try {
3882
- const result = await fn();
4935
+ const result = await fn2();
3883
4936
  await writeResult(JSON.stringify(result, null, 2), options);
3884
4937
  } catch (error) {
3885
4938
  logger2.error(error instanceof Error ? error.message : String(error));
@@ -3919,6 +4972,8 @@ async function deleteProjectCommand(projectId, options) {
3919
4972
  }
3920
4973
 
3921
4974
  // src/commands/flows/index.ts
4975
+ init_auth();
4976
+ init_cli_logger();
3922
4977
  async function listFlows(options = {}) {
3923
4978
  const id = options.projectId ?? requireProjectId();
3924
4979
  const client = createApiClient();
@@ -4001,10 +5056,10 @@ async function duplicateFlow(options) {
4001
5056
  throw new Error(error.error?.message || "Failed to duplicate flow");
4002
5057
  return data;
4003
5058
  }
4004
- async function handleResult2(fn, options) {
4005
- const logger2 = createCommandLogger(options);
5059
+ async function handleResult2(fn2, options) {
5060
+ const logger2 = createCLILogger(options);
4006
5061
  try {
4007
- const result = await fn();
5062
+ const result = await fn2();
4008
5063
  await writeResult(JSON.stringify(result, null, 2), options);
4009
5064
  } catch (error) {
4010
5065
  logger2.error(error instanceof Error ? error.message : String(error));
@@ -4067,27 +5122,73 @@ async function readFlowStdin() {
4067
5122
  }
4068
5123
 
4069
5124
  // src/commands/deploy/index.ts
5125
+ init_auth();
5126
+ init_cli_logger();
4070
5127
  async function resolveConfigId(options) {
4071
5128
  const flow = await getFlow({
4072
5129
  flowId: options.flowId,
4073
5130
  projectId: options.projectId
4074
5131
  });
4075
- const content = flow.content;
4076
- const flowNames = Object.keys(content.flows ?? {});
4077
- if (!flowNames.includes(options.flowName)) {
5132
+ const configs = flow.configs;
5133
+ if (!configs?.length) {
5134
+ throw new Error("Flow has no configs.");
5135
+ }
5136
+ const match = configs.find((c2) => c2.name === options.flowName);
5137
+ if (!match) {
4078
5138
  throw new Error(
4079
- `Flow "${options.flowName}" not found. Available: ${flowNames.join(", ")}`
5139
+ `Flow "${options.flowName}" not found. Available: ${configs.map((c2) => c2.name).join(", ")}`
4080
5140
  );
4081
5141
  }
4082
- return options.flowName;
5142
+ return match.id;
4083
5143
  }
4084
5144
  async function getAvailableFlowNames(options) {
4085
5145
  const flow = await getFlow({
4086
5146
  flowId: options.flowId,
4087
5147
  projectId: options.projectId
4088
5148
  });
4089
- const content = flow.content;
4090
- return Object.keys(content.flows ?? {});
5149
+ const configs = flow.configs;
5150
+ return configs?.map((c2) => c2.name) ?? [];
5151
+ }
5152
+ async function streamDeploymentStatus(projectId, deploymentId, options) {
5153
+ const base = resolveBaseUrl();
5154
+ const timeoutMs = options.timeout ?? 12e4;
5155
+ const response = await authenticatedFetch(
5156
+ `${base}/api/projects/${projectId}/deployments/${deploymentId}/stream`,
5157
+ {
5158
+ headers: { Accept: "text/event-stream" },
5159
+ signal: options.signal ?? AbortSignal.timeout(timeoutMs)
5160
+ }
5161
+ );
5162
+ if (!response.ok) throw new Error(`Stream failed: ${response.status}`);
5163
+ if (!response.body) throw new Error("No response body");
5164
+ const reader = response.body.getReader();
5165
+ const decoder = new TextDecoder();
5166
+ let result = null;
5167
+ let buffer = "";
5168
+ try {
5169
+ while (true) {
5170
+ const { done, value } = await reader.read();
5171
+ if (done) break;
5172
+ buffer += decoder.decode(value, { stream: true });
5173
+ const { parsed, remainder } = parseSSEEvents(buffer);
5174
+ buffer = remainder;
5175
+ for (const event of parsed) {
5176
+ if (event.type === "status") {
5177
+ const data = JSON.parse(event.data);
5178
+ result = data;
5179
+ options.onStatus?.(data.status, data.substatus ?? null);
5180
+ }
5181
+ if (event.type === "done") {
5182
+ return result;
5183
+ }
5184
+ }
5185
+ }
5186
+ } finally {
5187
+ reader.cancel().catch(() => {
5188
+ });
5189
+ }
5190
+ if (!result) throw new Error("Stream ended without terminal status");
5191
+ return result;
4091
5192
  }
4092
5193
  async function deploy(options) {
4093
5194
  const projectId = options.projectId ?? requireProjectId();
@@ -4098,7 +5199,14 @@ async function deploy(options) {
4098
5199
  projectId,
4099
5200
  flowName: options.flowName
4100
5201
  });
4101
- return deployConfig({ ...options, projectId, configId });
5202
+ return deployConfig({
5203
+ ...options,
5204
+ projectId,
5205
+ configId,
5206
+ timeout: options.timeout,
5207
+ signal: options.signal,
5208
+ onStatus: options.onStatus
5209
+ });
4102
5210
  }
4103
5211
  const { data, error } = await client.POST(
4104
5212
  "/api/projects/{projectId}/flows/{flowId}/deploy",
@@ -4120,33 +5228,12 @@ Available: ${names.join(", ")}`
4120
5228
  throw new Error(msg);
4121
5229
  }
4122
5230
  if (!options.wait) return data;
4123
- const terminalStatuses = ["active", "published", "failed", "deleted"];
4124
- let status = data.status;
4125
- let result = { ...data };
4126
- while (!terminalStatuses.includes(status)) {
4127
- await new Promise((r) => setTimeout(r, 3e3));
4128
- const { data: advanced, error: advanceError } = await client.POST(
4129
- "/api/projects/{projectId}/flows/{flowId}/deploy/{deploymentId}/advance",
4130
- {
4131
- params: {
4132
- path: {
4133
- projectId,
4134
- flowId: options.flowId,
4135
- deploymentId: data.deploymentId
4136
- }
4137
- }
4138
- }
4139
- );
4140
- if (advanceError)
4141
- throw new Error(
4142
- advanceError.error?.message || "Failed to advance deployment"
4143
- );
4144
- if (advanced) {
4145
- status = advanced.status;
4146
- result = { ...advanced };
4147
- }
4148
- }
4149
- return result;
5231
+ const result = await streamDeploymentStatus(projectId, data.deploymentId, {
5232
+ timeout: options.timeout,
5233
+ signal: options.signal,
5234
+ onStatus: options.onStatus
5235
+ });
5236
+ return { ...data, ...result };
4150
5237
  }
4151
5238
  async function deployConfig(options) {
4152
5239
  const { flowId, projectId, configId } = options;
@@ -4163,24 +5250,12 @@ async function deployConfig(options) {
4163
5250
  }
4164
5251
  const data = await response.json();
4165
5252
  if (!options.wait) return data;
4166
- const terminalStatuses = ["active", "published", "failed", "deleted"];
4167
- let status = data.status;
4168
- let result = { ...data };
4169
- while (!terminalStatuses.includes(status)) {
4170
- await new Promise((r) => setTimeout(r, 3e3));
4171
- const advResponse = await authenticatedFetch(
4172
- `${base}/api/projects/${projectId}/flows/${flowId}/configs/${configId}/deployments/${data.deploymentId}/advance`,
4173
- { method: "POST" }
4174
- );
4175
- if (!advResponse.ok) {
4176
- const body = await advResponse.json().catch(() => ({}));
4177
- throw new Error(body.error?.message || "Failed to advance deployment");
4178
- }
4179
- const advanced = await advResponse.json();
4180
- status = advanced.status;
4181
- result = { ...advanced };
4182
- }
4183
- return result;
5253
+ const result = await streamDeploymentStatus(projectId, data.deploymentId, {
5254
+ timeout: options.timeout,
5255
+ signal: options.signal,
5256
+ onStatus: options.onStatus
5257
+ });
5258
+ return { ...data, ...result };
4184
5259
  }
4185
5260
  async function getDeployment(options) {
4186
5261
  const projectId = options.projectId ?? requireProjectId();
@@ -4209,32 +5284,50 @@ async function getDeployment(options) {
4209
5284
  throw new Error(error.error?.message || "Failed to get deployment");
4210
5285
  return data;
4211
5286
  }
5287
+ var statusLabels = {
5288
+ bundling: "Building bundle...",
5289
+ "bundling:building": "Building bundle...",
5290
+ "bundling:publishing": "Publishing to web...",
5291
+ deploying: "Deploying container...",
5292
+ "deploying:provisioning": "Provisioning container...",
5293
+ "deploying:starting": "Starting container...",
5294
+ active: "Container is live",
5295
+ published: "Published",
5296
+ failed: "Deployment failed"
5297
+ };
4212
5298
  async function deployCommand(flowId, options) {
4213
- const log = createCommandLogger(options);
5299
+ const log = createCLILogger(options);
5300
+ const timeoutMs = options.timeout ? parseInt(options.timeout, 10) * 1e3 : void 0;
4214
5301
  try {
4215
5302
  const result = await deploy({
4216
5303
  flowId,
4217
5304
  projectId: options.project,
4218
5305
  flowName: options.flow,
4219
- wait: options.wait !== false
5306
+ wait: options.wait !== false,
5307
+ timeout: timeoutMs,
5308
+ onStatus: options.json ? void 0 : (status, substatus) => {
5309
+ const key = substatus ? `${status}:${substatus}` : status;
5310
+ log.info(
5311
+ statusLabels[key] || statusLabels[status] || `Status: ${status}`
5312
+ );
5313
+ }
4220
5314
  });
4221
5315
  if (options.json) {
4222
5316
  await writeResult(JSON.stringify(result, null, 2), options);
4223
5317
  return;
4224
5318
  }
4225
- const r = result;
4226
- if (r.status === "published") {
4227
- log.success(`Published: ${r.publicUrl}`);
4228
- if (r.scriptTag) log.info(`Script tag: ${r.scriptTag}`);
4229
- } else if (r.status === "active") {
4230
- log.success(`Active: ${r.containerUrl}`);
4231
- } else if (r.status === "failed") {
4232
- log.error(`Failed: ${r.errorMessage || "Unknown error"}`);
5319
+ const r2 = result;
5320
+ if (r2.status === "published") {
5321
+ log.info(`Published: ${r2.publicUrl}`);
5322
+ } else if (r2.status === "active") {
5323
+ log.info(`Active: ${r2.containerUrl}`);
5324
+ } else if (r2.status === "failed") {
5325
+ log.error(`Failed: ${r2.errorMessage || "Unknown error"}`);
4233
5326
  process.exit(1);
4234
- } else if (r.status === "bundling") {
4235
- log.info(`Deployment started: ${r.deploymentId} (${r.type})`);
5327
+ } else if (r2.status === "bundling") {
5328
+ log.info(`Deployment started: ${r2.deploymentId} (${r2.type})`);
4236
5329
  } else {
4237
- log.info(`Status: ${r.status}`);
5330
+ log.info(`Status: ${r2.status}`);
4238
5331
  }
4239
5332
  } catch (err) {
4240
5333
  log.error(err instanceof Error ? err.message : "Deploy failed");
@@ -4242,7 +5335,7 @@ async function deployCommand(flowId, options) {
4242
5335
  }
4243
5336
  }
4244
5337
  async function getDeploymentCommand(flowId, options) {
4245
- const log = createCommandLogger(options);
5338
+ const log = createCLILogger(options);
4246
5339
  try {
4247
5340
  const result = await getDeployment({
4248
5341
  flowId,
@@ -4257,50 +5350,258 @@ async function getDeploymentCommand(flowId, options) {
4257
5350
  log.info("No deployment found");
4258
5351
  return;
4259
5352
  }
4260
- const r = result;
4261
- log.info(`Deployment: ${r.id}`);
4262
- log.info(`Type: ${r.type}`);
4263
- log.info(`Status: ${r.status}`);
4264
- if (r.containerUrl) log.info(`Endpoint: ${r.containerUrl}`);
4265
- if (r.publicUrl) log.info(`URL: ${r.publicUrl}`);
4266
- if (r.scriptTag) log.info(`Script tag: ${r.scriptTag}`);
4267
- if (r.errorMessage) log.error(`Error: ${r.errorMessage}`);
5353
+ const r2 = result;
5354
+ log.info(`Deployment: ${r2.id}`);
5355
+ log.info(`Type: ${r2.type}`);
5356
+ log.info(`Status: ${r2.status}`);
5357
+ if (r2.containerUrl) log.info(`Endpoint: ${r2.containerUrl}`);
5358
+ if (r2.publicUrl) log.info(`URL: ${r2.publicUrl}`);
5359
+ if (r2.errorMessage) log.error(`Error: ${r2.errorMessage}`);
4268
5360
  } catch (err) {
4269
5361
  log.error(err instanceof Error ? err.message : "Failed to get deployment");
4270
5362
  process.exit(1);
4271
5363
  }
4272
5364
  }
5365
+
5366
+ // src/commands/deployments/index.ts
5367
+ init_auth();
5368
+ init_cli_logger();
5369
+ import { getPlatform as getPlatform4 } from "@walkeros/core";
5370
+ async function listDeployments(options = {}) {
5371
+ const id = options.projectId ?? requireProjectId();
5372
+ const base = resolveBaseUrl();
5373
+ const params = new URLSearchParams();
5374
+ if (options.type) params.set("type", options.type);
5375
+ if (options.status) params.set("status", options.status);
5376
+ const qs = params.toString();
5377
+ const response = await authenticatedFetch(
5378
+ `${base}/api/projects/${id}/deployments${qs ? `?${qs}` : ""}`
5379
+ );
5380
+ if (!response.ok) {
5381
+ const body = await response.json().catch(() => ({}));
5382
+ throw new Error(
5383
+ body.error?.message || "Failed to list deployments"
5384
+ );
5385
+ }
5386
+ return response.json();
5387
+ }
5388
+ async function getDeploymentBySlug(options) {
5389
+ const id = options.projectId ?? requireProjectId();
5390
+ const base = resolveBaseUrl();
5391
+ const response = await authenticatedFetch(
5392
+ `${base}/api/projects/${id}/deployments/${options.slug}`
5393
+ );
5394
+ if (!response.ok) {
5395
+ const body = await response.json().catch(() => ({}));
5396
+ throw new Error(
5397
+ body.error?.message || "Failed to get deployment"
5398
+ );
5399
+ }
5400
+ return response.json();
5401
+ }
5402
+ async function createDeployment(options) {
5403
+ const id = options.projectId ?? requireProjectId();
5404
+ const base = resolveBaseUrl();
5405
+ const response = await authenticatedFetch(
5406
+ `${base}/api/projects/${id}/deployments`,
5407
+ {
5408
+ method: "POST",
5409
+ headers: { "Content-Type": "application/json" },
5410
+ body: JSON.stringify({ type: options.type, label: options.label })
5411
+ }
5412
+ );
5413
+ if (!response.ok) {
5414
+ const body = await response.json().catch(() => ({}));
5415
+ throw new Error(
5416
+ body.error?.message || "Failed to create deployment"
5417
+ );
5418
+ }
5419
+ return response.json();
5420
+ }
5421
+ async function deleteDeployment(options) {
5422
+ const id = options.projectId ?? requireProjectId();
5423
+ const base = resolveBaseUrl();
5424
+ const response = await authenticatedFetch(
5425
+ `${base}/api/projects/${id}/deployments/${options.slug}`,
5426
+ { method: "DELETE" }
5427
+ );
5428
+ if (!response.ok) {
5429
+ const body = await response.json().catch(() => ({}));
5430
+ throw new Error(
5431
+ body.error?.message || "Failed to delete deployment"
5432
+ );
5433
+ }
5434
+ const data = await response.json().catch(() => null);
5435
+ return data ?? { success: true };
5436
+ }
5437
+ async function handleResult3(fn2, options) {
5438
+ const logger2 = createCLILogger(options);
5439
+ try {
5440
+ const result = await fn2();
5441
+ await writeResult(JSON.stringify(result, null, 2), options);
5442
+ } catch (error) {
5443
+ logger2.error(error instanceof Error ? error.message : String(error));
5444
+ process.exit(1);
5445
+ }
5446
+ }
5447
+ async function listDeploymentsCommand(options) {
5448
+ await handleResult3(
5449
+ () => listDeployments({
5450
+ projectId: options.project,
5451
+ type: options.type,
5452
+ status: options.status
5453
+ }),
5454
+ options
5455
+ );
5456
+ }
5457
+ async function getDeploymentBySlugCommand(slug, options) {
5458
+ await handleResult3(
5459
+ () => getDeploymentBySlug({ slug, projectId: options.project }),
5460
+ options
5461
+ );
5462
+ }
5463
+ async function createDeploymentCommand(options) {
5464
+ await handleResult3(
5465
+ () => createDeployment({
5466
+ type: options.type,
5467
+ label: options.label,
5468
+ projectId: options.project
5469
+ }),
5470
+ options
5471
+ );
5472
+ }
5473
+ async function deleteDeploymentCommand(slug, options) {
5474
+ await handleResult3(
5475
+ () => deleteDeployment({ slug, projectId: options.project }),
5476
+ options
5477
+ );
5478
+ }
5479
+ async function createDeployCommand(config, options) {
5480
+ const log = createCLILogger(options);
5481
+ try {
5482
+ let type;
5483
+ if (!config) {
5484
+ log.error(
5485
+ "Config required. Provide a flow config file or remote flow ID (cfg_xxx)."
5486
+ );
5487
+ process.exit(1);
5488
+ }
5489
+ const isRemoteFlow = config.startsWith("cfg_");
5490
+ if (isRemoteFlow) {
5491
+ const id = options.project ?? requireProjectId();
5492
+ const base = resolveBaseUrl();
5493
+ const resp = await authenticatedFetch(
5494
+ `${base}/api/projects/${id}/flows/${config}`
5495
+ );
5496
+ if (!resp.ok) {
5497
+ const body = await resp.json().catch(() => ({}));
5498
+ throw new Error(
5499
+ body.error?.message || `Failed to fetch flow ${config}`
5500
+ );
5501
+ }
5502
+ const flow = await resp.json();
5503
+ if (!flow.content) throw new Error("Flow has no content");
5504
+ const content = flow.content;
5505
+ const flows = content.flows;
5506
+ if (!flows) throw new Error("Invalid flow content: missing flows");
5507
+ const flowName = options.flow ?? Object.keys(flows)[0];
5508
+ if (!flowName) throw new Error("No flows found in config");
5509
+ const flowConfig = flows[flowName];
5510
+ if (!flowConfig || typeof flowConfig !== "object")
5511
+ throw new Error("Invalid flow config");
5512
+ if ("web" in flowConfig) type = "web";
5513
+ else if ("server" in flowConfig) type = "server";
5514
+ else throw new Error('Flow must have "web" or "server" key');
5515
+ } else {
5516
+ const result2 = await loadFlowConfig(config, {
5517
+ flowName: options.flow
5518
+ });
5519
+ type = getPlatform4(result2.flowConfig);
5520
+ }
5521
+ const deployment = await createDeployment({
5522
+ type,
5523
+ label: options.label,
5524
+ projectId: options.project
5525
+ });
5526
+ const result = deployment;
5527
+ if (options.json) {
5528
+ await writeResult(JSON.stringify(result, null, 2), options);
5529
+ return;
5530
+ }
5531
+ log.info(`Deployment created: ${result.id}`);
5532
+ log.info(` Slug: ${result.slug}`);
5533
+ log.info(` Type: ${result.type}`);
5534
+ if (result.deployToken) {
5535
+ log.info(` Token: ${result.deployToken}`);
5536
+ log.warn(" Save this token \u2014 it will not be shown again.");
5537
+ }
5538
+ log.info("");
5539
+ log.info("Run locally:");
5540
+ log.info(
5541
+ ` walkeros run ${isRemoteFlow ? "flow.json" : config} --deploy ${result.id}`
5542
+ );
5543
+ log.info("");
5544
+ log.info("Docker:");
5545
+ log.info(
5546
+ ` docker run -e WALKEROS_DEPLOY_TOKEN=${result.deployToken ?? "<token>"} \\`
5547
+ );
5548
+ log.info(" -e WALKEROS_APP_URL=https://app.walkeros.io \\");
5549
+ log.info(" walkeros/flow:latest");
5550
+ } catch (err) {
5551
+ log.error(
5552
+ err instanceof Error ? err.message : "Failed to create deployment"
5553
+ );
5554
+ process.exit(1);
5555
+ }
5556
+ }
5557
+
5558
+ // src/index.ts
5559
+ init_auth();
4273
5560
  export {
4274
5561
  bundle,
4275
5562
  bundleCommand,
4276
5563
  bundleRemote,
5564
+ compareOutput,
4277
5565
  createApiClient,
5566
+ createDeployCommand,
5567
+ createDeployment,
5568
+ createDeploymentCommand,
4278
5569
  createFlow,
4279
5570
  createFlowCommand,
4280
5571
  createProject,
4281
5572
  createProjectCommand,
5573
+ deleteDeployment,
5574
+ deleteDeploymentCommand,
4282
5575
  deleteFlow,
4283
5576
  deleteFlowCommand,
4284
5577
  deleteProject,
4285
5578
  deleteProjectCommand,
4286
5579
  deploy,
5580
+ deployAuthenticatedFetch,
4287
5581
  deployCommand,
4288
5582
  duplicateFlow,
4289
5583
  duplicateFlowCommand,
5584
+ findExample,
4290
5585
  getAuthHeaders,
4291
5586
  getDeployment,
5587
+ getDeploymentBySlug,
5588
+ getDeploymentBySlugCommand,
4292
5589
  getDeploymentCommand,
4293
5590
  getFlow,
4294
5591
  getFlowCommand,
4295
5592
  getProject,
4296
5593
  getProjectCommand,
4297
5594
  getToken,
5595
+ listDeployments,
5596
+ listDeploymentsCommand,
4298
5597
  listFlows,
4299
5598
  listFlowsCommand,
4300
5599
  listProjects,
4301
5600
  listProjectsCommand,
5601
+ loadJsonConfig,
4302
5602
  loginCommand,
4303
5603
  logoutCommand,
5604
+ parseSSEEvents,
4304
5605
  push,
4305
5606
  pushCommand,
4306
5607
  requireProjectId,