jetic-cli 0.2.5 → 0.2.7
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/README.md +65 -153
- package/dist/dashboard/assets/index-DizUZ0Rd.js +430 -0
- package/dist/dashboard/assets/index-Dz50r4Cq.css +1 -0
- package/dist/dashboard/brand/JeticLogo.tsx +79 -0
- package/dist/dashboard/brand/jetic-animated.svg +33 -0
- package/dist/dashboard/brand/jetic-black.svg +5 -0
- package/dist/dashboard/brand/jetic-intro.svg +38 -0
- package/dist/dashboard/brand/jetic.svg +5 -0
- package/dist/dashboard/index.html +12 -3
- package/dist/index.js +1429 -320
- package/package.json +68 -67
- package/public/brand/JeticLogo.tsx +79 -0
- package/public/brand/jetic-animated.svg +33 -0
- package/public/brand/jetic-black.svg +5 -0
- package/public/brand/jetic-intro.svg +38 -0
- package/public/brand/jetic.svg +5 -0
- package/public/jetic_logo.png +0 -0
- package/dist/dashboard/assets/index-BxpFwhwt.css +0 -1
- package/dist/dashboard/assets/index-D8QbiQBG.js +0 -409
package/dist/index.js
CHANGED
|
@@ -269150,6 +269150,7 @@ var require_yaml_io = __commonJS({
|
|
|
269150
269150
|
exports2.loadModelFromDir = loadModelFromDir5;
|
|
269151
269151
|
exports2.readWorkflowDefinitionFile = readWorkflowDefinitionFile2;
|
|
269152
269152
|
exports2.listWorkflowFiles = listWorkflowFiles2;
|
|
269153
|
+
exports2.loadWorkflowByName = loadWorkflowByName3;
|
|
269153
269154
|
exports2.hasYamlModel = hasYamlModel5;
|
|
269154
269155
|
exports2.loadModelCompat = loadModelCompat;
|
|
269155
269156
|
var fs10 = __importStar(require("fs"));
|
|
@@ -269245,6 +269246,29 @@ var require_yaml_io = __commonJS({
|
|
|
269245
269246
|
return [];
|
|
269246
269247
|
return fs10.readdirSync(workflowsDir).filter((f) => f.endsWith(".json") || f.endsWith(".yaml") || f.endsWith(".yml")).sort();
|
|
269247
269248
|
}
|
|
269249
|
+
function loadWorkflowByName3(workflowsDir, slugOrName) {
|
|
269250
|
+
const wanted = slugOrName.trim().toLowerCase();
|
|
269251
|
+
if (!wanted)
|
|
269252
|
+
return null;
|
|
269253
|
+
const files = listWorkflowFiles2(workflowsDir);
|
|
269254
|
+
const bySlug = files.find((f) => f.replace(/.(json|ya?ml)$/, "").toLowerCase() === wanted);
|
|
269255
|
+
if (bySlug) {
|
|
269256
|
+
try {
|
|
269257
|
+
return readWorkflowDefinitionFile2(path15.join(workflowsDir, bySlug));
|
|
269258
|
+
} catch {
|
|
269259
|
+
return null;
|
|
269260
|
+
}
|
|
269261
|
+
}
|
|
269262
|
+
for (const file of files) {
|
|
269263
|
+
try {
|
|
269264
|
+
const def = readWorkflowDefinitionFile2(path15.join(workflowsDir, file));
|
|
269265
|
+
if (def?.name && def.name.trim().toLowerCase() === wanted)
|
|
269266
|
+
return def;
|
|
269267
|
+
} catch {
|
|
269268
|
+
}
|
|
269269
|
+
}
|
|
269270
|
+
return null;
|
|
269271
|
+
}
|
|
269248
269272
|
function hasYamlModel5(modelDir) {
|
|
269249
269273
|
return fs10.existsSync(path15.join(modelDir, "api.yaml"));
|
|
269250
269274
|
}
|
|
@@ -272353,12 +272377,15 @@ var require_dist12 = __commonJS({
|
|
|
272353
272377
|
exports2.JeticMemory = void 0;
|
|
272354
272378
|
var fs10 = __importStar(require("fs"));
|
|
272355
272379
|
var path15 = __importStar(require("path"));
|
|
272380
|
+
function memoryFilePath(projectRoot) {
|
|
272381
|
+
return path15.join(projectRoot ?? process.cwd(), ".jetic", "memory.json");
|
|
272382
|
+
}
|
|
272356
272383
|
var JeticMemory4 = class {
|
|
272357
272384
|
scope;
|
|
272358
272385
|
filePath;
|
|
272359
272386
|
constructor(options) {
|
|
272360
272387
|
this.scope = options.scope;
|
|
272361
|
-
this.filePath =
|
|
272388
|
+
this.filePath = memoryFilePath(options.projectRoot);
|
|
272362
272389
|
}
|
|
272363
272390
|
ensureDirectory() {
|
|
272364
272391
|
const dir = path15.dirname(this.filePath);
|
|
@@ -272451,8 +272478,8 @@ var require_dist12 = __commonJS({
|
|
|
272451
272478
|
this.writeData(data);
|
|
272452
272479
|
}
|
|
272453
272480
|
}
|
|
272454
|
-
static getAllMemory() {
|
|
272455
|
-
const filePath =
|
|
272481
|
+
static getAllMemory(projectRoot) {
|
|
272482
|
+
const filePath = memoryFilePath(projectRoot);
|
|
272456
272483
|
if (!fs10.existsSync(filePath))
|
|
272457
272484
|
return {};
|
|
272458
272485
|
try {
|
|
@@ -272474,8 +272501,8 @@ var require_dist12 = __commonJS({
|
|
|
272474
272501
|
return {};
|
|
272475
272502
|
}
|
|
272476
272503
|
}
|
|
272477
|
-
static clearAllMemory() {
|
|
272478
|
-
const filePath =
|
|
272504
|
+
static clearAllMemory(projectRoot) {
|
|
272505
|
+
const filePath = memoryFilePath(projectRoot);
|
|
272479
272506
|
if (fs10.existsSync(filePath)) {
|
|
272480
272507
|
fs10.unlinkSync(filePath);
|
|
272481
272508
|
}
|
|
@@ -272485,6 +272512,158 @@ var require_dist12 = __commonJS({
|
|
|
272485
272512
|
}
|
|
272486
272513
|
});
|
|
272487
272514
|
|
|
272515
|
+
// ../../packages/simulator/dist/conditions.js
|
|
272516
|
+
var require_conditions = __commonJS({
|
|
272517
|
+
"../../packages/simulator/dist/conditions.js"(exports2) {
|
|
272518
|
+
"use strict";
|
|
272519
|
+
init_cjs_shims();
|
|
272520
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
272521
|
+
exports2.UNARY_OPERATORS = exports2.CONDITION_OPERATORS = void 0;
|
|
272522
|
+
exports2.conditionKind = conditionKind3;
|
|
272523
|
+
exports2.isRulesCondition = isRulesCondition3;
|
|
272524
|
+
exports2.isLegacyCondition = isLegacyCondition;
|
|
272525
|
+
exports2.describeInvalidCondition = describeInvalidCondition3;
|
|
272526
|
+
exports2.evaluateRules = evaluateRules3;
|
|
272527
|
+
exports2.evaluateLegacyCondition = evaluateLegacyCondition3;
|
|
272528
|
+
exports2.CONDITION_OPERATORS = [
|
|
272529
|
+
"equals",
|
|
272530
|
+
"not_equals",
|
|
272531
|
+
"greater_than",
|
|
272532
|
+
"greater_than_or_equal",
|
|
272533
|
+
"less_than",
|
|
272534
|
+
"less_than_or_equal",
|
|
272535
|
+
"exists",
|
|
272536
|
+
"not_exists",
|
|
272537
|
+
"is_empty",
|
|
272538
|
+
"is_not_empty",
|
|
272539
|
+
"contains",
|
|
272540
|
+
"not_contains",
|
|
272541
|
+
"starts_with",
|
|
272542
|
+
"ends_with"
|
|
272543
|
+
];
|
|
272544
|
+
exports2.UNARY_OPERATORS = ["exists", "not_exists", "is_empty", "is_not_empty"];
|
|
272545
|
+
function isObject(value) {
|
|
272546
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
272547
|
+
}
|
|
272548
|
+
function conditionKind3(condition) {
|
|
272549
|
+
if (!isObject(condition))
|
|
272550
|
+
return "invalid";
|
|
272551
|
+
if (isObject(condition.rules))
|
|
272552
|
+
return "rules";
|
|
272553
|
+
if (typeof condition.if === "string")
|
|
272554
|
+
return "legacy";
|
|
272555
|
+
return "invalid";
|
|
272556
|
+
}
|
|
272557
|
+
function isRulesCondition3(condition) {
|
|
272558
|
+
return conditionKind3(condition) === "rules";
|
|
272559
|
+
}
|
|
272560
|
+
function isLegacyCondition(condition) {
|
|
272561
|
+
return conditionKind3(condition) === "legacy";
|
|
272562
|
+
}
|
|
272563
|
+
function describeInvalidCondition3(condition) {
|
|
272564
|
+
if (!isObject(condition))
|
|
272565
|
+
return "condition must be an object";
|
|
272566
|
+
const keys = Object.keys(condition);
|
|
272567
|
+
return `unrecognized condition (keys: ${keys.join(", ") || "none"}). Use { "rules": { "all": [{ "left", "operator", "right" }] }, "onFail": "abort|continue|switch" }`;
|
|
272568
|
+
}
|
|
272569
|
+
async function evaluateRule(rule, resolve2) {
|
|
272570
|
+
const left = await resolve2(String(rule.left ?? ""));
|
|
272571
|
+
const right = rule.right !== void 0 ? await resolve2(String(rule.right)) : void 0;
|
|
272572
|
+
switch (rule.operator) {
|
|
272573
|
+
case "equals":
|
|
272574
|
+
return { ok: left === right };
|
|
272575
|
+
case "not_equals":
|
|
272576
|
+
return { ok: left !== right };
|
|
272577
|
+
case "greater_than":
|
|
272578
|
+
return { ok: Number(left) > Number(right) };
|
|
272579
|
+
case "greater_than_or_equal":
|
|
272580
|
+
return { ok: Number(left) >= Number(right) };
|
|
272581
|
+
case "less_than":
|
|
272582
|
+
return { ok: Number(left) < Number(right) };
|
|
272583
|
+
case "less_than_or_equal":
|
|
272584
|
+
return { ok: Number(left) <= Number(right) };
|
|
272585
|
+
case "exists":
|
|
272586
|
+
return { ok: left !== "" && left !== void 0 };
|
|
272587
|
+
case "not_exists":
|
|
272588
|
+
return { ok: left === "" || left === void 0 };
|
|
272589
|
+
case "is_empty":
|
|
272590
|
+
return { ok: left === "" };
|
|
272591
|
+
case "is_not_empty":
|
|
272592
|
+
return { ok: left !== "" };
|
|
272593
|
+
case "contains":
|
|
272594
|
+
return { ok: right !== void 0 && left.includes(right) };
|
|
272595
|
+
case "not_contains":
|
|
272596
|
+
return { ok: right !== void 0 && !left.includes(right) };
|
|
272597
|
+
case "starts_with":
|
|
272598
|
+
return { ok: right !== void 0 && left.startsWith(right) };
|
|
272599
|
+
case "ends_with":
|
|
272600
|
+
return { ok: right !== void 0 && left.endsWith(right) };
|
|
272601
|
+
default:
|
|
272602
|
+
return { ok: false, unknownOperator: true };
|
|
272603
|
+
}
|
|
272604
|
+
}
|
|
272605
|
+
function describeRule(rule) {
|
|
272606
|
+
return `${rule.left} ${rule.operator}${rule.right !== void 0 ? ` ${rule.right}` : ""}`;
|
|
272607
|
+
}
|
|
272608
|
+
async function evaluateRules3(group, resolve2) {
|
|
272609
|
+
const all = Array.isArray(group.all) ? group.all : void 0;
|
|
272610
|
+
const any = Array.isArray(group.any) ? group.any : void 0;
|
|
272611
|
+
if ((!all || all.length === 0) && (!any || any.length === 0)) {
|
|
272612
|
+
return { passed: true, reason: "No rules defined" };
|
|
272613
|
+
}
|
|
272614
|
+
const failing = [];
|
|
272615
|
+
let passed = true;
|
|
272616
|
+
if (all && all.length > 0) {
|
|
272617
|
+
const results = [];
|
|
272618
|
+
for (const rule of all)
|
|
272619
|
+
results.push({ rule, ...await evaluateRule(rule, resolve2) });
|
|
272620
|
+
const bad = results.filter((r) => !r.ok);
|
|
272621
|
+
if (bad.length > 0) {
|
|
272622
|
+
passed = false;
|
|
272623
|
+
failing.push(...bad.map((r) => r.unknownOperator ? `${describeRule(r.rule)} (unknown operator)` : describeRule(r.rule)));
|
|
272624
|
+
}
|
|
272625
|
+
}
|
|
272626
|
+
if (any && any.length > 0) {
|
|
272627
|
+
const results = [];
|
|
272628
|
+
for (const rule of any)
|
|
272629
|
+
results.push({ rule, ...await evaluateRule(rule, resolve2) });
|
|
272630
|
+
if (!results.some((r) => r.ok)) {
|
|
272631
|
+
passed = false;
|
|
272632
|
+
failing.push(`none of: ${results.map((r) => describeRule(r.rule)).join(" | ")}`);
|
|
272633
|
+
}
|
|
272634
|
+
}
|
|
272635
|
+
const mode = all && any ? "AND+OR" : all ? "AND" : "OR";
|
|
272636
|
+
return {
|
|
272637
|
+
passed,
|
|
272638
|
+
reason: passed ? `All conditions met (${mode})` : `Failed: ${failing.join(", ")}`
|
|
272639
|
+
};
|
|
272640
|
+
}
|
|
272641
|
+
async function evaluateLegacyCondition3(condition, resolve2) {
|
|
272642
|
+
const evaluated = await resolve2(condition.if);
|
|
272643
|
+
let matched;
|
|
272644
|
+
if (condition.exists !== void 0) {
|
|
272645
|
+
const hasValue = evaluated !== "" && evaluated !== "null" && evaluated !== "undefined";
|
|
272646
|
+
matched = condition.exists ? hasValue : !hasValue;
|
|
272647
|
+
} else if (condition.equals !== void 0) {
|
|
272648
|
+
matched = evaluated === condition.equals;
|
|
272649
|
+
} else if (condition.notEquals !== void 0) {
|
|
272650
|
+
matched = evaluated !== condition.notEquals;
|
|
272651
|
+
} else if (condition.contains !== void 0) {
|
|
272652
|
+
matched = evaluated.includes(condition.contains);
|
|
272653
|
+
} else if (condition.greaterThan !== void 0) {
|
|
272654
|
+
matched = Number(evaluated) > condition.greaterThan;
|
|
272655
|
+
} else if (condition.lessThan !== void 0) {
|
|
272656
|
+
matched = Number(evaluated) < condition.lessThan;
|
|
272657
|
+
} else if (condition.in !== void 0) {
|
|
272658
|
+
matched = condition.in.includes(evaluated);
|
|
272659
|
+
} else {
|
|
272660
|
+
matched = evaluated !== "" && evaluated !== "false" && evaluated !== "0" && evaluated !== "null" && evaluated !== "undefined";
|
|
272661
|
+
}
|
|
272662
|
+
return { matched, evaluated };
|
|
272663
|
+
}
|
|
272664
|
+
}
|
|
272665
|
+
});
|
|
272666
|
+
|
|
272488
272667
|
// ../../packages/simulator/dist/workflow-simulator.js
|
|
272489
272668
|
var require_workflow_simulator = __commonJS({
|
|
272490
272669
|
"../../packages/simulator/dist/workflow-simulator.js"(exports2) {
|
|
@@ -272494,6 +272673,22 @@ var require_workflow_simulator = __commonJS({
|
|
|
272494
272673
|
exports2.WorkflowSimulator = void 0;
|
|
272495
272674
|
var memory_1 = require_dist12();
|
|
272496
272675
|
var faker_1 = require("@faker-js/faker");
|
|
272676
|
+
var conditions_1 = require_conditions();
|
|
272677
|
+
var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
272678
|
+
"ECONNREFUSED",
|
|
272679
|
+
"ECONNRESET",
|
|
272680
|
+
"ENOTFOUND",
|
|
272681
|
+
"EAI_AGAIN",
|
|
272682
|
+
"ETIMEDOUT",
|
|
272683
|
+
"EHOSTUNREACH",
|
|
272684
|
+
"ENETUNREACH",
|
|
272685
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
272686
|
+
"UND_ERR_SOCKET"
|
|
272687
|
+
]);
|
|
272688
|
+
function networkErrorCode(error) {
|
|
272689
|
+
const raw = error?.cause?.code ?? error?.cause?.errno ?? error?.code ?? error?.errno;
|
|
272690
|
+
return typeof raw === "string" ? raw : void 0;
|
|
272691
|
+
}
|
|
272497
272692
|
function deepGet2(obj, dotPath) {
|
|
272498
272693
|
if (obj === null || obj === void 0)
|
|
272499
272694
|
return void 0;
|
|
@@ -272509,27 +272704,120 @@ var require_workflow_simulator = __commonJS({
|
|
|
272509
272704
|
var WorkflowSimulator2 = class {
|
|
272510
272705
|
model;
|
|
272511
272706
|
environment;
|
|
272512
|
-
|
|
272707
|
+
runtime;
|
|
272708
|
+
strictTemplates = true;
|
|
272709
|
+
requestTimeoutMs = 3e4;
|
|
272710
|
+
constructor(model, environment, runtime = {}) {
|
|
272513
272711
|
this.model = model;
|
|
272514
272712
|
this.environment = environment;
|
|
272713
|
+
this.runtime = runtime;
|
|
272714
|
+
}
|
|
272715
|
+
memory(scope) {
|
|
272716
|
+
return new memory_1.JeticMemory({ scope, projectRoot: this.runtime.projectRoot });
|
|
272515
272717
|
}
|
|
272516
272718
|
async simulateWorkflow(workflow, options) {
|
|
272517
272719
|
if (options?.clearMemory) {
|
|
272518
|
-
memory_1.JeticMemory.clearAllMemory();
|
|
272720
|
+
memory_1.JeticMemory.clearAllMemory(this.runtime.projectRoot);
|
|
272519
272721
|
}
|
|
272520
|
-
|
|
272722
|
+
this.strictTemplates = options?.strictTemplates ?? true;
|
|
272723
|
+
this.requestTimeoutMs = options?.requestTimeoutMs ?? 3e4;
|
|
272521
272724
|
const startTime = Date.now();
|
|
272522
|
-
const
|
|
272523
|
-
|
|
272725
|
+
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;
|
|
272727
|
+
if (preflight && preflightMode === "abort" && preflight.verdict === "unreachable") {
|
|
272728
|
+
preflight.aborted = true;
|
|
272729
|
+
return {
|
|
272730
|
+
success: false,
|
|
272731
|
+
workflow: {
|
|
272732
|
+
name: workflow.name,
|
|
272733
|
+
description: workflow.description,
|
|
272734
|
+
stepsCount: workflow.steps.length
|
|
272735
|
+
},
|
|
272736
|
+
environment: {
|
|
272737
|
+
name: this.environment.name,
|
|
272738
|
+
baseUrl: this.environment.baseUrl
|
|
272739
|
+
},
|
|
272740
|
+
stats: {
|
|
272741
|
+
totalSteps: workflow.steps.length,
|
|
272742
|
+
executedSteps: 0,
|
|
272743
|
+
passedSteps: 0,
|
|
272744
|
+
failedSteps: 0,
|
|
272745
|
+
totalTimeMs: Date.now() - startTime
|
|
272746
|
+
},
|
|
272747
|
+
results: [],
|
|
272748
|
+
preflight,
|
|
272749
|
+
abortReason: preflight.message
|
|
272750
|
+
};
|
|
272751
|
+
}
|
|
272752
|
+
const state = { results: [], allPassed: true, halted: false };
|
|
272753
|
+
await this.runSteps(workflow, options, state, [workflow.name]);
|
|
272754
|
+
const stepResults = state.results;
|
|
272755
|
+
const totalTimeMs = Date.now() - startTime;
|
|
272756
|
+
return {
|
|
272757
|
+
success: state.allPassed,
|
|
272758
|
+
workflow: {
|
|
272759
|
+
name: workflow.name,
|
|
272760
|
+
description: workflow.description,
|
|
272761
|
+
stepsCount: workflow.steps.length
|
|
272762
|
+
},
|
|
272763
|
+
environment: {
|
|
272764
|
+
name: this.environment.name,
|
|
272765
|
+
baseUrl: this.environment.baseUrl
|
|
272766
|
+
},
|
|
272767
|
+
stats: {
|
|
272768
|
+
totalSteps: workflow.steps.length,
|
|
272769
|
+
executedSteps: stepResults.length,
|
|
272770
|
+
passedSteps: stepResults.filter((r) => r.passed).length,
|
|
272771
|
+
failedSteps: stepResults.filter((r) => !r.passed).length,
|
|
272772
|
+
totalTimeMs
|
|
272773
|
+
},
|
|
272774
|
+
results: stepResults,
|
|
272775
|
+
...preflight ? { preflight } : {}
|
|
272776
|
+
};
|
|
272777
|
+
}
|
|
272778
|
+
/**
|
|
272779
|
+
* Runs one workflow's steps, appending to `state`. Returns `'stop'` when the remaining steps
|
|
272780
|
+
* of THIS workflow must not run (failure, `onFail: continue|switch`), else `'done'`.
|
|
272781
|
+
* `state.halted` is set only for failures, so a caller resuming after a switch knows whether to go on.
|
|
272782
|
+
*/
|
|
272783
|
+
async runSteps(workflow, options, state, lineage) {
|
|
272784
|
+
const baseUrl = this.environment.baseUrl;
|
|
272785
|
+
const resolve2 = (value) => this.resolveTemplateString(value, false);
|
|
272524
272786
|
for (let i = 0; i < workflow.steps.length; i++) {
|
|
272525
272787
|
const step = workflow.steps[i];
|
|
272526
272788
|
const stepStartTime = Date.now();
|
|
272527
|
-
|
|
272528
|
-
|
|
272789
|
+
const stepName = step.name || `${step.method} ${step.path}`;
|
|
272790
|
+
const kind = step.condition === void 0 ? void 0 : (0, conditions_1.conditionKind)(step.condition);
|
|
272791
|
+
if (kind === "invalid") {
|
|
272792
|
+
const reason = (0, conditions_1.describeInvalidCondition)(step.condition);
|
|
272793
|
+
state.results.push({
|
|
272794
|
+
stepIndex: i + 1,
|
|
272795
|
+
name: stepName,
|
|
272796
|
+
method: step.method,
|
|
272797
|
+
path: step.path,
|
|
272798
|
+
targetUrl: "",
|
|
272799
|
+
expectedStatus: step.expectStatus ?? 200,
|
|
272800
|
+
actualStatus: 0,
|
|
272801
|
+
durationMs: Date.now() - stepStartTime,
|
|
272802
|
+
passed: false,
|
|
272803
|
+
skipped: false,
|
|
272804
|
+
retryCount: 0,
|
|
272805
|
+
requestHeaders: {},
|
|
272806
|
+
requestBody: {},
|
|
272807
|
+
responseBody: null,
|
|
272808
|
+
capturedMemory: [],
|
|
272809
|
+
error: `Step ${i + 1} "${stepName}": ${reason}`
|
|
272810
|
+
});
|
|
272811
|
+
state.allPassed = false;
|
|
272812
|
+
state.halted = true;
|
|
272813
|
+
return "stop";
|
|
272814
|
+
}
|
|
272815
|
+
if (kind === "legacy") {
|
|
272816
|
+
const condResult = await (0, conditions_1.evaluateLegacyCondition)(step.condition, resolve2);
|
|
272529
272817
|
if (!condResult.matched) {
|
|
272530
|
-
|
|
272818
|
+
state.results.push({
|
|
272531
272819
|
stepIndex: i + 1,
|
|
272532
|
-
name:
|
|
272820
|
+
name: stepName,
|
|
272533
272821
|
method: step.method,
|
|
272534
272822
|
path: step.path,
|
|
272535
272823
|
targetUrl: "",
|
|
@@ -272548,12 +272836,44 @@ var require_workflow_simulator = __commonJS({
|
|
|
272548
272836
|
continue;
|
|
272549
272837
|
}
|
|
272550
272838
|
}
|
|
272551
|
-
|
|
272552
|
-
|
|
272553
|
-
|
|
272554
|
-
|
|
272555
|
-
|
|
272556
|
-
|
|
272839
|
+
let injectedHeaders = {};
|
|
272840
|
+
let finalBody = {};
|
|
272841
|
+
let resolvedQuery = {};
|
|
272842
|
+
let resolvedPath = step.path;
|
|
272843
|
+
let fullUrl = "";
|
|
272844
|
+
let inputCaptured = [];
|
|
272845
|
+
try {
|
|
272846
|
+
const injections = await this.resolveInjections(step.inject);
|
|
272847
|
+
injectedHeaders = injections.headers;
|
|
272848
|
+
const resolvedBody = await this.resolveBodyTemplates(step.body || {});
|
|
272849
|
+
finalBody = { ...injections.body, ...resolvedBody };
|
|
272850
|
+
resolvedQuery = await this.resolveBodyTemplates(step.query || {});
|
|
272851
|
+
inputCaptured = await this.captureInputToMemory(step.captureInput, finalBody);
|
|
272852
|
+
resolvedPath = await this.resolvePathParams(step.path, finalBody);
|
|
272853
|
+
fullUrl = `${baseUrl.replace(/\/$/, "")}${resolvedPath}`;
|
|
272854
|
+
} catch (err) {
|
|
272855
|
+
state.results.push({
|
|
272856
|
+
stepIndex: i + 1,
|
|
272857
|
+
name: stepName,
|
|
272858
|
+
method: step.method,
|
|
272859
|
+
path: step.path,
|
|
272860
|
+
targetUrl: "",
|
|
272861
|
+
expectedStatus: step.expectStatus ?? 200,
|
|
272862
|
+
actualStatus: 0,
|
|
272863
|
+
durationMs: Date.now() - stepStartTime,
|
|
272864
|
+
passed: false,
|
|
272865
|
+
skipped: false,
|
|
272866
|
+
retryCount: 0,
|
|
272867
|
+
requestHeaders: {},
|
|
272868
|
+
requestBody: {},
|
|
272869
|
+
responseBody: null,
|
|
272870
|
+
capturedMemory: [],
|
|
272871
|
+
error: `Step ${i + 1} "${stepName}": ${err?.message || err}`
|
|
272872
|
+
});
|
|
272873
|
+
state.allPassed = false;
|
|
272874
|
+
state.halted = true;
|
|
272875
|
+
return "stop";
|
|
272876
|
+
}
|
|
272557
272877
|
const expectedStatus = step.expectStatus ?? 200;
|
|
272558
272878
|
const maxAttempts = Math.max(1, step.retry?.times ?? workflow.retry?.times ?? 1);
|
|
272559
272879
|
const retryDelayMs = step.retry?.delayMs ?? workflow.retry?.delayMs ?? 0;
|
|
@@ -272562,6 +272882,7 @@ var require_workflow_simulator = __commonJS({
|
|
|
272562
272882
|
let responseBody = null;
|
|
272563
272883
|
let stepPassed = false;
|
|
272564
272884
|
let errorMsg;
|
|
272885
|
+
let connectionCode;
|
|
272565
272886
|
while (attempts < maxAttempts) {
|
|
272566
272887
|
attempts++;
|
|
272567
272888
|
if (attempts > 1 && retryDelayMs > 0) {
|
|
@@ -272570,22 +272891,42 @@ var require_workflow_simulator = __commonJS({
|
|
|
272570
272891
|
responseStatus = 0;
|
|
272571
272892
|
responseBody = null;
|
|
272572
272893
|
errorMsg = void 0;
|
|
272894
|
+
connectionCode = void 0;
|
|
272895
|
+
const timeoutMs = step.timeoutMs ?? this.requestTimeoutMs;
|
|
272573
272896
|
try {
|
|
272897
|
+
const upperMethod = step.method.toUpperCase();
|
|
272574
272898
|
const fetchOptions = {
|
|
272575
|
-
method:
|
|
272899
|
+
method: upperMethod,
|
|
272576
272900
|
headers: { "Content-Type": "application/json", ...injectedHeaders }
|
|
272577
272901
|
};
|
|
272578
|
-
if (!["GET", "HEAD"].includes(
|
|
272902
|
+
if (!["GET", "HEAD"].includes(upperMethod) && Object.keys(finalBody).length > 0) {
|
|
272579
272903
|
fetchOptions.body = JSON.stringify(finalBody);
|
|
272580
272904
|
}
|
|
272905
|
+
if (timeoutMs > 0) {
|
|
272906
|
+
fetchOptions.signal = AbortSignal.timeout(timeoutMs);
|
|
272907
|
+
}
|
|
272581
272908
|
let finalUrl = fullUrl;
|
|
272582
|
-
|
|
272909
|
+
const bodyAsQuery = ["GET", "HEAD"].includes(upperMethod) && Object.keys(finalBody).length > 0;
|
|
272910
|
+
if (Object.keys(resolvedQuery).length > 0 || bodyAsQuery) {
|
|
272583
272911
|
const params = new URLSearchParams();
|
|
272584
|
-
|
|
272585
|
-
|
|
272586
|
-
|
|
272587
|
-
|
|
272588
|
-
|
|
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}`;
|
|
272589
272930
|
}
|
|
272590
272931
|
const resp = await fetch(finalUrl, fetchOptions);
|
|
272591
272932
|
responseStatus = resp.status;
|
|
@@ -272605,151 +272946,206 @@ var require_workflow_simulator = __commonJS({
|
|
|
272605
272946
|
break;
|
|
272606
272947
|
} catch (err) {
|
|
272607
272948
|
stepPassed = false;
|
|
272608
|
-
|
|
272949
|
+
connectionCode = networkErrorCode(err);
|
|
272950
|
+
if (err?.name === "TimeoutError" || connectionCode === "ETIMEDOUT") {
|
|
272951
|
+
errorMsg = `Request timed out after ${timeoutMs}ms (${step.method} ${resolvedPath}).`;
|
|
272952
|
+
} else if (connectionCode === "ECONNREFUSED") {
|
|
272953
|
+
errorMsg = `Connection refused (ECONNREFUSED) at ${baseUrl} \u2014 the server is not accepting connections.`;
|
|
272954
|
+
} else {
|
|
272955
|
+
errorMsg = err?.message || String(err);
|
|
272956
|
+
}
|
|
272609
272957
|
}
|
|
272610
272958
|
}
|
|
272611
272959
|
const responseCaptured = stepPassed ? await this.captureToMemory(step.capture, responseBody) : [];
|
|
272612
|
-
|
|
272613
|
-
|
|
272614
|
-
const onFailure = step.onFailure ?? "abort";
|
|
272615
|
-
stepResults.push({
|
|
272616
|
-
stepIndex: i + 1,
|
|
272617
|
-
name: step.name || `${step.method} ${step.path}`,
|
|
272618
|
-
method: step.method,
|
|
272619
|
-
path: step.path,
|
|
272620
|
-
targetUrl: fullUrl,
|
|
272621
|
-
expectedStatus,
|
|
272622
|
-
actualStatus: responseStatus,
|
|
272623
|
-
durationMs: Date.now() - stepStartTime,
|
|
272624
|
-
passed: false,
|
|
272625
|
-
skipped: false,
|
|
272626
|
-
retryCount: attempts - 1,
|
|
272627
|
-
failureHandling: onFailure === "continue" ? "continued" : "aborted",
|
|
272628
|
-
requestHeaders: injectedHeaders,
|
|
272629
|
-
requestBody: finalBody,
|
|
272630
|
-
responseBody,
|
|
272631
|
-
capturedMemory: [...inputCaptured],
|
|
272632
|
-
error: errorMsg
|
|
272633
|
-
});
|
|
272634
|
-
if (onFailure === "abort")
|
|
272635
|
-
break;
|
|
272636
|
-
continue;
|
|
272637
|
-
}
|
|
272638
|
-
stepResults.push({
|
|
272960
|
+
const onFailure = step.onFailure ?? "abort";
|
|
272961
|
+
const result = {
|
|
272639
272962
|
stepIndex: i + 1,
|
|
272640
|
-
name:
|
|
272963
|
+
name: stepName,
|
|
272641
272964
|
method: step.method,
|
|
272642
272965
|
path: step.path,
|
|
272643
272966
|
targetUrl: fullUrl,
|
|
272644
272967
|
expectedStatus,
|
|
272645
272968
|
actualStatus: responseStatus,
|
|
272646
272969
|
durationMs: Date.now() - stepStartTime,
|
|
272647
|
-
passed:
|
|
272970
|
+
passed: stepPassed,
|
|
272648
272971
|
skipped: false,
|
|
272649
272972
|
retryCount: attempts - 1,
|
|
272650
272973
|
requestHeaders: injectedHeaders,
|
|
272651
272974
|
requestBody: finalBody,
|
|
272975
|
+
...Object.keys(resolvedQuery).length > 0 ? { requestQuery: resolvedQuery } : {},
|
|
272652
272976
|
responseBody,
|
|
272653
|
-
capturedMemory: [...inputCaptured, ...responseCaptured]
|
|
272654
|
-
|
|
272655
|
-
|
|
272656
|
-
|
|
272657
|
-
return {
|
|
272658
|
-
success: allPassed,
|
|
272659
|
-
workflow: {
|
|
272660
|
-
name: workflow.name,
|
|
272661
|
-
description: workflow.description,
|
|
272662
|
-
stepsCount: workflow.steps.length
|
|
272663
|
-
},
|
|
272664
|
-
environment: {
|
|
272665
|
-
name: this.environment.name,
|
|
272666
|
-
baseUrl
|
|
272667
|
-
},
|
|
272668
|
-
stats: {
|
|
272669
|
-
totalSteps: workflow.steps.length,
|
|
272670
|
-
executedSteps: stepResults.length,
|
|
272671
|
-
passedSteps: stepResults.filter((r) => r.passed).length,
|
|
272672
|
-
failedSteps: stepResults.filter((r) => !r.passed).length,
|
|
272673
|
-
totalTimeMs
|
|
272674
|
-
},
|
|
272675
|
-
results: stepResults
|
|
272676
|
-
};
|
|
272677
|
-
}
|
|
272678
|
-
// ── Condition evaluator ────────────────────────────────────────────────────
|
|
272679
|
-
// Resolves the `if` template and applies the declared operator. Returns
|
|
272680
|
-
// `matched: true` when the condition passes (step should execute).
|
|
272681
|
-
async evaluateCondition(condition) {
|
|
272682
|
-
const evaluated = await this.resolveTemplateString(condition.if);
|
|
272683
|
-
let matched;
|
|
272684
|
-
if (condition.exists !== void 0) {
|
|
272685
|
-
const hasValue = evaluated !== "" && evaluated !== "null" && evaluated !== "undefined";
|
|
272686
|
-
matched = condition.exists ? hasValue : !hasValue;
|
|
272687
|
-
} else if (condition.equals !== void 0) {
|
|
272688
|
-
matched = evaluated === condition.equals;
|
|
272689
|
-
} else if (condition.notEquals !== void 0) {
|
|
272690
|
-
matched = evaluated !== condition.notEquals;
|
|
272691
|
-
} else if (condition.contains !== void 0) {
|
|
272692
|
-
matched = evaluated.includes(condition.contains);
|
|
272693
|
-
} else if (condition.greaterThan !== void 0) {
|
|
272694
|
-
matched = Number(evaluated) > condition.greaterThan;
|
|
272695
|
-
} else if (condition.lessThan !== void 0) {
|
|
272696
|
-
matched = Number(evaluated) < condition.lessThan;
|
|
272697
|
-
} else if (condition.in !== void 0) {
|
|
272698
|
-
matched = condition.in.includes(evaluated);
|
|
272699
|
-
} else {
|
|
272700
|
-
matched = evaluated !== "" && evaluated !== "false" && evaluated !== "0" && evaluated !== "null" && evaluated !== "undefined";
|
|
272701
|
-
}
|
|
272702
|
-
return { matched, evaluated };
|
|
272703
|
-
}
|
|
272704
|
-
async resolveTemplateString(value) {
|
|
272705
|
-
const templateRe = /\{\{([^}]+)\}\}/g;
|
|
272706
|
-
let match;
|
|
272707
|
-
const replacements = [];
|
|
272708
|
-
while ((match = templateRe.exec(value)) !== null) {
|
|
272709
|
-
const expr = match[1].trim();
|
|
272710
|
-
if (expr.includes(":") && !expr.startsWith("faker.")) {
|
|
272711
|
-
const colonIdx = expr.indexOf(":");
|
|
272712
|
-
const scope = expr.slice(0, colonIdx);
|
|
272713
|
-
const key = expr.slice(colonIdx + 1);
|
|
272714
|
-
if (scope === "human") {
|
|
272715
|
-
const envVal = process.env[`JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`];
|
|
272716
|
-
if (envVal !== void 0 && envVal !== "") {
|
|
272717
|
-
replacements.push({ placeholder: match[0], resolved: envVal });
|
|
272718
|
-
continue;
|
|
272719
|
-
}
|
|
272977
|
+
capturedMemory: stepPassed ? [...inputCaptured, ...responseCaptured] : [...inputCaptured],
|
|
272978
|
+
...stepPassed ? {} : {
|
|
272979
|
+
failureHandling: onFailure === "continue" ? "continued" : "aborted",
|
|
272980
|
+
error: errorMsg
|
|
272720
272981
|
}
|
|
272721
|
-
|
|
272722
|
-
|
|
272723
|
-
|
|
272982
|
+
};
|
|
272983
|
+
state.results.push(result);
|
|
272984
|
+
if (!stepPassed) {
|
|
272985
|
+
state.allPassed = false;
|
|
272986
|
+
if (connectionCode && CONNECTION_ERROR_CODES.has(connectionCode)) {
|
|
272987
|
+
const remaining = workflow.steps.length - (i + 1);
|
|
272988
|
+
result.failureHandling = "aborted";
|
|
272989
|
+
result.error = `${result.error || "Connection failed"}; aborting early because the target is unreachable${remaining > 0 ? ` (${remaining} step(s) not executed)` : ""}.`;
|
|
272990
|
+
state.halted = true;
|
|
272991
|
+
return "stop";
|
|
272992
|
+
}
|
|
272993
|
+
if (onFailure === "abort") {
|
|
272994
|
+
state.halted = true;
|
|
272995
|
+
return "stop";
|
|
272996
|
+
}
|
|
272997
|
+
}
|
|
272998
|
+
if (kind === "rules") {
|
|
272999
|
+
const condition = step.condition;
|
|
273000
|
+
const outcome = await (0, conditions_1.evaluateRules)(condition.rules, resolve2);
|
|
273001
|
+
result.conditionResult = { evaluated: outcome.reason, matched: outcome.passed };
|
|
273002
|
+
if (outcome.passed)
|
|
273003
|
+
continue;
|
|
273004
|
+
result.conditionOutcome = condition.onFail;
|
|
273005
|
+
const fail = (message) => {
|
|
273006
|
+
result.passed = false;
|
|
273007
|
+
result.error = result.error ? `${result.error}; ${message}` : message;
|
|
273008
|
+
state.allPassed = false;
|
|
273009
|
+
state.halted = true;
|
|
273010
|
+
return "stop";
|
|
273011
|
+
};
|
|
273012
|
+
if (condition.onFail === "continue")
|
|
273013
|
+
return "stop";
|
|
273014
|
+
if (condition.onFail !== "switch")
|
|
273015
|
+
return fail(`Condition failed at step ${i + 1}: ${outcome.reason}`);
|
|
273016
|
+
const target = condition.switchToWorkflow;
|
|
273017
|
+
if (!target)
|
|
273018
|
+
return fail(`Condition failed at step ${i + 1} (${outcome.reason}) and onFail "switch" has no switchToWorkflow`);
|
|
273019
|
+
if (!options?.loadWorkflow) {
|
|
273020
|
+
return fail(`Condition failed at step ${i + 1} (${outcome.reason}); cannot switch to "${target}": this runner was not given a workflow loader`);
|
|
273021
|
+
}
|
|
273022
|
+
const switched = await options.loadWorkflow(target);
|
|
273023
|
+
if (!switched)
|
|
273024
|
+
return fail(`Condition failed at step ${i + 1} (${outcome.reason}); switch target workflow "${target}" was not found`);
|
|
273025
|
+
if (lineage.includes(switched.name)) {
|
|
273026
|
+
return fail(`Condition failed at step ${i + 1}; switching to "${switched.name}" would loop (${[...lineage, switched.name].join(" \u2192 ")})`);
|
|
273027
|
+
}
|
|
273028
|
+
await this.runSteps(switched, options, state, [...lineage, switched.name]);
|
|
273029
|
+
if (state.halted || !condition.returnOnComplete)
|
|
273030
|
+
return "stop";
|
|
273031
|
+
}
|
|
273032
|
+
}
|
|
273033
|
+
return "done";
|
|
273034
|
+
}
|
|
273035
|
+
findPlaceholders(value) {
|
|
273036
|
+
const placeholders = [];
|
|
273037
|
+
let i = 0;
|
|
273038
|
+
while (i < value.length - 1) {
|
|
273039
|
+
if (value[i] !== "{" || value[i + 1] !== "{") {
|
|
273040
|
+
i++;
|
|
272724
273041
|
continue;
|
|
272725
273042
|
}
|
|
272726
|
-
|
|
272727
|
-
|
|
272728
|
-
|
|
272729
|
-
|
|
272730
|
-
|
|
272731
|
-
|
|
272732
|
-
|
|
272733
|
-
|
|
272734
|
-
|
|
272735
|
-
|
|
273043
|
+
let depth = 0;
|
|
273044
|
+
let closed = -1;
|
|
273045
|
+
let j = i + 2;
|
|
273046
|
+
while (j < value.length) {
|
|
273047
|
+
const ch = value[j];
|
|
273048
|
+
if (ch === "{")
|
|
273049
|
+
depth++;
|
|
273050
|
+
else if (ch === "}") {
|
|
273051
|
+
if (depth > 0)
|
|
273052
|
+
depth--;
|
|
273053
|
+
else if (value[j + 1] === "}") {
|
|
273054
|
+
closed = j;
|
|
273055
|
+
break;
|
|
273056
|
+
}
|
|
272736
273057
|
}
|
|
272737
|
-
|
|
273058
|
+
j++;
|
|
272738
273059
|
}
|
|
272739
|
-
|
|
273060
|
+
if (closed === -1)
|
|
273061
|
+
break;
|
|
273062
|
+
placeholders.push({ raw: value.slice(i, closed + 2), expr: value.slice(i + 2, closed).trim(), index: i });
|
|
273063
|
+
i = closed + 2;
|
|
273064
|
+
}
|
|
273065
|
+
return placeholders;
|
|
273066
|
+
}
|
|
273067
|
+
async resolveTemplateString(value, strict = this.strictTemplates) {
|
|
273068
|
+
const placeholders = this.findPlaceholders(value);
|
|
273069
|
+
if (placeholders.length === 0)
|
|
273070
|
+
return value;
|
|
273071
|
+
let result = "";
|
|
273072
|
+
let cursor = 0;
|
|
273073
|
+
for (const placeholder of placeholders) {
|
|
273074
|
+
result += value.slice(cursor, placeholder.index);
|
|
273075
|
+
result += await this.resolveExpression(placeholder.expr, strict, placeholder.raw);
|
|
273076
|
+
cursor = placeholder.index + placeholder.raw.length;
|
|
272740
273077
|
}
|
|
272741
|
-
|
|
272742
|
-
|
|
272743
|
-
|
|
273078
|
+
return result + value.slice(cursor);
|
|
273079
|
+
}
|
|
273080
|
+
async resolveExpression(expr, strict, raw) {
|
|
273081
|
+
if (expr.startsWith("faker.")) {
|
|
273082
|
+
return this.resolveFakerExpression(expr, strict, raw);
|
|
273083
|
+
}
|
|
273084
|
+
if (expr.includes(":")) {
|
|
273085
|
+
const colonIdx = expr.indexOf(":");
|
|
273086
|
+
const scope = expr.slice(0, colonIdx);
|
|
273087
|
+
const key = expr.slice(colonIdx + 1);
|
|
273088
|
+
const envKey = `JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
273089
|
+
if (scope === "human") {
|
|
273090
|
+
const envVal = process.env[envKey];
|
|
273091
|
+
if (envVal !== void 0 && envVal !== "")
|
|
273092
|
+
return envVal;
|
|
273093
|
+
}
|
|
273094
|
+
const memVal = await this.memory(scope).get(key);
|
|
273095
|
+
if (memVal !== null && memVal !== void 0) {
|
|
273096
|
+
return typeof memVal === "string" ? memVal : JSON.stringify(memVal);
|
|
273097
|
+
}
|
|
273098
|
+
if (!strict)
|
|
273099
|
+
return "";
|
|
273100
|
+
if (scope === "human") {
|
|
273101
|
+
throw new Error(`missing human input ${raw} \u2014 set ${envKey} or seed "human:${key}" memory (non-interactive runners cannot prompt)`);
|
|
273102
|
+
}
|
|
273103
|
+
throw new Error(`unresolved template ${raw} \u2014 no value for "${scope}:${key}"; capture it in an earlier step or seed it first`);
|
|
273104
|
+
}
|
|
273105
|
+
return raw;
|
|
273106
|
+
}
|
|
273107
|
+
resolveFakerExpression(expr, strict, raw) {
|
|
273108
|
+
if (/[({]/.test(expr)) {
|
|
273109
|
+
if (!strict)
|
|
273110
|
+
return raw;
|
|
273111
|
+
throw new Error(`unsupported faker expression ${raw} \u2014 parameterized calls are not supported; use a parameterless path like {{faker.internet.email}} or a literal value`);
|
|
273112
|
+
}
|
|
273113
|
+
let fn = faker_1.faker;
|
|
273114
|
+
for (const part of expr.split(".").slice(1))
|
|
273115
|
+
fn = fn?.[part];
|
|
273116
|
+
if (typeof fn !== "function") {
|
|
273117
|
+
if (!strict)
|
|
273118
|
+
return raw;
|
|
273119
|
+
throw new Error(`unknown faker path ${raw} \u2014 use a complete parameterless path like {{faker.internet.email}} or {{faker.string.uuid}}`);
|
|
273120
|
+
}
|
|
273121
|
+
try {
|
|
273122
|
+
return String(fn());
|
|
273123
|
+
} catch (err) {
|
|
273124
|
+
if (!strict)
|
|
273125
|
+
return raw;
|
|
273126
|
+
throw new Error(`faker expression ${raw} failed: ${err?.message || err} \u2014 this faker path needs arguments; parameterized faker calls are not supported`);
|
|
272744
273127
|
}
|
|
272745
|
-
return result;
|
|
272746
273128
|
}
|
|
272747
273129
|
async resolveBodyTemplates(body) {
|
|
272748
|
-
const resolved =
|
|
272749
|
-
|
|
272750
|
-
|
|
273130
|
+
const resolved = await this.resolveDeep(body);
|
|
273131
|
+
return resolved && typeof resolved === "object" && !Array.isArray(resolved) ? resolved : {};
|
|
273132
|
+
}
|
|
273133
|
+
async resolveDeep(value) {
|
|
273134
|
+
if (typeof value === "string")
|
|
273135
|
+
return this.resolveTemplateString(value);
|
|
273136
|
+
if (Array.isArray(value)) {
|
|
273137
|
+
const out = [];
|
|
273138
|
+
for (const item of value)
|
|
273139
|
+
out.push(await this.resolveDeep(item));
|
|
273140
|
+
return out;
|
|
273141
|
+
}
|
|
273142
|
+
if (value && typeof value === "object") {
|
|
273143
|
+
const out = {};
|
|
273144
|
+
for (const [key, item] of Object.entries(value))
|
|
273145
|
+
out[key] = await this.resolveDeep(item);
|
|
273146
|
+
return out;
|
|
272751
273147
|
}
|
|
272752
|
-
return
|
|
273148
|
+
return value;
|
|
272753
273149
|
}
|
|
272754
273150
|
async resolveInjections(inject) {
|
|
272755
273151
|
const headers = {};
|
|
@@ -272762,12 +273158,20 @@ var require_workflow_simulator = __commonJS({
|
|
|
272762
273158
|
strValue = await this.resolveTemplateString(memKeyOrTemplate);
|
|
272763
273159
|
} else {
|
|
272764
273160
|
const [scope, key] = memKeyOrTemplate.includes(":") ? memKeyOrTemplate.split(":", 2) : ["workflow", memKeyOrTemplate];
|
|
272765
|
-
const scopedMemory =
|
|
273161
|
+
const scopedMemory = this.memory(scope);
|
|
272766
273162
|
const value = await scopedMemory.get(key);
|
|
272767
|
-
if (value === null)
|
|
273163
|
+
if (value === null || value === void 0) {
|
|
273164
|
+
if (this.strictTemplates) {
|
|
273165
|
+
throw new Error(`inject target "${target}" references "${scope}:${key}", which is not in memory \u2014 capture it earlier or seed it first`);
|
|
273166
|
+
}
|
|
272768
273167
|
continue;
|
|
273168
|
+
}
|
|
272769
273169
|
strValue = typeof value === "string" ? value : JSON.stringify(value);
|
|
272770
273170
|
}
|
|
273171
|
+
const headerName = target.startsWith("header:") ? target.slice("header:".length) : target.startsWith("body:") ? null : target;
|
|
273172
|
+
if (headerName && headerName.toLowerCase() === "authorization" && strValue && !/^[A-Za-z][A-Za-z0-9-]*\s/.test(strValue)) {
|
|
273173
|
+
strValue = `Bearer ${strValue}`;
|
|
273174
|
+
}
|
|
272771
273175
|
if (target.startsWith("body:")) {
|
|
272772
273176
|
body[target.slice(5)] = strValue;
|
|
272773
273177
|
} else if (target.startsWith("header:")) {
|
|
@@ -272787,7 +273191,7 @@ var require_workflow_simulator = __commonJS({
|
|
|
272787
273191
|
if (value === void 0 || value === null)
|
|
272788
273192
|
continue;
|
|
272789
273193
|
const [scope, key] = memKey.includes(":") ? memKey.split(":", 2) : ["workflow", memKey];
|
|
272790
|
-
const memory =
|
|
273194
|
+
const memory = this.memory(scope);
|
|
272791
273195
|
await memory.set(key, value);
|
|
272792
273196
|
captured.push(`${scope}:${key} = ${JSON.stringify(value)}`);
|
|
272793
273197
|
}
|
|
@@ -272802,31 +273206,64 @@ var require_workflow_simulator = __commonJS({
|
|
|
272802
273206
|
if (value === void 0 || value === null)
|
|
272803
273207
|
continue;
|
|
272804
273208
|
const [scope, key] = memKey.includes(":") ? memKey.split(":", 2) : ["workflow", memKey];
|
|
272805
|
-
const memory =
|
|
273209
|
+
const memory = this.memory(scope);
|
|
272806
273210
|
await memory.set(key, value);
|
|
272807
273211
|
captured.push(`${scope}:${key} = ${JSON.stringify(value)}`);
|
|
272808
273212
|
}
|
|
272809
273213
|
return captured;
|
|
272810
273214
|
}
|
|
272811
273215
|
async resolvePathParams(urlPath, body) {
|
|
272812
|
-
let result = urlPath;
|
|
272813
|
-
const paramMatches =
|
|
273216
|
+
let result = await this.resolveTemplateString(urlPath);
|
|
273217
|
+
const paramMatches = result.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);
|
|
272814
273218
|
if (!paramMatches)
|
|
272815
273219
|
return result;
|
|
272816
|
-
for (const p of paramMatches) {
|
|
273220
|
+
for (const p of [...new Set(paramMatches)].sort((a, b) => b.length - a.length)) {
|
|
272817
273221
|
const paramName = p.substring(1);
|
|
272818
|
-
|
|
272819
|
-
|
|
272820
|
-
|
|
272821
|
-
|
|
272822
|
-
|
|
272823
|
-
|
|
272824
|
-
|
|
272825
|
-
|
|
273222
|
+
let value = body[paramName];
|
|
273223
|
+
if (value === void 0 || value === null) {
|
|
273224
|
+
value = await this.memory("workflow").get(paramName);
|
|
273225
|
+
}
|
|
273226
|
+
if (value !== void 0 && value !== null) {
|
|
273227
|
+
result = result.split(p).join(typeof value === "string" ? value : String(value));
|
|
273228
|
+
continue;
|
|
273229
|
+
}
|
|
273230
|
+
if (this.strictTemplates) {
|
|
273231
|
+
throw new Error(`unresolved path parameter "${p}" in "${urlPath}" \u2014 provide it through a body field, a {{template}}, or a memory key named "${paramName}"`);
|
|
272826
273232
|
}
|
|
272827
273233
|
}
|
|
272828
273234
|
return result;
|
|
272829
273235
|
}
|
|
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
|
+
}
|
|
272830
273267
|
};
|
|
272831
273268
|
exports2.WorkflowSimulator = WorkflowSimulator2;
|
|
272832
273269
|
}
|
|
@@ -272837,6 +273274,22 @@ var require_dist13 = __commonJS({
|
|
|
272837
273274
|
"../../packages/simulator/dist/index.js"(exports2) {
|
|
272838
273275
|
"use strict";
|
|
272839
273276
|
init_cjs_shims();
|
|
273277
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
273278
|
+
if (k2 === void 0) k2 = k;
|
|
273279
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
273280
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
273281
|
+
desc = { enumerable: true, get: function() {
|
|
273282
|
+
return m[k];
|
|
273283
|
+
} };
|
|
273284
|
+
}
|
|
273285
|
+
Object.defineProperty(o, k2, desc);
|
|
273286
|
+
}) : (function(o, m, k, k2) {
|
|
273287
|
+
if (k2 === void 0) k2 = k;
|
|
273288
|
+
o[k2] = m[k];
|
|
273289
|
+
}));
|
|
273290
|
+
var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
|
|
273291
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
273292
|
+
};
|
|
272840
273293
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
272841
273294
|
exports2.WorkflowSimulator = exports2.ResponseValidator = exports2.DataGenerator = exports2.EndpointSimulator = void 0;
|
|
272842
273295
|
var simulator_1 = require_simulator();
|
|
@@ -272855,6 +273308,7 @@ var require_dist13 = __commonJS({
|
|
|
272855
273308
|
Object.defineProperty(exports2, "WorkflowSimulator", { enumerable: true, get: function() {
|
|
272856
273309
|
return workflow_simulator_1.WorkflowSimulator;
|
|
272857
273310
|
} });
|
|
273311
|
+
__exportStar(require_conditions(), exports2);
|
|
272858
273312
|
}
|
|
272859
273313
|
});
|
|
272860
273314
|
|
|
@@ -273009,6 +273463,243 @@ var require_environment_tools = __commonJS({
|
|
|
273009
273463
|
}
|
|
273010
273464
|
});
|
|
273011
273465
|
|
|
273466
|
+
// ../../packages/mcp-server/dist/run-store.js
|
|
273467
|
+
var require_run_store = __commonJS({
|
|
273468
|
+
"../../packages/mcp-server/dist/run-store.js"(exports2) {
|
|
273469
|
+
"use strict";
|
|
273470
|
+
init_cjs_shims();
|
|
273471
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
273472
|
+
if (k2 === void 0) k2 = k;
|
|
273473
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
273474
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
273475
|
+
desc = { enumerable: true, get: function() {
|
|
273476
|
+
return m[k];
|
|
273477
|
+
} };
|
|
273478
|
+
}
|
|
273479
|
+
Object.defineProperty(o, k2, desc);
|
|
273480
|
+
}) : (function(o, m, k, k2) {
|
|
273481
|
+
if (k2 === void 0) k2 = k;
|
|
273482
|
+
o[k2] = m[k];
|
|
273483
|
+
}));
|
|
273484
|
+
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
|
|
273485
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
273486
|
+
}) : function(o, v) {
|
|
273487
|
+
o["default"] = v;
|
|
273488
|
+
});
|
|
273489
|
+
var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
|
|
273490
|
+
var ownKeys = function(o) {
|
|
273491
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
273492
|
+
var ar = [];
|
|
273493
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
273494
|
+
return ar;
|
|
273495
|
+
};
|
|
273496
|
+
return ownKeys(o);
|
|
273497
|
+
};
|
|
273498
|
+
return function(mod) {
|
|
273499
|
+
if (mod && mod.__esModule) return mod;
|
|
273500
|
+
var result = {};
|
|
273501
|
+
if (mod != null) {
|
|
273502
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
273503
|
+
}
|
|
273504
|
+
__setModuleDefault(result, mod);
|
|
273505
|
+
return result;
|
|
273506
|
+
};
|
|
273507
|
+
})();
|
|
273508
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
273509
|
+
exports2.newRunId = newRunId;
|
|
273510
|
+
exports2.runsDir = runsDir;
|
|
273511
|
+
exports2.runDir = runDir;
|
|
273512
|
+
exports2.deriveRunStatus = deriveRunStatus;
|
|
273513
|
+
exports2.writeSimulationRun = writeSimulationRun;
|
|
273514
|
+
exports2.readSimulationRun = readSimulationRun;
|
|
273515
|
+
exports2.readRunStatus = readRunStatus;
|
|
273516
|
+
exports2.writeRunningStatus = writeRunningStatus;
|
|
273517
|
+
exports2.writeRunError = writeRunError;
|
|
273518
|
+
exports2.listRuns = listRuns;
|
|
273519
|
+
exports2.compactSimulationResult = compactSimulationResult;
|
|
273520
|
+
var fs10 = __importStar(require("fs"));
|
|
273521
|
+
var path15 = __importStar(require("path"));
|
|
273522
|
+
var core_1 = require_dist();
|
|
273523
|
+
function newRunId() {
|
|
273524
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
273525
|
+
}
|
|
273526
|
+
function runsDir(jeticDir) {
|
|
273527
|
+
return path15.join(jeticDir, "runs");
|
|
273528
|
+
}
|
|
273529
|
+
function runDir(jeticDir, runId) {
|
|
273530
|
+
return path15.join(runsDir(jeticDir), runId);
|
|
273531
|
+
}
|
|
273532
|
+
function reportPath(jeticDir, runId) {
|
|
273533
|
+
return path15.join(runDir(jeticDir, runId), "simulation-report.json");
|
|
273534
|
+
}
|
|
273535
|
+
function statusPath(jeticDir, runId) {
|
|
273536
|
+
return path15.join(runDir(jeticDir, runId), "simulation-status.json");
|
|
273537
|
+
}
|
|
273538
|
+
function deriveRunStatus(result) {
|
|
273539
|
+
if (result.success)
|
|
273540
|
+
return "passed";
|
|
273541
|
+
if (result.abortReason)
|
|
273542
|
+
return "aborted";
|
|
273543
|
+
return "failed";
|
|
273544
|
+
}
|
|
273545
|
+
function writeSimulationRun(jeticDir, runId, result, meta) {
|
|
273546
|
+
const status = deriveRunStatus(result);
|
|
273547
|
+
const durationMs = result.stats?.totalTimeMs ?? 0;
|
|
273548
|
+
const report = {
|
|
273549
|
+
type: "simulation",
|
|
273550
|
+
runId,
|
|
273551
|
+
workflowName: result.workflow?.name || "workflow",
|
|
273552
|
+
workflowFile: meta.workflowFile,
|
|
273553
|
+
environment: result.environment?.name || "unknown",
|
|
273554
|
+
baseUrl: result.environment?.baseUrl || "",
|
|
273555
|
+
status,
|
|
273556
|
+
startedAt: meta.startedAt,
|
|
273557
|
+
finishedAt: meta.finishedAt,
|
|
273558
|
+
durationMs,
|
|
273559
|
+
result
|
|
273560
|
+
};
|
|
273561
|
+
const rPath = reportPath(jeticDir, runId);
|
|
273562
|
+
(0, core_1.writeJsonSync)(rPath, report);
|
|
273563
|
+
const sPath = statusPath(jeticDir, runId);
|
|
273564
|
+
const statusDoc = {
|
|
273565
|
+
type: "simulation",
|
|
273566
|
+
runId,
|
|
273567
|
+
workflowName: report.workflowName,
|
|
273568
|
+
environment: report.environment,
|
|
273569
|
+
baseUrl: report.baseUrl,
|
|
273570
|
+
status,
|
|
273571
|
+
startedAt: meta.startedAt,
|
|
273572
|
+
finishedAt: meta.finishedAt,
|
|
273573
|
+
durationMs,
|
|
273574
|
+
stats: {
|
|
273575
|
+
totalSteps: result.stats?.totalSteps ?? result.results.length,
|
|
273576
|
+
executedSteps: result.stats?.executedSteps ?? 0,
|
|
273577
|
+
passedSteps: result.stats?.passedSteps ?? 0,
|
|
273578
|
+
failedSteps: result.stats?.failedSteps ?? 0,
|
|
273579
|
+
skippedSteps: result.results.filter((r) => r.skipped).length
|
|
273580
|
+
},
|
|
273581
|
+
reportPath: rPath
|
|
273582
|
+
};
|
|
273583
|
+
(0, core_1.writeJsonSync)(sPath, statusDoc);
|
|
273584
|
+
return { runDir: runDir(jeticDir, runId), reportPath: rPath };
|
|
273585
|
+
}
|
|
273586
|
+
function readSimulationRun(jeticDir, runId) {
|
|
273587
|
+
return (0, core_1.readJsonSync)(reportPath(jeticDir, runId));
|
|
273588
|
+
}
|
|
273589
|
+
function readRunStatus(jeticDir, runId) {
|
|
273590
|
+
return (0, core_1.readJsonSync)(statusPath(jeticDir, runId));
|
|
273591
|
+
}
|
|
273592
|
+
function writeRunningStatus(jeticDir, runId, meta) {
|
|
273593
|
+
const doc = {
|
|
273594
|
+
type: "simulation",
|
|
273595
|
+
runId,
|
|
273596
|
+
workflowName: meta.workflowName,
|
|
273597
|
+
environment: meta.environment,
|
|
273598
|
+
baseUrl: meta.baseUrl,
|
|
273599
|
+
status: "running",
|
|
273600
|
+
startedAt: meta.startedAt,
|
|
273601
|
+
stats: { totalSteps: 0, executedSteps: 0, passedSteps: 0, failedSteps: 0, skippedSteps: 0 },
|
|
273602
|
+
reportPath: reportPath(jeticDir, runId)
|
|
273603
|
+
};
|
|
273604
|
+
(0, core_1.writeJsonSync)(statusPath(jeticDir, runId), doc);
|
|
273605
|
+
}
|
|
273606
|
+
function writeRunError(jeticDir, runId, error, meta) {
|
|
273607
|
+
const doc = {
|
|
273608
|
+
type: "simulation",
|
|
273609
|
+
runId,
|
|
273610
|
+
workflowName: meta.workflowName,
|
|
273611
|
+
environment: meta.environment,
|
|
273612
|
+
baseUrl: meta.baseUrl,
|
|
273613
|
+
status: "error",
|
|
273614
|
+
startedAt: meta.startedAt,
|
|
273615
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
273616
|
+
error: error instanceof Error ? error.message : String(error),
|
|
273617
|
+
stats: { totalSteps: 0, executedSteps: 0, passedSteps: 0, failedSteps: 0, skippedSteps: 0 },
|
|
273618
|
+
reportPath: reportPath(jeticDir, runId)
|
|
273619
|
+
};
|
|
273620
|
+
(0, core_1.writeJsonSync)(statusPath(jeticDir, runId), doc);
|
|
273621
|
+
}
|
|
273622
|
+
function listRuns(jeticDir, limit = 20) {
|
|
273623
|
+
const dir = runsDir(jeticDir);
|
|
273624
|
+
if (!fs10.existsSync(dir))
|
|
273625
|
+
return [];
|
|
273626
|
+
const items = [];
|
|
273627
|
+
for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
|
|
273628
|
+
if (!entry.isDirectory())
|
|
273629
|
+
continue;
|
|
273630
|
+
const runId = entry.name;
|
|
273631
|
+
const simStatus = (0, core_1.readJsonSync)(statusPath(jeticDir, runId));
|
|
273632
|
+
if (simStatus) {
|
|
273633
|
+
items.push({
|
|
273634
|
+
runId,
|
|
273635
|
+
type: "simulation",
|
|
273636
|
+
name: simStatus.workflowName,
|
|
273637
|
+
status: simStatus.status,
|
|
273638
|
+
startedAt: simStatus.startedAt,
|
|
273639
|
+
durationMs: simStatus.durationMs,
|
|
273640
|
+
reportPath: simStatus.reportPath
|
|
273641
|
+
});
|
|
273642
|
+
continue;
|
|
273643
|
+
}
|
|
273644
|
+
const agentReport = (0, core_1.readJsonSync)(path15.join(runDir(jeticDir, runId), "agent-report.json"));
|
|
273645
|
+
if (agentReport) {
|
|
273646
|
+
items.push({
|
|
273647
|
+
runId,
|
|
273648
|
+
type: "agent",
|
|
273649
|
+
name: agentReport.agentId || "agent",
|
|
273650
|
+
status: agentReport.status || "completed",
|
|
273651
|
+
reportPath: path15.join(runDir(jeticDir, runId), "agent-report.json")
|
|
273652
|
+
});
|
|
273653
|
+
}
|
|
273654
|
+
}
|
|
273655
|
+
items.sort((a, b) => b.runId.localeCompare(a.runId));
|
|
273656
|
+
return items.slice(0, limit);
|
|
273657
|
+
}
|
|
273658
|
+
function compactSimulationResult(result, runId, reportFilePath) {
|
|
273659
|
+
const steps = result.results.map((r) => {
|
|
273660
|
+
const summary = {
|
|
273661
|
+
// stepIndex is already 1-based (set as i + 1 by the simulator).
|
|
273662
|
+
step: r.stepIndex,
|
|
273663
|
+
name: r.name,
|
|
273664
|
+
method: r.method,
|
|
273665
|
+
path: r.path,
|
|
273666
|
+
targetUrl: r.targetUrl,
|
|
273667
|
+
status: r.skipped ? "skipped" : r.passed ? "passed" : "failed",
|
|
273668
|
+
expectedStatus: r.expectedStatus,
|
|
273669
|
+
durationMs: r.durationMs
|
|
273670
|
+
};
|
|
273671
|
+
if (!r.skipped)
|
|
273672
|
+
summary.actualStatus = r.actualStatus;
|
|
273673
|
+
if (r.retryCount > 0)
|
|
273674
|
+
summary.retryCount = r.retryCount;
|
|
273675
|
+
if (r.conditionResult) {
|
|
273676
|
+
summary.condition = `${r.conditionResult.evaluated} \u2192 ${r.conditionResult.matched ? "matched" : "not matched"}`;
|
|
273677
|
+
}
|
|
273678
|
+
if (r.error)
|
|
273679
|
+
summary.error = r.error;
|
|
273680
|
+
return summary;
|
|
273681
|
+
});
|
|
273682
|
+
return {
|
|
273683
|
+
ok: result.success,
|
|
273684
|
+
success: result.success,
|
|
273685
|
+
runId,
|
|
273686
|
+
status: deriveRunStatus(result),
|
|
273687
|
+
workflow: result.workflow?.name || "workflow",
|
|
273688
|
+
environment: {
|
|
273689
|
+
name: result.environment?.name || "unknown",
|
|
273690
|
+
baseUrl: result.environment?.baseUrl || ""
|
|
273691
|
+
},
|
|
273692
|
+
stats: result.stats,
|
|
273693
|
+
...result.preflight ? { preflight: result.preflight } : {},
|
|
273694
|
+
...result.abortReason ? { abortReason: result.abortReason } : {},
|
|
273695
|
+
steps,
|
|
273696
|
+
reportPath: reportFilePath,
|
|
273697
|
+
next: result.success ? `Full request/response detail: jetic_get_run { "runId": "${runId}" }` : `Inspect the failing step in full detail: jetic_get_run { "runId": "${runId}" }`
|
|
273698
|
+
};
|
|
273699
|
+
}
|
|
273700
|
+
}
|
|
273701
|
+
});
|
|
273702
|
+
|
|
273012
273703
|
// ../../packages/mcp-server/dist/tools/workflow-tools.js
|
|
273013
273704
|
var require_workflow_tools = __commonJS({
|
|
273014
273705
|
"../../packages/mcp-server/dist/tools/workflow-tools.js"(exports2) {
|
|
@@ -273061,6 +273752,7 @@ var require_workflow_tools = __commonJS({
|
|
|
273061
273752
|
var types_1 = require_types();
|
|
273062
273753
|
var model_1 = require_dist2();
|
|
273063
273754
|
var simulator_1 = require_dist13();
|
|
273755
|
+
var run_store_1 = require_run_store();
|
|
273064
273756
|
exports2.listWorkflowsSchema = zod_1.z.object({
|
|
273065
273757
|
projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder")
|
|
273066
273758
|
});
|
|
@@ -273152,7 +273844,12 @@ var require_workflow_tools = __commonJS({
|
|
|
273152
273844
|
workflow: zod_1.z.string().optional().describe("Workflow slug, name, or JSON file path to execute. If omitted, defaults to available workflow"),
|
|
273153
273845
|
envName: zod_1.z.string().optional().describe("Target environment name (e.g., local, staging)"),
|
|
273154
273846
|
baseUrl: zod_1.z.string().optional().describe("Override target base URL (e.g. http://localhost:3000)"),
|
|
273155
|
-
clearMemory: zod_1.z.boolean().optional().default(false).describe("If true, clears Jetic memory before execution")
|
|
273847
|
+
clearMemory: zod_1.z.boolean().optional().default(false).describe("If true, clears Jetic memory before execution"),
|
|
273848
|
+
includeStepResults: zod_1.z.boolean().optional().default(false).describe("Return every step's full request/response detail (large payload). Default false returns a compact per-step summary plus a runId \u2014 use jetic_get_run for full detail."),
|
|
273849
|
+
async: zod_1.z.boolean().optional().default(false).describe("Start the run in the background and return immediately with a runId. Poll it with jetic_get_run or jetic_list_runs."),
|
|
273850
|
+
preflight: zod_1.z.enum(["abort", "warn", "off"]).optional().default("abort").describe('Probe the target baseUrl before step 1. "abort" (default) fails fast when the server is unreachable; "warn" records the probe and continues; "off" skips it.'),
|
|
273851
|
+
strictTemplates: zod_1.z.boolean().optional().default(true).describe("Fail a step before sending when a {{template}} or :path param has no value (default true). Set false to restore legacy empty-string behaviour."),
|
|
273852
|
+
requestTimeoutMs: zod_1.z.number().int().positive().max(12e4).optional().default(3e4).describe("Per-request timeout in ms (default 30000, max 120000).")
|
|
273156
273853
|
});
|
|
273157
273854
|
async function handleSimulateWorkflow(args) {
|
|
273158
273855
|
const { model, filePath } = (0, types_1.loadModel)(args.projectPath);
|
|
@@ -273238,10 +273935,63 @@ var require_workflow_tools = __commonJS({
|
|
|
273238
273935
|
...targetEnv,
|
|
273239
273936
|
baseUrl: args.baseUrl || targetEnv.baseUrl
|
|
273240
273937
|
};
|
|
273241
|
-
const
|
|
273242
|
-
const
|
|
273938
|
+
const projectRoot = (0, types_1.projectRootOfModel)(filePath);
|
|
273939
|
+
const jeticDir = path15.dirname(filePath);
|
|
273940
|
+
const workflowsDir = path15.join(jeticDir, "workflows");
|
|
273941
|
+
const simulator = new simulator_1.WorkflowSimulator(model, effectiveEnv, { projectRoot });
|
|
273942
|
+
const runId = (0, run_store_1.newRunId)();
|
|
273943
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
273944
|
+
const runMeta = {
|
|
273945
|
+
workflowName: workflowDef.name || selectedItem.name,
|
|
273946
|
+
environment: effectiveEnv.name,
|
|
273947
|
+
baseUrl: effectiveEnv.baseUrl,
|
|
273948
|
+
startedAt
|
|
273949
|
+
};
|
|
273950
|
+
const execute = async () => {
|
|
273951
|
+
const result2 = await simulator.simulateWorkflow(workflowDef, {
|
|
273952
|
+
clearMemory: args.clearMemory ?? false,
|
|
273953
|
+
loadWorkflow: (slugOrName) => (0, model_1.loadWorkflowByName)(workflowsDir, slugOrName),
|
|
273954
|
+
strictTemplates: args.strictTemplates ?? true,
|
|
273955
|
+
preflight: args.preflight ?? "abort",
|
|
273956
|
+
requestTimeoutMs: args.requestTimeoutMs ?? 3e4
|
|
273957
|
+
});
|
|
273958
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
273959
|
+
const { reportPath: reportPath2 } = (0, run_store_1.writeSimulationRun)(jeticDir, runId, result2, {
|
|
273960
|
+
startedAt,
|
|
273961
|
+
finishedAt,
|
|
273962
|
+
workflowFile: selectedItem.filePath
|
|
273963
|
+
});
|
|
273964
|
+
return { result: result2, reportPath: reportPath2 };
|
|
273965
|
+
};
|
|
273966
|
+
if (args.async) {
|
|
273967
|
+
(0, run_store_1.writeRunningStatus)(jeticDir, runId, runMeta);
|
|
273968
|
+
execute().then(() => void 0).catch((err) => {
|
|
273969
|
+
try {
|
|
273970
|
+
(0, run_store_1.writeRunError)(jeticDir, runId, err, runMeta);
|
|
273971
|
+
} catch {
|
|
273972
|
+
}
|
|
273973
|
+
});
|
|
273974
|
+
return {
|
|
273975
|
+
status: "running",
|
|
273976
|
+
runId,
|
|
273977
|
+
workflowName: runMeta.workflowName,
|
|
273978
|
+
environment: { name: effectiveEnv.name, baseUrl: effectiveEnv.baseUrl },
|
|
273979
|
+
message: `Run started in the background against ${effectiveEnv.baseUrl}. Poll with jetic_get_run { "runId": "${runId}" } or jetic_list_runs.`
|
|
273980
|
+
};
|
|
273981
|
+
}
|
|
273982
|
+
const { result, reportPath } = await execute();
|
|
273983
|
+
if (args.includeStepResults) {
|
|
273984
|
+
return {
|
|
273985
|
+
runId,
|
|
273986
|
+
status: (0, run_store_1.deriveRunStatus)(result),
|
|
273987
|
+
reportPath,
|
|
273988
|
+
filePath,
|
|
273989
|
+
workflowFile: selectedItem.filePath,
|
|
273990
|
+
...result
|
|
273991
|
+
};
|
|
273992
|
+
}
|
|
273243
273993
|
return {
|
|
273244
|
-
...result,
|
|
273994
|
+
...(0, run_store_1.compactSimulationResult)(result, runId, reportPath),
|
|
273245
273995
|
filePath,
|
|
273246
273996
|
workflowFile: selectedItem.filePath
|
|
273247
273997
|
};
|
|
@@ -273304,7 +274054,23 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273304
274054
|
var path15 = __importStar(require("path"));
|
|
273305
274055
|
var types_1 = require_types();
|
|
273306
274056
|
var workflow_tools_1 = require_workflow_tools();
|
|
273307
|
-
var
|
|
274057
|
+
var simulator_1 = require_dist13();
|
|
274058
|
+
var model_1 = require_dist2();
|
|
274059
|
+
var conditionRuleSchema = zod_1.z.object({
|
|
274060
|
+
left: zod_1.z.string().min(1).describe('Value to test, usually a template: "{{workflow:role}}"'),
|
|
274061
|
+
operator: zod_1.z.enum(simulator_1.CONDITION_OPERATORS).describe("equals, not_equals, greater_than(_or_equal), less_than(_or_equal), exists, not_exists, is_empty, is_not_empty, contains, not_contains, starts_with, ends_with"),
|
|
274062
|
+
right: zod_1.z.string().optional().describe("Value to compare against (omit for exists/not_exists/is_empty/is_not_empty)")
|
|
274063
|
+
});
|
|
274064
|
+
var rulesConditionSchema = zod_1.z.object({
|
|
274065
|
+
rules: zod_1.z.object({
|
|
274066
|
+
all: zod_1.z.array(conditionRuleSchema).optional().describe("AND \u2014 every rule must hold"),
|
|
274067
|
+
any: zod_1.z.array(conditionRuleSchema).optional().describe("OR \u2014 at least one rule must hold")
|
|
274068
|
+
}).describe("Rule group evaluated after the step runs"),
|
|
274069
|
+
onFail: zod_1.z.enum(["abort", "continue", "switch"]).describe("When the rules do not hold: abort = fail the workflow; continue = stop remaining steps without failing; switch = run switchToWorkflow"),
|
|
274070
|
+
switchToWorkflow: zod_1.z.string().optional().describe('Workflow slug or name to run when onFail is "switch"'),
|
|
274071
|
+
returnOnComplete: zod_1.z.boolean().optional().describe('With onFail "switch": resume this workflow after the target finishes')
|
|
274072
|
+
}).describe("Canonical condition: evaluated after the step; onFail steers the run");
|
|
274073
|
+
var legacyConditionSchema = zod_1.z.object({
|
|
273308
274074
|
if: zod_1.z.string().min(1).describe('Template expression to resolve, e.g. "{{workflow:role}}"'),
|
|
273309
274075
|
equals: zod_1.z.string().optional().describe("Resolved value must equal this string"),
|
|
273310
274076
|
notEquals: zod_1.z.string().optional().describe("Resolved value must NOT equal this string"),
|
|
@@ -273313,7 +274079,8 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273313
274079
|
greaterThan: zod_1.z.number().optional().describe("Numeric greater-than comparison"),
|
|
273314
274080
|
lessThan: zod_1.z.number().optional().describe("Numeric less-than comparison"),
|
|
273315
274081
|
in: zod_1.z.array(zod_1.z.string()).optional().describe("Resolved value must be one of these strings")
|
|
273316
|
-
}).describe("
|
|
274082
|
+
}).describe("Legacy guard: evaluated before the step; if not met the step is skipped");
|
|
274083
|
+
var stepConditionSchema = zod_1.z.union([rulesConditionSchema, legacyConditionSchema]);
|
|
273317
274084
|
var stepRetrySchema = zod_1.z.object({
|
|
273318
274085
|
times: zod_1.z.number().int().positive().max(10).describe("Max retry attempts (1\u201310)"),
|
|
273319
274086
|
delayMs: zod_1.z.number().int().nonnegative().optional().describe("Delay between retries in ms (default 0)")
|
|
@@ -273321,14 +274088,16 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273321
274088
|
exports2.workflowStepSchema = zod_1.z.object({
|
|
273322
274089
|
name: zod_1.z.string().min(1).describe("Step name (must be unique within the workflow)"),
|
|
273323
274090
|
method: zod_1.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]).describe("HTTP method"),
|
|
273324
|
-
path: zod_1.z.string().min(1).describe("Route path
|
|
274091
|
+
path: zod_1.z.string().min(1).describe("Route path. :params resolve from a body field, or a memory key of the same name; {{templates}} are also resolved in the path."),
|
|
273325
274092
|
description: zod_1.z.string().optional().describe("What this step does"),
|
|
273326
|
-
body: zod_1.z.record(zod_1.z.any()).optional().describe("Request payload. Values support {{faker.*}}, {{scope:key}}, {{workflow:key}} and {{human:key}} templates"),
|
|
274093
|
+
body: zod_1.z.record(zod_1.z.any()).optional().describe("Request payload (nested objects/arrays are templated too). Values support {{faker.*}}, {{scope:key}}, {{workflow:key}} and {{human:key}} templates"),
|
|
274094
|
+
query: zod_1.z.record(zod_1.z.any()).optional().describe("URL query parameters (deep-resolved templates). Use this instead of a body on GET/HEAD steps."),
|
|
274095
|
+
timeoutMs: zod_1.z.number().int().positive().optional().describe("Per-step request timeout in ms (overrides the run default, which is 30000; max 120000)."),
|
|
273327
274096
|
inject: zod_1.z.record(zod_1.z.string()).optional().describe('Header/body injection: "header:Name" or "body:field" \u2192 "scope:key" or "{{template}}"'),
|
|
273328
274097
|
capture: zod_1.z.record(zod_1.z.string()).optional().describe('Save RESPONSE fields to memory: "scope:key" \u2192 "dot.path.in.response"'),
|
|
273329
274098
|
captureInput: zod_1.z.record(zod_1.z.string()).optional().describe('Save REQUEST body fields to memory BEFORE sending: "scope:key" \u2192 "bodyField"'),
|
|
273330
274099
|
expectStatus: zod_1.z.number().int().positive().optional().describe("Expected HTTP status (default 200; any 2xx passes when expecting 2xx)"),
|
|
273331
|
-
condition: stepConditionSchema.optional().describe(
|
|
274100
|
+
condition: stepConditionSchema.optional().describe("Preferred: { rules: { all|any: [{ left, operator, right }] }, onFail: abort|continue|switch } \u2014 evaluated after the step. Legacy: { if, equals\u2026 } skips the step when not met."),
|
|
273332
274101
|
onFailure: zod_1.z.enum(["abort", "continue"]).optional().describe('"abort" (default): stop the workflow on failure. "continue": log failure and proceed (captures skipped).'),
|
|
273333
274102
|
continueOnStatus: zod_1.z.array(zod_1.z.number().int()).optional().describe("Additional HTTP status codes treated as pass for this step (e.g. [404])."),
|
|
273334
274103
|
retry: stepRetrySchema.optional().describe("Retry on failure \u2014 overrides the workflow-level global retry.")
|
|
@@ -273343,7 +274112,6 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273343
274112
|
retry: stepRetrySchema.optional().describe("Global retry default applied to all steps (overridden per step)"),
|
|
273344
274113
|
steps: zod_1.z.array(exports2.workflowStepSchema).min(1).describe("Ordered steps; later steps can use memory captured by earlier ones")
|
|
273345
274114
|
}).passthrough();
|
|
273346
|
-
var TEMPLATE_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
273347
274115
|
var KNOWN_WORKFLOW_KEYS = /* @__PURE__ */ new Set(["name", "description", "environment", "steps", "generatedAt", "retry"]);
|
|
273348
274116
|
var KNOWN_STEP_KEYS = /* @__PURE__ */ new Set([
|
|
273349
274117
|
"name",
|
|
@@ -273355,6 +274123,8 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273355
274123
|
"captureInput",
|
|
273356
274124
|
"expectStatus",
|
|
273357
274125
|
"body",
|
|
274126
|
+
"query",
|
|
274127
|
+
"timeoutMs",
|
|
273358
274128
|
"condition",
|
|
273359
274129
|
"onFailure",
|
|
273360
274130
|
"continueOnStatus",
|
|
@@ -273392,10 +274162,35 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273392
274162
|
}
|
|
273393
274163
|
function collectTemplateRefs(value, out) {
|
|
273394
274164
|
if (typeof value === "string") {
|
|
273395
|
-
|
|
273396
|
-
|
|
273397
|
-
|
|
273398
|
-
|
|
274165
|
+
let i = 0;
|
|
274166
|
+
while (i < value.length) {
|
|
274167
|
+
const open = value.indexOf("{{", i);
|
|
274168
|
+
if (open === -1)
|
|
274169
|
+
break;
|
|
274170
|
+
let depth = 0;
|
|
274171
|
+
let end = -1;
|
|
274172
|
+
for (let j = open + 2; j < value.length; j++) {
|
|
274173
|
+
const ch = value[j];
|
|
274174
|
+
if (ch === "{") {
|
|
274175
|
+
depth++;
|
|
274176
|
+
} else if (ch === "}") {
|
|
274177
|
+
if (depth > 0) {
|
|
274178
|
+
depth--;
|
|
274179
|
+
} else if (value[j + 1] === "}") {
|
|
274180
|
+
end = j;
|
|
274181
|
+
break;
|
|
274182
|
+
} else {
|
|
274183
|
+
break;
|
|
274184
|
+
}
|
|
274185
|
+
}
|
|
274186
|
+
}
|
|
274187
|
+
if (end === -1) {
|
|
274188
|
+
i = open + 2;
|
|
274189
|
+
continue;
|
|
274190
|
+
}
|
|
274191
|
+
out.push(value.slice(open + 2, end).trim());
|
|
274192
|
+
i = end + 2;
|
|
274193
|
+
}
|
|
273399
274194
|
} else if (Array.isArray(value)) {
|
|
273400
274195
|
for (const v of value)
|
|
273401
274196
|
collectTemplateRefs(v, out);
|
|
@@ -273411,6 +274206,58 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273411
274206
|
const prefix = wfPath.split("/").slice(0, 3).join("/");
|
|
273412
274207
|
return model.filter((e) => e.path.startsWith(prefix)).map((e) => `${e.method} ${e.path}`).slice(0, 3);
|
|
273413
274208
|
}
|
|
274209
|
+
function checkRulesCondition(cond, stepNo, sName, errors, warnings, ownName, workflowsDir2) {
|
|
274210
|
+
const refs = [];
|
|
274211
|
+
const rules = cond.rules;
|
|
274212
|
+
let ruleCount = 0;
|
|
274213
|
+
for (const group of ["all", "any"]) {
|
|
274214
|
+
const list = rules[group];
|
|
274215
|
+
if (list === void 0)
|
|
274216
|
+
continue;
|
|
274217
|
+
if (!Array.isArray(list)) {
|
|
274218
|
+
err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition.rules.${group}" must be an array of rules.`, `Use "${group}": [{ "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }].`);
|
|
274219
|
+
continue;
|
|
274220
|
+
}
|
|
274221
|
+
list.forEach((rule, ri) => {
|
|
274222
|
+
ruleCount++;
|
|
274223
|
+
const where = `Step ${stepNo} condition.rules.${group}[${ri}]`;
|
|
274224
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) {
|
|
274225
|
+
err(errors, stepNo, sName, "BAD_CONDITION", `${where} must be an object { left, operator, right? }.`, 'Use { "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }.');
|
|
274226
|
+
return;
|
|
274227
|
+
}
|
|
274228
|
+
if (typeof rule.left !== "string" || !rule.left.trim()) {
|
|
274229
|
+
err(errors, stepNo, sName, "BAD_CONDITION", `${where} needs a non-empty "left".`, 'Set "left" to a value or template like "{{workflow:role}}".');
|
|
274230
|
+
} else if (!/\{\{[^}]+\}\}/.test(rule.left)) {
|
|
274231
|
+
warn(warnings, stepNo, sName, "CONDITION_BARE_IF", `${where} "left" ("${rule.left}") contains no {{template}} and is compared literally.`, "Use {{workflow:key}} or {{scope:key}} to reference captured memory values.");
|
|
274232
|
+
}
|
|
274233
|
+
if (!simulator_1.CONDITION_OPERATORS.includes(rule.operator)) {
|
|
274234
|
+
err(errors, stepNo, sName, "BAD_CONDITION_OPERATOR", `${where} has unknown operator ${JSON.stringify(rule.operator)}.`, `Use one of: ${simulator_1.CONDITION_OPERATORS.join(", ")}.`);
|
|
274235
|
+
} else if (!simulator_1.UNARY_OPERATORS.includes(rule.operator) && rule.right === void 0) {
|
|
274236
|
+
err(errors, stepNo, sName, "CONDITION_MISSING_RIGHT", `${where} uses "${rule.operator}" but has no "right" operand.`, 'Add "right", or use exists / not_exists / is_empty / is_not_empty.');
|
|
274237
|
+
}
|
|
274238
|
+
collectTemplateRefs(rule.left, refs);
|
|
274239
|
+
collectTemplateRefs(rule.right, refs);
|
|
274240
|
+
});
|
|
274241
|
+
}
|
|
274242
|
+
if (ruleCount === 0 && !errors.some((e) => e.stepIndex === stepNo && e.code === "BAD_CONDITION")) {
|
|
274243
|
+
warn(warnings, stepNo, sName, "CONDITION_NO_RULES", `Step ${stepNo} condition has no rules, so it always passes.`, 'Add rules under "rules.all" or "rules.any".');
|
|
274244
|
+
}
|
|
274245
|
+
if (!["abort", "continue", "switch"].includes(cond.onFail)) {
|
|
274246
|
+
err(errors, stepNo, sName, "BAD_CONDITION_ONFAIL", `Step ${stepNo} condition needs "onFail" of "abort", "continue" or "switch" (got ${JSON.stringify(cond.onFail)}).`, '"abort" fails the workflow, "continue" stops the remaining steps quietly, "switch" runs switchToWorkflow.');
|
|
274247
|
+
} else if (cond.onFail === "switch") {
|
|
274248
|
+
const target = typeof cond.switchToWorkflow === "string" ? cond.switchToWorkflow.trim() : "";
|
|
274249
|
+
if (!target) {
|
|
274250
|
+
err(errors, stepNo, sName, "CONDITION_SWITCH_NO_TARGET", `Step ${stepNo} uses onFail "switch" but has no "switchToWorkflow".`, 'Set "switchToWorkflow" to the slug or name of the workflow to run.');
|
|
274251
|
+
} else if (ownName && target.toLowerCase() === ownName.trim().toLowerCase()) {
|
|
274252
|
+
err(errors, stepNo, sName, "CONDITION_SWITCH_SELF", `Step ${stepNo} switches to this same workflow ("${target}"), which would loop.`, 'Point "switchToWorkflow" at a different workflow.');
|
|
274253
|
+
} else if (!(0, model_1.loadWorkflowByName)(workflowsDir2, target)) {
|
|
274254
|
+
warn(warnings, stepNo, sName, "CONDITION_SWITCH_TARGET_MISSING", `Step ${stepNo} switches to "${target}", which does not exist in .jetic/workflows yet.`, "Create that workflow before running this one, or the run fails when the condition triggers.");
|
|
274255
|
+
}
|
|
274256
|
+
} else if (cond.returnOnComplete !== void 0 || cond.switchToWorkflow !== void 0) {
|
|
274257
|
+
warn(warnings, stepNo, sName, "CONDITION_UNUSED_SWITCH_FIELDS", `Step ${stepNo} sets switchToWorkflow/returnOnComplete but onFail is "${cond.onFail}", so they are ignored.`, 'Use onFail "switch" or remove those fields.');
|
|
274258
|
+
}
|
|
274259
|
+
return refs;
|
|
274260
|
+
}
|
|
273414
274261
|
function validateWorkflowDefinition2(wf, opts) {
|
|
273415
274262
|
const errors = [];
|
|
273416
274263
|
const warnings = [];
|
|
@@ -273439,6 +274286,25 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273439
274286
|
const seenNames = /* @__PURE__ */ new Set();
|
|
273440
274287
|
const defined = /* @__PURE__ */ new Set();
|
|
273441
274288
|
const humanKeys = /* @__PURE__ */ new Set();
|
|
274289
|
+
const refsUsedAnywhere = /* @__PURE__ */ new Set();
|
|
274290
|
+
for (const rawStep of wf.steps) {
|
|
274291
|
+
const stepRefs = [];
|
|
274292
|
+
collectTemplateRefs(rawStep?.body, stepRefs);
|
|
274293
|
+
collectTemplateRefs(rawStep?.query, stepRefs);
|
|
274294
|
+
collectTemplateRefs(rawStep?.path, stepRefs);
|
|
274295
|
+
const inject = rawStep?.inject;
|
|
274296
|
+
if (inject && typeof inject === "object" && !Array.isArray(inject)) {
|
|
274297
|
+
for (const v of Object.values(inject))
|
|
274298
|
+
collectTemplateRefs(v, stepRefs);
|
|
274299
|
+
}
|
|
274300
|
+
for (const ref of stepRefs) {
|
|
274301
|
+
if (ref.startsWith("faker.") || !ref.includes(":"))
|
|
274302
|
+
continue;
|
|
274303
|
+
const { scope, key } = splitMemKey(ref);
|
|
274304
|
+
if (key)
|
|
274305
|
+
refsUsedAnywhere.add(`${scope}:${key}`);
|
|
274306
|
+
}
|
|
274307
|
+
}
|
|
273442
274308
|
wf.steps.forEach((rawStep, i) => {
|
|
273443
274309
|
const stepNo = i + 1;
|
|
273444
274310
|
const sName = typeof rawStep?.name === "string" ? rawStep.name : void 0;
|
|
@@ -273447,19 +274313,22 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273447
274313
|
return;
|
|
273448
274314
|
}
|
|
273449
274315
|
const step = rawStep;
|
|
274316
|
+
let rulesRefs = [];
|
|
273450
274317
|
for (const key of Object.keys(step)) {
|
|
273451
274318
|
if (MODEL_DIALECT_KEYS.has(key)) {
|
|
273452
274319
|
err(errors, stepNo, sName, "MODEL_DIALECT_KEY", `Step ${stepNo} uses "${key}" from the model-embedded workflow dialect, which file workflows ignore.`, 'File-workflow equivalent: split "call" into method+path; replace auth/bind with inject/capture.');
|
|
273453
274320
|
} else if (!KNOWN_STEP_KEYS.has(key)) {
|
|
273454
|
-
warn(warnings, stepNo, sName, "UNKNOWN_STEP_KEY", `Step ${stepNo} has unknown key "${key}" which the simulator ignores.`, "Remove it or check the field name (valid: name, method, path, description, body, inject, capture, captureInput, expectStatus).");
|
|
274321
|
+
warn(warnings, stepNo, sName, "UNKNOWN_STEP_KEY", `Step ${stepNo} has unknown key "${key}" which the simulator ignores.`, "Remove it or check the field name (valid: name, method, path, description, body, query, timeoutMs, inject, capture, captureInput, expectStatus, condition, onFailure, continueOnStatus, retry).");
|
|
273455
274322
|
}
|
|
273456
274323
|
}
|
|
273457
274324
|
if ("condition" in step && step.condition !== void 0) {
|
|
273458
274325
|
const cond = step.condition;
|
|
273459
274326
|
if (!cond || typeof cond !== "object" || Array.isArray(cond)) {
|
|
273460
|
-
err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must be an object.`, 'Use { "condition": { "
|
|
274327
|
+
err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must be an object.`, 'Use { "condition": { "rules": { "all": [{ "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }] }, "onFail": "abort" } }.');
|
|
274328
|
+
} else if ((0, simulator_1.conditionKind)(cond) === "rules") {
|
|
274329
|
+
rulesRefs = checkRulesCondition(cond, stepNo, sName, errors, warnings, wf.name, path15.join(projectRoot, ".jetic", "workflows"));
|
|
273461
274330
|
} else if (!cond.if || typeof cond.if !== "string" || !cond.if.trim()) {
|
|
273462
|
-
err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition"
|
|
274331
|
+
err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" is neither a rules condition (needs "rules" + "onFail") nor a legacy guard (needs a non-empty "if"). The runners fail steps with an unrecognized condition.`, 'Use { "rules": { "all": [{ "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }] }, "onFail": "abort" }.');
|
|
273463
274332
|
} else {
|
|
273464
274333
|
const operators = ["equals", "notEquals", "contains", "exists", "greaterThan", "lessThan", "in"];
|
|
273465
274334
|
const usedOps = operators.filter((op) => op in cond && cond[op] !== void 0);
|
|
@@ -273529,30 +274398,46 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273529
274398
|
if (step.body !== void 0 && (typeof step.body !== "object" || step.body === null || Array.isArray(step.body))) {
|
|
273530
274399
|
err(errors, stepNo, sName, "BAD_BODY", `Step ${stepNo} "body" must be an object of field -> value.`, 'Use { "email": "{{faker.internet.email}}" }.');
|
|
273531
274400
|
}
|
|
273532
|
-
if (
|
|
273533
|
-
|
|
274401
|
+
if (step.query !== void 0 && (typeof step.query !== "object" || step.query === null || Array.isArray(step.query))) {
|
|
274402
|
+
err(errors, stepNo, sName, "BAD_QUERY", `Step ${stepNo} "query" must be an object of param -> value.`, 'Use { "query": { "page": 1, "search": "{{workflow:term}}" } }.');
|
|
274403
|
+
}
|
|
274404
|
+
if ((method === "GET" || method === "HEAD") && step.body && typeof step.body === "object" && !Array.isArray(step.body)) {
|
|
274405
|
+
const nested = Object.values(step.body).some((v) => v !== null && typeof v === "object");
|
|
274406
|
+
if (nested) {
|
|
274407
|
+
warn(warnings, stepNo, sName, "GET_WITH_BODY", `Step ${stepNo} is ${method} with nested body values \u2014 they are stringified into the URL query string.`, 'Move flat filters to the "query" field instead.');
|
|
274408
|
+
}
|
|
273534
274409
|
}
|
|
273535
|
-
if (endpoint?.requestBody?.fields && step.body && typeof step.body === "object") {
|
|
274410
|
+
if (endpoint?.requestBody?.fields && step.body && typeof step.body === "object" && method !== "GET" && method !== "HEAD") {
|
|
273536
274411
|
for (const field of Object.keys(step.body)) {
|
|
273537
274412
|
if (!(field in endpoint.requestBody.fields)) {
|
|
273538
274413
|
warn(warnings, stepNo, sName, "UNKNOWN_BODY_FIELD", `Step ${stepNo} sends body field "${field}" not declared on ${method} ${wfPath}.`, "Check the spelling, or extend the endpoint with jetic_update_endpoint.");
|
|
273539
274414
|
}
|
|
273540
274415
|
}
|
|
273541
274416
|
}
|
|
274417
|
+
if (step.query && typeof step.query === "object" && !Array.isArray(step.query) && endpoint) {
|
|
274418
|
+
const declaredQuery = (endpoint.parameters || []).filter((p) => p.in === "query");
|
|
274419
|
+
if (declaredQuery.length > 0) {
|
|
274420
|
+
for (const param of Object.keys(step.query)) {
|
|
274421
|
+
if (!declaredQuery.some((p) => p.name === param)) {
|
|
274422
|
+
warn(warnings, stepNo, sName, "UNKNOWN_QUERY_PARAM", `Step ${stepNo} sends query param "${param}" not declared on ${method} ${wfPath}.`, "Check the spelling, or extend the endpoint with jetic_update_endpoint.");
|
|
274423
|
+
}
|
|
274424
|
+
}
|
|
274425
|
+
}
|
|
274426
|
+
}
|
|
273542
274427
|
const pathParams = wfPath.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g) || [];
|
|
273543
274428
|
for (const p of pathParams) {
|
|
273544
274429
|
const pname = p.substring(1);
|
|
273545
274430
|
const inBody = step.body && typeof step.body === "object" && pname in step.body;
|
|
274431
|
+
const inInjectBody = step.inject && typeof step.inject === "object" && !Array.isArray(step.inject) && Object.keys(step.inject).includes(`body:${pname}`);
|
|
273546
274432
|
const inMemory = [...defined].some((d) => d.split(":")[1] === pname) || [...persistedMemory].some((d) => d.split(":")[1] === pname);
|
|
273547
|
-
if (!inBody && !inMemory) {
|
|
273548
|
-
err(errors, stepNo, sName, "UNRESOLVED_PATH_PARAM", `Path param ":${pname}" in step ${stepNo} has no body field or memory source \u2014 the URL will literally contain ":${pname}" and fail.`, `Add "${pname}" to this step's body
|
|
274433
|
+
if (!inBody && !inInjectBody && !inMemory) {
|
|
274434
|
+
err(errors, stepNo, sName, "UNRESOLVED_PATH_PARAM", `Path param ":${pname}" in step ${stepNo} has no body field or memory source \u2014 the URL will literally contain ":${pname}" and fail.`, `Add "${pname}" to this step's body (or inject { "body:${pname}": "<scope:key>" }), capture it as memory in an earlier step (capture: { "workflow:${pname}": "data.${pname}" }), or inline it as "/users/{{workflow:${pname}}}".`);
|
|
273549
274435
|
}
|
|
273550
274436
|
}
|
|
273551
|
-
if (/\{\{[^}]+\}\}/.test(wfPath)) {
|
|
273552
|
-
warn(warnings, stepNo, sName, "PATH_TEMPLATE", `Step ${stepNo} path contains a {{template}} \u2014 paths only resolve :params, templates work in body/inject.`, 'Use "/api/users/:id" with an "id" body field or captured memory key.');
|
|
273553
|
-
}
|
|
273554
274437
|
const refs = [];
|
|
273555
274438
|
collectTemplateRefs(step.body, refs);
|
|
274439
|
+
collectTemplateRefs(step.query, refs);
|
|
274440
|
+
collectTemplateRefs(step.path, refs);
|
|
273556
274441
|
if (step.inject && typeof step.inject === "object") {
|
|
273557
274442
|
for (const v of Object.values(step.inject))
|
|
273558
274443
|
collectTemplateRefs(v, refs);
|
|
@@ -273561,6 +274446,8 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273561
274446
|
if (ref.startsWith("faker.")) {
|
|
273562
274447
|
if (ref.split(".").length < 3) {
|
|
273563
274448
|
warn(warnings, stepNo, sName, "SUSPICIOUS_FAKER", `"{{${ref}}}" looks like an incomplete faker path.`, "Use full paths like {{faker.internet.email}} or {{faker.string.uuid}}.");
|
|
274449
|
+
} else if (ref.includes("(") || ref.includes("{") || ref.includes("[")) {
|
|
274450
|
+
err(errors, stepNo, sName, "UNSUPPORTED_FAKER_CALL", `"{{${ref}}}" in step ${stepNo} passes arguments \u2014 the simulator only supports parameterless faker paths.`, "Use a plain path like {{faker.internet.email}} or {{faker.number.int}}, or hard-code the value.");
|
|
273564
274451
|
}
|
|
273565
274452
|
continue;
|
|
273566
274453
|
}
|
|
@@ -273619,7 +274506,13 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273619
274506
|
err(errors, stepNo, sName, "BAD_ON_FAILURE", `Step ${stepNo} "onFailure" must be "abort" or "continue" (got "${step.onFailure}").`, 'Use "abort" (default, stops the workflow) or "continue" (logs failure, proceeds).');
|
|
273620
274507
|
}
|
|
273621
274508
|
if (step.onFailure === "continue" && step.capture && Object.keys(step.capture).length > 0) {
|
|
273622
|
-
|
|
274509
|
+
const consumedDownstream = Object.keys(step.capture).some((k) => {
|
|
274510
|
+
const { scope, key } = splitMemKey(k);
|
|
274511
|
+
return key && refsUsedAnywhere.has(`${scope}:${key}`);
|
|
274512
|
+
});
|
|
274513
|
+
if (consumedDownstream) {
|
|
274514
|
+
warn(warnings, stepNo, sName, "CONTINUE_WITH_CAPTURE", `Step ${stepNo} uses onFailure:"continue" but later steps depend on its captures \u2014 if the request fails, those captures are skipped and downstream steps will fail or be skipped.`, "Make the run conditional on the capture, or move the capture to a step that must pass.");
|
|
274515
|
+
}
|
|
273623
274516
|
}
|
|
273624
274517
|
}
|
|
273625
274518
|
if (step.continueOnStatus !== void 0) {
|
|
@@ -273658,6 +274551,25 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273658
274551
|
}
|
|
273659
274552
|
}
|
|
273660
274553
|
}
|
|
274554
|
+
for (const ref of rulesRefs) {
|
|
274555
|
+
if (ref.startsWith("faker."))
|
|
274556
|
+
continue;
|
|
274557
|
+
if (!ref.includes(":")) {
|
|
274558
|
+
err(errors, stepNo, sName, "BAD_TEMPLATE", `"{{${ref}}}" in the condition of step ${stepNo} needs a scope ("{{workflow:key}}").`, "Bare {{key}} is left literal \u2014 add the scope prefix.");
|
|
274559
|
+
continue;
|
|
274560
|
+
}
|
|
274561
|
+
const { scope, key } = splitMemKey(ref);
|
|
274562
|
+
if (!key)
|
|
274563
|
+
continue;
|
|
274564
|
+
if (scope === "human") {
|
|
274565
|
+
humanKeys.add(key);
|
|
274566
|
+
continue;
|
|
274567
|
+
}
|
|
274568
|
+
const full = `${scope}:${key}`;
|
|
274569
|
+
if (!defined.has(full) && !persistedMemory.has(full)) {
|
|
274570
|
+
err(errors, stepNo, sName, "UNDEFINED_MEMORY_REF", `"{{${ref}}}" in the condition of step ${stepNo} is not captured by this or an earlier step and is not in memory.json.`, `Capture "${full}" in this step or an earlier one before referencing it in a condition.`);
|
|
274571
|
+
}
|
|
274572
|
+
}
|
|
273661
274573
|
});
|
|
273662
274574
|
return toReport(errors, warnings, humanKeys);
|
|
273663
274575
|
}
|
|
@@ -288323,6 +289235,7 @@ var require_response_envelope = __commonJS({
|
|
|
288323
289235
|
"WRITE_DISABLED",
|
|
288324
289236
|
"INTERNAL"
|
|
288325
289237
|
];
|
|
289238
|
+
var MAX_MESSAGE_LENGTH = 500;
|
|
288326
289239
|
function errorCodeFrom(error) {
|
|
288327
289240
|
if (typeof error !== "object" || error === null || !("code" in error)) {
|
|
288328
289241
|
return "INTERNAL";
|
|
@@ -288330,6 +289243,28 @@ var require_response_envelope = __commonJS({
|
|
|
288330
289243
|
const code = error.code;
|
|
288331
289244
|
return typeof code === "string" && ERROR_CODES.includes(code) ? code : "INTERNAL";
|
|
288332
289245
|
}
|
|
289246
|
+
function describeError(error) {
|
|
289247
|
+
const record = typeof error === "object" && error !== null ? error : {};
|
|
289248
|
+
const issues = Array.isArray(record.issues) ? record.issues : void 0;
|
|
289249
|
+
if (issues && issues.length > 0) {
|
|
289250
|
+
const parts = issues.slice(0, 5).map((issue) => {
|
|
289251
|
+
const path15 = Array.isArray(issue?.path) ? issue.path.join(".") : "";
|
|
289252
|
+
return `${path15 ? `${path15}: ` : ""}${issue?.message ?? "invalid"}`;
|
|
289253
|
+
});
|
|
289254
|
+
const more = issues.length > 5 ? ` (+${issues.length - 5} more)` : "";
|
|
289255
|
+
return {
|
|
289256
|
+
message: `Invalid input \u2014 ${parts.join("; ")}${more}`,
|
|
289257
|
+
hint: "Fix the flagged argument(s) and retry. Use the tool schema for exact field types."
|
|
289258
|
+
};
|
|
289259
|
+
}
|
|
289260
|
+
const rawMessage = typeof record.message === "string" ? record.message.trim() : "";
|
|
289261
|
+
const message = rawMessage ? rawMessage.length > MAX_MESSAGE_LENGTH ? `${rawMessage.slice(0, MAX_MESSAGE_LENGTH)}\u2026` : rawMessage : "Tool execution failed.";
|
|
289262
|
+
const customHint = typeof record.hint === "string" && record.hint.trim() ? record.hint.trim() : "";
|
|
289263
|
+
return {
|
|
289264
|
+
message,
|
|
289265
|
+
hint: customHint || "Check the project configuration and arguments, then retry."
|
|
289266
|
+
};
|
|
289267
|
+
}
|
|
288333
289268
|
function toTextResult(data) {
|
|
288334
289269
|
return {
|
|
288335
289270
|
content: [{
|
|
@@ -288339,6 +289274,7 @@ var require_response_envelope = __commonJS({
|
|
|
288339
289274
|
};
|
|
288340
289275
|
}
|
|
288341
289276
|
function toErrorResult(error) {
|
|
289277
|
+
const { message, hint } = describeError(error);
|
|
288342
289278
|
return {
|
|
288343
289279
|
isError: true,
|
|
288344
289280
|
content: [{
|
|
@@ -288347,8 +289283,8 @@ var require_response_envelope = __commonJS({
|
|
|
288347
289283
|
ok: false,
|
|
288348
289284
|
error: {
|
|
288349
289285
|
code: errorCodeFrom(error),
|
|
288350
|
-
message
|
|
288351
|
-
hint
|
|
289286
|
+
message,
|
|
289287
|
+
hint
|
|
288352
289288
|
}
|
|
288353
289289
|
}, null, 2)
|
|
288354
289290
|
}]
|
|
@@ -288691,6 +289627,47 @@ var require_workflow_designer_tools = __commonJS({
|
|
|
288691
289627
|
if (Object.keys(inject).length > 0)
|
|
288692
289628
|
step.inject = inject;
|
|
288693
289629
|
}
|
|
289630
|
+
const pathParams = [...new Set(endpoint.path.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g) || [])].map((p) => p.slice(1));
|
|
289631
|
+
for (const pname of pathParams) {
|
|
289632
|
+
const inBody = step.body && typeof step.body === "object" && pname in step.body;
|
|
289633
|
+
if (inBody)
|
|
289634
|
+
continue;
|
|
289635
|
+
const memKey = [...producedKeys].find((k) => (k.split(":").pop() || "").toLowerCase() === pname.toLowerCase());
|
|
289636
|
+
if (memKey) {
|
|
289637
|
+
step.path = step.path.split(`:${pname}`).join(`{{${memKey}}}`);
|
|
289638
|
+
} else {
|
|
289639
|
+
missing.push(`Path param ":${pname}" has no source \u2014 add a body field "${pname}", capture a memory key named "${pname}" in an earlier step, or inline {{workflow:${pname}}} in the path.`);
|
|
289640
|
+
}
|
|
289641
|
+
}
|
|
289642
|
+
const queryParams = (endpoint.parameters || []).filter((p) => p.in === "query");
|
|
289643
|
+
if (queryParams.length > 0) {
|
|
289644
|
+
const query = {};
|
|
289645
|
+
for (const p of queryParams) {
|
|
289646
|
+
const memKey = [...producedKeys].find((k) => (k.split(":").pop() || "").toLowerCase() === p.name.toLowerCase());
|
|
289647
|
+
if (memKey) {
|
|
289648
|
+
query[p.name] = `{{${memKey}}}`;
|
|
289649
|
+
continue;
|
|
289650
|
+
}
|
|
289651
|
+
if (p.required !== true)
|
|
289652
|
+
continue;
|
|
289653
|
+
const val = fieldToFakerValue(p.name, {
|
|
289654
|
+
type: p.type || "string",
|
|
289655
|
+
format: p.format,
|
|
289656
|
+
min: p.min,
|
|
289657
|
+
max: p.max,
|
|
289658
|
+
enum: p.enum
|
|
289659
|
+
});
|
|
289660
|
+
if (val !== null) {
|
|
289661
|
+
query[p.name] = val;
|
|
289662
|
+
} else {
|
|
289663
|
+
query[p.name] = `{{human:${p.name}}}`;
|
|
289664
|
+
allHumanKeys.add(p.name);
|
|
289665
|
+
missing.push(`Query param "${p.name}" (required, type: ${p.type || "string"}) \u2014 no auto-mapping. Using {{human:${p.name}}} (will pause for interactive input at runtime).`);
|
|
289666
|
+
}
|
|
289667
|
+
}
|
|
289668
|
+
if (Object.keys(query).length > 0)
|
|
289669
|
+
step.query = query;
|
|
289670
|
+
}
|
|
288694
289671
|
const capture = {};
|
|
288695
289672
|
if (endpoint.produces) {
|
|
288696
289673
|
for (const p of endpoint.produces) {
|
|
@@ -288741,6 +289718,109 @@ var require_workflow_designer_tools = __commonJS({
|
|
|
288741
289718
|
}
|
|
288742
289719
|
});
|
|
288743
289720
|
|
|
289721
|
+
// ../../packages/mcp-server/dist/tools/run-tools.js
|
|
289722
|
+
var require_run_tools = __commonJS({
|
|
289723
|
+
"../../packages/mcp-server/dist/tools/run-tools.js"(exports2) {
|
|
289724
|
+
"use strict";
|
|
289725
|
+
init_cjs_shims();
|
|
289726
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
289727
|
+
if (k2 === void 0) k2 = k;
|
|
289728
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
289729
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
289730
|
+
desc = { enumerable: true, get: function() {
|
|
289731
|
+
return m[k];
|
|
289732
|
+
} };
|
|
289733
|
+
}
|
|
289734
|
+
Object.defineProperty(o, k2, desc);
|
|
289735
|
+
}) : (function(o, m, k, k2) {
|
|
289736
|
+
if (k2 === void 0) k2 = k;
|
|
289737
|
+
o[k2] = m[k];
|
|
289738
|
+
}));
|
|
289739
|
+
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
|
|
289740
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
289741
|
+
}) : function(o, v) {
|
|
289742
|
+
o["default"] = v;
|
|
289743
|
+
});
|
|
289744
|
+
var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
|
|
289745
|
+
var ownKeys = function(o) {
|
|
289746
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
289747
|
+
var ar = [];
|
|
289748
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
289749
|
+
return ar;
|
|
289750
|
+
};
|
|
289751
|
+
return ownKeys(o);
|
|
289752
|
+
};
|
|
289753
|
+
return function(mod) {
|
|
289754
|
+
if (mod && mod.__esModule) return mod;
|
|
289755
|
+
var result = {};
|
|
289756
|
+
if (mod != null) {
|
|
289757
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
289758
|
+
}
|
|
289759
|
+
__setModuleDefault(result, mod);
|
|
289760
|
+
return result;
|
|
289761
|
+
};
|
|
289762
|
+
})();
|
|
289763
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
289764
|
+
exports2.listRunsSchema = exports2.getRunSchema = void 0;
|
|
289765
|
+
exports2.handleGetRun = handleGetRun;
|
|
289766
|
+
exports2.handleListRuns = handleListRuns;
|
|
289767
|
+
var zod_1 = require("zod");
|
|
289768
|
+
var path15 = __importStar(require("path"));
|
|
289769
|
+
var core_1 = require_dist();
|
|
289770
|
+
var types_1 = require_types();
|
|
289771
|
+
var run_store_1 = require_run_store();
|
|
289772
|
+
exports2.getRunSchema = zod_1.z.object({
|
|
289773
|
+
projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder"),
|
|
289774
|
+
runId: zod_1.z.string().describe('Run id returned by jetic_simulate_workflow (e.g. "2026-10-19T12-34-56-789Z") or jetic_run_agent')
|
|
289775
|
+
});
|
|
289776
|
+
exports2.listRunsSchema = zod_1.z.object({
|
|
289777
|
+
projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder"),
|
|
289778
|
+
limit: zod_1.z.number().int().positive().max(100).optional().default(20).describe("Max runs to return, newest first (default 20)")
|
|
289779
|
+
});
|
|
289780
|
+
function handleGetRun(args) {
|
|
289781
|
+
const projectRoot = path15.resolve((0, types_1.resolveProjectInput)(args.projectPath));
|
|
289782
|
+
const config = (0, core_1.loadConfig)(projectRoot);
|
|
289783
|
+
const report = (0, run_store_1.readSimulationRun)(config.jeticDir, args.runId);
|
|
289784
|
+
if (report)
|
|
289785
|
+
return { found: true, ...report };
|
|
289786
|
+
const status = (0, run_store_1.readRunStatus)(config.jeticDir, args.runId);
|
|
289787
|
+
if (status) {
|
|
289788
|
+
return {
|
|
289789
|
+
found: true,
|
|
289790
|
+
type: "simulation",
|
|
289791
|
+
runId: args.runId,
|
|
289792
|
+
status: status.status,
|
|
289793
|
+
workflowName: status.workflowName,
|
|
289794
|
+
environment: status.environment,
|
|
289795
|
+
baseUrl: status.baseUrl,
|
|
289796
|
+
startedAt: status.startedAt,
|
|
289797
|
+
finishedAt: status.finishedAt,
|
|
289798
|
+
stats: status.stats,
|
|
289799
|
+
reportPath: status.reportPath,
|
|
289800
|
+
...status.error ? { error: status.error } : {},
|
|
289801
|
+
...status.status === "running" ? { message: "Run still in progress \u2014 poll again shortly." } : {}
|
|
289802
|
+
};
|
|
289803
|
+
}
|
|
289804
|
+
const agentReport = (0, core_1.readJsonSync)(path15.join(config.jeticDir, "runs", args.runId, "agent-report.json"));
|
|
289805
|
+
if (agentReport)
|
|
289806
|
+
return { found: true, type: "agent", runId: args.runId, ...agentReport };
|
|
289807
|
+
const available = (0, run_store_1.listRuns)(config.jeticDir, 10).map((r) => `${r.runId} (${r.type}: ${r.status})`);
|
|
289808
|
+
const detail = available.length > 0 ? ` Recent runs: ${available.join(", ")}.` : " No runs found yet.";
|
|
289809
|
+
throw new Error(`Run "${args.runId}" not found under .jetic/runs/.${detail}`);
|
|
289810
|
+
}
|
|
289811
|
+
function handleListRuns(args) {
|
|
289812
|
+
const projectRoot = path15.resolve((0, types_1.resolveProjectInput)(args.projectPath));
|
|
289813
|
+
const config = (0, core_1.loadConfig)(projectRoot);
|
|
289814
|
+
const runs = (0, run_store_1.listRuns)(config.jeticDir, args.limit ?? 20);
|
|
289815
|
+
return {
|
|
289816
|
+
runsDir: path15.join(config.jeticDir, "runs"),
|
|
289817
|
+
totalCount: runs.length,
|
|
289818
|
+
runs
|
|
289819
|
+
};
|
|
289820
|
+
}
|
|
289821
|
+
}
|
|
289822
|
+
});
|
|
289823
|
+
|
|
288744
289824
|
// ../../packages/mcp-server/dist/server.js
|
|
288745
289825
|
var require_server3 = __commonJS({
|
|
288746
289826
|
"../../packages/mcp-server/dist/server.js"(exports2) {
|
|
@@ -288805,6 +289885,7 @@ var require_server3 = __commonJS({
|
|
|
288805
289885
|
var session_tools_1 = require_session_tools();
|
|
288806
289886
|
var workflow_designer_tools_1 = require_workflow_designer_tools();
|
|
288807
289887
|
var agent_tools_1 = require_agent_tools();
|
|
289888
|
+
var run_tools_1 = require_run_tools();
|
|
288808
289889
|
exports2.JETIC_MCP_SERVER_NAME = "jetic-mcp-server";
|
|
288809
289890
|
function getJeticMcpVersion() {
|
|
288810
289891
|
try {
|
|
@@ -288859,11 +289940,20 @@ var require_server3 = __commonJS({
|
|
|
288859
289940
|
" The scaffold tool already handles faker mapping \u2014 only override to {{human:key}} when a",
|
|
288860
289941
|
" field appears in missingFields AND genuinely requires interactive user input.",
|
|
288861
289942
|
"",
|
|
288862
|
-
"STEP 4c \u2014 CONDITIONS (branching /
|
|
288863
|
-
"
|
|
288864
|
-
' { "condition": { "
|
|
288865
|
-
"
|
|
288866
|
-
"
|
|
289943
|
+
"STEP 4c \u2014 CONDITIONS (branching / assertions on memory)",
|
|
289944
|
+
" Preferred form \u2014 checked AFTER the step runs, so it can use values that step just captured:",
|
|
289945
|
+
' { "condition": { "rules": { "all": [ { "left": "{{workflow:userRole}}", "operator": "equals", "right": "admin" } ] },',
|
|
289946
|
+
' "onFail": "abort" } }',
|
|
289947
|
+
" rules.all = AND, rules.any = OR. Operators: equals, not_equals, greater_than(_or_equal), less_than(_or_equal),",
|
|
289948
|
+
" exists, not_exists, is_empty, is_not_empty, contains, not_contains, starts_with, ends_with.",
|
|
289949
|
+
" onFail when the rules do NOT hold:",
|
|
289950
|
+
' \u2022 "abort" \u2014 fail the workflow',
|
|
289951
|
+
' \u2022 "continue" \u2014 stop the remaining steps, workflow still passes (branch not applicable)',
|
|
289952
|
+
' \u2022 "switch" \u2014 run another saved workflow: add "switchToWorkflow": "<slug or name>";',
|
|
289953
|
+
' add "returnOnComplete": true to resume this workflow afterwards',
|
|
289954
|
+
" Legacy form (still supported) \u2014 a guard checked BEFORE the step; a miss skips just that step (counts as passed):",
|
|
289955
|
+
' { "condition": { "if": "{{workflow:userRole}}", "equals": "admin" } } // equals, notEquals, contains, exists, greaterThan, lessThan, in',
|
|
289956
|
+
" A condition that is neither shape FAILS the run \u2014 it is never ignored.",
|
|
288867
289957
|
' Pair with onFailure:"continue" for negative/branching flows.',
|
|
288868
289958
|
"",
|
|
288869
289959
|
"STEP 4d \u2014 FAILURE MODES & RETRY",
|
|
@@ -288884,6 +289974,9 @@ var require_server3 = __commonJS({
|
|
|
288884
289974
|
"",
|
|
288885
289975
|
"STEP 4g \u2014 RUN",
|
|
288886
289976
|
' jetic_simulate_workflow { "workflow": "<slug>" }',
|
|
289977
|
+
" It preflights the target first and fails fast with a clear reason when the app is unreachable.",
|
|
289978
|
+
' The response is a compact summary + runId. Full step detail: jetic_get_run { "runId": "..." };',
|
|
289979
|
+
" recent runs: jetic_list_runs. For long journeys pass async:true, then poll jetic_get_run.",
|
|
288887
289980
|
"",
|
|
288888
289981
|
"\u2501\u2501 GENERAL RULES \u2501\u2501",
|
|
288889
289982
|
"No AI-provider setup is needed: never ask for OpenRouter/OpenAI keys and never use `jetic config ai`.",
|
|
@@ -288951,9 +290044,11 @@ var require_server3 = __commonJS({
|
|
|
288951
290044
|
register("jetic_list_workflows", "List workflows", "Lists multi-step integration test workflows from .jetic/workflows/*.json, .jetic/workflow.json, or embedded model.json workflows.", workflow_tools_1.listWorkflowsSchema.shape, READ_ONLY, workflow_tools_1.handleListWorkflows);
|
|
288952
290045
|
register("jetic_simulate_workflow", "Simulate workflow", "Executes a full end-to-end multi-step workflow simulation with dynamic memory capture, header injection, and schema validation against the live server.", workflow_tools_1.simulateWorkflowSchema.shape, READ_LIVE, workflow_tools_1.handleSimulateWorkflow);
|
|
288953
290046
|
register("jetic_simulate", "Simulate workflow (alias)", "Alias of jetic_simulate_workflow: runs a multi-step user-journey workflow against the live server. Prefer jetic_simulate_workflow in new prompts.", workflow_tools_1.simulateWorkflowSchema.shape, READ_LIVE, workflow_tools_1.handleSimulateWorkflow);
|
|
290047
|
+
register("jetic_get_run", "Get run report", "Returns the full stored report of a previous run \u2014 every step's request, response, captures and errors \u2014 from .jetic/runs/<runId>/. Use the runId returned by jetic_simulate_workflow or jetic_list_runs.", run_tools_1.getRunSchema.shape, READ_ONLY, run_tools_1.handleGetRun);
|
|
290048
|
+
register("jetic_list_runs", "List runs", "Lists recent simulation and agent runs from .jetic/runs/, newest first, with status, step counts and duration. Use jetic_get_run for the full report of one run.", run_tools_1.listRunsSchema.shape, READ_ONLY, run_tools_1.handleListRuns);
|
|
288954
290049
|
register("jetic_scaffold_workflow", "Scaffold workflow steps from model", "PHASE 4a \u2014 FIRST STEP when building a workflow. Given an ordered endpoint list, reads model.json and returns ready-made `draftSteps`: body fields faker-mapped by type/format/name, inject derived from securitySchemes.obtainedFrom token-producer chains, capture from endpoint.produces, captureInput for reusable credentials. Returns missingFields (fields needing human input or manual fix) and securityChain analysis. Does NOT save. Review draftSteps, then call jetic_validate_workflow \u2192 jetic_create_workflow.", workflow_designer_tools_1.scaffoldWorkflowSchema.shape, READ_ONLY, workflow_designer_tools_1.handleScaffoldWorkflow);
|
|
288955
|
-
register("jetic_validate_workflow", "Validate workflow (dry run)", "Deep-validates a workflow WITHOUT saving: step structure, endpoint cross-checks against model.json, full memory data-flow analysis.
|
|
288956
|
-
register("jetic_create_workflow", "Create workflow", "Creates a .jetic/workflows/<slug>.json workflow. Supports: condition (
|
|
290050
|
+
register("jetic_validate_workflow", "Validate workflow (dry run)", "Deep-validates a workflow WITHOUT saving: step structure, endpoint cross-checks against model.json, full memory data-flow analysis. Also validates conditions (rules + onFail/switch targets, or legacy if guards; template refs), onFailure, continueOnStatus, and retry fields. Accepts a saved workflow slug/name or an inline definition.", workflow_authoring_tools_1.validateWorkflowSchema.shape, READ_ONLY, workflow_authoring_tools_1.handleValidateWorkflow);
|
|
290051
|
+
register("jetic_create_workflow", "Create workflow", "Creates a .jetic/workflows/<slug>.json workflow. Supports: condition (rules with onFail abort|continue|switch, or legacy if-guards), onFailure (abort|continue), continueOnStatus (extra pass codes), retry (per-step or global default). Refuses to save on validation errors.", workflow_authoring_tools_1.createWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleCreateWorkflow);
|
|
288957
290052
|
register("jetic_update_workflow", "Update workflow", "Updates a saved file workflow: rename, description/environment, full steps replacement, or appendSteps. Validates before writing and refuses to save on errors.", workflow_authoring_tools_1.updateWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleUpdateWorkflow);
|
|
288958
290053
|
register("jetic_delete_workflow", "Delete workflow", "Deletes a saved .jetic/workflows/*.json workflow file by slug, name, or path. Destructive: confirm with the user before calling. Cannot delete model.json-embedded workflows.", workflow_authoring_tools_1.deleteWorkflowSchema.shape, DESTRUCTIVE, workflow_authoring_tools_1.handleDeleteWorkflow);
|
|
288959
290054
|
register("jetic_list_agents", "List agents", "Lists registered deterministic agents (model-watcher, workflow-impact) available to jetic_run_agent.", agent_tools_1.listAgentsSchema.shape, READ_ONLY, agent_tools_1.handleListAgents);
|
|
@@ -289697,9 +290792,12 @@ Running ${files.length} workflow(s)...
|
|
|
289697
290792
|
for (const file of files) {
|
|
289698
290793
|
const workflow = (0, import_model2.readWorkflowDefinitionFile)(path9.join(config.workflowsDir, file));
|
|
289699
290794
|
import_core11.jeticEvents.emit("workflow.started", { workflowId: workflow.name });
|
|
289700
|
-
const simulator = new import_simulator.WorkflowSimulator(model, targetEnv);
|
|
289701
|
-
const result = await simulator.simulateWorkflow(workflow
|
|
289702
|
-
|
|
290795
|
+
const simulator = new import_simulator.WorkflowSimulator(model, targetEnv, { projectRoot: config.projectRoot });
|
|
290796
|
+
const result = await simulator.simulateWorkflow(workflow, {
|
|
290797
|
+
loadWorkflow: (slugOrName) => (0, import_model2.loadWorkflowByName)(config.workflowsDir, slugOrName)
|
|
290798
|
+
});
|
|
290799
|
+
const failures = result.results.filter((r) => !r.passed).map((r) => ({ step: r.name, status: r.actualStatus, error: r.error }));
|
|
290800
|
+
results.push({ name: workflow.name, passed: result.success, durationMs: result.stats.totalTimeMs, failures });
|
|
289703
290801
|
if (result.success) {
|
|
289704
290802
|
import_core11.jeticEvents.emit("workflow.completed", { workflowId: workflow.name, passed: true });
|
|
289705
290803
|
} else {
|
|
@@ -289707,6 +290805,10 @@ Running ${files.length} workflow(s)...
|
|
|
289707
290805
|
}
|
|
289708
290806
|
if (!options.ci) {
|
|
289709
290807
|
console.log(` ${result.success ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m"} ${workflow.name}`);
|
|
290808
|
+
for (const failure of failures) {
|
|
290809
|
+
const detail = failure.error ?? (failure.status ? `HTTP ${failure.status}` : "failed");
|
|
290810
|
+
console.log(` \x1B[2m\u21B3 ${failure.step}: ${detail}\x1B[0m`);
|
|
290811
|
+
}
|
|
289710
290812
|
}
|
|
289711
290813
|
}
|
|
289712
290814
|
const passed = results.filter((r) => r.passed).length;
|
|
@@ -289830,7 +290932,7 @@ memoryCommand.command("clear").description("Clear memory").action(() => {
|
|
|
289830
290932
|
init_cjs_shims();
|
|
289831
290933
|
var import_commander15 = require("commander");
|
|
289832
290934
|
var import_core14 = __toESM(require_dist());
|
|
289833
|
-
var
|
|
290935
|
+
var import_simulator3 = __toESM(require_dist13());
|
|
289834
290936
|
var path12 = __toESM(require("path"));
|
|
289835
290937
|
|
|
289836
290938
|
// src/commands/simulate-workflow.ts
|
|
@@ -289839,7 +290941,9 @@ var path11 = __toESM(require("path"));
|
|
|
289839
290941
|
var fs7 = __toESM(require("fs"));
|
|
289840
290942
|
var import_commander14 = require("commander");
|
|
289841
290943
|
var import_core13 = __toESM(require_dist());
|
|
290944
|
+
var import_model5 = __toESM(require_dist2());
|
|
289842
290945
|
var import_memory2 = __toESM(require_dist12());
|
|
290946
|
+
var import_simulator2 = __toESM(require_dist13());
|
|
289843
290947
|
var import_faker = require("@faker-js/faker");
|
|
289844
290948
|
var c = {
|
|
289845
290949
|
reset: "\x1B[0m",
|
|
@@ -289896,58 +291000,32 @@ function deepGet(obj, dotPath) {
|
|
|
289896
291000
|
async function resolveConditionRef(ref, context = "condition") {
|
|
289897
291001
|
return ref.replace(/\{\{([^}]+)\}\}/g, () => "").trim() === ref.trim() ? ref : await resolveTemplateString(ref, context);
|
|
289898
291002
|
}
|
|
289899
|
-
async function evalConditionRule(rule, context = "condition") {
|
|
289900
|
-
const left = await resolveConditionRef(rule.left, context);
|
|
289901
|
-
const right = rule.right !== void 0 ? await resolveConditionRef(rule.right, context) : void 0;
|
|
289902
|
-
const op = rule.operator;
|
|
289903
|
-
switch (op) {
|
|
289904
|
-
case "equals":
|
|
289905
|
-
return left === right;
|
|
289906
|
-
case "not_equals":
|
|
289907
|
-
return left !== right;
|
|
289908
|
-
case "greater_than":
|
|
289909
|
-
return Number(left) > Number(right);
|
|
289910
|
-
case "greater_than_or_equal":
|
|
289911
|
-
return Number(left) >= Number(right);
|
|
289912
|
-
case "less_than":
|
|
289913
|
-
return Number(left) < Number(right);
|
|
289914
|
-
case "less_than_or_equal":
|
|
289915
|
-
return Number(left) <= Number(right);
|
|
289916
|
-
case "exists":
|
|
289917
|
-
return left !== "" && left !== void 0;
|
|
289918
|
-
case "not_exists":
|
|
289919
|
-
return left === "" || left === void 0;
|
|
289920
|
-
case "is_empty":
|
|
289921
|
-
return left === "";
|
|
289922
|
-
case "is_not_empty":
|
|
289923
|
-
return left !== "";
|
|
289924
|
-
case "contains":
|
|
289925
|
-
return right !== void 0 && left.includes(right);
|
|
289926
|
-
case "not_contains":
|
|
289927
|
-
return right !== void 0 && !left.includes(right);
|
|
289928
|
-
case "starts_with":
|
|
289929
|
-
return right !== void 0 && left.startsWith(right);
|
|
289930
|
-
case "ends_with":
|
|
289931
|
-
return right !== void 0 && left.endsWith(right);
|
|
289932
|
-
default:
|
|
289933
|
-
return false;
|
|
289934
|
-
}
|
|
289935
|
-
}
|
|
289936
291003
|
async function evaluateStepCondition(condition, context = "condition") {
|
|
289937
|
-
|
|
289938
|
-
|
|
289939
|
-
|
|
289940
|
-
if (
|
|
289941
|
-
const
|
|
289942
|
-
|
|
289943
|
-
|
|
291004
|
+
return (0, import_simulator2.evaluateRules)(condition.rules, (value) => resolveConditionRef(value, context));
|
|
291005
|
+
}
|
|
291006
|
+
async function preStepCondition(step) {
|
|
291007
|
+
if (!step.condition) return null;
|
|
291008
|
+
const label = step.name || `${step.method} ${step.path}`;
|
|
291009
|
+
const base = { step, status: 0, responseBody: null, durationMs: 0, captured: [], injected: {} };
|
|
291010
|
+
const kind = (0, import_simulator2.conditionKind)(step.condition);
|
|
291011
|
+
if (kind === "invalid") {
|
|
291012
|
+
return { ...base, passed: false, error: `Step "${label}": ${(0, import_simulator2.describeInvalidCondition)(step.condition)}` };
|
|
291013
|
+
}
|
|
291014
|
+
if (kind === "legacy") {
|
|
291015
|
+
const outcome = await (0, import_simulator2.evaluateLegacyCondition)(
|
|
291016
|
+
step.condition,
|
|
291017
|
+
(value) => resolveConditionRef(value, `condition of step "${label}"`)
|
|
291018
|
+
);
|
|
291019
|
+
if (!outcome.matched) {
|
|
291020
|
+
return {
|
|
291021
|
+
...base,
|
|
291022
|
+
passed: true,
|
|
291023
|
+
skipped: true,
|
|
291024
|
+
conditionResult: { passed: false, reason: `legacy guard not met (resolved "${outcome.evaluated}")` }
|
|
291025
|
+
};
|
|
291026
|
+
}
|
|
289944
291027
|
}
|
|
289945
|
-
|
|
289946
|
-
const failing = results.filter((r) => !r.ok);
|
|
289947
|
-
const reason = passed ? `All conditions met (${isAnd ? "AND" : "OR"})` : `Failed: ${failing.map(
|
|
289948
|
-
(r) => `${r.rule.left} ${r.rule.operator}${r.rule.right !== void 0 ? " " + r.rule.right : ""}`
|
|
289949
|
-
).join(", ")}`;
|
|
289950
|
-
return { passed, reason };
|
|
291028
|
+
return null;
|
|
289951
291029
|
}
|
|
289952
291030
|
function envKeyForHuman(key) {
|
|
289953
291031
|
return `JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
@@ -290236,12 +291314,14 @@ function renderStepResult(result, index, total) {
|
|
|
290236
291314
|
console.log(` ${CHAIN} ${c.green}\u{1F4BE} captured ${cap}${c.reset}`);
|
|
290237
291315
|
}
|
|
290238
291316
|
}
|
|
290239
|
-
if (result.
|
|
291317
|
+
if (result.skipped) {
|
|
291318
|
+
console.log(` ${CHAIN} ${c.yellow}\u2298 skipped${c.reset} ${c.dim}${result.conditionResult?.reason ?? ""}${c.reset}`);
|
|
291319
|
+
} else if (result.conditionResult) {
|
|
290240
291320
|
const { passed: condPassed, reason } = result.conditionResult;
|
|
290241
291321
|
const condIcon = condPassed ? `${c.magenta}\u25C7${c.reset}` : `${c.yellow}\u25C8${c.reset}`;
|
|
290242
291322
|
const condColor = condPassed ? c.magenta : c.yellow;
|
|
290243
291323
|
console.log(` ${CHAIN} ${condIcon} ${condColor}condition ${condPassed ? "passed" : "failed"}${c.reset} ${c.dim}${reason}${c.reset}`);
|
|
290244
|
-
if (!condPassed && result.step.condition) {
|
|
291324
|
+
if (!condPassed && (0, import_simulator2.isRulesCondition)(result.step.condition)) {
|
|
290245
291325
|
const { onFail, switchToWorkflow } = result.step.condition;
|
|
290246
291326
|
const actionStr = onFail === "switch" ? `switching to workflow: ${switchToWorkflow ?? "unknown"}` : onFail === "continue" ? "continuing without remaining steps" : "aborting workflow";
|
|
290247
291327
|
console.log(` ${CHAIN} ${c.yellow} \u2192 ${actionStr}${c.reset}`);
|
|
@@ -290458,12 +291538,17 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290458
291538
|
spinner.start(`${stepLabel} ${c.dim}${step.name}${c.reset}...`);
|
|
290459
291539
|
let result;
|
|
290460
291540
|
try {
|
|
290461
|
-
|
|
290462
|
-
|
|
291541
|
+
const preempted = await preStepCondition(step);
|
|
291542
|
+
result = preempted ?? await executeStep(step, baseUrl, {});
|
|
291543
|
+
if (!preempted && (0, import_simulator2.isRulesCondition)(step.condition)) {
|
|
290463
291544
|
const condResult = await evaluateStepCondition(step.condition, `condition of step "${step.name || step.path}"`);
|
|
290464
291545
|
result.conditionResult = condResult;
|
|
290465
291546
|
if (!condResult.passed) {
|
|
290466
291547
|
const { onFail, switchToWorkflow } = step.condition;
|
|
291548
|
+
if (onFail === "abort") {
|
|
291549
|
+
result.passed = false;
|
|
291550
|
+
result.error = `Condition failed: ${condResult.reason}`;
|
|
291551
|
+
}
|
|
290467
291552
|
results.push(result);
|
|
290468
291553
|
const icon2 = result.passed ? TICK : CROSS;
|
|
290469
291554
|
spinner.stop(` ${icon2} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
|
|
@@ -290481,10 +291566,9 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290481
291566
|
} else if (onFail === "switch" && switchToWorkflow) {
|
|
290482
291567
|
console.log(` ${ARROW} Condition failed \u2014 switching to workflow: ${c.bold}${switchToWorkflow}${c.reset}
|
|
290483
291568
|
`);
|
|
290484
|
-
const
|
|
290485
|
-
if (
|
|
291569
|
+
const switchedWorkflow = (0, import_model5.loadWorkflowByName)(workflowsDir, switchToWorkflow);
|
|
291570
|
+
if (switchedWorkflow) {
|
|
290486
291571
|
try {
|
|
290487
|
-
const switchedWorkflow = JSON.parse(fs7.readFileSync(switchPath, "utf-8"));
|
|
290488
291572
|
console.log(` ${TICK} Loaded switch workflow: ${c.bold}${switchedWorkflow.name}${c.reset}
|
|
290489
291573
|
`);
|
|
290490
291574
|
renderWorkflowHeader(switchedWorkflow);
|
|
@@ -290495,8 +291579,9 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290495
291579
|
const switchLabel = `${c.dim}${si + 1}/${switchedWorkflow.steps.length}${c.reset} ${getMethodColor(switchStep.method)}${switchStep.method}${c.reset} ${switchStep.path}`;
|
|
290496
291580
|
const switchSpinner = new Spinner();
|
|
290497
291581
|
switchSpinner.start(`${switchLabel} ${c.dim}${switchStep.name}${c.reset}...`);
|
|
290498
|
-
const
|
|
290499
|
-
|
|
291582
|
+
const switchPre = await preStepCondition(switchStep);
|
|
291583
|
+
const switchResult = switchPre ?? await executeStep(switchStep, baseUrl, {});
|
|
291584
|
+
if (!switchPre && (0, import_simulator2.isRulesCondition)(switchStep.condition)) {
|
|
290500
291585
|
switchResult.conditionResult = await evaluateStepCondition(
|
|
290501
291586
|
switchStep.condition,
|
|
290502
291587
|
`condition of step "${switchStep.name || switchStep.path}"`
|
|
@@ -290508,12 +291593,16 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290508
291593
|
renderStepResult(switchResult, si, switchedWorkflow.steps.length);
|
|
290509
291594
|
}
|
|
290510
291595
|
} catch (e) {
|
|
290511
|
-
console.log(` ${c.red}\u2717 Failed to
|
|
291596
|
+
console.log(` ${c.red}\u2717 Failed to run switch workflow: ${e.message}${c.reset}
|
|
290512
291597
|
`);
|
|
291598
|
+
result.passed = false;
|
|
291599
|
+
result.error = `Switch workflow "${switchToWorkflow}" failed: ${e.message}`;
|
|
290513
291600
|
}
|
|
290514
291601
|
} else {
|
|
290515
|
-
console.log(` ${c.
|
|
291602
|
+
console.log(` ${c.red}\u2717 Switch workflow not found: ${switchToWorkflow}${c.reset}
|
|
290516
291603
|
`);
|
|
291604
|
+
result.passed = false;
|
|
291605
|
+
result.error = `Switch workflow "${switchToWorkflow}" was not found in ${workflowsDir}`;
|
|
290517
291606
|
}
|
|
290518
291607
|
if (step.condition.returnOnComplete) {
|
|
290519
291608
|
console.log(` ${c.green}\u21A9 Sub-workflow complete. Returning to main workflow...${c.reset}
|
|
@@ -290524,6 +291613,14 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290524
291613
|
break;
|
|
290525
291614
|
}
|
|
290526
291615
|
}
|
|
291616
|
+
if (onFail === "switch" && !switchToWorkflow) {
|
|
291617
|
+
console.log(` ${c.red}\u2717 Condition failed and onFail "switch" has no switchToWorkflow.${c.reset}
|
|
291618
|
+
`);
|
|
291619
|
+
result.passed = false;
|
|
291620
|
+
result.error = 'onFail "switch" has no switchToWorkflow';
|
|
291621
|
+
stopOnFailure = true;
|
|
291622
|
+
break;
|
|
291623
|
+
}
|
|
290527
291624
|
continue;
|
|
290528
291625
|
}
|
|
290529
291626
|
}
|
|
@@ -290538,9 +291635,15 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290538
291635
|
throw err;
|
|
290539
291636
|
}
|
|
290540
291637
|
results.push(result);
|
|
290541
|
-
const icon = result.passed ? TICK : CROSS;
|
|
291638
|
+
const icon = result.skipped ? SKIP : result.passed ? TICK : CROSS;
|
|
290542
291639
|
spinner.stop(` ${icon} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
|
|
290543
291640
|
renderStepResult(result, i, workflow.steps.length);
|
|
291641
|
+
if (!result.passed && result.error && !result.status && (0, import_simulator2.conditionKind)(step.condition) === "invalid") {
|
|
291642
|
+
console.log(` ${c.red}${c.bold}\u26A0 Unrecognized condition \u2014 stopping workflow.${c.reset}
|
|
291643
|
+
`);
|
|
291644
|
+
stopOnFailure = true;
|
|
291645
|
+
break;
|
|
291646
|
+
}
|
|
290544
291647
|
if (!result.passed && (step.path.includes("/login") || step.path.includes("/register") || step.path.includes("/auth"))) {
|
|
290545
291648
|
console.log(` ${c.red}${c.bold}\u26A0 Auth step failed \u2014 stopping workflow to prevent cascading failures.${c.reset}
|
|
290546
291649
|
`);
|
|
@@ -290663,15 +291766,19 @@ var Spinner2 = class {
|
|
|
290663
291766
|
}
|
|
290664
291767
|
}
|
|
290665
291768
|
};
|
|
290666
|
-
|
|
291769
|
+
function defaultEnvironment(model, environments) {
|
|
291770
|
+
const defaultName = model.defaultEnvironment ?? environments.find((e) => e.default)?.name;
|
|
291771
|
+
return environments.find((e) => e.name === defaultName) ?? environments[0];
|
|
291772
|
+
}
|
|
291773
|
+
async function selectEnvironment(model, environments) {
|
|
290667
291774
|
if (environments.length === 0) {
|
|
290668
291775
|
console.log(` ${c2.yellow}\u26A0${c2.reset} No environments defined in model.json`);
|
|
290669
291776
|
console.log(` ${c2.dim}Using default: http://localhost:3000${c2.reset}
|
|
290670
291777
|
`);
|
|
290671
291778
|
return { name: "default", baseUrl: "http://localhost:3000" };
|
|
290672
291779
|
}
|
|
290673
|
-
if (environments.length === 1) {
|
|
290674
|
-
const env = environments
|
|
291780
|
+
if (environments.length === 1 || !process.stdin.isTTY) {
|
|
291781
|
+
const env = defaultEnvironment(model, environments);
|
|
290675
291782
|
console.log(` ${c2.cyan}\u{1F30D}${c2.reset} Environment: ${c2.bold}${env.name}${c2.reset} ${c2.dim}\u2192 ${env.baseUrl}${c2.reset}
|
|
290676
291783
|
`);
|
|
290677
291784
|
return env;
|
|
@@ -290914,7 +292021,7 @@ function loadModel3() {
|
|
|
290914
292021
|
}
|
|
290915
292022
|
var simulateCommand = new import_commander15.Command("simulate").description("Simulate API endpoints against a live server");
|
|
290916
292023
|
simulateCommand.addCommand(simulateWorkflowCommand);
|
|
290917
|
-
simulateCommand.command("endpoint [method] [endpointPath]").description("Simulate a specific endpoint or all endpoints").option("--all", "Simulate all endpoints").option("--verbose", "Show detailed request/response info", false).action(async (method, endpointPath, options) => {
|
|
292024
|
+
simulateCommand.command("endpoint [method] [endpointPath]").description("Simulate a specific endpoint or all endpoints").option("--all", "Simulate all endpoints").option("--verbose", "Show detailed request/response info", false).option("--env <name>", "Environment name to use from model.json (skips the interactive picker)").action(async (method, endpointPath, options) => {
|
|
290918
292025
|
console.log("");
|
|
290919
292026
|
console.log(` ${c2.bgCyan}${c2.black}${c2.bold} JETIC ${c2.reset} ${c2.cyan}${c2.bold}Endpoint Simulator${c2.reset}`);
|
|
290920
292027
|
console.log("");
|
|
@@ -290928,7 +292035,20 @@ simulateCommand.command("endpoint [method] [endpointPath]").description("Simulat
|
|
|
290928
292035
|
console.log(` ${c2.dim}Model v${model.version} \u2022 ${model.endpoints.length} endpoints${c2.reset}
|
|
290929
292036
|
`);
|
|
290930
292037
|
const environments = model.environments || [];
|
|
290931
|
-
|
|
292038
|
+
let selectedEnv;
|
|
292039
|
+
if (options.env) {
|
|
292040
|
+
const named = environments.find((e) => e.name === options.env);
|
|
292041
|
+
if (!named) {
|
|
292042
|
+
console.log(` ${c2.red}\u2717${c2.reset} Environment ${c2.bold}${options.env}${c2.reset} not found. Available: ${environments.map((e) => e.name).join(", ") || "(none)"}
|
|
292043
|
+
`);
|
|
292044
|
+
process.exit(2);
|
|
292045
|
+
}
|
|
292046
|
+
selectedEnv = named;
|
|
292047
|
+
console.log(` ${c2.cyan}\u{1F30D}${c2.reset} Environment: ${c2.bold}${named.name}${c2.reset} ${c2.dim}\u2192 ${named.baseUrl}${c2.reset}
|
|
292048
|
+
`);
|
|
292049
|
+
} else {
|
|
292050
|
+
selectedEnv = await selectEnvironment(model, environments);
|
|
292051
|
+
}
|
|
290932
292052
|
const checkSpinner = new Spinner2();
|
|
290933
292053
|
checkSpinner.start(`Checking if backend is active at ${selectedEnv.baseUrl}...`);
|
|
290934
292054
|
try {
|
|
@@ -290942,7 +292062,7 @@ simulateCommand.command("endpoint [method] [endpointPath]").description("Simulat
|
|
|
290942
292062
|
`);
|
|
290943
292063
|
process.exit(1);
|
|
290944
292064
|
}
|
|
290945
|
-
const simulator = new
|
|
292065
|
+
const simulator = new import_simulator3.EndpointSimulator(model, selectedEnv);
|
|
290946
292066
|
const spinner = new Spinner2();
|
|
290947
292067
|
if (options.all) {
|
|
290948
292068
|
console.log(` ${c2.magenta}\u{1F680}${c2.reset} Simulating ${c2.bold}${model.endpoints.length}${c2.reset} endpoints...
|
|
@@ -291005,8 +292125,9 @@ var import_commander16 = require("commander");
|
|
|
291005
292125
|
var import_express = __toESM(require("express"));
|
|
291006
292126
|
var import_path = __toESM(require("path"));
|
|
291007
292127
|
var import_fs = __toESM(require("fs"));
|
|
291008
|
-
var
|
|
292128
|
+
var import_model6 = __toESM(require_dist2());
|
|
291009
292129
|
var import_memory3 = __toESM(require_dist12());
|
|
292130
|
+
var import_simulator4 = __toESM(require_dist13());
|
|
291010
292131
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".cache", "__pycache__", ".next", ".nuxt", ".venv"]);
|
|
291011
292132
|
var IGNORED_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".mp4", ".webm", ".woff", ".woff2", ".ttf", ".eot", ".map"]);
|
|
291012
292133
|
var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".kt", ".cs", ".rb", ".php", ".json", ".yaml", ".yml", ".toml", ".env", ".graphql", ".gql", ".sql", ".prisma", ".proto"]);
|
|
@@ -291155,7 +292276,7 @@ var ChangeScanner = class {
|
|
|
291155
292276
|
this.saveChanges(data);
|
|
291156
292277
|
}
|
|
291157
292278
|
};
|
|
291158
|
-
var devCommand = new import_commander16.Command("dev").description("Start the Jetic local dashboard").option("-p, --port <number>", "Port to run the dashboard on", "8787").action(async (options) => {
|
|
292279
|
+
var devCommand = new import_commander16.Command("dev").description("Start the Jetic local dashboard").option("-p, --port <number>", "Port to run the dashboard on", "8787").option("--no-open", "Do not open the dashboard in a browser (also: JETIC_NO_OPEN=1)").action(async (options) => {
|
|
291159
292280
|
const port = parseInt(options.port, 10);
|
|
291160
292281
|
const app = (0, import_express.default)();
|
|
291161
292282
|
const projectRoot = process.cwd();
|
|
@@ -291183,7 +292304,7 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
|
|
|
291183
292304
|
const jeticDir2 = import_path.default.join(process.cwd(), ".jetic");
|
|
291184
292305
|
const modelDir = import_path.default.join(jeticDir2, "model");
|
|
291185
292306
|
try {
|
|
291186
|
-
if ((0,
|
|
292307
|
+
if ((0, import_model6.hasYamlModel)(modelDir)) return res.json((0, import_model6.loadModelFromDir)(modelDir));
|
|
291187
292308
|
const modelPath = import_path.default.join(jeticDir2, "model.json");
|
|
291188
292309
|
if (!import_fs.default.existsSync(modelPath)) return res.json(null);
|
|
291189
292310
|
res.json(JSON.parse(import_fs.default.readFileSync(modelPath, "utf8")));
|
|
@@ -291793,57 +292914,43 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
|
|
|
291793
292914
|
}
|
|
291794
292915
|
return result;
|
|
291795
292916
|
}
|
|
291796
|
-
async function evalCondRule(rule, stepIndex, stepName) {
|
|
291797
|
-
const left = await resolveCondRef(rule.left, stepIndex, stepName);
|
|
291798
|
-
const right = rule.right !== void 0 ? await resolveCondRef(rule.right, stepIndex, stepName) : void 0;
|
|
291799
|
-
switch (rule.operator) {
|
|
291800
|
-
case "equals":
|
|
291801
|
-
return left === right;
|
|
291802
|
-
case "not_equals":
|
|
291803
|
-
return left !== right;
|
|
291804
|
-
case "greater_than":
|
|
291805
|
-
return Number(left) > Number(right);
|
|
291806
|
-
case "greater_than_or_equal":
|
|
291807
|
-
return Number(left) >= Number(right);
|
|
291808
|
-
case "less_than":
|
|
291809
|
-
return Number(left) < Number(right);
|
|
291810
|
-
case "less_than_or_equal":
|
|
291811
|
-
return Number(left) <= Number(right);
|
|
291812
|
-
case "exists":
|
|
291813
|
-
return left !== "" && left !== void 0;
|
|
291814
|
-
case "not_exists":
|
|
291815
|
-
return left === "" || left === void 0;
|
|
291816
|
-
case "is_empty":
|
|
291817
|
-
return left === "";
|
|
291818
|
-
case "is_not_empty":
|
|
291819
|
-
return left !== "";
|
|
291820
|
-
case "contains":
|
|
291821
|
-
return right !== void 0 && left.includes(right);
|
|
291822
|
-
case "not_contains":
|
|
291823
|
-
return right !== void 0 && !left.includes(right);
|
|
291824
|
-
case "starts_with":
|
|
291825
|
-
return right !== void 0 && left.startsWith(right);
|
|
291826
|
-
case "ends_with":
|
|
291827
|
-
return right !== void 0 && left.endsWith(right);
|
|
291828
|
-
default:
|
|
291829
|
-
return false;
|
|
291830
|
-
}
|
|
291831
|
-
}
|
|
291832
292917
|
async function evalCondition(condition, stepIndex, stepName) {
|
|
291833
|
-
|
|
291834
|
-
const isAnd = !!condition.rules?.all;
|
|
291835
|
-
if (rules.length === 0) return { passed: true, reason: "No rules" };
|
|
291836
|
-
const results = [];
|
|
291837
|
-
for (const rule of rules) results.push({ rule, ok: await evalCondRule(rule, stepIndex, stepName) });
|
|
291838
|
-
const passed2 = isAnd ? results.every((r) => r.ok) : results.some((r) => r.ok);
|
|
291839
|
-
const failing = results.filter((r) => !r.ok);
|
|
291840
|
-
const reason = passed2 ? `All conditions met (${isAnd ? "AND" : "OR"})` : `Failed: ${failing.map((r) => `${r.rule.left} ${r.rule.operator}${r.rule.right !== void 0 ? " " + r.rule.right : ""}`).join(", ")}`;
|
|
291841
|
-
return { passed: passed2, reason };
|
|
292918
|
+
return (0, import_simulator4.evaluateRules)(condition.rules ?? {}, (value) => resolveCondRef(value, stepIndex, stepName));
|
|
291842
292919
|
}
|
|
291843
292920
|
for (let i = 0; i < workflow.steps.length; i++) {
|
|
291844
292921
|
if (aborted) break;
|
|
291845
292922
|
const step = workflow.steps[i];
|
|
291846
292923
|
send("step_start", { index: i, step: { name: step.name, method: step.method, path: step.path } });
|
|
292924
|
+
const condKind = step.condition ? (0, import_simulator4.conditionKind)(step.condition) : void 0;
|
|
292925
|
+
const stepSummary = { name: step.name, method: step.method, path: step.path, description: step.description };
|
|
292926
|
+
if (condKind === "invalid") {
|
|
292927
|
+
const reason = `Step ${i + 1} "${step.name || `${step.method} ${step.path}`}": ${(0, import_simulator4.describeInvalidCondition)(step.condition)}`;
|
|
292928
|
+
failed++;
|
|
292929
|
+
send("step_result", { index: i, step: stepSummary, status: 0, passed: false, durationMs: 0, captured: [], injected: {}, responseBody: null, error: reason });
|
|
292930
|
+
send("aborted", { index: i, reason });
|
|
292931
|
+
res.end();
|
|
292932
|
+
return;
|
|
292933
|
+
}
|
|
292934
|
+
if (condKind === "legacy") {
|
|
292935
|
+
const guard = await (0, import_simulator4.evaluateLegacyCondition)(step.condition, (value) => resolveCondRef(value, i, step.name || ""));
|
|
292936
|
+
if (!guard.matched) {
|
|
292937
|
+
passed++;
|
|
292938
|
+
send("step_result", {
|
|
292939
|
+
index: i,
|
|
292940
|
+
step: stepSummary,
|
|
292941
|
+
status: 0,
|
|
292942
|
+
passed: true,
|
|
292943
|
+
skipped: true,
|
|
292944
|
+
durationMs: 0,
|
|
292945
|
+
captured: [],
|
|
292946
|
+
injected: {},
|
|
292947
|
+
responseBody: null,
|
|
292948
|
+
error: null,
|
|
292949
|
+
skipReason: `legacy guard not met (resolved "${guard.evaluated}")`
|
|
292950
|
+
});
|
|
292951
|
+
continue;
|
|
292952
|
+
}
|
|
292953
|
+
}
|
|
291847
292954
|
let result;
|
|
291848
292955
|
try {
|
|
291849
292956
|
result = await executeStep2(step, i);
|
|
@@ -291868,7 +292975,7 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
|
|
|
291868
292975
|
responseBody: result.responseBody,
|
|
291869
292976
|
error: result.error
|
|
291870
292977
|
});
|
|
291871
|
-
if (step.condition && !aborted) {
|
|
292978
|
+
if ((0, import_simulator4.isRulesCondition)(step.condition) && !aborted) {
|
|
291872
292979
|
const condResult = await evalCondition(step.condition, i, step.name || `${step.method} ${step.path}`);
|
|
291873
292980
|
send("condition_result", {
|
|
291874
292981
|
index: i,
|
|
@@ -292050,6 +293157,8 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
|
|
|
292050
293157
|
console.log(`
|
|
292051
293158
|
\u{1F680} Jetic Studio is running at ${url}
|
|
292052
293159
|
`);
|
|
293160
|
+
const noOpenEnv = process.env.JETIC_NO_OPEN;
|
|
293161
|
+
if (!options.open || noOpenEnv && noOpenEnv !== "0" && noOpenEnv.toLowerCase() !== "false") return;
|
|
292053
293162
|
const { exec } = require("child_process");
|
|
292054
293163
|
const start = process.platform == "darwin" ? "open" : process.platform == "win32" ? "start" : "xdg-open";
|
|
292055
293164
|
exec(`${start} ${url}`);
|
|
@@ -292212,7 +293321,7 @@ var mcpCommand = new import_commander18.Command("mcp").description("Launch Model
|
|
|
292212
293321
|
|
|
292213
293322
|
// src/index.ts
|
|
292214
293323
|
var program = new import_commander19.Command();
|
|
292215
|
-
program.name("jetic").description("AI-Native API Behavior Testing").version("0.2.
|
|
293324
|
+
program.name("jetic").description("AI-Native API Behavior Testing").version("0.2.6");
|
|
292216
293325
|
program.addCommand(initCommand);
|
|
292217
293326
|
program.addCommand(scanCommand);
|
|
292218
293327
|
program.addCommand(inspectCommand);
|