jetic-cli 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -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,7 @@ 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();
|
|
272497
272677
|
function deepGet2(obj, dotPath) {
|
|
272498
272678
|
if (obj === null || obj === void 0)
|
|
272499
272679
|
return void 0;
|
|
@@ -272509,27 +272689,88 @@ var require_workflow_simulator = __commonJS({
|
|
|
272509
272689
|
var WorkflowSimulator2 = class {
|
|
272510
272690
|
model;
|
|
272511
272691
|
environment;
|
|
272512
|
-
|
|
272692
|
+
runtime;
|
|
272693
|
+
constructor(model, environment, runtime = {}) {
|
|
272513
272694
|
this.model = model;
|
|
272514
272695
|
this.environment = environment;
|
|
272696
|
+
this.runtime = runtime;
|
|
272697
|
+
}
|
|
272698
|
+
memory(scope) {
|
|
272699
|
+
return new memory_1.JeticMemory({ scope, projectRoot: this.runtime.projectRoot });
|
|
272515
272700
|
}
|
|
272516
272701
|
async simulateWorkflow(workflow, options) {
|
|
272517
272702
|
if (options?.clearMemory) {
|
|
272518
|
-
memory_1.JeticMemory.clearAllMemory();
|
|
272703
|
+
memory_1.JeticMemory.clearAllMemory(this.runtime.projectRoot);
|
|
272519
272704
|
}
|
|
272520
|
-
const baseUrl = this.environment.baseUrl;
|
|
272521
272705
|
const startTime = Date.now();
|
|
272522
|
-
const
|
|
272523
|
-
|
|
272706
|
+
const state = { results: [], allPassed: true, halted: false };
|
|
272707
|
+
await this.runSteps(workflow, options, state, [workflow.name]);
|
|
272708
|
+
const stepResults = state.results;
|
|
272709
|
+
const totalTimeMs = Date.now() - startTime;
|
|
272710
|
+
return {
|
|
272711
|
+
success: state.allPassed,
|
|
272712
|
+
workflow: {
|
|
272713
|
+
name: workflow.name,
|
|
272714
|
+
description: workflow.description,
|
|
272715
|
+
stepsCount: workflow.steps.length
|
|
272716
|
+
},
|
|
272717
|
+
environment: {
|
|
272718
|
+
name: this.environment.name,
|
|
272719
|
+
baseUrl: this.environment.baseUrl
|
|
272720
|
+
},
|
|
272721
|
+
stats: {
|
|
272722
|
+
totalSteps: workflow.steps.length,
|
|
272723
|
+
executedSteps: stepResults.length,
|
|
272724
|
+
passedSteps: stepResults.filter((r) => r.passed).length,
|
|
272725
|
+
failedSteps: stepResults.filter((r) => !r.passed).length,
|
|
272726
|
+
totalTimeMs
|
|
272727
|
+
},
|
|
272728
|
+
results: stepResults
|
|
272729
|
+
};
|
|
272730
|
+
}
|
|
272731
|
+
/**
|
|
272732
|
+
* Runs one workflow's steps, appending to `state`. Returns `'stop'` when the remaining steps
|
|
272733
|
+
* of THIS workflow must not run (failure, `onFail: continue|switch`), else `'done'`.
|
|
272734
|
+
* `state.halted` is set only for failures, so a caller resuming after a switch knows whether to go on.
|
|
272735
|
+
*/
|
|
272736
|
+
async runSteps(workflow, options, state, lineage) {
|
|
272737
|
+
const baseUrl = this.environment.baseUrl;
|
|
272738
|
+
const resolve2 = (value) => this.resolveTemplateString(value);
|
|
272524
272739
|
for (let i = 0; i < workflow.steps.length; i++) {
|
|
272525
272740
|
const step = workflow.steps[i];
|
|
272526
272741
|
const stepStartTime = Date.now();
|
|
272527
|
-
|
|
272528
|
-
|
|
272742
|
+
const stepName = step.name || `${step.method} ${step.path}`;
|
|
272743
|
+
const kind = step.condition === void 0 ? void 0 : (0, conditions_1.conditionKind)(step.condition);
|
|
272744
|
+
if (kind === "invalid") {
|
|
272745
|
+
const reason = (0, conditions_1.describeInvalidCondition)(step.condition);
|
|
272746
|
+
state.results.push({
|
|
272747
|
+
stepIndex: i + 1,
|
|
272748
|
+
name: stepName,
|
|
272749
|
+
method: step.method,
|
|
272750
|
+
path: step.path,
|
|
272751
|
+
targetUrl: "",
|
|
272752
|
+
expectedStatus: step.expectStatus ?? 200,
|
|
272753
|
+
actualStatus: 0,
|
|
272754
|
+
durationMs: Date.now() - stepStartTime,
|
|
272755
|
+
passed: false,
|
|
272756
|
+
skipped: false,
|
|
272757
|
+
retryCount: 0,
|
|
272758
|
+
requestHeaders: {},
|
|
272759
|
+
requestBody: {},
|
|
272760
|
+
responseBody: null,
|
|
272761
|
+
capturedMemory: [],
|
|
272762
|
+
error: `Step ${i + 1} "${stepName}": ${reason}`
|
|
272763
|
+
});
|
|
272764
|
+
state.allPassed = false;
|
|
272765
|
+
state.halted = true;
|
|
272766
|
+
return "stop";
|
|
272767
|
+
}
|
|
272768
|
+
if (kind === "legacy") {
|
|
272769
|
+
const condResult = await (0, conditions_1.evaluateLegacyCondition)(step.condition, resolve2);
|
|
272529
272770
|
if (!condResult.matched) {
|
|
272530
|
-
|
|
272771
|
+
state.results.push({
|
|
272531
272772
|
stepIndex: i + 1,
|
|
272532
|
-
name:
|
|
272773
|
+
name: stepName,
|
|
272533
272774
|
method: step.method,
|
|
272534
272775
|
path: step.path,
|
|
272535
272776
|
targetUrl: "",
|
|
@@ -272609,97 +272850,72 @@ var require_workflow_simulator = __commonJS({
|
|
|
272609
272850
|
}
|
|
272610
272851
|
}
|
|
272611
272852
|
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({
|
|
272853
|
+
const onFailure = step.onFailure ?? "abort";
|
|
272854
|
+
const result = {
|
|
272639
272855
|
stepIndex: i + 1,
|
|
272640
|
-
name:
|
|
272856
|
+
name: stepName,
|
|
272641
272857
|
method: step.method,
|
|
272642
272858
|
path: step.path,
|
|
272643
272859
|
targetUrl: fullUrl,
|
|
272644
272860
|
expectedStatus,
|
|
272645
272861
|
actualStatus: responseStatus,
|
|
272646
272862
|
durationMs: Date.now() - stepStartTime,
|
|
272647
|
-
passed:
|
|
272863
|
+
passed: stepPassed,
|
|
272648
272864
|
skipped: false,
|
|
272649
272865
|
retryCount: attempts - 1,
|
|
272650
272866
|
requestHeaders: injectedHeaders,
|
|
272651
272867
|
requestBody: finalBody,
|
|
272652
272868
|
responseBody,
|
|
272653
|
-
capturedMemory: [...inputCaptured, ...responseCaptured]
|
|
272654
|
-
|
|
272655
|
-
|
|
272656
|
-
|
|
272657
|
-
|
|
272658
|
-
|
|
272659
|
-
|
|
272660
|
-
|
|
272661
|
-
|
|
272662
|
-
|
|
272663
|
-
|
|
272664
|
-
|
|
272665
|
-
|
|
272666
|
-
|
|
272667
|
-
|
|
272668
|
-
|
|
272669
|
-
|
|
272670
|
-
|
|
272671
|
-
|
|
272672
|
-
|
|
272673
|
-
|
|
272674
|
-
|
|
272675
|
-
|
|
272676
|
-
|
|
272677
|
-
|
|
272678
|
-
|
|
272679
|
-
|
|
272680
|
-
|
|
272681
|
-
|
|
272682
|
-
|
|
272683
|
-
|
|
272684
|
-
|
|
272685
|
-
|
|
272686
|
-
|
|
272687
|
-
|
|
272688
|
-
|
|
272689
|
-
|
|
272690
|
-
|
|
272691
|
-
|
|
272692
|
-
|
|
272693
|
-
|
|
272694
|
-
|
|
272695
|
-
|
|
272696
|
-
|
|
272697
|
-
|
|
272698
|
-
|
|
272699
|
-
|
|
272700
|
-
|
|
272869
|
+
capturedMemory: stepPassed ? [...inputCaptured, ...responseCaptured] : [...inputCaptured],
|
|
272870
|
+
...stepPassed ? {} : {
|
|
272871
|
+
failureHandling: onFailure === "continue" ? "continued" : "aborted",
|
|
272872
|
+
error: errorMsg
|
|
272873
|
+
}
|
|
272874
|
+
};
|
|
272875
|
+
state.results.push(result);
|
|
272876
|
+
if (!stepPassed) {
|
|
272877
|
+
state.allPassed = false;
|
|
272878
|
+
if (onFailure === "abort") {
|
|
272879
|
+
state.halted = true;
|
|
272880
|
+
return "stop";
|
|
272881
|
+
}
|
|
272882
|
+
}
|
|
272883
|
+
if (kind === "rules") {
|
|
272884
|
+
const condition = step.condition;
|
|
272885
|
+
const outcome = await (0, conditions_1.evaluateRules)(condition.rules, resolve2);
|
|
272886
|
+
result.conditionResult = { evaluated: outcome.reason, matched: outcome.passed };
|
|
272887
|
+
if (outcome.passed)
|
|
272888
|
+
continue;
|
|
272889
|
+
result.conditionOutcome = condition.onFail;
|
|
272890
|
+
const fail = (message) => {
|
|
272891
|
+
result.passed = false;
|
|
272892
|
+
result.error = result.error ? `${result.error}; ${message}` : message;
|
|
272893
|
+
state.allPassed = false;
|
|
272894
|
+
state.halted = true;
|
|
272895
|
+
return "stop";
|
|
272896
|
+
};
|
|
272897
|
+
if (condition.onFail === "continue")
|
|
272898
|
+
return "stop";
|
|
272899
|
+
if (condition.onFail !== "switch")
|
|
272900
|
+
return fail(`Condition failed at step ${i + 1}: ${outcome.reason}`);
|
|
272901
|
+
const target = condition.switchToWorkflow;
|
|
272902
|
+
if (!target)
|
|
272903
|
+
return fail(`Condition failed at step ${i + 1} (${outcome.reason}) and onFail "switch" has no switchToWorkflow`);
|
|
272904
|
+
if (!options?.loadWorkflow) {
|
|
272905
|
+
return fail(`Condition failed at step ${i + 1} (${outcome.reason}); cannot switch to "${target}": this runner was not given a workflow loader`);
|
|
272906
|
+
}
|
|
272907
|
+
const switched = await options.loadWorkflow(target);
|
|
272908
|
+
if (!switched)
|
|
272909
|
+
return fail(`Condition failed at step ${i + 1} (${outcome.reason}); switch target workflow "${target}" was not found`);
|
|
272910
|
+
if (lineage.includes(switched.name)) {
|
|
272911
|
+
return fail(`Condition failed at step ${i + 1}; switching to "${switched.name}" would loop (${[...lineage, switched.name].join(" \u2192 ")})`);
|
|
272912
|
+
}
|
|
272913
|
+
await this.runSteps(switched, options, state, [...lineage, switched.name]);
|
|
272914
|
+
if (state.halted || !condition.returnOnComplete)
|
|
272915
|
+
return "stop";
|
|
272916
|
+
}
|
|
272701
272917
|
}
|
|
272702
|
-
return
|
|
272918
|
+
return "done";
|
|
272703
272919
|
}
|
|
272704
272920
|
async resolveTemplateString(value) {
|
|
272705
272921
|
const templateRe = /\{\{([^}]+)\}\}/g;
|
|
@@ -272718,7 +272934,7 @@ var require_workflow_simulator = __commonJS({
|
|
|
272718
272934
|
continue;
|
|
272719
272935
|
}
|
|
272720
272936
|
}
|
|
272721
|
-
const mem =
|
|
272937
|
+
const mem = this.memory(scope);
|
|
272722
272938
|
const memVal = await mem.get(key);
|
|
272723
272939
|
replacements.push({ placeholder: match[0], resolved: memVal != null ? String(memVal) : "" });
|
|
272724
272940
|
continue;
|
|
@@ -272762,7 +272978,7 @@ var require_workflow_simulator = __commonJS({
|
|
|
272762
272978
|
strValue = await this.resolveTemplateString(memKeyOrTemplate);
|
|
272763
272979
|
} else {
|
|
272764
272980
|
const [scope, key] = memKeyOrTemplate.includes(":") ? memKeyOrTemplate.split(":", 2) : ["workflow", memKeyOrTemplate];
|
|
272765
|
-
const scopedMemory =
|
|
272981
|
+
const scopedMemory = this.memory(scope);
|
|
272766
272982
|
const value = await scopedMemory.get(key);
|
|
272767
272983
|
if (value === null)
|
|
272768
272984
|
continue;
|
|
@@ -272787,7 +273003,7 @@ var require_workflow_simulator = __commonJS({
|
|
|
272787
273003
|
if (value === void 0 || value === null)
|
|
272788
273004
|
continue;
|
|
272789
273005
|
const [scope, key] = memKey.includes(":") ? memKey.split(":", 2) : ["workflow", memKey];
|
|
272790
|
-
const memory =
|
|
273006
|
+
const memory = this.memory(scope);
|
|
272791
273007
|
await memory.set(key, value);
|
|
272792
273008
|
captured.push(`${scope}:${key} = ${JSON.stringify(value)}`);
|
|
272793
273009
|
}
|
|
@@ -272802,7 +273018,7 @@ var require_workflow_simulator = __commonJS({
|
|
|
272802
273018
|
if (value === void 0 || value === null)
|
|
272803
273019
|
continue;
|
|
272804
273020
|
const [scope, key] = memKey.includes(":") ? memKey.split(":", 2) : ["workflow", memKey];
|
|
272805
|
-
const memory =
|
|
273021
|
+
const memory = this.memory(scope);
|
|
272806
273022
|
await memory.set(key, value);
|
|
272807
273023
|
captured.push(`${scope}:${key} = ${JSON.stringify(value)}`);
|
|
272808
273024
|
}
|
|
@@ -272818,7 +273034,7 @@ var require_workflow_simulator = __commonJS({
|
|
|
272818
273034
|
if (body[paramName] !== void 0) {
|
|
272819
273035
|
result = result.replace(p, String(body[paramName]));
|
|
272820
273036
|
} else {
|
|
272821
|
-
const memory =
|
|
273037
|
+
const memory = this.memory("workflow");
|
|
272822
273038
|
const memVal = await memory.get(paramName);
|
|
272823
273039
|
if (memVal !== null) {
|
|
272824
273040
|
result = result.replace(p, String(memVal));
|
|
@@ -272837,6 +273053,22 @@ var require_dist13 = __commonJS({
|
|
|
272837
273053
|
"../../packages/simulator/dist/index.js"(exports2) {
|
|
272838
273054
|
"use strict";
|
|
272839
273055
|
init_cjs_shims();
|
|
273056
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
273057
|
+
if (k2 === void 0) k2 = k;
|
|
273058
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
273059
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
273060
|
+
desc = { enumerable: true, get: function() {
|
|
273061
|
+
return m[k];
|
|
273062
|
+
} };
|
|
273063
|
+
}
|
|
273064
|
+
Object.defineProperty(o, k2, desc);
|
|
273065
|
+
}) : (function(o, m, k, k2) {
|
|
273066
|
+
if (k2 === void 0) k2 = k;
|
|
273067
|
+
o[k2] = m[k];
|
|
273068
|
+
}));
|
|
273069
|
+
var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
|
|
273070
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
273071
|
+
};
|
|
272840
273072
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
272841
273073
|
exports2.WorkflowSimulator = exports2.ResponseValidator = exports2.DataGenerator = exports2.EndpointSimulator = void 0;
|
|
272842
273074
|
var simulator_1 = require_simulator();
|
|
@@ -272855,6 +273087,7 @@ var require_dist13 = __commonJS({
|
|
|
272855
273087
|
Object.defineProperty(exports2, "WorkflowSimulator", { enumerable: true, get: function() {
|
|
272856
273088
|
return workflow_simulator_1.WorkflowSimulator;
|
|
272857
273089
|
} });
|
|
273090
|
+
__exportStar(require_conditions(), exports2);
|
|
272858
273091
|
}
|
|
272859
273092
|
});
|
|
272860
273093
|
|
|
@@ -273238,8 +273471,13 @@ var require_workflow_tools = __commonJS({
|
|
|
273238
273471
|
...targetEnv,
|
|
273239
273472
|
baseUrl: args.baseUrl || targetEnv.baseUrl
|
|
273240
273473
|
};
|
|
273241
|
-
const
|
|
273242
|
-
const
|
|
273474
|
+
const projectRoot = (0, types_1.projectRootOfModel)(filePath);
|
|
273475
|
+
const workflowsDir = path15.join(path15.dirname(filePath), "workflows");
|
|
273476
|
+
const simulator = new simulator_1.WorkflowSimulator(model, effectiveEnv, { projectRoot });
|
|
273477
|
+
const result = await simulator.simulateWorkflow(workflowDef, {
|
|
273478
|
+
clearMemory: args.clearMemory ?? false,
|
|
273479
|
+
loadWorkflow: (slugOrName) => (0, model_1.loadWorkflowByName)(workflowsDir, slugOrName)
|
|
273480
|
+
});
|
|
273243
273481
|
return {
|
|
273244
273482
|
...result,
|
|
273245
273483
|
filePath,
|
|
@@ -273304,7 +273542,23 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273304
273542
|
var path15 = __importStar(require("path"));
|
|
273305
273543
|
var types_1 = require_types();
|
|
273306
273544
|
var workflow_tools_1 = require_workflow_tools();
|
|
273307
|
-
var
|
|
273545
|
+
var simulator_1 = require_dist13();
|
|
273546
|
+
var model_1 = require_dist2();
|
|
273547
|
+
var conditionRuleSchema = zod_1.z.object({
|
|
273548
|
+
left: zod_1.z.string().min(1).describe('Value to test, usually a template: "{{workflow:role}}"'),
|
|
273549
|
+
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"),
|
|
273550
|
+
right: zod_1.z.string().optional().describe("Value to compare against (omit for exists/not_exists/is_empty/is_not_empty)")
|
|
273551
|
+
});
|
|
273552
|
+
var rulesConditionSchema = zod_1.z.object({
|
|
273553
|
+
rules: zod_1.z.object({
|
|
273554
|
+
all: zod_1.z.array(conditionRuleSchema).optional().describe("AND \u2014 every rule must hold"),
|
|
273555
|
+
any: zod_1.z.array(conditionRuleSchema).optional().describe("OR \u2014 at least one rule must hold")
|
|
273556
|
+
}).describe("Rule group evaluated after the step runs"),
|
|
273557
|
+
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"),
|
|
273558
|
+
switchToWorkflow: zod_1.z.string().optional().describe('Workflow slug or name to run when onFail is "switch"'),
|
|
273559
|
+
returnOnComplete: zod_1.z.boolean().optional().describe('With onFail "switch": resume this workflow after the target finishes')
|
|
273560
|
+
}).describe("Canonical condition: evaluated after the step; onFail steers the run");
|
|
273561
|
+
var legacyConditionSchema = zod_1.z.object({
|
|
273308
273562
|
if: zod_1.z.string().min(1).describe('Template expression to resolve, e.g. "{{workflow:role}}"'),
|
|
273309
273563
|
equals: zod_1.z.string().optional().describe("Resolved value must equal this string"),
|
|
273310
273564
|
notEquals: zod_1.z.string().optional().describe("Resolved value must NOT equal this string"),
|
|
@@ -273313,7 +273567,8 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273313
273567
|
greaterThan: zod_1.z.number().optional().describe("Numeric greater-than comparison"),
|
|
273314
273568
|
lessThan: zod_1.z.number().optional().describe("Numeric less-than comparison"),
|
|
273315
273569
|
in: zod_1.z.array(zod_1.z.string()).optional().describe("Resolved value must be one of these strings")
|
|
273316
|
-
}).describe("
|
|
273570
|
+
}).describe("Legacy guard: evaluated before the step; if not met the step is skipped");
|
|
273571
|
+
var stepConditionSchema = zod_1.z.union([rulesConditionSchema, legacyConditionSchema]);
|
|
273317
273572
|
var stepRetrySchema = zod_1.z.object({
|
|
273318
273573
|
times: zod_1.z.number().int().positive().max(10).describe("Max retry attempts (1\u201310)"),
|
|
273319
273574
|
delayMs: zod_1.z.number().int().nonnegative().optional().describe("Delay between retries in ms (default 0)")
|
|
@@ -273328,7 +273583,7 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273328
273583
|
capture: zod_1.z.record(zod_1.z.string()).optional().describe('Save RESPONSE fields to memory: "scope:key" \u2192 "dot.path.in.response"'),
|
|
273329
273584
|
captureInput: zod_1.z.record(zod_1.z.string()).optional().describe('Save REQUEST body fields to memory BEFORE sending: "scope:key" \u2192 "bodyField"'),
|
|
273330
273585
|
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(
|
|
273586
|
+
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
273587
|
onFailure: zod_1.z.enum(["abort", "continue"]).optional().describe('"abort" (default): stop the workflow on failure. "continue": log failure and proceed (captures skipped).'),
|
|
273333
273588
|
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
273589
|
retry: stepRetrySchema.optional().describe("Retry on failure \u2014 overrides the workflow-level global retry.")
|
|
@@ -273411,6 +273666,58 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273411
273666
|
const prefix = wfPath.split("/").slice(0, 3).join("/");
|
|
273412
273667
|
return model.filter((e) => e.path.startsWith(prefix)).map((e) => `${e.method} ${e.path}`).slice(0, 3);
|
|
273413
273668
|
}
|
|
273669
|
+
function checkRulesCondition(cond, stepNo, sName, errors, warnings, ownName, workflowsDir2) {
|
|
273670
|
+
const refs = [];
|
|
273671
|
+
const rules = cond.rules;
|
|
273672
|
+
let ruleCount = 0;
|
|
273673
|
+
for (const group of ["all", "any"]) {
|
|
273674
|
+
const list = rules[group];
|
|
273675
|
+
if (list === void 0)
|
|
273676
|
+
continue;
|
|
273677
|
+
if (!Array.isArray(list)) {
|
|
273678
|
+
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" }].`);
|
|
273679
|
+
continue;
|
|
273680
|
+
}
|
|
273681
|
+
list.forEach((rule, ri) => {
|
|
273682
|
+
ruleCount++;
|
|
273683
|
+
const where = `Step ${stepNo} condition.rules.${group}[${ri}]`;
|
|
273684
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) {
|
|
273685
|
+
err(errors, stepNo, sName, "BAD_CONDITION", `${where} must be an object { left, operator, right? }.`, 'Use { "left": "{{workflow:role}}", "operator": "equals", "right": "admin" }.');
|
|
273686
|
+
return;
|
|
273687
|
+
}
|
|
273688
|
+
if (typeof rule.left !== "string" || !rule.left.trim()) {
|
|
273689
|
+
err(errors, stepNo, sName, "BAD_CONDITION", `${where} needs a non-empty "left".`, 'Set "left" to a value or template like "{{workflow:role}}".');
|
|
273690
|
+
} else if (!/\{\{[^}]+\}\}/.test(rule.left)) {
|
|
273691
|
+
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.");
|
|
273692
|
+
}
|
|
273693
|
+
if (!simulator_1.CONDITION_OPERATORS.includes(rule.operator)) {
|
|
273694
|
+
err(errors, stepNo, sName, "BAD_CONDITION_OPERATOR", `${where} has unknown operator ${JSON.stringify(rule.operator)}.`, `Use one of: ${simulator_1.CONDITION_OPERATORS.join(", ")}.`);
|
|
273695
|
+
} else if (!simulator_1.UNARY_OPERATORS.includes(rule.operator) && rule.right === void 0) {
|
|
273696
|
+
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.');
|
|
273697
|
+
}
|
|
273698
|
+
collectTemplateRefs(rule.left, refs);
|
|
273699
|
+
collectTemplateRefs(rule.right, refs);
|
|
273700
|
+
});
|
|
273701
|
+
}
|
|
273702
|
+
if (ruleCount === 0 && !errors.some((e) => e.stepIndex === stepNo && e.code === "BAD_CONDITION")) {
|
|
273703
|
+
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".');
|
|
273704
|
+
}
|
|
273705
|
+
if (!["abort", "continue", "switch"].includes(cond.onFail)) {
|
|
273706
|
+
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.');
|
|
273707
|
+
} else if (cond.onFail === "switch") {
|
|
273708
|
+
const target = typeof cond.switchToWorkflow === "string" ? cond.switchToWorkflow.trim() : "";
|
|
273709
|
+
if (!target) {
|
|
273710
|
+
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.');
|
|
273711
|
+
} else if (ownName && target.toLowerCase() === ownName.trim().toLowerCase()) {
|
|
273712
|
+
err(errors, stepNo, sName, "CONDITION_SWITCH_SELF", `Step ${stepNo} switches to this same workflow ("${target}"), which would loop.`, 'Point "switchToWorkflow" at a different workflow.');
|
|
273713
|
+
} else if (!(0, model_1.loadWorkflowByName)(workflowsDir2, target)) {
|
|
273714
|
+
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.");
|
|
273715
|
+
}
|
|
273716
|
+
} else if (cond.returnOnComplete !== void 0 || cond.switchToWorkflow !== void 0) {
|
|
273717
|
+
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.');
|
|
273718
|
+
}
|
|
273719
|
+
return refs;
|
|
273720
|
+
}
|
|
273414
273721
|
function validateWorkflowDefinition2(wf, opts) {
|
|
273415
273722
|
const errors = [];
|
|
273416
273723
|
const warnings = [];
|
|
@@ -273447,6 +273754,7 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273447
273754
|
return;
|
|
273448
273755
|
}
|
|
273449
273756
|
const step = rawStep;
|
|
273757
|
+
let rulesRefs = [];
|
|
273450
273758
|
for (const key of Object.keys(step)) {
|
|
273451
273759
|
if (MODEL_DIALECT_KEYS.has(key)) {
|
|
273452
273760
|
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.');
|
|
@@ -273457,9 +273765,11 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273457
273765
|
if ("condition" in step && step.condition !== void 0) {
|
|
273458
273766
|
const cond = step.condition;
|
|
273459
273767
|
if (!cond || typeof cond !== "object" || Array.isArray(cond)) {
|
|
273460
|
-
err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must be an object.`, 'Use { "condition": { "
|
|
273768
|
+
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" } }.');
|
|
273769
|
+
} else if ((0, simulator_1.conditionKind)(cond) === "rules") {
|
|
273770
|
+
rulesRefs = checkRulesCondition(cond, stepNo, sName, errors, warnings, wf.name, path15.join(projectRoot, ".jetic", "workflows"));
|
|
273461
273771
|
} else if (!cond.if || typeof cond.if !== "string" || !cond.if.trim()) {
|
|
273462
|
-
err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition"
|
|
273772
|
+
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
273773
|
} else {
|
|
273464
273774
|
const operators = ["equals", "notEquals", "contains", "exists", "greaterThan", "lessThan", "in"];
|
|
273465
273775
|
const usedOps = operators.filter((op) => op in cond && cond[op] !== void 0);
|
|
@@ -273658,6 +273968,25 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
273658
273968
|
}
|
|
273659
273969
|
}
|
|
273660
273970
|
}
|
|
273971
|
+
for (const ref of rulesRefs) {
|
|
273972
|
+
if (ref.startsWith("faker."))
|
|
273973
|
+
continue;
|
|
273974
|
+
if (!ref.includes(":")) {
|
|
273975
|
+
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.");
|
|
273976
|
+
continue;
|
|
273977
|
+
}
|
|
273978
|
+
const { scope, key } = splitMemKey(ref);
|
|
273979
|
+
if (!key)
|
|
273980
|
+
continue;
|
|
273981
|
+
if (scope === "human") {
|
|
273982
|
+
humanKeys.add(key);
|
|
273983
|
+
continue;
|
|
273984
|
+
}
|
|
273985
|
+
const full = `${scope}:${key}`;
|
|
273986
|
+
if (!defined.has(full) && !persistedMemory.has(full)) {
|
|
273987
|
+
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.`);
|
|
273988
|
+
}
|
|
273989
|
+
}
|
|
273661
273990
|
});
|
|
273662
273991
|
return toReport(errors, warnings, humanKeys);
|
|
273663
273992
|
}
|
|
@@ -288859,11 +289188,20 @@ var require_server3 = __commonJS({
|
|
|
288859
289188
|
" The scaffold tool already handles faker mapping \u2014 only override to {{human:key}} when a",
|
|
288860
289189
|
" field appears in missingFields AND genuinely requires interactive user input.",
|
|
288861
289190
|
"",
|
|
288862
|
-
"STEP 4c \u2014 CONDITIONS (branching /
|
|
288863
|
-
"
|
|
288864
|
-
' { "condition": { "
|
|
288865
|
-
"
|
|
288866
|
-
"
|
|
289191
|
+
"STEP 4c \u2014 CONDITIONS (branching / assertions on memory)",
|
|
289192
|
+
" Preferred form \u2014 checked AFTER the step runs, so it can use values that step just captured:",
|
|
289193
|
+
' { "condition": { "rules": { "all": [ { "left": "{{workflow:userRole}}", "operator": "equals", "right": "admin" } ] },',
|
|
289194
|
+
' "onFail": "abort" } }',
|
|
289195
|
+
" rules.all = AND, rules.any = OR. Operators: equals, not_equals, greater_than(_or_equal), less_than(_or_equal),",
|
|
289196
|
+
" exists, not_exists, is_empty, is_not_empty, contains, not_contains, starts_with, ends_with.",
|
|
289197
|
+
" onFail when the rules do NOT hold:",
|
|
289198
|
+
' \u2022 "abort" \u2014 fail the workflow',
|
|
289199
|
+
' \u2022 "continue" \u2014 stop the remaining steps, workflow still passes (branch not applicable)',
|
|
289200
|
+
' \u2022 "switch" \u2014 run another saved workflow: add "switchToWorkflow": "<slug or name>";',
|
|
289201
|
+
' add "returnOnComplete": true to resume this workflow afterwards',
|
|
289202
|
+
" Legacy form (still supported) \u2014 a guard checked BEFORE the step; a miss skips just that step (counts as passed):",
|
|
289203
|
+
' { "condition": { "if": "{{workflow:userRole}}", "equals": "admin" } } // equals, notEquals, contains, exists, greaterThan, lessThan, in',
|
|
289204
|
+
" A condition that is neither shape FAILS the run \u2014 it is never ignored.",
|
|
288867
289205
|
' Pair with onFailure:"continue" for negative/branching flows.',
|
|
288868
289206
|
"",
|
|
288869
289207
|
"STEP 4d \u2014 FAILURE MODES & RETRY",
|
|
@@ -288952,8 +289290,8 @@ var require_server3 = __commonJS({
|
|
|
288952
289290
|
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
289291
|
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);
|
|
288954
289292
|
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 (
|
|
289293
|
+
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);
|
|
289294
|
+
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
289295
|
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
289296
|
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
289297
|
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 +290035,12 @@ Running ${files.length} workflow(s)...
|
|
|
289697
290035
|
for (const file of files) {
|
|
289698
290036
|
const workflow = (0, import_model2.readWorkflowDefinitionFile)(path9.join(config.workflowsDir, file));
|
|
289699
290037
|
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
|
-
|
|
290038
|
+
const simulator = new import_simulator.WorkflowSimulator(model, targetEnv, { projectRoot: config.projectRoot });
|
|
290039
|
+
const result = await simulator.simulateWorkflow(workflow, {
|
|
290040
|
+
loadWorkflow: (slugOrName) => (0, import_model2.loadWorkflowByName)(config.workflowsDir, slugOrName)
|
|
290041
|
+
});
|
|
290042
|
+
const failures = result.results.filter((r) => !r.passed).map((r) => ({ step: r.name, status: r.actualStatus, error: r.error }));
|
|
290043
|
+
results.push({ name: workflow.name, passed: result.success, durationMs: result.stats.totalTimeMs, failures });
|
|
289703
290044
|
if (result.success) {
|
|
289704
290045
|
import_core11.jeticEvents.emit("workflow.completed", { workflowId: workflow.name, passed: true });
|
|
289705
290046
|
} else {
|
|
@@ -289707,6 +290048,10 @@ Running ${files.length} workflow(s)...
|
|
|
289707
290048
|
}
|
|
289708
290049
|
if (!options.ci) {
|
|
289709
290050
|
console.log(` ${result.success ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m"} ${workflow.name}`);
|
|
290051
|
+
for (const failure of failures) {
|
|
290052
|
+
const detail = failure.error ?? (failure.status ? `HTTP ${failure.status}` : "failed");
|
|
290053
|
+
console.log(` \x1B[2m\u21B3 ${failure.step}: ${detail}\x1B[0m`);
|
|
290054
|
+
}
|
|
289710
290055
|
}
|
|
289711
290056
|
}
|
|
289712
290057
|
const passed = results.filter((r) => r.passed).length;
|
|
@@ -289830,7 +290175,7 @@ memoryCommand.command("clear").description("Clear memory").action(() => {
|
|
|
289830
290175
|
init_cjs_shims();
|
|
289831
290176
|
var import_commander15 = require("commander");
|
|
289832
290177
|
var import_core14 = __toESM(require_dist());
|
|
289833
|
-
var
|
|
290178
|
+
var import_simulator3 = __toESM(require_dist13());
|
|
289834
290179
|
var path12 = __toESM(require("path"));
|
|
289835
290180
|
|
|
289836
290181
|
// src/commands/simulate-workflow.ts
|
|
@@ -289839,7 +290184,9 @@ var path11 = __toESM(require("path"));
|
|
|
289839
290184
|
var fs7 = __toESM(require("fs"));
|
|
289840
290185
|
var import_commander14 = require("commander");
|
|
289841
290186
|
var import_core13 = __toESM(require_dist());
|
|
290187
|
+
var import_model5 = __toESM(require_dist2());
|
|
289842
290188
|
var import_memory2 = __toESM(require_dist12());
|
|
290189
|
+
var import_simulator2 = __toESM(require_dist13());
|
|
289843
290190
|
var import_faker = require("@faker-js/faker");
|
|
289844
290191
|
var c = {
|
|
289845
290192
|
reset: "\x1B[0m",
|
|
@@ -289896,58 +290243,32 @@ function deepGet(obj, dotPath) {
|
|
|
289896
290243
|
async function resolveConditionRef(ref, context = "condition") {
|
|
289897
290244
|
return ref.replace(/\{\{([^}]+)\}\}/g, () => "").trim() === ref.trim() ? ref : await resolveTemplateString(ref, context);
|
|
289898
290245
|
}
|
|
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
290246
|
async function evaluateStepCondition(condition, context = "condition") {
|
|
289937
|
-
|
|
289938
|
-
|
|
289939
|
-
|
|
289940
|
-
if (
|
|
289941
|
-
const
|
|
289942
|
-
|
|
289943
|
-
|
|
290247
|
+
return (0, import_simulator2.evaluateRules)(condition.rules, (value) => resolveConditionRef(value, context));
|
|
290248
|
+
}
|
|
290249
|
+
async function preStepCondition(step) {
|
|
290250
|
+
if (!step.condition) return null;
|
|
290251
|
+
const label = step.name || `${step.method} ${step.path}`;
|
|
290252
|
+
const base = { step, status: 0, responseBody: null, durationMs: 0, captured: [], injected: {} };
|
|
290253
|
+
const kind = (0, import_simulator2.conditionKind)(step.condition);
|
|
290254
|
+
if (kind === "invalid") {
|
|
290255
|
+
return { ...base, passed: false, error: `Step "${label}": ${(0, import_simulator2.describeInvalidCondition)(step.condition)}` };
|
|
290256
|
+
}
|
|
290257
|
+
if (kind === "legacy") {
|
|
290258
|
+
const outcome = await (0, import_simulator2.evaluateLegacyCondition)(
|
|
290259
|
+
step.condition,
|
|
290260
|
+
(value) => resolveConditionRef(value, `condition of step "${label}"`)
|
|
290261
|
+
);
|
|
290262
|
+
if (!outcome.matched) {
|
|
290263
|
+
return {
|
|
290264
|
+
...base,
|
|
290265
|
+
passed: true,
|
|
290266
|
+
skipped: true,
|
|
290267
|
+
conditionResult: { passed: false, reason: `legacy guard not met (resolved "${outcome.evaluated}")` }
|
|
290268
|
+
};
|
|
290269
|
+
}
|
|
289944
290270
|
}
|
|
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 };
|
|
290271
|
+
return null;
|
|
289951
290272
|
}
|
|
289952
290273
|
function envKeyForHuman(key) {
|
|
289953
290274
|
return `JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
@@ -290236,12 +290557,14 @@ function renderStepResult(result, index, total) {
|
|
|
290236
290557
|
console.log(` ${CHAIN} ${c.green}\u{1F4BE} captured ${cap}${c.reset}`);
|
|
290237
290558
|
}
|
|
290238
290559
|
}
|
|
290239
|
-
if (result.
|
|
290560
|
+
if (result.skipped) {
|
|
290561
|
+
console.log(` ${CHAIN} ${c.yellow}\u2298 skipped${c.reset} ${c.dim}${result.conditionResult?.reason ?? ""}${c.reset}`);
|
|
290562
|
+
} else if (result.conditionResult) {
|
|
290240
290563
|
const { passed: condPassed, reason } = result.conditionResult;
|
|
290241
290564
|
const condIcon = condPassed ? `${c.magenta}\u25C7${c.reset}` : `${c.yellow}\u25C8${c.reset}`;
|
|
290242
290565
|
const condColor = condPassed ? c.magenta : c.yellow;
|
|
290243
290566
|
console.log(` ${CHAIN} ${condIcon} ${condColor}condition ${condPassed ? "passed" : "failed"}${c.reset} ${c.dim}${reason}${c.reset}`);
|
|
290244
|
-
if (!condPassed && result.step.condition) {
|
|
290567
|
+
if (!condPassed && (0, import_simulator2.isRulesCondition)(result.step.condition)) {
|
|
290245
290568
|
const { onFail, switchToWorkflow } = result.step.condition;
|
|
290246
290569
|
const actionStr = onFail === "switch" ? `switching to workflow: ${switchToWorkflow ?? "unknown"}` : onFail === "continue" ? "continuing without remaining steps" : "aborting workflow";
|
|
290247
290570
|
console.log(` ${CHAIN} ${c.yellow} \u2192 ${actionStr}${c.reset}`);
|
|
@@ -290458,12 +290781,17 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290458
290781
|
spinner.start(`${stepLabel} ${c.dim}${step.name}${c.reset}...`);
|
|
290459
290782
|
let result;
|
|
290460
290783
|
try {
|
|
290461
|
-
|
|
290462
|
-
|
|
290784
|
+
const preempted = await preStepCondition(step);
|
|
290785
|
+
result = preempted ?? await executeStep(step, baseUrl, {});
|
|
290786
|
+
if (!preempted && (0, import_simulator2.isRulesCondition)(step.condition)) {
|
|
290463
290787
|
const condResult = await evaluateStepCondition(step.condition, `condition of step "${step.name || step.path}"`);
|
|
290464
290788
|
result.conditionResult = condResult;
|
|
290465
290789
|
if (!condResult.passed) {
|
|
290466
290790
|
const { onFail, switchToWorkflow } = step.condition;
|
|
290791
|
+
if (onFail === "abort") {
|
|
290792
|
+
result.passed = false;
|
|
290793
|
+
result.error = `Condition failed: ${condResult.reason}`;
|
|
290794
|
+
}
|
|
290467
290795
|
results.push(result);
|
|
290468
290796
|
const icon2 = result.passed ? TICK : CROSS;
|
|
290469
290797
|
spinner.stop(` ${icon2} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
|
|
@@ -290481,10 +290809,9 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290481
290809
|
} else if (onFail === "switch" && switchToWorkflow) {
|
|
290482
290810
|
console.log(` ${ARROW} Condition failed \u2014 switching to workflow: ${c.bold}${switchToWorkflow}${c.reset}
|
|
290483
290811
|
`);
|
|
290484
|
-
const
|
|
290485
|
-
if (
|
|
290812
|
+
const switchedWorkflow = (0, import_model5.loadWorkflowByName)(workflowsDir, switchToWorkflow);
|
|
290813
|
+
if (switchedWorkflow) {
|
|
290486
290814
|
try {
|
|
290487
|
-
const switchedWorkflow = JSON.parse(fs7.readFileSync(switchPath, "utf-8"));
|
|
290488
290815
|
console.log(` ${TICK} Loaded switch workflow: ${c.bold}${switchedWorkflow.name}${c.reset}
|
|
290489
290816
|
`);
|
|
290490
290817
|
renderWorkflowHeader(switchedWorkflow);
|
|
@@ -290495,8 +290822,9 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290495
290822
|
const switchLabel = `${c.dim}${si + 1}/${switchedWorkflow.steps.length}${c.reset} ${getMethodColor(switchStep.method)}${switchStep.method}${c.reset} ${switchStep.path}`;
|
|
290496
290823
|
const switchSpinner = new Spinner();
|
|
290497
290824
|
switchSpinner.start(`${switchLabel} ${c.dim}${switchStep.name}${c.reset}...`);
|
|
290498
|
-
const
|
|
290499
|
-
|
|
290825
|
+
const switchPre = await preStepCondition(switchStep);
|
|
290826
|
+
const switchResult = switchPre ?? await executeStep(switchStep, baseUrl, {});
|
|
290827
|
+
if (!switchPre && (0, import_simulator2.isRulesCondition)(switchStep.condition)) {
|
|
290500
290828
|
switchResult.conditionResult = await evaluateStepCondition(
|
|
290501
290829
|
switchStep.condition,
|
|
290502
290830
|
`condition of step "${switchStep.name || switchStep.path}"`
|
|
@@ -290508,12 +290836,16 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290508
290836
|
renderStepResult(switchResult, si, switchedWorkflow.steps.length);
|
|
290509
290837
|
}
|
|
290510
290838
|
} catch (e) {
|
|
290511
|
-
console.log(` ${c.red}\u2717 Failed to
|
|
290839
|
+
console.log(` ${c.red}\u2717 Failed to run switch workflow: ${e.message}${c.reset}
|
|
290512
290840
|
`);
|
|
290841
|
+
result.passed = false;
|
|
290842
|
+
result.error = `Switch workflow "${switchToWorkflow}" failed: ${e.message}`;
|
|
290513
290843
|
}
|
|
290514
290844
|
} else {
|
|
290515
|
-
console.log(` ${c.
|
|
290845
|
+
console.log(` ${c.red}\u2717 Switch workflow not found: ${switchToWorkflow}${c.reset}
|
|
290516
290846
|
`);
|
|
290847
|
+
result.passed = false;
|
|
290848
|
+
result.error = `Switch workflow "${switchToWorkflow}" was not found in ${workflowsDir}`;
|
|
290517
290849
|
}
|
|
290518
290850
|
if (step.condition.returnOnComplete) {
|
|
290519
290851
|
console.log(` ${c.green}\u21A9 Sub-workflow complete. Returning to main workflow...${c.reset}
|
|
@@ -290524,6 +290856,14 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290524
290856
|
break;
|
|
290525
290857
|
}
|
|
290526
290858
|
}
|
|
290859
|
+
if (onFail === "switch" && !switchToWorkflow) {
|
|
290860
|
+
console.log(` ${c.red}\u2717 Condition failed and onFail "switch" has no switchToWorkflow.${c.reset}
|
|
290861
|
+
`);
|
|
290862
|
+
result.passed = false;
|
|
290863
|
+
result.error = 'onFail "switch" has no switchToWorkflow';
|
|
290864
|
+
stopOnFailure = true;
|
|
290865
|
+
break;
|
|
290866
|
+
}
|
|
290527
290867
|
continue;
|
|
290528
290868
|
}
|
|
290529
290869
|
}
|
|
@@ -290538,9 +290878,15 @@ var simulateWorkflowCommand = new import_commander14.Command("workflow").descrip
|
|
|
290538
290878
|
throw err;
|
|
290539
290879
|
}
|
|
290540
290880
|
results.push(result);
|
|
290541
|
-
const icon = result.passed ? TICK : CROSS;
|
|
290881
|
+
const icon = result.skipped ? SKIP : result.passed ? TICK : CROSS;
|
|
290542
290882
|
spinner.stop(` ${icon} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
|
|
290543
290883
|
renderStepResult(result, i, workflow.steps.length);
|
|
290884
|
+
if (!result.passed && result.error && !result.status && (0, import_simulator2.conditionKind)(step.condition) === "invalid") {
|
|
290885
|
+
console.log(` ${c.red}${c.bold}\u26A0 Unrecognized condition \u2014 stopping workflow.${c.reset}
|
|
290886
|
+
`);
|
|
290887
|
+
stopOnFailure = true;
|
|
290888
|
+
break;
|
|
290889
|
+
}
|
|
290544
290890
|
if (!result.passed && (step.path.includes("/login") || step.path.includes("/register") || step.path.includes("/auth"))) {
|
|
290545
290891
|
console.log(` ${c.red}${c.bold}\u26A0 Auth step failed \u2014 stopping workflow to prevent cascading failures.${c.reset}
|
|
290546
290892
|
`);
|
|
@@ -290663,15 +291009,19 @@ var Spinner2 = class {
|
|
|
290663
291009
|
}
|
|
290664
291010
|
}
|
|
290665
291011
|
};
|
|
290666
|
-
|
|
291012
|
+
function defaultEnvironment(model, environments) {
|
|
291013
|
+
const defaultName = model.defaultEnvironment ?? environments.find((e) => e.default)?.name;
|
|
291014
|
+
return environments.find((e) => e.name === defaultName) ?? environments[0];
|
|
291015
|
+
}
|
|
291016
|
+
async function selectEnvironment(model, environments) {
|
|
290667
291017
|
if (environments.length === 0) {
|
|
290668
291018
|
console.log(` ${c2.yellow}\u26A0${c2.reset} No environments defined in model.json`);
|
|
290669
291019
|
console.log(` ${c2.dim}Using default: http://localhost:3000${c2.reset}
|
|
290670
291020
|
`);
|
|
290671
291021
|
return { name: "default", baseUrl: "http://localhost:3000" };
|
|
290672
291022
|
}
|
|
290673
|
-
if (environments.length === 1) {
|
|
290674
|
-
const env = environments
|
|
291023
|
+
if (environments.length === 1 || !process.stdin.isTTY) {
|
|
291024
|
+
const env = defaultEnvironment(model, environments);
|
|
290675
291025
|
console.log(` ${c2.cyan}\u{1F30D}${c2.reset} Environment: ${c2.bold}${env.name}${c2.reset} ${c2.dim}\u2192 ${env.baseUrl}${c2.reset}
|
|
290676
291026
|
`);
|
|
290677
291027
|
return env;
|
|
@@ -290914,7 +291264,7 @@ function loadModel3() {
|
|
|
290914
291264
|
}
|
|
290915
291265
|
var simulateCommand = new import_commander15.Command("simulate").description("Simulate API endpoints against a live server");
|
|
290916
291266
|
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) => {
|
|
291267
|
+
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
291268
|
console.log("");
|
|
290919
291269
|
console.log(` ${c2.bgCyan}${c2.black}${c2.bold} JETIC ${c2.reset} ${c2.cyan}${c2.bold}Endpoint Simulator${c2.reset}`);
|
|
290920
291270
|
console.log("");
|
|
@@ -290928,7 +291278,20 @@ simulateCommand.command("endpoint [method] [endpointPath]").description("Simulat
|
|
|
290928
291278
|
console.log(` ${c2.dim}Model v${model.version} \u2022 ${model.endpoints.length} endpoints${c2.reset}
|
|
290929
291279
|
`);
|
|
290930
291280
|
const environments = model.environments || [];
|
|
290931
|
-
|
|
291281
|
+
let selectedEnv;
|
|
291282
|
+
if (options.env) {
|
|
291283
|
+
const named = environments.find((e) => e.name === options.env);
|
|
291284
|
+
if (!named) {
|
|
291285
|
+
console.log(` ${c2.red}\u2717${c2.reset} Environment ${c2.bold}${options.env}${c2.reset} not found. Available: ${environments.map((e) => e.name).join(", ") || "(none)"}
|
|
291286
|
+
`);
|
|
291287
|
+
process.exit(2);
|
|
291288
|
+
}
|
|
291289
|
+
selectedEnv = named;
|
|
291290
|
+
console.log(` ${c2.cyan}\u{1F30D}${c2.reset} Environment: ${c2.bold}${named.name}${c2.reset} ${c2.dim}\u2192 ${named.baseUrl}${c2.reset}
|
|
291291
|
+
`);
|
|
291292
|
+
} else {
|
|
291293
|
+
selectedEnv = await selectEnvironment(model, environments);
|
|
291294
|
+
}
|
|
290932
291295
|
const checkSpinner = new Spinner2();
|
|
290933
291296
|
checkSpinner.start(`Checking if backend is active at ${selectedEnv.baseUrl}...`);
|
|
290934
291297
|
try {
|
|
@@ -290942,7 +291305,7 @@ simulateCommand.command("endpoint [method] [endpointPath]").description("Simulat
|
|
|
290942
291305
|
`);
|
|
290943
291306
|
process.exit(1);
|
|
290944
291307
|
}
|
|
290945
|
-
const simulator = new
|
|
291308
|
+
const simulator = new import_simulator3.EndpointSimulator(model, selectedEnv);
|
|
290946
291309
|
const spinner = new Spinner2();
|
|
290947
291310
|
if (options.all) {
|
|
290948
291311
|
console.log(` ${c2.magenta}\u{1F680}${c2.reset} Simulating ${c2.bold}${model.endpoints.length}${c2.reset} endpoints...
|
|
@@ -291005,8 +291368,9 @@ var import_commander16 = require("commander");
|
|
|
291005
291368
|
var import_express = __toESM(require("express"));
|
|
291006
291369
|
var import_path = __toESM(require("path"));
|
|
291007
291370
|
var import_fs = __toESM(require("fs"));
|
|
291008
|
-
var
|
|
291371
|
+
var import_model6 = __toESM(require_dist2());
|
|
291009
291372
|
var import_memory3 = __toESM(require_dist12());
|
|
291373
|
+
var import_simulator4 = __toESM(require_dist13());
|
|
291010
291374
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".cache", "__pycache__", ".next", ".nuxt", ".venv"]);
|
|
291011
291375
|
var IGNORED_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".mp4", ".webm", ".woff", ".woff2", ".ttf", ".eot", ".map"]);
|
|
291012
291376
|
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 +291519,7 @@ var ChangeScanner = class {
|
|
|
291155
291519
|
this.saveChanges(data);
|
|
291156
291520
|
}
|
|
291157
291521
|
};
|
|
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) => {
|
|
291522
|
+
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
291523
|
const port = parseInt(options.port, 10);
|
|
291160
291524
|
const app = (0, import_express.default)();
|
|
291161
291525
|
const projectRoot = process.cwd();
|
|
@@ -291183,7 +291547,7 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
|
|
|
291183
291547
|
const jeticDir2 = import_path.default.join(process.cwd(), ".jetic");
|
|
291184
291548
|
const modelDir = import_path.default.join(jeticDir2, "model");
|
|
291185
291549
|
try {
|
|
291186
|
-
if ((0,
|
|
291550
|
+
if ((0, import_model6.hasYamlModel)(modelDir)) return res.json((0, import_model6.loadModelFromDir)(modelDir));
|
|
291187
291551
|
const modelPath = import_path.default.join(jeticDir2, "model.json");
|
|
291188
291552
|
if (!import_fs.default.existsSync(modelPath)) return res.json(null);
|
|
291189
291553
|
res.json(JSON.parse(import_fs.default.readFileSync(modelPath, "utf8")));
|
|
@@ -291793,57 +292157,43 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
|
|
|
291793
292157
|
}
|
|
291794
292158
|
return result;
|
|
291795
292159
|
}
|
|
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
292160
|
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 };
|
|
292161
|
+
return (0, import_simulator4.evaluateRules)(condition.rules ?? {}, (value) => resolveCondRef(value, stepIndex, stepName));
|
|
291842
292162
|
}
|
|
291843
292163
|
for (let i = 0; i < workflow.steps.length; i++) {
|
|
291844
292164
|
if (aborted) break;
|
|
291845
292165
|
const step = workflow.steps[i];
|
|
291846
292166
|
send("step_start", { index: i, step: { name: step.name, method: step.method, path: step.path } });
|
|
292167
|
+
const condKind = step.condition ? (0, import_simulator4.conditionKind)(step.condition) : void 0;
|
|
292168
|
+
const stepSummary = { name: step.name, method: step.method, path: step.path, description: step.description };
|
|
292169
|
+
if (condKind === "invalid") {
|
|
292170
|
+
const reason = `Step ${i + 1} "${step.name || `${step.method} ${step.path}`}": ${(0, import_simulator4.describeInvalidCondition)(step.condition)}`;
|
|
292171
|
+
failed++;
|
|
292172
|
+
send("step_result", { index: i, step: stepSummary, status: 0, passed: false, durationMs: 0, captured: [], injected: {}, responseBody: null, error: reason });
|
|
292173
|
+
send("aborted", { index: i, reason });
|
|
292174
|
+
res.end();
|
|
292175
|
+
return;
|
|
292176
|
+
}
|
|
292177
|
+
if (condKind === "legacy") {
|
|
292178
|
+
const guard = await (0, import_simulator4.evaluateLegacyCondition)(step.condition, (value) => resolveCondRef(value, i, step.name || ""));
|
|
292179
|
+
if (!guard.matched) {
|
|
292180
|
+
passed++;
|
|
292181
|
+
send("step_result", {
|
|
292182
|
+
index: i,
|
|
292183
|
+
step: stepSummary,
|
|
292184
|
+
status: 0,
|
|
292185
|
+
passed: true,
|
|
292186
|
+
skipped: true,
|
|
292187
|
+
durationMs: 0,
|
|
292188
|
+
captured: [],
|
|
292189
|
+
injected: {},
|
|
292190
|
+
responseBody: null,
|
|
292191
|
+
error: null,
|
|
292192
|
+
skipReason: `legacy guard not met (resolved "${guard.evaluated}")`
|
|
292193
|
+
});
|
|
292194
|
+
continue;
|
|
292195
|
+
}
|
|
292196
|
+
}
|
|
291847
292197
|
let result;
|
|
291848
292198
|
try {
|
|
291849
292199
|
result = await executeStep2(step, i);
|
|
@@ -291868,7 +292218,7 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
|
|
|
291868
292218
|
responseBody: result.responseBody,
|
|
291869
292219
|
error: result.error
|
|
291870
292220
|
});
|
|
291871
|
-
if (step.condition && !aborted) {
|
|
292221
|
+
if ((0, import_simulator4.isRulesCondition)(step.condition) && !aborted) {
|
|
291872
292222
|
const condResult = await evalCondition(step.condition, i, step.name || `${step.method} ${step.path}`);
|
|
291873
292223
|
send("condition_result", {
|
|
291874
292224
|
index: i,
|
|
@@ -292050,6 +292400,8 @@ var devCommand = new import_commander16.Command("dev").description("Start the Je
|
|
|
292050
292400
|
console.log(`
|
|
292051
292401
|
\u{1F680} Jetic Studio is running at ${url}
|
|
292052
292402
|
`);
|
|
292403
|
+
const noOpenEnv = process.env.JETIC_NO_OPEN;
|
|
292404
|
+
if (!options.open || noOpenEnv && noOpenEnv !== "0" && noOpenEnv.toLowerCase() !== "false") return;
|
|
292053
292405
|
const { exec } = require("child_process");
|
|
292054
292406
|
const start = process.platform == "darwin" ? "open" : process.platform == "win32" ? "start" : "xdg-open";
|
|
292055
292407
|
exec(`${start} ${url}`);
|