@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 CHANGED
@@ -2010,19 +2010,112 @@ function detectRunner(config, cwd) {
2010
2010
  }
2011
2011
  return "default";
2012
2012
  }
2013
+ function normalizeGlobLikePath(input) {
2014
+ return input.replace(/\\/g, "/").replace(/^\.\//u, "");
2015
+ }
2016
+ function patternIsUnderRoot(pattern, root) {
2017
+ const p = normalizeGlobLikePath(pattern);
2018
+ const r = normalizeGlobLikePath(root).replace(/\/+$/u, "");
2019
+ if (!r) {
2020
+ return false;
2021
+ }
2022
+ return p === r || p.startsWith(`${r}/`);
2023
+ }
2024
+ function filterPatternsByGroupsAndModules(options) {
2025
+ const { patterns, groups, modules, config, verbose } = options;
2026
+ const hasGroupSelection = (groups?.length ?? 0) > 0;
2027
+ const hasModuleSelection = (modules?.length ?? 0) > 0;
2028
+ const hasAnySelection = hasGroupSelection || hasModuleSelection;
2029
+ const hasExplicitPatterns = patterns.length > 0;
2030
+ if (hasExplicitPatterns) {
2031
+ if (hasAnySelection && verbose) {
2032
+ const shouldWarn = (() => {
2033
+ const configGroups = config.modules?.groups;
2034
+ if (!configGroups) {
2035
+ return true;
2036
+ }
2037
+ if (hasGroupSelection) {
2038
+ const allowedGroups = new Set(groups);
2039
+ const allowedRoots = Object.entries(configGroups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
2040
+ if (allowedRoots.length > 0) {
2041
+ return patterns.some(
2042
+ (pattern) => !allowedRoots.some((root) => patternIsUnderRoot(pattern, root))
2043
+ );
2044
+ }
2045
+ }
2046
+ return true;
2047
+ })();
2048
+ if (shouldWarn) {
2049
+ console.warn(
2050
+ "[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."
2051
+ );
2052
+ }
2053
+ }
2054
+ return patterns;
2055
+ }
2056
+ const roots = [...config.roots.features];
2057
+ if (hasAnySelection && config.modules?.groups) {
2058
+ const allowedGroups = hasGroupSelection ? new Set(groups) : new Set(Object.keys(config.modules.groups));
2059
+ if (hasModuleSelection && modules && modules.length > 0) {
2060
+ const modulePatterns = [];
2061
+ const relativeRoots = config.modules.relativeRoots?.features ?? [".features/**/*.feature"];
2062
+ for (const [groupName, groupConfig] of Object.entries(config.modules.groups)) {
2063
+ if (!allowedGroups.has(groupName)) {
2064
+ continue;
2065
+ }
2066
+ const groupRoot = groupConfig.root;
2067
+ const groupModules = groupConfig.modules ?? [];
2068
+ for (const moduleName of modules) {
2069
+ const moduleExists = Array.isArray(groupModules) && groupModules.some((m) => {
2070
+ if (typeof m === "string") {
2071
+ return m === moduleName || m.endsWith(`/${moduleName}`) || m.endsWith(`:${moduleName}`);
2072
+ }
2073
+ return false;
2074
+ });
2075
+ if (moduleExists) {
2076
+ for (const relativeRoot of relativeRoots) {
2077
+ const normalizedRelative = relativeRoot.replace(/^\.\//, "");
2078
+ const modulePath = path2.join(groupRoot, moduleName, normalizedRelative);
2079
+ modulePatterns.push(modulePath);
2080
+ }
2081
+ }
2082
+ }
2083
+ }
2084
+ if (modulePatterns.length > 0) {
2085
+ return modulePatterns;
2086
+ }
2087
+ }
2088
+ const allowedRoots = Object.entries(config.modules.groups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
2089
+ const filtered = roots.filter(
2090
+ (pattern) => allowedRoots.some((root) => patternIsUnderRoot(pattern, root))
2091
+ );
2092
+ if (filtered.length > 0) {
2093
+ return filtered;
2094
+ }
2095
+ }
2096
+ return roots;
2097
+ }
2013
2098
  async function orchestrate(options) {
2014
- const { cwd, config, patterns = [], runnerArgs = [], dryRun = false, watch = false, verbose = false } = options;
2099
+ const { cwd, config, patterns = [], runnerArgs = [], dryRun = false, watch = false, verbose = false, groups, modules } = options;
2015
2100
  const runner = detectRunner(config, cwd);
2016
2101
  if (verbose) {
2017
2102
  console.log(`[autometa] Detected runner: ${runner}`);
2018
2103
  }
2104
+ const filteredPatterns = filterPatternsByGroupsAndModules({
2105
+ patterns,
2106
+ groups,
2107
+ modules,
2108
+ config,
2109
+ cwd,
2110
+ verbose
2111
+ });
2019
2112
  switch (runner) {
2020
2113
  case "vitest":
2021
- return spawnVitest({ cwd, patterns, runnerArgs, dryRun, watch, verbose });
2114
+ return spawnVitest({ cwd, patterns: filteredPatterns, runnerArgs, dryRun, watch, verbose });
2022
2115
  case "jest":
2023
- return spawnJest({ cwd, patterns, runnerArgs, dryRun, watch, verbose });
2116
+ return spawnJest({ cwd, patterns: filteredPatterns, runnerArgs, dryRun, watch, verbose });
2024
2117
  case "playwright":
2025
- return spawnPlaywright({ cwd, patterns, runnerArgs, dryRun, watch, verbose });
2118
+ return spawnPlaywright({ cwd, patterns: filteredPatterns, runnerArgs, dryRun, watch, verbose });
2026
2119
  case "default":
2027
2120
  return { success: true, exitCode: 0, runner: "default" };
2028
2121
  }
@@ -2034,7 +2127,8 @@ async function spawnVitest(options) {
2034
2127
  args.push("run");
2035
2128
  }
2036
2129
  if (patterns.length > 0) {
2037
- args.push(...patterns);
2130
+ const expanded = await expandFilePatterns(patterns, cwd);
2131
+ args.push(...expanded.length > 0 ? expanded : patterns);
2038
2132
  }
2039
2133
  if (dryRun) {
2040
2134
  args.push("--passWithNoTests");
@@ -2136,17 +2230,53 @@ var STEP_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"
2136
2230
  var STEP_FALLBACK_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}";
2137
2231
  var FEATURE_FALLBACK_GLOB = "**/*.feature";
2138
2232
  var ROOT_LOAD_ORDER = ["parameterTypes", "support", "hooks", "app"];
2139
- function normalizeGlobLikePath(input) {
2233
+ var GLOB_MAGIC_CHARACTERS = /* @__PURE__ */ new Set(["*", "?", "[", "]", "{", "}", "(", ")", "!"]);
2234
+ function normalizeGlobLikePath2(input) {
2140
2235
  return input.replace(/\\/g, "/").replace(/^\.\//u, "");
2141
2236
  }
2142
- function patternIsUnderRoot(pattern, root) {
2143
- const p = normalizeGlobLikePath(pattern);
2144
- const r = normalizeGlobLikePath(root).replace(/\/+$/u, "");
2237
+ function patternIsUnderRoot2(pattern, root) {
2238
+ const p = normalizeGlobLikePath2(pattern);
2239
+ const r = normalizeGlobLikePath2(root).replace(/\/+$/u, "");
2145
2240
  if (!r) {
2146
2241
  return false;
2147
2242
  }
2148
2243
  return p === r || p.startsWith(`${r}/`);
2149
2244
  }
2245
+ function inferGlobBaseDirectory(pattern) {
2246
+ const normalized = normalizeGlobLikePath2(pattern).replace(/^!/u, "");
2247
+ for (let i = 0; i < normalized.length; i += 1) {
2248
+ if (!GLOB_MAGIC_CHARACTERS.has(normalized[i] ?? "")) {
2249
+ continue;
2250
+ }
2251
+ const prefix = normalized.slice(0, i);
2252
+ const lastSlash = prefix.lastIndexOf("/");
2253
+ if (lastSlash <= 0) {
2254
+ return ".";
2255
+ }
2256
+ const base = prefix.slice(0, lastSlash);
2257
+ return base || ".";
2258
+ }
2259
+ if (path2.extname(normalized)) {
2260
+ return path2.dirname(normalized);
2261
+ }
2262
+ return normalized || ".";
2263
+ }
2264
+ function inferFeatureRootDirectories(patterns, cwd) {
2265
+ if (!patterns || patterns.length === 0) {
2266
+ return [];
2267
+ }
2268
+ const roots = /* @__PURE__ */ new Set();
2269
+ for (const entry of patterns) {
2270
+ const trimmed = entry.trim();
2271
+ if (!trimmed) {
2272
+ continue;
2273
+ }
2274
+ const base = hasGlobMagic(trimmed) ? inferGlobBaseDirectory(trimmed) : trimmed;
2275
+ const resolved = path2.resolve(cwd, base);
2276
+ roots.add(resolved.replace(/\/+$/u, ""));
2277
+ }
2278
+ return Array.from(roots).sort((a, b) => b.length - a.length);
2279
+ }
2150
2280
  function flattenModuleDeclarations(declarations, prefix = []) {
2151
2281
  if (!declarations || declarations.length === 0) {
2152
2282
  return [];
@@ -2230,6 +2360,48 @@ function resolveFileScope(fileAbs, groupIndex) {
2230
2360
  }
2231
2361
  return { kind: "root" };
2232
2362
  }
2363
+ function resolveHoistedDirectoryScope(options) {
2364
+ const rootAbs = options.hoistedFeatureRootsAbs.find(
2365
+ (root) => isPathUnderRoot(options.featureAbsPath, root)
2366
+ );
2367
+ if (!rootAbs) {
2368
+ return { kind: "root" };
2369
+ }
2370
+ const rel = path2.relative(rootAbs, options.featureAbsPath);
2371
+ const segments = normalizePathSegments(rel);
2372
+ const dirSegments = segments.slice(0, -1);
2373
+ if (dirSegments.length === 0) {
2374
+ return { kind: "root" };
2375
+ }
2376
+ const group = dirSegments[0];
2377
+ if (!group) {
2378
+ return { kind: "root" };
2379
+ }
2380
+ const groupEntry = options.groupIndex.find((entry) => entry.group === group);
2381
+ if (!groupEntry) {
2382
+ if (options.strict) {
2383
+ throw new Error(
2384
+ `[autometa] Feature "${options.featureAbsPath}" is under "${rootAbs}" and implies group "${group}", but "${group}" is not declared in config.modules.groups.`
2385
+ );
2386
+ }
2387
+ return { kind: "root" };
2388
+ }
2389
+ const moduleSegments = dirSegments.slice(1);
2390
+ if (moduleSegments.length === 0) {
2391
+ return { kind: "group", group };
2392
+ }
2393
+ for (const modulePath of groupEntry.modulePaths) {
2394
+ if (startsWithSegments(moduleSegments, modulePath)) {
2395
+ return { kind: "module", group, modulePath };
2396
+ }
2397
+ }
2398
+ if (options.strict) {
2399
+ throw new Error(
2400
+ `[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.`
2401
+ );
2402
+ }
2403
+ return { kind: "group", group };
2404
+ }
2233
2405
  function parseScopeOverrideTag(tags) {
2234
2406
  if (!tags || tags.length === 0) {
2235
2407
  return void 0;
@@ -2253,15 +2425,36 @@ function parseScopeOverrideTag(tags) {
2253
2425
  }
2254
2426
  return void 0;
2255
2427
  }
2256
- function resolveFeatureScope(featureAbsPath, feature, groupIndex) {
2257
- const override = parseScopeOverrideTag(feature.tags);
2428
+ function resolveFeatureScope(options) {
2429
+ const override = parseScopeOverrideTag(options.feature.tags);
2430
+ const fileScope = resolveFileScope(options.featureAbsPath, options.groupIndex);
2431
+ const inferred = (() => {
2432
+ if (fileScope.kind !== "root") {
2433
+ return fileScope;
2434
+ }
2435
+ if (options.hoistedFeatureScopingMode !== "directory") {
2436
+ return fileScope;
2437
+ }
2438
+ return resolveHoistedDirectoryScope({
2439
+ featureAbsPath: options.featureAbsPath,
2440
+ hoistedFeatureRootsAbs: options.hoistedFeatureRootsAbs,
2441
+ groupIndex: options.groupIndex,
2442
+ strict: options.hoistedFeatureScopingStrict
2443
+ });
2444
+ })();
2258
2445
  if (override) {
2259
2446
  if (override.modulePath && override.modulePath.length > 0) {
2260
2447
  return { kind: "module", group: override.group, modulePath: override.modulePath };
2261
2448
  }
2449
+ if (inferred.kind === "module" && inferred.group === override.group) {
2450
+ return inferred;
2451
+ }
2452
+ if (inferred.kind === "group" && inferred.group === override.group) {
2453
+ return inferred;
2454
+ }
2262
2455
  return { kind: "group", group: override.group };
2263
2456
  }
2264
- return resolveFileScope(featureAbsPath, groupIndex);
2457
+ return inferred;
2265
2458
  }
2266
2459
  function isVisibleStepScope(stepScope, featureScope) {
2267
2460
  if (featureScope.kind === "root") {
@@ -2354,7 +2547,14 @@ function createFeatureScopePlan(feature, basePlan, options) {
2354
2547
  const scopingMode = options.config.modules?.stepScoping ?? "global";
2355
2548
  const useScopedSteps = scopingMode === "scoped" && options.groupIndex.length > 0;
2356
2549
  const allSteps = Array.from(basePlan.stepsById.values());
2357
- const featureFileScope = useScopedSteps ? resolveFeatureScope(options.featureAbsPath, feature, options.groupIndex) : { kind: "root" };
2550
+ const featureFileScope = useScopedSteps ? resolveFeatureScope({
2551
+ featureAbsPath: options.featureAbsPath,
2552
+ feature,
2553
+ groupIndex: options.groupIndex,
2554
+ hoistedFeatureRootsAbs: options.hoistedFeatureRootsAbs,
2555
+ hoistedFeatureScopingMode: options.config.modules?.hoistedFeatures?.scope ?? "tag",
2556
+ hoistedFeatureScopingStrict: options.config.modules?.hoistedFeatures?.strict ?? true
2557
+ }) : { kind: "root" };
2358
2558
  const visibleSteps = useScopedSteps ? allSteps.filter((definition) => {
2359
2559
  const file = definition.source?.file;
2360
2560
  const stepScope = file ? resolveFileScope(path2.resolve(options.cwd, file), options.groupIndex) : { kind: "root" };
@@ -2481,7 +2681,9 @@ async function runFeatures(options = {}) {
2481
2681
  ...options.runnerArgs ? { runnerArgs: options.runnerArgs } : {},
2482
2682
  ...options.dryRun !== void 0 ? { dryRun: options.dryRun } : {},
2483
2683
  ...options.watch !== void 0 ? { watch: options.watch } : {},
2484
- ...options.verbose !== void 0 ? { verbose: options.verbose } : {}
2684
+ ...options.verbose !== void 0 ? { verbose: options.verbose } : {},
2685
+ groups: options.groups,
2686
+ modules: options.modules
2485
2687
  });
2486
2688
  if (orchestratorResult.runner !== "default") {
2487
2689
  return {
@@ -2523,7 +2725,7 @@ async function runFeatures(options = {}) {
2523
2725
  const allowedRoots = Object.entries(groups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
2524
2726
  if (allowedRoots.length > 0) {
2525
2727
  return patterns.some(
2526
- (pattern) => !allowedRoots.some((root) => patternIsUnderRoot(pattern, root))
2728
+ (pattern) => !allowedRoots.some((root) => patternIsUnderRoot2(pattern, root))
2527
2729
  );
2528
2730
  }
2529
2731
  }
@@ -2544,7 +2746,7 @@ async function runFeatures(options = {}) {
2544
2746
  const allowedGroups = (options.groups?.length ?? 0) > 0 ? new Set(options.groups) : new Set(Object.keys(executorConfig.modules.groups));
2545
2747
  const allowedRoots = Object.entries(executorConfig.modules.groups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
2546
2748
  const filtered = roots.filter(
2547
- (pattern) => allowedRoots.some((root) => patternIsUnderRoot(pattern, root))
2749
+ (pattern) => allowedRoots.some((root) => patternIsUnderRoot2(pattern, root))
2548
2750
  );
2549
2751
  if (filtered.length > 0) {
2550
2752
  return filtered;
@@ -2575,6 +2777,7 @@ async function runFeatures(options = {}) {
2575
2777
  }
2576
2778
  const groupIndex = buildGroupIndex(executorConfig, cwd);
2577
2779
  const environmentIndex = indexStepsEnvironments(stepsEnvironments, cwd, groupIndex);
2780
+ const hoistedFeatureRootsAbs = inferFeatureRootDirectories(executorConfig.roots.features, cwd);
2578
2781
  for (const featurePath of featureFiles) {
2579
2782
  const feature = await readFeatureFile(featurePath, cwd);
2580
2783
  const featureAbsPath = path2.resolve(cwd, featurePath);
@@ -2582,7 +2785,9 @@ async function runFeatures(options = {}) {
2582
2785
  featureAbsPath,
2583
2786
  feature,
2584
2787
  environmentIndex,
2585
- groupIndex
2788
+ groupIndex,
2789
+ executorConfig,
2790
+ hoistedFeatureRootsAbs
2586
2791
  );
2587
2792
  runner.CucumberRunner.setSteps(selectedStepsEnvironment);
2588
2793
  const basePlan = selectedStepsEnvironment.getPlan();
@@ -2590,7 +2795,8 @@ async function runFeatures(options = {}) {
2590
2795
  featureAbsPath,
2591
2796
  cwd,
2592
2797
  config: executorConfig,
2593
- groupIndex
2798
+ groupIndex,
2799
+ hoistedFeatureRootsAbs
2594
2800
  });
2595
2801
  const coordinated = selectedStepsEnvironment.coordinateFeature({
2596
2802
  feature,
@@ -2697,8 +2903,15 @@ function indexStepsEnvironments(environments, cwd, groupIndex) {
2697
2903
  }
2698
2904
  return entries;
2699
2905
  }
2700
- function resolveFeatureStepsEnvironment(featureAbsPath, feature, environments, groupIndex) {
2701
- const featureScope = resolveFeatureScope(featureAbsPath, feature, groupIndex);
2906
+ function resolveFeatureStepsEnvironment(featureAbsPath, feature, environments, groupIndex, config, hoistedFeatureRootsAbs) {
2907
+ const featureScope = resolveFeatureScope({
2908
+ featureAbsPath,
2909
+ feature,
2910
+ groupIndex,
2911
+ hoistedFeatureRootsAbs,
2912
+ hoistedFeatureScopingMode: config.modules?.hoistedFeatures?.scope ?? "tag",
2913
+ hoistedFeatureScopingStrict: config.modules?.hoistedFeatures?.strict ?? true
2914
+ });
2702
2915
  if (featureScope.kind === "root") {
2703
2916
  const root = environments.find((entry) => entry.kind === "root");
2704
2917
  return root?.environment ?? environments[0]?.environment ?? runner.CucumberRunner.steps();