@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/index.js CHANGED
@@ -2755,6 +2755,9 @@ ${firstError.text}`
2755
2755
  ` + (location ? ` at ${location.file}:${location.line}:${location.column}` : "")
2756
2756
  );
2757
2757
  }
2758
+ function buildDataPayload(flowSettings) {
2759
+ return buildSplitConfigObject(flowSettings, detectNamedImports(flowSettings)).dataPayloadObj;
2760
+ }
2758
2761
  function buildSplitConfigObject(flowSettings, namedImports) {
2759
2762
  const sources = flowSettings.sources || {};
2760
2763
  const destinations = flowSettings.destinations || {};
@@ -2777,7 +2780,7 @@ function buildSplitConfigObject(flowSettings, namedImports) {
2777
2780
  }
2778
2781
  return props;
2779
2782
  }
2780
- function buildSplitStepEntry(section, stepId, step) {
2783
+ function buildSplitStepEntry(section, stepId2, step) {
2781
2784
  const codeVar = resolveCodeVar(step);
2782
2785
  const stepProps = getStepProps(step);
2783
2786
  const { codeProps, dataProps } = classifyStepProperties(stepProps);
@@ -2788,13 +2791,13 @@ function buildSplitConfigObject(flowSettings, namedImports) {
2788
2791
  codeEntries.push(`${key}: ${processConfigValue(value)}`);
2789
2792
  }
2790
2793
  for (const key of Object.keys(dataProps)) {
2791
- codeEntries.push(`${key}: __data.${section}.${stepId}.${key}`);
2794
+ codeEntries.push(`${key}: __data.${section}.${stepId2}.${key}`);
2792
2795
  }
2793
2796
  if (Object.keys(dataProps).length > 0) {
2794
2797
  if (!dataPayloadObj[section]) dataPayloadObj[section] = {};
2795
- dataPayloadObj[section][stepId] = dataProps;
2798
+ dataPayloadObj[section][stepId2] = dataProps;
2796
2799
  }
2797
- return ` ${stepId}: {
2800
+ return ` ${stepId2}: {
2798
2801
  ${codeEntries.join(",\n ")}
2799
2802
  }`;
2800
2803
  }
@@ -2904,7 +2907,7 @@ ${destinationsEntries.join(",\n")}
2904
2907
  stores${collectorStr}
2905
2908
  }`;
2906
2909
  const dataPayload = JSON.stringify(dataPayloadObj, null, 2);
2907
- return { storesDeclaration, codeConfigObject, dataPayload };
2910
+ return { storesDeclaration, codeConfigObject, dataPayloadObj, dataPayload };
2908
2911
  }
2909
2912
  function generateSplitWireConfigModule(storesDeclaration, codeConfigObject, userCode) {
2910
2913
  const codeSection = userCode ? `
@@ -3848,7 +3851,8 @@ import {
3848
3851
  createIngest,
3849
3852
  getPlatform as getPlatform3,
3850
3853
  getNextSteps,
3851
- buildCacheContext
3854
+ buildCacheContext,
3855
+ stepId
3852
3856
  } from "@walkeros/core";
3853
3857
  import {
3854
3858
  enrichEvent,
@@ -4052,13 +4056,13 @@ function installTimerInterception(options = {}) {
4052
4056
  setInterval: Reflect.get(target, "setInterval"),
4053
4057
  clearInterval: Reflect.get(target, "clearInterval")
4054
4058
  });
4055
- const trackedSetTimeout = (callback, delay, ...args) => {
4059
+ const trackedSetTimeout = (callback, delay2, ...args) => {
4056
4060
  if (typeof callback !== "function") return 0;
4057
4061
  const id = nextId++;
4058
4062
  pending.set(id, {
4059
4063
  id,
4060
4064
  callback,
4061
- delay: delay ?? 0,
4065
+ delay: delay2 ?? 0,
4062
4066
  type: "timeout",
4063
4067
  args,
4064
4068
  cleared: false
@@ -4071,13 +4075,13 @@ function installTimerInterception(options = {}) {
4071
4075
  const entry = pending.get(numId);
4072
4076
  if (entry) entry.cleared = true;
4073
4077
  };
4074
- const trackedSetInterval = (callback, delay, ...args) => {
4078
+ const trackedSetInterval = (callback, delay2, ...args) => {
4075
4079
  if (typeof callback !== "function") return 0;
4076
4080
  const id = nextId++;
4077
4081
  pending.set(id, {
4078
4082
  id,
4079
4083
  callback,
4080
- delay: delay ?? 0,
4084
+ delay: delay2 ?? 0,
4081
4085
  type: "interval",
4082
4086
  args,
4083
4087
  cleared: false
@@ -4354,7 +4358,7 @@ function toError(error) {
4354
4358
  return error instanceof Error ? error : new Error(getErrorMessage(error));
4355
4359
  }
4356
4360
  function buildSimulationResult(args) {
4357
- const { step, name, startTime, captured, usage, error } = args;
4361
+ const { step, name, startTime, captured, usage, mappingKey, error } = args;
4358
4362
  const events = (captured ?? []).filter(hasEvent).map((entry) => entry.event);
4359
4363
  const calls = usage ? Object.values(usage).flat().map((call) => ({ fn: call.fn, args: call.args, ts: call.ts })) : [];
4360
4364
  return {
@@ -4363,6 +4367,7 @@ function buildSimulationResult(args) {
4363
4367
  events,
4364
4368
  calls,
4365
4369
  duration: Date.now() - startTime,
4370
+ ...mappingKey !== void 0 ? { mappingKey } : {},
4366
4371
  ...error !== void 0 ? { error: toError(error) } : {}
4367
4372
  };
4368
4373
  }
@@ -4956,7 +4961,9 @@ async function simulateSource(configOrPath, input, options) {
4956
4961
  `Source package "${sourceConfig.package}" has no createTrigger in /dev export`
4957
4962
  );
4958
4963
  }
4959
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
4964
+ const flowConfig = module.wireConfig(
4965
+ options.data ?? module.__configData ?? void 0
4966
+ );
4960
4967
  applyOverrides(flowConfig, prepared.overrides);
4961
4968
  const captured = [];
4962
4969
  flowConfig.hooks = {
@@ -5061,7 +5068,9 @@ async function simulateTransformer(configOrPath, event, options) {
5061
5068
  networkCalls
5062
5069
  },
5063
5070
  async (module) => {
5064
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
5071
+ const flowConfig = module.wireConfig(
5072
+ options.data ?? module.__configData ?? void 0
5073
+ );
5065
5074
  applyOverrides(flowConfig, prepared.overrides);
5066
5075
  if (flowConfig.sources) flowConfig.sources = {};
5067
5076
  if (flowConfig.destinations) flowConfig.destinations = {};
@@ -5227,7 +5236,9 @@ async function simulateCollector(configOrPath, event, options) {
5227
5236
  networkCalls
5228
5237
  },
5229
5238
  async (module) => {
5230
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
5239
+ const flowConfig = module.wireConfig(
5240
+ options.data ?? module.__configData ?? void 0
5241
+ );
5231
5242
  applyOverrides(flowConfig, prepared.overrides);
5232
5243
  if (flowConfig.sources) flowConfig.sources = {};
5233
5244
  if (flowConfig.destinations) flowConfig.destinations = {};
@@ -5330,7 +5341,9 @@ async function simulateDestination(configOrPath, event, options) {
5330
5341
  networkCalls
5331
5342
  },
5332
5343
  async (module) => {
5333
- const flowConfig = module.wireConfig(module.__configData ?? void 0);
5344
+ const flowConfig = module.wireConfig(
5345
+ options.data ?? module.__configData ?? void 0
5346
+ );
5334
5347
  applyOverrides(flowConfig, prepared.overrides);
5335
5348
  const destPkg = (prepared.flowSettings.destinations ?? {})[options.destinationId];
5336
5349
  let trackedCalls = [];
@@ -5366,6 +5379,16 @@ async function simulateDestination(configOrPath, event, options) {
5366
5379
  );
5367
5380
  }
5368
5381
  logger.info(`Simulating destination: ${options.destinationId}`);
5382
+ let mappingKey;
5383
+ const targetStepId = stepId("destination", options.destinationId);
5384
+ const captureMappingKey = (state) => {
5385
+ if (state.stepId === targetStepId && state.mappingKey) {
5386
+ mappingKey = state.mappingKey;
5387
+ }
5388
+ };
5389
+ if (collector.observers instanceof Set) {
5390
+ collector.observers.add(captureMappingKey);
5391
+ }
5369
5392
  await collector.push(event, {
5370
5393
  include: [options.destinationId]
5371
5394
  });
@@ -5374,7 +5397,8 @@ async function simulateDestination(configOrPath, event, options) {
5374
5397
  step: "destination",
5375
5398
  name: options.destinationId,
5376
5399
  startTime,
5377
- usage: trackedCalls.length ? { [options.destinationId]: trackedCalls } : void 0
5400
+ usage: trackedCalls.length ? { [options.destinationId]: trackedCalls } : void 0,
5401
+ mappingKey
5378
5402
  });
5379
5403
  },
5380
5404
  (error) => buildSimulationResult({
@@ -5419,8 +5443,117 @@ import {
5419
5443
  import { dirname as dirname3, join as join4 } from "path";
5420
5444
  import { Readable } from "stream";
5421
5445
  import { x as tarExtract } from "tar";
5446
+
5447
+ // src/runtime/fetch-retry.ts
5448
+ var DEFAULT_PER_ATTEMPT_TIMEOUT_MS = 3e4;
5449
+ var DEFAULT_MAX_TOTAL_MS = 6e4;
5450
+ var DEFAULT_ATTEMPTS = 3;
5451
+ var MIN_ATTEMPT_BUDGET_MS = 1e3;
5452
+ var BASE_BACKOFF_MS = [2e3, 5e3];
5453
+ var JITTER = 0.2;
5454
+ var RETRYABLE_NETWORK_CODES = /* @__PURE__ */ new Set([
5455
+ "ECONNRESET",
5456
+ "ECONNREFUSED",
5457
+ "ETIMEDOUT",
5458
+ "EAI_AGAIN",
5459
+ "ENOTFOUND"
5460
+ ]);
5461
+ function readErrorCode(value) {
5462
+ if (typeof value !== "object" || value === null) return void 0;
5463
+ const code = Reflect.get(value, "code");
5464
+ return typeof code === "string" ? code : void 0;
5465
+ }
5466
+ function readErrorName(value) {
5467
+ if (typeof value !== "object" || value === null) return void 0;
5468
+ const name = Reflect.get(value, "name");
5469
+ return typeof name === "string" ? name : void 0;
5470
+ }
5471
+ function isTransientThrow(error) {
5472
+ const name = readErrorName(error);
5473
+ if (name === "TimeoutError" || name === "AbortError") return true;
5474
+ const directCode = readErrorCode(error);
5475
+ if (directCode && RETRYABLE_NETWORK_CODES.has(directCode)) return true;
5476
+ if (typeof error === "object" && error !== null) {
5477
+ const causeCode = readErrorCode(Reflect.get(error, "cause"));
5478
+ if (causeCode && RETRYABLE_NETWORK_CODES.has(causeCode)) return true;
5479
+ }
5480
+ return error instanceof TypeError;
5481
+ }
5482
+ function isTransientStatus(status) {
5483
+ return status >= 500 || status === 429;
5484
+ }
5485
+ function readErrorMessage(value) {
5486
+ if (typeof value !== "object" || value === null) return void 0;
5487
+ const message = Reflect.get(value, "message");
5488
+ return typeof message === "string" ? message : void 0;
5489
+ }
5490
+ function describeReason(reason) {
5491
+ if (reason.kind === "status") return `HTTP ${reason.status}`;
5492
+ const name = readErrorName(reason.error);
5493
+ const message = reason.error instanceof Error ? reason.error.message : String(reason.error);
5494
+ let detail = name ? `${name}: ${message}` : message;
5495
+ if (typeof reason.error === "object" && reason.error !== null) {
5496
+ const cause = Reflect.get(reason.error, "cause");
5497
+ const causeCode = readErrorCode(cause);
5498
+ const causeMessage = readErrorMessage(cause);
5499
+ const causeDetail = causeCode ?? causeMessage;
5500
+ if (causeDetail) detail = `${detail} (${causeDetail})`;
5501
+ }
5502
+ return detail;
5503
+ }
5504
+ function backoffForAttempt(index) {
5505
+ const base = BASE_BACKOFF_MS[Math.min(index, BASE_BACKOFF_MS.length - 1)] ?? 0;
5506
+ const spread = base * JITTER;
5507
+ return base + (Math.random() * 2 - 1) * spread;
5508
+ }
5509
+ function delay(ms) {
5510
+ return new Promise((resolve5) => {
5511
+ setTimeout(resolve5, ms);
5512
+ });
5513
+ }
5514
+ async function fetchWithRetry(url, options = {}) {
5515
+ const attempts = options.attempts ?? DEFAULT_ATTEMPTS;
5516
+ const perAttemptTimeoutMs = options.perAttemptTimeoutMs ?? DEFAULT_PER_ATTEMPT_TIMEOUT_MS;
5517
+ const maxTotalMs = options.maxTotalMs ?? DEFAULT_MAX_TOTAL_MS;
5518
+ const init = options.init;
5519
+ const start = Date.now();
5520
+ let lastReason;
5521
+ let made = 0;
5522
+ for (let attempt = 0; attempt < attempts; attempt++) {
5523
+ const remaining = maxTotalMs - (Date.now() - start);
5524
+ if (remaining <= MIN_ATTEMPT_BUDGET_MS) break;
5525
+ const attemptTimeoutMs = Math.min(perAttemptTimeoutMs, remaining);
5526
+ made = attempt + 1;
5527
+ let reason;
5528
+ try {
5529
+ const response = await fetch(url, {
5530
+ ...init,
5531
+ signal: AbortSignal.timeout(attemptTimeoutMs)
5532
+ });
5533
+ if (!isTransientStatus(response.status)) return response;
5534
+ await response.body?.cancel();
5535
+ reason = { kind: "status", status: response.status };
5536
+ } catch (error) {
5537
+ if (!isTransientThrow(error)) throw error;
5538
+ reason = { kind: "throw", error };
5539
+ }
5540
+ lastReason = reason;
5541
+ const isLastAttempt = attempt === attempts - 1;
5542
+ const budgetSpent = Date.now() - start >= maxTotalMs;
5543
+ if (isLastAttempt || budgetSpent) break;
5544
+ const sleepMs = Math.min(
5545
+ backoffForAttempt(attempt),
5546
+ maxTotalMs - (Date.now() - start)
5547
+ );
5548
+ if (sleepMs <= 0) break;
5549
+ await delay(sleepMs);
5550
+ }
5551
+ const cause = lastReason ? describeReason(lastReason) : "no attempts made";
5552
+ throw new Error(`Fetch failed after ${made} attempts: ${cause}`);
5553
+ }
5554
+
5555
+ // src/runtime/resolve-bundle.ts
5422
5556
  var ARCHIVE_ENTRY = "flow.mjs";
5423
- var FETCH_TIMEOUT_MS = 3e4;
5424
5557
  function getDefaultWritePath() {
5425
5558
  if (existsSync2("/app/flow")) return "/app/flow/flow.mjs";
5426
5559
  return "/tmp/walkeros-flow.mjs";
@@ -5445,9 +5578,7 @@ function writeBundleToDisk(writePath, content) {
5445
5578
  writeFileSync2(writePath, content, "utf-8");
5446
5579
  }
5447
5580
  async function fetchOk(url) {
5448
- const response = await fetch(url, {
5449
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
5450
- });
5581
+ const response = await fetchWithRetry(url);
5451
5582
  if (!response.ok) {
5452
5583
  throw new Error(
5453
5584
  `Failed to fetch bundle from ${url}: ${response.status} ${response.statusText}`
@@ -5568,10 +5699,7 @@ async function fetchConfig(options) {
5568
5699
  options.token,
5569
5700
  options.lastEtag ? { "If-None-Match": options.lastEtag } : void 0
5570
5701
  );
5571
- const response = await fetch(url, {
5572
- headers,
5573
- signal: AbortSignal.timeout(3e4)
5574
- });
5702
+ const response = await fetchWithRetry(url, { init: { headers } });
5575
5703
  if (response.status === 304) {
5576
5704
  return { changed: false };
5577
5705
  }
@@ -6113,8 +6241,11 @@ var SecretsHttpError = class extends Error {
6113
6241
  async function fetchSecrets(options) {
6114
6242
  const { appUrl, token, projectId, flowId } = options;
6115
6243
  const url = `${appUrl}/api/projects/${encodeURIComponent(projectId)}/flows/${encodeURIComponent(flowId)}/secrets/values`;
6116
- const res = await fetch(url, {
6117
- headers: mergeAuthHeaders(token, { "Content-Type": "application/json" })
6244
+ const res = await fetchWithRetry(url, {
6245
+ maxTotalMs: 2e4,
6246
+ init: {
6247
+ headers: mergeAuthHeaders(token, { "Content-Type": "application/json" })
6248
+ }
6118
6249
  });
6119
6250
  await throwIfRunnerAuthFailure(res);
6120
6251
  if (!res.ok) {
@@ -6129,13 +6260,21 @@ init_cache();
6129
6260
  async function runPipeline(options) {
6130
6261
  const { bundlePath, port, logger, loggerConfig, api } = options;
6131
6262
  let configVersion;
6263
+ const configFrozen = readConfigFrozen();
6132
6264
  if (api) {
6133
6265
  await injectSecrets(api, logger);
6134
6266
  }
6135
6267
  logger.info(`walkeros/flow v${VERSION}`);
6136
6268
  logger.info(`Instance: ${getInstanceId()}`);
6269
+ if (configFrozen) {
6270
+ logger.info("Config frozen: hot-swap and heartbeat disabled");
6271
+ }
6137
6272
  const healthServer = await createHealthServer(port, logger);
6138
- const telemetryObservers = buildTelemetryObservers(api?.flowId ?? "flow");
6273
+ const observeLevel = readObserveLevel(logger);
6274
+ const telemetryObservers = buildTelemetryObservers(
6275
+ api?.flowId ?? "flow",
6276
+ observeLevel
6277
+ );
6139
6278
  const runtimeConfig = { port };
6140
6279
  let handle;
6141
6280
  try {
@@ -6164,20 +6303,24 @@ async function runPipeline(options) {
6164
6303
  const ingestToken = process.env.WALKEROS_INGEST_TOKEN;
6165
6304
  const deploymentId = process.env.WALKEROS_DEPLOYMENT_ID;
6166
6305
  if (observerBase && ingestToken && deploymentId) {
6167
- tracePoller = createTracePoller(
6168
- {
6169
- url: `${observerBase}/trace/${deploymentId}`,
6170
- token: ingestToken,
6171
- intervalMs: 15e3
6172
- },
6173
- logger
6174
- );
6175
- tracePoller.start();
6176
- logger.info("Trace poller: active (every 15s)");
6306
+ if (observeLevel === "trace") {
6307
+ logger.info("Trace poller: skipped (observe level is trace)");
6308
+ } else {
6309
+ tracePoller = createTracePoller(
6310
+ {
6311
+ url: `${observerBase}/trace/${deploymentId}`,
6312
+ token: ingestToken,
6313
+ intervalMs: 15e3
6314
+ },
6315
+ logger
6316
+ );
6317
+ tracePoller.start();
6318
+ logger.info("Trace poller: active (every 15s)");
6319
+ }
6177
6320
  }
6178
6321
  let currentBundleCleanup;
6179
6322
  let currentConfigPath;
6180
- if (api) {
6323
+ if (api && !configFrozen) {
6181
6324
  heartbeat = createHeartbeat(
6182
6325
  {
6183
6326
  appUrl: api.appUrl,
@@ -6294,17 +6437,39 @@ async function runPipeline(options) {
6294
6437
  await new Promise(() => {
6295
6438
  });
6296
6439
  }
6297
- function buildTelemetryObservers(flowId) {
6440
+ var OBSERVE_LEVELS = [
6441
+ "off",
6442
+ "standard",
6443
+ "trace"
6444
+ ];
6445
+ function readConfigFrozen() {
6446
+ const raw = process.env.WALKEROS_CONFIG_FROZEN;
6447
+ return raw === "1" || raw === "true";
6448
+ }
6449
+ function readObserveLevel(logger) {
6450
+ const raw = process.env.WALKEROS_OBSERVE_LEVEL;
6451
+ if (raw === void 0 || raw === "") return void 0;
6452
+ const level = OBSERVE_LEVELS.find((candidate) => candidate === raw);
6453
+ if (!level) {
6454
+ logger.warn(
6455
+ `Ignoring invalid WALKEROS_OBSERVE_LEVEL "${raw}" (expected off, standard, or trace)`
6456
+ );
6457
+ return void 0;
6458
+ }
6459
+ return level;
6460
+ }
6461
+ function buildTelemetryObservers(flowId, observeLevel) {
6298
6462
  const base = process.env.WALKEROS_OBSERVER_URL;
6299
6463
  const token = process.env.WALKEROS_INGEST_TOKEN;
6300
6464
  const deploymentId = process.env.WALKEROS_DEPLOYMENT_ID;
6301
6465
  if (!base || !token || !deploymentId) return void 0;
6302
6466
  const url = `${base}/ingest/${deploymentId}`;
6303
6467
  const emit = createBatchedPoster({ url, token });
6468
+ const observe = observeLevel !== void 0 ? { level: observeLevel } : void 0;
6304
6469
  return [
6305
6470
  createTelemetryObserver(
6306
6471
  emit,
6307
- () => resolveTelemetryOptions({ flowId, traceUntil: getTraceUntil() })
6472
+ () => resolveTelemetryOptions({ flowId, observe, traceUntil: getTraceUntil() })
6308
6473
  )
6309
6474
  ];
6310
6475
  }
@@ -7235,7 +7400,7 @@ function validateMapping(input) {
7235
7400
  // src/commands/validate/validators/entry.ts
7236
7401
  import Ajv from "ajv";
7237
7402
  import { fetchPackageSchema } from "@walkeros/core";
7238
- var CLIENT_HEADER = "walkeros-cli/4.2.0";
7403
+ var CLIENT_HEADER = "walkeros-cli/4.2.1-next-1781526381392";
7239
7404
  var SECTIONS = ["destinations", "sources", "transformers"];
7240
7405
  function resolveEntry(path20, flowConfig) {
7241
7406
  const flows = flowConfig.flows;
@@ -7800,8 +7965,8 @@ import createClient from "openapi-fetch";
7800
7965
  init_config_file();
7801
7966
  import { createHash } from "crypto";
7802
7967
  import semver4 from "semver";
7803
- var bakedContractVersion = true ? "1.1.0" : PLACEHOLDER;
7804
- var bakedContractHash = true ? "847253b928a84dcdfc448e296418a5e48fa97b95f4a655e47e400d59d8e7f3cd" : "";
7968
+ var bakedContractVersion = true ? "2.0.0" : PLACEHOLDER;
7969
+ var bakedContractHash = true ? "b9ac141c5d759debbb52b838669daabec64659c3b79038dee69e5488b3f31dea" : "";
7805
7970
  function isRecord2(value) {
7806
7971
  return value !== null && typeof value === "object" && !Array.isArray(value);
7807
7972
  }
@@ -7932,9 +8097,13 @@ function createApiClient() {
7932
8097
  }
7933
8098
 
7934
8099
  // src/core/api-error.ts
8100
+ var EXIT_RETRYABLE = 75;
7935
8101
  var ApiError = class extends Error {
7936
8102
  code;
7937
8103
  details;
8104
+ status;
8105
+ retryable;
8106
+ retryAfterSeconds;
7938
8107
  // Populated only for CLIENT_OUTDATED responses (HTTP 426).
7939
8108
  minVersion;
7940
8109
  clientVersion;
@@ -7946,6 +8115,9 @@ var ApiError = class extends Error {
7946
8115
  this.name = "ApiError";
7947
8116
  this.code = options?.code;
7948
8117
  this.details = options?.details;
8118
+ this.status = options?.status;
8119
+ this.retryable = options?.retryable;
8120
+ this.retryAfterSeconds = options?.retryAfterSeconds;
7949
8121
  this.minVersion = options?.minVersion;
7950
8122
  this.clientVersion = options?.clientVersion;
7951
8123
  this.client = options?.client;
@@ -7953,25 +8125,62 @@ var ApiError = class extends Error {
7953
8125
  this.docs = options?.docs;
7954
8126
  }
7955
8127
  };
7956
- function throwApiError(error, fallbackMessage) {
7957
- if (error && typeof error === "object" && "error" in error && typeof error.error === "object") {
7958
- const inner = error.error;
7959
- const message = inner.message || fallbackMessage;
7960
- const code = inner.code;
7961
- const details = inner.details?.errors;
7962
- const options = { code, details };
7963
- if (code === "CLIENT_OUTDATED") {
7964
- options.minVersion = inner.minVersion;
7965
- options.clientVersion = inner.clientVersion;
7966
- options.client = inner.client;
7967
- options.upgrade = inner.upgrade;
7968
- options.docs = inner.docs;
7969
- }
7970
- throw new ApiError(message, options);
8128
+ function extractApiErrorOptions(error, fallbackMessage) {
8129
+ if (!error || typeof error !== "object" || !("error" in error) || typeof error.error !== "object") {
8130
+ return null;
7971
8131
  }
8132
+ const inner = error.error;
8133
+ const message = inner.message || fallbackMessage;
8134
+ const code = inner.code;
8135
+ const details = inner.details?.errors;
8136
+ const options = { code, details };
8137
+ if (code === "CLIENT_OUTDATED") {
8138
+ options.minVersion = inner.minVersion;
8139
+ options.clientVersion = inner.clientVersion;
8140
+ options.client = inner.client;
8141
+ options.upgrade = inner.upgrade;
8142
+ options.docs = inner.docs;
8143
+ }
8144
+ return { message, options };
8145
+ }
8146
+ function throwApiError(error, fallbackMessage) {
8147
+ const extracted = extractApiErrorOptions(error, fallbackMessage);
8148
+ if (extracted) throw new ApiError(extracted.message, extracted.options);
7972
8149
  throw new ApiError(fallbackMessage);
7973
8150
  }
8151
+ function parseRetryAfter(value) {
8152
+ if (!value) return void 0;
8153
+ const trimmed = value.trim();
8154
+ if (/^\d+$/.test(trimmed)) return parseInt(trimmed, 10);
8155
+ const when = Date.parse(trimmed);
8156
+ if (Number.isNaN(when)) return void 0;
8157
+ return Math.max(0, Math.round((when - Date.now()) / 1e3));
8158
+ }
8159
+ function throwApiResponseError(response, body, fallbackMessage) {
8160
+ const status = response.status;
8161
+ const retryAfterSeconds = parseRetryAfter(
8162
+ response.headers.get("retry-after")
8163
+ );
8164
+ const retryable = status === 429 || status === 503 && retryAfterSeconds !== void 0;
8165
+ const extracted = extractApiErrorOptions(body, fallbackMessage);
8166
+ const message = extracted?.message ?? fallbackMessage;
8167
+ const options = {
8168
+ ...extracted?.options ?? {},
8169
+ status,
8170
+ retryable,
8171
+ retryAfterSeconds
8172
+ };
8173
+ throw new ApiError(message, options);
8174
+ }
8175
+ function machineReadableErrorLine(err) {
8176
+ const code = err instanceof ApiError ? err.code ?? "UNKNOWN" : "UNKNOWN";
8177
+ const retryable = err instanceof ApiError ? err.retryable === true : false;
8178
+ const retryAfter = err instanceof ApiError && err.retryAfterSeconds !== void 0 ? ` retryAfter=${err.retryAfterSeconds}` : "";
8179
+ const message = err instanceof Error ? err.message : String(err);
8180
+ return `error: code=${code} retryable=${retryable}${retryAfter} message=${message}`;
8181
+ }
7974
8182
  function handleCliError(err) {
8183
+ console.error(machineReadableErrorLine(err));
7975
8184
  if (err instanceof ApiError && err.code === "CLIENT_OUTDATED") {
7976
8185
  console.error(`
7977
8186
  ${err.message}
@@ -7981,8 +8190,12 @@ ${err.message}
7981
8190
  `);
7982
8191
  process.exit(2);
7983
8192
  }
7984
- const message = err instanceof Error ? err.message : String(err);
7985
- console.error(message);
8193
+ if (err instanceof ApiError && err.retryable) {
8194
+ const hint = err.retryAfterSeconds !== void 0 ? ` Retry after ${err.retryAfterSeconds}s.` : " This is temporary, retry shortly.";
8195
+ console.error(`${err.message}${hint}`);
8196
+ process.exit(EXIT_RETRYABLE);
8197
+ }
8198
+ console.error(err instanceof Error ? err.message : String(err));
7986
8199
  process.exit(1);
7987
8200
  }
7988
8201
 
@@ -8278,6 +8491,7 @@ async function readFlowStdin() {
8278
8491
  }
8279
8492
 
8280
8493
  // src/commands/deploy/index.ts
8494
+ import { randomUUID } from "crypto";
8281
8495
  init_auth();
8282
8496
  init_http();
8283
8497
  init_sse();
@@ -8308,8 +8522,9 @@ async function getAvailableFlowNames(options) {
8308
8522
  const settings = flow.settings;
8309
8523
  return settings?.map((c) => c.name) ?? [];
8310
8524
  }
8525
+ var DEFAULT_DEPLOY_WAIT_MS = 12 * 60 * 1e3;
8311
8526
  async function streamDeploymentStatus(projectId, deploymentId, options) {
8312
- const timeoutMs = options.timeout ?? 12e4;
8527
+ const timeoutMs = options.timeout ?? DEFAULT_DEPLOY_WAIT_MS;
8313
8528
  const response = await apiFetch(
8314
8529
  `/api/projects/${projectId}/deployments/${deploymentId}/stream`,
8315
8530
  {
@@ -8368,7 +8583,10 @@ async function deploy(options) {
8368
8583
  }
8369
8584
  const { data, error } = await client.POST(
8370
8585
  "/api/projects/{projectId}/flows/{flowId}/deploy",
8371
- { params: { path: { projectId, flowId: options.flowId } } }
8586
+ {
8587
+ params: { path: { projectId, flowId: options.flowId } },
8588
+ headers: { "Idempotency-Key": randomUUID() }
8589
+ }
8372
8590
  );
8373
8591
  if (error) {
8374
8592
  try {
@@ -8398,13 +8616,38 @@ Available: ${names.join(", ")}`,
8398
8616
  }
8399
8617
  async function deploySettings(options) {
8400
8618
  const { flowId, projectId, settingsId } = options;
8401
- const response = await apiFetch(
8619
+ const triggerDeploy = () => apiFetch(
8402
8620
  `/api/projects/${projectId}/flows/${flowId}/settings/${settingsId}/deploy`,
8403
- { method: "POST" }
8621
+ { method: "POST", headers: { "Idempotency-Key": randomUUID() } }
8404
8622
  );
8623
+ let response = await triggerDeploy();
8624
+ if (!response.ok && options.wait) {
8625
+ let retryBody = {};
8626
+ try {
8627
+ retryBody = await response.clone().json();
8628
+ } catch {
8629
+ retryBody = {};
8630
+ }
8631
+ try {
8632
+ throwApiResponseError(
8633
+ response,
8634
+ retryBody,
8635
+ `Deploy failed (${response.status})`
8636
+ );
8637
+ } catch (e) {
8638
+ if (e instanceof ApiError && e.retryable) {
8639
+ const waitSeconds = Math.min(e.retryAfterSeconds ?? 5, 60);
8640
+ options.onStatus?.("rate_limited", `retrying in ${waitSeconds}s`);
8641
+ await new Promise((resolve5) => setTimeout(resolve5, waitSeconds * 1e3));
8642
+ response = await triggerDeploy();
8643
+ } else {
8644
+ throw e;
8645
+ }
8646
+ }
8647
+ }
8405
8648
  if (!response.ok) {
8406
8649
  const body = await response.json().catch(() => ({}));
8407
- throwApiError(body, `Deploy failed (${response.status})`);
8650
+ throwApiResponseError(response, body, `Deploy failed (${response.status})`);
8408
8651
  }
8409
8652
  const data = await response.json();
8410
8653
  if (!options.wait) return data;
@@ -8428,7 +8671,7 @@ async function getDeployment(options) {
8428
8671
  );
8429
8672
  if (!response.ok) {
8430
8673
  const body = await response.json().catch(() => ({}));
8431
- throwApiError(body, "Failed to get deployment");
8674
+ throwApiResponseError(response, body, "Failed to get deployment");
8432
8675
  }
8433
8676
  return response.json();
8434
8677
  }
@@ -8441,16 +8684,20 @@ async function getDeployment(options) {
8441
8684
  return data;
8442
8685
  }
8443
8686
  var statusLabels = {
8444
- bundling: "Building bundle...",
8445
- "bundling:building": "Building bundle...",
8446
- "bundling:publishing": "Publishing to web...",
8447
- deploying: "Deploying container...",
8687
+ "deploying:building": "Building bundle...",
8688
+ "deploying:publishing": "Publishing to web...",
8448
8689
  "deploying:provisioning": "Provisioning container...",
8449
8690
  "deploying:starting": "Starting container...",
8691
+ deploying: "Deploying...",
8450
8692
  active: "Container is live",
8451
8693
  published: "Published",
8694
+ stopped: "Stopped",
8452
8695
  failed: "Deployment failed"
8453
8696
  };
8697
+ function renderStatusLabel(status, substatus) {
8698
+ const key = substatus ? `${status}:${substatus}` : status;
8699
+ return statusLabels[key] || statusLabels[status] || `Status: ${status}`;
8700
+ }
8454
8701
  async function deployCommand(flowId, options) {
8455
8702
  const log = createCLILogger(options);
8456
8703
  const timeoutMs = options.timeout ? parseInt(options.timeout, 10) * 1e3 : void 0;
@@ -8462,10 +8709,7 @@ async function deployCommand(flowId, options) {
8462
8709
  wait: options.wait !== false,
8463
8710
  timeout: timeoutMs,
8464
8711
  onStatus: options.json ? void 0 : (status, substatus) => {
8465
- const key = substatus ? `${status}:${substatus}` : status;
8466
- log.info(
8467
- statusLabels[key] || statusLabels[status] || `Status: ${status}`
8468
- );
8712
+ log.info(renderStatusLabel(status, substatus));
8469
8713
  }
8470
8714
  });
8471
8715
  if (options.json) {
@@ -8480,7 +8724,7 @@ async function deployCommand(flowId, options) {
8480
8724
  } else if (r.status === "failed") {
8481
8725
  log.error(`Failed: ${r.errorMessage || "Unknown error"}`);
8482
8726
  process.exit(1);
8483
- } else if (r.status === "bundling") {
8727
+ } else if (r.status === "deploying") {
8484
8728
  log.info(`Deployment started: ${r.deploymentId} (${r.type})`);
8485
8729
  } else {
8486
8730
  log.info(`Status: ${r.status}`);
@@ -8708,20 +8952,16 @@ async function createDeployCommand(config, options) {
8708
8952
  log.info(`Deployment created: ${result.id}`);
8709
8953
  log.info(` Slug: ${result.slug}`);
8710
8954
  log.info(` Type: ${result.type}`);
8711
- if (result.deployToken) {
8712
- log.info(` Token: ${result.deployToken}`);
8713
- log.warn(" Save this token \u2014 it will not be shown again.");
8714
- }
8715
8955
  log.info("");
8716
8956
  log.info("Run locally:");
8717
8957
  log.info(
8718
8958
  ` walkeros run ${isRemoteFlow ? "flow.json" : config} --deploy ${result.id}`
8719
8959
  );
8720
8960
  log.info("");
8721
- log.info("Docker:");
8722
- log.info(
8723
- ` docker run -e WALKEROS_DEPLOY_TOKEN=${result.deployToken ?? "<token>"} \\`
8724
- );
8961
+ log.info("Create a deploy token for this flow in the app");
8962
+ log.info("(Settings, Self-hosted deploy token) and set it as");
8963
+ log.info("WALKEROS_DEPLOY_TOKEN, then run with Docker:");
8964
+ log.info(" docker run -e WALKEROS_DEPLOY_TOKEN \\");
8725
8965
  log.info(" -e WALKEROS_APP_URL=https://app.walkeros.io \\");
8726
8966
  log.info(" walkeros/flow:latest");
8727
8967
  } catch (err) {
@@ -8807,14 +9047,14 @@ init_config_file();
8807
9047
 
8808
9048
  // src/telemetry/install-id.ts
8809
9049
  init_config_file();
8810
- import { randomUUID } from "crypto";
9050
+ import { randomUUID as randomUUID2 } from "crypto";
8811
9051
  function getInstallationId() {
8812
9052
  return readConfig()?.installationId;
8813
9053
  }
8814
9054
  function createInstallationId() {
8815
9055
  const existing = readConfig()?.installationId;
8816
9056
  if (existing) return existing;
8817
- const id = randomUUID();
9057
+ const id = randomUUID2();
8818
9058
  writeTelemetryOnlyConfig({ installationId: id });
8819
9059
  return id;
8820
9060
  }
@@ -8875,6 +9115,8 @@ function telemetryDisableCommand() {
8875
9115
 
8876
9116
  // src/index.ts
8877
9117
  init_bundle();
9118
+ init_bundler();
9119
+ init_config_classifier();
8878
9120
 
8879
9121
  // src/commands/bundle/validate-structure.ts
8880
9122
  init_structural_validators();
@@ -9560,12 +9802,15 @@ export {
9560
9802
  apiFetch,
9561
9803
  bakedContractHash,
9562
9804
  bakedContractVersion,
9805
+ buildDataPayload,
9563
9806
  bundle,
9564
9807
  bundleCommand,
9565
9808
  canonicalContractHash,
9809
+ classifyStepProperties,
9566
9810
  clientContextHeaders,
9567
9811
  compareContract,
9568
9812
  compareOutput,
9813
+ containsCodeMarkers,
9569
9814
  createApiClient,
9570
9815
  createDeployCommand,
9571
9816
  createDeployment,