@overshift/sfs 1.1.0
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 +215 -0
- package/dist/cli/coverage-output.d.ts +3 -0
- package/dist/cli/coverage-output.d.ts.map +1 -0
- package/dist/cli/coverage-output.js +74 -0
- package/dist/cli/coverage-output.js.map +1 -0
- package/dist/cli/coverage-types.d.ts +24 -0
- package/dist/cli/coverage-types.d.ts.map +1 -0
- package/dist/cli/coverage-types.js +2 -0
- package/dist/cli/coverage-types.js.map +1 -0
- package/dist/cli/coverage.d.ts +3 -0
- package/dist/cli/coverage.d.ts.map +1 -0
- package/dist/cli/coverage.js +69 -0
- package/dist/cli/coverage.js.map +1 -0
- package/dist/cli/init-templates.d.ts +5 -0
- package/dist/cli/init-templates.d.ts.map +1 -0
- package/dist/cli/init-templates.js +108 -0
- package/dist/cli/init-templates.js.map +1 -0
- package/dist/cli/init.d.ts +7 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +170 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/run.d.ts +3 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +58 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/story-extractor.d.ts +7 -0
- package/dist/cli/story-extractor.d.ts.map +1 -0
- package/dist/cli/story-extractor.js +130 -0
- package/dist/cli/story-extractor.js.map +1 -0
- package/dist/cli/validate.d.ts +2 -0
- package/dist/cli/validate.d.ts.map +1 -0
- package/dist/cli/validate.js +39 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/executor/assertion-executor.d.ts +7 -0
- package/dist/executor/assertion-executor.d.ts.map +1 -0
- package/dist/executor/assertion-executor.js +206 -0
- package/dist/executor/assertion-executor.js.map +1 -0
- package/dist/executor/config-loader.d.ts +5 -0
- package/dist/executor/config-loader.d.ts.map +1 -0
- package/dist/executor/config-loader.js +48 -0
- package/dist/executor/config-loader.js.map +1 -0
- package/dist/executor/executor-types.d.ts +64 -0
- package/dist/executor/executor-types.d.ts.map +1 -0
- package/dist/executor/executor-types.js +6 -0
- package/dist/executor/executor-types.js.map +1 -0
- package/dist/executor/retry-handler.d.ts +7 -0
- package/dist/executor/retry-handler.d.ts.map +1 -0
- package/dist/executor/retry-handler.js +32 -0
- package/dist/executor/retry-handler.js.map +1 -0
- package/dist/executor/run-context.d.ts +27 -0
- package/dist/executor/run-context.d.ts.map +1 -0
- package/dist/executor/run-context.js +52 -0
- package/dist/executor/run-context.js.map +1 -0
- package/dist/executor/run-engine.d.ts +9 -0
- package/dist/executor/run-engine.d.ts.map +1 -0
- package/dist/executor/run-engine.js +140 -0
- package/dist/executor/run-engine.js.map +1 -0
- package/dist/executor/session-manager.d.ts +16 -0
- package/dist/executor/session-manager.d.ts.map +1 -0
- package/dist/executor/session-manager.js +103 -0
- package/dist/executor/session-manager.js.map +1 -0
- package/dist/executor/shell-executor.d.ts +12 -0
- package/dist/executor/shell-executor.d.ts.map +1 -0
- package/dist/executor/shell-executor.js +66 -0
- package/dist/executor/shell-executor.js.map +1 -0
- package/dist/executor/step-executor.d.ts +6 -0
- package/dist/executor/step-executor.d.ts.map +1 -0
- package/dist/executor/step-executor.js +73 -0
- package/dist/executor/step-executor.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/assertion-parser.d.ts +3 -0
- package/dist/parser/assertion-parser.d.ts.map +1 -0
- package/dist/parser/assertion-parser.js +92 -0
- package/dist/parser/assertion-parser.js.map +1 -0
- package/dist/parser/command-parser.d.ts +3 -0
- package/dist/parser/command-parser.d.ts.map +1 -0
- package/dist/parser/command-parser.js +252 -0
- package/dist/parser/command-parser.js.map +1 -0
- package/dist/parser/config-parser.d.ts +16 -0
- package/dist/parser/config-parser.d.ts.map +1 -0
- package/dist/parser/config-parser.js +217 -0
- package/dist/parser/config-parser.js.map +1 -0
- package/dist/parser/sfs-parser.d.ts +3 -0
- package/dist/parser/sfs-parser.d.ts.map +1 -0
- package/dist/parser/sfs-parser.js +184 -0
- package/dist/parser/sfs-parser.js.map +1 -0
- package/dist/parser/step-parser.d.ts +3 -0
- package/dist/parser/step-parser.d.ts.map +1 -0
- package/dist/parser/step-parser.js +72 -0
- package/dist/parser/step-parser.js.map +1 -0
- package/dist/parser/types.d.ts +115 -0
- package/dist/parser/types.d.ts.map +1 -0
- package/dist/parser/types.js +3 -0
- package/dist/parser/types.js.map +1 -0
- package/dist/utils/mcp-detector.d.ts +9 -0
- package/dist/utils/mcp-detector.d.ts.map +1 -0
- package/dist/utils/mcp-detector.js +62 -0
- package/dist/utils/mcp-detector.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { createConnection, createServer } from "net";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import { DEFAULT_RETRY_CONFIG } from "./executor-types.js";
|
|
5
|
+
import { createShellExecutor, substituteCommandVariables, } from "./shell-executor.js";
|
|
6
|
+
import { isRetryable, withRetry } from "./retry-handler.js";
|
|
7
|
+
export function createAssertionExecutor() {
|
|
8
|
+
const shell = createShellExecutor();
|
|
9
|
+
return {
|
|
10
|
+
async execute(assertion, variables = {}, retryConfig = DEFAULT_RETRY_CONFIG) {
|
|
11
|
+
const target = substituteCommandVariables(assertion.target, variables);
|
|
12
|
+
const value = assertion.value
|
|
13
|
+
? substituteCommandVariables(assertion.value, variables)
|
|
14
|
+
: undefined;
|
|
15
|
+
const shouldRetry = isRetryable(assertion.type);
|
|
16
|
+
try {
|
|
17
|
+
const { result, attempts } = await withRetry(() => runAssertion(assertion.type, target, value, shell), retryConfig, shouldRetry);
|
|
18
|
+
return { ...result, attempts };
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const error = err;
|
|
22
|
+
return {
|
|
23
|
+
type: assertion.type,
|
|
24
|
+
passed: false,
|
|
25
|
+
message: error.message ?? "Assertion failed",
|
|
26
|
+
attempts: error.attempts ?? 1,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function runAssertion(type, target, value, shell) {
|
|
33
|
+
switch (type) {
|
|
34
|
+
case "port_open":
|
|
35
|
+
return assertPortOpen(parseInt(target, 10));
|
|
36
|
+
case "port_free":
|
|
37
|
+
return assertPortFree(parseInt(target, 10));
|
|
38
|
+
case "file_exists":
|
|
39
|
+
return assertFileExists(target);
|
|
40
|
+
case "env_set":
|
|
41
|
+
return assertEnvSet(target);
|
|
42
|
+
case "env_check":
|
|
43
|
+
return assertEnvCheck();
|
|
44
|
+
case "url_responds":
|
|
45
|
+
return assertUrlResponds(target);
|
|
46
|
+
case "url_responds_with":
|
|
47
|
+
return assertUrlResponds(target, value ? parseInt(value, 10) : undefined);
|
|
48
|
+
case "shell_succeeds":
|
|
49
|
+
return assertShellSucceeds(target, shell);
|
|
50
|
+
case "docker_ready":
|
|
51
|
+
return assertDockerReady(shell);
|
|
52
|
+
default:
|
|
53
|
+
return {
|
|
54
|
+
type: type,
|
|
55
|
+
passed: false,
|
|
56
|
+
message: `Unknown assertion type: ${type}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function assertPortOpen(port) {
|
|
61
|
+
return new Promise((res) => {
|
|
62
|
+
const socket = createConnection({ port, host: "127.0.0.1" }, () => {
|
|
63
|
+
socket.destroy();
|
|
64
|
+
res({ type: "port_open", passed: true, message: `Port ${port} is open` });
|
|
65
|
+
});
|
|
66
|
+
socket.setTimeout(3000);
|
|
67
|
+
socket.on("timeout", () => {
|
|
68
|
+
socket.destroy();
|
|
69
|
+
res({
|
|
70
|
+
type: "port_open",
|
|
71
|
+
passed: false,
|
|
72
|
+
message: `Port ${port} connection timed out`,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
socket.on("error", () => {
|
|
76
|
+
socket.destroy();
|
|
77
|
+
res({
|
|
78
|
+
type: "port_open",
|
|
79
|
+
passed: false,
|
|
80
|
+
message: `Port ${port} is not open`,
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function assertPortFree(port) {
|
|
86
|
+
return new Promise((res) => {
|
|
87
|
+
const server = createServer();
|
|
88
|
+
server.once("error", () => {
|
|
89
|
+
res({
|
|
90
|
+
type: "port_free",
|
|
91
|
+
passed: false,
|
|
92
|
+
message: `Port ${port} is in use`,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
server.listen(port, "127.0.0.1", () => {
|
|
96
|
+
server.close(() => {
|
|
97
|
+
res({
|
|
98
|
+
type: "port_free",
|
|
99
|
+
passed: true,
|
|
100
|
+
message: `Port ${port} is free`,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function assertFileExists(path) {
|
|
107
|
+
const resolvedPath = resolve(path);
|
|
108
|
+
const exists = existsSync(resolvedPath);
|
|
109
|
+
return {
|
|
110
|
+
type: "file_exists",
|
|
111
|
+
passed: exists,
|
|
112
|
+
message: exists ? `File ${path} exists` : `File ${path} does not exist`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function assertEnvSet(varName) {
|
|
116
|
+
const value = process.env[varName];
|
|
117
|
+
const isSet = value !== undefined && value !== "";
|
|
118
|
+
return {
|
|
119
|
+
type: "env_set",
|
|
120
|
+
passed: isSet,
|
|
121
|
+
message: isSet ? `Env ${varName} is set` : `Env ${varName} is not set`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function assertEnvCheck() {
|
|
125
|
+
const requiredVars = process.env["SFS_REQUIRED_VARS"];
|
|
126
|
+
if (!requiredVars) {
|
|
127
|
+
return {
|
|
128
|
+
type: "env_check",
|
|
129
|
+
passed: true,
|
|
130
|
+
message: "No SFS_REQUIRED_VARS defined, env-check passed",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const vars = requiredVars
|
|
134
|
+
.split(",")
|
|
135
|
+
.map((v) => v.trim())
|
|
136
|
+
.filter(Boolean);
|
|
137
|
+
const missing = vars.filter((v) => !process.env[v]);
|
|
138
|
+
if (missing.length === 0) {
|
|
139
|
+
return {
|
|
140
|
+
type: "env_check",
|
|
141
|
+
passed: true,
|
|
142
|
+
message: `All required env vars set: ${vars.join(", ")}`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
type: "env_check",
|
|
147
|
+
passed: false,
|
|
148
|
+
message: `Missing required env vars: ${missing.join(", ")}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async function assertUrlResponds(url, expectedStatus) {
|
|
152
|
+
try {
|
|
153
|
+
const controller = new AbortController();
|
|
154
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
155
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
156
|
+
clearTimeout(timeout);
|
|
157
|
+
if (expectedStatus !== undefined) {
|
|
158
|
+
const passed = response.status === expectedStatus;
|
|
159
|
+
return {
|
|
160
|
+
type: expectedStatus ? "url_responds_with" : "url_responds",
|
|
161
|
+
passed,
|
|
162
|
+
message: passed
|
|
163
|
+
? `URL ${url} responded with ${response.status}`
|
|
164
|
+
: `URL ${url} responded with ${response.status}, expected ${expectedStatus}`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
type: "url_responds",
|
|
169
|
+
passed: response.ok,
|
|
170
|
+
message: response.ok
|
|
171
|
+
? `URL ${url} responds (${response.status})`
|
|
172
|
+
: `URL ${url} responded with error ${response.status}`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
177
|
+
return {
|
|
178
|
+
type: "url_responds",
|
|
179
|
+
passed: false,
|
|
180
|
+
message: message.includes("abort")
|
|
181
|
+
? `URL ${url} timed out`
|
|
182
|
+
: `URL ${url} failed: ${message}`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function assertShellSucceeds(command, shell) {
|
|
187
|
+
const result = shell.execute(command);
|
|
188
|
+
return {
|
|
189
|
+
type: "shell_succeeds",
|
|
190
|
+
passed: result.success,
|
|
191
|
+
message: result.success
|
|
192
|
+
? `Shell command succeeded: ${command}`
|
|
193
|
+
: `Shell command failed: ${command}`,
|
|
194
|
+
details: result.success ? result.stdout : result.stderr,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function assertDockerReady(shell) {
|
|
198
|
+
const result = shell.execute("docker info");
|
|
199
|
+
return {
|
|
200
|
+
type: "docker_ready",
|
|
201
|
+
passed: result.success,
|
|
202
|
+
message: result.success ? "Docker is ready" : "Docker is not ready",
|
|
203
|
+
details: result.success ? undefined : result.stderr,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=assertion-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertion-executor.js","sourceRoot":"","sources":["../../src/executor/assertion-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SfsConfig } from "../parser/types.js";
|
|
2
|
+
import type { RunResult, RunSummary } from "./executor-types.js";
|
|
3
|
+
export declare function loadConfig(envName?: string): Promise<SfsConfig>;
|
|
4
|
+
export declare function buildSummary(results: RunResult[], totalDuration: number): RunSummary;
|
|
5
|
+
//# sourceMappingURL=config-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/executor/config-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAYjE,wBAAsB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAiBrE;AAeD,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,EAAE,EACpB,aAAa,EAAE,MAAM,GACpB,UAAU,CASZ"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFile, access } from "fs/promises";
|
|
2
|
+
import { parseConfigFile, mergeConfigs } from "../parser/config-parser.js";
|
|
3
|
+
const DEFAULT_CONFIG = {
|
|
4
|
+
settings: {},
|
|
5
|
+
hints: [],
|
|
6
|
+
personas: [],
|
|
7
|
+
beforeSession: [],
|
|
8
|
+
afterSession: [],
|
|
9
|
+
beforeEach: [],
|
|
10
|
+
afterEach: [],
|
|
11
|
+
};
|
|
12
|
+
export async function loadConfig(envName) {
|
|
13
|
+
const baseConfig = await loadConfigFile("sfs.config.md");
|
|
14
|
+
if (!envName) {
|
|
15
|
+
return baseConfig ?? DEFAULT_CONFIG;
|
|
16
|
+
}
|
|
17
|
+
const envConfig = await loadConfigFile(`sfs.config.${envName}.md`);
|
|
18
|
+
if (!baseConfig && !envConfig) {
|
|
19
|
+
return DEFAULT_CONFIG;
|
|
20
|
+
}
|
|
21
|
+
if (!baseConfig)
|
|
22
|
+
return envConfig ?? DEFAULT_CONFIG;
|
|
23
|
+
if (!envConfig)
|
|
24
|
+
return baseConfig;
|
|
25
|
+
return mergeConfigs(baseConfig, envConfig);
|
|
26
|
+
}
|
|
27
|
+
async function loadConfigFile(filename) {
|
|
28
|
+
try {
|
|
29
|
+
await access(filename);
|
|
30
|
+
const content = await readFile(filename, "utf-8");
|
|
31
|
+
const result = parseConfigFile(content, filename);
|
|
32
|
+
return result.config;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function buildSummary(results, totalDuration) {
|
|
39
|
+
return {
|
|
40
|
+
totalFiles: results.length,
|
|
41
|
+
passed: results.filter((r) => r.success && !r.skipped).length,
|
|
42
|
+
failed: results.filter((r) => !r.success && !r.skipped).length,
|
|
43
|
+
skipped: results.filter((r) => r.skipped).length,
|
|
44
|
+
totalDuration,
|
|
45
|
+
results,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=config-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/executor/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAI3E,MAAM,cAAc,GAAc;IAChC,QAAQ,EAAE,EAAE;IACZ,KAAK,EAAE,EAAE;IACT,QAAQ,EAAE,EAAE;IACZ,aAAa,EAAE,EAAE;IACjB,YAAY,EAAE,EAAE;IAChB,UAAU,EAAE,EAAE;IACd,SAAS,EAAE,EAAE;CACd,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAgB;IAC/C,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,eAAe,CAAC,CAAC;IAEzD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,UAAU,IAAI,cAAc,CAAC;IACtC,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,cAAc,OAAO,KAAK,CAAC,CAAC;IAEnE,IAAI,CAAC,UAAU,IAAI,CAAC,SAAS,EAAE,CAAC;QAC9B,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,IAAI,cAAc,CAAC;IACpD,IAAI,CAAC,SAAS;QAAE,OAAO,UAAU,CAAC;IAElC,OAAO,YAAY,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAC7C,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,QAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAClD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,OAAoB,EACpB,aAAqB;IAErB,OAAO;QACL,UAAU,EAAE,OAAO,CAAC,MAAM;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM;QAC7D,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM;QAC9D,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM;QAChD,aAAa;QACb,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AssertionType } from "../parser/types.js";
|
|
2
|
+
export interface ExecutionError {
|
|
3
|
+
type: "assertion_failed" | "command_failed" | "timeout" | "parse_error";
|
|
4
|
+
message: string;
|
|
5
|
+
line?: number;
|
|
6
|
+
raw?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface StepResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
output?: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
duration: number;
|
|
13
|
+
}
|
|
14
|
+
export interface PhaseResult {
|
|
15
|
+
name: string;
|
|
16
|
+
success: boolean;
|
|
17
|
+
steps: StepResult[];
|
|
18
|
+
duration: number;
|
|
19
|
+
}
|
|
20
|
+
export interface RunResult {
|
|
21
|
+
file: string;
|
|
22
|
+
storyId: string;
|
|
23
|
+
success: boolean;
|
|
24
|
+
skipped: boolean;
|
|
25
|
+
skipReason?: string;
|
|
26
|
+
phases: PhaseResult[];
|
|
27
|
+
exitConditionMet: boolean;
|
|
28
|
+
totalDuration: number;
|
|
29
|
+
error?: ExecutionError;
|
|
30
|
+
}
|
|
31
|
+
export interface RunSummary {
|
|
32
|
+
totalFiles: number;
|
|
33
|
+
passed: number;
|
|
34
|
+
failed: number;
|
|
35
|
+
skipped: number;
|
|
36
|
+
totalDuration: number;
|
|
37
|
+
results: RunResult[];
|
|
38
|
+
}
|
|
39
|
+
export interface RetryConfig {
|
|
40
|
+
maxAttempts: number;
|
|
41
|
+
intervalMs: number;
|
|
42
|
+
}
|
|
43
|
+
export declare const DEFAULT_RETRY_CONFIG: RetryConfig;
|
|
44
|
+
export interface ShellResult {
|
|
45
|
+
success: boolean;
|
|
46
|
+
exitCode: number;
|
|
47
|
+
stdout: string;
|
|
48
|
+
stderr: string;
|
|
49
|
+
duration: number;
|
|
50
|
+
}
|
|
51
|
+
export interface AssertionResult {
|
|
52
|
+
type: AssertionType;
|
|
53
|
+
passed: boolean;
|
|
54
|
+
message: string;
|
|
55
|
+
details?: string;
|
|
56
|
+
attempts?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface HookResult {
|
|
59
|
+
hook: string;
|
|
60
|
+
success: boolean;
|
|
61
|
+
commandResults: StepResult[];
|
|
62
|
+
error?: string;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=executor-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor-types.d.ts","sourceRoot":"","sources":["../../src/executor/executor-types.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,GAAG,gBAAgB,GAAG,SAAS,GAAG,aAAa,CAAC;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,oBAAoB,EAAE,WAGlC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,UAAU,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor-types.js","sourceRoot":"","sources":["../../src/executor/executor-types.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAmD3C,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAC/C,WAAW,EAAE,CAAC;IACd,UAAU,EAAE,IAAI;CACjB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RetryConfig } from "./executor-types.js";
|
|
2
|
+
export declare function isRetryable(assertionType: string): boolean;
|
|
3
|
+
export declare function withRetry<T>(fn: () => Promise<T>, config?: RetryConfig, shouldRetry?: boolean): Promise<{
|
|
4
|
+
result: T;
|
|
5
|
+
attempts: number;
|
|
6
|
+
}>;
|
|
7
|
+
//# sourceMappingURL=retry-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-handler.d.ts","sourceRoot":"","sources":["../../src/executor/retry-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAYvD,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,MAAM,GAAE,WAAkC,EAC1C,WAAW,GAAE,OAAc,GAC1B,OAAO,CAAC;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAsB1C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DEFAULT_RETRY_CONFIG } from "./executor-types.js";
|
|
2
|
+
const RETRYABLE_TYPES = new Set([
|
|
3
|
+
"port_open",
|
|
4
|
+
"port_free",
|
|
5
|
+
"url_responds",
|
|
6
|
+
"url_responds_with",
|
|
7
|
+
"shell_succeeds",
|
|
8
|
+
"docker_ready",
|
|
9
|
+
]);
|
|
10
|
+
export function isRetryable(assertionType) {
|
|
11
|
+
return RETRYABLE_TYPES.has(assertionType);
|
|
12
|
+
}
|
|
13
|
+
export async function withRetry(fn, config = DEFAULT_RETRY_CONFIG, shouldRetry = true) {
|
|
14
|
+
const maxAttempts = shouldRetry ? config.maxAttempts : 1;
|
|
15
|
+
let lastError;
|
|
16
|
+
let attempts = 0;
|
|
17
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
18
|
+
attempts++;
|
|
19
|
+
try {
|
|
20
|
+
const result = await fn();
|
|
21
|
+
return { result, attempts };
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
lastError = err;
|
|
25
|
+
if (i < maxAttempts - 1) {
|
|
26
|
+
await new Promise((resolve) => setTimeout(resolve, config.intervalMs));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw Object.assign(lastError instanceof Error ? lastError : new Error(String(lastError)), { attempts });
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=retry-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-handler.js","sourceRoot":"","sources":["../../src/executor/retry-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,eAAe,GAAG,IAAI,GAAG,CAAS;IACtC,WAAW;IACX,WAAW;IACX,cAAc;IACd,mBAAmB;IACnB,gBAAgB;IAChB,cAAc;CACf,CAAC,CAAC;AAEH,MAAM,UAAU,WAAW,CAAC,aAAqB;IAC/C,OAAO,eAAe,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAoB,EACpB,SAAsB,oBAAoB,EAC1C,cAAuB,IAAI;IAE3B,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,IAAI,SAAkB,CAAC;IACvB,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,QAAQ,EAAE,CAAC;QACX,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC;YAC1B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,SAAS,GAAG,GAAG,CAAC;YAChB,IAAI,CAAC,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;gBACxB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,MAAM,CAAC,MAAM,CACjB,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,EACrE,EAAE,QAAQ,EAAE,CACb,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SfsConfig } from "../parser/types.js";
|
|
2
|
+
import type { RetryConfig } from "./executor-types.js";
|
|
3
|
+
export interface RunOptions {
|
|
4
|
+
env?: string;
|
|
5
|
+
verbose?: boolean;
|
|
6
|
+
headed?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface RunContext {
|
|
9
|
+
config: SfsConfig;
|
|
10
|
+
options: RunOptions;
|
|
11
|
+
currentFile: string;
|
|
12
|
+
variables: Record<string, string>;
|
|
13
|
+
captures: Record<string, string>;
|
|
14
|
+
retryConfig: RetryConfig;
|
|
15
|
+
runDir: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function createRunContext(options: RunOptions, config: SfsConfig, retryConfig?: RetryConfig): RunContext;
|
|
18
|
+
/**
|
|
19
|
+
* Substitute variables in a template string.
|
|
20
|
+
* Supports {{ENV_VAR}}, {{RUN_DIR}}, {{TIMESTAMP}}, $variable.
|
|
21
|
+
* Undefined variables substitute to empty string.
|
|
22
|
+
*/
|
|
23
|
+
export declare function substituteVariables(template: string, context: RunContext): string;
|
|
24
|
+
export declare function setContextVariable(context: RunContext, name: string, value: string): void;
|
|
25
|
+
export declare function setCapturedVariable(context: RunContext, name: string, value: string): void;
|
|
26
|
+
export declare function getContextVariables(context: RunContext): Record<string, string>;
|
|
27
|
+
//# sourceMappingURL=run-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-context.d.ts","sourceRoot":"","sources":["../../src/executor/run-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvD,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,UAAU,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,SAAS,EACjB,WAAW,GAAE,WAAkC,GAC9C,UAAU,CAWZ;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,UAAU,GAClB,MAAM,CAmBR;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,IAAI,CAEN;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,IAAI,CAEN;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,UAAU,GAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAExB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DEFAULT_RETRY_CONFIG } from "./executor-types.js";
|
|
2
|
+
export function createRunContext(options, config, retryConfig = DEFAULT_RETRY_CONFIG) {
|
|
3
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
4
|
+
return {
|
|
5
|
+
config,
|
|
6
|
+
options,
|
|
7
|
+
currentFile: "",
|
|
8
|
+
variables: {},
|
|
9
|
+
captures: {},
|
|
10
|
+
retryConfig,
|
|
11
|
+
runDir: `runs/${timestamp}`,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Substitute variables in a template string.
|
|
16
|
+
* Supports {{ENV_VAR}}, {{RUN_DIR}}, {{TIMESTAMP}}, $variable.
|
|
17
|
+
* Undefined variables substitute to empty string.
|
|
18
|
+
*/
|
|
19
|
+
export function substituteVariables(template, context) {
|
|
20
|
+
let result = template;
|
|
21
|
+
result = result.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
|
|
22
|
+
if (name === "RUN_DIR")
|
|
23
|
+
return context.runDir;
|
|
24
|
+
if (name === "TIMESTAMP")
|
|
25
|
+
return new Date().toISOString();
|
|
26
|
+
if (name === "SFS_NAME")
|
|
27
|
+
return context.currentFile;
|
|
28
|
+
if (name in context.variables)
|
|
29
|
+
return context.variables[name] ?? "";
|
|
30
|
+
if (name in context.captures)
|
|
31
|
+
return context.captures[name] ?? "";
|
|
32
|
+
return process.env[name] ?? "";
|
|
33
|
+
});
|
|
34
|
+
result = result.replace(/\$(\w+)/g, (_match, name) => {
|
|
35
|
+
if (name in context.captures)
|
|
36
|
+
return context.captures[name] ?? "";
|
|
37
|
+
if (name in context.variables)
|
|
38
|
+
return context.variables[name] ?? "";
|
|
39
|
+
return "";
|
|
40
|
+
});
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
export function setContextVariable(context, name, value) {
|
|
44
|
+
context.variables[name] = value;
|
|
45
|
+
}
|
|
46
|
+
export function setCapturedVariable(context, name, value) {
|
|
47
|
+
context.captures[name] = value;
|
|
48
|
+
}
|
|
49
|
+
export function getContextVariables(context) {
|
|
50
|
+
return { ...context.variables, ...context.captures };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=run-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-context.js","sourceRoot":"","sources":["../../src/executor/run-context.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAkB3D,MAAM,UAAU,gBAAgB,CAC9B,OAAmB,EACnB,MAAiB,EACjB,cAA2B,oBAAoB;IAE/C,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjE,OAAO;QACL,MAAM;QACN,OAAO;QACP,WAAW,EAAE,EAAE;QACf,SAAS,EAAE,EAAE;QACb,QAAQ,EAAE,EAAE;QACZ,WAAW;QACX,MAAM,EAAE,QAAQ,SAAS,EAAE;KAC5B,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,OAAmB;IAEnB,IAAI,MAAM,GAAG,QAAQ,CAAC;IAEtB,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE;QACjE,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC;QAC9C,IAAI,IAAI,KAAK,WAAW;YAAE,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC1D,IAAI,IAAI,KAAK,UAAU;YAAE,OAAO,OAAO,CAAC,WAAW,CAAC;QACpD,IAAI,IAAI,IAAI,OAAO,CAAC,SAAS;YAAE,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACpE,IAAI,IAAI,IAAI,OAAO,CAAC,QAAQ;YAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAClE,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE;QAC3D,IAAI,IAAI,IAAI,OAAO,CAAC,QAAQ;YAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAClE,IAAI,IAAI,IAAI,OAAO,CAAC,SAAS;YAAE,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACpE,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,OAAmB,EACnB,IAAY,EACZ,KAAa;IAEb,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,OAAmB,EACnB,IAAY,EACZ,KAAa;IAEb,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,OAAmB;IAEnB,OAAO,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RunResult, RunSummary } from "./executor-types.js";
|
|
2
|
+
import type { RunOptions } from "./run-context.js";
|
|
3
|
+
interface RunEngine {
|
|
4
|
+
run(files: string[], options: RunOptions): Promise<RunSummary>;
|
|
5
|
+
runFile(filePath: string, options: RunOptions): Promise<RunResult>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createRunEngine(): RunEngine;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=run-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-engine.d.ts","sourceRoot":"","sources":["../../src/executor/run-engine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAKjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,UAAU,SAAS;IACjB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACpE;AAED,wBAAgB,eAAe,IAAI,SAAS,CA2J3C"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { basename } from "path";
|
|
3
|
+
import { parseSfsFile } from "../parser/sfs-parser.js";
|
|
4
|
+
import { DEFAULT_RETRY_CONFIG } from "./executor-types.js";
|
|
5
|
+
import { createShellExecutor } from "./shell-executor.js";
|
|
6
|
+
import { createAssertionExecutor } from "./assertion-executor.js";
|
|
7
|
+
import { createRunContext } from "./run-context.js";
|
|
8
|
+
import { createSessionManager } from "./session-manager.js";
|
|
9
|
+
import { executePhases } from "./step-executor.js";
|
|
10
|
+
import { loadConfig, buildSummary } from "./config-loader.js";
|
|
11
|
+
export function createRunEngine() {
|
|
12
|
+
return {
|
|
13
|
+
async run(files, options) {
|
|
14
|
+
const start = Date.now();
|
|
15
|
+
const config = await loadConfig(options.env);
|
|
16
|
+
const context = createRunContext(options, config, DEFAULT_RETRY_CONFIG);
|
|
17
|
+
const shell = createShellExecutor();
|
|
18
|
+
const assertionExecutor = createAssertionExecutor();
|
|
19
|
+
const session = createSessionManager(config, context, shell, assertionExecutor);
|
|
20
|
+
const results = [];
|
|
21
|
+
// Run before session
|
|
22
|
+
const beforeResult = await session.runBeforeSession();
|
|
23
|
+
if (!beforeResult.success) {
|
|
24
|
+
// All files are skipped when before-session fails
|
|
25
|
+
for (const file of files) {
|
|
26
|
+
results.push({
|
|
27
|
+
file,
|
|
28
|
+
storyId: "",
|
|
29
|
+
success: false,
|
|
30
|
+
skipped: true,
|
|
31
|
+
skipReason: `Before session failed: ${beforeResult.error}`,
|
|
32
|
+
phases: [],
|
|
33
|
+
exitConditionMet: false,
|
|
34
|
+
totalDuration: 0,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return buildSummary(results, Date.now() - start);
|
|
38
|
+
}
|
|
39
|
+
// Run each file
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
const fileResult = await this.runFile(file, options);
|
|
42
|
+
results.push(fileResult);
|
|
43
|
+
}
|
|
44
|
+
// Run after session (always)
|
|
45
|
+
await session.runAfterSession();
|
|
46
|
+
return buildSummary(results, Date.now() - start);
|
|
47
|
+
},
|
|
48
|
+
async runFile(filePath, options) {
|
|
49
|
+
const start = Date.now();
|
|
50
|
+
const storyId = basename(filePath, ".sfs");
|
|
51
|
+
try {
|
|
52
|
+
const content = await readFile(filePath, "utf-8");
|
|
53
|
+
const parseResult = parseSfsFile(content, filePath);
|
|
54
|
+
if (!parseResult.ast || parseResult.errors.length > 0) {
|
|
55
|
+
return {
|
|
56
|
+
file: filePath,
|
|
57
|
+
storyId,
|
|
58
|
+
success: false,
|
|
59
|
+
skipped: false,
|
|
60
|
+
phases: [],
|
|
61
|
+
exitConditionMet: false,
|
|
62
|
+
totalDuration: Date.now() - start,
|
|
63
|
+
error: {
|
|
64
|
+
type: "parse_error",
|
|
65
|
+
message: parseResult.errors.map((e) => e.message).join("; "),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const ast = parseResult.ast;
|
|
70
|
+
const config = await loadConfig(options.env);
|
|
71
|
+
const context = createRunContext(options, config, DEFAULT_RETRY_CONFIG);
|
|
72
|
+
context.currentFile = storyId;
|
|
73
|
+
const shell = createShellExecutor();
|
|
74
|
+
const assertionExecutor = createAssertionExecutor();
|
|
75
|
+
const session = createSessionManager(config, context, shell, assertionExecutor);
|
|
76
|
+
// Run before-each
|
|
77
|
+
const beforeEachResult = await session.runBeforeEach(storyId);
|
|
78
|
+
if (!beforeEachResult.success) {
|
|
79
|
+
await session.runAfterEach(storyId, false);
|
|
80
|
+
return {
|
|
81
|
+
file: filePath,
|
|
82
|
+
storyId,
|
|
83
|
+
success: false,
|
|
84
|
+
skipped: true,
|
|
85
|
+
skipReason: `Before each failed: ${beforeEachResult.error}`,
|
|
86
|
+
phases: [],
|
|
87
|
+
exitConditionMet: false,
|
|
88
|
+
totalDuration: Date.now() - start,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Run BEFORE THIS
|
|
92
|
+
const beforeThisResult = await session.runBeforeThis(ast.prerequisites.assertions, ast.prerequisites.beforeThis);
|
|
93
|
+
if (!beforeThisResult.success) {
|
|
94
|
+
await session.runAfterThis(ast.prerequisites.afterThis, false);
|
|
95
|
+
await session.runAfterEach(storyId, false);
|
|
96
|
+
return {
|
|
97
|
+
file: filePath,
|
|
98
|
+
storyId,
|
|
99
|
+
success: false,
|
|
100
|
+
skipped: true,
|
|
101
|
+
skipReason: `Before this failed: ${beforeThisResult.error}`,
|
|
102
|
+
phases: [],
|
|
103
|
+
exitConditionMet: false,
|
|
104
|
+
totalDuration: Date.now() - start,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// Execute phases
|
|
108
|
+
const phaseResults = executePhases(ast.phases, shell, context);
|
|
109
|
+
const allPassed = phaseResults.every((p) => p.success);
|
|
110
|
+
// Run AFTER THIS (always)
|
|
111
|
+
await session.runAfterThis(ast.prerequisites.afterThis, allPassed);
|
|
112
|
+
// Run after-each (always)
|
|
113
|
+
await session.runAfterEach(storyId, allPassed);
|
|
114
|
+
return {
|
|
115
|
+
file: filePath,
|
|
116
|
+
storyId,
|
|
117
|
+
success: allPassed,
|
|
118
|
+
skipped: false,
|
|
119
|
+
phases: phaseResults,
|
|
120
|
+
exitConditionMet: allPassed,
|
|
121
|
+
totalDuration: Date.now() - start,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
126
|
+
return {
|
|
127
|
+
file: filePath,
|
|
128
|
+
storyId,
|
|
129
|
+
success: false,
|
|
130
|
+
skipped: false,
|
|
131
|
+
phases: [],
|
|
132
|
+
exitConditionMet: false,
|
|
133
|
+
totalDuration: Date.now() - start,
|
|
134
|
+
error: { type: "command_failed", message },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=run-engine.js.map
|