@walkeros/cli 4.0.0-next-1777882869103 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @walkeros/cli
2
2
 
3
- ## 4.0.0-next-1777882869103
3
+ ## 4.0.0
4
4
 
5
5
  ### Major Changes
6
6
 
@@ -232,6 +232,31 @@
232
232
 
233
233
  ### Patch Changes
234
234
 
235
+ - ca237ef: Fix `walkeros push` deadlock for web flows whose destinations await
236
+ real timers during init.
237
+
238
+ Previously, async-drain timer interception captured every `setTimeout` into a
239
+ pending map and only fired them via a post-`fn` flush. If a destination's init
240
+ awaited one of those captured timers (e.g.,
241
+ `@walkeros/web-destination-amplitude`'s engagement plugin awaits a 10s
242
+ setTimeout to give up on a CDN script load), `init` never resolved,
243
+ `await collector.push` deadlocked, and Node exited with
244
+ `Detected unsettled top-level await` (exit 13).
245
+
246
+ A drain pump now runs alongside `fn(flowModule)` for non-`--simulate` runs:
247
+ each tick fires every captured non-cleared timer using a real `setImmediate`
248
+ reference. Timers fire in delay-ascending order, intervals re-register,
249
+ callback errors are reported via `console.warn`. Bounded by max-iterations
250
+ (1000) and wall-clock (30s) caps.
251
+
252
+ `--simulate <step>` continues to use the post-`fn` flush path so snapshot
253
+ ordering remains stable.
254
+
255
+ Behavior change (edge case): a destination using `setTimeout` for retry
256
+ backoff under `walkeros push` (real, non-simulate) now sees its timer fire
257
+ instantly. This was already the documented contract for `--simulate <step>`
258
+ snapshots; it now extends to real `push` for consistency.
259
+
235
260
  - 6422b9b: Validate device-code and token responses from the auth server with
236
261
  Zod schemas at the trust boundary in `login/index.ts`. Malformed responses now
237
262
  surface as structured errors instead of being trusted into config writes or
@@ -271,10 +296,10 @@
271
296
  - Updated dependencies [8e06b1f]
272
297
  - Updated dependencies [3d50dd6]
273
298
  - Updated dependencies [1ef33d9]
274
- - @walkeros/core@4.0.0-next-1777882869103
275
- - @walkeros/collector@4.0.0-next-1777882869103
276
- - @walkeros/server-destination-api@4.0.0-next-1777882869103
277
- - @walkeros/server-core@4.0.0-next-1777882869103
299
+ - @walkeros/core@4.0.0
300
+ - @walkeros/collector@4.0.0
301
+ - @walkeros/server-destination-api@4.0.0
302
+ - @walkeros/server-core@4.0.0
278
303
 
279
304
  ## 3.4.2
280
305
 
package/dist/cli.js CHANGED
@@ -4050,22 +4050,22 @@ import {
4050
4050
  mkdirSync as mkdirSync3,
4051
4051
  copyFileSync,
4052
4052
  writeFileSync as writeFileSync3,
4053
- readFileSync as readFileSync4
4053
+ readFileSync as readFileSync3
4054
4054
  } from "fs";
4055
- import { join as join4 } from "path";
4055
+ import { join as join3 } from "path";
4056
4056
  function writeCache(cacheDir, bundlePath, configContent, version) {
4057
4057
  mkdirSync3(cacheDir, { recursive: true });
4058
- copyFileSync(bundlePath, join4(cacheDir, "bundle.mjs"));
4059
- writeFileSync3(join4(cacheDir, "config.json"), configContent, "utf-8");
4058
+ copyFileSync(bundlePath, join3(cacheDir, "bundle.mjs"));
4059
+ writeFileSync3(join3(cacheDir, "config.json"), configContent, "utf-8");
4060
4060
  const meta = { version, timestamp: Date.now() };
4061
- writeFileSync3(join4(cacheDir, "meta.json"), JSON.stringify(meta), "utf-8");
4061
+ writeFileSync3(join3(cacheDir, "meta.json"), JSON.stringify(meta), "utf-8");
4062
4062
  }
4063
4063
  function readCache(cacheDir) {
4064
4064
  try {
4065
- const metaPath = join4(cacheDir, "meta.json");
4066
- const bundlePath = join4(cacheDir, "bundle.mjs");
4065
+ const metaPath = join3(cacheDir, "meta.json");
4066
+ const bundlePath = join3(cacheDir, "bundle.mjs");
4067
4067
  if (!existsSync3(metaPath) || !existsSync3(bundlePath)) return null;
4068
- const meta = JSON.parse(readFileSync4(metaPath, "utf-8"));
4068
+ const meta = JSON.parse(readFileSync3(metaPath, "utf-8"));
4069
4069
  return { bundlePath, version: meta.version };
4070
4070
  } catch {
4071
4071
  return null;
@@ -4073,9 +4073,9 @@ function readCache(cacheDir) {
4073
4073
  }
4074
4074
  function readCacheConfig(cacheDir) {
4075
4075
  try {
4076
- const configPath = join4(cacheDir, "config.json");
4076
+ const configPath = join3(cacheDir, "config.json");
4077
4077
  if (!existsSync3(configPath)) return null;
4078
- return readFileSync4(configPath, "utf-8");
4078
+ return readFileSync3(configPath, "utf-8");
4079
4079
  } catch {
4080
4080
  return null;
4081
4081
  }
@@ -4577,11 +4577,6 @@ function printBanner(version) {
4577
4577
  console.error("");
4578
4578
  }
4579
4579
 
4580
- // src/telemetry/emitter.ts
4581
- import { readFileSync as readFileSync3 } from "fs";
4582
- import { join as join3, dirname as dirname2 } from "path";
4583
- import { fileURLToPath as fileURLToPath2 } from "url";
4584
-
4585
4580
  // ../collector/dist/index.mjs
4586
4581
  init_dist();
4587
4582
  init_dist();
@@ -5214,7 +5209,7 @@ function Re2(e3, n4) {
5214
5209
  }
5215
5210
  async function Ge(e3) {
5216
5211
  const n4 = G({ globalsStatic: {}, sessionStatic: {}, run: true }, e3, { merge: false, extend: false }), t3 = { level: e3.logger?.level, handler: e3.logger?.handler }, o2 = _e(t3), s4 = { ...n4.globalsStatic, ...e3.globals }, i2 = { allowed: false, config: n4, consent: e3.consent || {}, custom: e3.custom || {}, destinations: {}, transformers: {}, stores: {}, globals: s4, hooks: e3.hooks || {}, logger: o2, on: {}, queue: [], round: 0, session: void 0, status: { startedAt: Date.now(), in: 0, out: 0, failed: 0, sources: {}, destinations: {} }, timing: Date.now(), user: e3.user || {}, sources: {}, pending: { sources: {}, destinations: {} }, push: void 0, command: void 0 };
5217
- i2.push = Re2(i2, (e4) => ({ timing: Math.round((Date.now() - i2.timing) / 10) / 100, source: { type: "collector", schema: "4", version: "4.0.0-next-1777882869103" }, ...e4 })), i2.command = (function(e4, n5) {
5212
+ i2.push = Re2(i2, (e4) => ({ timing: Math.round((Date.now() - i2.timing) / 10) / 100, source: { type: "collector", schema: "4", version: "4.0.0" }, ...e4 })), i2.command = (function(e4, n5) {
5218
5213
  return Je(async (t4, o3, s5) => await Te(async () => await n5(e4, t4, o3, s5), () => ye({ ok: false }))(), "Command", e4.hooks, e4.logger);
5219
5214
  })(i2, qe);
5220
5215
  const c3 = e3.stores || {};
@@ -5414,6 +5409,7 @@ function maybePrintFirstRunNotice() {
5414
5409
  }
5415
5410
 
5416
5411
  // src/telemetry/emitter.ts
5412
+ init_config_file();
5417
5413
  var SEND_TIMEOUT_MS = 1e3;
5418
5414
  async function createEmitter(opts) {
5419
5415
  if (!isTelemetryEnabled()) {
@@ -5431,7 +5427,7 @@ async function createEmitter(opts) {
5431
5427
  }
5432
5428
  const device = maybeDevice;
5433
5429
  const debug = isDebugMode();
5434
- const endpoint = process.env.TELEMETRY_ENDPOINT || loadFlowJsonEndpoint();
5430
+ const endpoint = resolveTelemetryEndpoint();
5435
5431
  if (!endpoint && !debug) {
5436
5432
  return {
5437
5433
  async send() {
@@ -5506,15 +5502,10 @@ async function createEmitter(opts) {
5506
5502
  }
5507
5503
  };
5508
5504
  }
5509
- function loadFlowJsonEndpoint() {
5510
- try {
5511
- const here = dirname2(fileURLToPath2(import.meta.url));
5512
- const raw = JSON.parse(readFileSync3(join3(here, "flow.json"), "utf-8"));
5513
- const url = raw.flows?.default?.destinations?.api?.config?.url;
5514
- if (url && !url.startsWith("$")) return url;
5515
- } catch {
5516
- }
5517
- return void 0;
5505
+ function resolveTelemetryEndpoint() {
5506
+ const appUrl = resolveAppUrl();
5507
+ if (!appUrl) return void 0;
5508
+ return `${appUrl.replace(/\/$/, "")}/api/telemetry`;
5518
5509
  }
5519
5510
  async function withTimeout(p2, ms) {
5520
5511
  return Promise.race([
@@ -5822,7 +5813,51 @@ function installTimerInterception(options = {}) {
5822
5813
  }
5823
5814
  pending.clear();
5824
5815
  }
5825
- return { flush, countPending, restore };
5816
+ return { flush, countPending, restore, pending };
5817
+ }
5818
+
5819
+ // src/commands/push/async-drain-pump.ts
5820
+ var realSetImmediate = setImmediate;
5821
+ var DEFAULT_MAX_ITERATIONS = 1e3;
5822
+ var DEFAULT_MAX_WALL_MS = 3e4;
5823
+ var intervalRequeueCounter = -1;
5824
+ function startDrainPump(pending, options = {}) {
5825
+ const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
5826
+ const maxWallMs = options.maxWallMs ?? DEFAULT_MAX_WALL_MS;
5827
+ const start = Date.now();
5828
+ let running = true;
5829
+ let iterations = 0;
5830
+ const tick = () => {
5831
+ if (!running) return;
5832
+ if (iterations >= maxIterations) return;
5833
+ if (Date.now() - start > maxWallMs) return;
5834
+ if (pending.size === 0) {
5835
+ realSetImmediate(tick);
5836
+ return;
5837
+ }
5838
+ iterations += 1;
5839
+ const snapshot = [...pending.values()].filter((t3) => !t3.cleared).sort((a4, b2) => a4.delay - b2.delay);
5840
+ for (const timer of snapshot) {
5841
+ pending.delete(timer.id);
5842
+ try {
5843
+ timer.callback(...timer.args);
5844
+ } catch (err) {
5845
+ console.warn(`[async-drain] timer ${timer.id} threw during pump:`, err);
5846
+ }
5847
+ if (timer.type === "interval" && !timer.cleared) {
5848
+ const requeued = {
5849
+ ...timer,
5850
+ id: intervalRequeueCounter--
5851
+ };
5852
+ pending.set(requeued.id, requeued);
5853
+ }
5854
+ }
5855
+ realSetImmediate(tick);
5856
+ };
5857
+ realSetImmediate(tick);
5858
+ return () => {
5859
+ running = false;
5860
+ };
5826
5861
  }
5827
5862
 
5828
5863
  // src/commands/push/flow-context.ts
@@ -5834,7 +5869,8 @@ async function withFlowContext(options, fn) {
5834
5869
  snapshotCode,
5835
5870
  timeout,
5836
5871
  networkCalls,
5837
- asyncDrain
5872
+ asyncDrain,
5873
+ drainPump
5838
5874
  } = options;
5839
5875
  const startTime = Date.now();
5840
5876
  const g2 = global;
@@ -5897,7 +5933,13 @@ async function withFlowContext(options, fn) {
5897
5933
  __devExports: module.__devExports
5898
5934
  };
5899
5935
  if (timerControl) {
5900
- const result = await fn(flowModule);
5936
+ const stopPump = drainPump ? startDrainPump(timerControl.pending) : null;
5937
+ let result;
5938
+ try {
5939
+ result = await fn(flowModule);
5940
+ } finally {
5941
+ if (stopPump) stopPump();
5942
+ }
5901
5943
  await timerControl.flush(asyncDrain?.timeout ?? 5e3);
5902
5944
  return result;
5903
5945
  } else if (timeout) {
@@ -6041,6 +6083,145 @@ async function prepareFlow(input) {
6041
6083
 
6042
6084
  // src/commands/push/index.ts
6043
6085
  init_dev();
6086
+
6087
+ // src/commands/push/plan-simulate.ts
6088
+ function planSimulate(flags) {
6089
+ if (flags.length === 0) return { kind: "none", ids: [] };
6090
+ const parsed = flags.map((flag) => {
6091
+ const step = parseStep(flag);
6092
+ if (step.chainType) {
6093
+ throw new Error(
6094
+ `--simulate "${flag}": chain syntax (${step.type}.${step.name}.${step.chainType}.\u2026) is not supported for --simulate. Use --mock for path-specific overrides.`
6095
+ );
6096
+ }
6097
+ return step;
6098
+ });
6099
+ const types = new Set(parsed.map((p2) => p2.type));
6100
+ if (types.size > 1) {
6101
+ const sorted = [...types].sort();
6102
+ throw new Error(
6103
+ `Cannot --simulate ${sorted.join(" and ")} in the same invocation. Run separate commands for each step type.`
6104
+ );
6105
+ }
6106
+ const [type] = types;
6107
+ const ids = [...new Set(parsed.map((p2) => p2.name))];
6108
+ if ((type === "source" || type === "transformer") && ids.length > 1) {
6109
+ throw new Error(
6110
+ `--simulate ${type}.* expects a single target; got ${ids.length}. Run one --simulate ${type}.NAME per invocation.`
6111
+ );
6112
+ }
6113
+ return { kind: type, ids };
6114
+ }
6115
+
6116
+ // src/commands/push/dispatch-simulate.ts
6117
+ function dispatchSimulate(flags) {
6118
+ const plan = planSimulate(flags);
6119
+ return { route: plan.kind, ids: plan.ids };
6120
+ }
6121
+
6122
+ // src/commands/push/run.ts
6123
+ init_core();
6124
+ init_config();
6125
+ async function runPushCommand(options) {
6126
+ const startTime = Date.now();
6127
+ try {
6128
+ const plan = dispatchSimulate(options.simulate ?? []);
6129
+ let config;
6130
+ if (isStdinPiped() && !options.config) {
6131
+ config = await readStdinToTempFile("push");
6132
+ } else {
6133
+ config = options.config || "bundle.config.json";
6134
+ }
6135
+ let resolvedEvent = options.event;
6136
+ if (typeof options.event === "string") {
6137
+ resolvedEvent = await loadJsonFromSource(options.event, {
6138
+ name: "event"
6139
+ });
6140
+ }
6141
+ let result;
6142
+ switch (plan.route) {
6143
+ case "none":
6144
+ result = await push(config, resolvedEvent, {
6145
+ flow: options.flow,
6146
+ json: options.json,
6147
+ verbose: options.verbose,
6148
+ silent: options.silent,
6149
+ platform: options.platform,
6150
+ mock: options.mock,
6151
+ snapshot: options.snapshot
6152
+ });
6153
+ break;
6154
+ case "source":
6155
+ result = await simulateSource(config, resolvedEvent, {
6156
+ sourceId: plan.ids[0],
6157
+ flow: options.flow,
6158
+ silent: options.silent,
6159
+ verbose: options.verbose,
6160
+ snapshot: options.snapshot
6161
+ });
6162
+ break;
6163
+ case "transformer":
6164
+ result = await simulateTransformer(
6165
+ config,
6166
+ resolvedEvent,
6167
+ {
6168
+ transformerId: plan.ids[0],
6169
+ flow: options.flow,
6170
+ mock: options.mock,
6171
+ silent: options.silent,
6172
+ verbose: options.verbose,
6173
+ snapshot: options.snapshot
6174
+ }
6175
+ );
6176
+ break;
6177
+ case "destination":
6178
+ result = await runDestinationSimulationLoop(
6179
+ config,
6180
+ resolvedEvent,
6181
+ plan.ids,
6182
+ options
6183
+ );
6184
+ break;
6185
+ }
6186
+ return result;
6187
+ } catch (error) {
6188
+ return {
6189
+ success: false,
6190
+ duration: Date.now() - startTime,
6191
+ error: getErrorMessage(error)
6192
+ };
6193
+ }
6194
+ }
6195
+ async function runDestinationSimulationLoop(config, event, destinationIds, options) {
6196
+ const startTime = Date.now();
6197
+ const perDestination = {};
6198
+ for (const destinationId of destinationIds) {
6199
+ const r3 = await simulateDestination(config, event, {
6200
+ destinationId,
6201
+ flow: options.flow,
6202
+ mock: options.mock,
6203
+ silent: options.silent,
6204
+ verbose: options.verbose,
6205
+ snapshot: options.snapshot
6206
+ });
6207
+ perDestination[destinationId] = r3;
6208
+ if (!r3.success) {
6209
+ return {
6210
+ success: false,
6211
+ duration: Date.now() - startTime,
6212
+ error: `simulate destination.${destinationId}: ${r3.error ?? "unknown error"}`,
6213
+ perDestination
6214
+ };
6215
+ }
6216
+ }
6217
+ return {
6218
+ success: true,
6219
+ duration: Date.now() - startTime,
6220
+ perDestination
6221
+ };
6222
+ }
6223
+
6224
+ // src/commands/push/index.ts
6044
6225
  function resolveBeforeChain(before, transformers, ingest, event) {
6045
6226
  if (!before) return [];
6046
6227
  const next = before;
@@ -6119,108 +6300,32 @@ async function pushCore(inputPath, event, options = {}) {
6119
6300
  }
6120
6301
  }
6121
6302
  async function pushCommand(options) {
6122
- const logger = createCLILogger({ ...options, stderr: true });
6123
- const startTime = Date.now();
6124
- try {
6125
- let config;
6126
- if (isStdinPiped() && !options.config) {
6127
- config = await readStdinToTempFile("push");
6128
- } else {
6129
- config = options.config || "bundle.config.json";
6130
- }
6131
- let resolvedEvent = options.event;
6132
- if (typeof options.event === "string") {
6133
- resolvedEvent = await loadJsonFromSource(options.event, {
6134
- name: "event"
6135
- });
6136
- }
6137
- const simulateFlag = options.simulate?.[0];
6138
- let result;
6139
- if (simulateFlag?.startsWith("source.")) {
6140
- result = await simulateSource(config, resolvedEvent, {
6141
- sourceId: simulateFlag.replace("source.", ""),
6142
- flow: options.flow,
6143
- silent: options.silent,
6144
- verbose: options.verbose,
6145
- snapshot: options.snapshot
6146
- });
6147
- } else if (simulateFlag?.startsWith("transformer.")) {
6148
- result = await simulateTransformer(
6149
- config,
6150
- resolvedEvent,
6151
- {
6152
- transformerId: simulateFlag.replace("transformer.", ""),
6153
- flow: options.flow,
6154
- mock: options.mock,
6155
- silent: options.silent,
6156
- verbose: options.verbose,
6157
- snapshot: options.snapshot
6158
- }
6159
- );
6160
- } else if (simulateFlag?.startsWith("destination.")) {
6161
- result = await simulateDestination(
6162
- config,
6163
- resolvedEvent,
6164
- {
6165
- destinationId: simulateFlag.replace("destination.", ""),
6166
- flow: options.flow,
6167
- mock: options.mock,
6168
- silent: options.silent,
6169
- verbose: options.verbose,
6170
- snapshot: options.snapshot
6171
- }
6172
- );
6173
- } else {
6174
- result = await push(config, resolvedEvent, {
6175
- flow: options.flow,
6176
- json: options.json,
6177
- verbose: options.verbose,
6178
- silent: options.silent,
6179
- platform: options.platform,
6180
- mock: options.mock,
6181
- snapshot: options.snapshot
6182
- });
6183
- }
6184
- const duration = Date.now() - startTime;
6185
- let output;
6186
- if (options.json) {
6187
- output = JSON.stringify({ ...result, duration }, null, 2);
6188
- } else {
6189
- const lines = [];
6190
- if (result.success) {
6191
- lines.push("Event pushed successfully");
6192
- if (result.elbResult && typeof result.elbResult === "object") {
6193
- const pushResult = result.elbResult;
6194
- if ("id" in pushResult && pushResult.id)
6195
- lines.push(` Event ID: ${pushResult.id}`);
6196
- if ("entity" in pushResult && pushResult.entity)
6197
- lines.push(` Entity: ${pushResult.entity}`);
6198
- if ("action" in pushResult && pushResult.action)
6199
- lines.push(` Action: ${pushResult.action}`);
6200
- }
6201
- lines.push(` Duration: ${duration}ms`);
6202
- } else {
6203
- lines.push(`Error: ${result.error}`);
6204
- }
6205
- output = lines.join("\n");
6206
- }
6207
- await writeResult(output + "\n", { output: options.output });
6208
- process.exit(result.success ? 0 : 1);
6209
- } catch (error) {
6210
- const duration = Date.now() - startTime;
6211
- const errorMessage = getErrorMessage(error);
6212
- if (options.json) {
6213
- const errorOutput = JSON.stringify(
6214
- { success: false, error: errorMessage, duration },
6215
- null,
6216
- 2
6217
- );
6218
- await writeResult(errorOutput + "\n", { output: options.output });
6303
+ const result = await runPushCommand(options);
6304
+ const duration = result.duration;
6305
+ let output;
6306
+ if (options.json) {
6307
+ output = JSON.stringify({ ...result, duration }, null, 2);
6308
+ } else {
6309
+ const lines = [];
6310
+ if (result.success) {
6311
+ lines.push("Event pushed successfully");
6312
+ if (result.elbResult && typeof result.elbResult === "object") {
6313
+ const pushResult = result.elbResult;
6314
+ if ("id" in pushResult && pushResult.id)
6315
+ lines.push(` Event ID: ${pushResult.id}`);
6316
+ if ("entity" in pushResult && pushResult.entity)
6317
+ lines.push(` Entity: ${pushResult.entity}`);
6318
+ if ("action" in pushResult && pushResult.action)
6319
+ lines.push(` Action: ${pushResult.action}`);
6320
+ }
6321
+ lines.push(` Duration: ${duration}ms`);
6219
6322
  } else {
6220
- logger.error(`Error: ${errorMessage}`);
6323
+ lines.push(`Error: ${result.error}`);
6221
6324
  }
6222
- process.exit(1);
6325
+ output = lines.join("\n");
6223
6326
  }
6327
+ await writeResult(output + "\n", { output: options.output });
6328
+ process.exit(result.success ? 0 : 1);
6224
6329
  }
6225
6330
  async function push(configOrPath, event, options = {}) {
6226
6331
  if (typeof configOrPath !== "string") {
@@ -6319,7 +6424,12 @@ async function executeDestinationPush(esmPath, event, logger, platform, override
6319
6424
  snapshotCode,
6320
6425
  timeout,
6321
6426
  networkCalls,
6322
- asyncDrain: { timeout: 5e3 }
6427
+ asyncDrain: { timeout: 5e3 },
6428
+ // Real push (non-simulate) needs the pump so destinations whose init
6429
+ // awaits a captured setTimeout (e.g., amplitude engagement plugin)
6430
+ // don't deadlock. Simulate routes use their own withFlowContext call
6431
+ // sites without `drainPump`, preserving snapshot ordering.
6432
+ drainPump: true
6323
6433
  },
6324
6434
  async (module) => {
6325
6435
  const config = module.wireConfig(module.__configData ?? void 0);
@@ -6719,12 +6829,12 @@ init_auth();
6719
6829
  import path17 from "path";
6720
6830
  import { writeFileSync as writeFileSync5 } from "fs";
6721
6831
  import { homedir as homedir2 } from "os";
6722
- import { join as join5 } from "path";
6832
+ import { join as join4 } from "path";
6723
6833
 
6724
6834
  // src/runtime/resolve-bundle.ts
6725
6835
  init_stdin();
6726
6836
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
6727
- import { dirname as dirname3 } from "path";
6837
+ import { dirname as dirname2 } from "path";
6728
6838
  function getDefaultWritePath() {
6729
6839
  if (existsSync2("/app/flow")) return "/app/flow/bundle.mjs";
6730
6840
  return "/tmp/walkeros-bundle.mjs";
@@ -6733,7 +6843,7 @@ function isUrl2(value) {
6733
6843
  return value.startsWith("http://") || value.startsWith("https://");
6734
6844
  }
6735
6845
  function writeBundleToDisk(writePath, content) {
6736
- const dir = dirname3(writePath);
6846
+ const dir = dirname2(writePath);
6737
6847
  if (!existsSync2(dir)) {
6738
6848
  mkdirSync2(dir, { recursive: true });
6739
6849
  }
@@ -7009,7 +7119,7 @@ function createHealthServer(port, logger) {
7009
7119
  }
7010
7120
 
7011
7121
  // src/runtime/runner.ts
7012
- import { resolve as resolve2, dirname as dirname4 } from "path";
7122
+ import { resolve as resolve2, dirname as dirname3 } from "path";
7013
7123
 
7014
7124
  // src/runtime/load-bundle.ts
7015
7125
  import { resolve } from "path";
@@ -7040,7 +7150,7 @@ async function loadBundle(file, context2, logger) {
7040
7150
  // src/runtime/runner.ts
7041
7151
  async function loadFlow(file, config, logger, loggerConfig, healthServer) {
7042
7152
  const absolutePath = resolve2(file);
7043
- const flowDir = dirname4(absolutePath);
7153
+ const flowDir = dirname3(absolutePath);
7044
7154
  process.chdir(flowDir);
7045
7155
  const flowContext = {
7046
7156
  ...config,
@@ -7433,8 +7543,8 @@ async function injectSecrets(api, logger) {
7433
7543
  // src/commands/run/index.ts
7434
7544
  function defaultCacheDir() {
7435
7545
  const xdgCache = process.env.XDG_CACHE_HOME;
7436
- const base = xdgCache || join5(homedir2(), ".cache");
7437
- return join5(base, "walkeros");
7546
+ const base = xdgCache || join4(homedir2(), ".cache");
7547
+ return join4(base, "walkeros");
7438
7548
  }
7439
7549
  async function lazyPrepareBundleForRun(configPath, options) {
7440
7550
  const { prepareBundleForRun: prepareBundleForRun2 } = await Promise.resolve().then(() => (init_utils3(), utils_exports));
@@ -8122,7 +8232,7 @@ function validateMapping(input) {
8122
8232
  // src/commands/validate/validators/entry.ts
8123
8233
  init_dist();
8124
8234
  import Ajv from "ajv";
8125
- var CLIENT_HEADER = "walkeros-cli/4.0.0-next-1777882869103";
8235
+ var CLIENT_HEADER = "walkeros-cli/4.0.0";
8126
8236
  var SECTIONS = ["destinations", "sources", "transformers"];
8127
8237
  function resolveEntry(path18, flowConfig) {
8128
8238
  const flows = flowConfig.flows;
@@ -9274,7 +9384,7 @@ program.command("push [file]").description("Push an event through the flow with
9274
9384
  "event to push (JSON string, file path, or URL)"
9275
9385
  ).option("-o, --output <path>", "write result to file").option("-f, --flow <name>", "flow name for multi-flow configs").option("-p, --platform <platform>", "platform override (web or server)").option("--json", "output as JSON").option("-v, --verbose", "verbose output").option("-s, --silent", "suppress output").option(
9276
9386
  "--simulate <step>",
9277
- "simulate a destination step (repeatable)",
9387
+ "simulate a step (repeatable for destination.*; format: source.NAME | destination.NAME | transformer.NAME)",
9278
9388
  (val, arr) => {
9279
9389
  arr.push(val);
9280
9390
  return arr;
package/dist/index.d.ts CHANGED
@@ -336,6 +336,11 @@ interface PushResult {
336
336
  }>>;
337
337
  /** Network calls captured during web simulation (fetch + sendBeacon) */
338
338
  networkCalls?: NetworkCall[];
339
+ /**
340
+ * Per-destination simulation results when --simulate destination.* is
341
+ * called with multiple ids. Undefined for single-target or non-simulate runs.
342
+ */
343
+ perDestination?: Record<string, PushResult>;
339
344
  duration: number;
340
345
  error?: string;
341
346
  }
@@ -360,7 +365,11 @@ declare const PushOptionsSchema: z.ZodObject<{
360
365
  type PushOptions = z.infer<typeof PushOptionsSchema>;
361
366
 
362
367
  /**
363
- * CLI command handler for push command
368
+ * CLI command handler for push command.
369
+ *
370
+ * Thin wrapper around `runPushCommand`: delegates result production to the
371
+ * pure helper, then formats output and decides the exit code. Tests target
372
+ * `runPushCommand` directly to avoid `process.exit` killing Jest workers.
364
373
  */
365
374
  declare function pushCommand(options: PushCommandOptions): Promise<void>;
366
375
  /**