@autometa/cli 1.0.0-rc.4 → 1.0.0-rc.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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
- args.push(...patterns);
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
- function normalizeGlobLikePath(input) {
2229
+ var GLOB_MAGIC_CHARACTERS = /* @__PURE__ */ new Set(["*", "?", "[", "]", "{", "}", "(", ")", "!"]);
2230
+ function normalizeGlobLikePath2(input) {
2136
2231
  return input.replace(/\\/g, "/").replace(/^\.\//u, "");
2137
2232
  }
2138
- function patternIsUnderRoot(pattern, root) {
2139
- const p = normalizeGlobLikePath(pattern);
2140
- const r = normalizeGlobLikePath(root).replace(/\/+$/u, "");
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,27 @@ function parseScopeOverrideTag(tags) {
2249
2421
  }
2250
2422
  return void 0;
2251
2423
  }
2252
- function resolveFeatureScope(featureAbsPath, feature, groupIndex) {
2253
- const override = parseScopeOverrideTag(feature.tags);
2424
+ function resolveFeatureScope(options) {
2425
+ const override = parseScopeOverrideTag(options.feature.tags);
2254
2426
  if (override) {
2255
2427
  if (override.modulePath && override.modulePath.length > 0) {
2256
2428
  return { kind: "module", group: override.group, modulePath: override.modulePath };
2257
2429
  }
2258
2430
  return { kind: "group", group: override.group };
2259
2431
  }
2260
- return resolveFileScope(featureAbsPath, groupIndex);
2432
+ const fileScope = resolveFileScope(options.featureAbsPath, options.groupIndex);
2433
+ if (fileScope.kind !== "root") {
2434
+ return fileScope;
2435
+ }
2436
+ if (options.hoistedFeatureScopingMode !== "directory") {
2437
+ return fileScope;
2438
+ }
2439
+ return resolveHoistedDirectoryScope({
2440
+ featureAbsPath: options.featureAbsPath,
2441
+ hoistedFeatureRootsAbs: options.hoistedFeatureRootsAbs,
2442
+ groupIndex: options.groupIndex,
2443
+ strict: options.hoistedFeatureScopingStrict
2444
+ });
2261
2445
  }
2262
2446
  function isVisibleStepScope(stepScope, featureScope) {
2263
2447
  if (featureScope.kind === "root") {
@@ -2350,7 +2534,14 @@ function createFeatureScopePlan(feature, basePlan, options) {
2350
2534
  const scopingMode = options.config.modules?.stepScoping ?? "global";
2351
2535
  const useScopedSteps = scopingMode === "scoped" && options.groupIndex.length > 0;
2352
2536
  const allSteps = Array.from(basePlan.stepsById.values());
2353
- const featureFileScope = useScopedSteps ? resolveFeatureScope(options.featureAbsPath, feature, options.groupIndex) : { kind: "root" };
2537
+ const featureFileScope = useScopedSteps ? resolveFeatureScope({
2538
+ featureAbsPath: options.featureAbsPath,
2539
+ feature,
2540
+ groupIndex: options.groupIndex,
2541
+ hoistedFeatureRootsAbs: options.hoistedFeatureRootsAbs,
2542
+ hoistedFeatureScopingMode: options.config.modules?.hoistedFeatures?.scope ?? "tag",
2543
+ hoistedFeatureScopingStrict: options.config.modules?.hoistedFeatures?.strict ?? true
2544
+ }) : { kind: "root" };
2354
2545
  const visibleSteps = useScopedSteps ? allSteps.filter((definition) => {
2355
2546
  const file = definition.source?.file;
2356
2547
  const stepScope = file ? resolveFileScope(resolve(options.cwd, file), options.groupIndex) : { kind: "root" };
@@ -2477,7 +2668,9 @@ async function runFeatures(options = {}) {
2477
2668
  ...options.runnerArgs ? { runnerArgs: options.runnerArgs } : {},
2478
2669
  ...options.dryRun !== void 0 ? { dryRun: options.dryRun } : {},
2479
2670
  ...options.watch !== void 0 ? { watch: options.watch } : {},
2480
- ...options.verbose !== void 0 ? { verbose: options.verbose } : {}
2671
+ ...options.verbose !== void 0 ? { verbose: options.verbose } : {},
2672
+ groups: options.groups,
2673
+ modules: options.modules
2481
2674
  });
2482
2675
  if (orchestratorResult.runner !== "default") {
2483
2676
  return {
@@ -2519,7 +2712,7 @@ async function runFeatures(options = {}) {
2519
2712
  const allowedRoots = Object.entries(groups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
2520
2713
  if (allowedRoots.length > 0) {
2521
2714
  return patterns.some(
2522
- (pattern) => !allowedRoots.some((root) => patternIsUnderRoot(pattern, root))
2715
+ (pattern) => !allowedRoots.some((root) => patternIsUnderRoot2(pattern, root))
2523
2716
  );
2524
2717
  }
2525
2718
  }
@@ -2540,7 +2733,7 @@ async function runFeatures(options = {}) {
2540
2733
  const allowedGroups = (options.groups?.length ?? 0) > 0 ? new Set(options.groups) : new Set(Object.keys(executorConfig.modules.groups));
2541
2734
  const allowedRoots = Object.entries(executorConfig.modules.groups).filter(([group]) => allowedGroups.has(group)).map(([, groupConfig]) => groupConfig.root);
2542
2735
  const filtered = roots.filter(
2543
- (pattern) => allowedRoots.some((root) => patternIsUnderRoot(pattern, root))
2736
+ (pattern) => allowedRoots.some((root) => patternIsUnderRoot2(pattern, root))
2544
2737
  );
2545
2738
  if (filtered.length > 0) {
2546
2739
  return filtered;
@@ -2571,6 +2764,7 @@ async function runFeatures(options = {}) {
2571
2764
  }
2572
2765
  const groupIndex = buildGroupIndex(executorConfig, cwd);
2573
2766
  const environmentIndex = indexStepsEnvironments(stepsEnvironments, cwd, groupIndex);
2767
+ const hoistedFeatureRootsAbs = inferFeatureRootDirectories(executorConfig.roots.features, cwd);
2574
2768
  for (const featurePath of featureFiles) {
2575
2769
  const feature = await readFeatureFile(featurePath, cwd);
2576
2770
  const featureAbsPath = resolve(cwd, featurePath);
@@ -2578,7 +2772,9 @@ async function runFeatures(options = {}) {
2578
2772
  featureAbsPath,
2579
2773
  feature,
2580
2774
  environmentIndex,
2581
- groupIndex
2775
+ groupIndex,
2776
+ executorConfig,
2777
+ hoistedFeatureRootsAbs
2582
2778
  );
2583
2779
  CucumberRunner.setSteps(selectedStepsEnvironment);
2584
2780
  const basePlan = selectedStepsEnvironment.getPlan();
@@ -2586,7 +2782,8 @@ async function runFeatures(options = {}) {
2586
2782
  featureAbsPath,
2587
2783
  cwd,
2588
2784
  config: executorConfig,
2589
- groupIndex
2785
+ groupIndex,
2786
+ hoistedFeatureRootsAbs
2590
2787
  });
2591
2788
  const coordinated = selectedStepsEnvironment.coordinateFeature({
2592
2789
  feature,
@@ -2693,8 +2890,15 @@ function indexStepsEnvironments(environments, cwd, groupIndex) {
2693
2890
  }
2694
2891
  return entries;
2695
2892
  }
2696
- function resolveFeatureStepsEnvironment(featureAbsPath, feature, environments, groupIndex) {
2697
- const featureScope = resolveFeatureScope(featureAbsPath, feature, groupIndex);
2893
+ function resolveFeatureStepsEnvironment(featureAbsPath, feature, environments, groupIndex, config, hoistedFeatureRootsAbs) {
2894
+ const featureScope = resolveFeatureScope({
2895
+ featureAbsPath,
2896
+ feature,
2897
+ groupIndex,
2898
+ hoistedFeatureRootsAbs,
2899
+ hoistedFeatureScopingMode: config.modules?.hoistedFeatures?.scope ?? "tag",
2900
+ hoistedFeatureScopingStrict: config.modules?.hoistedFeatures?.strict ?? true
2901
+ });
2698
2902
  if (featureScope.kind === "root") {
2699
2903
  const root = environments.find((entry) => entry.kind === "root");
2700
2904
  return root?.environment ?? environments[0]?.environment ?? CucumberRunner.steps();