@autometa/cli 1.0.0-rc.2
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/bin.cjs +2882 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +2878 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/run.d.ts +37 -0
- package/dist/compiler/module-compiler.d.ts +12 -0
- package/dist/index.cjs +2872 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +91 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2867 -0
- package/dist/index.js.map +1 -0
- package/dist/loaders/config.d.ts +16 -0
- package/dist/loaders/module-loader.d.ts +6 -0
- package/dist/orchestrator/index.d.ts +35 -0
- package/dist/runtime/cli-runtime.d.ts +17 -0
- package/dist/runtime/types.d.ts +24 -0
- package/dist/utils/cache-dir.d.ts +13 -0
- package/dist/utils/formatter.d.ts +16 -0
- package/dist/utils/glob.d.ts +1 -0
- package/dist/utils/handover.d.ts +11 -0
- package/dist/utils/logging/hierarchical-log.d.ts +25 -0
- package/dist/utils/reporter.d.ts +88 -0
- package/dist/utils/reporting/gherkin-context-printer.d.ts +19 -0
- package/dist/utils/reporting/location.d.ts +3 -0
- package/dist/utils/reporting/scenario-error-renderer.d.ts +26 -0
- package/dist/utils/reporting/stack-utils.d.ts +8 -0
- package/dist/utils/reporting/status-formatters.d.ts +6 -0
- package/dist/utils/reporting/step-summary.d.ts +5 -0
- package/package.json +72 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2867 @@
|
|
|
1
|
+
import { builtinModules, createRequire } from 'node:module';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import fs, { promises, constants, existsSync } from 'node:fs';
|
|
4
|
+
import path2, { resolve, extname, relative, join, isAbsolute } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { HTTP, createLoggingPlugin } from '@autometa/http';
|
|
7
|
+
import { CucumberRunner, STEPS_ENVIRONMENT_META } from '@autometa/runner';
|
|
8
|
+
import { parseGherkin } from '@autometa/gherkin';
|
|
9
|
+
import { rm, mkdir, writeFile, access } from 'node:fs/promises';
|
|
10
|
+
import { build } from 'esbuild';
|
|
11
|
+
import 'source-map-support/register';
|
|
12
|
+
import pc5 from 'picocolors';
|
|
13
|
+
import { getGherkinErrorContext } from '@autometa/errors';
|
|
14
|
+
import { codeFrameColumns } from '@babel/code-frame';
|
|
15
|
+
import fg from 'fast-glob';
|
|
16
|
+
import { Config } from '@autometa/config';
|
|
17
|
+
import { createHash as createHash$1 } from 'crypto';
|
|
18
|
+
import { promises as promises$1 } from 'fs';
|
|
19
|
+
import { builtinModules as builtinModules$1 } from 'module';
|
|
20
|
+
import { resolve as resolve$1, extname as extname$1, isAbsolute as isAbsolute$1, dirname, join as join$1 } from 'path';
|
|
21
|
+
import { pathToFileURL as pathToFileURL$1 } from 'url';
|
|
22
|
+
import { createHash } from 'node:crypto';
|
|
23
|
+
import { homedir } from 'node:os';
|
|
24
|
+
import { spawn } from 'node:child_process';
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var DEFAULT_FORMAT = "cjs";
|
|
28
|
+
var DEFAULT_TARGET = "node20";
|
|
29
|
+
var DEFAULT_SOURCEMAP = "inline";
|
|
30
|
+
var DEFAULT_EXTERNALS = [
|
|
31
|
+
"p-limit",
|
|
32
|
+
"@autometa/*",
|
|
33
|
+
"@cucumber/*"
|
|
34
|
+
];
|
|
35
|
+
var BUILTIN_EXTERNALS = /* @__PURE__ */ new Set([
|
|
36
|
+
...builtinModules,
|
|
37
|
+
...builtinModules.map((moduleId) => `node:${moduleId}`)
|
|
38
|
+
]);
|
|
39
|
+
async function compileModules(orderedEntries, options) {
|
|
40
|
+
const format = options.builder?.format ?? DEFAULT_FORMAT;
|
|
41
|
+
const target = options.builder?.target ?? DEFAULT_TARGET;
|
|
42
|
+
const esbuildTarget = Array.isArray(target) ? [...target] : target;
|
|
43
|
+
const sourcemap = options.builder?.sourcemap ?? DEFAULT_SOURCEMAP;
|
|
44
|
+
const tsconfig = options.builder?.tsconfig ? resolve(options.cwd, options.builder.tsconfig) : void 0;
|
|
45
|
+
const userExternal = options.builder?.external ?? [];
|
|
46
|
+
const external = Array.from(
|
|
47
|
+
/* @__PURE__ */ new Set([...BUILTIN_EXTERNALS, ...DEFAULT_EXTERNALS, ...userExternal])
|
|
48
|
+
);
|
|
49
|
+
const baseOutDir = resolveOutputBase(options);
|
|
50
|
+
const outDir = join(baseOutDir, format);
|
|
51
|
+
const entryFile = await createAggregateEntryFile(orderedEntries, options);
|
|
52
|
+
await ensureOutputDirectory(outDir, format);
|
|
53
|
+
const resolvedEntries = orderedEntries.map(
|
|
54
|
+
(entry) => isAbsolute(entry) ? entry : resolve(options.cwd, entry)
|
|
55
|
+
);
|
|
56
|
+
const hookContext = {
|
|
57
|
+
cwd: options.cwd,
|
|
58
|
+
cacheDir: options.cacheDir,
|
|
59
|
+
outDir,
|
|
60
|
+
entries: resolvedEntries,
|
|
61
|
+
format,
|
|
62
|
+
...target !== void 0 ? { target } : {},
|
|
63
|
+
...sourcemap !== void 0 ? { sourcemap } : {},
|
|
64
|
+
...tsconfig ? { tsconfig } : {},
|
|
65
|
+
...external.length > 0 ? { external } : {}
|
|
66
|
+
};
|
|
67
|
+
await runHooks(options.builder?.hooks?.before, hookContext);
|
|
68
|
+
if (resolvedEntries.length > 0) {
|
|
69
|
+
const entryName = "__autometa_entry__";
|
|
70
|
+
const bundleFileName = format === "esm" ? "__modules__.mjs" : "__modules__.cjs";
|
|
71
|
+
const outfile = join(outDir, bundleFileName);
|
|
72
|
+
const buildOptions = {
|
|
73
|
+
absWorkingDir: options.cwd,
|
|
74
|
+
entryPoints: {
|
|
75
|
+
[entryName]: entryFile
|
|
76
|
+
},
|
|
77
|
+
outfile,
|
|
78
|
+
bundle: true,
|
|
79
|
+
format,
|
|
80
|
+
platform: "node",
|
|
81
|
+
target: esbuildTarget,
|
|
82
|
+
sourcemap,
|
|
83
|
+
splitting: false,
|
|
84
|
+
write: true,
|
|
85
|
+
external,
|
|
86
|
+
logLevel: "silent",
|
|
87
|
+
treeShaking: false,
|
|
88
|
+
...tsconfig ? { tsconfig } : {}
|
|
89
|
+
};
|
|
90
|
+
await build(buildOptions);
|
|
91
|
+
await runHooks(options.builder?.hooks?.after, hookContext);
|
|
92
|
+
return {
|
|
93
|
+
bundlePath: outfile,
|
|
94
|
+
outDir,
|
|
95
|
+
format
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
await runHooks(options.builder?.hooks?.after, hookContext);
|
|
99
|
+
return {
|
|
100
|
+
bundlePath: entryFile,
|
|
101
|
+
format
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function resolveOutputBase(options) {
|
|
105
|
+
if (!options.builder?.outDir) {
|
|
106
|
+
return join(options.cacheDir, "modules");
|
|
107
|
+
}
|
|
108
|
+
const configured = options.builder.outDir;
|
|
109
|
+
return isAbsolute(configured) ? configured : resolve(options.cwd, configured);
|
|
110
|
+
}
|
|
111
|
+
async function ensureOutputDirectory(outDir, format) {
|
|
112
|
+
await rm(outDir, { recursive: true, force: true });
|
|
113
|
+
await mkdir(outDir, { recursive: true });
|
|
114
|
+
const packageJsonPath = join(outDir, "package.json");
|
|
115
|
+
const typeField = format === "esm" ? "module" : "commonjs";
|
|
116
|
+
await writeFile(packageJsonPath, JSON.stringify({ type: typeField }, null, 2), "utf8");
|
|
117
|
+
}
|
|
118
|
+
async function createAggregateEntryFile(entries, options) {
|
|
119
|
+
if (entries.length === 0) {
|
|
120
|
+
const stubPath = join(options.cacheDir, "modules", "__noop__.ts");
|
|
121
|
+
await mkdir(join(options.cacheDir, "modules"), { recursive: true });
|
|
122
|
+
await writeFile(stubPath, "export {};", "utf8");
|
|
123
|
+
return stubPath;
|
|
124
|
+
}
|
|
125
|
+
const modulesDir = join(options.cacheDir, "modules");
|
|
126
|
+
await mkdir(modulesDir, { recursive: true });
|
|
127
|
+
const aggregatePath = join(modulesDir, "__entry__.ts");
|
|
128
|
+
const uniqueEntries = Array.from(new Set(entries.map(
|
|
129
|
+
(entry) => isAbsolute(entry) ? entry : resolve(options.cwd, entry)
|
|
130
|
+
)));
|
|
131
|
+
const moduleIdentifiers = uniqueEntries.map((_, index) => `module_${index}`);
|
|
132
|
+
const imports = uniqueEntries.map((entry, index) => `import * as ${moduleIdentifiers[index]} from ${JSON.stringify(entry)};`).join("\n");
|
|
133
|
+
const stepsChecks = moduleIdentifiers.map((id) => {
|
|
134
|
+
return ` const val_${id} = ${id}['stepsEnvironment'];
|
|
135
|
+
if (val_${id}) return val_${id};`;
|
|
136
|
+
}).join("\n");
|
|
137
|
+
const contents = `${imports}
|
|
138
|
+
|
|
139
|
+
function __findStepsEnvironment() {
|
|
140
|
+
${stepsChecks}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const stepsEnvironment = __findStepsEnvironment();
|
|
145
|
+
export const modules = [${moduleIdentifiers.join(", ")}];
|
|
146
|
+
export default modules;
|
|
147
|
+
`;
|
|
148
|
+
await writeFile(aggregatePath, contents, "utf8");
|
|
149
|
+
return aggregatePath;
|
|
150
|
+
}
|
|
151
|
+
async function runHooks(hooks, context) {
|
|
152
|
+
if (!hooks || hooks.length === 0) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
for (const hook of hooks) {
|
|
156
|
+
await hook(context);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function formatSourceLocation(location) {
|
|
160
|
+
return `${relativePath(location.filePath)}:${location.start.line}:${location.start.column}`;
|
|
161
|
+
}
|
|
162
|
+
function relativePath(filePath) {
|
|
163
|
+
const cwd = process.cwd();
|
|
164
|
+
const relative2 = path2.relative(cwd, filePath);
|
|
165
|
+
return relative2 || filePath;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/utils/reporting/step-summary.ts
|
|
169
|
+
function describeStepSummary(step) {
|
|
170
|
+
const keyword = step.keyword?.trim();
|
|
171
|
+
const text = step.text?.trim();
|
|
172
|
+
const descriptionParts = [keyword, text].filter((value) => Boolean(value && value.length));
|
|
173
|
+
const description = descriptionParts.length ? descriptionParts.join(" ") : "Step";
|
|
174
|
+
const location = step.location ? pc5.dim(` (${formatSourceLocation(step.location)})`) : void 0;
|
|
175
|
+
return {
|
|
176
|
+
description,
|
|
177
|
+
...location ? { location } : {}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function getScenarioStatusIcon(status) {
|
|
181
|
+
switch (status) {
|
|
182
|
+
case "passed":
|
|
183
|
+
return pc5.green("\u2713");
|
|
184
|
+
case "failed":
|
|
185
|
+
return pc5.red("\u2717");
|
|
186
|
+
case "skipped":
|
|
187
|
+
return pc5.yellow("\u25CB");
|
|
188
|
+
case "pending":
|
|
189
|
+
return pc5.cyan("\u25CC");
|
|
190
|
+
default:
|
|
191
|
+
return pc5.dim("?");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function colorizeScenarioStatus(text, status) {
|
|
195
|
+
switch (status) {
|
|
196
|
+
case "passed":
|
|
197
|
+
return pc5.green(text);
|
|
198
|
+
case "failed":
|
|
199
|
+
return pc5.red(text);
|
|
200
|
+
case "skipped":
|
|
201
|
+
return pc5.yellow(text);
|
|
202
|
+
case "pending":
|
|
203
|
+
return pc5.cyan(text);
|
|
204
|
+
default:
|
|
205
|
+
return text;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function getStepStatusIcon(status) {
|
|
209
|
+
switch (status) {
|
|
210
|
+
case "passed":
|
|
211
|
+
return pc5.green("\u2713");
|
|
212
|
+
case "failed":
|
|
213
|
+
return pc5.red("\u2717");
|
|
214
|
+
case "skipped":
|
|
215
|
+
default:
|
|
216
|
+
return pc5.yellow("\u25CB");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function colorizeStepDescription(description, status) {
|
|
220
|
+
switch (status) {
|
|
221
|
+
case "passed":
|
|
222
|
+
return pc5.green(description);
|
|
223
|
+
case "failed":
|
|
224
|
+
return pc5.red(description);
|
|
225
|
+
case "skipped":
|
|
226
|
+
return pc5.yellow(description);
|
|
227
|
+
default:
|
|
228
|
+
return description;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/utils/logging/hierarchical-log.ts
|
|
233
|
+
var DEFAULT_INDENT = " ";
|
|
234
|
+
var BufferedHierarchicalLog = class {
|
|
235
|
+
constructor(sink = console.log, options = {}) {
|
|
236
|
+
this.sink = sink;
|
|
237
|
+
this.entries = [];
|
|
238
|
+
this.indent = options.indent ?? DEFAULT_INDENT;
|
|
239
|
+
}
|
|
240
|
+
write(line, depth = 0) {
|
|
241
|
+
const normalizedDepth = depth >= 0 ? depth : 0;
|
|
242
|
+
this.entries.push({ line, depth: normalizedDepth });
|
|
243
|
+
}
|
|
244
|
+
flush() {
|
|
245
|
+
if (this.entries.length === 0) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
for (const entry of this.entries) {
|
|
249
|
+
if (entry.line.length === 0) {
|
|
250
|
+
this.sink("");
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const indent = this.indent.repeat(entry.depth);
|
|
254
|
+
this.sink(`${indent}${entry.line}`);
|
|
255
|
+
}
|
|
256
|
+
this.entries.length = 0;
|
|
257
|
+
}
|
|
258
|
+
scoped(offset) {
|
|
259
|
+
const base = offset >= 0 ? offset : 0;
|
|
260
|
+
return new BufferedScopedHierarchicalLog(this, base);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var BufferedScopedHierarchicalLog = class _BufferedScopedHierarchicalLog {
|
|
264
|
+
constructor(root, base) {
|
|
265
|
+
this.root = root;
|
|
266
|
+
this.base = base;
|
|
267
|
+
}
|
|
268
|
+
write(line, depth = 0) {
|
|
269
|
+
const normalizedDepth = depth >= 0 ? depth : 0;
|
|
270
|
+
this.root.write(line, this.base + normalizedDepth);
|
|
271
|
+
}
|
|
272
|
+
flush() {
|
|
273
|
+
this.root.flush();
|
|
274
|
+
}
|
|
275
|
+
scoped(offset) {
|
|
276
|
+
const nextBase = this.base + (offset >= 0 ? offset : 0);
|
|
277
|
+
return new _BufferedScopedHierarchicalLog(this.root, nextBase);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
var ImmediateHierarchicalLog = class {
|
|
281
|
+
constructor(sink = console.log, options = {}) {
|
|
282
|
+
this.sink = sink;
|
|
283
|
+
this.indent = options.indent ?? DEFAULT_INDENT;
|
|
284
|
+
}
|
|
285
|
+
write(line, depth = 0) {
|
|
286
|
+
const normalizedDepth = depth >= 0 ? depth : 0;
|
|
287
|
+
if (line.length === 0) {
|
|
288
|
+
this.sink("");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const indent = this.indent.repeat(normalizedDepth);
|
|
292
|
+
this.sink(`${indent}${line}`);
|
|
293
|
+
}
|
|
294
|
+
flush() {
|
|
295
|
+
}
|
|
296
|
+
scoped(offset) {
|
|
297
|
+
const base = offset >= 0 ? offset : 0;
|
|
298
|
+
return new ImmediateScopedHierarchicalLog(this, base);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
var ImmediateScopedHierarchicalLog = class _ImmediateScopedHierarchicalLog {
|
|
302
|
+
constructor(root, base) {
|
|
303
|
+
this.root = root;
|
|
304
|
+
this.base = base;
|
|
305
|
+
}
|
|
306
|
+
write(line, depth = 0) {
|
|
307
|
+
const normalizedDepth = depth >= 0 ? depth : 0;
|
|
308
|
+
this.root.write(line, this.base + normalizedDepth);
|
|
309
|
+
}
|
|
310
|
+
flush() {
|
|
311
|
+
this.root.flush();
|
|
312
|
+
}
|
|
313
|
+
scoped(offset) {
|
|
314
|
+
const nextBase = this.base + (offset >= 0 ? offset : 0);
|
|
315
|
+
return new _ImmediateScopedHierarchicalLog(this.root, nextBase);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// src/utils/reporting/scenario-error-renderer.ts
|
|
320
|
+
var BaselineErrorRenderer = class {
|
|
321
|
+
constructor(contextPrinter, log = new ImmediateHierarchicalLog()) {
|
|
322
|
+
this.contextPrinter = contextPrinter;
|
|
323
|
+
this.log = log;
|
|
324
|
+
}
|
|
325
|
+
print(options) {
|
|
326
|
+
const {
|
|
327
|
+
context,
|
|
328
|
+
depth,
|
|
329
|
+
messageLines,
|
|
330
|
+
formattedStack,
|
|
331
|
+
truncated
|
|
332
|
+
} = options;
|
|
333
|
+
const scope = this.log.scoped(depth);
|
|
334
|
+
if (messageLines.length > 0) {
|
|
335
|
+
for (const line of messageLines) {
|
|
336
|
+
const trimmed = line.trimEnd();
|
|
337
|
+
if (trimmed.length === 0) {
|
|
338
|
+
scope.write("");
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
scope.write(pc5.red(trimmed));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (context) {
|
|
345
|
+
this.contextPrinter.printContext(context, depth + 1);
|
|
346
|
+
}
|
|
347
|
+
for (const line of formattedStack) {
|
|
348
|
+
scope.write(pc5.dim(line));
|
|
349
|
+
}
|
|
350
|
+
if (truncated) {
|
|
351
|
+
scope.write(pc5.dim(" \u2026"));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
var ScenarioErrorRenderer = class {
|
|
356
|
+
constructor(baselineRenderer, log = new ImmediateHierarchicalLog()) {
|
|
357
|
+
this.baselineRenderer = baselineRenderer;
|
|
358
|
+
this.log = log;
|
|
359
|
+
}
|
|
360
|
+
print(options) {
|
|
361
|
+
const {
|
|
362
|
+
context,
|
|
363
|
+
depth,
|
|
364
|
+
messageLines,
|
|
365
|
+
formattedStack,
|
|
366
|
+
truncated
|
|
367
|
+
} = options;
|
|
368
|
+
const steps = context.steps ?? [];
|
|
369
|
+
const failingIndex = steps.findIndex((step) => step.status === "failed");
|
|
370
|
+
if (steps.length === 0 || failingIndex === -1) {
|
|
371
|
+
this.baselineRenderer.print({
|
|
372
|
+
context,
|
|
373
|
+
depth,
|
|
374
|
+
messageLines,
|
|
375
|
+
formattedStack,
|
|
376
|
+
truncated
|
|
377
|
+
});
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const scope = this.log.scoped(depth);
|
|
381
|
+
let detailsPrinted = false;
|
|
382
|
+
for (const step of steps) {
|
|
383
|
+
const { description, location } = describeStepSummary(step);
|
|
384
|
+
const icon = getStepStatusIcon(step.status);
|
|
385
|
+
const coloredDescription = colorizeStepDescription(description, step.status);
|
|
386
|
+
const label = location ? `${coloredDescription}${location}` : coloredDescription;
|
|
387
|
+
scope.write(`${icon} ${label}`);
|
|
388
|
+
if (!detailsPrinted && step.status === "failed") {
|
|
389
|
+
this.baselineRenderer.print({
|
|
390
|
+
context,
|
|
391
|
+
depth: depth + 1,
|
|
392
|
+
messageLines,
|
|
393
|
+
formattedStack,
|
|
394
|
+
truncated
|
|
395
|
+
});
|
|
396
|
+
detailsPrinted = true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (!detailsPrinted) {
|
|
400
|
+
this.baselineRenderer.print({
|
|
401
|
+
context,
|
|
402
|
+
depth: depth + 1,
|
|
403
|
+
messageLines,
|
|
404
|
+
formattedStack,
|
|
405
|
+
truncated
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
var GherkinContextPrinter = class {
|
|
411
|
+
constructor(log = new ImmediateHierarchicalLog(), options = {}) {
|
|
412
|
+
this.log = log;
|
|
413
|
+
this.options = options;
|
|
414
|
+
}
|
|
415
|
+
printContext(context, depth) {
|
|
416
|
+
if (this.options.includeCodeFrame && context.gherkin) {
|
|
417
|
+
const details = this.describeGherkinSegment(context.gherkin);
|
|
418
|
+
const pathSegments = context.path;
|
|
419
|
+
this.printCodeFrameSection(
|
|
420
|
+
"Gherkin",
|
|
421
|
+
context.gherkin.location,
|
|
422
|
+
details,
|
|
423
|
+
depth,
|
|
424
|
+
{
|
|
425
|
+
includeLocation: !pathSegments || pathSegments.length === 0
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (this.options.includePath && context.path && context.path.length > 0) {
|
|
430
|
+
this.printGherkinPath(context.path, depth);
|
|
431
|
+
}
|
|
432
|
+
if (context.code) {
|
|
433
|
+
const details = this.describeCodeSegment(context.code);
|
|
434
|
+
this.printCodeFrameSection("Step implementation", context.code.location, details, depth);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
printCodeFrameSection(title, location, details, depth, options = {}) {
|
|
438
|
+
const headerParts = [title];
|
|
439
|
+
if (details) {
|
|
440
|
+
headerParts.push(details);
|
|
441
|
+
}
|
|
442
|
+
if (options.includeLocation !== false) {
|
|
443
|
+
headerParts.push(pc5.dim(formatSourceLocation(location)));
|
|
444
|
+
}
|
|
445
|
+
const scope = this.log.scoped(depth);
|
|
446
|
+
scope.write(pc5.cyan(headerParts.join(" - ")));
|
|
447
|
+
const frame = this.buildCodeFrame(location);
|
|
448
|
+
if (!frame) {
|
|
449
|
+
scope.scoped(1).write(pc5.dim("Unable to read source snippet"));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
for (const line of frame) {
|
|
453
|
+
scope.scoped(1).write(line);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
buildCodeFrame(location) {
|
|
457
|
+
try {
|
|
458
|
+
const contents = fs.readFileSync(location.filePath, "utf8");
|
|
459
|
+
const frame = codeFrameColumns(
|
|
460
|
+
contents,
|
|
461
|
+
{
|
|
462
|
+
start: location.start,
|
|
463
|
+
...location.end ? { end: location.end } : {}
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
linesAbove: 2,
|
|
467
|
+
linesBelow: 2,
|
|
468
|
+
highlightCode: true
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
return frame.split("\n");
|
|
472
|
+
} catch {
|
|
473
|
+
return void 0;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
printGherkinPath(pathSegments, depth) {
|
|
477
|
+
if (pathSegments.length === 0) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
let scope = this.log.scoped(depth);
|
|
481
|
+
let previousKey;
|
|
482
|
+
for (const segment of pathSegments) {
|
|
483
|
+
const segmentKey = this.describePathKey(segment);
|
|
484
|
+
if (segmentKey === previousKey) {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
previousKey = segmentKey;
|
|
488
|
+
scope.write(pc5.dim(`at ${this.describePathLabel(segment)}`));
|
|
489
|
+
scope = scope.scoped(1);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
describeGherkinSegment(segment) {
|
|
493
|
+
if (!segment) {
|
|
494
|
+
return void 0;
|
|
495
|
+
}
|
|
496
|
+
const keyword = segment.stepKeyword?.trim();
|
|
497
|
+
const text = segment.stepText?.trim();
|
|
498
|
+
const parts = [keyword, text].filter((value) => Boolean(value && value.length));
|
|
499
|
+
if (parts.length) {
|
|
500
|
+
return parts.join(" ");
|
|
501
|
+
}
|
|
502
|
+
if (segment.featureName) {
|
|
503
|
+
return `Feature: ${segment.featureName}`;
|
|
504
|
+
}
|
|
505
|
+
return void 0;
|
|
506
|
+
}
|
|
507
|
+
describeCodeSegment(segment) {
|
|
508
|
+
if (!segment) {
|
|
509
|
+
return void 0;
|
|
510
|
+
}
|
|
511
|
+
return segment.functionName ?? void 0;
|
|
512
|
+
}
|
|
513
|
+
describePathKey(segment) {
|
|
514
|
+
return `${segment.role}|${segment.name ?? ""}|${segment.text ?? ""}|${segment.index ?? ""}`;
|
|
515
|
+
}
|
|
516
|
+
describePathLabel(segment) {
|
|
517
|
+
const location = formatSourceLocation(segment.location);
|
|
518
|
+
switch (segment.role) {
|
|
519
|
+
case "feature": {
|
|
520
|
+
const name = segment.name?.trim();
|
|
521
|
+
return name ? `Feature: ${name} (${location})` : `Feature (${location})`;
|
|
522
|
+
}
|
|
523
|
+
case "rule": {
|
|
524
|
+
const name = segment.name?.trim();
|
|
525
|
+
return name ? `Rule: ${name} (${location})` : `Rule (${location})`;
|
|
526
|
+
}
|
|
527
|
+
case "outline": {
|
|
528
|
+
const name = segment.name?.trim();
|
|
529
|
+
return name ? `Scenario Outline: ${name} (${location})` : `Scenario Outline (${location})`;
|
|
530
|
+
}
|
|
531
|
+
case "scenario": {
|
|
532
|
+
const name = segment.name?.trim();
|
|
533
|
+
return name ? `Scenario: ${name} (${location})` : `Scenario (${location})`;
|
|
534
|
+
}
|
|
535
|
+
case "example": {
|
|
536
|
+
const label = segment.name?.trim() ?? (segment.index !== void 0 ? `Example #${segment.index + 1}` : void 0);
|
|
537
|
+
return label ? `${label} (${location})` : `Example (${location})`;
|
|
538
|
+
}
|
|
539
|
+
case "step": {
|
|
540
|
+
const keyword = segment.keyword?.trim();
|
|
541
|
+
const text = segment.text?.trim();
|
|
542
|
+
const labelParts = [keyword, text].filter((value) => Boolean(value && value.length));
|
|
543
|
+
const label = labelParts.length ? labelParts.join(" ") : "Step";
|
|
544
|
+
return `Step: ${label} (${location})`;
|
|
545
|
+
}
|
|
546
|
+
default:
|
|
547
|
+
return `${segment.role} (${location})`;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
function formatStackLines(lines, limit) {
|
|
552
|
+
const cwd = process.cwd();
|
|
553
|
+
const max = Math.max(limit, 0);
|
|
554
|
+
const normalized = [];
|
|
555
|
+
let count = 0;
|
|
556
|
+
for (const line of lines) {
|
|
557
|
+
if (max && count >= max) {
|
|
558
|
+
return { lines: filterFrameworkFrames(normalized), truncated: true };
|
|
559
|
+
}
|
|
560
|
+
const info = normalizeStackLine(line.trimEnd(), cwd);
|
|
561
|
+
normalized.push({
|
|
562
|
+
text: info.text,
|
|
563
|
+
framework: info.relativePath ? isFrameworkPath(info.relativePath) : false
|
|
564
|
+
});
|
|
565
|
+
count += 1;
|
|
566
|
+
}
|
|
567
|
+
return { lines: filterFrameworkFrames(normalized), truncated: false };
|
|
568
|
+
}
|
|
569
|
+
function partitionErrorLines(lines) {
|
|
570
|
+
const messageLines = [];
|
|
571
|
+
const stackLines = [];
|
|
572
|
+
for (const line of lines) {
|
|
573
|
+
const trimmed = line.trim();
|
|
574
|
+
if (isStackLine(trimmed)) {
|
|
575
|
+
stackLines.push(line);
|
|
576
|
+
} else {
|
|
577
|
+
messageLines.push(line);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
while (messageLines.length > 0) {
|
|
581
|
+
const last = messageLines[messageLines.length - 1];
|
|
582
|
+
if (!last || last.trim().length === 0) {
|
|
583
|
+
messageLines.pop();
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
return { messageLines, stackLines };
|
|
589
|
+
}
|
|
590
|
+
function normalizeStackLine(line, cwd) {
|
|
591
|
+
const stackRegex = /(.*?\()?(?<filepath>(?:[a-zA-Z]:)?[\\/][^:\)]+)(?<position>:\d+:\d+)(\))?/;
|
|
592
|
+
const match = line.match(stackRegex);
|
|
593
|
+
if (!match || !match.groups) {
|
|
594
|
+
return { text: line };
|
|
595
|
+
}
|
|
596
|
+
const { filepath, position } = match.groups;
|
|
597
|
+
const absolute = path2.normalize(filepath);
|
|
598
|
+
const relative2 = path2.relative(cwd, absolute) || absolute;
|
|
599
|
+
return {
|
|
600
|
+
text: line.replace(`${filepath}${position}`, `${relative2}${position}`),
|
|
601
|
+
relativePath: relative2
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function filterFrameworkFrames(frames) {
|
|
605
|
+
if (frames.length === 0) {
|
|
606
|
+
return [];
|
|
607
|
+
}
|
|
608
|
+
const filtered = frames.filter((frame, index) => index === 0 || !frame.framework);
|
|
609
|
+
if (filtered.length === 0) {
|
|
610
|
+
return frames.map((frame) => frame.text);
|
|
611
|
+
}
|
|
612
|
+
return filtered.map((frame) => frame.text);
|
|
613
|
+
}
|
|
614
|
+
function isFrameworkPath(relativePath2) {
|
|
615
|
+
const normalized = relativePath2.split(path2.sep).join("/");
|
|
616
|
+
return normalized.includes("node_modules/") || normalized.startsWith("packages/runner/") || normalized.startsWith("packages/executor/");
|
|
617
|
+
}
|
|
618
|
+
function isStackLine(line) {
|
|
619
|
+
if (!line) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
if (line.startsWith("at ")) {
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
return /\((?:[a-zA-Z]:)?[^():]+:\d+:\d+\)$/.test(line);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/utils/reporter.ts
|
|
629
|
+
var HierarchicalReporter = class _HierarchicalReporter {
|
|
630
|
+
constructor(log = console.log, options = {}) {
|
|
631
|
+
this.suiteStack = [];
|
|
632
|
+
this.rootSuites = [];
|
|
633
|
+
this.testNodesByFullName = /* @__PURE__ */ new Map();
|
|
634
|
+
this.suiteMetadataCache = /* @__PURE__ */ new Map();
|
|
635
|
+
this.suiteFailureCache = /* @__PURE__ */ new WeakMap();
|
|
636
|
+
const useBuffer = options.bufferOutput ?? true;
|
|
637
|
+
this.log = useBuffer ? new BufferedHierarchicalLog(log) : new ImmediateHierarchicalLog(log);
|
|
638
|
+
const includeGherkinContext = options.showGherkinStack ?? false;
|
|
639
|
+
const printerOptions = {
|
|
640
|
+
includePath: includeGherkinContext,
|
|
641
|
+
includeCodeFrame: includeGherkinContext
|
|
642
|
+
};
|
|
643
|
+
this.gherkinContextPrinter = new GherkinContextPrinter(this.log, printerOptions);
|
|
644
|
+
this.baselineErrorRenderer = new BaselineErrorRenderer(this.gherkinContextPrinter, this.log);
|
|
645
|
+
this.scenarioErrorRenderer = new ScenarioErrorRenderer(this.baselineErrorRenderer, this.log);
|
|
646
|
+
}
|
|
647
|
+
async onRunStart() {
|
|
648
|
+
this.reset();
|
|
649
|
+
}
|
|
650
|
+
async onSuiteStart(event) {
|
|
651
|
+
const parentNames = event.ancestors;
|
|
652
|
+
const parentSuite = parentNames.length > 0 ? this.ensureSuitePathByNames(parentNames) : void 0;
|
|
653
|
+
let suiteNode;
|
|
654
|
+
if (parentSuite) {
|
|
655
|
+
suiteNode = this.findSuite(parentSuite.children, event.title);
|
|
656
|
+
} else {
|
|
657
|
+
suiteNode = this.findSuite(this.rootSuites, event.title);
|
|
658
|
+
}
|
|
659
|
+
if (!suiteNode) {
|
|
660
|
+
suiteNode = {
|
|
661
|
+
type: "suite",
|
|
662
|
+
name: event.title,
|
|
663
|
+
children: []
|
|
664
|
+
};
|
|
665
|
+
if (parentSuite) {
|
|
666
|
+
parentSuite.children.push(suiteNode);
|
|
667
|
+
} else {
|
|
668
|
+
this.rootSuites.push(suiteNode);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (event.kind) {
|
|
672
|
+
suiteNode.kind = event.kind;
|
|
673
|
+
}
|
|
674
|
+
if (event.keyword) {
|
|
675
|
+
suiteNode.keyword = event.keyword;
|
|
676
|
+
}
|
|
677
|
+
const path3 = [...parentNames, event.title];
|
|
678
|
+
if (event.kind || event.keyword) {
|
|
679
|
+
this.updateSuiteMetadataCache(path3, {
|
|
680
|
+
...event.kind ? { kind: event.kind } : {},
|
|
681
|
+
...event.keyword ? { keyword: event.keyword } : {}
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
this.suiteStack.push({ node: suiteNode });
|
|
685
|
+
}
|
|
686
|
+
async onSuiteEnd() {
|
|
687
|
+
this.suiteStack.pop();
|
|
688
|
+
}
|
|
689
|
+
async onTestResult(event) {
|
|
690
|
+
const fullName = event.result.fullName;
|
|
691
|
+
const segments = fullName.split(" \u203A ");
|
|
692
|
+
const suiteNames = event.result.path && event.result.path.length > 0 ? [...event.result.path] : segments.slice(0, -1);
|
|
693
|
+
const parentSuite = this.ensureSuitePathByNames(suiteNames);
|
|
694
|
+
let testNode = this.testNodesByFullName.get(fullName);
|
|
695
|
+
if (!testNode) {
|
|
696
|
+
testNode = {
|
|
697
|
+
type: "test",
|
|
698
|
+
name: event.result.name,
|
|
699
|
+
logs: []
|
|
700
|
+
};
|
|
701
|
+
parentSuite.children.push(testNode);
|
|
702
|
+
this.testNodesByFullName.set(fullName, testNode);
|
|
703
|
+
}
|
|
704
|
+
testNode.status = event.result.status;
|
|
705
|
+
if (event.result.durationMs !== void 0) {
|
|
706
|
+
testNode.durationMs = event.result.durationMs;
|
|
707
|
+
} else {
|
|
708
|
+
delete testNode.durationMs;
|
|
709
|
+
}
|
|
710
|
+
if (event.result.error) {
|
|
711
|
+
testNode.error = event.result.error;
|
|
712
|
+
} else {
|
|
713
|
+
delete testNode.error;
|
|
714
|
+
}
|
|
715
|
+
if (event.result.reason) {
|
|
716
|
+
testNode.reason = event.result.reason;
|
|
717
|
+
} else {
|
|
718
|
+
delete testNode.reason;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
onHookLog(event) {
|
|
722
|
+
const suiteNames = this.extractSuiteNames(event.path);
|
|
723
|
+
const scenarioSegment = this.findScenarioSegment(event.path);
|
|
724
|
+
const scenarioName = event.scenario?.name ?? scenarioSegment?.name;
|
|
725
|
+
const context = {
|
|
726
|
+
...scenarioName ? { scenarioName } : {}
|
|
727
|
+
};
|
|
728
|
+
const formatted = this.formatHookMessage(event, context);
|
|
729
|
+
if (suiteNames.length === 0 && !scenarioSegment) {
|
|
730
|
+
this.log.write(formatted, 0);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
const parentSuite = this.ensureSuitePathByNames(suiteNames);
|
|
734
|
+
if (scenarioSegment) {
|
|
735
|
+
const testNode = this.ensureTestNode(parentSuite, suiteNames, scenarioSegment, event);
|
|
736
|
+
const offset = event.step ? 1 : 0;
|
|
737
|
+
testNode.logs.push({
|
|
738
|
+
type: "log",
|
|
739
|
+
message: formatted,
|
|
740
|
+
offset
|
|
741
|
+
});
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
parentSuite.children.push({
|
|
745
|
+
type: "log",
|
|
746
|
+
message: formatted,
|
|
747
|
+
offset: 0
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
async onRunEnd() {
|
|
751
|
+
this.flush();
|
|
752
|
+
}
|
|
753
|
+
reset() {
|
|
754
|
+
this.suiteStack = [];
|
|
755
|
+
this.rootSuites = [];
|
|
756
|
+
this.testNodesByFullName.clear();
|
|
757
|
+
this.suiteFailureCache = /* @__PURE__ */ new WeakMap();
|
|
758
|
+
}
|
|
759
|
+
flush() {
|
|
760
|
+
for (const suite of this.rootSuites) {
|
|
761
|
+
this.printNode(suite, 0);
|
|
762
|
+
}
|
|
763
|
+
this.log.flush();
|
|
764
|
+
}
|
|
765
|
+
printNode(node, depth) {
|
|
766
|
+
if (node.type === "suite") {
|
|
767
|
+
const isSyntheticRoot = node.name === "(root)";
|
|
768
|
+
const nextDepth = isSyntheticRoot ? depth : depth + 1;
|
|
769
|
+
if (!isSyntheticRoot) {
|
|
770
|
+
const heading = this.formatSuiteHeading(node);
|
|
771
|
+
this.log.write(heading, depth);
|
|
772
|
+
}
|
|
773
|
+
for (const child of node.children) {
|
|
774
|
+
if (child.type === "log") {
|
|
775
|
+
if (this.suiteHasFailingDescendant(node)) {
|
|
776
|
+
this.log.write(child.message, nextDepth + child.offset);
|
|
777
|
+
}
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
this.printNode(child, nextDepth);
|
|
781
|
+
}
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
if (node.type === "log") {
|
|
785
|
+
this.log.write(node.message, depth + node.offset);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (!node.status) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const icon = getScenarioStatusIcon(node.status);
|
|
792
|
+
const coloredName = colorizeScenarioStatus(node.name, node.status);
|
|
793
|
+
const duration = node.durationMs !== void 0 ? pc5.dim(` (${this.formatDuration(node.durationMs)})`) : "";
|
|
794
|
+
const scenarioLabel = `${pc5.bold("Scenario:")} ${coloredName}`;
|
|
795
|
+
this.log.write(`${icon} ${scenarioLabel}${duration}`, depth);
|
|
796
|
+
if (node.status !== "passed") {
|
|
797
|
+
for (const logEntry of node.logs) {
|
|
798
|
+
this.log.write(logEntry.message, depth + 1 + logEntry.offset);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
if (node.error) {
|
|
802
|
+
this.printError(node.error, depth + 1);
|
|
803
|
+
} else if (node.reason) {
|
|
804
|
+
this.log.write(pc5.dim(`Reason: ${node.reason}`), depth + 1);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
suiteHasFailingDescendant(node) {
|
|
808
|
+
const cached = this.suiteFailureCache.get(node);
|
|
809
|
+
if (cached !== void 0) {
|
|
810
|
+
return cached;
|
|
811
|
+
}
|
|
812
|
+
let hasFailure = false;
|
|
813
|
+
for (const child of node.children) {
|
|
814
|
+
if (child.type === "test") {
|
|
815
|
+
if (child.status === "failed") {
|
|
816
|
+
hasFailure = true;
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
if (child.type === "suite") {
|
|
822
|
+
if (this.suiteHasFailingDescendant(child)) {
|
|
823
|
+
hasFailure = true;
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
this.suiteFailureCache.set(node, hasFailure);
|
|
829
|
+
return hasFailure;
|
|
830
|
+
}
|
|
831
|
+
formatSuiteHeading(node) {
|
|
832
|
+
const keyword = this.resolveSuiteKeyword(node);
|
|
833
|
+
if (!keyword) {
|
|
834
|
+
return pc5.bold(node.name);
|
|
835
|
+
}
|
|
836
|
+
const prefix = this.highlightSuiteKeyword(keyword, node.kind);
|
|
837
|
+
const name = node.name ? this.sanitizeSuiteName(node.name, keyword) : void 0;
|
|
838
|
+
return name && name.length > 0 ? `${prefix} ${name}` : prefix;
|
|
839
|
+
}
|
|
840
|
+
resolveSuiteKeyword(node) {
|
|
841
|
+
const preferred = node.keyword;
|
|
842
|
+
const fallback = this.defaultSuiteKeyword(node.kind);
|
|
843
|
+
const chosen = (preferred ?? fallback)?.trim();
|
|
844
|
+
if (!chosen || chosen.length === 0) {
|
|
845
|
+
return void 0;
|
|
846
|
+
}
|
|
847
|
+
return chosen.replace(/:\s*$/u, "");
|
|
848
|
+
}
|
|
849
|
+
sanitizeSuiteName(name, keyword) {
|
|
850
|
+
const trimmed = name.trim();
|
|
851
|
+
if (trimmed.length === 0) {
|
|
852
|
+
return "";
|
|
853
|
+
}
|
|
854
|
+
const pattern = new RegExp(`^${_HierarchicalReporter.escapeRegExp(keyword)}\\s*:`, "i");
|
|
855
|
+
if (pattern.test(trimmed)) {
|
|
856
|
+
return trimmed.replace(pattern, "").trim();
|
|
857
|
+
}
|
|
858
|
+
return trimmed;
|
|
859
|
+
}
|
|
860
|
+
defaultSuiteKeyword(kind) {
|
|
861
|
+
switch (kind) {
|
|
862
|
+
case "feature":
|
|
863
|
+
return "Feature";
|
|
864
|
+
case "rule":
|
|
865
|
+
return "Rule";
|
|
866
|
+
case "scenarioOutline":
|
|
867
|
+
return "Scenario Outline";
|
|
868
|
+
case "examples":
|
|
869
|
+
return "Examples";
|
|
870
|
+
default:
|
|
871
|
+
return void 0;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
highlightSuiteKeyword(keyword, kind) {
|
|
875
|
+
const label = `${keyword}:`;
|
|
876
|
+
switch (kind) {
|
|
877
|
+
case "feature":
|
|
878
|
+
return pc5.bold(pc5.cyan(label));
|
|
879
|
+
case "rule":
|
|
880
|
+
return pc5.bold(pc5.magenta(label));
|
|
881
|
+
case "scenarioOutline":
|
|
882
|
+
return pc5.bold(pc5.blue(label));
|
|
883
|
+
case "examples":
|
|
884
|
+
return pc5.bold(pc5.yellow(label));
|
|
885
|
+
default:
|
|
886
|
+
return pc5.bold(label);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
ensureSuitePathByNames(names) {
|
|
890
|
+
if (names.length === 0) {
|
|
891
|
+
if (this.rootSuites.length === 0) {
|
|
892
|
+
const rootSuite = { type: "suite", name: "(root)", children: [] };
|
|
893
|
+
this.rootSuites.push(rootSuite);
|
|
894
|
+
return rootSuite;
|
|
895
|
+
}
|
|
896
|
+
const [first] = this.rootSuites;
|
|
897
|
+
if (first) {
|
|
898
|
+
return first;
|
|
899
|
+
}
|
|
900
|
+
const fallback = { type: "suite", name: "(root)", children: [] };
|
|
901
|
+
this.rootSuites.push(fallback);
|
|
902
|
+
return fallback;
|
|
903
|
+
}
|
|
904
|
+
let currentChildren = this.rootSuites;
|
|
905
|
+
let currentSuite;
|
|
906
|
+
const path3 = [];
|
|
907
|
+
for (const name of names) {
|
|
908
|
+
path3.push(name);
|
|
909
|
+
let suite = this.findSuite(currentChildren, name);
|
|
910
|
+
if (!suite) {
|
|
911
|
+
suite = { type: "suite", name, children: [] };
|
|
912
|
+
this.applyMetadataFromCache(path3, suite);
|
|
913
|
+
currentChildren.push(suite);
|
|
914
|
+
} else {
|
|
915
|
+
this.applyMetadataFromCache(path3, suite);
|
|
916
|
+
}
|
|
917
|
+
currentSuite = suite;
|
|
918
|
+
currentChildren = suite.children;
|
|
919
|
+
}
|
|
920
|
+
if (!currentSuite) {
|
|
921
|
+
return this.ensureSuitePathByNames([]);
|
|
922
|
+
}
|
|
923
|
+
return currentSuite;
|
|
924
|
+
}
|
|
925
|
+
findSuite(children, name) {
|
|
926
|
+
for (const child of children) {
|
|
927
|
+
if (child.type === "suite" && child.name === name) {
|
|
928
|
+
return child;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return void 0;
|
|
932
|
+
}
|
|
933
|
+
findSuiteByPath(path3) {
|
|
934
|
+
let currentChildren = this.rootSuites;
|
|
935
|
+
let current;
|
|
936
|
+
for (const name of path3) {
|
|
937
|
+
const next = this.findSuite(currentChildren, name);
|
|
938
|
+
if (!next) {
|
|
939
|
+
return void 0;
|
|
940
|
+
}
|
|
941
|
+
current = next;
|
|
942
|
+
currentChildren = next.children;
|
|
943
|
+
}
|
|
944
|
+
return current;
|
|
945
|
+
}
|
|
946
|
+
getSuitePathKey(path3) {
|
|
947
|
+
return path3.join(" \u203A ");
|
|
948
|
+
}
|
|
949
|
+
applyMetadataFromCache(path3, node) {
|
|
950
|
+
const cached = this.suiteMetadataCache.get(this.getSuitePathKey(path3));
|
|
951
|
+
if (!cached) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
if (cached.kind) {
|
|
955
|
+
node.kind = cached.kind;
|
|
956
|
+
}
|
|
957
|
+
if (cached.keyword) {
|
|
958
|
+
node.keyword = cached.keyword;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
updateSuiteMetadataCache(path3, metadata) {
|
|
962
|
+
const key = this.getSuitePathKey(path3);
|
|
963
|
+
const existing = this.suiteMetadataCache.get(key) ?? {};
|
|
964
|
+
this.suiteMetadataCache.set(key, { ...existing, ...metadata });
|
|
965
|
+
const node = this.findSuiteByPath(path3);
|
|
966
|
+
if (node) {
|
|
967
|
+
if (metadata.kind) {
|
|
968
|
+
node.kind = metadata.kind;
|
|
969
|
+
}
|
|
970
|
+
if (metadata.keyword) {
|
|
971
|
+
node.keyword = metadata.keyword;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
ensureTestNode(parentSuite, suiteNames, scenarioSegment, event) {
|
|
976
|
+
const fullName = event.scenario?.fullName ?? [...suiteNames, scenarioSegment.name].join(" \u203A ");
|
|
977
|
+
let node = this.testNodesByFullName.get(fullName);
|
|
978
|
+
if (!node) {
|
|
979
|
+
node = {
|
|
980
|
+
type: "test",
|
|
981
|
+
name: event.scenario?.name ?? scenarioSegment.name,
|
|
982
|
+
logs: []
|
|
983
|
+
};
|
|
984
|
+
parentSuite.children.push(node);
|
|
985
|
+
this.testNodesByFullName.set(fullName, node);
|
|
986
|
+
}
|
|
987
|
+
return node;
|
|
988
|
+
}
|
|
989
|
+
extractSuiteNames(path3) {
|
|
990
|
+
const suites = [];
|
|
991
|
+
const currentPath = [];
|
|
992
|
+
for (const segment of path3) {
|
|
993
|
+
if (!segment) {
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
if (segment.kind === "feature" || segment.kind === "rule" || segment.kind === "scenarioOutline") {
|
|
997
|
+
suites.push(segment.name);
|
|
998
|
+
currentPath.push(segment.name);
|
|
999
|
+
const suiteKind = this.toSuiteHierarchyKind(segment.kind);
|
|
1000
|
+
if (suiteKind) {
|
|
1001
|
+
const metadata = {
|
|
1002
|
+
kind: suiteKind,
|
|
1003
|
+
...segment.keyword ? { keyword: segment.keyword } : {}
|
|
1004
|
+
};
|
|
1005
|
+
this.updateSuiteMetadataCache(currentPath, metadata);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return suites;
|
|
1010
|
+
}
|
|
1011
|
+
toSuiteHierarchyKind(kind) {
|
|
1012
|
+
switch (kind) {
|
|
1013
|
+
case "feature":
|
|
1014
|
+
return "feature";
|
|
1015
|
+
case "rule":
|
|
1016
|
+
return "rule";
|
|
1017
|
+
case "scenarioOutline":
|
|
1018
|
+
return "scenarioOutline";
|
|
1019
|
+
default:
|
|
1020
|
+
return void 0;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
findScenarioSegment(path3) {
|
|
1024
|
+
for (let index = path3.length - 1; index >= 0; index -= 1) {
|
|
1025
|
+
const segment = path3[index];
|
|
1026
|
+
if (!segment) {
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
if (segment.kind === "scenario") {
|
|
1030
|
+
return segment;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return void 0;
|
|
1034
|
+
}
|
|
1035
|
+
formatHookMessage(event, context) {
|
|
1036
|
+
const kind = this.getHookTargetKind(event.hookType);
|
|
1037
|
+
if (kind === "step") {
|
|
1038
|
+
return this.formatStepHookMessage(event, context);
|
|
1039
|
+
}
|
|
1040
|
+
return this.formatContainerHookMessage(event, kind, context);
|
|
1041
|
+
}
|
|
1042
|
+
getHookTargetKind(hookType) {
|
|
1043
|
+
switch (hookType) {
|
|
1044
|
+
case "beforeFeature":
|
|
1045
|
+
case "afterFeature":
|
|
1046
|
+
return "feature";
|
|
1047
|
+
case "beforeRule":
|
|
1048
|
+
case "afterRule":
|
|
1049
|
+
return "rule";
|
|
1050
|
+
case "beforeScenario":
|
|
1051
|
+
case "afterScenario":
|
|
1052
|
+
case "beforeStep":
|
|
1053
|
+
case "afterStep":
|
|
1054
|
+
return hookType === "beforeStep" || hookType === "afterStep" ? "step" : "scenario";
|
|
1055
|
+
case "beforeScenarioOutline":
|
|
1056
|
+
case "afterScenarioOutline":
|
|
1057
|
+
return "scenarioOutline";
|
|
1058
|
+
default:
|
|
1059
|
+
return "scenario";
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
formatStepHookMessage(event, context) {
|
|
1063
|
+
const phaseLabel = this.wrapPhaseLabel(event.phase);
|
|
1064
|
+
const stepKeyword = this.extractStepKeyword(event) ?? "Step";
|
|
1065
|
+
const coloredKeyword = this.wrapStepKeyword(stepKeyword);
|
|
1066
|
+
const text = this.extractStepText(event, context);
|
|
1067
|
+
const status = this.describeStepStatus(event.step?.status);
|
|
1068
|
+
const parts = [phaseLabel, coloredKeyword];
|
|
1069
|
+
if (text && text.length > 0) {
|
|
1070
|
+
parts.push(text);
|
|
1071
|
+
}
|
|
1072
|
+
if (status) {
|
|
1073
|
+
parts.push(status);
|
|
1074
|
+
}
|
|
1075
|
+
return parts.join(" ");
|
|
1076
|
+
}
|
|
1077
|
+
formatContainerHookMessage(event, kind, context) {
|
|
1078
|
+
const phaseLabel = this.wrapPhaseLabel(event.phase);
|
|
1079
|
+
const keyword = this.resolveTargetKeyword(kind, event) ?? this.describeFallbackTarget(kind, event);
|
|
1080
|
+
const coloredKeyword = keyword ? this.wrapContainerKeyword(keyword) : void 0;
|
|
1081
|
+
const message = this.sanitizeHookMessage(event.message, context);
|
|
1082
|
+
if (coloredKeyword && message) {
|
|
1083
|
+
return `${phaseLabel} ${coloredKeyword}: ${message}`;
|
|
1084
|
+
}
|
|
1085
|
+
if (coloredKeyword) {
|
|
1086
|
+
return `${phaseLabel} ${coloredKeyword}`;
|
|
1087
|
+
}
|
|
1088
|
+
if (message) {
|
|
1089
|
+
return `${phaseLabel} ${message}`;
|
|
1090
|
+
}
|
|
1091
|
+
return phaseLabel;
|
|
1092
|
+
}
|
|
1093
|
+
extractStepText(event, context) {
|
|
1094
|
+
const text = event.step?.text?.trim();
|
|
1095
|
+
if (text && text.length > 0) {
|
|
1096
|
+
return text;
|
|
1097
|
+
}
|
|
1098
|
+
return this.sanitizeHookMessage(event.message, context);
|
|
1099
|
+
}
|
|
1100
|
+
sanitizeHookMessage(message, context) {
|
|
1101
|
+
if (!message) {
|
|
1102
|
+
return void 0;
|
|
1103
|
+
}
|
|
1104
|
+
let result = message.trim();
|
|
1105
|
+
if (context.scenarioName) {
|
|
1106
|
+
const escaped = _HierarchicalReporter.escapeRegExp(context.scenarioName);
|
|
1107
|
+
const patterns = [
|
|
1108
|
+
new RegExp(`^Scenario\\s+"?${escaped}"?\\s*::\\s*`, "i"),
|
|
1109
|
+
new RegExp(`^Scenario\\s+"?${escaped}"?\\s*`, "i")
|
|
1110
|
+
];
|
|
1111
|
+
for (const pattern of patterns) {
|
|
1112
|
+
const updated = result.replace(pattern, "").trim();
|
|
1113
|
+
if (updated !== result) {
|
|
1114
|
+
result = updated;
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return result;
|
|
1120
|
+
}
|
|
1121
|
+
wrapPhaseLabel(phase) {
|
|
1122
|
+
const label = phase === "before" ? "Before" : "After";
|
|
1123
|
+
return phase === "before" ? pc5.cyan(label) : pc5.magenta(label);
|
|
1124
|
+
}
|
|
1125
|
+
wrapStepKeyword(keyword) {
|
|
1126
|
+
const normalized = keyword.trim().toLowerCase();
|
|
1127
|
+
switch (normalized) {
|
|
1128
|
+
case "given":
|
|
1129
|
+
return pc5.blue(keyword);
|
|
1130
|
+
case "when":
|
|
1131
|
+
return pc5.yellow(keyword);
|
|
1132
|
+
case "then":
|
|
1133
|
+
return pc5.green(keyword);
|
|
1134
|
+
case "and":
|
|
1135
|
+
case "but":
|
|
1136
|
+
return pc5.cyan(keyword);
|
|
1137
|
+
default:
|
|
1138
|
+
return pc5.white(keyword);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
wrapContainerKeyword(keyword) {
|
|
1142
|
+
return pc5.bold(keyword);
|
|
1143
|
+
}
|
|
1144
|
+
describeStepStatus(status) {
|
|
1145
|
+
if (!status) {
|
|
1146
|
+
return void 0;
|
|
1147
|
+
}
|
|
1148
|
+
switch (status.toLowerCase()) {
|
|
1149
|
+
case "passed":
|
|
1150
|
+
return pc5.dim("(passed)");
|
|
1151
|
+
case "failed":
|
|
1152
|
+
return pc5.red("(failed)");
|
|
1153
|
+
case "skipped":
|
|
1154
|
+
return pc5.yellow("(skipped)");
|
|
1155
|
+
default:
|
|
1156
|
+
return pc5.dim(`(${status})`);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
static escapeRegExp(value) {
|
|
1160
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
1161
|
+
}
|
|
1162
|
+
resolveTargetKeyword(kind, event) {
|
|
1163
|
+
const normalized = (value) => {
|
|
1164
|
+
if (!value) {
|
|
1165
|
+
return void 0;
|
|
1166
|
+
}
|
|
1167
|
+
return this.cleanKeyword(value);
|
|
1168
|
+
};
|
|
1169
|
+
if (kind === "scenario") {
|
|
1170
|
+
return normalized(event.scenario?.keyword) ?? normalized(event.targetKeyword) ?? normalized(this.findSegmentKeyword(kind, event.path));
|
|
1171
|
+
}
|
|
1172
|
+
if (kind === "scenarioOutline") {
|
|
1173
|
+
return normalized(event.targetKeyword) ?? normalized(this.findSegmentKeyword(kind, event.path));
|
|
1174
|
+
}
|
|
1175
|
+
if (kind === "feature" || kind === "rule") {
|
|
1176
|
+
return normalized(event.targetKeyword) ?? normalized(this.findSegmentKeyword(kind, event.path));
|
|
1177
|
+
}
|
|
1178
|
+
return void 0;
|
|
1179
|
+
}
|
|
1180
|
+
describeFallbackTarget(kind, _event) {
|
|
1181
|
+
switch (kind) {
|
|
1182
|
+
case "feature":
|
|
1183
|
+
return "Feature";
|
|
1184
|
+
case "rule":
|
|
1185
|
+
return "Rule";
|
|
1186
|
+
case "scenario":
|
|
1187
|
+
return "Scenario";
|
|
1188
|
+
case "scenarioOutline":
|
|
1189
|
+
return "Scenario Outline";
|
|
1190
|
+
case "step":
|
|
1191
|
+
default:
|
|
1192
|
+
return "Step";
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
extractStepKeyword(event) {
|
|
1196
|
+
const keyword = event.step?.keyword;
|
|
1197
|
+
if (!keyword || keyword.trim().length === 0) {
|
|
1198
|
+
return void 0;
|
|
1199
|
+
}
|
|
1200
|
+
return this.cleanKeyword(keyword);
|
|
1201
|
+
}
|
|
1202
|
+
findSegmentKeyword(kind, path3) {
|
|
1203
|
+
for (let index = path3.length - 1; index >= 0; index -= 1) {
|
|
1204
|
+
const segment = path3[index];
|
|
1205
|
+
if (!segment) {
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
if (segment.kind === kind && segment.keyword) {
|
|
1209
|
+
return segment.keyword;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return void 0;
|
|
1213
|
+
}
|
|
1214
|
+
cleanKeyword(keyword) {
|
|
1215
|
+
return keyword.trim().replace(/:\s*$/u, "");
|
|
1216
|
+
}
|
|
1217
|
+
formatDuration(ms) {
|
|
1218
|
+
if (ms < 1) {
|
|
1219
|
+
return `${ms.toFixed(2)} ms`;
|
|
1220
|
+
}
|
|
1221
|
+
if (ms < 1e3) {
|
|
1222
|
+
return `${ms.toFixed(0)} ms`;
|
|
1223
|
+
}
|
|
1224
|
+
return `${(ms / 1e3).toFixed(2)} s`;
|
|
1225
|
+
}
|
|
1226
|
+
printError(error, depth) {
|
|
1227
|
+
const stack = error.stack ?? error.message;
|
|
1228
|
+
const lines = stack.split("\n");
|
|
1229
|
+
if (lines.length === 0) {
|
|
1230
|
+
this.log.write(pc5.red(error.message), depth);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
const [headline, ...rest] = lines;
|
|
1234
|
+
this.log.write(pc5.red(headline), depth);
|
|
1235
|
+
const { messageLines, stackLines } = partitionErrorLines(rest);
|
|
1236
|
+
const { lines: formattedStack, truncated } = formatStackLines(stackLines, 4);
|
|
1237
|
+
const context = getGherkinErrorContext(error);
|
|
1238
|
+
if (context?.steps && context.steps.length > 0) {
|
|
1239
|
+
this.scenarioErrorRenderer.print({
|
|
1240
|
+
context,
|
|
1241
|
+
depth,
|
|
1242
|
+
messageLines,
|
|
1243
|
+
formattedStack,
|
|
1244
|
+
truncated
|
|
1245
|
+
});
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
this.baselineErrorRenderer.print({
|
|
1249
|
+
...context ? { context } : {},
|
|
1250
|
+
depth,
|
|
1251
|
+
messageLines,
|
|
1252
|
+
formattedStack,
|
|
1253
|
+
truncated
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
// src/runtime/cli-runtime.ts
|
|
1259
|
+
var clock = typeof globalThis.performance?.now === "function" ? globalThis.performance : { now: () => Date.now() };
|
|
1260
|
+
function createCliRuntime(options = {}) {
|
|
1261
|
+
const root = {
|
|
1262
|
+
title: "(root)",
|
|
1263
|
+
mode: "default",
|
|
1264
|
+
children: []
|
|
1265
|
+
};
|
|
1266
|
+
let currentSuite = root;
|
|
1267
|
+
let hasFocusedBlock = false;
|
|
1268
|
+
let currentTestName;
|
|
1269
|
+
const state = {
|
|
1270
|
+
total: 0,
|
|
1271
|
+
passed: 0,
|
|
1272
|
+
failed: 0,
|
|
1273
|
+
skipped: 0,
|
|
1274
|
+
pending: 0,
|
|
1275
|
+
success: true
|
|
1276
|
+
};
|
|
1277
|
+
let emitHookLog = () => void 0;
|
|
1278
|
+
let pendingSuiteMetadata;
|
|
1279
|
+
const assignMetadataHandler = (target) => {
|
|
1280
|
+
const fn = target;
|
|
1281
|
+
fn.__withMetadata = (metadata, register) => {
|
|
1282
|
+
const previous = pendingSuiteMetadata;
|
|
1283
|
+
pendingSuiteMetadata = metadata;
|
|
1284
|
+
try {
|
|
1285
|
+
register();
|
|
1286
|
+
} finally {
|
|
1287
|
+
pendingSuiteMetadata = previous;
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
return fn;
|
|
1291
|
+
};
|
|
1292
|
+
function registerSuite(mode, title, handler, timeout) {
|
|
1293
|
+
const metadata = pendingSuiteMetadata;
|
|
1294
|
+
const node = {
|
|
1295
|
+
title,
|
|
1296
|
+
mode,
|
|
1297
|
+
...timeout !== void 0 ? { timeout } : {},
|
|
1298
|
+
...currentSuite === root ? {} : { parent: currentSuite },
|
|
1299
|
+
children: [],
|
|
1300
|
+
...metadata ? { metadata: { ...metadata } } : {}
|
|
1301
|
+
};
|
|
1302
|
+
currentSuite.children.push({ kind: "suite", node });
|
|
1303
|
+
if (mode === "only") {
|
|
1304
|
+
hasFocusedBlock = true;
|
|
1305
|
+
}
|
|
1306
|
+
if (mode === "skip" || !handler) {
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
const previous = currentSuite;
|
|
1310
|
+
currentSuite = node;
|
|
1311
|
+
try {
|
|
1312
|
+
handler();
|
|
1313
|
+
} finally {
|
|
1314
|
+
currentSuite = previous;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
function registerTest(entry) {
|
|
1318
|
+
const node = {
|
|
1319
|
+
title: entry.title,
|
|
1320
|
+
mode: entry.mode,
|
|
1321
|
+
kind: entry.kind,
|
|
1322
|
+
...entry.handler ? { handler: entry.handler } : {},
|
|
1323
|
+
...entry.timeout !== void 0 ? { timeout: entry.timeout } : {},
|
|
1324
|
+
...entry.reason !== void 0 ? { reason: entry.reason } : {}
|
|
1325
|
+
};
|
|
1326
|
+
if (node.mode === "only") {
|
|
1327
|
+
hasFocusedBlock = true;
|
|
1328
|
+
}
|
|
1329
|
+
currentSuite.children.push({ kind: "test", node });
|
|
1330
|
+
}
|
|
1331
|
+
const suiteDefault = assignMetadataHandler((title, handler, timeout) => {
|
|
1332
|
+
registerSuite("default", title, handler, timeout);
|
|
1333
|
+
});
|
|
1334
|
+
const suiteSkip = assignMetadataHandler((title, _handler, timeout) => {
|
|
1335
|
+
registerSuite("skip", title, void 0, timeout);
|
|
1336
|
+
});
|
|
1337
|
+
const suiteOnly = assignMetadataHandler((title, handler, timeout) => {
|
|
1338
|
+
registerSuite("only", title, handler, timeout);
|
|
1339
|
+
});
|
|
1340
|
+
const suiteConcurrent = assignMetadataHandler((title, handler, timeout) => {
|
|
1341
|
+
registerSuite("concurrent", title, handler, timeout);
|
|
1342
|
+
});
|
|
1343
|
+
suiteDefault.skip = suiteSkip;
|
|
1344
|
+
suiteDefault.only = suiteOnly;
|
|
1345
|
+
suiteDefault.concurrent = suiteConcurrent;
|
|
1346
|
+
suiteSkip.skip = suiteSkip;
|
|
1347
|
+
suiteSkip.only = suiteOnly;
|
|
1348
|
+
suiteSkip.concurrent = suiteConcurrent;
|
|
1349
|
+
suiteOnly.skip = suiteSkip;
|
|
1350
|
+
suiteOnly.only = suiteOnly;
|
|
1351
|
+
suiteOnly.concurrent = suiteConcurrent;
|
|
1352
|
+
suiteConcurrent.skip = suiteSkip;
|
|
1353
|
+
suiteConcurrent.only = suiteOnly;
|
|
1354
|
+
suiteConcurrent.concurrent = suiteConcurrent;
|
|
1355
|
+
const suite = suiteDefault;
|
|
1356
|
+
const testDefault = (title, handler, timeout) => {
|
|
1357
|
+
registerTest({
|
|
1358
|
+
title,
|
|
1359
|
+
handler,
|
|
1360
|
+
...timeout !== void 0 ? { timeout } : {},
|
|
1361
|
+
mode: "default",
|
|
1362
|
+
kind: "test"
|
|
1363
|
+
});
|
|
1364
|
+
};
|
|
1365
|
+
const testSkip = (title, _handler, timeout) => {
|
|
1366
|
+
registerTest({
|
|
1367
|
+
title,
|
|
1368
|
+
...timeout !== void 0 ? { timeout } : {},
|
|
1369
|
+
mode: "skip",
|
|
1370
|
+
kind: "test"
|
|
1371
|
+
});
|
|
1372
|
+
};
|
|
1373
|
+
const testOnly = (title, handler, timeout) => {
|
|
1374
|
+
registerTest({
|
|
1375
|
+
title,
|
|
1376
|
+
handler,
|
|
1377
|
+
...timeout !== void 0 ? { timeout } : {},
|
|
1378
|
+
mode: "only",
|
|
1379
|
+
kind: "test"
|
|
1380
|
+
});
|
|
1381
|
+
};
|
|
1382
|
+
const testConcurrent = (title, handler, timeout) => {
|
|
1383
|
+
registerTest({
|
|
1384
|
+
title,
|
|
1385
|
+
handler,
|
|
1386
|
+
...timeout !== void 0 ? { timeout } : {},
|
|
1387
|
+
mode: "concurrent",
|
|
1388
|
+
kind: "test"
|
|
1389
|
+
});
|
|
1390
|
+
};
|
|
1391
|
+
testDefault.skip = testSkip;
|
|
1392
|
+
testDefault.only = testOnly;
|
|
1393
|
+
testDefault.concurrent = testConcurrent;
|
|
1394
|
+
testSkip.skip = testSkip;
|
|
1395
|
+
testSkip.only = testOnly;
|
|
1396
|
+
testSkip.concurrent = testConcurrent;
|
|
1397
|
+
testOnly.skip = testSkip;
|
|
1398
|
+
testOnly.only = testOnly;
|
|
1399
|
+
testOnly.concurrent = testConcurrent;
|
|
1400
|
+
testConcurrent.skip = testSkip;
|
|
1401
|
+
testConcurrent.only = testOnly;
|
|
1402
|
+
testConcurrent.concurrent = testConcurrent;
|
|
1403
|
+
testDefault.todo = (title, reason) => {
|
|
1404
|
+
registerTest({
|
|
1405
|
+
title,
|
|
1406
|
+
mode: "skip",
|
|
1407
|
+
kind: "todo",
|
|
1408
|
+
...reason !== void 0 ? { reason } : {}
|
|
1409
|
+
});
|
|
1410
|
+
};
|
|
1411
|
+
testDefault.pending = (title, reason) => {
|
|
1412
|
+
registerTest({
|
|
1413
|
+
title,
|
|
1414
|
+
mode: "skip",
|
|
1415
|
+
kind: "pending",
|
|
1416
|
+
...reason !== void 0 ? { reason } : {}
|
|
1417
|
+
});
|
|
1418
|
+
};
|
|
1419
|
+
testSkip.todo = testDefault.todo;
|
|
1420
|
+
testSkip.pending = testDefault.pending;
|
|
1421
|
+
testOnly.todo = testDefault.todo;
|
|
1422
|
+
testOnly.pending = testDefault.pending;
|
|
1423
|
+
testConcurrent.todo = testDefault.todo;
|
|
1424
|
+
testConcurrent.pending = testDefault.pending;
|
|
1425
|
+
const test = testDefault;
|
|
1426
|
+
function invokeHook(handler) {
|
|
1427
|
+
const outcome = handler();
|
|
1428
|
+
if (outcome && typeof outcome.then === "function") {
|
|
1429
|
+
void outcome.catch((error) => {
|
|
1430
|
+
console.error(error);
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
const runtime = {
|
|
1435
|
+
suite,
|
|
1436
|
+
test,
|
|
1437
|
+
beforeAll(handler) {
|
|
1438
|
+
invokeHook(handler);
|
|
1439
|
+
},
|
|
1440
|
+
afterAll(handler) {
|
|
1441
|
+
invokeHook(handler);
|
|
1442
|
+
},
|
|
1443
|
+
beforeEach(handler) {
|
|
1444
|
+
invokeHook(handler);
|
|
1445
|
+
},
|
|
1446
|
+
afterEach(handler) {
|
|
1447
|
+
invokeHook(handler);
|
|
1448
|
+
},
|
|
1449
|
+
currentTestName: () => currentTestName
|
|
1450
|
+
};
|
|
1451
|
+
const hookLogger = (event) => {
|
|
1452
|
+
emitHookLog(event);
|
|
1453
|
+
};
|
|
1454
|
+
async function execute() {
|
|
1455
|
+
const startedAt = clock.now();
|
|
1456
|
+
const reports = [];
|
|
1457
|
+
const reporters = [
|
|
1458
|
+
...options.reporters ? [...options.reporters] : [new HierarchicalReporter(void 0, options.reporter?.hierarchical)]
|
|
1459
|
+
];
|
|
1460
|
+
emitHookLog = (event) => {
|
|
1461
|
+
for (const reporter of reporters) {
|
|
1462
|
+
if (typeof reporter.onHookLog === "function") {
|
|
1463
|
+
reporter.onHookLog(event);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
await dispatchRunStart({ timestamp: startedAt });
|
|
1468
|
+
await runSuite(root, {
|
|
1469
|
+
skip: false,
|
|
1470
|
+
focus: false,
|
|
1471
|
+
path: []
|
|
1472
|
+
});
|
|
1473
|
+
const finishedAt = clock.now();
|
|
1474
|
+
const summary = {
|
|
1475
|
+
total: state.total,
|
|
1476
|
+
passed: state.passed,
|
|
1477
|
+
failed: state.failed,
|
|
1478
|
+
skipped: state.skipped,
|
|
1479
|
+
pending: state.pending,
|
|
1480
|
+
durationMs: finishedAt - startedAt,
|
|
1481
|
+
success: state.success,
|
|
1482
|
+
scenarios: reports
|
|
1483
|
+
};
|
|
1484
|
+
await dispatchRunEnd({ timestamp: finishedAt, summary });
|
|
1485
|
+
emitHookLog = () => void 0;
|
|
1486
|
+
return summary;
|
|
1487
|
+
async function runSuite(node, context) {
|
|
1488
|
+
const skip = context.skip || node.mode === "skip";
|
|
1489
|
+
const focus = context.focus || node.mode === "only";
|
|
1490
|
+
const suitePath = node.parent ? [...context.path, node.title] : context.path;
|
|
1491
|
+
const shouldTrack = node.title && node.title !== "(root)";
|
|
1492
|
+
if (shouldTrack) {
|
|
1493
|
+
await dispatchSuiteStart({
|
|
1494
|
+
title: node.title,
|
|
1495
|
+
ancestors: context.path,
|
|
1496
|
+
path: suitePath,
|
|
1497
|
+
...node.metadata?.kind ? { kind: node.metadata.kind } : {},
|
|
1498
|
+
...node.metadata?.keyword ? { keyword: node.metadata.keyword } : {}
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
for (const child of node.children) {
|
|
1502
|
+
if (child.kind === "suite") {
|
|
1503
|
+
await runSuite(child.node, { skip, focus, path: suitePath });
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
await runTest(child.node, suitePath, skip, focus);
|
|
1507
|
+
}
|
|
1508
|
+
if (shouldTrack) {
|
|
1509
|
+
await dispatchSuiteEnd({
|
|
1510
|
+
title: node.title,
|
|
1511
|
+
ancestors: context.path,
|
|
1512
|
+
path: suitePath,
|
|
1513
|
+
...node.metadata?.kind ? { kind: node.metadata.kind } : {},
|
|
1514
|
+
...node.metadata?.keyword ? { keyword: node.metadata.keyword } : {}
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
async function runTest(node, ancestors, skipBranch, focusBranch) {
|
|
1519
|
+
const suitePath = [...ancestors];
|
|
1520
|
+
const fullPath = [...suitePath, node.title];
|
|
1521
|
+
const fullName = fullPath.join(" \u203A ");
|
|
1522
|
+
if (node.kind === "todo" || node.kind === "pending") {
|
|
1523
|
+
await record({
|
|
1524
|
+
status: "pending",
|
|
1525
|
+
name: node.title,
|
|
1526
|
+
fullName,
|
|
1527
|
+
path: suitePath,
|
|
1528
|
+
...node.reason !== void 0 ? { reason: node.reason } : {}
|
|
1529
|
+
});
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const skipDueToFocus = hasFocusedBlock && !focusBranch && node.mode !== "only";
|
|
1533
|
+
const shouldSkip = skipBranch || node.mode === "skip" || skipDueToFocus;
|
|
1534
|
+
if (options.dryRun) {
|
|
1535
|
+
await record({
|
|
1536
|
+
status: "pending",
|
|
1537
|
+
name: node.title,
|
|
1538
|
+
fullName,
|
|
1539
|
+
path: suitePath,
|
|
1540
|
+
reason: "dry run"
|
|
1541
|
+
});
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
if (shouldSkip || !node.handler) {
|
|
1545
|
+
await record({
|
|
1546
|
+
status: "skipped",
|
|
1547
|
+
name: node.title,
|
|
1548
|
+
fullName,
|
|
1549
|
+
path: suitePath
|
|
1550
|
+
});
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
const startedAt2 = clock.now();
|
|
1554
|
+
currentTestName = fullName;
|
|
1555
|
+
try {
|
|
1556
|
+
const result = node.handler();
|
|
1557
|
+
if (result && typeof result.then === "function") {
|
|
1558
|
+
await result;
|
|
1559
|
+
}
|
|
1560
|
+
const duration = clock.now() - startedAt2;
|
|
1561
|
+
await record({
|
|
1562
|
+
status: "passed",
|
|
1563
|
+
name: node.title,
|
|
1564
|
+
fullName,
|
|
1565
|
+
path: suitePath,
|
|
1566
|
+
durationMs: duration
|
|
1567
|
+
});
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
const duration = clock.now() - startedAt2;
|
|
1570
|
+
await record({
|
|
1571
|
+
status: "failed",
|
|
1572
|
+
name: node.title,
|
|
1573
|
+
fullName,
|
|
1574
|
+
path: suitePath,
|
|
1575
|
+
durationMs: duration,
|
|
1576
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
1577
|
+
});
|
|
1578
|
+
} finally {
|
|
1579
|
+
currentTestName = void 0;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
async function record(report) {
|
|
1583
|
+
state.total += 1;
|
|
1584
|
+
switch (report.status) {
|
|
1585
|
+
case "passed":
|
|
1586
|
+
state.passed += 1;
|
|
1587
|
+
break;
|
|
1588
|
+
case "failed":
|
|
1589
|
+
state.failed += 1;
|
|
1590
|
+
state.success = false;
|
|
1591
|
+
break;
|
|
1592
|
+
case "skipped":
|
|
1593
|
+
state.skipped += 1;
|
|
1594
|
+
break;
|
|
1595
|
+
case "pending":
|
|
1596
|
+
state.pending += 1;
|
|
1597
|
+
break;
|
|
1598
|
+
}
|
|
1599
|
+
reports.push(report);
|
|
1600
|
+
await dispatchTestResult({ result: report });
|
|
1601
|
+
}
|
|
1602
|
+
async function dispatchRunStart(event) {
|
|
1603
|
+
for (const reporter of reporters) {
|
|
1604
|
+
if (typeof reporter.onRunStart === "function") {
|
|
1605
|
+
await reporter.onRunStart(event);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
async function dispatchSuiteStart(event) {
|
|
1610
|
+
for (const reporter of reporters) {
|
|
1611
|
+
if (typeof reporter.onSuiteStart === "function") {
|
|
1612
|
+
await reporter.onSuiteStart(event);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
async function dispatchSuiteEnd(event) {
|
|
1617
|
+
for (const reporter of reporters) {
|
|
1618
|
+
if (typeof reporter.onSuiteEnd === "function") {
|
|
1619
|
+
await reporter.onSuiteEnd(event);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
async function dispatchTestResult(event) {
|
|
1624
|
+
for (const reporter of reporters) {
|
|
1625
|
+
if (typeof reporter.onTestResult === "function") {
|
|
1626
|
+
await reporter.onTestResult(event);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
async function dispatchRunEnd(event) {
|
|
1631
|
+
for (const reporter of reporters) {
|
|
1632
|
+
if (typeof reporter.onRunEnd === "function") {
|
|
1633
|
+
await reporter.onRunEnd(event);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
return {
|
|
1639
|
+
runtime,
|
|
1640
|
+
hookLogger,
|
|
1641
|
+
async execute() {
|
|
1642
|
+
return execute();
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
async function expandFilePatterns(patterns, cwd) {
|
|
1647
|
+
if (!patterns || patterns.length === 0) {
|
|
1648
|
+
return [];
|
|
1649
|
+
}
|
|
1650
|
+
const normalized = patterns.map(
|
|
1651
|
+
(pattern) => isAbsolute(pattern) ? pattern : resolve(cwd, pattern)
|
|
1652
|
+
);
|
|
1653
|
+
const entries = await fg(normalized, {
|
|
1654
|
+
cwd,
|
|
1655
|
+
absolute: true,
|
|
1656
|
+
onlyFiles: true,
|
|
1657
|
+
unique: true,
|
|
1658
|
+
followSymbolicLinks: true
|
|
1659
|
+
});
|
|
1660
|
+
return entries.sort((a, b) => a.localeCompare(b));
|
|
1661
|
+
}
|
|
1662
|
+
async function resolveCliCacheDir(cwd) {
|
|
1663
|
+
const envCacheDir = process.env.AUTOMETA_CACHE_DIR?.trim();
|
|
1664
|
+
if (envCacheDir) {
|
|
1665
|
+
return isAbsolute(envCacheDir) ? envCacheDir : resolve(cwd, envCacheDir);
|
|
1666
|
+
}
|
|
1667
|
+
const envHome = process.env.AUTOMETA_HOME?.trim();
|
|
1668
|
+
if (envHome) {
|
|
1669
|
+
const base = isAbsolute(envHome) ? envHome : resolve(cwd, envHome);
|
|
1670
|
+
return join(base, "cache");
|
|
1671
|
+
}
|
|
1672
|
+
const nodeModules = join(cwd, "node_modules");
|
|
1673
|
+
if (await pathExists(nodeModules)) {
|
|
1674
|
+
return join(nodeModules, ".cache", "autometa");
|
|
1675
|
+
}
|
|
1676
|
+
const osBase = resolveOsCacheBase();
|
|
1677
|
+
const projectKey = createHash("sha1").update(resolve(cwd)).digest("hex");
|
|
1678
|
+
return join(osBase, projectKey, "cache");
|
|
1679
|
+
}
|
|
1680
|
+
function resolveOsCacheBase() {
|
|
1681
|
+
const xdg = process.env.XDG_CACHE_HOME?.trim();
|
|
1682
|
+
if (xdg) {
|
|
1683
|
+
return join(xdg, "autometa");
|
|
1684
|
+
}
|
|
1685
|
+
if (process.platform === "darwin") {
|
|
1686
|
+
return join(homedir(), "Library", "Caches", "autometa");
|
|
1687
|
+
}
|
|
1688
|
+
if (process.platform === "win32") {
|
|
1689
|
+
const local = process.env.LOCALAPPDATA?.trim();
|
|
1690
|
+
if (local) {
|
|
1691
|
+
return join(local, "autometa", "Cache");
|
|
1692
|
+
}
|
|
1693
|
+
return join(homedir(), "AppData", "Local", "autometa", "Cache");
|
|
1694
|
+
}
|
|
1695
|
+
return join(homedir(), ".cache", "autometa");
|
|
1696
|
+
}
|
|
1697
|
+
async function pathExists(path3) {
|
|
1698
|
+
try {
|
|
1699
|
+
await access(path3, constants.F_OK);
|
|
1700
|
+
return true;
|
|
1701
|
+
} catch {
|
|
1702
|
+
return false;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
// src/loaders/module-loader.ts
|
|
1707
|
+
var TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".cts", ".mts"]);
|
|
1708
|
+
var BUILTIN_EXTERNALS2 = /* @__PURE__ */ new Set([
|
|
1709
|
+
...builtinModules$1,
|
|
1710
|
+
...builtinModules$1.map((moduleId) => `node:${moduleId}`)
|
|
1711
|
+
]);
|
|
1712
|
+
var DEFAULT_EXTERNALS2 = /* @__PURE__ */ new Set(["p-limit"]);
|
|
1713
|
+
async function loadModule(specifier, options) {
|
|
1714
|
+
if (isBareSpecifier(specifier)) {
|
|
1715
|
+
return import(specifier);
|
|
1716
|
+
}
|
|
1717
|
+
const resolvedPath = resolve$1(options.cwd, specifier);
|
|
1718
|
+
const filePath = await resolveFilePath(resolvedPath);
|
|
1719
|
+
const extension = extname$1(filePath).toLowerCase();
|
|
1720
|
+
if (TS_EXTENSIONS.has(extension)) {
|
|
1721
|
+
return loadTranspiledModule(filePath, options);
|
|
1722
|
+
}
|
|
1723
|
+
return import(pathToFileURL$1(filePath).href);
|
|
1724
|
+
}
|
|
1725
|
+
function isBareSpecifier(specifier) {
|
|
1726
|
+
if (!specifier) {
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
1730
|
+
return false;
|
|
1731
|
+
}
|
|
1732
|
+
return !isAbsolute$1(specifier);
|
|
1733
|
+
}
|
|
1734
|
+
async function resolveFilePath(pathCandidate) {
|
|
1735
|
+
if (await pathExists2(pathCandidate)) {
|
|
1736
|
+
return pathCandidate;
|
|
1737
|
+
}
|
|
1738
|
+
const directory = dirname(pathCandidate);
|
|
1739
|
+
const fileName = pathCandidate.slice(directory.length + 1);
|
|
1740
|
+
for (const extension of TS_EXTENSIONS) {
|
|
1741
|
+
const candidate = join$1(directory, `${fileName}${extension}`);
|
|
1742
|
+
if (await pathExists2(candidate)) {
|
|
1743
|
+
return candidate;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
throw new Error(`Unable to resolve module at ${pathCandidate}`);
|
|
1747
|
+
}
|
|
1748
|
+
async function loadTranspiledModule(filePath, options) {
|
|
1749
|
+
const cacheDirectory = options.cacheDir ?? await resolveCliCacheDir(options.cwd);
|
|
1750
|
+
await promises$1.mkdir(cacheDirectory, { recursive: true });
|
|
1751
|
+
const stats = await promises$1.stat(filePath);
|
|
1752
|
+
const cacheKey = createCacheKey(filePath, stats.mtimeMs);
|
|
1753
|
+
const outputFile = join$1(cacheDirectory, `${cacheKey}.cjs`);
|
|
1754
|
+
if (!await pathExists2(outputFile)) {
|
|
1755
|
+
const external = /* @__PURE__ */ new Set([
|
|
1756
|
+
...BUILTIN_EXTERNALS2,
|
|
1757
|
+
...DEFAULT_EXTERNALS2,
|
|
1758
|
+
...options.external ?? []
|
|
1759
|
+
]);
|
|
1760
|
+
const result = await build({
|
|
1761
|
+
entryPoints: [filePath],
|
|
1762
|
+
absWorkingDir: options.cwd,
|
|
1763
|
+
bundle: true,
|
|
1764
|
+
platform: "node",
|
|
1765
|
+
format: "cjs",
|
|
1766
|
+
target: "node20",
|
|
1767
|
+
sourcemap: "inline",
|
|
1768
|
+
write: false,
|
|
1769
|
+
external: Array.from(external)
|
|
1770
|
+
});
|
|
1771
|
+
const output = result.outputFiles?.find((file) => !file.path.endsWith(".map"));
|
|
1772
|
+
if (!output) {
|
|
1773
|
+
throw new Error(`Failed to compile module at ${filePath}`);
|
|
1774
|
+
}
|
|
1775
|
+
await promises$1.writeFile(outputFile, output.text, "utf8");
|
|
1776
|
+
}
|
|
1777
|
+
const moduleUrl = `${pathToFileURL$1(outputFile).href}?v=${cacheKey}`;
|
|
1778
|
+
return import(moduleUrl);
|
|
1779
|
+
}
|
|
1780
|
+
function createCacheKey(filePath, mtimeMs) {
|
|
1781
|
+
return createHash$1("sha1").update(filePath).update(String(mtimeMs)).digest("hex");
|
|
1782
|
+
}
|
|
1783
|
+
async function pathExists2(path3) {
|
|
1784
|
+
try {
|
|
1785
|
+
await promises$1.access(path3);
|
|
1786
|
+
return true;
|
|
1787
|
+
} catch {
|
|
1788
|
+
return false;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// src/loaders/config.ts
|
|
1793
|
+
var CONFIG_CANDIDATES = [
|
|
1794
|
+
"autometa.config.ts",
|
|
1795
|
+
"autometa.config.mts",
|
|
1796
|
+
"autometa.config.cts",
|
|
1797
|
+
"autometa.config.js",
|
|
1798
|
+
"autometa.config.mjs",
|
|
1799
|
+
"autometa.config.cjs"
|
|
1800
|
+
];
|
|
1801
|
+
async function loadExecutorConfig(cwd, options = {}) {
|
|
1802
|
+
const configPath = await resolveConfigPath(cwd, options.configPath);
|
|
1803
|
+
const module = await loadModule(configPath, createModuleLoadOptions(cwd, options));
|
|
1804
|
+
const config = extractConfig(module);
|
|
1805
|
+
if (!config) {
|
|
1806
|
+
throw new Error(
|
|
1807
|
+
`Failed to load Autometa config from "${configPath}". Ensure the module exports a Config instance (e.g. export default defineConfig({...})).`
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
const resolveOptions = {
|
|
1811
|
+
...options.environment && options.environment.trim().length > 0 ? { environment: options.environment } : {},
|
|
1812
|
+
...options.modules && options.modules.length > 0 ? { modules: options.modules } : {},
|
|
1813
|
+
...options.groups && options.groups.length > 0 ? { groups: options.groups } : {}
|
|
1814
|
+
};
|
|
1815
|
+
const resolved = config.resolve(
|
|
1816
|
+
Object.keys(resolveOptions).length > 0 ? resolveOptions : void 0
|
|
1817
|
+
);
|
|
1818
|
+
return {
|
|
1819
|
+
filePath: configPath,
|
|
1820
|
+
config,
|
|
1821
|
+
resolved
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
async function resolveConfigPath(cwd, explicitPath) {
|
|
1825
|
+
if (explicitPath) {
|
|
1826
|
+
const absolutePath = resolve(cwd, explicitPath);
|
|
1827
|
+
await ensureFileExists(absolutePath);
|
|
1828
|
+
return absolutePath;
|
|
1829
|
+
}
|
|
1830
|
+
for (const candidate of CONFIG_CANDIDATES) {
|
|
1831
|
+
const absoluteCandidate = resolve(cwd, candidate);
|
|
1832
|
+
if (await fileExists(absoluteCandidate)) {
|
|
1833
|
+
return absoluteCandidate;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
throw new Error(
|
|
1837
|
+
`Unable to locate an Autometa config file in "${cwd}". Expected one of: ${CONFIG_CANDIDATES.join(
|
|
1838
|
+
", "
|
|
1839
|
+
)}`
|
|
1840
|
+
);
|
|
1841
|
+
}
|
|
1842
|
+
async function ensureFileExists(path3) {
|
|
1843
|
+
try {
|
|
1844
|
+
await access(path3, constants.F_OK);
|
|
1845
|
+
} catch {
|
|
1846
|
+
throw new Error(`Config file not found at "${path3}"`);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
async function fileExists(path3) {
|
|
1850
|
+
try {
|
|
1851
|
+
await access(path3, constants.F_OK);
|
|
1852
|
+
return true;
|
|
1853
|
+
} catch {
|
|
1854
|
+
return false;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
function createModuleLoadOptions(cwd, options) {
|
|
1858
|
+
return {
|
|
1859
|
+
cwd,
|
|
1860
|
+
...options.cacheDir ? { cacheDir: options.cacheDir } : {},
|
|
1861
|
+
...options.external ? { external: options.external } : {}
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
function extractConfig(exports) {
|
|
1865
|
+
const candidates = [];
|
|
1866
|
+
if (exports.default !== void 0) {
|
|
1867
|
+
candidates.push(exports.default);
|
|
1868
|
+
}
|
|
1869
|
+
for (const value of Object.values(exports)) {
|
|
1870
|
+
candidates.push(value);
|
|
1871
|
+
}
|
|
1872
|
+
for (const candidate of candidates) {
|
|
1873
|
+
const config = asConfig(candidate);
|
|
1874
|
+
if (config) {
|
|
1875
|
+
return config;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
return void 0;
|
|
1879
|
+
}
|
|
1880
|
+
function asConfig(value) {
|
|
1881
|
+
if (!value || typeof value !== "object" && typeof value !== "function") {
|
|
1882
|
+
return void 0;
|
|
1883
|
+
}
|
|
1884
|
+
if (value instanceof Config) {
|
|
1885
|
+
return value;
|
|
1886
|
+
}
|
|
1887
|
+
const maybeConfig = value;
|
|
1888
|
+
if (typeof maybeConfig.resolve === "function" && typeof maybeConfig.current === "function") {
|
|
1889
|
+
return maybeConfig;
|
|
1890
|
+
}
|
|
1891
|
+
const maybeDefault = value.default;
|
|
1892
|
+
if (maybeDefault && maybeDefault !== value) {
|
|
1893
|
+
return asConfig(maybeDefault);
|
|
1894
|
+
}
|
|
1895
|
+
return void 0;
|
|
1896
|
+
}
|
|
1897
|
+
function formatDuration(ms) {
|
|
1898
|
+
if (ms < 1) {
|
|
1899
|
+
return `${ms.toFixed(2)} ms`;
|
|
1900
|
+
}
|
|
1901
|
+
if (ms < 1e3) {
|
|
1902
|
+
return `${ms.toFixed(0)} ms`;
|
|
1903
|
+
}
|
|
1904
|
+
return `${(ms / 1e3).toFixed(2)} s`;
|
|
1905
|
+
}
|
|
1906
|
+
function formatSummary(summary, context) {
|
|
1907
|
+
const parts = [`Environment: ${pc5.cyan(context.environment)}`];
|
|
1908
|
+
parts.push(`Total: ${pc5.bold(String(summary.total))}`);
|
|
1909
|
+
if (summary.passed > 0) {
|
|
1910
|
+
parts.push(`Passed: ${pc5.green(String(summary.passed))}`);
|
|
1911
|
+
}
|
|
1912
|
+
if (summary.failed > 0) {
|
|
1913
|
+
parts.push(`Failed: ${pc5.red(String(summary.failed))}`);
|
|
1914
|
+
}
|
|
1915
|
+
if (summary.skipped > 0) {
|
|
1916
|
+
parts.push(`Skipped: ${pc5.yellow(String(summary.skipped))}`);
|
|
1917
|
+
}
|
|
1918
|
+
if (summary.pending > 0) {
|
|
1919
|
+
parts.push(`Pending: ${pc5.cyan(String(summary.pending))}`);
|
|
1920
|
+
}
|
|
1921
|
+
parts.push(`Duration: ${pc5.dim(formatDuration(summary.durationMs))}`);
|
|
1922
|
+
return parts.join(" | ");
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
// src/utils/handover.ts
|
|
1926
|
+
function extractArgsAfterDoubleDash(rawArgv) {
|
|
1927
|
+
const idx = rawArgv.indexOf("--");
|
|
1928
|
+
if (idx < 0) {
|
|
1929
|
+
return [];
|
|
1930
|
+
}
|
|
1931
|
+
return rawArgv.slice(idx + 1);
|
|
1932
|
+
}
|
|
1933
|
+
function stripTrailingArgs(all, suffix) {
|
|
1934
|
+
if (suffix.length === 0) {
|
|
1935
|
+
return [...all];
|
|
1936
|
+
}
|
|
1937
|
+
if (suffix.length > all.length) {
|
|
1938
|
+
return [...all];
|
|
1939
|
+
}
|
|
1940
|
+
const start = all.length - suffix.length;
|
|
1941
|
+
for (let i = 0; i < suffix.length; i += 1) {
|
|
1942
|
+
if (all[start + i] !== suffix[i]) {
|
|
1943
|
+
return [...all];
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
return all.slice(0, start);
|
|
1947
|
+
}
|
|
1948
|
+
function splitPatternsAndRunnerArgs(options) {
|
|
1949
|
+
const runnerArgs = options.handover ? extractArgsAfterDoubleDash(options.rawArgv) : [];
|
|
1950
|
+
const patterns = runnerArgs.length > 0 ? stripTrailingArgs(options.patterns, runnerArgs) : [...options.patterns];
|
|
1951
|
+
return { patterns, runnerArgs };
|
|
1952
|
+
}
|
|
1953
|
+
function detectRunner(config, cwd) {
|
|
1954
|
+
if (config.runner === "vitest") {
|
|
1955
|
+
return "vitest";
|
|
1956
|
+
}
|
|
1957
|
+
if (config.runner === "jest") {
|
|
1958
|
+
return "jest";
|
|
1959
|
+
}
|
|
1960
|
+
if (config.runner === "playwright") {
|
|
1961
|
+
return "playwright";
|
|
1962
|
+
}
|
|
1963
|
+
if (existsSync(join(cwd, "vitest.config.ts")) || existsSync(join(cwd, "vitest.config.js"))) {
|
|
1964
|
+
return "vitest";
|
|
1965
|
+
}
|
|
1966
|
+
if (existsSync(join(cwd, "jest.config.js")) || existsSync(join(cwd, "jest.config.cjs")) || existsSync(join(cwd, "jest.config.ts"))) {
|
|
1967
|
+
return "jest";
|
|
1968
|
+
}
|
|
1969
|
+
if (existsSync(join(cwd, "playwright.config.ts")) || existsSync(join(cwd, "playwright.config.js"))) {
|
|
1970
|
+
return "playwright";
|
|
1971
|
+
}
|
|
1972
|
+
return "default";
|
|
1973
|
+
}
|
|
1974
|
+
async function orchestrate(options) {
|
|
1975
|
+
const { cwd, config, patterns = [], runnerArgs = [], dryRun = false, watch = false, verbose = false } = options;
|
|
1976
|
+
const runner = detectRunner(config, cwd);
|
|
1977
|
+
if (verbose) {
|
|
1978
|
+
console.log(`[autometa] Detected runner: ${runner}`);
|
|
1979
|
+
}
|
|
1980
|
+
switch (runner) {
|
|
1981
|
+
case "vitest":
|
|
1982
|
+
return spawnVitest({ cwd, patterns, runnerArgs, dryRun, watch, verbose });
|
|
1983
|
+
case "jest":
|
|
1984
|
+
return spawnJest({ cwd, patterns, runnerArgs, dryRun, watch, verbose });
|
|
1985
|
+
case "playwright":
|
|
1986
|
+
return spawnPlaywright({ cwd, patterns, runnerArgs, dryRun, watch, verbose });
|
|
1987
|
+
case "default":
|
|
1988
|
+
return { success: true, exitCode: 0, runner: "default" };
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
async function spawnVitest(options) {
|
|
1992
|
+
const { cwd, patterns, runnerArgs, dryRun, watch, verbose } = options;
|
|
1993
|
+
const args = [];
|
|
1994
|
+
if (!watch) {
|
|
1995
|
+
args.push("run");
|
|
1996
|
+
}
|
|
1997
|
+
if (patterns.length > 0) {
|
|
1998
|
+
args.push(...patterns);
|
|
1999
|
+
}
|
|
2000
|
+
if (dryRun) {
|
|
2001
|
+
args.push("--passWithNoTests");
|
|
2002
|
+
}
|
|
2003
|
+
if (runnerArgs.length > 0) {
|
|
2004
|
+
args.push(...runnerArgs);
|
|
2005
|
+
}
|
|
2006
|
+
if (verbose) {
|
|
2007
|
+
console.log(`[autometa] Running: vitest ${args.join(" ")}`);
|
|
2008
|
+
}
|
|
2009
|
+
const result = await spawnRunner("vitest", args, { cwd });
|
|
2010
|
+
return { ...result, runner: "vitest" };
|
|
2011
|
+
}
|
|
2012
|
+
async function spawnJest(options) {
|
|
2013
|
+
const { cwd, patterns, runnerArgs, dryRun, watch, verbose } = options;
|
|
2014
|
+
const args = [];
|
|
2015
|
+
if (watch) {
|
|
2016
|
+
args.push("--watch");
|
|
2017
|
+
}
|
|
2018
|
+
if (patterns.length > 0) {
|
|
2019
|
+
args.push(...patterns);
|
|
2020
|
+
}
|
|
2021
|
+
if (dryRun) {
|
|
2022
|
+
args.push("--listTests");
|
|
2023
|
+
}
|
|
2024
|
+
if (runnerArgs.length > 0) {
|
|
2025
|
+
args.push(...runnerArgs);
|
|
2026
|
+
}
|
|
2027
|
+
if (verbose) {
|
|
2028
|
+
console.log(`[autometa] Running: jest ${args.join(" ")}`);
|
|
2029
|
+
}
|
|
2030
|
+
const result = await spawnRunner("jest", args, { cwd });
|
|
2031
|
+
return { ...result, runner: "jest" };
|
|
2032
|
+
}
|
|
2033
|
+
async function spawnPlaywright(options) {
|
|
2034
|
+
const { cwd, patterns, runnerArgs, dryRun, watch, verbose } = options;
|
|
2035
|
+
const args = ["test"];
|
|
2036
|
+
if (patterns.length > 0) {
|
|
2037
|
+
args.push(...patterns);
|
|
2038
|
+
}
|
|
2039
|
+
if (dryRun) {
|
|
2040
|
+
args.push("--list");
|
|
2041
|
+
}
|
|
2042
|
+
if (watch) {
|
|
2043
|
+
args.push("--ui");
|
|
2044
|
+
}
|
|
2045
|
+
if (runnerArgs.length > 0) {
|
|
2046
|
+
args.push(...runnerArgs);
|
|
2047
|
+
}
|
|
2048
|
+
if (verbose) {
|
|
2049
|
+
console.log(`[autometa] Running: playwright ${args.join(" ")}`);
|
|
2050
|
+
}
|
|
2051
|
+
const loaderImportFlag = "--import @autometa/playwright-loader/register";
|
|
2052
|
+
const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
|
|
2053
|
+
const hasLoaderFlag = existingNodeOptions.includes(loaderImportFlag);
|
|
2054
|
+
const nodeOptions = hasLoaderFlag ? existingNodeOptions : [existingNodeOptions, loaderImportFlag].filter(Boolean).join(" ").trim();
|
|
2055
|
+
const result = await spawnRunner("playwright", args, {
|
|
2056
|
+
cwd,
|
|
2057
|
+
env: {
|
|
2058
|
+
NODE_OPTIONS: nodeOptions
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
return { ...result, runner: "playwright" };
|
|
2062
|
+
}
|
|
2063
|
+
function spawnRunner(command, args, options) {
|
|
2064
|
+
return new Promise((resolve4) => {
|
|
2065
|
+
const spawnOptions = {
|
|
2066
|
+
cwd: options.cwd,
|
|
2067
|
+
stdio: "inherit",
|
|
2068
|
+
shell: true,
|
|
2069
|
+
env: {
|
|
2070
|
+
...process.env,
|
|
2071
|
+
FORCE_COLOR: process.env.FORCE_COLOR ?? "1",
|
|
2072
|
+
...options.env ?? {}
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
const binPath = join(options.cwd, "node_modules", ".bin", command);
|
|
2076
|
+
const actualCommand = existsSync(binPath) ? binPath : command;
|
|
2077
|
+
const child = spawn(actualCommand, args, spawnOptions);
|
|
2078
|
+
child.on("close", (code) => {
|
|
2079
|
+
const exitCode = code ?? 0;
|
|
2080
|
+
resolve4({
|
|
2081
|
+
success: exitCode === 0,
|
|
2082
|
+
exitCode
|
|
2083
|
+
});
|
|
2084
|
+
});
|
|
2085
|
+
child.on("error", (error) => {
|
|
2086
|
+
console.error(`[autometa] Failed to spawn ${command}:`, error.message);
|
|
2087
|
+
resolve4({
|
|
2088
|
+
success: false,
|
|
2089
|
+
exitCode: 1
|
|
2090
|
+
});
|
|
2091
|
+
});
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// src/commands/run.ts
|
|
2096
|
+
var STEP_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
|
|
2097
|
+
var STEP_FALLBACK_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}";
|
|
2098
|
+
var FEATURE_FALLBACK_GLOB = "**/*.feature";
|
|
2099
|
+
var ROOT_LOAD_ORDER = ["parameterTypes", "support", "hooks", "app"];
|
|
2100
|
+
function normalizeGlobLikePath(input) {
|
|
2101
|
+
return input.replace(/\\/g, "/").replace(/^\.\//u, "");
|
|
2102
|
+
}
|
|
2103
|
+
function patternIsUnderRoot(pattern, root) {
|
|
2104
|
+
const p = normalizeGlobLikePath(pattern);
|
|
2105
|
+
const r = normalizeGlobLikePath(root).replace(/\/+$/u, "");
|
|
2106
|
+
if (!r) {
|
|
2107
|
+
return false;
|
|
2108
|
+
}
|
|
2109
|
+
return p === r || p.startsWith(`${r}/`);
|
|
2110
|
+
}
|
|
2111
|
+
function flattenModuleDeclarations(declarations, prefix = []) {
|
|
2112
|
+
if (!declarations || declarations.length === 0) {
|
|
2113
|
+
return [];
|
|
2114
|
+
}
|
|
2115
|
+
const results = [];
|
|
2116
|
+
for (const entry of declarations) {
|
|
2117
|
+
if (typeof entry === "string") {
|
|
2118
|
+
const next2 = [...prefix, entry];
|
|
2119
|
+
results.push(next2);
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
const next = [...prefix, entry.name];
|
|
2123
|
+
results.push(next);
|
|
2124
|
+
const nested = flattenModuleDeclarations(entry.submodules ?? void 0, next);
|
|
2125
|
+
for (const path3 of nested) {
|
|
2126
|
+
results.push([...path3]);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
const unique = /* @__PURE__ */ new Map();
|
|
2130
|
+
for (const path3 of results) {
|
|
2131
|
+
unique.set(path3.join("/"), path3);
|
|
2132
|
+
}
|
|
2133
|
+
return Array.from(unique.values()).sort((a, b) => b.length - a.length);
|
|
2134
|
+
}
|
|
2135
|
+
function buildGroupIndex(config, cwd) {
|
|
2136
|
+
const groups = config.modules?.groups;
|
|
2137
|
+
if (!groups) {
|
|
2138
|
+
return [];
|
|
2139
|
+
}
|
|
2140
|
+
return Object.entries(groups).map(([group, groupConfig]) => {
|
|
2141
|
+
const rootAbs = resolve(cwd, groupConfig.root);
|
|
2142
|
+
const modulePaths = flattenModuleDeclarations(groupConfig.modules);
|
|
2143
|
+
return { group, rootAbs, modulePaths };
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
function normalizePathSegments(input) {
|
|
2147
|
+
const normalized = input.replace(/\\/g, "/");
|
|
2148
|
+
return normalized.split("/").filter(Boolean);
|
|
2149
|
+
}
|
|
2150
|
+
function isPathUnderRoot(fileAbs, rootAbs) {
|
|
2151
|
+
const rel = relative(rootAbs, fileAbs);
|
|
2152
|
+
if (rel === "") {
|
|
2153
|
+
return true;
|
|
2154
|
+
}
|
|
2155
|
+
return !rel.startsWith("..") && !rel.startsWith("../") && !rel.startsWith("..\\");
|
|
2156
|
+
}
|
|
2157
|
+
function startsWithSegments(haystack, needle) {
|
|
2158
|
+
if (needle.length > haystack.length) {
|
|
2159
|
+
return false;
|
|
2160
|
+
}
|
|
2161
|
+
for (let i = 0; i < needle.length; i += 1) {
|
|
2162
|
+
if (haystack[i] !== needle[i]) {
|
|
2163
|
+
return false;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
return true;
|
|
2167
|
+
}
|
|
2168
|
+
function resolveFileScope(fileAbs, groupIndex) {
|
|
2169
|
+
if (fileAbs.startsWith("node:")) {
|
|
2170
|
+
return { kind: "root" };
|
|
2171
|
+
}
|
|
2172
|
+
for (const entry of groupIndex) {
|
|
2173
|
+
if (!isPathUnderRoot(fileAbs, entry.rootAbs)) {
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
const rel = relative(entry.rootAbs, fileAbs);
|
|
2177
|
+
const segments = normalizePathSegments(rel);
|
|
2178
|
+
for (const modulePath of entry.modulePaths) {
|
|
2179
|
+
if (startsWithSegments(segments, modulePath)) {
|
|
2180
|
+
return {
|
|
2181
|
+
kind: "module",
|
|
2182
|
+
group: entry.group,
|
|
2183
|
+
modulePath
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return {
|
|
2188
|
+
kind: "group",
|
|
2189
|
+
group: entry.group
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
return { kind: "root" };
|
|
2193
|
+
}
|
|
2194
|
+
function parseScopeOverrideTag(tags) {
|
|
2195
|
+
if (!tags || tags.length === 0) {
|
|
2196
|
+
return void 0;
|
|
2197
|
+
}
|
|
2198
|
+
for (const tag of tags) {
|
|
2199
|
+
const match = tag.match(/^@scope(?::|=|\()(.+?)(?:\))?$/u);
|
|
2200
|
+
if (!match) {
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
const raw = (match[1] ?? "").trim();
|
|
2204
|
+
if (!raw) {
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
const normalized = raw.replace(/\//g, ":");
|
|
2208
|
+
const parts = normalized.split(":").filter(Boolean);
|
|
2209
|
+
const [group, ...rest] = parts;
|
|
2210
|
+
if (!group) {
|
|
2211
|
+
continue;
|
|
2212
|
+
}
|
|
2213
|
+
return rest.length > 0 ? { group, modulePath: rest } : { group };
|
|
2214
|
+
}
|
|
2215
|
+
return void 0;
|
|
2216
|
+
}
|
|
2217
|
+
function resolveFeatureScope(featureAbsPath, feature, groupIndex) {
|
|
2218
|
+
const override = parseScopeOverrideTag(feature.tags);
|
|
2219
|
+
if (override) {
|
|
2220
|
+
if (override.modulePath && override.modulePath.length > 0) {
|
|
2221
|
+
return { kind: "module", group: override.group, modulePath: override.modulePath };
|
|
2222
|
+
}
|
|
2223
|
+
return { kind: "group", group: override.group };
|
|
2224
|
+
}
|
|
2225
|
+
return resolveFileScope(featureAbsPath, groupIndex);
|
|
2226
|
+
}
|
|
2227
|
+
function isVisibleStepScope(stepScope, featureScope) {
|
|
2228
|
+
if (featureScope.kind === "root") {
|
|
2229
|
+
return stepScope.kind === "root";
|
|
2230
|
+
}
|
|
2231
|
+
if (featureScope.kind === "group") {
|
|
2232
|
+
if (stepScope.kind === "root") {
|
|
2233
|
+
return true;
|
|
2234
|
+
}
|
|
2235
|
+
return stepScope.kind === "group" && stepScope.group === featureScope.group;
|
|
2236
|
+
}
|
|
2237
|
+
if (stepScope.kind === "root") {
|
|
2238
|
+
return true;
|
|
2239
|
+
}
|
|
2240
|
+
if (stepScope.kind === "group") {
|
|
2241
|
+
return stepScope.group === featureScope.group;
|
|
2242
|
+
}
|
|
2243
|
+
if (stepScope.kind === "module") {
|
|
2244
|
+
return stepScope.group === featureScope.group && startsWithSegments(featureScope.modulePath, stepScope.modulePath);
|
|
2245
|
+
}
|
|
2246
|
+
return false;
|
|
2247
|
+
}
|
|
2248
|
+
function stepScopeRank(scope) {
|
|
2249
|
+
switch (scope.kind) {
|
|
2250
|
+
case "module":
|
|
2251
|
+
return 200 + scope.modulePath.length;
|
|
2252
|
+
case "group":
|
|
2253
|
+
return 100;
|
|
2254
|
+
default:
|
|
2255
|
+
return 0;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
function collectRepeatedString(value, previous) {
|
|
2259
|
+
return [...previous, value];
|
|
2260
|
+
}
|
|
2261
|
+
function registerRunCommand(program) {
|
|
2262
|
+
return program.command("run").description("Execute Autometa feature files").argument("[patterns...]", "Feature files or glob patterns").option(
|
|
2263
|
+
"--handover",
|
|
2264
|
+
"Pass through runner args after '--' directly to the detected native runner (vitest/jest/playwright)"
|
|
2265
|
+
).option(
|
|
2266
|
+
"--cache-dir <dir>",
|
|
2267
|
+
"Directory for Autometa CLI cache (defaults to node_modules/.cache/autometa when available)"
|
|
2268
|
+
).option("--dry-run", "Collect scenarios without executing steps").option("--watch", "Run in watch mode (vitest/jest only)").option("--verbose", "Show detailed output including runner detection").option("--standalone", "Force standalone runtime instead of native runner").option("-e, --environment <environment>", "Select config environment").option(
|
|
2269
|
+
"-g, --group <group>",
|
|
2270
|
+
"Filter module groups to include (affects module/step loading; patterns are not auto-scoped)",
|
|
2271
|
+
collectRepeatedString,
|
|
2272
|
+
[]
|
|
2273
|
+
).option(
|
|
2274
|
+
"-m, --module <module>",
|
|
2275
|
+
"Filter modules to include (by id or unambiguous suffix; affects module/step loading; patterns are not auto-scoped)",
|
|
2276
|
+
collectRepeatedString,
|
|
2277
|
+
[]
|
|
2278
|
+
).action(async (patterns, flags) => {
|
|
2279
|
+
try {
|
|
2280
|
+
const split = flags?.handover === true ? splitPatternsAndRunnerArgs({ patterns, rawArgv: process.argv, handover: true }) : splitPatternsAndRunnerArgs({ patterns, rawArgv: process.argv });
|
|
2281
|
+
const summary = await runFeatures({
|
|
2282
|
+
cwd: process.cwd(),
|
|
2283
|
+
...typeof flags?.cacheDir === "string" && flags.cacheDir.trim().length > 0 ? { cacheDir: flags.cacheDir } : {},
|
|
2284
|
+
...split.patterns.length > 0 ? { patterns: split.patterns } : {},
|
|
2285
|
+
...split.runnerArgs.length > 0 ? { runnerArgs: split.runnerArgs } : {},
|
|
2286
|
+
...typeof flags?.dryRun === "boolean" ? { dryRun: flags.dryRun } : {},
|
|
2287
|
+
...typeof flags?.watch === "boolean" ? { watch: flags.watch } : {},
|
|
2288
|
+
...typeof flags?.verbose === "boolean" ? { verbose: flags.verbose } : {},
|
|
2289
|
+
...flags?.standalone ? { mode: "standalone" } : {},
|
|
2290
|
+
...typeof flags?.environment === "string" && flags.environment.trim().length > 0 ? { environment: flags.environment } : {},
|
|
2291
|
+
...Array.isArray(flags?.group) && flags.group.length > 0 ? { groups: flags.group } : {},
|
|
2292
|
+
...Array.isArray(flags?.module) && flags.module.length > 0 ? { modules: flags.module } : {}
|
|
2293
|
+
});
|
|
2294
|
+
if (!summary.success) {
|
|
2295
|
+
process.exitCode = 1;
|
|
2296
|
+
}
|
|
2297
|
+
} catch (error) {
|
|
2298
|
+
const output = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
2299
|
+
console.error(output);
|
|
2300
|
+
process.exitCode = 1;
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
function isScenario(element) {
|
|
2305
|
+
return "steps" in element && !("exampleGroups" in element) && !("elements" in element);
|
|
2306
|
+
}
|
|
2307
|
+
function isScenarioOutline(element) {
|
|
2308
|
+
return "steps" in element && "exampleGroups" in element;
|
|
2309
|
+
}
|
|
2310
|
+
function isRule(element) {
|
|
2311
|
+
return "elements" in element && Array.isArray(element.elements);
|
|
2312
|
+
}
|
|
2313
|
+
function createFeatureScopePlan(feature, basePlan, options) {
|
|
2314
|
+
const scopingMode = options.config.modules?.stepScoping ?? "global";
|
|
2315
|
+
const useScopedSteps = scopingMode === "scoped" && options.groupIndex.length > 0;
|
|
2316
|
+
const allSteps = Array.from(basePlan.stepsById.values());
|
|
2317
|
+
const featureFileScope = useScopedSteps ? resolveFeatureScope(options.featureAbsPath, feature, options.groupIndex) : { kind: "root" };
|
|
2318
|
+
const visibleSteps = useScopedSteps ? allSteps.filter((definition) => {
|
|
2319
|
+
const file = definition.source?.file;
|
|
2320
|
+
const stepScope = file ? resolveFileScope(resolve(options.cwd, file), options.groupIndex) : { kind: "root" };
|
|
2321
|
+
return isVisibleStepScope(stepScope, featureFileScope);
|
|
2322
|
+
}).sort((a, b) => {
|
|
2323
|
+
const aFile = a.source?.file;
|
|
2324
|
+
const bFile = b.source?.file;
|
|
2325
|
+
const aScope = aFile ? resolveFileScope(resolve(options.cwd, aFile), options.groupIndex) : { kind: "root" };
|
|
2326
|
+
const bScope = bFile ? resolveFileScope(resolve(options.cwd, bFile), options.groupIndex) : { kind: "root" };
|
|
2327
|
+
const delta = stepScopeRank(bScope) - stepScopeRank(aScope);
|
|
2328
|
+
return delta !== 0 ? delta : a.id.localeCompare(b.id);
|
|
2329
|
+
}) : allSteps;
|
|
2330
|
+
const scopedStepsById = useScopedSteps ? (() => {
|
|
2331
|
+
const allowed = new Set(visibleSteps.map((step) => step.id));
|
|
2332
|
+
const next = /* @__PURE__ */ new Map();
|
|
2333
|
+
for (const [id, def] of basePlan.stepsById.entries()) {
|
|
2334
|
+
if (allowed.has(id)) {
|
|
2335
|
+
next.set(id, def);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
return next;
|
|
2339
|
+
})() : basePlan.stepsById;
|
|
2340
|
+
const featureChildren = [];
|
|
2341
|
+
const scopesById = new Map(basePlan.scopesById);
|
|
2342
|
+
for (const element of feature.elements ?? []) {
|
|
2343
|
+
if (isScenario(element) || isScenarioOutline(element)) {
|
|
2344
|
+
const scenarioScope = {
|
|
2345
|
+
id: element.id ?? element.name,
|
|
2346
|
+
kind: isScenarioOutline(element) ? "scenarioOutline" : "scenario",
|
|
2347
|
+
name: element.name,
|
|
2348
|
+
mode: "default",
|
|
2349
|
+
tags: element.tags ?? [],
|
|
2350
|
+
steps: visibleSteps,
|
|
2351
|
+
hooks: [],
|
|
2352
|
+
children: [],
|
|
2353
|
+
pending: false
|
|
2354
|
+
};
|
|
2355
|
+
featureChildren.push(scenarioScope);
|
|
2356
|
+
scopesById.set(scenarioScope.id, scenarioScope);
|
|
2357
|
+
} else if (isRule(element)) {
|
|
2358
|
+
const ruleChildren = [];
|
|
2359
|
+
for (const ruleElement of element.elements ?? []) {
|
|
2360
|
+
if (isScenario(ruleElement) || isScenarioOutline(ruleElement)) {
|
|
2361
|
+
const scenarioScope = {
|
|
2362
|
+
id: ruleElement.id ?? ruleElement.name,
|
|
2363
|
+
kind: isScenarioOutline(ruleElement) ? "scenarioOutline" : "scenario",
|
|
2364
|
+
name: ruleElement.name,
|
|
2365
|
+
mode: "default",
|
|
2366
|
+
tags: ruleElement.tags ?? [],
|
|
2367
|
+
steps: visibleSteps,
|
|
2368
|
+
hooks: [],
|
|
2369
|
+
children: [],
|
|
2370
|
+
pending: false
|
|
2371
|
+
};
|
|
2372
|
+
ruleChildren.push(scenarioScope);
|
|
2373
|
+
scopesById.set(scenarioScope.id, scenarioScope);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
const ruleScope = {
|
|
2377
|
+
id: element.id ?? element.name,
|
|
2378
|
+
kind: "rule",
|
|
2379
|
+
name: element.name,
|
|
2380
|
+
mode: "default",
|
|
2381
|
+
tags: element.tags ?? [],
|
|
2382
|
+
steps: visibleSteps,
|
|
2383
|
+
hooks: [],
|
|
2384
|
+
children: ruleChildren,
|
|
2385
|
+
pending: false
|
|
2386
|
+
};
|
|
2387
|
+
featureChildren.push(ruleScope);
|
|
2388
|
+
scopesById.set(ruleScope.id, ruleScope);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
const featureScope = {
|
|
2392
|
+
id: feature.uri ?? feature.name,
|
|
2393
|
+
kind: "feature",
|
|
2394
|
+
name: feature.name,
|
|
2395
|
+
mode: "default",
|
|
2396
|
+
tags: feature.tags ?? [],
|
|
2397
|
+
steps: visibleSteps,
|
|
2398
|
+
hooks: [],
|
|
2399
|
+
children: featureChildren,
|
|
2400
|
+
pending: false
|
|
2401
|
+
};
|
|
2402
|
+
const existingRoot = basePlan.root;
|
|
2403
|
+
const updatedRoot = {
|
|
2404
|
+
...existingRoot,
|
|
2405
|
+
children: [...existingRoot.children, featureScope]
|
|
2406
|
+
};
|
|
2407
|
+
scopesById.set(featureScope.id, featureScope);
|
|
2408
|
+
scopesById.set(updatedRoot.id, updatedRoot);
|
|
2409
|
+
const scopePlan = {
|
|
2410
|
+
root: updatedRoot,
|
|
2411
|
+
stepsById: scopedStepsById,
|
|
2412
|
+
hooksById: basePlan.hooksById,
|
|
2413
|
+
scopesById
|
|
2414
|
+
};
|
|
2415
|
+
if (basePlan.worldFactory) {
|
|
2416
|
+
scopePlan.worldFactory = basePlan.worldFactory;
|
|
2417
|
+
}
|
|
2418
|
+
if (basePlan.parameterRegistry) {
|
|
2419
|
+
scopePlan.parameterRegistry = basePlan.parameterRegistry;
|
|
2420
|
+
}
|
|
2421
|
+
return scopePlan;
|
|
2422
|
+
}
|
|
2423
|
+
async function runFeatures(options = {}) {
|
|
2424
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2425
|
+
const cacheDir = options.cacheDir ?? await resolveCliCacheDir(cwd);
|
|
2426
|
+
const summaryFormatter = options.summaryFormatter ?? formatSummary;
|
|
2427
|
+
const { resolved } = await loadExecutorConfig(cwd, {
|
|
2428
|
+
cacheDir,
|
|
2429
|
+
...typeof options.environment === "string" && options.environment.trim().length > 0 ? { environment: options.environment } : {},
|
|
2430
|
+
...options.modules ? { modules: [...options.modules] } : {},
|
|
2431
|
+
...options.groups ? { groups: [...options.groups] } : {}
|
|
2432
|
+
});
|
|
2433
|
+
const executorConfig = resolved.config;
|
|
2434
|
+
const mode = options.mode ?? "native";
|
|
2435
|
+
if (mode !== "standalone") {
|
|
2436
|
+
const orchestratorResult = await orchestrate({
|
|
2437
|
+
cwd,
|
|
2438
|
+
config: executorConfig,
|
|
2439
|
+
...options.patterns ? { patterns: options.patterns } : {},
|
|
2440
|
+
...options.runnerArgs ? { runnerArgs: options.runnerArgs } : {},
|
|
2441
|
+
...options.dryRun !== void 0 ? { dryRun: options.dryRun } : {},
|
|
2442
|
+
...options.watch !== void 0 ? { watch: options.watch } : {},
|
|
2443
|
+
...options.verbose !== void 0 ? { verbose: options.verbose } : {}
|
|
2444
|
+
});
|
|
2445
|
+
if (orchestratorResult.runner !== "default") {
|
|
2446
|
+
return {
|
|
2447
|
+
success: orchestratorResult.success,
|
|
2448
|
+
total: 0,
|
|
2449
|
+
// Native runner handles its own reporting
|
|
2450
|
+
passed: 0,
|
|
2451
|
+
failed: 0,
|
|
2452
|
+
skipped: 0,
|
|
2453
|
+
pending: 0,
|
|
2454
|
+
durationMs: 0,
|
|
2455
|
+
scenarios: []
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
if (options.verbose) {
|
|
2459
|
+
console.log("[autometa] Using standalone runtime");
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
const hierarchicalBufferOutput = executorConfig.reporting?.hierarchical?.bufferOutput;
|
|
2463
|
+
const reporterOptions = hierarchicalBufferOutput !== void 0 ? { hierarchical: { bufferOutput: hierarchicalBufferOutput } } : void 0;
|
|
2464
|
+
const runtimeOptions = {
|
|
2465
|
+
...typeof options.dryRun === "boolean" ? { dryRun: options.dryRun } : {},
|
|
2466
|
+
...options.reporters ? { reporters: options.reporters } : {},
|
|
2467
|
+
...reporterOptions ? { reporter: reporterOptions } : {}
|
|
2468
|
+
};
|
|
2469
|
+
const { runtime, hookLogger, execute } = createCliRuntime(runtimeOptions);
|
|
2470
|
+
configureHttpLogging(executorConfig.logging);
|
|
2471
|
+
const hasModuleSelection = (options.groups?.length ?? 0) > 0 || (options.modules?.length ?? 0) > 0;
|
|
2472
|
+
const hasExplicitPatterns = (options.patterns?.length ?? 0) > 0;
|
|
2473
|
+
if (hasExplicitPatterns && hasModuleSelection) {
|
|
2474
|
+
const patterns = [...options.patterns ?? []];
|
|
2475
|
+
const shouldWarn = (() => {
|
|
2476
|
+
const groups = executorConfig.modules?.groups;
|
|
2477
|
+
if (!groups) {
|
|
2478
|
+
return true;
|
|
2479
|
+
}
|
|
2480
|
+
if ((options.groups?.length ?? 0) > 0) {
|
|
2481
|
+
const allowedGroups = new Set(options.groups);
|
|
2482
|
+
const allowedRoots = Object.entries(groups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
|
|
2483
|
+
if (allowedRoots.length > 0) {
|
|
2484
|
+
return patterns.some(
|
|
2485
|
+
(pattern) => !allowedRoots.some((root) => patternIsUnderRoot(pattern, root))
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
return true;
|
|
2490
|
+
})();
|
|
2491
|
+
if (shouldWarn) {
|
|
2492
|
+
console.warn(
|
|
2493
|
+
"[autometa] Note: when you pass explicit feature patterns, they are used as-is. Group/module filters (-g/-m) affect module/step loading, but do not automatically filter your feature patterns."
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
const patternSource = (() => {
|
|
2498
|
+
if (hasExplicitPatterns) {
|
|
2499
|
+
return [...options.patterns ?? []];
|
|
2500
|
+
}
|
|
2501
|
+
const roots = [...executorConfig.roots.features];
|
|
2502
|
+
if (hasModuleSelection && executorConfig.modules?.groups) {
|
|
2503
|
+
const allowedGroups = (options.groups?.length ?? 0) > 0 ? new Set(options.groups) : new Set(Object.keys(executorConfig.modules.groups));
|
|
2504
|
+
const allowedRoots = Object.entries(executorConfig.modules.groups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
|
|
2505
|
+
const filtered = roots.filter(
|
|
2506
|
+
(pattern) => allowedRoots.some((root) => patternIsUnderRoot(pattern, root))
|
|
2507
|
+
);
|
|
2508
|
+
if (filtered.length > 0) {
|
|
2509
|
+
return filtered;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return roots;
|
|
2513
|
+
})();
|
|
2514
|
+
const featurePatterns = buildPatterns(patternSource, FEATURE_FALLBACK_GLOB);
|
|
2515
|
+
const featureFiles = (await expandFilePatterns(featurePatterns, cwd)).filter(
|
|
2516
|
+
(file) => file.toLowerCase().endsWith(".feature")
|
|
2517
|
+
);
|
|
2518
|
+
if (featureFiles.length === 0) {
|
|
2519
|
+
throw new Error(
|
|
2520
|
+
`No feature files found for patterns: ${patternSource.map((pattern) => `"${pattern}"`).join(", ")}`
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
const builder = CucumberRunner.builder();
|
|
2524
|
+
let stepsEnvironments = [builder.steps()];
|
|
2525
|
+
const modulePlan = await createModulePlan(executorConfig, cwd);
|
|
2526
|
+
const compileOptions = executorConfig.builder ? { cwd, cacheDir, builder: executorConfig.builder } : { cwd, cacheDir };
|
|
2527
|
+
const compileResult = await compileModules(modulePlan.orderedFiles, compileOptions);
|
|
2528
|
+
if (modulePlan.orderedFiles.length > 0) {
|
|
2529
|
+
const imported = await import(pathToFileURL(compileResult.bundlePath).href);
|
|
2530
|
+
const resolved2 = collectStepsEnvironments(imported);
|
|
2531
|
+
if (resolved2.length > 0) {
|
|
2532
|
+
stepsEnvironments = resolved2;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
const groupIndex = buildGroupIndex(executorConfig, cwd);
|
|
2536
|
+
const environmentIndex = indexStepsEnvironments(stepsEnvironments, cwd, groupIndex);
|
|
2537
|
+
for (const featurePath of featureFiles) {
|
|
2538
|
+
const feature = await readFeatureFile(featurePath, cwd);
|
|
2539
|
+
const featureAbsPath = resolve(cwd, featurePath);
|
|
2540
|
+
const selectedStepsEnvironment = resolveFeatureStepsEnvironment(
|
|
2541
|
+
featureAbsPath,
|
|
2542
|
+
feature,
|
|
2543
|
+
environmentIndex,
|
|
2544
|
+
groupIndex
|
|
2545
|
+
);
|
|
2546
|
+
CucumberRunner.setSteps(selectedStepsEnvironment);
|
|
2547
|
+
const basePlan = selectedStepsEnvironment.getPlan();
|
|
2548
|
+
const scopePlan = createFeatureScopePlan(feature, basePlan, {
|
|
2549
|
+
featureAbsPath,
|
|
2550
|
+
cwd,
|
|
2551
|
+
config: executorConfig,
|
|
2552
|
+
groupIndex
|
|
2553
|
+
});
|
|
2554
|
+
const coordinated = selectedStepsEnvironment.coordinateFeature({
|
|
2555
|
+
feature,
|
|
2556
|
+
plan: scopePlan,
|
|
2557
|
+
config: executorConfig,
|
|
2558
|
+
runtime,
|
|
2559
|
+
hookLogger
|
|
2560
|
+
});
|
|
2561
|
+
coordinated.register(runtime);
|
|
2562
|
+
}
|
|
2563
|
+
const summary = await execute();
|
|
2564
|
+
logSummary(summary, resolved.environment, summaryFormatter);
|
|
2565
|
+
return summary;
|
|
2566
|
+
}
|
|
2567
|
+
function collectStepsEnvironments(imported) {
|
|
2568
|
+
const candidates = collectCandidateModules(imported);
|
|
2569
|
+
const environments = /* @__PURE__ */ new Set();
|
|
2570
|
+
for (const candidate of candidates) {
|
|
2571
|
+
const extracted = extractStepsEnvironments(candidate);
|
|
2572
|
+
for (const env of extracted) {
|
|
2573
|
+
environments.add(env);
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
return Array.from(environments);
|
|
2577
|
+
}
|
|
2578
|
+
function collectCandidateModules(imported) {
|
|
2579
|
+
if (!imported || typeof imported !== "object") {
|
|
2580
|
+
return [];
|
|
2581
|
+
}
|
|
2582
|
+
const record = imported;
|
|
2583
|
+
const modules = /* @__PURE__ */ new Set();
|
|
2584
|
+
modules.add(record);
|
|
2585
|
+
const exportedModules = record.modules;
|
|
2586
|
+
if (Array.isArray(exportedModules)) {
|
|
2587
|
+
for (const entry of exportedModules) {
|
|
2588
|
+
if (entry && typeof entry === "object") {
|
|
2589
|
+
modules.add(entry);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
const defaultExport = record.default;
|
|
2594
|
+
if (Array.isArray(defaultExport)) {
|
|
2595
|
+
for (const entry of defaultExport) {
|
|
2596
|
+
if (entry && typeof entry === "object") {
|
|
2597
|
+
modules.add(entry);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
} else if (defaultExport && typeof defaultExport === "object") {
|
|
2601
|
+
modules.add(defaultExport);
|
|
2602
|
+
}
|
|
2603
|
+
return Array.from(modules);
|
|
2604
|
+
}
|
|
2605
|
+
function extractStepsEnvironments(candidate) {
|
|
2606
|
+
const environments = [];
|
|
2607
|
+
if (isStepsEnvironment(candidate)) {
|
|
2608
|
+
environments.push(candidate);
|
|
2609
|
+
}
|
|
2610
|
+
const steps = candidate.stepsEnvironment;
|
|
2611
|
+
if (isStepsEnvironment(steps)) {
|
|
2612
|
+
environments.push(steps);
|
|
2613
|
+
}
|
|
2614
|
+
const defaultExport = candidate.default;
|
|
2615
|
+
if (isStepsEnvironment(defaultExport)) {
|
|
2616
|
+
environments.push(defaultExport);
|
|
2617
|
+
}
|
|
2618
|
+
return environments;
|
|
2619
|
+
}
|
|
2620
|
+
function inferEnvironmentGroup(environment, cwd, groupIndex) {
|
|
2621
|
+
const meta = environment[STEPS_ENVIRONMENT_META];
|
|
2622
|
+
if (meta?.kind === "group" && typeof meta.group === "string" && meta.group.trim().length > 0) {
|
|
2623
|
+
return { kind: "group", group: meta.group };
|
|
2624
|
+
}
|
|
2625
|
+
if (meta?.kind === "root") {
|
|
2626
|
+
return { kind: "root" };
|
|
2627
|
+
}
|
|
2628
|
+
const plan = environment.getPlan();
|
|
2629
|
+
const groups = /* @__PURE__ */ new Set();
|
|
2630
|
+
for (const step of plan.stepsById.values()) {
|
|
2631
|
+
const file = step.source?.file;
|
|
2632
|
+
if (!file) {
|
|
2633
|
+
continue;
|
|
2634
|
+
}
|
|
2635
|
+
const scope = resolveFileScope(resolve(cwd, file), groupIndex);
|
|
2636
|
+
if (scope.kind === "group" || scope.kind === "module") {
|
|
2637
|
+
groups.add(scope.group);
|
|
2638
|
+
}
|
|
2639
|
+
if (groups.size > 1) {
|
|
2640
|
+
return { kind: "ambiguous" };
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
const only = Array.from(groups.values())[0];
|
|
2644
|
+
return only ? { kind: "group", group: only } : { kind: "root" };
|
|
2645
|
+
}
|
|
2646
|
+
function indexStepsEnvironments(environments, cwd, groupIndex) {
|
|
2647
|
+
const entries = [];
|
|
2648
|
+
for (const env of environments) {
|
|
2649
|
+
const inferred = inferEnvironmentGroup(env, cwd, groupIndex);
|
|
2650
|
+
if (inferred.kind === "ambiguous") {
|
|
2651
|
+
continue;
|
|
2652
|
+
}
|
|
2653
|
+
entries.push(
|
|
2654
|
+
inferred.kind === "group" ? { kind: "group", group: inferred.group, environment: env } : { kind: "root", environment: env }
|
|
2655
|
+
);
|
|
2656
|
+
}
|
|
2657
|
+
return entries;
|
|
2658
|
+
}
|
|
2659
|
+
function resolveFeatureStepsEnvironment(featureAbsPath, feature, environments, groupIndex) {
|
|
2660
|
+
const featureScope = resolveFeatureScope(featureAbsPath, feature, groupIndex);
|
|
2661
|
+
if (featureScope.kind === "root") {
|
|
2662
|
+
const root = environments.find((entry) => entry.kind === "root");
|
|
2663
|
+
return root?.environment ?? environments[0]?.environment ?? CucumberRunner.steps();
|
|
2664
|
+
}
|
|
2665
|
+
const group = featureScope.group;
|
|
2666
|
+
const match = environments.find((entry) => entry.kind === "group" && entry.group === group);
|
|
2667
|
+
if (match) {
|
|
2668
|
+
return match.environment;
|
|
2669
|
+
}
|
|
2670
|
+
const available = environments.filter((entry) => entry.kind === "group").map((entry) => entry.group).sort();
|
|
2671
|
+
throw new Error(
|
|
2672
|
+
`No steps environment found for group "${group}". Available groups: ${available.length > 0 ? available.join(", ") : "<none>"}`
|
|
2673
|
+
);
|
|
2674
|
+
}
|
|
2675
|
+
function isStepsEnvironment(value) {
|
|
2676
|
+
if (!value || typeof value !== "object") {
|
|
2677
|
+
return false;
|
|
2678
|
+
}
|
|
2679
|
+
const candidate = value;
|
|
2680
|
+
return typeof candidate.coordinateFeature === "function" && typeof candidate.Given === "function" && typeof candidate.When === "function" && typeof candidate.Then === "function";
|
|
2681
|
+
}
|
|
2682
|
+
async function createModulePlan(config, cwd) {
|
|
2683
|
+
const orderedFiles = [];
|
|
2684
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
2685
|
+
const processed = /* @__PURE__ */ new Set(["features", "steps"]);
|
|
2686
|
+
const rootsRecord = config.roots;
|
|
2687
|
+
const pushFiles = (files) => {
|
|
2688
|
+
for (const file of files) {
|
|
2689
|
+
if (seenFiles.has(file)) {
|
|
2690
|
+
continue;
|
|
2691
|
+
}
|
|
2692
|
+
seenFiles.add(file);
|
|
2693
|
+
orderedFiles.push(file);
|
|
2694
|
+
}
|
|
2695
|
+
};
|
|
2696
|
+
const eventFiles = await resolveRootFiles(config.events ?? [], cwd);
|
|
2697
|
+
if (eventFiles.length > 0) {
|
|
2698
|
+
pushFiles(eventFiles);
|
|
2699
|
+
}
|
|
2700
|
+
for (const key of ROOT_LOAD_ORDER) {
|
|
2701
|
+
const entries = rootsRecord[key];
|
|
2702
|
+
if (!entries || entries.length === 0) {
|
|
2703
|
+
processed.add(key);
|
|
2704
|
+
continue;
|
|
2705
|
+
}
|
|
2706
|
+
const files = await resolveRootFiles(entries, cwd);
|
|
2707
|
+
if (files.length > 0) {
|
|
2708
|
+
pushFiles(files);
|
|
2709
|
+
}
|
|
2710
|
+
processed.add(key);
|
|
2711
|
+
}
|
|
2712
|
+
for (const [key, entries] of Object.entries(rootsRecord)) {
|
|
2713
|
+
if (processed.has(key)) {
|
|
2714
|
+
continue;
|
|
2715
|
+
}
|
|
2716
|
+
if (!entries || entries.length === 0) {
|
|
2717
|
+
continue;
|
|
2718
|
+
}
|
|
2719
|
+
const files = await resolveRootFiles(entries, cwd);
|
|
2720
|
+
if (files.length > 0) {
|
|
2721
|
+
pushFiles(files);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
const stepFiles = await resolveRootFiles(config.roots.steps, cwd);
|
|
2725
|
+
pushFiles(stepFiles);
|
|
2726
|
+
return {
|
|
2727
|
+
orderedFiles
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
async function resolveRootFiles(entries, cwd) {
|
|
2731
|
+
const patterns = buildPatterns(entries, STEP_FALLBACK_GLOB);
|
|
2732
|
+
if (patterns.length === 0) {
|
|
2733
|
+
return [];
|
|
2734
|
+
}
|
|
2735
|
+
const matches = await expandFilePatterns(patterns, cwd);
|
|
2736
|
+
return filterCodeFiles(matches);
|
|
2737
|
+
}
|
|
2738
|
+
function buildPatterns(entries, fallbackGlob) {
|
|
2739
|
+
const patterns = /* @__PURE__ */ new Set();
|
|
2740
|
+
for (const entry of entries) {
|
|
2741
|
+
const normalized = entry.trim();
|
|
2742
|
+
if (!normalized) {
|
|
2743
|
+
continue;
|
|
2744
|
+
}
|
|
2745
|
+
for (const pattern of toPatterns(normalized, fallbackGlob)) {
|
|
2746
|
+
patterns.add(pattern);
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
return Array.from(patterns);
|
|
2750
|
+
}
|
|
2751
|
+
function toPatterns(entry, fallbackGlob) {
|
|
2752
|
+
if (hasGlobMagic(entry) || hasFileExtension(entry)) {
|
|
2753
|
+
return [entry];
|
|
2754
|
+
}
|
|
2755
|
+
return [appendGlob(entry, fallbackGlob)];
|
|
2756
|
+
}
|
|
2757
|
+
function hasGlobMagic(input) {
|
|
2758
|
+
return /[*?{}()[\]!,@+]/.test(input);
|
|
2759
|
+
}
|
|
2760
|
+
function hasFileExtension(input) {
|
|
2761
|
+
const normalized = input.replace(/\\/g, "/");
|
|
2762
|
+
const trimmed = normalized === "/" ? "/" : normalized.replace(/\/+$/u, "");
|
|
2763
|
+
if (!trimmed || trimmed === "." || trimmed === "..") {
|
|
2764
|
+
return false;
|
|
2765
|
+
}
|
|
2766
|
+
return Boolean(extname(trimmed));
|
|
2767
|
+
}
|
|
2768
|
+
function appendGlob(entry, glob) {
|
|
2769
|
+
const normalized = entry.replace(/\\/g, "/");
|
|
2770
|
+
const trimmed = normalized === "/" ? "/" : normalized.replace(/\/+$/u, "");
|
|
2771
|
+
if (!trimmed || trimmed === ".") {
|
|
2772
|
+
return glob;
|
|
2773
|
+
}
|
|
2774
|
+
if (trimmed === "/") {
|
|
2775
|
+
return `/${glob}`;
|
|
2776
|
+
}
|
|
2777
|
+
return `${trimmed}/${glob}`;
|
|
2778
|
+
}
|
|
2779
|
+
function filterCodeFiles(files) {
|
|
2780
|
+
return files.filter((file) => {
|
|
2781
|
+
const lower = file.toLowerCase();
|
|
2782
|
+
if (lower.endsWith(".d.ts")) {
|
|
2783
|
+
return false;
|
|
2784
|
+
}
|
|
2785
|
+
const dotIndex = lower.lastIndexOf(".");
|
|
2786
|
+
if (dotIndex === -1) {
|
|
2787
|
+
return false;
|
|
2788
|
+
}
|
|
2789
|
+
const extension = lower.slice(dotIndex);
|
|
2790
|
+
return STEP_FILE_EXTENSIONS.has(extension);
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
async function readFeatureFile(path3, cwd) {
|
|
2794
|
+
const source = await promises.readFile(path3, "utf8");
|
|
2795
|
+
const feature = parseGherkin(source);
|
|
2796
|
+
if (!feature.uri) {
|
|
2797
|
+
const relativePath2 = relative(cwd, path3);
|
|
2798
|
+
const candidate = relativePath2.startsWith("..") ? path3 : relativePath2;
|
|
2799
|
+
feature.uri = candidate.replace(/\\/g, "/");
|
|
2800
|
+
}
|
|
2801
|
+
return feature;
|
|
2802
|
+
}
|
|
2803
|
+
function logSummary(summary, environment, formatter) {
|
|
2804
|
+
console.log(formatter(summary, { environment }));
|
|
2805
|
+
}
|
|
2806
|
+
function configureHttpLogging(logging) {
|
|
2807
|
+
if (!logging?.http) {
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
const registered = HTTP.getSharedPlugins();
|
|
2811
|
+
if (registered.some((plugin) => plugin.name === "http-logging")) {
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
HTTP.registerSharedPlugin(createLoggingPlugin(logHttpEvent));
|
|
2815
|
+
}
|
|
2816
|
+
function logHttpEvent(event) {
|
|
2817
|
+
const timestamp = new Date(event.timestamp).toISOString();
|
|
2818
|
+
const url = resolveRequestUrl(event.request);
|
|
2819
|
+
switch (event.type) {
|
|
2820
|
+
case "request": {
|
|
2821
|
+
const method = event.request.method ?? "<unknown>";
|
|
2822
|
+
console.log(`[HTTP ${timestamp}] \u2192 ${method} ${url}`);
|
|
2823
|
+
break;
|
|
2824
|
+
}
|
|
2825
|
+
case "response": {
|
|
2826
|
+
const status = event.response.status;
|
|
2827
|
+
console.log(`[HTTP ${timestamp}] \u2190 ${status} ${url}`);
|
|
2828
|
+
break;
|
|
2829
|
+
}
|
|
2830
|
+
case "error": {
|
|
2831
|
+
const message = event.error instanceof Error ? event.error.message : String(event.error);
|
|
2832
|
+
console.error(`[HTTP ${timestamp}] ! ${url} ${message}`);
|
|
2833
|
+
break;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
function resolveRequestUrl(request) {
|
|
2838
|
+
const url = request.fullUrl;
|
|
2839
|
+
if (url) {
|
|
2840
|
+
return url;
|
|
2841
|
+
}
|
|
2842
|
+
if (request.baseUrl && request.route && request.route.length > 0) {
|
|
2843
|
+
const normalizedRoute = request.route.join("/");
|
|
2844
|
+
return `${request.baseUrl.replace(/\/?$/u, "")}/${normalizedRoute}`;
|
|
2845
|
+
}
|
|
2846
|
+
if (request.baseUrl) {
|
|
2847
|
+
return request.baseUrl;
|
|
2848
|
+
}
|
|
2849
|
+
if (request.route && request.route.length > 0) {
|
|
2850
|
+
return request.route.join("/");
|
|
2851
|
+
}
|
|
2852
|
+
return "<unknown>";
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
// src/index.ts
|
|
2856
|
+
var require2 = createRequire(import.meta.url);
|
|
2857
|
+
var { version } = require2("../package.json");
|
|
2858
|
+
async function createCliProgram() {
|
|
2859
|
+
const program = new Command();
|
|
2860
|
+
program.name("autometa").description("Autometa command-line runner").version(version ?? "0.0.0");
|
|
2861
|
+
registerRunCommand(program);
|
|
2862
|
+
return program;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
export { createCliProgram, runFeatures };
|
|
2866
|
+
//# sourceMappingURL=out.js.map
|
|
2867
|
+
//# sourceMappingURL=index.js.map
|