@walkeros/cli 4.6.1 → 4.7.0-next-1790187973605

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
@@ -2689,6 +2689,7 @@ import path12 from "path";
2689
2689
  import fs11 from "fs-extra";
2690
2690
  import {
2691
2691
  packageNameToVariable as packageNameToVariable2,
2692
+ getStepRuntimeProps,
2692
2693
  ENV_MARKER_PREFIX,
2693
2694
  SECRET_MARKER_PREFIX as SECRET_MARKER_PREFIX2,
2694
2695
  isPathStepEntry,
@@ -2701,29 +2702,25 @@ function isInlineCode2(code) {
2701
2702
  function hasCodeReference2(code) {
2702
2703
  return isInlineCode2(code) || typeof code === "string";
2703
2704
  }
2704
- function generateInlineCode(inline, config, env, chains, isDestination) {
2705
+ function generateInlineCode(inline, runtimeProps, isDestination) {
2705
2706
  const pushFn = inline.push.replace("$code:", "");
2706
2707
  const initFn = inline.init ? inline.init.replace("$code:", "") : void 0;
2707
2708
  const typeLine = inline.type ? `type: '${inline.type}',` : "";
2708
- const chainLines = [];
2709
- if (chains?.before !== void 0) {
2710
- chainLines.push(`before: ${JSON.stringify(chains.before)}`);
2711
- }
2712
- if (chains?.next !== void 0) {
2713
- chainLines.push(`next: ${JSON.stringify(chains.next)}`);
2714
- }
2715
- const chainBlock = chainLines.length ? `,
2716
- ${chainLines.join(",\n ")}` : "";
2709
+ const props = {
2710
+ config: {},
2711
+ env: {},
2712
+ ...runtimeProps
2713
+ };
2714
+ const propLines = Object.entries(props).map(([key, value]) => `${key}: ${processConfigValue(value)}`).join(",\n ");
2717
2715
  if (isDestination) {
2718
2716
  return `{
2719
2717
  code: {
2720
2718
  ${typeLine}
2721
- config: ${processConfigValue(config || {})},
2719
+ config: ${processConfigValue(props.config)},
2722
2720
  ${initFn ? `init: ${initFn},` : ""}
2723
2721
  push: ${pushFn}
2724
2722
  },
2725
- config: ${processConfigValue(config || {})},
2726
- env: ${processConfigValue(env || {})}${chainBlock}
2723
+ ${propLines}
2727
2724
  }`;
2728
2725
  }
2729
2726
  return `{
@@ -2733,8 +2730,7 @@ function generateInlineCode(inline, config, env, chains, isDestination) {
2733
2730
  ${initFn ? `init: ${initFn},` : ""}
2734
2731
  push: ${pushFn}
2735
2732
  }),
2736
- config: ${processConfigValue(config || {})},
2737
- env: ${processConfigValue(env || {})}${chainBlock}
2733
+ ${propLines}
2738
2734
  }`;
2739
2735
  }
2740
2736
  async function copyIncludes(includes, sourceDir, outputDir, logger) {
@@ -3461,24 +3457,11 @@ function buildSplitConfigObject(flowSettings, namedImports) {
3461
3457
  }
3462
3458
  return packageNameToVariable2(step.package);
3463
3459
  }
3464
- function getStepProps(step) {
3465
- const props = {};
3466
- for (const [key, value] of Object.entries(step)) {
3467
- if (key === "code" || key === "package" || key === "import") continue;
3468
- if (value !== void 0 && value !== null) {
3469
- props[key] = value;
3470
- }
3471
- }
3472
- return props;
3473
- }
3474
- function buildSplitStepEntry(section, stepId3, step) {
3475
- const codeVar = resolveCodeVar(step);
3476
- const stepProps = getStepProps(step);
3477
- const { codeProps, dataProps } = classifyStepProperties(stepProps);
3460
+ function buildSplitStepEntry(section, stepId3, codeVar, runtimeProps) {
3461
+ const { codeProps, dataProps } = classifyStepProperties(runtimeProps);
3478
3462
  const codeEntries = [];
3479
- codeEntries.push(`code: ${codeVar}`);
3463
+ if (codeVar !== void 0) codeEntries.push(`code: ${codeVar}`);
3480
3464
  for (const [key, value] of Object.entries(codeProps)) {
3481
- if (key === "code") continue;
3482
3465
  codeEntries.push(`${key}: ${processConfigValue(value)}`);
3483
3466
  }
3484
3467
  for (const key of Object.keys(dataProps)) {
@@ -3492,6 +3475,14 @@ function buildSplitConfigObject(flowSettings, namedImports) {
3492
3475
  ${codeEntries.join(",\n ")}
3493
3476
  }`;
3494
3477
  }
3478
+ function buildStepEntry(kind, section, stepId3, step) {
3479
+ const runtimeProps = getStepRuntimeProps(step, kind);
3480
+ if (isInlineCode2(step.code)) {
3481
+ return ` ${stepId3}: ${generateInlineCode(step.code, runtimeProps, kind === "Destination")}`;
3482
+ }
3483
+ const codeVar = isPathStepEntry({ ...step }, kind) ? void 0 : resolveCodeVar(step);
3484
+ return buildSplitStepEntry(section, stepId3, codeVar, runtimeProps);
3485
+ }
3495
3486
  Object.entries(sources).forEach(([name, source]) => {
3496
3487
  validateReference("Source", name, source);
3497
3488
  });
@@ -3501,75 +3492,21 @@ function buildSplitConfigObject(flowSettings, namedImports) {
3501
3492
  Object.entries(transformers).forEach(([name, transformer]) => {
3502
3493
  validateReference("Transformer", name, transformer);
3503
3494
  });
3504
- const sourcesEntries = Object.entries(sources).filter(([, source]) => source.package || hasCodeReference2(source.code)).map(([key, source]) => {
3505
- if (isInlineCode2(source.code)) {
3506
- return ` ${key}: ${generateInlineCode(source.code, source.config || {}, source.env, { next: source.next })}`;
3507
- }
3508
- return buildSplitStepEntry("sources", key, source);
3509
- });
3510
- const destinationsEntries = Object.entries(destinations).filter(([, dest]) => dest.package || hasCodeReference2(dest.code)).map(([key, dest]) => {
3511
- if (isInlineCode2(dest.code)) {
3512
- return ` ${key}: ${generateInlineCode(dest.code, dest.config || {}, dest.env, { before: dest.before, next: dest.next }, true)}`;
3513
- }
3514
- return buildSplitStepEntry("destinations", key, dest);
3515
- });
3495
+ const sourcesEntries = Object.entries(sources).filter(([, source]) => source.package || hasCodeReference2(source.code)).map(([key, source]) => buildStepEntry("Source", "sources", key, source));
3496
+ const destinationsEntries = Object.entries(destinations).filter(([, dest]) => dest.package || hasCodeReference2(dest.code)).map(
3497
+ ([key, dest]) => buildStepEntry("Destination", "destinations", key, dest)
3498
+ );
3516
3499
  const transformersEntries = Object.entries(transformers).filter(
3517
3500
  ([, transformer]) => transformer.package || hasCodeReference2(transformer.code) || isPathStepEntry({ ...transformer }, "Transformer")
3518
- ).map(([key, transformer]) => {
3519
- if (isInlineCode2(transformer.code)) {
3520
- return ` ${key}: ${generateInlineCode(transformer.code, transformer.config || {}, transformer.env, { before: transformer.before, next: transformer.next })}`;
3521
- }
3522
- if (isPathStepEntry({ ...transformer }, "Transformer")) {
3523
- const chainLines = [];
3524
- if (transformer.before !== void 0) {
3525
- chainLines.push(`before: ${JSON.stringify(transformer.before)}`);
3526
- }
3527
- if (transformer.next !== void 0) {
3528
- chainLines.push(`next: ${JSON.stringify(transformer.next)}`);
3529
- }
3530
- if (transformer.cache !== void 0) {
3531
- chainLines.push(`cache: ${JSON.stringify(transformer.cache)}`);
3532
- }
3533
- if (transformer.config !== void 0) {
3534
- chainLines.push(
3535
- `config: ${processConfigValue(transformer.config)}`
3536
- );
3537
- }
3538
- return ` ${key}: {
3539
- ${chainLines.join(",\n ")}
3540
- }`;
3541
- }
3542
- return buildSplitStepEntry("transformers", key, transformer);
3543
- });
3501
+ ).map(
3502
+ ([key, transformer]) => buildStepEntry("Transformer", "transformers", key, transformer)
3503
+ );
3544
3504
  Object.entries(stores).forEach(([name, store]) => {
3545
3505
  if (store.package || hasCodeReference2(store.code)) {
3546
3506
  validateReference("Store", name, store);
3547
3507
  }
3548
3508
  });
3549
- const storesEntries = Object.entries(stores).filter(([, store]) => store.package || hasCodeReference2(store.code)).map(([key, store]) => {
3550
- if (isInlineCode2(store.code)) {
3551
- return ` ${key}: ${generateInlineCode(store.code, store.config || {}, store.env)}`;
3552
- }
3553
- const codeVar = resolveCodeVar(store);
3554
- const storeProps = getStepProps(store);
3555
- const { codeProps, dataProps } = classifyStepProperties(storeProps);
3556
- const codeEntries = [];
3557
- codeEntries.push(`code: ${codeVar}`);
3558
- for (const [propKey, value] of Object.entries(codeProps)) {
3559
- if (propKey === "code") continue;
3560
- codeEntries.push(`${propKey}: ${processConfigValue(value)}`);
3561
- }
3562
- for (const propKey of Object.keys(dataProps)) {
3563
- codeEntries.push(`${propKey}: __data.stores.${key}.${propKey}`);
3564
- }
3565
- if (Object.keys(dataProps).length > 0) {
3566
- if (!dataPayloadObj["stores"]) dataPayloadObj["stores"] = {};
3567
- dataPayloadObj["stores"][key] = dataProps;
3568
- }
3569
- return ` ${key}: {
3570
- ${codeEntries.join(",\n ")}
3571
- }`;
3572
- });
3509
+ const storesEntries = Object.entries(stores).filter(([, store]) => store.package || hasCodeReference2(store.code)).map(([key, store]) => buildStepEntry("Store", "stores", key, store));
3573
3510
  const storesDeclaration = storesEntries.length > 0 ? `const stores = {
3574
3511
  ${storesEntries.join(",\n")}
3575
3512
  };` : "const stores = {};";
@@ -4486,17 +4423,11 @@ init_utils();
4486
4423
  init_bundler();
4487
4424
  import path16 from "path";
4488
4425
  import fs15 from "fs-extra";
4489
- import {
4490
- createIngest,
4491
- getPlatform as getPlatform3,
4492
- getNextSteps,
4493
- buildCacheContext,
4494
- stepId
4495
- } from "@walkeros/core";
4426
+ import { createIngest, getPlatform as getPlatform3, stepId } from "@walkeros/core";
4496
4427
  import {
4497
4428
  enrichEvent,
4498
4429
  transformerInit,
4499
- transformerPush,
4430
+ runCollectorNext,
4500
4431
  runTransformerChain,
4501
4432
  wrapEnv
4502
4433
  } from "@walkeros/collector";
@@ -4564,6 +4495,11 @@ function buildOverrides(flags, flowConfig) {
4564
4495
  `--mock is not supported for sources. Use --simulate source.${parsed.name}`
4565
4496
  );
4566
4497
  }
4498
+ if (parsed.type === "collector" && !parsed.chainType) {
4499
+ throw new Error(
4500
+ `Use --mock collector.next.TRANSFORMER=VALUE to mock a step of the collector's chain`
4501
+ );
4502
+ }
4567
4503
  if (parsed.type === "transformer" && !parsed.chainType) {
4568
4504
  throw new Error(
4569
4505
  `Use --mock destination.NAME.before.${parsed.name}=VALUE for path-specific transformer mocks`
@@ -4576,7 +4512,7 @@ function buildOverrides(flags, flowConfig) {
4576
4512
  parsedValue = valuePart;
4577
4513
  }
4578
4514
  if (parsed.chainType && parsed.transformerId) {
4579
- const chainPath = `destination.${parsed.name}.${parsed.chainType}`;
4515
+ const chainPath = parsed.type === "collector" ? `collector.${parsed.chainType}` : `destination.${parsed.name}.${parsed.chainType}`;
4580
4516
  if (!overrides.transformerMocks) overrides.transformerMocks = {};
4581
4517
  if (!overrides.transformerMocks[chainPath])
4582
4518
  overrides.transformerMocks[chainPath] = {};
@@ -4613,6 +4549,19 @@ function parseStep(step) {
4613
4549
  missingName: (input, p) => `Invalid step format: "${input}". Missing name after "${p}."`
4614
4550
  }
4615
4551
  });
4552
+ if (prefix === "collector" && rest.length > 0) {
4553
+ if (name !== "next" || rest.length !== 1 || !rest[0]) {
4554
+ throw new Error(
4555
+ `Invalid step format: "${step}". Expected "collector.next.TRANSFORMER"`
4556
+ );
4557
+ }
4558
+ return {
4559
+ type: prefix,
4560
+ name,
4561
+ chainType: "next",
4562
+ transformerId: rest[0]
4563
+ };
4564
+ }
4616
4565
  if (rest.length >= 2) {
4617
4566
  const chainType = rest[0];
4618
4567
  if (chainType !== "before" && chainType !== "next") {
@@ -5186,6 +5135,7 @@ async function runPushCommand(options) {
5186
5135
  {
5187
5136
  collectorName: plan.ids[0],
5188
5137
  flow: options.flow,
5138
+ mock: options.mock,
5189
5139
  silent: options.silent,
5190
5140
  verbose: options.verbose,
5191
5141
  snapshot: options.snapshot
@@ -5268,37 +5218,6 @@ function getDevEnv(devModule) {
5268
5218
  function isStringArray(value) {
5269
5219
  return Array.isArray(value) && value.every(isString);
5270
5220
  }
5271
- function walkStaticChain(startId, transformers) {
5272
- const chain = [];
5273
- const visited = /* @__PURE__ */ new Set();
5274
- let current = startId;
5275
- while (current && transformers[current]) {
5276
- if (visited.has(current)) break;
5277
- visited.add(current);
5278
- chain.push(current);
5279
- const next = transformers[current].config?.next;
5280
- if (typeof next === "string") {
5281
- current = next;
5282
- continue;
5283
- }
5284
- if (Array.isArray(next) && next.every(isString)) {
5285
- chain.push(...next);
5286
- break;
5287
- }
5288
- break;
5289
- }
5290
- return chain;
5291
- }
5292
- function resolveBeforeChain(before, transformers, ingest, event) {
5293
- if (!before) return [];
5294
- if (Array.isArray(before) && before.every(isString)) {
5295
- return before;
5296
- }
5297
- const ids = getNextSteps(before, buildCacheContext(ingest, event));
5298
- if (ids.length === 0) return [];
5299
- if (ids.length === 1) return walkStaticChain(ids[0], transformers);
5300
- return ids;
5301
- }
5302
5221
  async function pushCore(inputPath, event, options = {}) {
5303
5222
  const logger = createCLILogger({
5304
5223
  silent: options.silent,
@@ -5649,6 +5568,18 @@ async function simulateSource(configOrPath, input, options) {
5649
5568
  await prepared.cleanup();
5650
5569
  }
5651
5570
  }
5571
+ async function runTransformerSimulation(collector, transformerId, event, ingest) {
5572
+ const result = await runTransformerChain(
5573
+ collector,
5574
+ collector.transformers,
5575
+ transformerId,
5576
+ event,
5577
+ ingest,
5578
+ void 0,
5579
+ `transformer.${transformerId}`
5580
+ );
5581
+ return result.copies.map((copy) => copy.event);
5582
+ }
5652
5583
  async function simulateTransformer(configOrPath, event, options) {
5653
5584
  const startTime = Date.now();
5654
5585
  const parsed = schemas3.PartialEventSchema.safeParse(event);
@@ -5740,58 +5671,17 @@ async function simulateTransformer(configOrPath, event, options) {
5740
5671
  };
5741
5672
  const captured = [];
5742
5673
  logger.info(`Simulating transformer: ${options.transformerId}`);
5743
- let processedEvent = inputEvent;
5744
- const before = transformer.config.before;
5745
- if (before && collector.transformers) {
5746
- const beforeChainIds = resolveBeforeChain(
5747
- before,
5748
- collector.transformers,
5749
- ingest,
5750
- processedEvent
5751
- );
5752
- if (beforeChainIds.length > 0) {
5753
- const beforeResult = await runTransformerChain(
5754
- collector,
5755
- collector.transformers,
5756
- beforeChainIds,
5757
- processedEvent,
5758
- ingest,
5759
- void 0,
5760
- `transformer.${options.transformerId}.before`
5761
- );
5762
- if (beforeResult === null) {
5763
- captured.push({ event: null, timestamp: Date.now() });
5764
- await collector.command("shutdown");
5765
- return buildSimulationResult({
5766
- step: "transformer",
5767
- name: options.transformerId,
5768
- startTime,
5769
- captured
5770
- });
5771
- }
5772
- processedEvent = Array.isArray(beforeResult) ? beforeResult[0] : beforeResult;
5773
- }
5774
- }
5775
- const pushResult = await transformerPush(
5674
+ const outputs = await runTransformerSimulation(
5776
5675
  collector,
5777
- transformer,
5778
5676
  options.transformerId,
5779
- processedEvent,
5677
+ inputEvent,
5780
5678
  ingest
5781
5679
  );
5782
- if (pushResult === false) {
5680
+ if (outputs.length === 0) {
5783
5681
  captured.push({ event: null, timestamp: Date.now() });
5784
- } else if (Array.isArray(pushResult)) {
5785
- for (const r of pushResult) {
5786
- captured.push({
5787
- event: r.event || processedEvent,
5788
- timestamp: Date.now()
5789
- });
5790
- }
5791
- } else if (pushResult && typeof pushResult === "object" && pushResult.event) {
5792
- captured.push({ event: pushResult.event, timestamp: Date.now() });
5793
- } else {
5794
- captured.push({ event: processedEvent, timestamp: Date.now() });
5682
+ }
5683
+ for (const output of outputs) {
5684
+ captured.push({ event: output, timestamp: Date.now() });
5795
5685
  }
5796
5686
  await collector.command("shutdown");
5797
5687
  return buildSimulationResult({
@@ -5819,6 +5709,13 @@ async function simulateTransformer(configOrPath, event, options) {
5819
5709
  await prepared.cleanup();
5820
5710
  }
5821
5711
  }
5712
+ async function runCollectorSimulation(collector, event) {
5713
+ const enriched = enrichEvent(collector, event);
5714
+ const result = await runCollectorNext(collector, enriched, {
5715
+ ingest: createIngest("collector")
5716
+ });
5717
+ return result.copies.map((copy) => copy.event);
5718
+ }
5822
5719
  async function simulateCollector(configOrPath, event, options) {
5823
5720
  const startTime = Date.now();
5824
5721
  const parsed = schemas3.PartialEventSchema.safeParse(event);
@@ -5842,6 +5739,7 @@ async function simulateCollector(configOrPath, event, options) {
5842
5739
  config,
5843
5740
  flow: options.flow,
5844
5741
  simulate: ["collector." + options.collectorName],
5742
+ mock: options.mock,
5845
5743
  silent: options.silent,
5846
5744
  verbose: options.verbose
5847
5745
  } : {
@@ -5849,6 +5747,7 @@ async function simulateCollector(configOrPath, event, options) {
5849
5747
  config,
5850
5748
  flow: options.flow,
5851
5749
  simulate: ["collector." + options.collectorName],
5750
+ mock: options.mock,
5852
5751
  silent: options.silent,
5853
5752
  verbose: options.verbose
5854
5753
  };
@@ -5895,8 +5794,11 @@ async function simulateCollector(configOrPath, event, options) {
5895
5794
  if (options.state.timing !== void 0)
5896
5795
  collector.timing = options.state.timing;
5897
5796
  }
5898
- const enriched = enrichEvent(collector, event);
5899
- const captured = [{ event: enriched, timestamp: Date.now() }];
5797
+ const outputs = await runCollectorSimulation(collector, event);
5798
+ const captured = outputs.length > 0 ? outputs.map((output) => ({
5799
+ event: output,
5800
+ timestamp: Date.now()
5801
+ })) : [{ event: null, timestamp: Date.now() }];
5900
5802
  await collector.command("shutdown");
5901
5803
  return buildSimulationResult({
5902
5804
  step: "collector",
@@ -8120,6 +8022,7 @@ function validateEvent2(input) {
8120
8022
  // src/commands/validate/validators/flow.ts
8121
8023
  import {
8122
8024
  getFlowSettings as getFlowSettings2,
8025
+ getRouteGraph,
8123
8026
  isObject as isObject5,
8124
8027
  resolveContracts,
8125
8028
  validateStepEntry as validateStepEntry2
@@ -8236,6 +8139,15 @@ function validateFlow(input, options = {}) {
8236
8139
  if (coreResult.context) {
8237
8140
  details.context = coreResult.context;
8238
8141
  }
8142
+ if (errors.length === 0 && isFlowJson(input)) {
8143
+ const typedFlows = input.flows;
8144
+ const flowsToLint = options.flow ? options.flow in typedFlows ? [options.flow] : [] : Object.keys(typedFlows);
8145
+ for (const name of flowsToLint) {
8146
+ const flowSettings = typedFlows[name];
8147
+ if (!flowSettings) continue;
8148
+ lintFlowRoutes(name, flowSettings, errors, warnings);
8149
+ }
8150
+ }
8239
8151
  if (errors.length === 0 && isFlowJson(input)) {
8240
8152
  const typedFlows = input.flows;
8241
8153
  const flowNames = Object.keys(typedFlows);
@@ -8283,15 +8195,6 @@ function validateFlow(input, options = {}) {
8283
8195
  }
8284
8196
  }
8285
8197
  }
8286
- if (errors.length === 0 && isFlowJson(input)) {
8287
- const typedFlows = input.flows;
8288
- const flowsToLint = options.flow ? options.flow in typedFlows ? [options.flow] : [] : Object.keys(typedFlows);
8289
- for (const name of flowsToLint) {
8290
- const flowSettings = typedFlows[name];
8291
- if (!flowSettings) continue;
8292
- lintFlowRoutes(name, flowSettings, warnings);
8293
- }
8294
- }
8295
8198
  if (errors.length === 0 && isFlowJson(input)) {
8296
8199
  const flowsMap = input.flows;
8297
8200
  const flowsToResolve = options.flow ? options.flow in flowsMap ? [options.flow] : [] : Object.keys(flowsMap);
@@ -8325,48 +8228,19 @@ function validateFlow(input, options = {}) {
8325
8228
  details
8326
8229
  };
8327
8230
  }
8328
- function isRouteConfig(spec) {
8329
- return typeof spec === "object" && spec !== null && !Array.isArray(spec);
8330
- }
8331
- function isRouteNext(spec) {
8332
- if (!("next" in spec)) return false;
8333
- const value = spec.next;
8334
- return value !== void 0;
8335
- }
8336
- function isRouteOne(spec) {
8337
- if (!("one" in spec)) return false;
8338
- const value = spec.one;
8339
- return Array.isArray(value);
8340
- }
8341
- function isRouteMany(spec) {
8342
- if (!("many" in spec)) return false;
8343
- const value = spec.many;
8344
- return Array.isArray(value);
8345
- }
8346
- function flattenRouteTargets(spec) {
8347
- if (!spec) return [];
8348
- if (typeof spec === "string") return [spec];
8349
- if (isRouteConfig(spec)) {
8350
- if (isRouteNext(spec)) return flattenRouteTargets(spec.next);
8351
- if (isRouteOne(spec)) {
8352
- return Array.from(new Set(spec.one.flatMap(flattenRouteTargets)));
8353
- }
8354
- if (isRouteMany(spec)) {
8355
- return Array.from(new Set(spec.many.flatMap(flattenRouteTargets)));
8356
- }
8357
- return [];
8358
- }
8359
- if (spec.length === 0) return [];
8360
- if (typeof spec[0] === "string") {
8361
- return spec.filter((s) => typeof s === "string");
8231
+ function routeTargets(spec) {
8232
+ if (spec === void 0) return [];
8233
+ const targets = /* @__PURE__ */ new Set();
8234
+ for (const node of getRouteGraph(spec)) {
8235
+ for (const target of node.targets) targets.add(target);
8362
8236
  }
8363
- return Array.from(new Set(spec.flatMap(flattenRouteTargets)));
8237
+ return [...targets];
8364
8238
  }
8365
8239
  function buildConnectionGraph(config) {
8366
8240
  const connections = [];
8367
8241
  for (const [name, source] of Object.entries(config.sources || {})) {
8368
8242
  if (!source.next || !source.examples) continue;
8369
- const nextNames = flattenRouteTargets(source.next);
8243
+ const nextNames = routeTargets(source.next);
8370
8244
  for (const nextName of nextNames) {
8371
8245
  const transformer = config.transformers?.[nextName];
8372
8246
  if (transformer?.examples) {
@@ -8383,7 +8257,7 @@ function buildConnectionGraph(config) {
8383
8257
  }
8384
8258
  for (const [name, transformer] of Object.entries(config.transformers || {})) {
8385
8259
  if (!transformer.next || !transformer.examples) continue;
8386
- const nextNames = flattenRouteTargets(transformer.next);
8260
+ const nextNames = routeTargets(transformer.next);
8387
8261
  for (const nextName of nextNames) {
8388
8262
  const nextTransformer = config.transformers?.[nextName];
8389
8263
  if (nextTransformer?.examples) {
@@ -8404,7 +8278,7 @@ function buildConnectionGraph(config) {
8404
8278
  }
8405
8279
  for (const [name, dest] of Object.entries(config.destinations || {})) {
8406
8280
  if (!dest.before || !dest.examples) continue;
8407
- const beforeNames = flattenRouteTargets(dest.before);
8281
+ const beforeNames = routeTargets(dest.before);
8408
8282
  for (const beforeName of beforeNames) {
8409
8283
  const transformer = config.transformers?.[beforeName];
8410
8284
  if (transformer?.examples) {
@@ -8422,8 +8296,8 @@ function buildConnectionGraph(config) {
8422
8296
  return connections;
8423
8297
  }
8424
8298
  function checkCompatibility(conn, errors, warnings) {
8425
- const fromOuts = Object.entries(conn.from.examples).filter(([, ex]) => ex.out !== void 0 && ex.out.length > 0).map(([name, ex]) => ({ name, value: ex.out }));
8426
- const toIns = Object.entries(conn.to.examples).filter(([, ex]) => ex.in !== void 0).map(([name, ex]) => ({ name, value: ex.in }));
8299
+ const fromOuts = Object.entries(conn.from.examples).filter(([, ex]) => hasComparableOut(ex.out)).map(([name, ex]) => ({ name, value: ex.out }));
8300
+ const toIns = Object.entries(conn.to.examples).filter(([, ex]) => ex.in !== void 0 && !ex.command).map(([name, ex]) => ({ name, value: ex.in }));
8427
8301
  const path20 = `${conn.from.type}.${conn.from.name} \u2192 ${conn.to.type}.${conn.to.name}`;
8428
8302
  if (fromOuts.length === 0 || toIns.length === 0) {
8429
8303
  warnings.push({
@@ -8451,6 +8325,10 @@ function checkCompatibility(conn, errors, warnings) {
8451
8325
  });
8452
8326
  }
8453
8327
  }
8328
+ function hasComparableOut(out) {
8329
+ if (Array.isArray(out) || typeof out === "string") return out.length > 0;
8330
+ return isObject5(out) && Object.keys(out).length > 0;
8331
+ }
8454
8332
  function isStructurallyCompatible(a, b) {
8455
8333
  if (typeof a !== typeof b) return false;
8456
8334
  if (a === null || b === null) return a === b;
@@ -8463,87 +8341,98 @@ function isStructurallyCompatible(a, b) {
8463
8341
  }
8464
8342
  return true;
8465
8343
  }
8466
- function lintFlowRoutes(flowName, flow, warnings) {
8344
+ function lintFlowRoutes(flowName, flow, errors, warnings) {
8345
+ const known = new Set(Object.keys(flow.transformers || {}));
8346
+ const check = (spec, position) => {
8347
+ if (spec !== void 0) lintRoute(spec, position, known, errors, warnings);
8348
+ };
8349
+ const at = `flows.${flowName}`;
8467
8350
  for (const [name, source] of Object.entries(flow.sources || {})) {
8468
- lintRoute(source.next, `flows.${flowName}.sources.${name}.next`, warnings);
8469
- lintRoute(
8470
- source.before,
8471
- `flows.${flowName}.sources.${name}.before`,
8472
- warnings
8473
- );
8351
+ check(source.before, `${at}.sources.${name}.before`);
8352
+ check(source.next, `${at}.sources.${name}.next`);
8474
8353
  }
8475
8354
  for (const [name, transformer] of Object.entries(flow.transformers || {})) {
8476
- lintRoute(
8477
- transformer.next,
8478
- `flows.${flowName}.transformers.${name}.next`,
8479
- warnings
8480
- );
8481
- lintRoute(
8482
- transformer.before,
8483
- `flows.${flowName}.transformers.${name}.before`,
8484
- warnings
8485
- );
8355
+ check(transformer.before, `${at}.transformers.${name}.before`);
8356
+ check(transformer.next, `${at}.transformers.${name}.next`);
8486
8357
  }
8358
+ check(flow.collector?.next, `${at}.collector.next`);
8487
8359
  for (const [name, dest] of Object.entries(flow.destinations || {})) {
8488
- lintRoute(
8489
- dest.before,
8490
- `flows.${flowName}.destinations.${name}.before`,
8491
- warnings
8492
- );
8493
- lintRoute(
8494
- dest.next,
8495
- `flows.${flowName}.destinations.${name}.next`,
8496
- warnings
8497
- );
8360
+ check(dest.before, `${at}.destinations.${name}.before`);
8361
+ check(dest.next, `${at}.destinations.${name}.next`);
8498
8362
  }
8499
8363
  }
8500
- function lintRoute(spec, position, warnings) {
8501
- if (!spec) return;
8502
- if (typeof spec === "string") return;
8503
- if (Array.isArray(spec)) {
8504
- for (let i = 0; i < spec.length; i++) {
8505
- const entry = spec[i];
8506
- if (i < spec.length - 1 && typeof entry === "object" && entry !== null && !Array.isArray(entry) && isRouteMany(entry)) {
8364
+ function routeAt(spec, path20) {
8365
+ let current = spec;
8366
+ for (const key of path20) {
8367
+ if (typeof key === "number")
8368
+ current = Array.isArray(current) ? current[key] : void 0;
8369
+ else current = isObject5(current) ? current[key] : void 0;
8370
+ }
8371
+ return current;
8372
+ }
8373
+ function lintRoute(spec, position, known, errors, warnings) {
8374
+ const where = (path20) => path20.length > 0 ? `${position}.${path20.join(".")}` : position;
8375
+ const reported = /* @__PURE__ */ new Set();
8376
+ const once = (key) => {
8377
+ if (reported.has(key)) return false;
8378
+ reported.add(key);
8379
+ return true;
8380
+ };
8381
+ for (const node of getRouteGraph(spec)) {
8382
+ for (const target of node.targets) {
8383
+ if (known.has(target) || !once(`unknown:${target}`)) continue;
8384
+ errors.push({
8385
+ path: position,
8386
+ message: `Unknown transformer "${target}" in route at ${position}`,
8387
+ code: "UNKNOWN_ROUTE_TARGET"
8388
+ });
8389
+ }
8390
+ const last = node.path[node.path.length - 1];
8391
+ if (node.stop && !node.match && node.kind !== "many") {
8392
+ const self = routeAt(spec, node.path);
8393
+ const list = routeAt(spec, node.path.slice(0, -1));
8394
+ if (isObject5(self) && self.stop === true && typeof last === "number" && Array.isArray(list) && last < list.length - 1 && once(`stop:${where(node.path)}`)) {
8395
+ const at = where(node.path);
8507
8396
  warnings.push({
8508
- path: position,
8509
- message: `dead code after many at ${position}: main chain terminates at the many operator`,
8510
- suggestion: "Remove entries after the many operator; move them into each many branch if they should still run."
8397
+ path: at,
8398
+ message: `dead code after stop at ${at}: entries after an unconditional stop never run`,
8399
+ suggestion: "Remove the entries after the stop, or give the stop a match."
8511
8400
  });
8512
8401
  }
8513
- lintRoute(entry, `${position}[${i}]`, warnings);
8514
- }
8515
- return;
8516
- }
8517
- if (isRouteNext(spec)) {
8518
- lintRoute(spec.next, `${position}.next`, warnings);
8519
- return;
8520
- }
8521
- if (isRouteOne(spec)) {
8522
- for (let i = 0; i < spec.one.length; i++) {
8523
- lintRoute(spec.one[i], `${position}.one[${i}]`, warnings);
8524
8402
  }
8525
- return;
8526
- }
8527
- if (isRouteMany(spec)) {
8528
- if (spec.many.length === 0) {
8529
- warnings.push({
8530
- path: position,
8531
- message: `empty many at ${position}: main chain terminates with no branches; use next or remove`,
8532
- suggestion: "Add one or more branch targets to many, or replace many with next if no fan-out is needed."
8533
- });
8534
- } else if (spec.many.length === 1) {
8535
- const only = spec.many[0];
8536
- const hint = typeof only === "string" ? `use 'next: "${only}"' for clarity` : `use 'next' for clarity`;
8403
+ if (node.kind === "many" && last === "many" && node.targets.length === 0 && once(`empty:${where(node.path)}`)) {
8404
+ const at = where(node.path.slice(0, -1));
8537
8405
  warnings.push({
8538
- path: position,
8539
- message: `single-entry many at ${position}: ${hint}`,
8540
- suggestion: "Replace many with next when only one branch exists."
8406
+ path: at,
8407
+ message: `empty many at ${at}: selects no branch, so it has no effect`,
8408
+ suggestion: "Add two or more branch targets to many, or remove it."
8541
8409
  });
8542
8410
  }
8543
- for (let i = 0; i < spec.many.length; i++) {
8544
- lintRoute(spec.many[i], `${position}.many[${i}]`, warnings);
8411
+ for (const decision of [...node.via ?? [], node]) {
8412
+ const index = decision.path[decision.path.length - 1];
8413
+ if (typeof index !== "number") continue;
8414
+ const listPath = decision.path.slice(0, -1);
8415
+ const list = routeAt(spec, listPath);
8416
+ if (!Array.isArray(list)) continue;
8417
+ const at = where(listPath);
8418
+ if (decision.kind === "many" && list.length === 1 && once(`single:${at}`)) {
8419
+ const only = list[0];
8420
+ const hint = typeof only === "string" ? `use 'next: "${only}"' for clarity` : `use 'next' for clarity`;
8421
+ warnings.push({
8422
+ path: at,
8423
+ message: `single-entry many at ${at}: ${hint}`,
8424
+ suggestion: "Replace many with next when only one branch exists."
8425
+ });
8426
+ }
8427
+ const explicitOne = listPath[listPath.length - 1] === "one";
8428
+ if (decision.kind === "one" && !explicitOne && list.length > 1 && once(`first-match:${at}`)) {
8429
+ warnings.push({
8430
+ path: at,
8431
+ message: `first-match array at ${at}: an array made only of route configs is an implicit one, the first matching entry wins`,
8432
+ suggestion: 'Write { "one": [...] } explicitly, or add the step ids as a sequence to run every entry in order.'
8433
+ });
8434
+ }
8545
8435
  }
8546
- return;
8547
8436
  }
8548
8437
  }
8549
8438
  function checkContractCompliance(config, contract, errors, warnings, strict) {
@@ -8641,7 +8530,7 @@ function validateMapping(input) {
8641
8530
  // src/commands/validate/validators/entry.ts
8642
8531
  import Ajv from "ajv";
8643
8532
  import { fetchPackageSchema } from "@walkeros/core";
8644
- var CLIENT_HEADER = "walkeros-cli/4.6.1";
8533
+ var CLIENT_HEADER = "walkeros-cli/4.7.0-next-1790187973605";
8645
8534
  var SECTIONS = ["destinations", "sources", "transformers"];
8646
8535
  function resolveEntry(path20, flowConfig) {
8647
8536
  const flows = flowConfig.flows;
@@ -8748,7 +8637,7 @@ async function validateEntry(path20, flowConfig) {
8748
8637
  }
8749
8638
  const config = entry.config;
8750
8639
  const settings = config?.settings;
8751
- const ajv = new Ajv({ allErrors: true });
8640
+ const ajv = new Ajv({ allErrors: true, validateFormats: false });
8752
8641
  const validate2 = ajv.compile(settingsSchema);
8753
8642
  const isValid = validate2(settings || {});
8754
8643
  if (!isValid) {
@@ -8778,10 +8667,8 @@ async function validateEntry(path20, flowConfig) {
8778
8667
  async function validate(type, input, options = {}) {
8779
8668
  let resolved = input;
8780
8669
  if (typeof input === "string") {
8781
- resolved = await loadJsonFromSource(input, {
8782
- name: type,
8783
- required: true
8784
- });
8670
+ if (input.trim() === "") throw new Error(`${type} is required`);
8671
+ resolved = await loadJsonConfig(input);
8785
8672
  }
8786
8673
  if (options.path) {
8787
8674
  return validateEntry(options.path, resolved);
@@ -10897,108 +10784,6 @@ init_config_file();
10897
10784
  init_sse();
10898
10785
  init_utils();
10899
10786
 
10900
- // src/commands/simulate/example-loader.ts
10901
- function findExample(config, exampleName, stepTarget) {
10902
- if (stepTarget) {
10903
- return findExampleInStep(config, exampleName, stepTarget);
10904
- }
10905
- return findExampleAcrossSteps(config, exampleName);
10906
- }
10907
- function findExampleInStep(config, exampleName, stepTarget) {
10908
- const dotIndex = stepTarget.indexOf(".");
10909
- if (dotIndex === -1) {
10910
- throw new Error(
10911
- `Invalid --step format: "${stepTarget}". Expected "type.name" (e.g. "destination.gtag")`
10912
- );
10913
- }
10914
- const type = stepTarget.substring(0, dotIndex);
10915
- const name = stepTarget.substring(dotIndex + 1);
10916
- const stepMap = getStepMap(config, type);
10917
- if (!stepMap) {
10918
- throw new Error(`No ${type}s found in flow config`);
10919
- }
10920
- const step = stepMap[name];
10921
- if (!step) {
10922
- const available = Object.keys(stepMap).join(", ");
10923
- throw new Error(`${type} "${name}" not found. Available: ${available}`);
10924
- }
10925
- const examples = step.examples;
10926
- if (!examples || !examples[exampleName]) {
10927
- const available = examples ? Object.keys(examples).join(", ") : "none";
10928
- throw new Error(
10929
- `Example "${exampleName}" not found in ${type} "${name}". Available: ${available}`
10930
- );
10931
- }
10932
- return {
10933
- stepType: type,
10934
- stepName: name,
10935
- exampleName,
10936
- example: examples[exampleName]
10937
- };
10938
- }
10939
- function findExampleAcrossSteps(config, exampleName) {
10940
- const matches = [];
10941
- const stepTypes = ["source", "transformer", "destination"];
10942
- for (const type of stepTypes) {
10943
- const stepMap = getStepMap(config, type);
10944
- if (!stepMap) continue;
10945
- for (const [name, step] of Object.entries(stepMap)) {
10946
- const examples = step.examples;
10947
- if (examples && examples[exampleName]) {
10948
- matches.push({
10949
- stepType: type,
10950
- stepName: name,
10951
- exampleName,
10952
- example: examples[exampleName]
10953
- });
10954
- }
10955
- }
10956
- }
10957
- if (matches.length === 0) {
10958
- throw new Error(`Example "${exampleName}" not found in any step`);
10959
- }
10960
- if (matches.length > 1) {
10961
- const locations = matches.map((m) => `${m.stepType}.${m.stepName}`).join(", ");
10962
- throw new Error(
10963
- `Example "${exampleName}" found in multiple steps: ${locations}. Use --step to disambiguate.`
10964
- );
10965
- }
10966
- return matches[0];
10967
- }
10968
- function getStepMap(config, type) {
10969
- switch (type) {
10970
- case "source":
10971
- return config.sources;
10972
- case "transformer":
10973
- return config.transformers;
10974
- case "destination":
10975
- return config.destinations;
10976
- default:
10977
- throw new Error(
10978
- `Invalid step type: "${type}". Must be "source", "transformer", or "destination"`
10979
- );
10980
- }
10981
- }
10982
-
10983
- // src/commands/simulate/compare.ts
10984
- function compareOutput(expected, actual) {
10985
- const expectedStr = JSON.stringify(expected, null, 2);
10986
- const actualStr = JSON.stringify(actual, null, 2);
10987
- if (expectedStr === actualStr) {
10988
- return { expected, actual, match: true };
10989
- }
10990
- return {
10991
- expected,
10992
- actual,
10993
- match: false,
10994
- diff: `Expected:
10995
- ${expectedStr}
10996
-
10997
- Actual:
10998
- ${actualStr}`
10999
- };
11000
- }
11001
-
11002
10787
  // src/telemetry/index.ts
11003
10788
  var telemetry_exports = {};
11004
10789
  __export(telemetry_exports, {
@@ -11217,7 +11002,6 @@ export {
11217
11002
  clearAuthFields,
11218
11003
  clientContextHeaders,
11219
11004
  compareContract,
11220
- compareOutput,
11221
11005
  completeDeviceLogin,
11222
11006
  containsCodeMarkers,
11223
11007
  createApiClient,
@@ -11251,7 +11035,6 @@ export {
11251
11035
  feedback,
11252
11036
  feedbackCommand,
11253
11037
  fetchHealth,
11254
- findExample,
11255
11038
  getAuthHeaders,
11256
11039
  getClientContext,
11257
11040
  getDefaultProject,