@walkeros/cli 4.2.0 → 4.2.1-next-1781538735002

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
@@ -25,6 +25,10 @@ function createCLILogger(options = {}) {
25
25
  handler: (level, message, _context, scope) => {
26
26
  const scopePath = scope.length > 0 ? `[${scope.join(":")}] ` : "";
27
27
  const fullMessage = `${scopePath}${message}`;
28
+ try {
29
+ options.onLine?.(level, fullMessage);
30
+ } catch {
31
+ }
28
32
  if (level === Level.ERROR) {
29
33
  if (!json) console.error(chalk.red(fullMessage));
30
34
  return;
@@ -2755,6 +2759,9 @@ ${firstError.text}`
2755
2759
  ` + (location ? ` at ${location.file}:${location.line}:${location.column}` : "")
2756
2760
  );
2757
2761
  }
2762
+ function buildDataPayload(flowSettings) {
2763
+ return buildSplitConfigObject(flowSettings, detectNamedImports(flowSettings)).dataPayloadObj;
2764
+ }
2758
2765
  function buildSplitConfigObject(flowSettings, namedImports) {
2759
2766
  const sources = flowSettings.sources || {};
2760
2767
  const destinations = flowSettings.destinations || {};
@@ -2777,7 +2784,7 @@ function buildSplitConfigObject(flowSettings, namedImports) {
2777
2784
  }
2778
2785
  return props;
2779
2786
  }
2780
- function buildSplitStepEntry(section, stepId, step) {
2787
+ function buildSplitStepEntry(section, stepId2, step) {
2781
2788
  const codeVar = resolveCodeVar(step);
2782
2789
  const stepProps = getStepProps(step);
2783
2790
  const { codeProps, dataProps } = classifyStepProperties(stepProps);
@@ -2788,13 +2795,13 @@ function buildSplitConfigObject(flowSettings, namedImports) {
2788
2795
  codeEntries.push(`${key}: ${processConfigValue(value)}`);
2789
2796
  }
2790
2797
  for (const key of Object.keys(dataProps)) {
2791
- codeEntries.push(`${key}: __data.${section}.${stepId}.${key}`);
2798
+ codeEntries.push(`${key}: __data.${section}.${stepId2}.${key}`);
2792
2799
  }
2793
2800
  if (Object.keys(dataProps).length > 0) {
2794
2801
  if (!dataPayloadObj[section]) dataPayloadObj[section] = {};
2795
- dataPayloadObj[section][stepId] = dataProps;
2802
+ dataPayloadObj[section][stepId2] = dataProps;
2796
2803
  }
2797
- return ` ${stepId}: {
2804
+ return ` ${stepId2}: {
2798
2805
  ${codeEntries.join(",\n ")}
2799
2806
  }`;
2800
2807
  }
@@ -2904,7 +2911,7 @@ ${destinationsEntries.join(",\n")}
2904
2911
  stores${collectorStr}
2905
2912
  }`;
2906
2913
  const dataPayload = JSON.stringify(dataPayloadObj, null, 2);
2907
- return { storesDeclaration, codeConfigObject, dataPayload };
2914
+ return { storesDeclaration, codeConfigObject, dataPayloadObj, dataPayload };
2908
2915
  }
2909
2916
  function generateSplitWireConfigModule(storesDeclaration, codeConfigObject, userCode) {
2910
2917
  const codeSection = userCode ? `
@@ -3848,7 +3855,8 @@ import {
3848
3855
  createIngest,
3849
3856
  getPlatform as getPlatform3,
3850
3857
  getNextSteps,
3851
- buildCacheContext
3858
+ buildCacheContext,
3859
+ stepId
3852
3860
  } from "@walkeros/core";
3853
3861
  import {
3854
3862
  enrichEvent,
@@ -4052,13 +4060,13 @@ function installTimerInterception(options = {}) {
4052
4060
  setInterval: Reflect.get(target, "setInterval"),
4053
4061
  clearInterval: Reflect.get(target, "clearInterval")
4054
4062
  });
4055
- const trackedSetTimeout = (callback, delay, ...args) => {
4063
+ const trackedSetTimeout = (callback, delay2, ...args) => {
4056
4064
  if (typeof callback !== "function") return 0;
4057
4065
  const id = nextId++;
4058
4066
  pending.set(id, {
4059
4067
  id,
4060
4068
  callback,
4061
- delay: delay ?? 0,
4069
+ delay: delay2 ?? 0,
4062
4070
  type: "timeout",
4063
4071
  args,
4064
4072
  cleared: false
@@ -4071,13 +4079,13 @@ function installTimerInterception(options = {}) {
4071
4079
  const entry = pending.get(numId);
4072
4080
  if (entry) entry.cleared = true;
4073
4081
  };
4074
- const trackedSetInterval = (callback, delay, ...args) => {
4082
+ const trackedSetInterval = (callback, delay2, ...args) => {
4075
4083
  if (typeof callback !== "function") return 0;
4076
4084
  const id = nextId++;
4077
4085
  pending.set(id, {
4078
4086
  id,
4079
4087
  callback,
4080
- delay: delay ?? 0,
4088
+ delay: delay2 ?? 0,
4081
4089
  type: "interval",
4082
4090
  args,
4083
4091
  cleared: false
@@ -4354,7 +4362,7 @@ function toError(error) {
4354
4362
  return error instanceof Error ? error : new Error(getErrorMessage(error));
4355
4363
  }
4356
4364
  function buildSimulationResult(args) {
4357
- const { step, name, startTime, captured, usage, error } = args;
4365
+ const { step, name, startTime, captured, usage, mappingKey, error } = args;
4358
4366
  const events = (captured ?? []).filter(hasEvent).map((entry) => entry.event);
4359
4367
  const calls = usage ? Object.values(usage).flat().map((call) => ({ fn: call.fn, args: call.args, ts: call.ts })) : [];
4360
4368
  return {
@@ -4363,6 +4371,7 @@ function buildSimulationResult(args) {
4363
4371
  events,
4364
4372
  calls,
4365
4373
  duration: Date.now() - startTime,
4374
+ ...mappingKey !== void 0 ? { mappingKey } : {},
4366
4375
  ...error !== void 0 ? { error: toError(error) } : {}
4367
4376
  };
4368
4377
  }
@@ -4956,7 +4965,9 @@ async function simulateSource(configOrPath, input, options) {
4956
4965
  `Source package "${sourceConfig.package}" has no createTrigger in /dev export`
4957
4966
  );
4958
4967
  }
4959
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
4968
+ const flowConfig = module.wireConfig(
4969
+ options.data ?? module.__configData ?? void 0
4970
+ );
4960
4971
  applyOverrides(flowConfig, prepared.overrides);
4961
4972
  const captured = [];
4962
4973
  flowConfig.hooks = {
@@ -5061,7 +5072,9 @@ async function simulateTransformer(configOrPath, event, options) {
5061
5072
  networkCalls
5062
5073
  },
5063
5074
  async (module) => {
5064
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
5075
+ const flowConfig = module.wireConfig(
5076
+ options.data ?? module.__configData ?? void 0
5077
+ );
5065
5078
  applyOverrides(flowConfig, prepared.overrides);
5066
5079
  if (flowConfig.sources) flowConfig.sources = {};
5067
5080
  if (flowConfig.destinations) flowConfig.destinations = {};
@@ -5227,7 +5240,9 @@ async function simulateCollector(configOrPath, event, options) {
5227
5240
  networkCalls
5228
5241
  },
5229
5242
  async (module) => {
5230
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
5243
+ const flowConfig = module.wireConfig(
5244
+ options.data ?? module.__configData ?? void 0
5245
+ );
5231
5246
  applyOverrides(flowConfig, prepared.overrides);
5232
5247
  if (flowConfig.sources) flowConfig.sources = {};
5233
5248
  if (flowConfig.destinations) flowConfig.destinations = {};
@@ -5330,7 +5345,9 @@ async function simulateDestination(configOrPath, event, options) {
5330
5345
  networkCalls
5331
5346
  },
5332
5347
  async (module) => {
5333
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
5348
+ const flowConfig = module.wireConfig(
5349
+ options.data ?? module.__configData ?? void 0
5350
+ );
5334
5351
  applyOverrides(flowConfig, prepared.overrides);
5335
5352
  const destPkg = (prepared.flowSettings.destinations ?? {})[options.destinationId];
5336
5353
  let trackedCalls = [];
@@ -5366,6 +5383,16 @@ async function simulateDestination(configOrPath, event, options) {
5366
5383
  );
5367
5384
  }
5368
5385
  logger.info(`Simulating destination: ${options.destinationId}`);
5386
+ let mappingKey;
5387
+ const targetStepId = stepId("destination", options.destinationId);
5388
+ const captureMappingKey = (state) => {
5389
+ if (state.stepId === targetStepId && state.mappingKey) {
5390
+ mappingKey = state.mappingKey;
5391
+ }
5392
+ };
5393
+ if (collector.observers instanceof Set) {
5394
+ collector.observers.add(captureMappingKey);
5395
+ }
5369
5396
  await collector.push(event, {
5370
5397
  include: [options.destinationId]
5371
5398
  });
@@ -5374,7 +5401,8 @@ async function simulateDestination(configOrPath, event, options) {
5374
5401
  step: "destination",
5375
5402
  name: options.destinationId,
5376
5403
  startTime,
5377
- usage: trackedCalls.length ? { [options.destinationId]: trackedCalls } : void 0
5404
+ usage: trackedCalls.length ? { [options.destinationId]: trackedCalls } : void 0,
5405
+ mappingKey
5378
5406
  });
5379
5407
  },
5380
5408
  (error) => buildSimulationResult({
@@ -5399,13 +5427,88 @@ async function simulateDestination(configOrPath, event, options) {
5399
5427
  // src/commands/run/index.ts
5400
5428
  init_cli_logger();
5401
5429
  init_core();
5402
- init_tmp();
5403
- init_config_file();
5404
- init_auth();
5405
5430
  import path18 from "path";
5406
5431
  import { writeFileSync as writeFileSync5 } from "fs";
5407
5432
  import { homedir as homedir2 } from "os";
5408
5433
  import { join as join7 } from "path";
5434
+ import { Level as Level2 } from "@walkeros/core";
5435
+
5436
+ // src/runtime/runner.ts
5437
+ import { resolve as resolve3, dirname as dirname3 } from "path";
5438
+
5439
+ // src/runtime/load-bundle.ts
5440
+ import { resolve as resolve2 } from "path";
5441
+ import { pathToFileURL as pathToFileURL2 } from "url";
5442
+ async function loadBundle(file, context2, logger) {
5443
+ const absolutePath = resolve2(file);
5444
+ const fileUrl = pathToFileURL2(absolutePath).href;
5445
+ logger?.debug?.(`Importing bundle: ${absolutePath}`);
5446
+ const module = await import(`${fileUrl}?t=${Date.now()}`);
5447
+ if (!module.default || typeof module.default !== "function") {
5448
+ throw new Error(
5449
+ `Invalid bundle: ${file} must export a default factory function`
5450
+ );
5451
+ }
5452
+ logger?.debug?.("Calling factory function...");
5453
+ const result = await module.default(context2 ?? {});
5454
+ if (!result || !result.collector || typeof result.collector.push !== "function") {
5455
+ throw new Error(
5456
+ `Invalid bundle: factory must return { collector } with a push function`
5457
+ );
5458
+ }
5459
+ return {
5460
+ collector: result.collector,
5461
+ ...typeof result.httpHandler === "function" ? { httpHandler: result.httpHandler } : {}
5462
+ };
5463
+ }
5464
+
5465
+ // src/runtime/runner.ts
5466
+ async function loadFlow(file, config, logger, loggerConfig, healthServer, observers) {
5467
+ const absolutePath = resolve3(file);
5468
+ const flowDir = dirname3(absolutePath);
5469
+ process.chdir(flowDir);
5470
+ const flowContext = {
5471
+ ...config,
5472
+ ...loggerConfig ? { logger: loggerConfig } : {},
5473
+ ...healthServer ? { sourceSettings: { port: void 0 } } : {},
5474
+ ...observers ? { observers } : {}
5475
+ };
5476
+ const result = await loadBundle(absolutePath, flowContext, logger);
5477
+ if (healthServer && typeof result.httpHandler === "function") {
5478
+ healthServer.setFlowHandler(result.httpHandler);
5479
+ }
5480
+ return {
5481
+ collector: {
5482
+ command: result.collector.command,
5483
+ status: result.collector.status
5484
+ },
5485
+ file,
5486
+ httpHandler: result.httpHandler
5487
+ };
5488
+ }
5489
+ async function swapFlow(currentHandle, newFile, config, logger, loggerConfig, healthServer, observers) {
5490
+ logger.info("Shutting down current flow for hot-swap...");
5491
+ if (healthServer) {
5492
+ healthServer.setFlowHandler(null);
5493
+ }
5494
+ try {
5495
+ if (currentHandle.collector.command) {
5496
+ await currentHandle.collector.command("shutdown");
5497
+ }
5498
+ } catch (error) {
5499
+ logger.debug(`Shutdown warning: ${error}`);
5500
+ }
5501
+ const newHandle = await loadFlow(
5502
+ newFile,
5503
+ config,
5504
+ logger,
5505
+ loggerConfig,
5506
+ healthServer,
5507
+ observers
5508
+ );
5509
+ logger.info("Flow swapped successfully");
5510
+ return newHandle;
5511
+ }
5409
5512
 
5410
5513
  // src/runtime/resolve-bundle.ts
5411
5514
  init_stdin();
@@ -5416,11 +5519,127 @@ import {
5416
5519
  rmSync,
5417
5520
  writeFileSync as writeFileSync2
5418
5521
  } from "fs";
5419
- import { dirname as dirname3, join as join4 } from "path";
5522
+ import { dirname as dirname4, join as join4 } from "path";
5420
5523
  import { Readable } from "stream";
5421
5524
  import { x as tarExtract } from "tar";
5525
+
5526
+ // src/runtime/fetch-retry.ts
5527
+ var DEFAULT_PER_ATTEMPT_TIMEOUT_MS = 3e4;
5528
+ var DEFAULT_MAX_TOTAL_MS = 6e4;
5529
+ var DEFAULT_ATTEMPTS = 3;
5530
+ var MIN_ATTEMPT_BUDGET_MS = 1e3;
5531
+ var BASE_BACKOFF_MS = [2e3, 5e3];
5532
+ var JITTER = 0.2;
5533
+ var RETRYABLE_NETWORK_CODES = /* @__PURE__ */ new Set([
5534
+ "ECONNRESET",
5535
+ "ECONNREFUSED",
5536
+ "ETIMEDOUT",
5537
+ "EAI_AGAIN",
5538
+ "ENOTFOUND"
5539
+ ]);
5540
+ function readErrorCode(value) {
5541
+ if (typeof value !== "object" || value === null) return void 0;
5542
+ const code = Reflect.get(value, "code");
5543
+ return typeof code === "string" ? code : void 0;
5544
+ }
5545
+ function readErrorName(value) {
5546
+ if (typeof value !== "object" || value === null) return void 0;
5547
+ const name = Reflect.get(value, "name");
5548
+ return typeof name === "string" ? name : void 0;
5549
+ }
5550
+ function isTransientThrow(error) {
5551
+ const name = readErrorName(error);
5552
+ if (name === "TimeoutError" || name === "AbortError") return true;
5553
+ const directCode = readErrorCode(error);
5554
+ if (directCode && RETRYABLE_NETWORK_CODES.has(directCode)) return true;
5555
+ if (typeof error === "object" && error !== null) {
5556
+ const causeCode = readErrorCode(Reflect.get(error, "cause"));
5557
+ if (causeCode && RETRYABLE_NETWORK_CODES.has(causeCode)) return true;
5558
+ }
5559
+ return error instanceof TypeError;
5560
+ }
5561
+ function isTransientStatus(status) {
5562
+ return status >= 500 || status === 429;
5563
+ }
5564
+ function readErrorMessage(value) {
5565
+ if (typeof value !== "object" || value === null) return void 0;
5566
+ const message = Reflect.get(value, "message");
5567
+ return typeof message === "string" ? message : void 0;
5568
+ }
5569
+ function describeReason(reason) {
5570
+ if (reason.kind === "status") return `HTTP ${reason.status}`;
5571
+ const name = readErrorName(reason.error);
5572
+ const message = reason.error instanceof Error ? reason.error.message : String(reason.error);
5573
+ let detail = name ? `${name}: ${message}` : message;
5574
+ if (typeof reason.error === "object" && reason.error !== null) {
5575
+ const cause = Reflect.get(reason.error, "cause");
5576
+ const causeCode = readErrorCode(cause);
5577
+ const causeMessage = readErrorMessage(cause);
5578
+ const causeDetail = causeCode ?? causeMessage;
5579
+ if (causeDetail) detail = `${detail} (${causeDetail})`;
5580
+ }
5581
+ return detail;
5582
+ }
5583
+ function backoffForAttempt(index) {
5584
+ const base = BASE_BACKOFF_MS[Math.min(index, BASE_BACKOFF_MS.length - 1)] ?? 0;
5585
+ const spread = base * JITTER;
5586
+ return base + (Math.random() * 2 - 1) * spread;
5587
+ }
5588
+ function delay(ms) {
5589
+ return new Promise((resolve5) => {
5590
+ setTimeout(resolve5, ms);
5591
+ });
5592
+ }
5593
+ async function fetchWithRetry(url, options = {}) {
5594
+ const attempts = options.attempts ?? DEFAULT_ATTEMPTS;
5595
+ const perAttemptTimeoutMs = options.perAttemptTimeoutMs ?? DEFAULT_PER_ATTEMPT_TIMEOUT_MS;
5596
+ const maxTotalMs = options.maxTotalMs ?? DEFAULT_MAX_TOTAL_MS;
5597
+ const init = options.init;
5598
+ const start = Date.now();
5599
+ let lastReason;
5600
+ let made = 0;
5601
+ for (let attempt = 0; attempt < attempts; attempt++) {
5602
+ const remaining = maxTotalMs - (Date.now() - start);
5603
+ if (remaining <= MIN_ATTEMPT_BUDGET_MS) break;
5604
+ const attemptTimeoutMs = Math.min(perAttemptTimeoutMs, remaining);
5605
+ made = attempt + 1;
5606
+ let reason;
5607
+ try {
5608
+ const controller = new AbortController();
5609
+ const timeoutId = setTimeout(() => controller.abort(), attemptTimeoutMs);
5610
+ let response;
5611
+ try {
5612
+ response = await fetch(url, {
5613
+ ...init,
5614
+ signal: controller.signal
5615
+ });
5616
+ } finally {
5617
+ clearTimeout(timeoutId);
5618
+ }
5619
+ if (!isTransientStatus(response.status)) return response;
5620
+ await response.body?.cancel();
5621
+ reason = { kind: "status", status: response.status };
5622
+ } catch (error) {
5623
+ if (!isTransientThrow(error)) throw error;
5624
+ reason = { kind: "throw", error };
5625
+ }
5626
+ lastReason = reason;
5627
+ const isLastAttempt = attempt === attempts - 1;
5628
+ const budgetSpent = Date.now() - start >= maxTotalMs;
5629
+ if (isLastAttempt || budgetSpent) break;
5630
+ const sleepMs = Math.min(
5631
+ backoffForAttempt(attempt),
5632
+ maxTotalMs - (Date.now() - start)
5633
+ );
5634
+ if (sleepMs <= 0) break;
5635
+ await delay(sleepMs);
5636
+ }
5637
+ const cause = lastReason ? describeReason(lastReason) : "no attempts made";
5638
+ throw new Error(`Fetch failed after ${made} attempts: ${cause}`);
5639
+ }
5640
+
5641
+ // src/runtime/resolve-bundle.ts
5422
5642
  var ARCHIVE_ENTRY = "flow.mjs";
5423
- var FETCH_TIMEOUT_MS = 3e4;
5424
5643
  function getDefaultWritePath() {
5425
5644
  if (existsSync2("/app/flow")) return "/app/flow/flow.mjs";
5426
5645
  return "/tmp/walkeros-flow.mjs";
@@ -5438,16 +5657,14 @@ function isArchive(value, contentType) {
5438
5657
  return false;
5439
5658
  }
5440
5659
  function writeBundleToDisk(writePath, content) {
5441
- const dir = dirname3(writePath);
5660
+ const dir = dirname4(writePath);
5442
5661
  if (!existsSync2(dir)) {
5443
5662
  mkdirSync2(dir, { recursive: true });
5444
5663
  }
5445
5664
  writeFileSync2(writePath, content, "utf-8");
5446
5665
  }
5447
5666
  async function fetchOk(url) {
5448
- const response = await fetch(url, {
5449
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
5450
- });
5667
+ const response = await fetchWithRetry(url);
5451
5668
  if (!response.ok) {
5452
5669
  throw new Error(
5453
5670
  `Failed to fetch bundle from ${url}: ${response.status} ${response.statusText}`
@@ -5499,7 +5716,7 @@ async function readBundleFromStdin(writePath) {
5499
5716
  }
5500
5717
  async function resolveBundle(bundleEnv) {
5501
5718
  const writePath = getDefaultWritePath();
5502
- const archiveDestDir = dirname3(writePath);
5719
+ const archiveDestDir = dirname4(writePath);
5503
5720
  if (!isUrl2(bundleEnv) && existsSync2(bundleEnv)) {
5504
5721
  if (isArchive(bundleEnv)) {
5505
5722
  const path20 = await extractToDir(
@@ -5568,10 +5785,7 @@ async function fetchConfig(options) {
5568
5785
  options.token,
5569
5786
  options.lastEtag ? { "If-None-Match": options.lastEtag } : void 0
5570
5787
  );
5571
- const response = await fetch(url, {
5572
- headers,
5573
- signal: AbortSignal.timeout(3e4)
5574
- });
5788
+ const response = await fetchWithRetry(url, { init: { headers } });
5575
5789
  if (response.status === 304) {
5576
5790
  return { changed: false };
5577
5791
  }
@@ -5592,301 +5806,151 @@ async function fetchConfig(options) {
5592
5806
  };
5593
5807
  }
5594
5808
 
5595
- // src/commands/run/index.ts
5809
+ // src/runtime/index.ts
5596
5810
  init_cache();
5597
5811
 
5598
- // src/commands/run/validators.ts
5599
- init_asset_resolver();
5600
- import { existsSync as existsSync4 } from "fs";
5601
-
5602
- // src/schemas/primitives.ts
5603
- import { z } from "@walkeros/core/dev";
5604
- 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");
5605
- var FilePathSchema = z.string().min(1, "File path cannot be empty").describe("Path to configuration file");
5606
-
5607
- // src/schemas/run.ts
5608
- import { z as z2 } from "@walkeros/core/dev";
5609
- var RunOptionsSchema = z2.object({
5610
- flow: FilePathSchema,
5611
- port: PortSchema.default(8080),
5612
- flowName: z2.string().optional().describe("Specific flow name to run")
5613
- });
5614
-
5615
- // src/schemas/validate.ts
5616
- import { z as z3 } from "@walkeros/core/dev";
5617
- var ValidationTypeSchema = z3.enum(["contract", "event", "flow", "mapping"]).describe('Validation type: "event", "flow", "mapping", or "contract"');
5618
- var ValidateOptionsSchema = z3.object({
5619
- flow: z3.string().optional().describe("Flow name for multi-flow configs"),
5620
- path: z3.string().optional().describe(
5621
- 'Entry path for package schema validation (e.g., "destinations.snowplow", "sources.browser")'
5622
- )
5623
- });
5624
- var ValidateInputShape = {
5625
- type: ValidationTypeSchema,
5626
- input: z3.string().min(1).describe("JSON string, file path, or URL to validate"),
5627
- flow: z3.string().optional().describe("Flow name for multi-flow configs"),
5628
- path: z3.string().optional().describe(
5629
- 'Entry path for package schema validation (e.g., "destinations.snowplow"). When provided, validates the entry against its package JSON Schema instead of using --type.'
5630
- )
5631
- };
5632
- var ValidateInputSchema = z3.object(ValidateInputShape);
5633
-
5634
- // src/schemas/bundle.ts
5635
- import { z as z4 } from "@walkeros/core/dev";
5636
- var BundleOptionsSchema = z4.object({
5637
- silent: z4.boolean().optional().describe("Suppress all output"),
5638
- verbose: z4.boolean().optional().describe("Enable verbose logging"),
5639
- stats: z4.boolean().optional().default(true).describe("Return bundle statistics"),
5640
- cache: z4.boolean().optional().default(true).describe("Enable package caching"),
5641
- flowName: z4.string().optional().describe("Flow name for multi-flow configs")
5642
- });
5643
- var BundleInputShape = {
5644
- configPath: FilePathSchema.describe(
5645
- "Path to flow configuration file (JSON or JavaScript), URL, or inline JSON string"
5646
- ),
5647
- flow: z4.string().optional().describe("Flow name for multi-flow configs"),
5648
- stats: z4.boolean().optional().default(true).describe("Return bundle statistics"),
5649
- output: z4.string().optional().describe("Output file path (defaults to config-defined)")
5650
- };
5651
- var BundleInputSchema = z4.object(BundleInputShape);
5812
+ // src/runtime/heartbeat.ts
5813
+ import { randomBytes } from "crypto";
5652
5814
 
5653
- // src/schemas/simulate.ts
5654
- import { z as z5 } from "@walkeros/core/dev";
5655
- var PlatformSchema = z5.enum(["web", "server"]).describe("Platform type for event processing");
5656
- var SimulateOptionsSchema = z5.object({
5657
- silent: z5.boolean().optional().describe("Suppress all output"),
5658
- verbose: z5.boolean().optional().describe("Enable verbose logging"),
5659
- json: z5.boolean().optional().describe("Format output as JSON")
5660
- });
5661
- var SimulateInputShape = {
5662
- configPath: FilePathSchema.describe(
5663
- "Path to flow configuration file, URL, or inline JSON string"
5664
- ),
5665
- event: z5.string().min(1).optional().describe(
5666
- "Event as JSON string, file path, or URL. For sources: { content, trigger?, env? }."
5667
- ),
5668
- flow: z5.string().optional().describe("Flow name for multi-flow configs"),
5669
- platform: PlatformSchema.optional().describe("Override platform detection"),
5670
- step: z5.string().optional().describe(
5671
- 'Step target in type.name format (e.g. "source.browser", "destination.gtag")'
5672
- )
5673
- };
5674
- var SimulateInputSchema = z5.object(SimulateInputShape);
5815
+ // src/version.ts
5816
+ import { readFileSync as readFileSync3 } from "fs";
5817
+ import { fileURLToPath } from "url";
5818
+ import { dirname as dirname5, join as join6 } from "path";
5819
+ var versionFilename = fileURLToPath(import.meta.url);
5820
+ var versionDirname = dirname5(versionFilename);
5821
+ function findPackageJson() {
5822
+ const paths = [
5823
+ join6(versionDirname, "../package.json"),
5824
+ // dist/ or src/
5825
+ join6(versionDirname, "../../package.json")
5826
+ // src/core/ (not used, but safe)
5827
+ ];
5828
+ for (const p of paths) {
5829
+ try {
5830
+ return readFileSync3(p, "utf-8");
5831
+ } catch {
5832
+ }
5833
+ }
5834
+ return JSON.stringify({ version: "0.0.0" });
5835
+ }
5836
+ var VERSION = JSON.parse(findPackageJson()).version;
5675
5837
 
5676
- // src/schemas/push.ts
5677
- import { z as z6 } from "@walkeros/core/dev";
5678
- var PushOptionsSchema = z6.object({
5679
- silent: z6.boolean().optional().describe("Suppress all output"),
5680
- verbose: z6.boolean().optional().describe("Enable verbose logging"),
5681
- json: z6.boolean().optional().describe("Format output as JSON")
5682
- });
5683
- var PushInputShape = {
5684
- configPath: FilePathSchema.describe("Path to flow configuration file"),
5685
- event: z6.string().min(1).describe("Event as JSON string, file path, or URL"),
5686
- flow: z6.string().optional().describe("Flow name for multi-flow configs"),
5687
- platform: PlatformSchema.optional().describe("Override platform detection")
5688
- };
5689
- var PushInputSchema = z6.object(PushInputShape);
5838
+ // src/runtime/heartbeat.ts
5839
+ init_http();
5690
5840
 
5691
- // src/commands/run/validators.ts
5692
- function validateFlowFile(filePath) {
5693
- const absolutePath = resolveAsset(filePath, "bundle");
5694
- if (!existsSync4(absolutePath)) {
5695
- throw new Error(
5696
- `Flow file not found: ${filePath}
5697
- Resolved path: ${absolutePath}
5698
- Make sure the file exists and the path is correct`
5699
- );
5700
- }
5701
- return absolutePath;
5841
+ // src/runtime/redact.ts
5842
+ var MAX_LENGTH = 256;
5843
+ var MIN_TOKEN_LEN = 20;
5844
+ var ENTROPY_THRESHOLD = 4;
5845
+ var FORCE_MASK_PREFIXES = [
5846
+ "sk-",
5847
+ "sk_",
5848
+ "pk_",
5849
+ "ghp_",
5850
+ "gho_",
5851
+ "xoxb-",
5852
+ "xoxp-",
5853
+ "AKIA"
5854
+ ];
5855
+ var RE_URL_CREDS = /(:\/\/[^/:@\s]+:)[^@\s]+(@)/g;
5856
+ var RE_JSON_PRIVATE_KEY = /("private_key"\s*:\s*)"[^"]*"/g;
5857
+ var RE_PEM_BEGIN = /^\s*-----BEGIN [A-Z ]*PRIVATE KEY-----/i;
5858
+ var RE_PEM_END = /-----END[A-Z -]*-----/i;
5859
+ var TOKEN_VALUE_CLASS = "[A-Za-z0-9+/._-]";
5860
+ var RE_KV_SECRET = new RegExp(
5861
+ `(^|\\s)([A-Za-z_][A-Za-z0-9_.]{0,63}\\s*[=:]\\s*)(${TOKEN_VALUE_CLASS}{12,}={0,2})`,
5862
+ "g"
5863
+ );
5864
+ var RE_TOKEN_RUN = new RegExp(`${TOKEN_VALUE_CLASS}{${MIN_TOKEN_LEN},}`, "g");
5865
+ function escapeRegex(s) {
5866
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5867
+ }
5868
+ var PREFIX_ALTERNATION = [...FORCE_MASK_PREFIXES].sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
5869
+ var RE_PREFIXED_TOKEN = new RegExp(
5870
+ `(?:${PREFIX_ALTERNATION})${TOKEN_VALUE_CLASS}*`,
5871
+ "g"
5872
+ );
5873
+ function shannonEntropy(s) {
5874
+ if (s.length === 0) return 0;
5875
+ const counts = /* @__PURE__ */ new Map();
5876
+ for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
5877
+ let entropy = 0;
5878
+ for (const count of counts.values()) {
5879
+ const p = count / s.length;
5880
+ entropy -= p * Math.log2(p);
5881
+ }
5882
+ return entropy;
5883
+ }
5884
+ var RE_ALL_HEX = /^[0-9a-fA-F]+$/;
5885
+ var RE_ALL_DIGIT = /^[0-9]+$/;
5886
+ var RE_BASE64_SPECIAL = /[+/=]/;
5887
+ var RE_HAS_DIGIT = /[0-9]/;
5888
+ var RE_HAS_LETTER = /[A-Za-z]/;
5889
+ function shouldMaskToken(run2) {
5890
+ if (RE_ALL_HEX.test(run2)) return true;
5891
+ if (RE_ALL_DIGIT.test(run2)) return true;
5892
+ if (RE_BASE64_SPECIAL.test(run2)) return true;
5893
+ if (RE_HAS_DIGIT.test(run2) && RE_HAS_LETTER.test(run2)) return true;
5894
+ if (shannonEntropy(run2) >= ENTROPY_THRESHOLD) return true;
5895
+ return false;
5702
5896
  }
5703
- function validatePort(port) {
5704
- const result = PortSchema.safeParse(port);
5705
- if (!result.success) {
5706
- throw new Error(
5707
- `Invalid port: ${port}
5708
- Port must be an integer between 1 and 65535
5709
- Example: --port 8080`
5710
- );
5897
+ function removePemBlocks(lines) {
5898
+ const out = [];
5899
+ let inBlock = false;
5900
+ for (const line of lines) {
5901
+ if (!inBlock) {
5902
+ if (RE_PEM_BEGIN.test(line)) {
5903
+ inBlock = true;
5904
+ continue;
5905
+ }
5906
+ out.push(line);
5907
+ } else {
5908
+ if (RE_PEM_END.test(line)) {
5909
+ inBlock = false;
5910
+ }
5911
+ }
5711
5912
  }
5913
+ return out;
5712
5914
  }
5713
-
5714
- // src/commands/run/index.ts
5715
- init_utils3();
5716
-
5717
- // src/commands/run/pipeline.ts
5718
- init_tmp();
5719
- import { writeFileSync as writeFileSync4 } from "fs";
5720
- import fs17 from "fs-extra";
5721
- import {
5722
- createBatchedPoster,
5723
- createTelemetryObserver,
5724
- getTraceUntil,
5725
- resolveTelemetryOptions
5726
- } from "@walkeros/core";
5727
-
5728
- // src/runtime/health-server.ts
5729
- import http from "http";
5730
- function createHealthServer(port, logger) {
5731
- return new Promise((resolve5, reject) => {
5732
- let flowHandler = null;
5733
- let ready = false;
5734
- let failureReason = null;
5735
- const server = http.createServer((req, res) => {
5736
- if (req.url === "/health" && req.method === "GET") {
5737
- res.writeHead(200, { "Content-Type": "application/json" });
5738
- res.end(JSON.stringify({ status: "ok" }));
5739
- return;
5740
- }
5741
- if (req.url === "/ready" && req.method === "GET") {
5742
- const code = ready ? 200 : 503;
5743
- const status = ready ? "ready" : failureReason ? "failed" : "not_ready";
5744
- res.writeHead(code, { "Content-Type": "application/json" });
5745
- res.end(
5746
- JSON.stringify(
5747
- failureReason && !ready ? { status, reason: failureReason } : { status }
5748
- )
5749
- );
5750
- return;
5751
- }
5752
- if (flowHandler) {
5753
- flowHandler(req, res);
5754
- return;
5755
- }
5756
- res.writeHead(503, { "Content-Type": "application/json" });
5757
- res.end(JSON.stringify({ error: "No flow loaded" }));
5758
- });
5759
- server.keepAliveTimeout = 5e3;
5760
- server.headersTimeout = 1e4;
5761
- server.listen(port, "0.0.0.0", () => {
5762
- logger.info(`Health server listening on port ${port}`);
5763
- resolve5({
5764
- server,
5765
- setFlowHandler(handler) {
5766
- flowHandler = handler;
5767
- },
5768
- setReady(value) {
5769
- ready = value;
5770
- if (value) failureReason = null;
5771
- },
5772
- setFailed(reason) {
5773
- ready = false;
5774
- failureReason = reason;
5775
- },
5776
- close: () => new Promise((res, rej) => {
5777
- server.close((err) => err ? rej(err) : res());
5778
- })
5779
- });
5780
- });
5781
- server.on("error", reject);
5782
- });
5783
- }
5784
-
5785
- // src/runtime/runner.ts
5786
- import { resolve as resolve3, dirname as dirname4 } from "path";
5787
-
5788
- // src/runtime/load-bundle.ts
5789
- import { resolve as resolve2 } from "path";
5790
- import { pathToFileURL as pathToFileURL2 } from "url";
5791
- async function loadBundle(file, context2, logger) {
5792
- const absolutePath = resolve2(file);
5793
- const fileUrl = pathToFileURL2(absolutePath).href;
5794
- logger?.debug?.(`Importing bundle: ${absolutePath}`);
5795
- const module = await import(`${fileUrl}?t=${Date.now()}`);
5796
- if (!module.default || typeof module.default !== "function") {
5797
- throw new Error(
5798
- `Invalid bundle: ${file} must export a default factory function`
5799
- );
5800
- }
5801
- logger?.debug?.("Calling factory function...");
5802
- const result = await module.default(context2 ?? {});
5803
- if (!result || !result.collector || typeof result.collector.push !== "function") {
5804
- throw new Error(
5805
- `Invalid bundle: factory must return { collector } with a push function`
5806
- );
5807
- }
5808
- return {
5809
- collector: result.collector,
5810
- ...typeof result.httpHandler === "function" ? { httpHandler: result.httpHandler } : {}
5811
- };
5812
- }
5813
-
5814
- // src/runtime/runner.ts
5815
- async function loadFlow(file, config, logger, loggerConfig, healthServer, observers) {
5816
- const absolutePath = resolve3(file);
5817
- const flowDir = dirname4(absolutePath);
5818
- process.chdir(flowDir);
5819
- const flowContext = {
5820
- ...config,
5821
- ...loggerConfig ? { logger: loggerConfig } : {},
5822
- ...healthServer ? { sourceSettings: { port: void 0 } } : {},
5823
- ...observers ? { observers } : {}
5824
- };
5825
- const result = await loadBundle(absolutePath, flowContext, logger);
5826
- if (healthServer && typeof result.httpHandler === "function") {
5827
- healthServer.setFlowHandler(result.httpHandler);
5828
- }
5829
- return {
5830
- collector: {
5831
- command: result.collector.command,
5832
- status: result.collector.status
5833
- },
5834
- file,
5835
- httpHandler: result.httpHandler
5836
- };
5837
- }
5838
- async function swapFlow(currentHandle, newFile, config, logger, loggerConfig, healthServer, observers) {
5839
- logger.info("Shutting down current flow for hot-swap...");
5840
- if (healthServer) {
5841
- healthServer.setFlowHandler(null);
5842
- }
5843
- try {
5844
- if (currentHandle.collector.command) {
5845
- await currentHandle.collector.command("shutdown");
5846
- }
5847
- } catch (error) {
5848
- logger.debug(`Shutdown warning: ${error}`);
5849
- }
5850
- const newHandle = await loadFlow(
5851
- newFile,
5852
- config,
5853
- logger,
5854
- loggerConfig,
5855
- healthServer,
5856
- observers
5915
+ function maskLine(line) {
5916
+ let s = line;
5917
+ s = s.replace(RE_URL_CREDS, "$1***$2");
5918
+ s = s.replace(RE_KV_SECRET, "$1$2***");
5919
+ s = s.replace(RE_PREFIXED_TOKEN, "***");
5920
+ s = s.replace(
5921
+ RE_TOKEN_RUN,
5922
+ (match) => shouldMaskToken(match) ? "***" : match
5857
5923
  );
5858
- logger.info("Flow swapped successfully");
5859
- return newHandle;
5924
+ return s;
5925
+ }
5926
+ function redactLine(line) {
5927
+ const withoutJsonKey = line.replace(RE_JSON_PRIVATE_KEY, '$1"***"');
5928
+ const rawLines = withoutJsonKey.split("\n");
5929
+ const cleanLines = removePemBlocks(rawLines);
5930
+ const maskedLines = cleanLines.map(maskLine);
5931
+ const joined = maskedLines.join("\n");
5932
+ if (joined.length > MAX_LENGTH) {
5933
+ return joined.slice(0, MAX_LENGTH - 1) + "\u2026";
5934
+ }
5935
+ return joined;
5936
+ }
5937
+ function redactErrors(errors) {
5938
+ return errors.map((e) => ({
5939
+ message: redactLine(e.message),
5940
+ count: e.count,
5941
+ firstSeen: new Date(e.firstSeen).toISOString(),
5942
+ lastSeen: new Date(e.lastSeen).toISOString()
5943
+ }));
5860
5944
  }
5861
-
5862
- // src/runtime/heartbeat.ts
5863
- import { randomBytes } from "crypto";
5864
-
5865
- // src/version.ts
5866
- import { readFileSync as readFileSync3 } from "fs";
5867
- import { fileURLToPath } from "url";
5868
- import { dirname as dirname5, join as join6 } from "path";
5869
- var versionFilename = fileURLToPath(import.meta.url);
5870
- var versionDirname = dirname5(versionFilename);
5871
- function findPackageJson() {
5872
- const paths = [
5873
- join6(versionDirname, "../package.json"),
5874
- // dist/ or src/
5875
- join6(versionDirname, "../../package.json")
5876
- // src/core/ (not used, but safe)
5877
- ];
5878
- for (const p of paths) {
5879
- try {
5880
- return readFileSync3(p, "utf-8");
5881
- } catch {
5882
- }
5883
- }
5884
- return JSON.stringify({ version: "0.0.0" });
5945
+ function redactLogs(entries) {
5946
+ return entries.map((e) => ({
5947
+ time: new Date(e.time).toISOString(),
5948
+ level: e.level,
5949
+ message: redactLine(e.message)
5950
+ }));
5885
5951
  }
5886
- var VERSION = JSON.parse(findPackageJson()).version;
5887
5952
 
5888
5953
  // src/runtime/heartbeat.ts
5889
- init_http();
5890
5954
  function computeCounterDelta(current, last) {
5891
5955
  const destinations = {};
5892
5956
  for (const [name, dest] of Object.entries(current.destinations)) {
@@ -5947,6 +6011,8 @@ function createHeartbeat(config, logger) {
5947
6011
  };
5948
6012
  counters = computeCounterDelta(current, lastReported);
5949
6013
  }
6014
+ const errors = config.getErrors ? redactErrors(config.getErrors()) : [];
6015
+ const logs = config.getLogs ? redactLogs(config.getLogs().slice(-50)) : [];
5950
6016
  const response = await fetch(
5951
6017
  `${config.appUrl}/api/projects/${config.projectId}/runners/heartbeat`,
5952
6018
  {
@@ -5963,7 +6029,9 @@ function createHeartbeat(config, logger) {
5963
6029
  configVersion,
5964
6030
  cliVersion: VERSION,
5965
6031
  uptime: Math.floor((Date.now() - startTime) / 1e3),
5966
- ...counters && { counters }
6032
+ ...counters && { counters },
6033
+ ...errors.length && { recentErrors: errors },
6034
+ ...logs.length && { recentLogs: logs }
5967
6035
  }),
5968
6036
  signal: AbortSignal.timeout(1e4)
5969
6037
  }
@@ -6038,6 +6106,275 @@ function createPoller(config, logger) {
6038
6106
  return { start, stop, pollOnce };
6039
6107
  }
6040
6108
 
6109
+ // src/runtime/health-server.ts
6110
+ import http from "http";
6111
+ function createHealthServer(port, logger) {
6112
+ return new Promise((resolve5, reject) => {
6113
+ let flowHandler = null;
6114
+ let ready = false;
6115
+ let failureReason = null;
6116
+ const server = http.createServer((req, res) => {
6117
+ if (req.url === "/health" && req.method === "GET") {
6118
+ res.writeHead(200, { "Content-Type": "application/json" });
6119
+ res.end(JSON.stringify({ status: "ok" }));
6120
+ return;
6121
+ }
6122
+ if (req.url === "/ready" && req.method === "GET") {
6123
+ const code = ready ? 200 : 503;
6124
+ const status = ready ? "ready" : failureReason ? "failed" : "not_ready";
6125
+ res.writeHead(code, { "Content-Type": "application/json" });
6126
+ res.end(
6127
+ JSON.stringify(
6128
+ failureReason && !ready ? { status, reason: failureReason } : { status }
6129
+ )
6130
+ );
6131
+ return;
6132
+ }
6133
+ if (flowHandler) {
6134
+ flowHandler(req, res);
6135
+ return;
6136
+ }
6137
+ res.writeHead(503, { "Content-Type": "application/json" });
6138
+ res.end(JSON.stringify({ error: "No flow loaded" }));
6139
+ });
6140
+ server.keepAliveTimeout = 5e3;
6141
+ server.headersTimeout = 1e4;
6142
+ server.listen(port, "0.0.0.0", () => {
6143
+ logger.info(`Health server listening on port ${port}`);
6144
+ resolve5({
6145
+ server,
6146
+ setFlowHandler(handler) {
6147
+ flowHandler = handler;
6148
+ },
6149
+ setReady(value) {
6150
+ ready = value;
6151
+ if (value) failureReason = null;
6152
+ },
6153
+ setFailed(reason) {
6154
+ ready = false;
6155
+ failureReason = reason;
6156
+ },
6157
+ close: () => new Promise((res, rej) => {
6158
+ server.close((err) => err ? rej(err) : res());
6159
+ })
6160
+ });
6161
+ });
6162
+ server.on("error", reject);
6163
+ });
6164
+ }
6165
+
6166
+ // src/runtime/secrets-fetcher.ts
6167
+ init_http();
6168
+ var SecretsHttpError = class extends Error {
6169
+ constructor(status, statusText) {
6170
+ super(`Failed to fetch secrets: ${status} ${statusText}`);
6171
+ this.status = status;
6172
+ this.name = "SecretsHttpError";
6173
+ }
6174
+ status;
6175
+ };
6176
+ async function fetchSecrets(options) {
6177
+ const { appUrl, token, projectId, flowId } = options;
6178
+ const url = `${appUrl}/api/projects/${encodeURIComponent(projectId)}/flows/${encodeURIComponent(flowId)}/secrets/values`;
6179
+ const res = await fetchWithRetry(url, {
6180
+ maxTotalMs: 2e4,
6181
+ init: {
6182
+ headers: mergeAuthHeaders(token, { "Content-Type": "application/json" })
6183
+ }
6184
+ });
6185
+ await throwIfRunnerAuthFailure(res);
6186
+ if (!res.ok) {
6187
+ throw new SecretsHttpError(res.status, res.statusText);
6188
+ }
6189
+ const json = await res.json();
6190
+ return json.values;
6191
+ }
6192
+
6193
+ // src/runtime/log-ring.ts
6194
+ var LogRing = class {
6195
+ constructor(max) {
6196
+ this.max = max;
6197
+ }
6198
+ max;
6199
+ entries = [];
6200
+ add(entry) {
6201
+ this.entries.push(entry);
6202
+ if (this.entries.length > this.max) this.entries.shift();
6203
+ }
6204
+ snapshot(limit = this.max) {
6205
+ return this.entries.slice(Math.max(0, this.entries.length - limit));
6206
+ }
6207
+ };
6208
+ var ErrorRing = class {
6209
+ constructor(maxUnique, now = () => Date.now()) {
6210
+ this.maxUnique = maxUnique;
6211
+ this.now = now;
6212
+ }
6213
+ maxUnique;
6214
+ now;
6215
+ map = /* @__PURE__ */ new Map();
6216
+ add(message) {
6217
+ const ts = this.now();
6218
+ const existing = this.map.get(message);
6219
+ if (existing) {
6220
+ existing.count += 1;
6221
+ existing.lastSeen = ts;
6222
+ return;
6223
+ }
6224
+ if (this.map.size >= this.maxUnique) {
6225
+ let oldestKey;
6226
+ let oldest = Infinity;
6227
+ for (const [k, v] of this.map) {
6228
+ if (v.lastSeen < oldest) {
6229
+ oldest = v.lastSeen;
6230
+ oldestKey = k;
6231
+ }
6232
+ }
6233
+ if (oldestKey !== void 0) this.map.delete(oldestKey);
6234
+ }
6235
+ this.map.set(message, { message, count: 1, firstSeen: ts, lastSeen: ts });
6236
+ }
6237
+ snapshot() {
6238
+ return [...this.map.values()].sort((a, b) => b.lastSeen - a.lastSeen);
6239
+ }
6240
+ };
6241
+
6242
+ // src/commands/run/index.ts
6243
+ init_tmp();
6244
+ init_config_file();
6245
+ init_auth();
6246
+ init_cache();
6247
+
6248
+ // src/commands/run/validators.ts
6249
+ init_asset_resolver();
6250
+ import { existsSync as existsSync4 } from "fs";
6251
+
6252
+ // src/schemas/primitives.ts
6253
+ import { z } from "@walkeros/core/dev";
6254
+ 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");
6255
+ var FilePathSchema = z.string().min(1, "File path cannot be empty").describe("Path to configuration file");
6256
+
6257
+ // src/schemas/run.ts
6258
+ import { z as z2 } from "@walkeros/core/dev";
6259
+ var RunOptionsSchema = z2.object({
6260
+ flow: FilePathSchema,
6261
+ port: PortSchema.default(8080),
6262
+ flowName: z2.string().optional().describe("Specific flow name to run")
6263
+ });
6264
+
6265
+ // src/schemas/validate.ts
6266
+ import { z as z3 } from "@walkeros/core/dev";
6267
+ var ValidationTypeSchema = z3.enum(["contract", "event", "flow", "mapping"]).describe('Validation type: "event", "flow", "mapping", or "contract"');
6268
+ var ValidateOptionsSchema = z3.object({
6269
+ flow: z3.string().optional().describe("Flow name for multi-flow configs"),
6270
+ path: z3.string().optional().describe(
6271
+ 'Entry path for package schema validation (e.g., "destinations.snowplow", "sources.browser")'
6272
+ )
6273
+ });
6274
+ var ValidateInputShape = {
6275
+ type: ValidationTypeSchema,
6276
+ input: z3.string().min(1).describe("JSON string, file path, or URL to validate"),
6277
+ flow: z3.string().optional().describe("Flow name for multi-flow configs"),
6278
+ path: z3.string().optional().describe(
6279
+ 'Entry path for package schema validation (e.g., "destinations.snowplow"). When provided, validates the entry against its package JSON Schema instead of using --type.'
6280
+ )
6281
+ };
6282
+ var ValidateInputSchema = z3.object(ValidateInputShape);
6283
+
6284
+ // src/schemas/bundle.ts
6285
+ import { z as z4 } from "@walkeros/core/dev";
6286
+ var BundleOptionsSchema = z4.object({
6287
+ silent: z4.boolean().optional().describe("Suppress all output"),
6288
+ verbose: z4.boolean().optional().describe("Enable verbose logging"),
6289
+ stats: z4.boolean().optional().default(true).describe("Return bundle statistics"),
6290
+ cache: z4.boolean().optional().default(true).describe("Enable package caching"),
6291
+ flowName: z4.string().optional().describe("Flow name for multi-flow configs")
6292
+ });
6293
+ var BundleInputShape = {
6294
+ configPath: FilePathSchema.describe(
6295
+ "Path to flow configuration file (JSON or JavaScript), URL, or inline JSON string"
6296
+ ),
6297
+ flow: z4.string().optional().describe("Flow name for multi-flow configs"),
6298
+ stats: z4.boolean().optional().default(true).describe("Return bundle statistics"),
6299
+ output: z4.string().optional().describe("Output file path (defaults to config-defined)")
6300
+ };
6301
+ var BundleInputSchema = z4.object(BundleInputShape);
6302
+
6303
+ // src/schemas/simulate.ts
6304
+ import { z as z5 } from "@walkeros/core/dev";
6305
+ var PlatformSchema = z5.enum(["web", "server"]).describe("Platform type for event processing");
6306
+ var SimulateOptionsSchema = z5.object({
6307
+ silent: z5.boolean().optional().describe("Suppress all output"),
6308
+ verbose: z5.boolean().optional().describe("Enable verbose logging"),
6309
+ json: z5.boolean().optional().describe("Format output as JSON")
6310
+ });
6311
+ var SimulateInputShape = {
6312
+ configPath: FilePathSchema.describe(
6313
+ "Path to flow configuration file, URL, or inline JSON string"
6314
+ ),
6315
+ event: z5.string().min(1).optional().describe(
6316
+ "Event as JSON string, file path, or URL. For sources: { content, trigger?, env? }."
6317
+ ),
6318
+ flow: z5.string().optional().describe("Flow name for multi-flow configs"),
6319
+ platform: PlatformSchema.optional().describe("Override platform detection"),
6320
+ step: z5.string().optional().describe(
6321
+ 'Step target in type.name format (e.g. "source.browser", "destination.gtag")'
6322
+ )
6323
+ };
6324
+ var SimulateInputSchema = z5.object(SimulateInputShape);
6325
+
6326
+ // src/schemas/push.ts
6327
+ import { z as z6 } from "@walkeros/core/dev";
6328
+ var PushOptionsSchema = z6.object({
6329
+ silent: z6.boolean().optional().describe("Suppress all output"),
6330
+ verbose: z6.boolean().optional().describe("Enable verbose logging"),
6331
+ json: z6.boolean().optional().describe("Format output as JSON")
6332
+ });
6333
+ var PushInputShape = {
6334
+ configPath: FilePathSchema.describe("Path to flow configuration file"),
6335
+ event: z6.string().min(1).describe("Event as JSON string, file path, or URL"),
6336
+ flow: z6.string().optional().describe("Flow name for multi-flow configs"),
6337
+ platform: PlatformSchema.optional().describe("Override platform detection")
6338
+ };
6339
+ var PushInputSchema = z6.object(PushInputShape);
6340
+
6341
+ // src/commands/run/validators.ts
6342
+ function validateFlowFile(filePath) {
6343
+ const absolutePath = resolveAsset(filePath, "bundle");
6344
+ if (!existsSync4(absolutePath)) {
6345
+ throw new Error(
6346
+ `Flow file not found: ${filePath}
6347
+ Resolved path: ${absolutePath}
6348
+ Make sure the file exists and the path is correct`
6349
+ );
6350
+ }
6351
+ return absolutePath;
6352
+ }
6353
+ function validatePort(port) {
6354
+ const result = PortSchema.safeParse(port);
6355
+ if (!result.success) {
6356
+ throw new Error(
6357
+ `Invalid port: ${port}
6358
+ Port must be an integer between 1 and 65535
6359
+ Example: --port 8080`
6360
+ );
6361
+ }
6362
+ }
6363
+
6364
+ // src/commands/run/index.ts
6365
+ init_utils3();
6366
+
6367
+ // src/commands/run/pipeline.ts
6368
+ init_tmp();
6369
+ import { writeFileSync as writeFileSync4 } from "fs";
6370
+ import fs17 from "fs-extra";
6371
+ import {
6372
+ createBatchedPoster,
6373
+ createTelemetryObserver,
6374
+ getTraceUntil,
6375
+ resolveTelemetryOptions
6376
+ } from "@walkeros/core";
6377
+
6041
6378
  // src/runtime/trace-poller.ts
6042
6379
  init_http();
6043
6380
  import { setTraceUntil } from "@walkeros/core";
@@ -6100,42 +6437,26 @@ function createTracePoller(config, logger) {
6100
6437
  }
6101
6438
  var defaultFetch = (url, init) => fetch(url, init);
6102
6439
 
6103
- // src/runtime/secrets-fetcher.ts
6104
- init_http();
6105
- var SecretsHttpError = class extends Error {
6106
- constructor(status, statusText) {
6107
- super(`Failed to fetch secrets: ${status} ${statusText}`);
6108
- this.status = status;
6109
- this.name = "SecretsHttpError";
6110
- }
6111
- status;
6112
- };
6113
- async function fetchSecrets(options) {
6114
- const { appUrl, token, projectId, flowId } = options;
6115
- const url = `${appUrl}/api/projects/${encodeURIComponent(projectId)}/flows/${encodeURIComponent(flowId)}/secrets/values`;
6116
- const res = await fetch(url, {
6117
- headers: mergeAuthHeaders(token, { "Content-Type": "application/json" })
6118
- });
6119
- await throwIfRunnerAuthFailure(res);
6120
- if (!res.ok) {
6121
- throw new SecretsHttpError(res.status, res.statusText);
6122
- }
6123
- const json = await res.json();
6124
- return json.values;
6125
- }
6126
-
6127
6440
  // src/commands/run/pipeline.ts
6128
6441
  init_cache();
6129
6442
  async function runPipeline(options) {
6130
6443
  const { bundlePath, port, logger, loggerConfig, api } = options;
6131
6444
  let configVersion;
6445
+ const configFrozen = readConfigFrozen();
6132
6446
  if (api) {
6133
6447
  await injectSecrets(api, logger);
6134
6448
  }
6135
6449
  logger.info(`walkeros/flow v${VERSION}`);
6136
6450
  logger.info(`Instance: ${getInstanceId()}`);
6451
+ if (configFrozen) {
6452
+ logger.info("Config frozen: hot-swap and heartbeat disabled");
6453
+ }
6137
6454
  const healthServer = await createHealthServer(port, logger);
6138
- const telemetryObservers = buildTelemetryObservers(api?.flowId ?? "flow");
6455
+ const observeLevel = readObserveLevel(logger);
6456
+ const telemetryObservers = buildTelemetryObservers(
6457
+ api?.flowId ?? "flow",
6458
+ observeLevel
6459
+ );
6139
6460
  const runtimeConfig = { port };
6140
6461
  let handle;
6141
6462
  try {
@@ -6164,20 +6485,24 @@ async function runPipeline(options) {
6164
6485
  const ingestToken = process.env.WALKEROS_INGEST_TOKEN;
6165
6486
  const deploymentId = process.env.WALKEROS_DEPLOYMENT_ID;
6166
6487
  if (observerBase && ingestToken && deploymentId) {
6167
- tracePoller = createTracePoller(
6168
- {
6169
- url: `${observerBase}/trace/${deploymentId}`,
6170
- token: ingestToken,
6171
- intervalMs: 15e3
6172
- },
6173
- logger
6174
- );
6175
- tracePoller.start();
6176
- logger.info("Trace poller: active (every 15s)");
6488
+ if (observeLevel === "trace") {
6489
+ logger.info("Trace poller: skipped (observe level is trace)");
6490
+ } else {
6491
+ tracePoller = createTracePoller(
6492
+ {
6493
+ url: `${observerBase}/trace/${deploymentId}`,
6494
+ token: ingestToken,
6495
+ intervalMs: 15e3
6496
+ },
6497
+ logger
6498
+ );
6499
+ tracePoller.start();
6500
+ logger.info("Trace poller: active (every 15s)");
6501
+ }
6177
6502
  }
6178
6503
  let currentBundleCleanup;
6179
6504
  let currentConfigPath;
6180
- if (api) {
6505
+ if (api && !configFrozen) {
6181
6506
  heartbeat = createHeartbeat(
6182
6507
  {
6183
6508
  appUrl: api.appUrl,
@@ -6187,7 +6512,9 @@ async function runPipeline(options) {
6187
6512
  deploymentId: api.deploymentId,
6188
6513
  configVersion,
6189
6514
  intervalMs: api.heartbeatIntervalMs,
6190
- getCounters: () => handle.collector.status
6515
+ getCounters: () => handle.collector.status,
6516
+ getErrors: () => options.errorRing?.snapshot() ?? [],
6517
+ getLogs: () => options.logRing?.snapshot() ?? []
6191
6518
  },
6192
6519
  logger
6193
6520
  );
@@ -6294,17 +6621,39 @@ async function runPipeline(options) {
6294
6621
  await new Promise(() => {
6295
6622
  });
6296
6623
  }
6297
- function buildTelemetryObservers(flowId) {
6624
+ var OBSERVE_LEVELS = [
6625
+ "off",
6626
+ "standard",
6627
+ "trace"
6628
+ ];
6629
+ function readConfigFrozen() {
6630
+ const raw = process.env.WALKEROS_CONFIG_FROZEN;
6631
+ return raw === "1" || raw === "true";
6632
+ }
6633
+ function readObserveLevel(logger) {
6634
+ const raw = process.env.WALKEROS_OBSERVE_LEVEL;
6635
+ if (raw === void 0 || raw === "") return void 0;
6636
+ const level = OBSERVE_LEVELS.find((candidate) => candidate === raw);
6637
+ if (!level) {
6638
+ logger.warn(
6639
+ `Ignoring invalid WALKEROS_OBSERVE_LEVEL "${raw}" (expected off, standard, or trace)`
6640
+ );
6641
+ return void 0;
6642
+ }
6643
+ return level;
6644
+ }
6645
+ function buildTelemetryObservers(flowId, observeLevel) {
6298
6646
  const base = process.env.WALKEROS_OBSERVER_URL;
6299
6647
  const token = process.env.WALKEROS_INGEST_TOKEN;
6300
6648
  const deploymentId = process.env.WALKEROS_DEPLOYMENT_ID;
6301
6649
  if (!base || !token || !deploymentId) return void 0;
6302
6650
  const url = `${base}/ingest/${deploymentId}`;
6303
6651
  const emit = createBatchedPoster({ url, token });
6652
+ const observe = observeLevel !== void 0 ? { level: observeLevel } : void 0;
6304
6653
  return [
6305
6654
  createTelemetryObserver(
6306
6655
  emit,
6307
- () => resolveTelemetryOptions({ flowId, traceUntil: getTraceUntil() })
6656
+ () => resolveTelemetryOptions({ flowId, observe, traceUntil: getTraceUntil() })
6308
6657
  )
6309
6658
  ];
6310
6659
  }
@@ -6347,7 +6696,21 @@ async function lazyPrepareBundleForRun(configPath, options) {
6347
6696
  async function runCommand(options) {
6348
6697
  const timer = createTimer();
6349
6698
  timer.start();
6350
- const logger = createCLILogger(options);
6699
+ const errorRing = new ErrorRing(20);
6700
+ const logRing = new LogRing(100);
6701
+ const LEVEL_NAME = {
6702
+ [Level2.ERROR]: "error",
6703
+ [Level2.WARN]: "warn",
6704
+ [Level2.INFO]: "info",
6705
+ [Level2.DEBUG]: "debug"
6706
+ };
6707
+ const logger = createCLILogger({
6708
+ ...options,
6709
+ onLine: (level, message) => {
6710
+ if (level === Level2.ERROR) errorRing.add(message);
6711
+ logRing.add({ time: Date.now(), level: LEVEL_NAME[level], message });
6712
+ }
6713
+ });
6351
6714
  try {
6352
6715
  if (options.envFile) {
6353
6716
  const { loadEnvFile: loadEnvFile2 } = await Promise.resolve().then(() => (init_env_file(), env_file_exports));
@@ -6417,7 +6780,9 @@ async function runCommand(options) {
6417
6780
  port,
6418
6781
  logger: logger.scope("runner"),
6419
6782
  loggerConfig: options.verbose ? { level: 0 } : void 0,
6420
- api: apiConfig
6783
+ api: apiConfig,
6784
+ errorRing,
6785
+ logRing
6421
6786
  });
6422
6787
  } catch (error) {
6423
6788
  const duration = timer.getElapsed() / 1e3;
@@ -7235,7 +7600,7 @@ function validateMapping(input) {
7235
7600
  // src/commands/validate/validators/entry.ts
7236
7601
  import Ajv from "ajv";
7237
7602
  import { fetchPackageSchema } from "@walkeros/core";
7238
- var CLIENT_HEADER = "walkeros-cli/4.2.0";
7603
+ var CLIENT_HEADER = "walkeros-cli/4.2.1-next-1781538735002";
7239
7604
  var SECTIONS = ["destinations", "sources", "transformers"];
7240
7605
  function resolveEntry(path20, flowConfig) {
7241
7606
  const flows = flowConfig.flows;
@@ -7800,8 +8165,8 @@ import createClient from "openapi-fetch";
7800
8165
  init_config_file();
7801
8166
  import { createHash } from "crypto";
7802
8167
  import semver4 from "semver";
7803
- var bakedContractVersion = true ? "1.1.0" : PLACEHOLDER;
7804
- var bakedContractHash = true ? "847253b928a84dcdfc448e296418a5e48fa97b95f4a655e47e400d59d8e7f3cd" : "";
8168
+ var bakedContractVersion = true ? "2.1.0" : PLACEHOLDER;
8169
+ var bakedContractHash = true ? "e5c8e836e1bd59ac2c56d40a998943f301e7e4516528c66d87dc51d6525c8b57" : "";
7805
8170
  function isRecord2(value) {
7806
8171
  return value !== null && typeof value === "object" && !Array.isArray(value);
7807
8172
  }
@@ -7932,9 +8297,13 @@ function createApiClient() {
7932
8297
  }
7933
8298
 
7934
8299
  // src/core/api-error.ts
8300
+ var EXIT_RETRYABLE = 75;
7935
8301
  var ApiError = class extends Error {
7936
8302
  code;
7937
8303
  details;
8304
+ status;
8305
+ retryable;
8306
+ retryAfterSeconds;
7938
8307
  // Populated only for CLIENT_OUTDATED responses (HTTP 426).
7939
8308
  minVersion;
7940
8309
  clientVersion;
@@ -7946,6 +8315,9 @@ var ApiError = class extends Error {
7946
8315
  this.name = "ApiError";
7947
8316
  this.code = options?.code;
7948
8317
  this.details = options?.details;
8318
+ this.status = options?.status;
8319
+ this.retryable = options?.retryable;
8320
+ this.retryAfterSeconds = options?.retryAfterSeconds;
7949
8321
  this.minVersion = options?.minVersion;
7950
8322
  this.clientVersion = options?.clientVersion;
7951
8323
  this.client = options?.client;
@@ -7953,25 +8325,62 @@ var ApiError = class extends Error {
7953
8325
  this.docs = options?.docs;
7954
8326
  }
7955
8327
  };
7956
- function throwApiError(error, fallbackMessage) {
7957
- if (error && typeof error === "object" && "error" in error && typeof error.error === "object") {
7958
- const inner = error.error;
7959
- const message = inner.message || fallbackMessage;
7960
- const code = inner.code;
7961
- const details = inner.details?.errors;
7962
- const options = { code, details };
7963
- if (code === "CLIENT_OUTDATED") {
7964
- options.minVersion = inner.minVersion;
7965
- options.clientVersion = inner.clientVersion;
7966
- options.client = inner.client;
7967
- options.upgrade = inner.upgrade;
7968
- options.docs = inner.docs;
7969
- }
7970
- throw new ApiError(message, options);
8328
+ function extractApiErrorOptions(error, fallbackMessage) {
8329
+ if (!error || typeof error !== "object" || !("error" in error) || typeof error.error !== "object") {
8330
+ return null;
8331
+ }
8332
+ const inner = error.error;
8333
+ const message = inner.message || fallbackMessage;
8334
+ const code = inner.code;
8335
+ const details = inner.details?.errors;
8336
+ const options = { code, details };
8337
+ if (code === "CLIENT_OUTDATED") {
8338
+ options.minVersion = inner.minVersion;
8339
+ options.clientVersion = inner.clientVersion;
8340
+ options.client = inner.client;
8341
+ options.upgrade = inner.upgrade;
8342
+ options.docs = inner.docs;
7971
8343
  }
8344
+ return { message, options };
8345
+ }
8346
+ function throwApiError(error, fallbackMessage) {
8347
+ const extracted = extractApiErrorOptions(error, fallbackMessage);
8348
+ if (extracted) throw new ApiError(extracted.message, extracted.options);
7972
8349
  throw new ApiError(fallbackMessage);
7973
8350
  }
8351
+ function parseRetryAfter(value) {
8352
+ if (!value) return void 0;
8353
+ const trimmed = value.trim();
8354
+ if (/^\d+$/.test(trimmed)) return parseInt(trimmed, 10);
8355
+ const when = Date.parse(trimmed);
8356
+ if (Number.isNaN(when)) return void 0;
8357
+ return Math.max(0, Math.round((when - Date.now()) / 1e3));
8358
+ }
8359
+ function throwApiResponseError(response, body, fallbackMessage) {
8360
+ const status = response.status;
8361
+ const retryAfterSeconds = parseRetryAfter(
8362
+ response.headers.get("retry-after")
8363
+ );
8364
+ const retryable = status === 429 || status === 503 && retryAfterSeconds !== void 0;
8365
+ const extracted = extractApiErrorOptions(body, fallbackMessage);
8366
+ const message = extracted?.message ?? fallbackMessage;
8367
+ const options = {
8368
+ ...extracted?.options ?? {},
8369
+ status,
8370
+ retryable,
8371
+ retryAfterSeconds
8372
+ };
8373
+ throw new ApiError(message, options);
8374
+ }
8375
+ function machineReadableErrorLine(err) {
8376
+ const code = err instanceof ApiError ? err.code ?? "UNKNOWN" : "UNKNOWN";
8377
+ const retryable = err instanceof ApiError ? err.retryable === true : false;
8378
+ const retryAfter = err instanceof ApiError && err.retryAfterSeconds !== void 0 ? ` retryAfter=${err.retryAfterSeconds}` : "";
8379
+ const message = err instanceof Error ? err.message : String(err);
8380
+ return `error: code=${code} retryable=${retryable}${retryAfter} message=${message}`;
8381
+ }
7974
8382
  function handleCliError(err) {
8383
+ console.error(machineReadableErrorLine(err));
7975
8384
  if (err instanceof ApiError && err.code === "CLIENT_OUTDATED") {
7976
8385
  console.error(`
7977
8386
  ${err.message}
@@ -7981,8 +8390,12 @@ ${err.message}
7981
8390
  `);
7982
8391
  process.exit(2);
7983
8392
  }
7984
- const message = err instanceof Error ? err.message : String(err);
7985
- console.error(message);
8393
+ if (err instanceof ApiError && err.retryable) {
8394
+ const hint = err.retryAfterSeconds !== void 0 ? ` Retry after ${err.retryAfterSeconds}s.` : " This is temporary, retry shortly.";
8395
+ console.error(`${err.message}${hint}`);
8396
+ process.exit(EXIT_RETRYABLE);
8397
+ }
8398
+ console.error(err instanceof Error ? err.message : String(err));
7986
8399
  process.exit(1);
7987
8400
  }
7988
8401
 
@@ -8278,6 +8691,7 @@ async function readFlowStdin() {
8278
8691
  }
8279
8692
 
8280
8693
  // src/commands/deploy/index.ts
8694
+ import { randomUUID } from "crypto";
8281
8695
  init_auth();
8282
8696
  init_http();
8283
8697
  init_sse();
@@ -8308,8 +8722,9 @@ async function getAvailableFlowNames(options) {
8308
8722
  const settings = flow.settings;
8309
8723
  return settings?.map((c) => c.name) ?? [];
8310
8724
  }
8725
+ var DEFAULT_DEPLOY_WAIT_MS = 12 * 60 * 1e3;
8311
8726
  async function streamDeploymentStatus(projectId, deploymentId, options) {
8312
- const timeoutMs = options.timeout ?? 12e4;
8727
+ const timeoutMs = options.timeout ?? DEFAULT_DEPLOY_WAIT_MS;
8313
8728
  const response = await apiFetch(
8314
8729
  `/api/projects/${projectId}/deployments/${deploymentId}/stream`,
8315
8730
  {
@@ -8368,7 +8783,10 @@ async function deploy(options) {
8368
8783
  }
8369
8784
  const { data, error } = await client.POST(
8370
8785
  "/api/projects/{projectId}/flows/{flowId}/deploy",
8371
- { params: { path: { projectId, flowId: options.flowId } } }
8786
+ {
8787
+ params: { path: { projectId, flowId: options.flowId } },
8788
+ headers: { "Idempotency-Key": randomUUID() }
8789
+ }
8372
8790
  );
8373
8791
  if (error) {
8374
8792
  try {
@@ -8398,13 +8816,38 @@ Available: ${names.join(", ")}`,
8398
8816
  }
8399
8817
  async function deploySettings(options) {
8400
8818
  const { flowId, projectId, settingsId } = options;
8401
- const response = await apiFetch(
8819
+ const triggerDeploy = () => apiFetch(
8402
8820
  `/api/projects/${projectId}/flows/${flowId}/settings/${settingsId}/deploy`,
8403
- { method: "POST" }
8821
+ { method: "POST", headers: { "Idempotency-Key": randomUUID() } }
8404
8822
  );
8823
+ let response = await triggerDeploy();
8824
+ if (!response.ok && options.wait) {
8825
+ let retryBody = {};
8826
+ try {
8827
+ retryBody = await response.clone().json();
8828
+ } catch {
8829
+ retryBody = {};
8830
+ }
8831
+ try {
8832
+ throwApiResponseError(
8833
+ response,
8834
+ retryBody,
8835
+ `Deploy failed (${response.status})`
8836
+ );
8837
+ } catch (e) {
8838
+ if (e instanceof ApiError && e.retryable) {
8839
+ const waitSeconds = Math.min(e.retryAfterSeconds ?? 5, 60);
8840
+ options.onStatus?.("rate_limited", `retrying in ${waitSeconds}s`);
8841
+ await new Promise((resolve5) => setTimeout(resolve5, waitSeconds * 1e3));
8842
+ response = await triggerDeploy();
8843
+ } else {
8844
+ throw e;
8845
+ }
8846
+ }
8847
+ }
8405
8848
  if (!response.ok) {
8406
8849
  const body = await response.json().catch(() => ({}));
8407
- throwApiError(body, `Deploy failed (${response.status})`);
8850
+ throwApiResponseError(response, body, `Deploy failed (${response.status})`);
8408
8851
  }
8409
8852
  const data = await response.json();
8410
8853
  if (!options.wait) return data;
@@ -8428,7 +8871,7 @@ async function getDeployment(options) {
8428
8871
  );
8429
8872
  if (!response.ok) {
8430
8873
  const body = await response.json().catch(() => ({}));
8431
- throwApiError(body, "Failed to get deployment");
8874
+ throwApiResponseError(response, body, "Failed to get deployment");
8432
8875
  }
8433
8876
  return response.json();
8434
8877
  }
@@ -8441,16 +8884,20 @@ async function getDeployment(options) {
8441
8884
  return data;
8442
8885
  }
8443
8886
  var statusLabels = {
8444
- bundling: "Building bundle...",
8445
- "bundling:building": "Building bundle...",
8446
- "bundling:publishing": "Publishing to web...",
8447
- deploying: "Deploying container...",
8887
+ "deploying:building": "Building bundle...",
8888
+ "deploying:publishing": "Publishing to web...",
8448
8889
  "deploying:provisioning": "Provisioning container...",
8449
8890
  "deploying:starting": "Starting container...",
8891
+ deploying: "Deploying...",
8450
8892
  active: "Container is live",
8451
8893
  published: "Published",
8894
+ stopped: "Stopped",
8452
8895
  failed: "Deployment failed"
8453
8896
  };
8897
+ function renderStatusLabel(status, substatus) {
8898
+ const key = substatus ? `${status}:${substatus}` : status;
8899
+ return statusLabels[key] || statusLabels[status] || `Status: ${status}`;
8900
+ }
8454
8901
  async function deployCommand(flowId, options) {
8455
8902
  const log = createCLILogger(options);
8456
8903
  const timeoutMs = options.timeout ? parseInt(options.timeout, 10) * 1e3 : void 0;
@@ -8462,10 +8909,7 @@ async function deployCommand(flowId, options) {
8462
8909
  wait: options.wait !== false,
8463
8910
  timeout: timeoutMs,
8464
8911
  onStatus: options.json ? void 0 : (status, substatus) => {
8465
- const key = substatus ? `${status}:${substatus}` : status;
8466
- log.info(
8467
- statusLabels[key] || statusLabels[status] || `Status: ${status}`
8468
- );
8912
+ log.info(renderStatusLabel(status, substatus));
8469
8913
  }
8470
8914
  });
8471
8915
  if (options.json) {
@@ -8480,7 +8924,7 @@ async function deployCommand(flowId, options) {
8480
8924
  } else if (r.status === "failed") {
8481
8925
  log.error(`Failed: ${r.errorMessage || "Unknown error"}`);
8482
8926
  process.exit(1);
8483
- } else if (r.status === "bundling") {
8927
+ } else if (r.status === "deploying") {
8484
8928
  log.info(`Deployment started: ${r.deploymentId} (${r.type})`);
8485
8929
  } else {
8486
8930
  log.info(`Status: ${r.status}`);
@@ -8708,20 +9152,16 @@ async function createDeployCommand(config, options) {
8708
9152
  log.info(`Deployment created: ${result.id}`);
8709
9153
  log.info(` Slug: ${result.slug}`);
8710
9154
  log.info(` Type: ${result.type}`);
8711
- if (result.deployToken) {
8712
- log.info(` Token: ${result.deployToken}`);
8713
- log.warn(" Save this token \u2014 it will not be shown again.");
8714
- }
8715
9155
  log.info("");
8716
9156
  log.info("Run locally:");
8717
9157
  log.info(
8718
9158
  ` walkeros run ${isRemoteFlow ? "flow.json" : config} --deploy ${result.id}`
8719
9159
  );
8720
9160
  log.info("");
8721
- log.info("Docker:");
8722
- log.info(
8723
- ` docker run -e WALKEROS_DEPLOY_TOKEN=${result.deployToken ?? "<token>"} \\`
8724
- );
9161
+ log.info("Create a deploy token for this flow in the app");
9162
+ log.info("(Settings, Self-hosted deploy token) and set it as");
9163
+ log.info("WALKEROS_DEPLOY_TOKEN, then run with Docker:");
9164
+ log.info(" docker run -e WALKEROS_DEPLOY_TOKEN \\");
8725
9165
  log.info(" -e WALKEROS_APP_URL=https://app.walkeros.io \\");
8726
9166
  log.info(" walkeros/flow:latest");
8727
9167
  } catch (err) {
@@ -8807,14 +9247,14 @@ init_config_file();
8807
9247
 
8808
9248
  // src/telemetry/install-id.ts
8809
9249
  init_config_file();
8810
- import { randomUUID } from "crypto";
9250
+ import { randomUUID as randomUUID2 } from "crypto";
8811
9251
  function getInstallationId() {
8812
9252
  return readConfig()?.installationId;
8813
9253
  }
8814
9254
  function createInstallationId() {
8815
9255
  const existing = readConfig()?.installationId;
8816
9256
  if (existing) return existing;
8817
- const id = randomUUID();
9257
+ const id = randomUUID2();
8818
9258
  writeTelemetryOnlyConfig({ installationId: id });
8819
9259
  return id;
8820
9260
  }
@@ -8875,6 +9315,8 @@ function telemetryDisableCommand() {
8875
9315
 
8876
9316
  // src/index.ts
8877
9317
  init_bundle();
9318
+ init_bundler();
9319
+ init_config_classifier();
8878
9320
 
8879
9321
  // src/commands/bundle/validate-structure.ts
8880
9322
  init_structural_validators();
@@ -9560,12 +10002,15 @@ export {
9560
10002
  apiFetch,
9561
10003
  bakedContractHash,
9562
10004
  bakedContractVersion,
10005
+ buildDataPayload,
9563
10006
  bundle,
9564
10007
  bundleCommand,
9565
10008
  canonicalContractHash,
10009
+ classifyStepProperties,
9566
10010
  clientContextHeaders,
9567
10011
  compareContract,
9568
10012
  compareOutput,
10013
+ containsCodeMarkers,
9569
10014
  createApiClient,
9570
10015
  createDeployCommand,
9571
10016
  createDeployment,