jetic-cli 0.2.7 → 0.2.8

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
@@ -272670,11 +272670,13 @@ var require_workflow_simulator = __commonJS({
272670
272670
  "use strict";
272671
272671
  init_cjs_shims();
272672
272672
  Object.defineProperty(exports2, "__esModule", { value: true });
272673
- exports2.WorkflowSimulator = void 0;
272673
+ exports2.WorkflowSimulator = exports2.CONNECTION_ERROR_CODES = void 0;
272674
+ exports2.networkErrorCode = networkErrorCode2;
272675
+ exports2.probeTarget = probeTarget2;
272674
272676
  var memory_1 = require_dist12();
272675
272677
  var faker_1 = require("@faker-js/faker");
272676
272678
  var conditions_1 = require_conditions();
272677
- var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
272679
+ exports2.CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
272678
272680
  "ECONNREFUSED",
272679
272681
  "ECONNRESET",
272680
272682
  "ENOTFOUND",
@@ -272685,7 +272687,7 @@ var require_workflow_simulator = __commonJS({
272685
272687
  "UND_ERR_CONNECT_TIMEOUT",
272686
272688
  "UND_ERR_SOCKET"
272687
272689
  ]);
272688
- function networkErrorCode(error) {
272690
+ function networkErrorCode2(error) {
272689
272691
  const raw = error?.cause?.code ?? error?.cause?.errno ?? error?.code ?? error?.errno;
272690
272692
  return typeof raw === "string" ? raw : void 0;
272691
272693
  }
@@ -272723,7 +272725,7 @@ var require_workflow_simulator = __commonJS({
272723
272725
  this.requestTimeoutMs = options?.requestTimeoutMs ?? 3e4;
272724
272726
  const startTime = Date.now();
272725
272727
  const preflightMode = options?.preflight ?? "off";
272726
- const preflight = preflightMode !== "off" && workflow.steps.length > 0 ? await this.runPreflight(this.environment.baseUrl, options?.preflightTimeoutMs ?? 3e3) : void 0;
272728
+ const preflight = preflightMode !== "off" && workflow.steps.length > 0 ? await probeTarget2(this.environment.baseUrl, options?.preflightTimeoutMs ?? 3e3) : void 0;
272727
272729
  if (preflight && preflightMode === "abort" && preflight.verdict === "unreachable") {
272728
272730
  preflight.aborted = true;
272729
272731
  return {
@@ -272783,6 +272785,21 @@ var require_workflow_simulator = __commonJS({
272783
272785
  async runSteps(workflow, options, state, lineage) {
272784
272786
  const baseUrl = this.environment.baseUrl;
272785
272787
  const resolve2 = (value) => this.resolveTemplateString(value, false);
272788
+ const record = async (result) => {
272789
+ state.results.push(result);
272790
+ if (!options?.onStepResult)
272791
+ return;
272792
+ try {
272793
+ await options.onStepResult(result, {
272794
+ totalSteps: workflow.steps.length,
272795
+ executedSteps: state.results.length,
272796
+ passedSteps: state.results.filter((r) => r.passed).length,
272797
+ failedSteps: state.results.filter((r) => !r.passed).length,
272798
+ skippedSteps: state.results.filter((r) => r.skipped).length
272799
+ });
272800
+ } catch {
272801
+ }
272802
+ };
272786
272803
  for (let i = 0; i < workflow.steps.length; i++) {
272787
272804
  const step = workflow.steps[i];
272788
272805
  const stepStartTime = Date.now();
@@ -272790,7 +272807,7 @@ var require_workflow_simulator = __commonJS({
272790
272807
  const kind = step.condition === void 0 ? void 0 : (0, conditions_1.conditionKind)(step.condition);
272791
272808
  if (kind === "invalid") {
272792
272809
  const reason = (0, conditions_1.describeInvalidCondition)(step.condition);
272793
- state.results.push({
272810
+ await record({
272794
272811
  stepIndex: i + 1,
272795
272812
  name: stepName,
272796
272813
  method: step.method,
@@ -272799,6 +272816,8 @@ var require_workflow_simulator = __commonJS({
272799
272816
  expectedStatus: step.expectStatus ?? 200,
272800
272817
  actualStatus: 0,
272801
272818
  durationMs: Date.now() - stepStartTime,
272819
+ startedAt: new Date(stepStartTime).toISOString(),
272820
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
272802
272821
  passed: false,
272803
272822
  skipped: false,
272804
272823
  retryCount: 0,
@@ -272815,7 +272834,7 @@ var require_workflow_simulator = __commonJS({
272815
272834
  if (kind === "legacy") {
272816
272835
  const condResult = await (0, conditions_1.evaluateLegacyCondition)(step.condition, resolve2);
272817
272836
  if (!condResult.matched) {
272818
- state.results.push({
272837
+ await record({
272819
272838
  stepIndex: i + 1,
272820
272839
  name: stepName,
272821
272840
  method: step.method,
@@ -272824,6 +272843,8 @@ var require_workflow_simulator = __commonJS({
272824
272843
  expectedStatus: step.expectStatus ?? 200,
272825
272844
  actualStatus: 0,
272826
272845
  durationMs: Date.now() - stepStartTime,
272846
+ startedAt: new Date(stepStartTime).toISOString(),
272847
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
272827
272848
  passed: true,
272828
272849
  skipped: true,
272829
272850
  retryCount: 0,
@@ -272852,7 +272873,7 @@ var require_workflow_simulator = __commonJS({
272852
272873
  resolvedPath = await this.resolvePathParams(step.path, finalBody);
272853
272874
  fullUrl = `${baseUrl.replace(/\/$/, "")}${resolvedPath}`;
272854
272875
  } catch (err) {
272855
- state.results.push({
272876
+ await record({
272856
272877
  stepIndex: i + 1,
272857
272878
  name: stepName,
272858
272879
  method: step.method,
@@ -272861,6 +272882,8 @@ var require_workflow_simulator = __commonJS({
272861
272882
  expectedStatus: step.expectStatus ?? 200,
272862
272883
  actualStatus: 0,
272863
272884
  durationMs: Date.now() - stepStartTime,
272885
+ startedAt: new Date(stepStartTime).toISOString(),
272886
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
272864
272887
  passed: false,
272865
272888
  skipped: false,
272866
272889
  retryCount: 0,
@@ -272875,11 +272898,42 @@ var require_workflow_simulator = __commonJS({
272875
272898
  return "stop";
272876
272899
  }
272877
272900
  const expectedStatus = step.expectStatus ?? 200;
272901
+ let requestUrl = fullUrl;
272902
+ {
272903
+ const upperMethod = step.method.toUpperCase();
272904
+ const bodyAsQuery = ["GET", "HEAD"].includes(upperMethod) && Object.keys(finalBody).length > 0;
272905
+ if (Object.keys(resolvedQuery).length > 0 || bodyAsQuery) {
272906
+ const params = new URLSearchParams();
272907
+ const append = (source) => {
272908
+ for (const [k, v] of Object.entries(source)) {
272909
+ if (v === void 0 || v === null)
272910
+ continue;
272911
+ if (Array.isArray(v)) {
272912
+ for (const item of v)
272913
+ params.append(k, typeof item === "object" ? JSON.stringify(item) : String(item));
272914
+ } else {
272915
+ params.append(k, typeof v === "object" ? JSON.stringify(v) : String(v));
272916
+ }
272917
+ }
272918
+ };
272919
+ append(resolvedQuery);
272920
+ if (bodyAsQuery)
272921
+ append(finalBody);
272922
+ const qs = params.toString();
272923
+ if (qs)
272924
+ requestUrl = `${fullUrl}${fullUrl.includes("?") ? "&" : "?"}${qs}`;
272925
+ }
272926
+ }
272927
+ const actualRequestHeaders = {
272928
+ "Content-Type": "application/json",
272929
+ ...injectedHeaders
272930
+ };
272878
272931
  const maxAttempts = Math.max(1, step.retry?.times ?? workflow.retry?.times ?? 1);
272879
272932
  const retryDelayMs = step.retry?.delayMs ?? workflow.retry?.delayMs ?? 0;
272880
272933
  let attempts = 0;
272881
272934
  let responseStatus = 0;
272882
272935
  let responseBody = null;
272936
+ let responseHeaders;
272883
272937
  let stepPassed = false;
272884
272938
  let errorMsg;
272885
272939
  let connectionCode;
@@ -272890,6 +272944,7 @@ var require_workflow_simulator = __commonJS({
272890
272944
  }
272891
272945
  responseStatus = 0;
272892
272946
  responseBody = null;
272947
+ responseHeaders = void 0;
272893
272948
  errorMsg = void 0;
272894
272949
  connectionCode = void 0;
272895
272950
  const timeoutMs = step.timeoutMs ?? this.requestTimeoutMs;
@@ -272897,7 +272952,7 @@ var require_workflow_simulator = __commonJS({
272897
272952
  const upperMethod = step.method.toUpperCase();
272898
272953
  const fetchOptions = {
272899
272954
  method: upperMethod,
272900
- headers: { "Content-Type": "application/json", ...injectedHeaders }
272955
+ headers: actualRequestHeaders
272901
272956
  };
272902
272957
  if (!["GET", "HEAD"].includes(upperMethod) && Object.keys(finalBody).length > 0) {
272903
272958
  fetchOptions.body = JSON.stringify(finalBody);
@@ -272905,31 +272960,9 @@ var require_workflow_simulator = __commonJS({
272905
272960
  if (timeoutMs > 0) {
272906
272961
  fetchOptions.signal = AbortSignal.timeout(timeoutMs);
272907
272962
  }
272908
- let finalUrl = fullUrl;
272909
- const bodyAsQuery = ["GET", "HEAD"].includes(upperMethod) && Object.keys(finalBody).length > 0;
272910
- if (Object.keys(resolvedQuery).length > 0 || bodyAsQuery) {
272911
- const params = new URLSearchParams();
272912
- const append = (source) => {
272913
- for (const [k, v] of Object.entries(source)) {
272914
- if (v === void 0 || v === null)
272915
- continue;
272916
- if (Array.isArray(v)) {
272917
- for (const item of v)
272918
- params.append(k, typeof item === "object" ? JSON.stringify(item) : String(item));
272919
- } else {
272920
- params.append(k, typeof v === "object" ? JSON.stringify(v) : String(v));
272921
- }
272922
- }
272923
- };
272924
- append(resolvedQuery);
272925
- if (bodyAsQuery)
272926
- append(finalBody);
272927
- const qs = params.toString();
272928
- if (qs)
272929
- finalUrl = `${fullUrl}${fullUrl.includes("?") ? "&" : "?"}${qs}`;
272930
- }
272931
- const resp = await fetch(finalUrl, fetchOptions);
272963
+ const resp = await fetch(requestUrl, fetchOptions);
272932
272964
  responseStatus = resp.status;
272965
+ responseHeaders = Object.fromEntries(resp.headers.entries());
272933
272966
  const ct = resp.headers.get("content-type") || "";
272934
272967
  if (ct.includes("application/json")) {
272935
272968
  try {
@@ -272946,7 +272979,7 @@ var require_workflow_simulator = __commonJS({
272946
272979
  break;
272947
272980
  } catch (err) {
272948
272981
  stepPassed = false;
272949
- connectionCode = networkErrorCode(err);
272982
+ connectionCode = networkErrorCode2(err);
272950
272983
  if (err?.name === "TimeoutError" || connectionCode === "ETIMEDOUT") {
272951
272984
  errorMsg = `Request timed out after ${timeoutMs}ms (${step.method} ${resolvedPath}).`;
272952
272985
  } else if (connectionCode === "ECONNREFUSED") {
@@ -272964,26 +272997,30 @@ var require_workflow_simulator = __commonJS({
272964
272997
  method: step.method,
272965
272998
  path: step.path,
272966
272999
  targetUrl: fullUrl,
273000
+ resolvedUrl: requestUrl,
272967
273001
  expectedStatus,
272968
273002
  actualStatus: responseStatus,
272969
273003
  durationMs: Date.now() - stepStartTime,
273004
+ startedAt: new Date(stepStartTime).toISOString(),
273005
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
272970
273006
  passed: stepPassed,
272971
273007
  skipped: false,
272972
273008
  retryCount: attempts - 1,
272973
- requestHeaders: injectedHeaders,
273009
+ requestHeaders: actualRequestHeaders,
272974
273010
  requestBody: finalBody,
272975
273011
  ...Object.keys(resolvedQuery).length > 0 ? { requestQuery: resolvedQuery } : {},
272976
273012
  responseBody,
273013
+ ...responseHeaders ? { responseHeaders } : {},
272977
273014
  capturedMemory: stepPassed ? [...inputCaptured, ...responseCaptured] : [...inputCaptured],
272978
273015
  ...stepPassed ? {} : {
272979
273016
  failureHandling: onFailure === "continue" ? "continued" : "aborted",
272980
273017
  error: errorMsg
272981
273018
  }
272982
273019
  };
272983
- state.results.push(result);
273020
+ await record(result);
272984
273021
  if (!stepPassed) {
272985
273022
  state.allPassed = false;
272986
- if (connectionCode && CONNECTION_ERROR_CODES.has(connectionCode)) {
273023
+ if (connectionCode && exports2.CONNECTION_ERROR_CODES.has(connectionCode)) {
272987
273024
  const remaining = workflow.steps.length - (i + 1);
272988
273025
  result.failureHandling = "aborted";
272989
273026
  result.error = `${result.error || "Connection failed"}; aborting early because the target is unreachable${remaining > 0 ? ` (${remaining} step(s) not executed)` : ""}.`;
@@ -273233,39 +273270,39 @@ var require_workflow_simulator = __commonJS({
273233
273270
  }
273234
273271
  return result;
273235
273272
  }
273236
- async runPreflight(baseUrl, timeoutMs) {
273237
- const probe = { url: baseUrl, verdict: "unreachable", message: "", aborted: false };
273238
- try {
273239
- const resp = await fetch(baseUrl, {
273240
- method: "GET",
273241
- redirect: "manual",
273242
- signal: AbortSignal.timeout(timeoutMs)
273243
- });
273244
- probe.status = resp.status;
273245
- if (resp.status >= 500) {
273246
- probe.verdict = "server-error";
273247
- probe.message = `Server is reachable at ${baseUrl} but responded HTTP ${resp.status} \u2014 the app is likely failing (database/init error). Check the server logs before simulating.`;
273248
- } else {
273249
- probe.verdict = "healthy";
273250
- probe.message = `Server reachable at ${baseUrl} (HTTP ${resp.status}).`;
273251
- }
273252
- } catch (err) {
273253
- const code = networkErrorCode(err);
273254
- probe.status = void 0;
273255
- if (err?.name === "TimeoutError" || code === "ETIMEDOUT") {
273256
- probe.message = `No response from ${baseUrl} within ${timeoutMs}ms \u2014 the server may be down or hanging.`;
273257
- } else if (code === "ECONNREFUSED") {
273258
- probe.message = `Connection refused at ${baseUrl} \u2014 nothing is listening there. Start the app (check its PORT/.env) and retry.`;
273259
- } else if (code === "ENOTFOUND" || code === "EAI_AGAIN") {
273260
- probe.message = `DNS lookup failed for ${baseUrl}${code ? ` (${code})` : ""} \u2014 check the host name.`;
273261
- } else {
273262
- probe.message = `Cannot reach ${baseUrl}${code ? ` (${code})` : ""}: ${err?.message || err}`;
273263
- }
273264
- }
273265
- return probe;
273266
- }
273267
273273
  };
273268
273274
  exports2.WorkflowSimulator = WorkflowSimulator2;
273275
+ async function probeTarget2(baseUrl, timeoutMs = 3e3) {
273276
+ const probe = { url: baseUrl, verdict: "unreachable", message: "", aborted: false };
273277
+ try {
273278
+ const resp = await fetch(baseUrl, {
273279
+ method: "GET",
273280
+ redirect: "manual",
273281
+ signal: AbortSignal.timeout(timeoutMs)
273282
+ });
273283
+ probe.status = resp.status;
273284
+ if (resp.status >= 500) {
273285
+ probe.verdict = "server-error";
273286
+ probe.message = `Server is reachable at ${baseUrl} but responded HTTP ${resp.status} \u2014 the app is likely failing (database/init error). Check the server logs before simulating.`;
273287
+ } else {
273288
+ probe.verdict = "healthy";
273289
+ probe.message = `Server reachable at ${baseUrl} (HTTP ${resp.status}).`;
273290
+ }
273291
+ } catch (err) {
273292
+ const code = networkErrorCode2(err);
273293
+ probe.status = void 0;
273294
+ if (err?.name === "TimeoutError" || code === "ETIMEDOUT") {
273295
+ probe.message = `No response from ${baseUrl} within ${timeoutMs}ms \u2014 the server may be down or hanging.`;
273296
+ } else if (code === "ECONNREFUSED") {
273297
+ probe.message = `Connection refused at ${baseUrl} \u2014 nothing is listening there. Start the app (check its PORT/.env) and retry.`;
273298
+ } else if (code === "ENOTFOUND" || code === "EAI_AGAIN") {
273299
+ probe.message = `DNS lookup failed for ${baseUrl}${code ? ` (${code})` : ""} \u2014 check the host name.`;
273300
+ } else {
273301
+ probe.message = `Cannot reach ${baseUrl}${code ? ` (${code})` : ""}: ${err?.message || err}`;
273302
+ }
273303
+ }
273304
+ return probe;
273305
+ }
273269
273306
  }
273270
273307
  });
273271
273308
 
@@ -273291,7 +273328,7 @@ var require_dist13 = __commonJS({
273291
273328
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
273292
273329
  };
273293
273330
  Object.defineProperty(exports2, "__esModule", { value: true });
273294
- exports2.WorkflowSimulator = exports2.ResponseValidator = exports2.DataGenerator = exports2.EndpointSimulator = void 0;
273331
+ exports2.CONNECTION_ERROR_CODES = exports2.networkErrorCode = exports2.probeTarget = exports2.WorkflowSimulator = exports2.ResponseValidator = exports2.DataGenerator = exports2.EndpointSimulator = void 0;
273295
273332
  var simulator_1 = require_simulator();
273296
273333
  Object.defineProperty(exports2, "EndpointSimulator", { enumerable: true, get: function() {
273297
273334
  return simulator_1.EndpointSimulator;
@@ -273308,6 +273345,15 @@ var require_dist13 = __commonJS({
273308
273345
  Object.defineProperty(exports2, "WorkflowSimulator", { enumerable: true, get: function() {
273309
273346
  return workflow_simulator_1.WorkflowSimulator;
273310
273347
  } });
273348
+ Object.defineProperty(exports2, "probeTarget", { enumerable: true, get: function() {
273349
+ return workflow_simulator_1.probeTarget;
273350
+ } });
273351
+ Object.defineProperty(exports2, "networkErrorCode", { enumerable: true, get: function() {
273352
+ return workflow_simulator_1.networkErrorCode;
273353
+ } });
273354
+ Object.defineProperty(exports2, "CONNECTION_ERROR_CODES", { enumerable: true, get: function() {
273355
+ return workflow_simulator_1.CONNECTION_ERROR_CODES;
273356
+ } });
273311
273357
  __exportStar(require_conditions(), exports2);
273312
273358
  }
273313
273359
  });
@@ -273506,14 +273552,19 @@ var require_run_store = __commonJS({
273506
273552
  };
273507
273553
  })();
273508
273554
  Object.defineProperty(exports2, "__esModule", { value: true });
273555
+ exports2.MAX_PERSISTED_BODY_CHARS = void 0;
273509
273556
  exports2.newRunId = newRunId;
273510
273557
  exports2.runsDir = runsDir;
273511
273558
  exports2.runDir = runDir;
273512
273559
  exports2.deriveRunStatus = deriveRunStatus;
273560
+ exports2.stepToProgress = stepToProgress;
273561
+ exports2.statsFromSteps = statsFromSteps;
273562
+ exports2.truncateResultForStorage = truncateResultForStorage;
273513
273563
  exports2.writeSimulationRun = writeSimulationRun;
273514
273564
  exports2.readSimulationRun = readSimulationRun;
273515
273565
  exports2.readRunStatus = readRunStatus;
273516
273566
  exports2.writeRunningStatus = writeRunningStatus;
273567
+ exports2.updateRunStatus = updateRunStatus;
273517
273568
  exports2.writeRunError = writeRunError;
273518
273569
  exports2.listRuns = listRuns;
273519
273570
  exports2.compactSimulationResult = compactSimulationResult;
@@ -273542,6 +273593,50 @@ var require_run_store = __commonJS({
273542
273593
  return "aborted";
273543
273594
  return "failed";
273544
273595
  }
273596
+ function stepToProgress(r) {
273597
+ return {
273598
+ index: r.stepIndex,
273599
+ name: r.name,
273600
+ method: r.method,
273601
+ path: r.path,
273602
+ status: r.skipped ? "skipped" : r.passed ? "passed" : "failed",
273603
+ expectedStatus: r.expectedStatus,
273604
+ ...r.skipped ? {} : { actualStatus: r.actualStatus },
273605
+ passed: r.passed,
273606
+ skipped: r.skipped,
273607
+ ...r.retryCount > 0 ? { retryCount: r.retryCount } : {},
273608
+ durationMs: r.durationMs,
273609
+ startedAt: r.startedAt,
273610
+ finishedAt: r.finishedAt,
273611
+ ...r.error ? { error: r.error } : {}
273612
+ };
273613
+ }
273614
+ function statsFromSteps(steps) {
273615
+ const completed = steps.filter((s) => s.status !== "pending");
273616
+ return {
273617
+ totalSteps: steps.length,
273618
+ executedSteps: completed.length,
273619
+ passedSteps: completed.filter((s) => s.status === "passed" || s.status === "skipped").length,
273620
+ failedSteps: completed.filter((s) => s.status === "failed").length,
273621
+ skippedSteps: completed.filter((s) => s.status === "skipped").length
273622
+ };
273623
+ }
273624
+ exports2.MAX_PERSISTED_BODY_CHARS = 16384;
273625
+ function truncateResultForStorage(result, maxChars = exports2.MAX_PERSISTED_BODY_CHARS) {
273626
+ const clone = JSON.parse(JSON.stringify(result));
273627
+ for (const step of clone.results) {
273628
+ if (step.responseBody === void 0 || step.responseBody === null)
273629
+ continue;
273630
+ const serialized = typeof step.responseBody === "string" ? step.responseBody : JSON.stringify(step.responseBody);
273631
+ if (serialized.length <= maxChars)
273632
+ continue;
273633
+ const removed = serialized.length - maxChars;
273634
+ step.responseBody = typeof step.responseBody === "string" ? `${step.responseBody.slice(0, maxChars)}
273635
+ \u2026[truncated: ${removed} more characters]` : { _truncated: true, preview: serialized.slice(0, maxChars), fullLengthChars: serialized.length };
273636
+ step.responseBodyTruncated = true;
273637
+ }
273638
+ return clone;
273639
+ }
273545
273640
  function writeSimulationRun(jeticDir, runId, result, meta) {
273546
273641
  const status = deriveRunStatus(result);
273547
273642
  const durationMs = result.stats?.totalTimeMs ?? 0;
@@ -273556,7 +273651,7 @@ var require_run_store = __commonJS({
273556
273651
  startedAt: meta.startedAt,
273557
273652
  finishedAt: meta.finishedAt,
273558
273653
  durationMs,
273559
- result
273654
+ result: truncateResultForStorage(result)
273560
273655
  };
273561
273656
  const rPath = reportPath(jeticDir, runId);
273562
273657
  (0, core_1.writeJsonSync)(rPath, report);
@@ -273578,6 +273673,7 @@ var require_run_store = __commonJS({
273578
273673
  failedSteps: result.stats?.failedSteps ?? 0,
273579
273674
  skippedSteps: result.results.filter((r) => r.skipped).length
273580
273675
  },
273676
+ steps: result.results.map(stepToProgress),
273581
273677
  reportPath: rPath
273582
273678
  };
273583
273679
  (0, core_1.writeJsonSync)(sPath, statusDoc);
@@ -273590,6 +273686,13 @@ var require_run_store = __commonJS({
273590
273686
  return (0, core_1.readJsonSync)(statusPath(jeticDir, runId));
273591
273687
  }
273592
273688
  function writeRunningStatus(jeticDir, runId, meta) {
273689
+ const steps = meta.steps?.map((s, i) => ({
273690
+ index: i + 1,
273691
+ name: s.name,
273692
+ method: s.method,
273693
+ path: s.path,
273694
+ status: "pending"
273695
+ }));
273593
273696
  const doc = {
273594
273697
  type: "simulation",
273595
273698
  runId,
@@ -273598,11 +273701,25 @@ var require_run_store = __commonJS({
273598
273701
  baseUrl: meta.baseUrl,
273599
273702
  status: "running",
273600
273703
  startedAt: meta.startedAt,
273601
- stats: { totalSteps: 0, executedSteps: 0, passedSteps: 0, failedSteps: 0, skippedSteps: 0 },
273704
+ stats: {
273705
+ totalSteps: steps?.length ?? 0,
273706
+ executedSteps: 0,
273707
+ passedSteps: 0,
273708
+ failedSteps: 0,
273709
+ skippedSteps: 0
273710
+ },
273711
+ ...steps ? { steps } : {},
273602
273712
  reportPath: reportPath(jeticDir, runId)
273603
273713
  };
273604
273714
  (0, core_1.writeJsonSync)(statusPath(jeticDir, runId), doc);
273605
273715
  }
273716
+ function updateRunStatus(jeticDir, runId, update) {
273717
+ const doc = (0, core_1.readJsonSync)(statusPath(jeticDir, runId));
273718
+ if (!doc)
273719
+ return;
273720
+ update(doc);
273721
+ (0, core_1.writeJsonSync)(statusPath(jeticDir, runId), doc);
273722
+ }
273606
273723
  function writeRunError(jeticDir, runId, error, meta) {
273607
273724
  const doc = {
273608
273725
  type: "simulation",
@@ -273941,11 +274058,17 @@ var require_workflow_tools = __commonJS({
273941
274058
  const simulator = new simulator_1.WorkflowSimulator(model, effectiveEnv, { projectRoot });
273942
274059
  const runId = (0, run_store_1.newRunId)();
273943
274060
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
274061
+ const plannedSteps = workflowDef.steps.map((s) => ({
274062
+ name: s.name || `${s.method} ${s.path}`,
274063
+ method: s.method,
274064
+ path: s.path
274065
+ }));
273944
274066
  const runMeta = {
273945
274067
  workflowName: workflowDef.name || selectedItem.name,
273946
274068
  environment: effectiveEnv.name,
273947
274069
  baseUrl: effectiveEnv.baseUrl,
273948
- startedAt
274070
+ startedAt,
274071
+ steps: plannedSteps
273949
274072
  };
273950
274073
  const execute = async () => {
273951
274074
  const result2 = await simulator.simulateWorkflow(workflowDef, {
@@ -273953,7 +274076,21 @@ var require_workflow_tools = __commonJS({
273953
274076
  loadWorkflow: (slugOrName) => (0, model_1.loadWorkflowByName)(workflowsDir, slugOrName),
273954
274077
  strictTemplates: args.strictTemplates ?? true,
273955
274078
  preflight: args.preflight ?? "abort",
273956
- requestTimeoutMs: args.requestTimeoutMs ?? 3e4
274079
+ requestTimeoutMs: args.requestTimeoutMs ?? 3e4,
274080
+ // Persist per-step progress so jetic_get_run can be polled while the run is in flight.
274081
+ onStepResult: (step) => {
274082
+ (0, run_store_1.updateRunStatus)(jeticDir, runId, (doc) => {
274083
+ if (!doc.steps)
274084
+ doc.steps = [];
274085
+ const progress = (0, run_store_1.stepToProgress)(step);
274086
+ const at = doc.steps.findIndex((s) => s.index === step.stepIndex && s.name === step.name);
274087
+ if (at >= 0)
274088
+ doc.steps[at] = progress;
274089
+ else
274090
+ doc.steps.push(progress);
274091
+ doc.stats = (0, run_store_1.statsFromSteps)(doc.steps);
274092
+ });
274093
+ }
273957
274094
  });
273958
274095
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
273959
274096
  const { reportPath: reportPath2 } = (0, run_store_1.writeSimulationRun)(jeticDir, runId, result2, {
@@ -289781,8 +289918,13 @@ var require_run_tools = __commonJS({
289781
289918
  const projectRoot = path15.resolve((0, types_1.resolveProjectInput)(args.projectPath));
289782
289919
  const config = (0, core_1.loadConfig)(projectRoot);
289783
289920
  const report = (0, run_store_1.readSimulationRun)(config.jeticDir, args.runId);
289784
- if (report)
289785
- return { found: true, ...report };
289921
+ if (report) {
289922
+ return {
289923
+ found: true,
289924
+ ...report,
289925
+ steps: report.result?.results ? report.result.results.map(run_store_1.stepToProgress) : []
289926
+ };
289927
+ }
289786
289928
  const status = (0, run_store_1.readRunStatus)(config.jeticDir, args.runId);
289787
289929
  if (status) {
289788
289930
  return {
@@ -289796,6 +289938,7 @@ var require_run_tools = __commonJS({
289796
289938
  startedAt: status.startedAt,
289797
289939
  finishedAt: status.finishedAt,
289798
289940
  stats: status.stats,
289941
+ ...status.steps ? { steps: status.steps } : {},
289799
289942
  reportPath: status.reportPath,
289800
289943
  ...status.error ? { error: status.error } : {},
289801
289944
  ...status.status === "running" ? { message: "Run still in progress \u2014 poll again shortly." } : {}
@@ -290997,6 +291140,14 @@ function deepGet(obj, dotPath) {
290997
291140
  }
290998
291141
  return current;
290999
291142
  }
291143
+ var TRACE_BODY_CAP = 16384;
291144
+ function truncateTraceBody(value) {
291145
+ if (value === void 0 || value === null) return value;
291146
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
291147
+ if (serialized.length <= TRACE_BODY_CAP) return value;
291148
+ return typeof value === "string" ? `${value.slice(0, TRACE_BODY_CAP)}
291149
+ \u2026[truncated: ${serialized.length - TRACE_BODY_CAP} more characters]` : { _truncated: true, preview: serialized.slice(0, TRACE_BODY_CAP), fullLengthChars: serialized.length };
291150
+ }
291000
291151
  async function resolveConditionRef(ref, context = "condition") {
291001
291152
  return ref.replace(/\{\{([^}]+)\}\}/g, () => "").trim() === ref.trim() ? ref : await resolveTemplateString(ref, context);
291002
291153
  }
@@ -291006,7 +291157,21 @@ async function evaluateStepCondition(condition, context = "condition") {
291006
291157
  async function preStepCondition(step) {
291007
291158
  if (!step.condition) return null;
291008
291159
  const label = step.name || `${step.method} ${step.path}`;
291009
- const base = { step, status: 0, responseBody: null, durationMs: 0, captured: [], injected: {} };
291160
+ const now = (/* @__PURE__ */ new Date()).toISOString();
291161
+ const base = {
291162
+ step,
291163
+ status: 0,
291164
+ responseBody: null,
291165
+ durationMs: 0,
291166
+ startedAt: now,
291167
+ finishedAt: now,
291168
+ passed: false,
291169
+ captured: [],
291170
+ injected: {},
291171
+ requestHeaders: {},
291172
+ requestBody: {},
291173
+ resolvedUrl: ""
291174
+ };
291010
291175
  const kind = (0, import_simulator2.conditionKind)(step.condition);
291011
291176
  if (kind === "invalid") {
291012
291177
  return { ...base, passed: false, error: `Step "${label}": ${(0, import_simulator2.describeInvalidCondition)(step.condition)}` };
@@ -291139,10 +291304,24 @@ async function resolveTemplateString(value, context) {
291139
291304
  }
291140
291305
  return result;
291141
291306
  }
291307
+ async function resolveDeepValue(value, context) {
291308
+ if (typeof value === "string") return resolveTemplateString(value, context);
291309
+ if (Array.isArray(value)) {
291310
+ const out = [];
291311
+ for (const item of value) out.push(await resolveDeepValue(item, context));
291312
+ return out;
291313
+ }
291314
+ if (value && typeof value === "object") {
291315
+ const out = {};
291316
+ for (const [key, item] of Object.entries(value)) out[key] = await resolveDeepValue(item, context);
291317
+ return out;
291318
+ }
291319
+ return value;
291320
+ }
291142
291321
  async function resolveBodyTemplates(body, context) {
291143
291322
  const resolved = {};
291144
291323
  for (const [key, value] of Object.entries(body)) {
291145
- resolved[key] = typeof value === "string" ? await resolveTemplateString(value, context) : value;
291324
+ resolved[key] = await resolveDeepValue(value, context);
291146
291325
  }
291147
291326
  return resolved;
291148
291327
  }
@@ -291161,6 +291340,10 @@ async function resolveInjections(inject, memory, allMemory, context) {
291161
291340
  if (value === null) continue;
291162
291341
  strValue = typeof value === "string" ? value : JSON.stringify(value);
291163
291342
  }
291343
+ const headerName = target.startsWith("header:") ? target.slice(7) : target.startsWith("body:") ? null : target;
291344
+ if (headerName && headerName.toLowerCase() === "authorization" && strValue && !/^[A-Za-z][A-Za-z0-9-]*\s/.test(strValue)) {
291345
+ strValue = `Bearer ${strValue}`;
291346
+ }
291164
291347
  if (target.startsWith("body:")) {
291165
291348
  body[target.slice(5)] = strValue;
291166
291349
  } else if (target.startsWith("header:")) {
@@ -291203,8 +291386,10 @@ async function resolvePathParams(urlPath, body) {
291203
291386
  return `:${param}`;
291204
291387
  });
291205
291388
  }
291389
+ var REQUEST_TIMEOUT_MS = 3e4;
291206
291390
  async function executeStep(step, baseUrl, defaultBody) {
291207
291391
  const startTime = Date.now();
291392
+ const startedAt = new Date(startTime).toISOString();
291208
291393
  const memory = new import_memory2.JeticMemory({ scope: "workflow" });
291209
291394
  const stepLabel = step.name || `${step.method} ${step.path}`;
291210
291395
  const { headers, body: injectedBody } = await resolveInjections(step.inject, memory, memory, stepLabel);
@@ -291214,13 +291399,12 @@ async function executeStep(step, baseUrl, defaultBody) {
291214
291399
  const resolvedPath = await resolvePathParams(step.path, requestBody);
291215
291400
  const url = `${baseUrl.replace(/\/$/, "")}${resolvedPath}`;
291216
291401
  const expectedStatus = step.expectStatus ?? 200;
291402
+ const requestHeaders = { "Content-Type": "application/json", ...headers };
291217
291403
  try {
291218
291404
  const fetchOptions = {
291219
291405
  method: step.method.toUpperCase(),
291220
- headers: {
291221
- "Content-Type": "application/json",
291222
- ...headers
291223
- }
291406
+ headers: requestHeaders,
291407
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
291224
291408
  };
291225
291409
  if (!["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(requestBody).length > 0) {
291226
291410
  fetchOptions.body = JSON.stringify(requestBody);
@@ -291253,20 +291437,39 @@ async function executeStep(step, baseUrl, defaultBody) {
291253
291437
  status: response.status,
291254
291438
  responseBody,
291255
291439
  durationMs,
291440
+ startedAt,
291441
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
291256
291442
  passed,
291257
291443
  captured: [...inputCaptured, ...captured],
291258
- injected: headers
291444
+ injected: headers,
291445
+ requestHeaders,
291446
+ requestBody,
291447
+ resolvedUrl: finalUrl,
291448
+ responseHeaders: Object.fromEntries(response.headers.entries())
291259
291449
  };
291260
291450
  } catch (err) {
291451
+ const code = (0, import_simulator2.networkErrorCode)(err);
291452
+ let message = err.message || String(err);
291453
+ if (err?.name === "TimeoutError" || code === "ETIMEDOUT") {
291454
+ message = `Request timed out after ${REQUEST_TIMEOUT_MS}ms (${step.method} ${resolvedPath}).`;
291455
+ } else if (code === "ECONNREFUSED") {
291456
+ message = `Connection refused (ECONNREFUSED) at ${baseUrl} \u2014 the server is not accepting connections.`;
291457
+ }
291261
291458
  return {
291262
291459
  step,
291263
291460
  status: 0,
291264
291461
  responseBody: null,
291265
291462
  durationMs: Date.now() - startTime,
291463
+ startedAt,
291464
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
291266
291465
  passed: false,
291267
291466
  captured: inputCaptured,
291268
- error: err.message || String(err),
291269
- injected: headers
291467
+ error: message,
291468
+ injected: headers,
291469
+ requestHeaders,
291470
+ requestBody,
291471
+ resolvedUrl: url,
291472
+ connectionError: code ? import_simulator2.CONNECTION_ERROR_CODES.has(code) : false
291270
291473
  };
291271
291474
  }
291272
291475
  }
@@ -291514,17 +291717,23 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
291514
291717
  }
291515
291718
  const checkSpinner = new Spinner();
291516
291719
  checkSpinner.start(`Checking if backend is active at ${baseUrl}...`);
291517
- try {
291518
- await fetch(baseUrl);
291519
- checkSpinner.stop(` ${c.green}\u2713${c.reset} Backend is active`);
291520
- console.log("");
291521
- } catch (e) {
291720
+ const probe = await (0, import_simulator2.probeTarget)(baseUrl, 3e3);
291721
+ if (probe.verdict === "unreachable") {
291522
291722
  checkSpinner.stop();
291523
291723
  console.log(` ${c.red}\u2717${c.reset} Backend is unreachable at ${c.bold}${baseUrl}${c.reset}`);
291524
- console.log(` ${c.yellow}\u26A0${c.reset} Please run/initialize your backend project and try again.
291724
+ console.log(` ${c.red}${probe.message}${c.reset}`);
291725
+ console.log(` ${c.yellow}\u26A0${c.reset} Start your backend project and try again.
291525
291726
  `);
291526
291727
  process.exit(1);
291527
291728
  }
291729
+ if (probe.verdict === "server-error") {
291730
+ checkSpinner.stop(` ${c.yellow}\u26A0${c.reset} Backend reachable but failing (HTTP ${probe.status}) at ${baseUrl}`);
291731
+ console.log(` ${c.yellow}${probe.message}${c.reset}
291732
+ `);
291733
+ } else {
291734
+ checkSpinner.stop(` ${c.green}\u2713${c.reset} Backend is active (HTTP ${probe.status})`);
291735
+ console.log("");
291736
+ }
291528
291737
  renderWorkflowHeader(workflow);
291529
291738
  console.log(` ${c.magenta}\u{1F680}${c.reset} Executing workflow steps...
291530
291739
  `);
@@ -291638,6 +291847,12 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
291638
291847
  const icon = result.skipped ? SKIP : result.passed ? TICK : CROSS;
291639
291848
  spinner.stop(` ${icon} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
291640
291849
  renderStepResult(result, i, workflow.steps.length);
291850
+ if (result.connectionError) {
291851
+ console.log(` ${c.red}${c.bold}\u26A0 Target unreachable \u2014 aborting early.${c.reset}
291852
+ `);
291853
+ stopOnFailure = true;
291854
+ break;
291855
+ }
291641
291856
  if (!result.passed && result.error && !result.status && (0, import_simulator2.conditionKind)(step.condition) === "invalid") {
291642
291857
  console.log(` ${c.red}${c.bold}\u26A0 Unrecognized condition \u2014 stopping workflow.${c.reset}
291643
291858
  `);
@@ -291690,12 +291905,19 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
291690
291905
  path: r.step?.path,
291691
291906
  description: r.step?.description,
291692
291907
  status: r.status,
291908
+ responseStatus: r.status,
291693
291909
  passed: r.passed,
291694
291910
  durationMs: r.durationMs,
291911
+ startedAt: r.startedAt,
291912
+ finishedAt: r.finishedAt,
291913
+ resolvedUrl: r.resolvedUrl,
291914
+ requestHeaders: r.requestHeaders,
291915
+ requestBody: r.requestBody,
291916
+ requestBodySpec: r.step?.body,
291917
+ responseHeaders: r.responseHeaders,
291918
+ responseBody: truncateTraceBody(r.responseBody),
291695
291919
  captured: Object.fromEntries((r.captured ?? []).map((cap) => [cap, "(captured)"])),
291696
291920
  injected: r.injected ?? {},
291697
- requestBody: r.step?.body,
291698
- responseBody: r.responseBody,
291699
291921
  error: r.error ?? null,
291700
291922
  expectStatus: r.step?.expectStatus,
291701
291923
  captureSpec: r.step?.capture,
@@ -293321,7 +293543,7 @@ var mcpCommand = new import_commander18.Command("mcp").description("Launch Model
293321
293543
 
293322
293544
  // src/index.ts
293323
293545
  var program = new import_commander19.Command();
293324
- program.name("jetic").description("AI-Native API Behavior Testing").version("0.2.6");
293546
+ program.name("jetic").description("AI-Native API Behavior Testing").version("0.2.7");
293325
293547
  program.addCommand(initCommand);
293326
293548
  program.addCommand(scanCommand);
293327
293549
  program.addCommand(inspectCommand);