@walkeros/cli 4.2.0 → 4.2.1-next-1781526381392

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/cli.js CHANGED
@@ -31,25 +31,62 @@ var init_client_context = __esm({
31
31
  });
32
32
 
33
33
  // src/core/api-error.ts
34
- function throwApiError(error, fallbackMessage) {
35
- if (error && typeof error === "object" && "error" in error && typeof error.error === "object") {
36
- const inner = error.error;
37
- const message = inner.message || fallbackMessage;
38
- const code = inner.code;
39
- const details = inner.details?.errors;
40
- const options = { code, details };
41
- if (code === "CLIENT_OUTDATED") {
42
- options.minVersion = inner.minVersion;
43
- options.clientVersion = inner.clientVersion;
44
- options.client = inner.client;
45
- options.upgrade = inner.upgrade;
46
- options.docs = inner.docs;
47
- }
48
- throw new ApiError(message, options);
34
+ function extractApiErrorOptions(error, fallbackMessage) {
35
+ if (!error || typeof error !== "object" || !("error" in error) || typeof error.error !== "object") {
36
+ return null;
37
+ }
38
+ const inner = error.error;
39
+ const message = inner.message || fallbackMessage;
40
+ const code = inner.code;
41
+ const details = inner.details?.errors;
42
+ const options = { code, details };
43
+ if (code === "CLIENT_OUTDATED") {
44
+ options.minVersion = inner.minVersion;
45
+ options.clientVersion = inner.clientVersion;
46
+ options.client = inner.client;
47
+ options.upgrade = inner.upgrade;
48
+ options.docs = inner.docs;
49
49
  }
50
+ return { message, options };
51
+ }
52
+ function throwApiError(error, fallbackMessage) {
53
+ const extracted = extractApiErrorOptions(error, fallbackMessage);
54
+ if (extracted) throw new ApiError(extracted.message, extracted.options);
50
55
  throw new ApiError(fallbackMessage);
51
56
  }
57
+ function parseRetryAfter(value) {
58
+ if (!value) return void 0;
59
+ const trimmed = value.trim();
60
+ if (/^\d+$/.test(trimmed)) return parseInt(trimmed, 10);
61
+ const when = Date.parse(trimmed);
62
+ if (Number.isNaN(when)) return void 0;
63
+ return Math.max(0, Math.round((when - Date.now()) / 1e3));
64
+ }
65
+ function throwApiResponseError(response, body, fallbackMessage) {
66
+ const status = response.status;
67
+ const retryAfterSeconds = parseRetryAfter(
68
+ response.headers.get("retry-after")
69
+ );
70
+ const retryable = status === 429 || status === 503 && retryAfterSeconds !== void 0;
71
+ const extracted = extractApiErrorOptions(body, fallbackMessage);
72
+ const message = extracted?.message ?? fallbackMessage;
73
+ const options = {
74
+ ...extracted?.options ?? {},
75
+ status,
76
+ retryable,
77
+ retryAfterSeconds
78
+ };
79
+ throw new ApiError(message, options);
80
+ }
81
+ function machineReadableErrorLine(err) {
82
+ const code = err instanceof ApiError ? err.code ?? "UNKNOWN" : "UNKNOWN";
83
+ const retryable = err instanceof ApiError ? err.retryable === true : false;
84
+ const retryAfter = err instanceof ApiError && err.retryAfterSeconds !== void 0 ? ` retryAfter=${err.retryAfterSeconds}` : "";
85
+ const message = err instanceof Error ? err.message : String(err);
86
+ return `error: code=${code} retryable=${retryable}${retryAfter} message=${message}`;
87
+ }
52
88
  function handleCliError(err) {
89
+ console.error(machineReadableErrorLine(err));
53
90
  if (err instanceof ApiError && err.code === "CLIENT_OUTDATED") {
54
91
  console.error(`
55
92
  ${err.message}
@@ -59,17 +96,25 @@ ${err.message}
59
96
  `);
60
97
  process.exit(2);
61
98
  }
62
- const message = err instanceof Error ? err.message : String(err);
63
- console.error(message);
99
+ if (err instanceof ApiError && err.retryable) {
100
+ const hint = err.retryAfterSeconds !== void 0 ? ` Retry after ${err.retryAfterSeconds}s.` : " This is temporary, retry shortly.";
101
+ console.error(`${err.message}${hint}`);
102
+ process.exit(EXIT_RETRYABLE);
103
+ }
104
+ console.error(err instanceof Error ? err.message : String(err));
64
105
  process.exit(1);
65
106
  }
66
- var ApiError;
107
+ var EXIT_RETRYABLE, ApiError;
67
108
  var init_api_error = __esm({
68
109
  "src/core/api-error.ts"() {
69
110
  "use strict";
111
+ EXIT_RETRYABLE = 75;
70
112
  ApiError = class extends Error {
71
113
  code;
72
114
  details;
115
+ status;
116
+ retryable;
117
+ retryAfterSeconds;
73
118
  // Populated only for CLIENT_OUTDATED responses (HTTP 426).
74
119
  minVersion;
75
120
  clientVersion;
@@ -81,6 +126,9 @@ var init_api_error = __esm({
81
126
  this.name = "ApiError";
82
127
  this.code = options?.code;
83
128
  this.details = options?.details;
129
+ this.status = options?.status;
130
+ this.retryable = options?.retryable;
131
+ this.retryAfterSeconds = options?.retryAfterSeconds;
84
132
  this.minVersion = options?.minVersion;
85
133
  this.clientVersion = options?.clientVersion;
86
134
  this.client = options?.client;
@@ -4570,7 +4618,7 @@ ${destinationsEntries.join(",\n")}
4570
4618
  stores${collectorStr}
4571
4619
  }`;
4572
4620
  const dataPayload = JSON.stringify(dataPayloadObj, null, 2);
4573
- return { storesDeclaration, codeConfigObject, dataPayload };
4621
+ return { storesDeclaration, codeConfigObject, dataPayloadObj, dataPayload };
4574
4622
  }
4575
4623
  function generateSplitWireConfigModule(storesDeclaration, codeConfigObject, userCode) {
4576
4624
  const codeSection = userCode ? `
@@ -5314,7 +5362,7 @@ var init_contract = __esm({
5314
5362
  "src/core/contract.ts"() {
5315
5363
  "use strict";
5316
5364
  init_config_file();
5317
- bakedContractVersion = true ? "1.1.0" : PLACEHOLDER;
5365
+ bakedContractVersion = true ? "2.0.0" : PLACEHOLDER;
5318
5366
  }
5319
5367
  });
5320
5368
 
@@ -6698,7 +6746,7 @@ async function wt(e4, t4, o4) {
6698
6746
  }
6699
6747
  }
6700
6748
  function vt(e4, t4) {
6701
- return { timing: Math.round((Date.now() - e4.timing) / 10) / 100, source: { type: "collector", schema: "4", version: "4.2.0" }, ...t4 };
6749
+ return { timing: Math.round((Date.now() - e4.timing) / 10) / 100, source: { type: "collector", schema: "4", version: "4.2.1-next-1781526381392" }, ...t4 };
6702
6750
  }
6703
6751
  function bt(e4, t4) {
6704
6752
  if (!t4.name) throw new Error("Event name is required");
@@ -7451,13 +7499,13 @@ function installTimerInterception(options = {}) {
7451
7499
  setInterval: Reflect.get(target, "setInterval"),
7452
7500
  clearInterval: Reflect.get(target, "clearInterval")
7453
7501
  });
7454
- const trackedSetTimeout = (callback, delay, ...args) => {
7502
+ const trackedSetTimeout = (callback, delay2, ...args) => {
7455
7503
  if (typeof callback !== "function") return 0;
7456
7504
  const id = nextId++;
7457
7505
  pending.set(id, {
7458
7506
  id,
7459
7507
  callback,
7460
- delay: delay ?? 0,
7508
+ delay: delay2 ?? 0,
7461
7509
  type: "timeout",
7462
7510
  args,
7463
7511
  cleared: false
@@ -7470,13 +7518,13 @@ function installTimerInterception(options = {}) {
7470
7518
  const entry = pending.get(numId);
7471
7519
  if (entry) entry.cleared = true;
7472
7520
  };
7473
- const trackedSetInterval = (callback, delay, ...args) => {
7521
+ const trackedSetInterval = (callback, delay2, ...args) => {
7474
7522
  if (typeof callback !== "function") return 0;
7475
7523
  const id = nextId++;
7476
7524
  pending.set(id, {
7477
7525
  id,
7478
7526
  callback,
7479
- delay: delay ?? 0,
7527
+ delay: delay2 ?? 0,
7480
7528
  type: "interval",
7481
7529
  args,
7482
7530
  cleared: false
@@ -7753,7 +7801,7 @@ function toError(error) {
7753
7801
  return error instanceof Error ? error : new Error(getErrorMessage(error));
7754
7802
  }
7755
7803
  function buildSimulationResult(args) {
7756
- const { step, name, startTime, captured, usage, error } = args;
7804
+ const { step, name, startTime, captured, usage, mappingKey, error } = args;
7757
7805
  const events = (captured ?? []).filter(hasEvent).map((entry) => entry.event);
7758
7806
  const calls = usage ? Object.values(usage).flat().map((call) => ({ fn: call.fn, args: call.args, ts: call.ts })) : [];
7759
7807
  return {
@@ -7762,6 +7810,7 @@ function buildSimulationResult(args) {
7762
7810
  events,
7763
7811
  calls,
7764
7812
  duration: Date.now() - startTime,
7813
+ ...mappingKey !== void 0 ? { mappingKey } : {},
7765
7814
  ...error !== void 0 ? { error: toError(error) } : {}
7766
7815
  };
7767
7816
  }
@@ -8355,7 +8404,9 @@ async function simulateSource(configOrPath, input, options) {
8355
8404
  `Source package "${sourceConfig.package}" has no createTrigger in /dev export`
8356
8405
  );
8357
8406
  }
8358
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
8407
+ const flowConfig = module.wireConfig(
8408
+ options.data ?? module.__configData ?? void 0
8409
+ );
8359
8410
  applyOverrides(flowConfig, prepared.overrides);
8360
8411
  const captured = [];
8361
8412
  flowConfig.hooks = {
@@ -8460,7 +8511,9 @@ async function simulateTransformer(configOrPath, event, options) {
8460
8511
  networkCalls
8461
8512
  },
8462
8513
  async (module) => {
8463
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
8514
+ const flowConfig = module.wireConfig(
8515
+ options.data ?? module.__configData ?? void 0
8516
+ );
8464
8517
  applyOverrides(flowConfig, prepared.overrides);
8465
8518
  if (flowConfig.sources) flowConfig.sources = {};
8466
8519
  if (flowConfig.destinations) flowConfig.destinations = {};
@@ -8626,7 +8679,9 @@ async function simulateCollector(configOrPath, event, options) {
8626
8679
  networkCalls
8627
8680
  },
8628
8681
  async (module) => {
8629
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
8682
+ const flowConfig = module.wireConfig(
8683
+ options.data ?? module.__configData ?? void 0
8684
+ );
8630
8685
  applyOverrides(flowConfig, prepared.overrides);
8631
8686
  if (flowConfig.sources) flowConfig.sources = {};
8632
8687
  if (flowConfig.destinations) flowConfig.destinations = {};
@@ -8729,7 +8784,9 @@ async function simulateDestination(configOrPath, event, options) {
8729
8784
  networkCalls
8730
8785
  },
8731
8786
  async (module) => {
8732
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
8787
+ const flowConfig = module.wireConfig(
8788
+ options.data ?? module.__configData ?? void 0
8789
+ );
8733
8790
  applyOverrides(flowConfig, prepared.overrides);
8734
8791
  const destPkg = (prepared.flowSettings.destinations ?? {})[options.destinationId];
8735
8792
  let trackedCalls = [];
@@ -8765,6 +8822,16 @@ async function simulateDestination(configOrPath, event, options) {
8765
8822
  );
8766
8823
  }
8767
8824
  logger.info(`Simulating destination: ${options.destinationId}`);
8825
+ let mappingKey;
8826
+ const targetStepId = o("destination", options.destinationId);
8827
+ const captureMappingKey = (state) => {
8828
+ if (state.stepId === targetStepId && state.mappingKey) {
8829
+ mappingKey = state.mappingKey;
8830
+ }
8831
+ };
8832
+ if (collector.observers instanceof Set) {
8833
+ collector.observers.add(captureMappingKey);
8834
+ }
8768
8835
  await collector.push(event, {
8769
8836
  include: [options.destinationId]
8770
8837
  });
@@ -8773,7 +8840,8 @@ async function simulateDestination(configOrPath, event, options) {
8773
8840
  step: "destination",
8774
8841
  name: options.destinationId,
8775
8842
  startTime,
8776
- usage: trackedCalls.length ? { [options.destinationId]: trackedCalls } : void 0
8843
+ usage: trackedCalls.length ? { [options.destinationId]: trackedCalls } : void 0,
8844
+ mappingKey
8777
8845
  });
8778
8846
  },
8779
8847
  (error) => buildSimulationResult({
@@ -8818,8 +8886,117 @@ import {
8818
8886
  import { dirname as dirname4, join as join5 } from "path";
8819
8887
  import { Readable } from "stream";
8820
8888
  import { x as tarExtract } from "tar";
8889
+
8890
+ // src/runtime/fetch-retry.ts
8891
+ var DEFAULT_PER_ATTEMPT_TIMEOUT_MS = 3e4;
8892
+ var DEFAULT_MAX_TOTAL_MS = 6e4;
8893
+ var DEFAULT_ATTEMPTS = 3;
8894
+ var MIN_ATTEMPT_BUDGET_MS = 1e3;
8895
+ var BASE_BACKOFF_MS = [2e3, 5e3];
8896
+ var JITTER = 0.2;
8897
+ var RETRYABLE_NETWORK_CODES = /* @__PURE__ */ new Set([
8898
+ "ECONNRESET",
8899
+ "ECONNREFUSED",
8900
+ "ETIMEDOUT",
8901
+ "EAI_AGAIN",
8902
+ "ENOTFOUND"
8903
+ ]);
8904
+ function readErrorCode(value) {
8905
+ if (typeof value !== "object" || value === null) return void 0;
8906
+ const code = Reflect.get(value, "code");
8907
+ return typeof code === "string" ? code : void 0;
8908
+ }
8909
+ function readErrorName(value) {
8910
+ if (typeof value !== "object" || value === null) return void 0;
8911
+ const name = Reflect.get(value, "name");
8912
+ return typeof name === "string" ? name : void 0;
8913
+ }
8914
+ function isTransientThrow(error) {
8915
+ const name = readErrorName(error);
8916
+ if (name === "TimeoutError" || name === "AbortError") return true;
8917
+ const directCode = readErrorCode(error);
8918
+ if (directCode && RETRYABLE_NETWORK_CODES.has(directCode)) return true;
8919
+ if (typeof error === "object" && error !== null) {
8920
+ const causeCode = readErrorCode(Reflect.get(error, "cause"));
8921
+ if (causeCode && RETRYABLE_NETWORK_CODES.has(causeCode)) return true;
8922
+ }
8923
+ return error instanceof TypeError;
8924
+ }
8925
+ function isTransientStatus(status) {
8926
+ return status >= 500 || status === 429;
8927
+ }
8928
+ function readErrorMessage(value) {
8929
+ if (typeof value !== "object" || value === null) return void 0;
8930
+ const message = Reflect.get(value, "message");
8931
+ return typeof message === "string" ? message : void 0;
8932
+ }
8933
+ function describeReason(reason) {
8934
+ if (reason.kind === "status") return `HTTP ${reason.status}`;
8935
+ const name = readErrorName(reason.error);
8936
+ const message = reason.error instanceof Error ? reason.error.message : String(reason.error);
8937
+ let detail = name ? `${name}: ${message}` : message;
8938
+ if (typeof reason.error === "object" && reason.error !== null) {
8939
+ const cause = Reflect.get(reason.error, "cause");
8940
+ const causeCode = readErrorCode(cause);
8941
+ const causeMessage = readErrorMessage(cause);
8942
+ const causeDetail = causeCode ?? causeMessage;
8943
+ if (causeDetail) detail = `${detail} (${causeDetail})`;
8944
+ }
8945
+ return detail;
8946
+ }
8947
+ function backoffForAttempt(index) {
8948
+ const base = BASE_BACKOFF_MS[Math.min(index, BASE_BACKOFF_MS.length - 1)] ?? 0;
8949
+ const spread = base * JITTER;
8950
+ return base + (Math.random() * 2 - 1) * spread;
8951
+ }
8952
+ function delay(ms) {
8953
+ return new Promise((resolve4) => {
8954
+ setTimeout(resolve4, ms);
8955
+ });
8956
+ }
8957
+ async function fetchWithRetry(url, options = {}) {
8958
+ const attempts = options.attempts ?? DEFAULT_ATTEMPTS;
8959
+ const perAttemptTimeoutMs = options.perAttemptTimeoutMs ?? DEFAULT_PER_ATTEMPT_TIMEOUT_MS;
8960
+ const maxTotalMs = options.maxTotalMs ?? DEFAULT_MAX_TOTAL_MS;
8961
+ const init = options.init;
8962
+ const start = Date.now();
8963
+ let lastReason;
8964
+ let made = 0;
8965
+ for (let attempt = 0; attempt < attempts; attempt++) {
8966
+ const remaining = maxTotalMs - (Date.now() - start);
8967
+ if (remaining <= MIN_ATTEMPT_BUDGET_MS) break;
8968
+ const attemptTimeoutMs = Math.min(perAttemptTimeoutMs, remaining);
8969
+ made = attempt + 1;
8970
+ let reason;
8971
+ try {
8972
+ const response = await fetch(url, {
8973
+ ...init,
8974
+ signal: AbortSignal.timeout(attemptTimeoutMs)
8975
+ });
8976
+ if (!isTransientStatus(response.status)) return response;
8977
+ await response.body?.cancel();
8978
+ reason = { kind: "status", status: response.status };
8979
+ } catch (error) {
8980
+ if (!isTransientThrow(error)) throw error;
8981
+ reason = { kind: "throw", error };
8982
+ }
8983
+ lastReason = reason;
8984
+ const isLastAttempt = attempt === attempts - 1;
8985
+ const budgetSpent = Date.now() - start >= maxTotalMs;
8986
+ if (isLastAttempt || budgetSpent) break;
8987
+ const sleepMs = Math.min(
8988
+ backoffForAttempt(attempt),
8989
+ maxTotalMs - (Date.now() - start)
8990
+ );
8991
+ if (sleepMs <= 0) break;
8992
+ await delay(sleepMs);
8993
+ }
8994
+ const cause = lastReason ? describeReason(lastReason) : "no attempts made";
8995
+ throw new Error(`Fetch failed after ${made} attempts: ${cause}`);
8996
+ }
8997
+
8998
+ // src/runtime/resolve-bundle.ts
8821
8999
  var ARCHIVE_ENTRY = "flow.mjs";
8822
- var FETCH_TIMEOUT_MS = 3e4;
8823
9000
  function getDefaultWritePath() {
8824
9001
  if (existsSync2("/app/flow")) return "/app/flow/flow.mjs";
8825
9002
  return "/tmp/walkeros-flow.mjs";
@@ -8844,9 +9021,7 @@ function writeBundleToDisk(writePath, content) {
8844
9021
  writeFileSync2(writePath, content, "utf-8");
8845
9022
  }
8846
9023
  async function fetchOk(url) {
8847
- const response = await fetch(url, {
8848
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
8849
- });
9024
+ const response = await fetchWithRetry(url);
8850
9025
  if (!response.ok) {
8851
9026
  throw new Error(
8852
9027
  `Failed to fetch bundle from ${url}: ${response.status} ${response.statusText}`
@@ -8967,10 +9142,7 @@ async function fetchConfig(options) {
8967
9142
  options.token,
8968
9143
  options.lastEtag ? { "If-None-Match": options.lastEtag } : void 0
8969
9144
  );
8970
- const response = await fetch(url, {
8971
- headers,
8972
- signal: AbortSignal.timeout(3e4)
8973
- });
9145
+ const response = await fetchWithRetry(url, { init: { headers } });
8974
9146
  if (response.status === 304) {
8975
9147
  return { changed: false };
8976
9148
  }
@@ -9482,8 +9654,11 @@ var SecretsHttpError = class extends Error {
9482
9654
  async function fetchSecrets(options) {
9483
9655
  const { appUrl, token, projectId, flowId } = options;
9484
9656
  const url = `${appUrl}/api/projects/${encodeURIComponent(projectId)}/flows/${encodeURIComponent(flowId)}/secrets/values`;
9485
- const res = await fetch(url, {
9486
- headers: mergeAuthHeaders(token, { "Content-Type": "application/json" })
9657
+ const res = await fetchWithRetry(url, {
9658
+ maxTotalMs: 2e4,
9659
+ init: {
9660
+ headers: mergeAuthHeaders(token, { "Content-Type": "application/json" })
9661
+ }
9487
9662
  });
9488
9663
  await throwIfRunnerAuthFailure(res);
9489
9664
  if (!res.ok) {
@@ -9498,13 +9673,21 @@ init_cache();
9498
9673
  async function runPipeline(options) {
9499
9674
  const { bundlePath, port, logger, loggerConfig, api } = options;
9500
9675
  let configVersion;
9676
+ const configFrozen = readConfigFrozen();
9501
9677
  if (api) {
9502
9678
  await injectSecrets(api, logger);
9503
9679
  }
9504
9680
  logger.info(`walkeros/flow v${VERSION}`);
9505
9681
  logger.info(`Instance: ${getInstanceId()}`);
9682
+ if (configFrozen) {
9683
+ logger.info("Config frozen: hot-swap and heartbeat disabled");
9684
+ }
9506
9685
  const healthServer = await createHealthServer(port, logger);
9507
- const telemetryObservers = buildTelemetryObservers(api?.flowId ?? "flow");
9686
+ const observeLevel = readObserveLevel(logger);
9687
+ const telemetryObservers = buildTelemetryObservers(
9688
+ api?.flowId ?? "flow",
9689
+ observeLevel
9690
+ );
9508
9691
  const runtimeConfig = { port };
9509
9692
  let handle;
9510
9693
  try {
@@ -9533,20 +9716,24 @@ async function runPipeline(options) {
9533
9716
  const ingestToken = process.env.WALKEROS_INGEST_TOKEN;
9534
9717
  const deploymentId = process.env.WALKEROS_DEPLOYMENT_ID;
9535
9718
  if (observerBase && ingestToken && deploymentId) {
9536
- tracePoller = createTracePoller(
9537
- {
9538
- url: `${observerBase}/trace/${deploymentId}`,
9539
- token: ingestToken,
9540
- intervalMs: 15e3
9541
- },
9542
- logger
9543
- );
9544
- tracePoller.start();
9545
- logger.info("Trace poller: active (every 15s)");
9719
+ if (observeLevel === "trace") {
9720
+ logger.info("Trace poller: skipped (observe level is trace)");
9721
+ } else {
9722
+ tracePoller = createTracePoller(
9723
+ {
9724
+ url: `${observerBase}/trace/${deploymentId}`,
9725
+ token: ingestToken,
9726
+ intervalMs: 15e3
9727
+ },
9728
+ logger
9729
+ );
9730
+ tracePoller.start();
9731
+ logger.info("Trace poller: active (every 15s)");
9732
+ }
9546
9733
  }
9547
9734
  let currentBundleCleanup;
9548
9735
  let currentConfigPath;
9549
- if (api) {
9736
+ if (api && !configFrozen) {
9550
9737
  heartbeat = createHeartbeat(
9551
9738
  {
9552
9739
  appUrl: api.appUrl,
@@ -9663,17 +9850,39 @@ async function runPipeline(options) {
9663
9850
  await new Promise(() => {
9664
9851
  });
9665
9852
  }
9666
- function buildTelemetryObservers(flowId) {
9853
+ var OBSERVE_LEVELS = [
9854
+ "off",
9855
+ "standard",
9856
+ "trace"
9857
+ ];
9858
+ function readConfigFrozen() {
9859
+ const raw = process.env.WALKEROS_CONFIG_FROZEN;
9860
+ return raw === "1" || raw === "true";
9861
+ }
9862
+ function readObserveLevel(logger) {
9863
+ const raw = process.env.WALKEROS_OBSERVE_LEVEL;
9864
+ if (raw === void 0 || raw === "") return void 0;
9865
+ const level = OBSERVE_LEVELS.find((candidate) => candidate === raw);
9866
+ if (!level) {
9867
+ logger.warn(
9868
+ `Ignoring invalid WALKEROS_OBSERVE_LEVEL "${raw}" (expected off, standard, or trace)`
9869
+ );
9870
+ return void 0;
9871
+ }
9872
+ return level;
9873
+ }
9874
+ function buildTelemetryObservers(flowId, observeLevel) {
9667
9875
  const base = process.env.WALKEROS_OBSERVER_URL;
9668
9876
  const token = process.env.WALKEROS_INGEST_TOKEN;
9669
9877
  const deploymentId = process.env.WALKEROS_DEPLOYMENT_ID;
9670
9878
  if (!base || !token || !deploymentId) return void 0;
9671
9879
  const url = `${base}/ingest/${deploymentId}`;
9672
9880
  const emit = ht({ url, token });
9881
+ const observe = observeLevel !== void 0 ? { level: observeLevel } : void 0;
9673
9882
  return [
9674
9883
  ft(
9675
9884
  emit,
9676
- () => pt({ flowId, traceUntil: mt() })
9885
+ () => pt({ flowId, observe, traceUntil: mt() })
9677
9886
  )
9678
9887
  ];
9679
9888
  }
@@ -11940,7 +12149,7 @@ function validateMapping(input) {
11940
12149
  // src/commands/validate/validators/entry.ts
11941
12150
  init_dist();
11942
12151
  import Ajv from "ajv";
11943
- var CLIENT_HEADER = "walkeros-cli/4.2.0";
12152
+ var CLIENT_HEADER = "walkeros-cli/4.2.1-next-1781526381392";
11944
12153
  var SECTIONS = ["destinations", "sources", "transformers"];
11945
12154
  function resolveEntry(path19, flowConfig) {
11946
12155
  const flows = flowConfig.flows;
@@ -12575,6 +12784,7 @@ init_sse();
12575
12784
  init_cli_logger();
12576
12785
  init_output();
12577
12786
  init_flows();
12787
+ import { randomUUID as randomUUID2 } from "crypto";
12578
12788
  async function resolveSettingsId(options) {
12579
12789
  const flow = await getFlow({
12580
12790
  flowId: options.flowId,
@@ -12600,8 +12810,9 @@ async function getAvailableFlowNames(options) {
12600
12810
  const settings = flow.settings;
12601
12811
  return settings?.map((c2) => c2.name) ?? [];
12602
12812
  }
12813
+ var DEFAULT_DEPLOY_WAIT_MS = 12 * 60 * 1e3;
12603
12814
  async function streamDeploymentStatus(projectId, deploymentId, options) {
12604
- const timeoutMs = options.timeout ?? 12e4;
12815
+ const timeoutMs = options.timeout ?? DEFAULT_DEPLOY_WAIT_MS;
12605
12816
  const response = await apiFetch(
12606
12817
  `/api/projects/${projectId}/deployments/${deploymentId}/stream`,
12607
12818
  {
@@ -12660,7 +12871,10 @@ async function deploy(options) {
12660
12871
  }
12661
12872
  const { data, error } = await client.POST(
12662
12873
  "/api/projects/{projectId}/flows/{flowId}/deploy",
12663
- { params: { path: { projectId, flowId: options.flowId } } }
12874
+ {
12875
+ params: { path: { projectId, flowId: options.flowId } },
12876
+ headers: { "Idempotency-Key": randomUUID2() }
12877
+ }
12664
12878
  );
12665
12879
  if (error) {
12666
12880
  try {
@@ -12690,13 +12904,38 @@ Available: ${names.join(", ")}`,
12690
12904
  }
12691
12905
  async function deploySettings(options) {
12692
12906
  const { flowId, projectId, settingsId } = options;
12693
- const response = await apiFetch(
12907
+ const triggerDeploy = () => apiFetch(
12694
12908
  `/api/projects/${projectId}/flows/${flowId}/settings/${settingsId}/deploy`,
12695
- { method: "POST" }
12909
+ { method: "POST", headers: { "Idempotency-Key": randomUUID2() } }
12696
12910
  );
12911
+ let response = await triggerDeploy();
12912
+ if (!response.ok && options.wait) {
12913
+ let retryBody = {};
12914
+ try {
12915
+ retryBody = await response.clone().json();
12916
+ } catch {
12917
+ retryBody = {};
12918
+ }
12919
+ try {
12920
+ throwApiResponseError(
12921
+ response,
12922
+ retryBody,
12923
+ `Deploy failed (${response.status})`
12924
+ );
12925
+ } catch (e4) {
12926
+ if (e4 instanceof ApiError && e4.retryable) {
12927
+ const waitSeconds = Math.min(e4.retryAfterSeconds ?? 5, 60);
12928
+ options.onStatus?.("rate_limited", `retrying in ${waitSeconds}s`);
12929
+ await new Promise((resolve4) => setTimeout(resolve4, waitSeconds * 1e3));
12930
+ response = await triggerDeploy();
12931
+ } else {
12932
+ throw e4;
12933
+ }
12934
+ }
12935
+ }
12697
12936
  if (!response.ok) {
12698
12937
  const body = await response.json().catch(() => ({}));
12699
- throwApiError(body, `Deploy failed (${response.status})`);
12938
+ throwApiResponseError(response, body, `Deploy failed (${response.status})`);
12700
12939
  }
12701
12940
  const data = await response.json();
12702
12941
  if (!options.wait) return data;
@@ -12708,16 +12947,20 @@ async function deploySettings(options) {
12708
12947
  return { ...data, ...result };
12709
12948
  }
12710
12949
  var statusLabels = {
12711
- bundling: "Building bundle...",
12712
- "bundling:building": "Building bundle...",
12713
- "bundling:publishing": "Publishing to web...",
12714
- deploying: "Deploying container...",
12950
+ "deploying:building": "Building bundle...",
12951
+ "deploying:publishing": "Publishing to web...",
12715
12952
  "deploying:provisioning": "Provisioning container...",
12716
12953
  "deploying:starting": "Starting container...",
12954
+ deploying: "Deploying...",
12717
12955
  active: "Container is live",
12718
12956
  published: "Published",
12957
+ stopped: "Stopped",
12719
12958
  failed: "Deployment failed"
12720
12959
  };
12960
+ function renderStatusLabel(status, substatus) {
12961
+ const key = substatus ? `${status}:${substatus}` : status;
12962
+ return statusLabels[key] || statusLabels[status] || `Status: ${status}`;
12963
+ }
12721
12964
  async function deployCommand(flowId, options) {
12722
12965
  const log = createCLILogger(options);
12723
12966
  const timeoutMs = options.timeout ? parseInt(options.timeout, 10) * 1e3 : void 0;
@@ -12729,10 +12972,7 @@ async function deployCommand(flowId, options) {
12729
12972
  wait: options.wait !== false,
12730
12973
  timeout: timeoutMs,
12731
12974
  onStatus: options.json ? void 0 : (status, substatus) => {
12732
- const key = substatus ? `${status}:${substatus}` : status;
12733
- log.info(
12734
- statusLabels[key] || statusLabels[status] || `Status: ${status}`
12735
- );
12975
+ log.info(renderStatusLabel(status, substatus));
12736
12976
  }
12737
12977
  });
12738
12978
  if (options.json) {
@@ -12747,7 +12987,7 @@ async function deployCommand(flowId, options) {
12747
12987
  } else if (r5.status === "failed") {
12748
12988
  log.error(`Failed: ${r5.errorMessage || "Unknown error"}`);
12749
12989
  process.exit(1);
12750
- } else if (r5.status === "bundling") {
12990
+ } else if (r5.status === "deploying") {
12751
12991
  log.info(`Deployment started: ${r5.deploymentId} (${r5.type})`);
12752
12992
  } else {
12753
12993
  log.info(`Status: ${r5.status}`);
@@ -12902,20 +13142,16 @@ async function createDeployCommand(config, options) {
12902
13142
  log.info(`Deployment created: ${result.id}`);
12903
13143
  log.info(` Slug: ${result.slug}`);
12904
13144
  log.info(` Type: ${result.type}`);
12905
- if (result.deployToken) {
12906
- log.info(` Token: ${result.deployToken}`);
12907
- log.warn(" Save this token \u2014 it will not be shown again.");
12908
- }
12909
13145
  log.info("");
12910
13146
  log.info("Run locally:");
12911
13147
  log.info(
12912
13148
  ` walkeros run ${isRemoteFlow ? "flow.json" : config} --deploy ${result.id}`
12913
13149
  );
12914
13150
  log.info("");
12915
- log.info("Docker:");
12916
- log.info(
12917
- ` docker run -e WALKEROS_DEPLOY_TOKEN=${result.deployToken ?? "<token>"} \\`
12918
- );
13151
+ log.info("Create a deploy token for this flow in the app");
13152
+ log.info("(Settings, Self-hosted deploy token) and set it as");
13153
+ log.info("WALKEROS_DEPLOY_TOKEN, then run with Docker:");
13154
+ log.info(" docker run -e WALKEROS_DEPLOY_TOKEN \\");
12919
13155
  log.info(" -e WALKEROS_APP_URL=https://app.walkeros.io \\");
12920
13156
  log.info(" walkeros/flow:latest");
12921
13157
  } catch (err) {
@@ -13231,7 +13467,7 @@ deployCmd.command("create [config]").description(
13231
13467
  });
13232
13468
  deployCmd.command("start <flowId>").description("Deploy a flow to walkerOS cloud (auto-detects web or server)").option("--project <id>", "project ID (defaults to WALKEROS_PROJECT_ID)").option("-f, --flow <name>", "flow name for multi-config flows").option("--no-wait", "do not wait for deployment to complete").option(
13233
13469
  "--timeout <seconds>",
13234
- "timeout for deployment polling (default: 120)"
13470
+ "timeout in seconds for deployment polling (default: 720, the 12-minute server deploy budget)"
13235
13471
  ).option("-o, --output <path>", "output file path").option("--json", "output as JSON").option("-v, --verbose", "verbose output").option("-s, --silent", "suppress output").action(async (flowId, options) => {
13236
13472
  await deployCommand(flowId, options);
13237
13473
  });