@autometa/cli 1.0.0-rc.5 → 1.0.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +233 -20
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +233 -20
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +233 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +233 -20
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/index.d.ts +2 -0
- package/package.json +10 -10
package/dist/bin.js
CHANGED
|
@@ -2006,19 +2006,112 @@ function detectRunner(config, cwd) {
|
|
|
2006
2006
|
}
|
|
2007
2007
|
return "default";
|
|
2008
2008
|
}
|
|
2009
|
+
function normalizeGlobLikePath(input) {
|
|
2010
|
+
return input.replace(/\\/g, "/").replace(/^\.\//u, "");
|
|
2011
|
+
}
|
|
2012
|
+
function patternIsUnderRoot(pattern, root) {
|
|
2013
|
+
const p = normalizeGlobLikePath(pattern);
|
|
2014
|
+
const r = normalizeGlobLikePath(root).replace(/\/+$/u, "");
|
|
2015
|
+
if (!r) {
|
|
2016
|
+
return false;
|
|
2017
|
+
}
|
|
2018
|
+
return p === r || p.startsWith(`${r}/`);
|
|
2019
|
+
}
|
|
2020
|
+
function filterPatternsByGroupsAndModules(options) {
|
|
2021
|
+
const { patterns, groups, modules, config, verbose } = options;
|
|
2022
|
+
const hasGroupSelection = (groups?.length ?? 0) > 0;
|
|
2023
|
+
const hasModuleSelection = (modules?.length ?? 0) > 0;
|
|
2024
|
+
const hasAnySelection = hasGroupSelection || hasModuleSelection;
|
|
2025
|
+
const hasExplicitPatterns = patterns.length > 0;
|
|
2026
|
+
if (hasExplicitPatterns) {
|
|
2027
|
+
if (hasAnySelection && verbose) {
|
|
2028
|
+
const shouldWarn = (() => {
|
|
2029
|
+
const configGroups = config.modules?.groups;
|
|
2030
|
+
if (!configGroups) {
|
|
2031
|
+
return true;
|
|
2032
|
+
}
|
|
2033
|
+
if (hasGroupSelection) {
|
|
2034
|
+
const allowedGroups = new Set(groups);
|
|
2035
|
+
const allowedRoots = Object.entries(configGroups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
|
|
2036
|
+
if (allowedRoots.length > 0) {
|
|
2037
|
+
return patterns.some(
|
|
2038
|
+
(pattern) => !allowedRoots.some((root) => patternIsUnderRoot(pattern, root))
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
return true;
|
|
2043
|
+
})();
|
|
2044
|
+
if (shouldWarn) {
|
|
2045
|
+
console.warn(
|
|
2046
|
+
"[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."
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
return patterns;
|
|
2051
|
+
}
|
|
2052
|
+
const roots = [...config.roots.features];
|
|
2053
|
+
if (hasAnySelection && config.modules?.groups) {
|
|
2054
|
+
const allowedGroups = hasGroupSelection ? new Set(groups) : new Set(Object.keys(config.modules.groups));
|
|
2055
|
+
if (hasModuleSelection && modules && modules.length > 0) {
|
|
2056
|
+
const modulePatterns = [];
|
|
2057
|
+
const relativeRoots = config.modules.relativeRoots?.features ?? [".features/**/*.feature"];
|
|
2058
|
+
for (const [groupName, groupConfig] of Object.entries(config.modules.groups)) {
|
|
2059
|
+
if (!allowedGroups.has(groupName)) {
|
|
2060
|
+
continue;
|
|
2061
|
+
}
|
|
2062
|
+
const groupRoot = groupConfig.root;
|
|
2063
|
+
const groupModules = groupConfig.modules ?? [];
|
|
2064
|
+
for (const moduleName of modules) {
|
|
2065
|
+
const moduleExists = Array.isArray(groupModules) && groupModules.some((m) => {
|
|
2066
|
+
if (typeof m === "string") {
|
|
2067
|
+
return m === moduleName || m.endsWith(`/${moduleName}`) || m.endsWith(`:${moduleName}`);
|
|
2068
|
+
}
|
|
2069
|
+
return false;
|
|
2070
|
+
});
|
|
2071
|
+
if (moduleExists) {
|
|
2072
|
+
for (const relativeRoot of relativeRoots) {
|
|
2073
|
+
const normalizedRelative = relativeRoot.replace(/^\.\//, "");
|
|
2074
|
+
const modulePath = join(groupRoot, moduleName, normalizedRelative);
|
|
2075
|
+
modulePatterns.push(modulePath);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
if (modulePatterns.length > 0) {
|
|
2081
|
+
return modulePatterns;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
const allowedRoots = Object.entries(config.modules.groups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
|
|
2085
|
+
const filtered = roots.filter(
|
|
2086
|
+
(pattern) => allowedRoots.some((root) => patternIsUnderRoot(pattern, root))
|
|
2087
|
+
);
|
|
2088
|
+
if (filtered.length > 0) {
|
|
2089
|
+
return filtered;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
return roots;
|
|
2093
|
+
}
|
|
2009
2094
|
async function orchestrate(options) {
|
|
2010
|
-
const { cwd, config, patterns = [], runnerArgs = [], dryRun = false, watch = false, verbose = false } = options;
|
|
2095
|
+
const { cwd, config, patterns = [], runnerArgs = [], dryRun = false, watch = false, verbose = false, groups, modules } = options;
|
|
2011
2096
|
const runner = detectRunner(config, cwd);
|
|
2012
2097
|
if (verbose) {
|
|
2013
2098
|
console.log(`[autometa] Detected runner: ${runner}`);
|
|
2014
2099
|
}
|
|
2100
|
+
const filteredPatterns = filterPatternsByGroupsAndModules({
|
|
2101
|
+
patterns,
|
|
2102
|
+
groups,
|
|
2103
|
+
modules,
|
|
2104
|
+
config,
|
|
2105
|
+
cwd,
|
|
2106
|
+
verbose
|
|
2107
|
+
});
|
|
2015
2108
|
switch (runner) {
|
|
2016
2109
|
case "vitest":
|
|
2017
|
-
return spawnVitest({ cwd, patterns, runnerArgs, dryRun, watch, verbose });
|
|
2110
|
+
return spawnVitest({ cwd, patterns: filteredPatterns, runnerArgs, dryRun, watch, verbose });
|
|
2018
2111
|
case "jest":
|
|
2019
|
-
return spawnJest({ cwd, patterns, runnerArgs, dryRun, watch, verbose });
|
|
2112
|
+
return spawnJest({ cwd, patterns: filteredPatterns, runnerArgs, dryRun, watch, verbose });
|
|
2020
2113
|
case "playwright":
|
|
2021
|
-
return spawnPlaywright({ cwd, patterns, runnerArgs, dryRun, watch, verbose });
|
|
2114
|
+
return spawnPlaywright({ cwd, patterns: filteredPatterns, runnerArgs, dryRun, watch, verbose });
|
|
2022
2115
|
case "default":
|
|
2023
2116
|
return { success: true, exitCode: 0, runner: "default" };
|
|
2024
2117
|
}
|
|
@@ -2030,7 +2123,8 @@ async function spawnVitest(options) {
|
|
|
2030
2123
|
args.push("run");
|
|
2031
2124
|
}
|
|
2032
2125
|
if (patterns.length > 0) {
|
|
2033
|
-
|
|
2126
|
+
const expanded = await expandFilePatterns(patterns, cwd);
|
|
2127
|
+
args.push(...expanded.length > 0 ? expanded : patterns);
|
|
2034
2128
|
}
|
|
2035
2129
|
if (dryRun) {
|
|
2036
2130
|
args.push("--passWithNoTests");
|
|
@@ -2132,17 +2226,53 @@ var STEP_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"
|
|
|
2132
2226
|
var STEP_FALLBACK_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}";
|
|
2133
2227
|
var FEATURE_FALLBACK_GLOB = "**/*.feature";
|
|
2134
2228
|
var ROOT_LOAD_ORDER = ["parameterTypes", "support", "hooks", "app"];
|
|
2135
|
-
|
|
2229
|
+
var GLOB_MAGIC_CHARACTERS = /* @__PURE__ */ new Set(["*", "?", "[", "]", "{", "}", "(", ")", "!"]);
|
|
2230
|
+
function normalizeGlobLikePath2(input) {
|
|
2136
2231
|
return input.replace(/\\/g, "/").replace(/^\.\//u, "");
|
|
2137
2232
|
}
|
|
2138
|
-
function
|
|
2139
|
-
const p =
|
|
2140
|
-
const r =
|
|
2233
|
+
function patternIsUnderRoot2(pattern, root) {
|
|
2234
|
+
const p = normalizeGlobLikePath2(pattern);
|
|
2235
|
+
const r = normalizeGlobLikePath2(root).replace(/\/+$/u, "");
|
|
2141
2236
|
if (!r) {
|
|
2142
2237
|
return false;
|
|
2143
2238
|
}
|
|
2144
2239
|
return p === r || p.startsWith(`${r}/`);
|
|
2145
2240
|
}
|
|
2241
|
+
function inferGlobBaseDirectory(pattern) {
|
|
2242
|
+
const normalized = normalizeGlobLikePath2(pattern).replace(/^!/u, "");
|
|
2243
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
2244
|
+
if (!GLOB_MAGIC_CHARACTERS.has(normalized[i] ?? "")) {
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
2247
|
+
const prefix = normalized.slice(0, i);
|
|
2248
|
+
const lastSlash = prefix.lastIndexOf("/");
|
|
2249
|
+
if (lastSlash <= 0) {
|
|
2250
|
+
return ".";
|
|
2251
|
+
}
|
|
2252
|
+
const base = prefix.slice(0, lastSlash);
|
|
2253
|
+
return base || ".";
|
|
2254
|
+
}
|
|
2255
|
+
if (extname(normalized)) {
|
|
2256
|
+
return dirname(normalized);
|
|
2257
|
+
}
|
|
2258
|
+
return normalized || ".";
|
|
2259
|
+
}
|
|
2260
|
+
function inferFeatureRootDirectories(patterns, cwd) {
|
|
2261
|
+
if (!patterns || patterns.length === 0) {
|
|
2262
|
+
return [];
|
|
2263
|
+
}
|
|
2264
|
+
const roots = /* @__PURE__ */ new Set();
|
|
2265
|
+
for (const entry of patterns) {
|
|
2266
|
+
const trimmed = entry.trim();
|
|
2267
|
+
if (!trimmed) {
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
const base = hasGlobMagic(trimmed) ? inferGlobBaseDirectory(trimmed) : trimmed;
|
|
2271
|
+
const resolved = resolve(cwd, base);
|
|
2272
|
+
roots.add(resolved.replace(/\/+$/u, ""));
|
|
2273
|
+
}
|
|
2274
|
+
return Array.from(roots).sort((a, b) => b.length - a.length);
|
|
2275
|
+
}
|
|
2146
2276
|
function flattenModuleDeclarations(declarations, prefix = []) {
|
|
2147
2277
|
if (!declarations || declarations.length === 0) {
|
|
2148
2278
|
return [];
|
|
@@ -2226,6 +2356,48 @@ function resolveFileScope(fileAbs, groupIndex) {
|
|
|
2226
2356
|
}
|
|
2227
2357
|
return { kind: "root" };
|
|
2228
2358
|
}
|
|
2359
|
+
function resolveHoistedDirectoryScope(options) {
|
|
2360
|
+
const rootAbs = options.hoistedFeatureRootsAbs.find(
|
|
2361
|
+
(root) => isPathUnderRoot(options.featureAbsPath, root)
|
|
2362
|
+
);
|
|
2363
|
+
if (!rootAbs) {
|
|
2364
|
+
return { kind: "root" };
|
|
2365
|
+
}
|
|
2366
|
+
const rel = relative(rootAbs, options.featureAbsPath);
|
|
2367
|
+
const segments = normalizePathSegments(rel);
|
|
2368
|
+
const dirSegments = segments.slice(0, -1);
|
|
2369
|
+
if (dirSegments.length === 0) {
|
|
2370
|
+
return { kind: "root" };
|
|
2371
|
+
}
|
|
2372
|
+
const group = dirSegments[0];
|
|
2373
|
+
if (!group) {
|
|
2374
|
+
return { kind: "root" };
|
|
2375
|
+
}
|
|
2376
|
+
const groupEntry = options.groupIndex.find((entry) => entry.group === group);
|
|
2377
|
+
if (!groupEntry) {
|
|
2378
|
+
if (options.strict) {
|
|
2379
|
+
throw new Error(
|
|
2380
|
+
`[autometa] Feature "${options.featureAbsPath}" is under "${rootAbs}" and implies group "${group}", but "${group}" is not declared in config.modules.groups.`
|
|
2381
|
+
);
|
|
2382
|
+
}
|
|
2383
|
+
return { kind: "root" };
|
|
2384
|
+
}
|
|
2385
|
+
const moduleSegments = dirSegments.slice(1);
|
|
2386
|
+
if (moduleSegments.length === 0) {
|
|
2387
|
+
return { kind: "group", group };
|
|
2388
|
+
}
|
|
2389
|
+
for (const modulePath of groupEntry.modulePaths) {
|
|
2390
|
+
if (startsWithSegments(moduleSegments, modulePath)) {
|
|
2391
|
+
return { kind: "module", group, modulePath };
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
if (options.strict) {
|
|
2395
|
+
throw new Error(
|
|
2396
|
+
`[autometa] Feature "${options.featureAbsPath}" is under "${rootAbs}" and implies module "${group}:${moduleSegments.join(":")}", but no matching module is declared in config.modules.groups.${group}.modules.`
|
|
2397
|
+
);
|
|
2398
|
+
}
|
|
2399
|
+
return { kind: "group", group };
|
|
2400
|
+
}
|
|
2229
2401
|
function parseScopeOverrideTag(tags) {
|
|
2230
2402
|
if (!tags || tags.length === 0) {
|
|
2231
2403
|
return void 0;
|
|
@@ -2249,15 +2421,36 @@ function parseScopeOverrideTag(tags) {
|
|
|
2249
2421
|
}
|
|
2250
2422
|
return void 0;
|
|
2251
2423
|
}
|
|
2252
|
-
function resolveFeatureScope(
|
|
2253
|
-
const override = parseScopeOverrideTag(feature.tags);
|
|
2424
|
+
function resolveFeatureScope(options) {
|
|
2425
|
+
const override = parseScopeOverrideTag(options.feature.tags);
|
|
2426
|
+
const fileScope = resolveFileScope(options.featureAbsPath, options.groupIndex);
|
|
2427
|
+
const inferred = (() => {
|
|
2428
|
+
if (fileScope.kind !== "root") {
|
|
2429
|
+
return fileScope;
|
|
2430
|
+
}
|
|
2431
|
+
if (options.hoistedFeatureScopingMode !== "directory") {
|
|
2432
|
+
return fileScope;
|
|
2433
|
+
}
|
|
2434
|
+
return resolveHoistedDirectoryScope({
|
|
2435
|
+
featureAbsPath: options.featureAbsPath,
|
|
2436
|
+
hoistedFeatureRootsAbs: options.hoistedFeatureRootsAbs,
|
|
2437
|
+
groupIndex: options.groupIndex,
|
|
2438
|
+
strict: options.hoistedFeatureScopingStrict
|
|
2439
|
+
});
|
|
2440
|
+
})();
|
|
2254
2441
|
if (override) {
|
|
2255
2442
|
if (override.modulePath && override.modulePath.length > 0) {
|
|
2256
2443
|
return { kind: "module", group: override.group, modulePath: override.modulePath };
|
|
2257
2444
|
}
|
|
2445
|
+
if (inferred.kind === "module" && inferred.group === override.group) {
|
|
2446
|
+
return inferred;
|
|
2447
|
+
}
|
|
2448
|
+
if (inferred.kind === "group" && inferred.group === override.group) {
|
|
2449
|
+
return inferred;
|
|
2450
|
+
}
|
|
2258
2451
|
return { kind: "group", group: override.group };
|
|
2259
2452
|
}
|
|
2260
|
-
return
|
|
2453
|
+
return inferred;
|
|
2261
2454
|
}
|
|
2262
2455
|
function isVisibleStepScope(stepScope, featureScope) {
|
|
2263
2456
|
if (featureScope.kind === "root") {
|
|
@@ -2350,7 +2543,14 @@ function createFeatureScopePlan(feature, basePlan, options) {
|
|
|
2350
2543
|
const scopingMode = options.config.modules?.stepScoping ?? "global";
|
|
2351
2544
|
const useScopedSteps = scopingMode === "scoped" && options.groupIndex.length > 0;
|
|
2352
2545
|
const allSteps = Array.from(basePlan.stepsById.values());
|
|
2353
|
-
const featureFileScope = useScopedSteps ? resolveFeatureScope(
|
|
2546
|
+
const featureFileScope = useScopedSteps ? resolveFeatureScope({
|
|
2547
|
+
featureAbsPath: options.featureAbsPath,
|
|
2548
|
+
feature,
|
|
2549
|
+
groupIndex: options.groupIndex,
|
|
2550
|
+
hoistedFeatureRootsAbs: options.hoistedFeatureRootsAbs,
|
|
2551
|
+
hoistedFeatureScopingMode: options.config.modules?.hoistedFeatures?.scope ?? "tag",
|
|
2552
|
+
hoistedFeatureScopingStrict: options.config.modules?.hoistedFeatures?.strict ?? true
|
|
2553
|
+
}) : { kind: "root" };
|
|
2354
2554
|
const visibleSteps = useScopedSteps ? allSteps.filter((definition) => {
|
|
2355
2555
|
const file = definition.source?.file;
|
|
2356
2556
|
const stepScope = file ? resolveFileScope(resolve(options.cwd, file), options.groupIndex) : { kind: "root" };
|
|
@@ -2477,7 +2677,9 @@ async function runFeatures(options = {}) {
|
|
|
2477
2677
|
...options.runnerArgs ? { runnerArgs: options.runnerArgs } : {},
|
|
2478
2678
|
...options.dryRun !== void 0 ? { dryRun: options.dryRun } : {},
|
|
2479
2679
|
...options.watch !== void 0 ? { watch: options.watch } : {},
|
|
2480
|
-
...options.verbose !== void 0 ? { verbose: options.verbose } : {}
|
|
2680
|
+
...options.verbose !== void 0 ? { verbose: options.verbose } : {},
|
|
2681
|
+
groups: options.groups,
|
|
2682
|
+
modules: options.modules
|
|
2481
2683
|
});
|
|
2482
2684
|
if (orchestratorResult.runner !== "default") {
|
|
2483
2685
|
return {
|
|
@@ -2519,7 +2721,7 @@ async function runFeatures(options = {}) {
|
|
|
2519
2721
|
const allowedRoots = Object.entries(groups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
|
|
2520
2722
|
if (allowedRoots.length > 0) {
|
|
2521
2723
|
return patterns.some(
|
|
2522
|
-
(pattern) => !allowedRoots.some((root) =>
|
|
2724
|
+
(pattern) => !allowedRoots.some((root) => patternIsUnderRoot2(pattern, root))
|
|
2523
2725
|
);
|
|
2524
2726
|
}
|
|
2525
2727
|
}
|
|
@@ -2540,7 +2742,7 @@ async function runFeatures(options = {}) {
|
|
|
2540
2742
|
const allowedGroups = (options.groups?.length ?? 0) > 0 ? new Set(options.groups) : new Set(Object.keys(executorConfig.modules.groups));
|
|
2541
2743
|
const allowedRoots = Object.entries(executorConfig.modules.groups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
|
|
2542
2744
|
const filtered = roots.filter(
|
|
2543
|
-
(pattern) => allowedRoots.some((root) =>
|
|
2745
|
+
(pattern) => allowedRoots.some((root) => patternIsUnderRoot2(pattern, root))
|
|
2544
2746
|
);
|
|
2545
2747
|
if (filtered.length > 0) {
|
|
2546
2748
|
return filtered;
|
|
@@ -2571,6 +2773,7 @@ async function runFeatures(options = {}) {
|
|
|
2571
2773
|
}
|
|
2572
2774
|
const groupIndex = buildGroupIndex(executorConfig, cwd);
|
|
2573
2775
|
const environmentIndex = indexStepsEnvironments(stepsEnvironments, cwd, groupIndex);
|
|
2776
|
+
const hoistedFeatureRootsAbs = inferFeatureRootDirectories(executorConfig.roots.features, cwd);
|
|
2574
2777
|
for (const featurePath of featureFiles) {
|
|
2575
2778
|
const feature = await readFeatureFile(featurePath, cwd);
|
|
2576
2779
|
const featureAbsPath = resolve(cwd, featurePath);
|
|
@@ -2578,7 +2781,9 @@ async function runFeatures(options = {}) {
|
|
|
2578
2781
|
featureAbsPath,
|
|
2579
2782
|
feature,
|
|
2580
2783
|
environmentIndex,
|
|
2581
|
-
groupIndex
|
|
2784
|
+
groupIndex,
|
|
2785
|
+
executorConfig,
|
|
2786
|
+
hoistedFeatureRootsAbs
|
|
2582
2787
|
);
|
|
2583
2788
|
CucumberRunner.setSteps(selectedStepsEnvironment);
|
|
2584
2789
|
const basePlan = selectedStepsEnvironment.getPlan();
|
|
@@ -2586,7 +2791,8 @@ async function runFeatures(options = {}) {
|
|
|
2586
2791
|
featureAbsPath,
|
|
2587
2792
|
cwd,
|
|
2588
2793
|
config: executorConfig,
|
|
2589
|
-
groupIndex
|
|
2794
|
+
groupIndex,
|
|
2795
|
+
hoistedFeatureRootsAbs
|
|
2590
2796
|
});
|
|
2591
2797
|
const coordinated = selectedStepsEnvironment.coordinateFeature({
|
|
2592
2798
|
feature,
|
|
@@ -2693,8 +2899,15 @@ function indexStepsEnvironments(environments, cwd, groupIndex) {
|
|
|
2693
2899
|
}
|
|
2694
2900
|
return entries;
|
|
2695
2901
|
}
|
|
2696
|
-
function resolveFeatureStepsEnvironment(featureAbsPath, feature, environments, groupIndex) {
|
|
2697
|
-
const featureScope = resolveFeatureScope(
|
|
2902
|
+
function resolveFeatureStepsEnvironment(featureAbsPath, feature, environments, groupIndex, config, hoistedFeatureRootsAbs) {
|
|
2903
|
+
const featureScope = resolveFeatureScope({
|
|
2904
|
+
featureAbsPath,
|
|
2905
|
+
feature,
|
|
2906
|
+
groupIndex,
|
|
2907
|
+
hoistedFeatureRootsAbs,
|
|
2908
|
+
hoistedFeatureScopingMode: config.modules?.hoistedFeatures?.scope ?? "tag",
|
|
2909
|
+
hoistedFeatureScopingStrict: config.modules?.hoistedFeatures?.strict ?? true
|
|
2910
|
+
});
|
|
2698
2911
|
if (featureScope.kind === "root") {
|
|
2699
2912
|
const root = environments.find((entry) => entry.kind === "root");
|
|
2700
2913
|
return root?.environment ?? environments[0]?.environment ?? CucumberRunner.steps();
|