@autometa/vitest-plugins 1.0.0-rc.2 → 1.0.0-rc.4

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/index.cjs CHANGED
@@ -10,6 +10,7 @@ var jiti__default = /*#__PURE__*/_interopDefault(jiti);
10
10
 
11
11
  // src/index.ts
12
12
  var STEP_FALLBACK_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}";
13
+ var FEATURE_FALLBACK_GLOB = "**/*.feature";
13
14
  function flattenModuleDeclarations(declarations, prefix = []) {
14
15
  if (!declarations || declarations.length === 0) {
15
16
  return [];
@@ -88,8 +89,14 @@ function autometa() {
88
89
  });
89
90
  const runtimeConfig = JSON.stringify(resolved.config);
90
91
  const stepScopingMode = resolved.config.modules?.stepScoping ?? "global";
92
+ const hoistedFeatureScopingMode = resolved.config.modules?.hoistedFeatures?.scope ?? "tag";
93
+ const hoistedFeatureScopingStrict = resolved.config.modules?.hoistedFeatures?.strict ?? true;
91
94
  const groupIndexData = buildStepScopingData(resolved.config, rootDir);
92
95
  const stepScopingData = stepScopingMode === "scoped" ? groupIndexData : null;
96
+ const featureRootDirs = buildFeatureRootDirs(resolved.config.roots.features, {
97
+ configDir,
98
+ projectRoot: rootDir
99
+ });
93
100
  const featureFile = cleanId;
94
101
  return {
95
102
  code: String.raw`
@@ -103,6 +110,9 @@ function autometa() {
103
110
  const __AUTOMETA_GROUP_INDEX = ${JSON.stringify(groupIndexData)};
104
111
  const __AUTOMETA_STEP_SCOPING = ${JSON.stringify(stepScopingData)};
105
112
  const __AUTOMETA_FEATURE_FILE = ${JSON.stringify(featureFile)};
113
+ const __AUTOMETA_HOISTED_FEATURE_SCOPING_MODE = ${JSON.stringify(hoistedFeatureScopingMode)};
114
+ const __AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT = ${JSON.stringify(hoistedFeatureScopingStrict)};
115
+ const __AUTOMETA_HOISTED_FEATURE_ROOTS = ${JSON.stringify(featureRootDirs)};
106
116
 
107
117
  const __AUTOMETA_EVENT_MODULES = ${eventGlobs.length > 0 ? `import.meta.glob(${JSON.stringify(eventGlobs)}, { eager: true })` : "{}"};
108
118
  const stepModules = import.meta.glob(${JSON.stringify(stepGlobs)}, { eager: true });
@@ -198,6 +208,107 @@ function autometa() {
198
208
  return true;
199
209
  }
200
210
 
211
+ function __isUnderRoot(fileAbs, rootAbs) {
212
+ const rel = rootAbs ? __pathRelative(String(rootAbs), String(fileAbs)) : '';
213
+ return rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\'));
214
+ }
215
+
216
+ function __selectHoistedFeatureRoot(fileAbs) {
217
+ if (!Array.isArray(__AUTOMETA_HOISTED_FEATURE_ROOTS) || __AUTOMETA_HOISTED_FEATURE_ROOTS.length === 0) {
218
+ return undefined;
219
+ }
220
+
221
+ const absoluteFile = __pathIsAbsolute(String(fileAbs))
222
+ ? String(fileAbs)
223
+ : __pathResolve(process.cwd(), String(fileAbs));
224
+
225
+ let best;
226
+ for (const rootAbs of __AUTOMETA_HOISTED_FEATURE_ROOTS) {
227
+ if (!rootAbs) continue;
228
+ if (__isUnderRoot(absoluteFile, rootAbs)) {
229
+ if (!best || String(rootAbs).length > String(best).length) {
230
+ best = rootAbs;
231
+ }
232
+ }
233
+ }
234
+ return best;
235
+ }
236
+
237
+ function __resolveHoistedDirectoryScope(fileAbs, groupIndexData) {
238
+ if (!groupIndexData || !Array.isArray(groupIndexData.groups)) {
239
+ return { kind: 'root' };
240
+ }
241
+
242
+ const rootAbs = __selectHoistedFeatureRoot(fileAbs);
243
+ if (!rootAbs) {
244
+ return { kind: 'root' };
245
+ }
246
+
247
+ const absoluteFile = __pathIsAbsolute(String(fileAbs))
248
+ ? String(fileAbs)
249
+ : __pathResolve(process.cwd(), String(fileAbs));
250
+
251
+ const rel = __pathRelative(String(rootAbs), String(absoluteFile));
252
+ const segments = __normalizePathSegments(rel);
253
+ const dirSegments = segments.slice(0, -1);
254
+ if (dirSegments.length === 0) {
255
+ return { kind: 'root' };
256
+ }
257
+
258
+ const group = dirSegments[0];
259
+ if (!group) {
260
+ return { kind: 'root' };
261
+ }
262
+
263
+ const groupEntry = groupIndexData.groups.find((entry) => entry && entry.group === group);
264
+ if (!groupEntry) {
265
+ if (__AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT) {
266
+ throw new Error(
267
+ '[autometa] Feature "' +
268
+ absoluteFile +
269
+ '" is under "' +
270
+ rootAbs +
271
+ '" and implies group "' +
272
+ group +
273
+ '", but "' +
274
+ group +
275
+ '" is not declared in config.modules.groups.'
276
+ );
277
+ }
278
+ return { kind: 'root' };
279
+ }
280
+
281
+ const moduleSegments = dirSegments.slice(1);
282
+ if (moduleSegments.length === 0) {
283
+ return { kind: 'group', group };
284
+ }
285
+
286
+ const modulePaths = groupEntry.modulePaths || [];
287
+ for (const modulePath of modulePaths) {
288
+ if (__startsWithSegments(moduleSegments, modulePath)) {
289
+ return { kind: 'module', group, modulePath };
290
+ }
291
+ }
292
+
293
+ if (__AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT) {
294
+ throw new Error(
295
+ '[autometa] Feature "' +
296
+ absoluteFile +
297
+ '" is under "' +
298
+ rootAbs +
299
+ '" and implies module "' +
300
+ group +
301
+ ':' +
302
+ moduleSegments.join(':') +
303
+ '", but no matching module is declared in config.modules.groups.' +
304
+ group +
305
+ '.modules.'
306
+ );
307
+ }
308
+
309
+ return { kind: 'group', group };
310
+ }
311
+
201
312
  function __resolveFileScope(fileAbs) {
202
313
  if (!__AUTOMETA_GROUP_INDEX || !__AUTOMETA_GROUP_INDEX.groups) {
203
314
  return { kind: 'root' };
@@ -252,7 +363,14 @@ function autometa() {
252
363
  }
253
364
  return { kind: 'group', group: override.group };
254
365
  }
255
- return __resolveFileScope(__AUTOMETA_FEATURE_FILE);
366
+ const fileScope = __resolveFileScope(__AUTOMETA_FEATURE_FILE);
367
+ if (fileScope.kind !== 'root') {
368
+ return fileScope;
369
+ }
370
+ if (__AUTOMETA_HOISTED_FEATURE_SCOPING_MODE !== 'directory') {
371
+ return fileScope;
372
+ }
373
+ return __resolveHoistedDirectoryScope(__AUTOMETA_FEATURE_FILE, __AUTOMETA_GROUP_INDEX);
256
374
  }
257
375
 
258
376
  function __inferEnvironmentGroup(environment) {
@@ -402,7 +520,14 @@ function autometa() {
402
520
  }
403
521
  return { kind: 'group', group: override.group };
404
522
  }
405
- return resolveFileScope(__AUTOMETA_FEATURE_FILE);
523
+ const fileScope = resolveFileScope(__AUTOMETA_FEATURE_FILE);
524
+ if (fileScope.kind !== 'root') {
525
+ return fileScope;
526
+ }
527
+ if (__AUTOMETA_HOISTED_FEATURE_SCOPING_MODE !== 'directory') {
528
+ return fileScope;
529
+ }
530
+ return __resolveHoistedDirectoryScope(__AUTOMETA_FEATURE_FILE, __AUTOMETA_STEP_SCOPING);
406
531
  }
407
532
 
408
533
  function isVisibleStepScope(stepScope, featureScope) {
@@ -582,6 +707,24 @@ function autometa() {
582
707
  }
583
708
  };
584
709
  }
710
+ function buildFeatureRootDirs(entries, options) {
711
+ if (!entries || entries.length === 0) {
712
+ return [];
713
+ }
714
+ const roots = /* @__PURE__ */ new Set();
715
+ for (const entry of entries) {
716
+ const normalized = entry.trim();
717
+ if (!normalized) {
718
+ continue;
719
+ }
720
+ for (const candidate of toPatterns(normalized, FEATURE_FALLBACK_GLOB)) {
721
+ const base = inferGlobBaseDirectory(candidate);
722
+ const absolute = path.isAbsolute(base) ? normalizeSlashes(base) : normalizeSlashes(path.resolve(options.configDir, base));
723
+ roots.add(absolute.replace(/\/+$/u, ""));
724
+ }
725
+ }
726
+ return Array.from(roots).sort((a, b) => b.length - a.length);
727
+ }
585
728
  function buildStepGlobs(entries, options) {
586
729
  if (!entries || entries.length === 0) {
587
730
  return [];
@@ -606,6 +749,25 @@ function toPatterns(entry, fallbackGlob) {
606
749
  }
607
750
  return [appendGlob(entry, fallbackGlob)];
608
751
  }
752
+ function inferGlobBaseDirectory(pattern) {
753
+ const normalized = normalizeSlashes(pattern).replace(/^!/u, "");
754
+ for (let i = 0; i < normalized.length; i += 1) {
755
+ if (!hasGlobMagic(normalized[i] ?? "")) {
756
+ continue;
757
+ }
758
+ const prefix = normalized.slice(0, i);
759
+ const lastSlash = prefix.lastIndexOf("/");
760
+ if (lastSlash <= 0) {
761
+ return ".";
762
+ }
763
+ const base = prefix.slice(0, lastSlash);
764
+ return base || ".";
765
+ }
766
+ if (hasFileExtension(normalized)) {
767
+ return path.dirname(normalized);
768
+ }
769
+ return normalized || ".";
770
+ }
609
771
  function hasGlobMagic(input) {
610
772
  return /[*?{}()[\]!,@+]/u.test(input);
611
773
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["next"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","sourcesContent":["import { resolve, isAbsolute, relative, dirname, extname } from \"path\";\nimport { existsSync } from \"fs\";\nimport type { Plugin } from \"vite\";\nimport type { Config } from \"@autometa/config\";\nimport jiti from \"jiti\";\n\nconst STEP_FALLBACK_GLOB = \"**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}\";\n\ntype ModuleDeclaration =\n | string\n | { readonly name: string; readonly submodules?: readonly ModuleDeclaration[] | undefined };\n\nfunction flattenModuleDeclarations(\n declarations: readonly ModuleDeclaration[] | undefined,\n prefix: readonly string[] = []\n): readonly (readonly string[])[] {\n if (!declarations || declarations.length === 0) {\n return [];\n }\n\n const results: string[][] = [];\n\n for (const entry of declarations) {\n if (typeof entry === \"string\") {\n const next = [...prefix, entry];\n results.push(next);\n continue;\n }\n\n const next = [...prefix, entry.name];\n results.push(next);\n\n const nested = flattenModuleDeclarations(entry.submodules ?? undefined, next);\n for (const path of nested) {\n results.push([...path]);\n }\n }\n\n const unique = new Map<string, readonly string[]>();\n for (const path of results) {\n unique.set(path.join(\"/\"), path);\n }\n\n return Array.from(unique.values()).sort((a, b) => b.length - a.length);\n}\n\nfunction buildStepScopingData(resolvedConfig: unknown, projectRoot: string): unknown {\n const groups = (resolvedConfig as { modules?: { groups?: unknown } } | null)?.modules?.groups;\n if (!groups || typeof groups !== \"object\") {\n return null;\n }\n\n const entries = Object.entries(groups as Record<string, unknown>).map(([group, groupConfig]) => {\n const configRecord =\n groupConfig && typeof groupConfig === \"object\" ? (groupConfig as Record<string, unknown>) : {};\n const rootAbs = resolve(projectRoot, String(configRecord.root ?? \"\"));\n const modules = configRecord.modules;\n const modulePaths = Array.isArray(modules)\n ? flattenModuleDeclarations(modules as ModuleDeclaration[])\n : [];\n return { group, rootAbs, modulePaths };\n });\n\n return { groups: entries };\n}\n\nexport function autometa(): Plugin {\n let autometaConfig: Config | undefined;\n let configPath: string | undefined;\n let projectRoot: string | undefined;\n\n return {\n name: \"autometa-vitest-plugin\",\n enforce: \"pre\",\n async configResolved(viteConfig) {\n projectRoot = viteConfig.root || process.cwd();\n const loaded = await loadAutometaConfig(projectRoot);\n autometaConfig = loaded.config;\n configPath = loaded.path;\n },\n transform(code, id) {\n const cleanId = id.split(\"?\", 1)[0] ?? id;\n\n if (!cleanId.endsWith(\".feature\")) {\n return;\n }\n\n if (!autometaConfig) {\n throw new Error(\"Autometa config not found\");\n }\n\n const resolved = autometaConfig.resolve();\n const stepRoots = resolved.config.roots.steps;\n const configDir = configPath ? dirname(configPath) : projectRoot ?? process.cwd();\n const rootDir = projectRoot ?? process.cwd();\n const stepGlobs = buildStepGlobs(stepRoots, {\n configDir,\n projectRoot: rootDir,\n });\n if (stepGlobs.length === 0) {\n throw new Error(\n \"Autometa config did not resolve any step files within the current project root.\"\n );\n }\n\n const eventGlobs = buildStepGlobs(resolved.config.events, {\n configDir,\n projectRoot: rootDir,\n });\n\n const runtimeConfig = JSON.stringify(resolved.config);\n const stepScopingMode = resolved.config.modules?.stepScoping ?? \"global\";\n\n // Group/module index data is useful even when step scoping is disabled,\n // since we may need it to select the correct steps environment.\n const groupIndexData = buildStepScopingData(resolved.config, rootDir);\n const stepScopingData = stepScopingMode === \"scoped\" ? groupIndexData : null;\n\n const featureFile = cleanId;\n\n return {\n code: String.raw`\n import { describe } from 'vitest';\n import { execute } from '@autometa/vitest-executor';\n import { coordinateRunnerFeature, CucumberRunner, STEPS_ENVIRONMENT_META } from '@autometa/runner';\n import { parseGherkin } from '@autometa/gherkin';\n import { relative as __pathRelative, resolve as __pathResolve, isAbsolute as __pathIsAbsolute } from 'node:path';\n\n const __AUTOMETA_STEP_SCOPING_MODE = ${JSON.stringify(stepScopingMode)};\n const __AUTOMETA_GROUP_INDEX = ${JSON.stringify(groupIndexData)};\n const __AUTOMETA_STEP_SCOPING = ${JSON.stringify(stepScopingData)};\n const __AUTOMETA_FEATURE_FILE = ${JSON.stringify(featureFile)};\n\n const __AUTOMETA_EVENT_MODULES = ${eventGlobs.length > 0\n ? `import.meta.glob(${JSON.stringify(eventGlobs)}, { eager: true })`\n : \"{}\"};\n const stepModules = import.meta.glob(${JSON.stringify(stepGlobs)}, { eager: true });\n\n function collectCandidateModules(imported) {\n if (!imported || typeof imported !== 'object') {\n return [];\n }\n\n const modules = new Set();\n modules.add(imported);\n\n const exportedModules = imported.modules;\n if (Array.isArray(exportedModules)) {\n for (const entry of exportedModules) {\n if (entry && typeof entry === 'object') {\n modules.add(entry);\n }\n }\n }\n\n const defaultExport = imported.default;\n if (Array.isArray(defaultExport)) {\n for (const entry of defaultExport) {\n if (entry && typeof entry === 'object') {\n modules.add(entry);\n }\n }\n } else if (defaultExport && typeof defaultExport === 'object') {\n modules.add(defaultExport);\n }\n\n return Array.from(modules);\n }\n\n function isStepsEnvironment(candidate) {\n return Boolean(\n candidate &&\n typeof candidate === 'object' &&\n typeof candidate.coordinateFeature === 'function' &&\n typeof candidate.getPlan === 'function' &&\n typeof candidate.Given === 'function' &&\n typeof candidate.When === 'function' &&\n typeof candidate.Then === 'function'\n );\n }\n\n function extractStepsEnvironments(candidate) {\n const environments = [];\n\n if (!candidate || typeof candidate !== 'object') {\n return environments;\n }\n\n if (isStepsEnvironment(candidate)) {\n environments.push(candidate);\n }\n\n const stepsEnv = candidate.stepsEnvironment;\n if (isStepsEnvironment(stepsEnv)) {\n environments.push(stepsEnv);\n }\n\n const defaultExport = candidate.default;\n if (isStepsEnvironment(defaultExport)) {\n environments.push(defaultExport);\n }\n\n return environments;\n }\n\n function collectStepsEnvironments(modules) {\n const environments = new Set();\n for (const moduleExports of Object.values(modules)) {\n for (const candidate of collectCandidateModules(moduleExports)) {\n for (const env of extractStepsEnvironments(candidate)) {\n environments.add(env);\n }\n }\n }\n return Array.from(environments);\n }\n\n function __normalizePathSegments(input) {\n return String(input).replace(/\\\\/g, '/').split('/').filter(Boolean);\n }\n\n function __startsWithSegments(haystack, needle) {\n if (needle.length > haystack.length) return false;\n for (let i = 0; i < needle.length; i += 1) {\n if (haystack[i] !== needle[i]) return false;\n }\n return true;\n }\n\n function __resolveFileScope(fileAbs) {\n if (!__AUTOMETA_GROUP_INDEX || !__AUTOMETA_GROUP_INDEX.groups) {\n return { kind: 'root' };\n }\n if (String(fileAbs).startsWith('node:')) {\n return { kind: 'root' };\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n for (const entry of __AUTOMETA_GROUP_INDEX.groups) {\n const rootAbs = entry.rootAbs;\n const rel = rootAbs ? __pathRelative(rootAbs, absoluteFile) : '';\n if (rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\\\'))) {\n const segments = __normalizePathSegments(rel);\n const modulePaths = entry.modulePaths || [];\n for (const modulePath of modulePaths) {\n if (__startsWithSegments(segments, modulePath)) {\n return { kind: 'module', group: entry.group, modulePath };\n }\n }\n return { kind: 'group', group: entry.group };\n }\n }\n return { kind: 'root' };\n }\n\n function __parseScopeOverrideTag(tags) {\n if (!Array.isArray(tags)) return undefined;\n for (const tag of tags) {\n const match = String(tag).match(/^@scope(?::|=|\\()(.+?)(?:\\))?$/u);\n if (!match) continue;\n const raw = String(match[1] ?? '').trim();\n if (!raw) continue;\n const normalized = raw.replace(/\\//g, ':');\n const parts = normalized.split(':').filter(Boolean);\n const group = parts[0];\n const rest = parts.slice(1);\n if (!group) continue;\n return rest.length > 0 ? { group, modulePath: rest } : { group };\n }\n return undefined;\n }\n\n function __resolveFeatureScope(feature) {\n const override = __parseScopeOverrideTag(feature.tags);\n if (override) {\n if (override.modulePath && override.modulePath.length > 0) {\n return { kind: 'module', group: override.group, modulePath: override.modulePath };\n }\n return { kind: 'group', group: override.group };\n }\n return __resolveFileScope(__AUTOMETA_FEATURE_FILE);\n }\n\n function __inferEnvironmentGroup(environment) {\n const meta = environment && typeof environment === 'object'\n ? environment[STEPS_ENVIRONMENT_META]\n : undefined;\n if (meta && typeof meta === 'object') {\n if (meta.kind === 'group' && typeof meta.group === 'string' && meta.group.trim().length > 0) {\n return { kind: 'group', group: meta.group };\n }\n if (meta.kind === 'root') {\n return { kind: 'root' };\n }\n }\n\n const plan = environment.getPlan();\n const groups = new Set();\n for (const def of plan.stepsById.values()) {\n const file = def && def.source ? def.source.file : undefined;\n if (!file) continue;\n const scope = __resolveFileScope(file);\n if (scope.kind === 'group' || scope.kind === 'module') {\n groups.add(scope.group);\n if (groups.size > 1) return { kind: 'ambiguous' };\n }\n }\n const only = Array.from(groups.values())[0];\n return only ? { kind: 'group', group: only } : { kind: 'root' };\n }\n\n function selectStepsEnvironment(environments, feature) {\n if (!Array.isArray(environments) || environments.length === 0) {\n return undefined;\n }\n if (environments.length === 1) {\n return environments[0];\n }\n\n const featureScope = __resolveFeatureScope(feature);\n const indexed = environments\n .map((env) => ({ env, inferred: __inferEnvironmentGroup(env) }))\n .filter((entry) => entry.inferred.kind !== 'ambiguous');\n\n if (featureScope.kind === 'root') {\n const root = indexed.find((entry) => entry.inferred.kind === 'root');\n return (root ? root.env : indexed[0]?.env) ?? environments[0];\n }\n\n const match = indexed.find((entry) => entry.inferred.kind === 'group' && entry.inferred.group === featureScope.group);\n return match ? match.env : undefined;\n }\n\n function isScenario(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'steps' in element &&\n !('exampleGroups' in element) &&\n !('elements' in element)\n );\n }\n\n function isScenarioOutline(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'steps' in element &&\n 'exampleGroups' in element\n );\n }\n\n function isRule(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'elements' in element &&\n Array.isArray(element.elements)\n );\n }\n\n function createFeatureScopePlan(feature, basePlan) {\n const allSteps = Array.from(basePlan.stepsById.values());\n\n function normalizePathSegments(input) {\n return String(input).replace(/\\\\/g, '/').split('/').filter(Boolean);\n }\n\n function startsWithSegments(haystack, needle) {\n if (needle.length > haystack.length) return false;\n for (let i = 0; i < needle.length; i += 1) {\n if (haystack[i] !== needle[i]) return false;\n }\n return true;\n }\n\n function resolveFileScope(fileAbs) {\n if (!__AUTOMETA_STEP_SCOPING || !__AUTOMETA_STEP_SCOPING.groups) {\n return { kind: 'root' };\n }\n if (String(fileAbs).startsWith('node:')) {\n return { kind: 'root' };\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n for (const entry of __AUTOMETA_STEP_SCOPING.groups) {\n const rootAbs = entry.rootAbs;\n const rel = rootAbs ? __pathRelative(rootAbs, absoluteFile) : '';\n if (rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\\\'))) {\n const segments = normalizePathSegments(rel);\n const modulePaths = entry.modulePaths || [];\n for (const modulePath of modulePaths) {\n if (startsWithSegments(segments, modulePath)) {\n return { kind: 'module', group: entry.group, modulePath };\n }\n }\n return { kind: 'group', group: entry.group };\n }\n }\n return { kind: 'root' };\n }\n\n function parseScopeOverrideTag(tags) {\n if (!Array.isArray(tags)) return undefined;\n for (const tag of tags) {\n const match = String(tag).match(/^@scope(?::|=|\\()(.+?)(?:\\))?$/u);\n if (!match) continue;\n const raw = String(match[1] ?? '').trim();\n if (!raw) continue;\n const normalized = raw.replace(/\\//g, ':');\n const parts = normalized.split(':').filter(Boolean);\n const group = parts[0];\n const rest = parts.slice(1);\n if (!group) continue;\n return rest.length > 0 ? { group, modulePath: rest } : { group };\n }\n return undefined;\n }\n\n function resolveFeatureScope() {\n const override = parseScopeOverrideTag(feature.tags);\n if (override) {\n if (override.modulePath && override.modulePath.length > 0) {\n return { kind: 'module', group: override.group, modulePath: override.modulePath };\n }\n return { kind: 'group', group: override.group };\n }\n return resolveFileScope(__AUTOMETA_FEATURE_FILE);\n }\n\n function isVisibleStepScope(stepScope, featureScope) {\n if (featureScope.kind === 'root') {\n return stepScope.kind === 'root';\n }\n if (featureScope.kind === 'group') {\n if (stepScope.kind === 'root') return true;\n return stepScope.kind === 'group' && stepScope.group === featureScope.group;\n }\n // module\n if (stepScope.kind === 'root') return true;\n if (stepScope.kind === 'group') return stepScope.group === featureScope.group;\n if (stepScope.kind === 'module') {\n return stepScope.group === featureScope.group && startsWithSegments(featureScope.modulePath, stepScope.modulePath);\n }\n return false;\n }\n\n function stepScopeRank(scope) {\n if (scope.kind === 'module') return 200 + (scope.modulePath ? scope.modulePath.length : 0);\n if (scope.kind === 'group') return 100;\n return 0;\n }\n\n const useScopedSteps = __AUTOMETA_STEP_SCOPING_MODE === 'scoped' && __AUTOMETA_STEP_SCOPING && __AUTOMETA_STEP_SCOPING.groups;\n const featureVisibilityScope = useScopedSteps ? resolveFeatureScope() : { kind: 'root' };\n const visibleSteps = useScopedSteps\n ? allSteps\n .filter((definition) => {\n const file = definition && definition.source ? definition.source.file : undefined;\n const scope = file ? resolveFileScope(file) : { kind: 'root' };\n return isVisibleStepScope(scope, featureVisibilityScope);\n })\n .sort((a, b) => {\n const aFile = a && a.source ? a.source.file : undefined;\n const bFile = b && b.source ? b.source.file : undefined;\n const aScope = aFile ? resolveFileScope(aFile) : { kind: 'root' };\n const bScope = bFile ? resolveFileScope(bFile) : { kind: 'root' };\n const delta = stepScopeRank(bScope) - stepScopeRank(aScope);\n return delta !== 0 ? delta : String(a.id).localeCompare(String(b.id));\n })\n : allSteps;\n\n const scopedStepsById = useScopedSteps\n ? (() => {\n const allowed = new Set(visibleSteps.map((s) => s.id));\n const next = new Map();\n for (const [id, def] of basePlan.stepsById.entries()) {\n if (allowed.has(id)) next.set(id, def);\n }\n return next;\n })()\n : basePlan.stepsById;\n const featureChildren = [];\n const scopesById = new Map(basePlan.scopesById);\n\n for (const element of feature.elements ?? []) {\n if (isScenario(element) || isScenarioOutline(element)) {\n const scenarioScope = {\n id: element.id ?? element.name,\n kind: isScenarioOutline(element) ? 'scenarioOutline' : 'scenario',\n name: element.name,\n mode: 'default',\n tags: element.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: [],\n pending: false,\n };\n featureChildren.push(scenarioScope);\n scopesById.set(scenarioScope.id, scenarioScope);\n continue;\n }\n\n if (isRule(element)) {\n const ruleChildren = [];\n for (const ruleElement of element.elements ?? []) {\n if (isScenario(ruleElement) || isScenarioOutline(ruleElement)) {\n const scenarioScope = {\n id: ruleElement.id ?? ruleElement.name,\n kind: isScenarioOutline(ruleElement) ? 'scenarioOutline' : 'scenario',\n name: ruleElement.name,\n mode: 'default',\n tags: ruleElement.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: [],\n pending: false,\n };\n ruleChildren.push(scenarioScope);\n scopesById.set(scenarioScope.id, scenarioScope);\n }\n }\n\n const ruleScope = {\n id: element.id ?? element.name,\n kind: 'rule',\n name: element.name,\n mode: 'default',\n tags: element.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: ruleChildren,\n pending: false,\n };\n featureChildren.push(ruleScope);\n scopesById.set(ruleScope.id, ruleScope);\n }\n }\n\n const featureScope = {\n id: feature.uri ?? feature.name,\n kind: 'feature',\n name: feature.name,\n mode: 'default',\n tags: feature.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: featureChildren,\n pending: false,\n };\n\n const existingRoot = basePlan.root;\n const updatedRoot = {\n ...existingRoot,\n children: [...existingRoot.children, featureScope],\n };\n\n scopesById.set(featureScope.id, featureScope);\n scopesById.set(updatedRoot.id, updatedRoot);\n\n const scopePlan = {\n root: updatedRoot,\n stepsById: scopedStepsById,\n hooksById: basePlan.hooksById,\n scopesById,\n };\n\n if (basePlan.worldFactory) {\n scopePlan.worldFactory = basePlan.worldFactory;\n }\n\n if (basePlan.parameterRegistry) {\n scopePlan.parameterRegistry = basePlan.parameterRegistry;\n }\n\n return scopePlan;\n }\n\n const gherkin = ${JSON.stringify(code)};\n const feature = parseGherkin(gherkin);\n const environments = collectStepsEnvironments(stepModules);\n const steps = selectStepsEnvironment(environments, feature);\n\n if (!steps) {\n throw new Error('Autometa could not find a steps environment for this feature. If you are using per-group environments, ensure each group exports a steps environment (\"stepsEnvironment\" or default export) under the configured step roots.');\n }\n\n CucumberRunner.setSteps(steps);\n\n describe(feature.name, () => {\n const basePlan = steps.getPlan();\n const scopedPlan = createFeatureScopePlan(feature, basePlan);\n const { plan, adapter } = coordinateRunnerFeature({\n feature,\n environment: steps,\n config: ${runtimeConfig},\n plan: scopedPlan\n });\n\n execute({ plan, adapter, config: ${runtimeConfig} });\n });\n `,\n map: null,\n };\n },\n };\n}\n\ninterface StepGlobOptions {\n readonly configDir: string;\n readonly projectRoot: string;\n}\n\nfunction buildStepGlobs(\n entries: readonly string[] | undefined,\n options: StepGlobOptions\n): string[] {\n if (!entries || entries.length === 0) {\n return [];\n }\n\n const patterns = new Set<string>();\n for (const entry of entries) {\n const normalized = entry.trim();\n if (!normalized) {\n continue;\n }\n\n for (const candidate of toPatterns(normalized, STEP_FALLBACK_GLOB)) {\n const absolute = isAbsolute(candidate)\n ? normalizeSlashes(candidate)\n : normalizeSlashes(resolve(options.configDir, candidate));\n const rootRelative = toRootRelativeGlob(absolute, options.projectRoot);\n patterns.add(rootRelative);\n }\n }\n\n return Array.from(patterns);\n}\n\nfunction toPatterns(entry: string, fallbackGlob: string): string[] {\n if (hasGlobMagic(entry) || hasFileExtension(entry)) {\n return [entry];\n }\n return [appendGlob(entry, fallbackGlob)];\n}\n\nfunction hasGlobMagic(input: string): boolean {\n return /[*?{}()[\\]!,@+]/u.test(input);\n}\n\nfunction hasFileExtension(input: string): boolean {\n const normalized = normalizeSlashes(input);\n const trimmed = normalized === \"/\" ? normalized : normalized.replace(/\\/+$/u, \"\");\n if (!trimmed || trimmed === \".\" || trimmed === \"..\") {\n return false;\n }\n return Boolean(extname(trimmed));\n}\n\nfunction appendGlob(entry: string, glob: string): string {\n const normalized = normalizeSlashes(entry);\n const trimmed = normalized === \"/\" ? normalized : normalized.replace(/\\/+$/u, \"\");\n if (!trimmed || trimmed === \".\") {\n return glob;\n }\n if (trimmed === \"/\") {\n return `/${glob}`;\n }\n return `${trimmed}/${glob}`;\n}\n\nfunction toRootRelativeGlob(pattern: string, rootDir: string): string {\n const normalizedRoot = normalizeSlashes(rootDir);\n const normalizedPattern = normalizeSlashes(pattern);\n let relativePattern = normalizeSlashes(relative(normalizedRoot, normalizedPattern));\n\n if (!relativePattern || relativePattern === \".\") {\n relativePattern = \"\";\n }\n\n if (!relativePattern || relativePattern.startsWith(\"..\")) {\n return ensureGlobPrefix(normalizedPattern);\n }\n\n return ensureGlobPrefix(relativePattern);\n}\n\nfunction ensureGlobPrefix(pattern: string): string {\n if (/^[A-Za-z]:\\//u.test(pattern)) {\n return pattern;\n }\n if (pattern.startsWith(\"/\")) {\n return pattern;\n }\n return `/${pattern.replace(/^\\/+/, \"\")}`;\n}\n\nfunction normalizeSlashes(pathname: string): string {\n return pathname.replace(/\\\\/gu, \"/\");\n}\n\nasync function loadAutometaConfig(root: string): Promise<{ config: Config; path: string }> {\n const _jiti = jiti(root, { interopDefault: true });\n\n const candidates = [\n \"autometa.config.ts\",\n \"autometa.config.js\",\n \"autometa.config.mts\",\n \"autometa.config.mjs\",\n \"autometa.config.cts\",\n \"autometa.config.cjs\",\n ];\n\n for (const candidate of candidates) {\n const path = resolve(root, candidate);\n if (!existsSync(path)) {\n continue;\n }\n\n try {\n const mod = _jiti(path);\n const config = mod.default || mod;\n\n if (isConfig(config)) {\n return { config, path };\n } else {\n console.warn(`Found config at ${path} but it does not export a Config instance.`);\n }\n } catch (error: unknown) {\n console.error(`Error loading config at ${path}:`, error);\n throw error;\n }\n }\n throw new Error(\"Could not find autometa.config.{ts,js,mjs,cjs} in \" + root);\n}\n\nfunction isConfig(config: unknown): config is Config {\n return (\n typeof config === \"object\" &&\n config !== null &&\n \"resolve\" in config &&\n typeof (config as Config).resolve === \"function\" &&\n \"current\" in config &&\n typeof (config as Config).current === \"function\"\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["next"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","sourcesContent":["import { resolve, isAbsolute, relative, dirname, extname } from \"path\";\nimport { existsSync } from \"fs\";\nimport type { Plugin } from \"vite\";\nimport type { Config } from \"@autometa/config\";\nimport jiti from \"jiti\";\n\nconst STEP_FALLBACK_GLOB = \"**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}\";\nconst FEATURE_FALLBACK_GLOB = \"**/*.feature\";\n\ntype ModuleDeclaration =\n | string\n | { readonly name: string; readonly submodules?: readonly ModuleDeclaration[] | undefined };\n\nfunction flattenModuleDeclarations(\n declarations: readonly ModuleDeclaration[] | undefined,\n prefix: readonly string[] = []\n): readonly (readonly string[])[] {\n if (!declarations || declarations.length === 0) {\n return [];\n }\n\n const results: string[][] = [];\n\n for (const entry of declarations) {\n if (typeof entry === \"string\") {\n const next = [...prefix, entry];\n results.push(next);\n continue;\n }\n\n const next = [...prefix, entry.name];\n results.push(next);\n\n const nested = flattenModuleDeclarations(entry.submodules ?? undefined, next);\n for (const path of nested) {\n results.push([...path]);\n }\n }\n\n const unique = new Map<string, readonly string[]>();\n for (const path of results) {\n unique.set(path.join(\"/\"), path);\n }\n\n return Array.from(unique.values()).sort((a, b) => b.length - a.length);\n}\n\nfunction buildStepScopingData(resolvedConfig: unknown, projectRoot: string): unknown {\n const groups = (resolvedConfig as { modules?: { groups?: unknown } } | null)?.modules?.groups;\n if (!groups || typeof groups !== \"object\") {\n return null;\n }\n\n const entries = Object.entries(groups as Record<string, unknown>).map(([group, groupConfig]) => {\n const configRecord =\n groupConfig && typeof groupConfig === \"object\" ? (groupConfig as Record<string, unknown>) : {};\n const rootAbs = resolve(projectRoot, String(configRecord.root ?? \"\"));\n const modules = configRecord.modules;\n const modulePaths = Array.isArray(modules)\n ? flattenModuleDeclarations(modules as ModuleDeclaration[])\n : [];\n return { group, rootAbs, modulePaths };\n });\n\n return { groups: entries };\n}\n\nexport function autometa(): Plugin {\n let autometaConfig: Config | undefined;\n let configPath: string | undefined;\n let projectRoot: string | undefined;\n\n return {\n name: \"autometa-vitest-plugin\",\n enforce: \"pre\",\n async configResolved(viteConfig) {\n projectRoot = viteConfig.root || process.cwd();\n const loaded = await loadAutometaConfig(projectRoot);\n autometaConfig = loaded.config;\n configPath = loaded.path;\n },\n transform(code, id) {\n const cleanId = id.split(\"?\", 1)[0] ?? id;\n\n if (!cleanId.endsWith(\".feature\")) {\n return;\n }\n\n if (!autometaConfig) {\n throw new Error(\"Autometa config not found\");\n }\n\n const resolved = autometaConfig.resolve();\n const stepRoots = resolved.config.roots.steps;\n const configDir = configPath ? dirname(configPath) : projectRoot ?? process.cwd();\n const rootDir = projectRoot ?? process.cwd();\n const stepGlobs = buildStepGlobs(stepRoots, {\n configDir,\n projectRoot: rootDir,\n });\n if (stepGlobs.length === 0) {\n throw new Error(\n \"Autometa config did not resolve any step files within the current project root.\"\n );\n }\n\n const eventGlobs = buildStepGlobs(resolved.config.events, {\n configDir,\n projectRoot: rootDir,\n });\n\n const runtimeConfig = JSON.stringify(resolved.config);\n const stepScopingMode = resolved.config.modules?.stepScoping ?? \"global\";\n const hoistedFeatureScopingMode = resolved.config.modules?.hoistedFeatures?.scope ?? \"tag\";\n const hoistedFeatureScopingStrict = resolved.config.modules?.hoistedFeatures?.strict ?? true;\n\n // Group/module index data is useful even when step scoping is disabled,\n // since we may need it to select the correct steps environment.\n const groupIndexData = buildStepScopingData(resolved.config, rootDir);\n const stepScopingData = stepScopingMode === \"scoped\" ? groupIndexData : null;\n const featureRootDirs = buildFeatureRootDirs(resolved.config.roots.features, {\n configDir,\n projectRoot: rootDir,\n });\n\n const featureFile = cleanId;\n\n return {\n code: String.raw`\n import { describe } from 'vitest';\n import { execute } from '@autometa/vitest-executor';\n import { coordinateRunnerFeature, CucumberRunner, STEPS_ENVIRONMENT_META } from '@autometa/runner';\n import { parseGherkin } from '@autometa/gherkin';\n import { relative as __pathRelative, resolve as __pathResolve, isAbsolute as __pathIsAbsolute } from 'node:path';\n\n const __AUTOMETA_STEP_SCOPING_MODE = ${JSON.stringify(stepScopingMode)};\n const __AUTOMETA_GROUP_INDEX = ${JSON.stringify(groupIndexData)};\n const __AUTOMETA_STEP_SCOPING = ${JSON.stringify(stepScopingData)};\n const __AUTOMETA_FEATURE_FILE = ${JSON.stringify(featureFile)};\n const __AUTOMETA_HOISTED_FEATURE_SCOPING_MODE = ${JSON.stringify(hoistedFeatureScopingMode)};\n const __AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT = ${JSON.stringify(hoistedFeatureScopingStrict)};\n const __AUTOMETA_HOISTED_FEATURE_ROOTS = ${JSON.stringify(featureRootDirs)};\n\n const __AUTOMETA_EVENT_MODULES = ${eventGlobs.length > 0\n ? `import.meta.glob(${JSON.stringify(eventGlobs)}, { eager: true })`\n : \"{}\"};\n const stepModules = import.meta.glob(${JSON.stringify(stepGlobs)}, { eager: true });\n\n function collectCandidateModules(imported) {\n if (!imported || typeof imported !== 'object') {\n return [];\n }\n\n const modules = new Set();\n modules.add(imported);\n\n const exportedModules = imported.modules;\n if (Array.isArray(exportedModules)) {\n for (const entry of exportedModules) {\n if (entry && typeof entry === 'object') {\n modules.add(entry);\n }\n }\n }\n\n const defaultExport = imported.default;\n if (Array.isArray(defaultExport)) {\n for (const entry of defaultExport) {\n if (entry && typeof entry === 'object') {\n modules.add(entry);\n }\n }\n } else if (defaultExport && typeof defaultExport === 'object') {\n modules.add(defaultExport);\n }\n\n return Array.from(modules);\n }\n\n function isStepsEnvironment(candidate) {\n return Boolean(\n candidate &&\n typeof candidate === 'object' &&\n typeof candidate.coordinateFeature === 'function' &&\n typeof candidate.getPlan === 'function' &&\n typeof candidate.Given === 'function' &&\n typeof candidate.When === 'function' &&\n typeof candidate.Then === 'function'\n );\n }\n\n function extractStepsEnvironments(candidate) {\n const environments = [];\n\n if (!candidate || typeof candidate !== 'object') {\n return environments;\n }\n\n if (isStepsEnvironment(candidate)) {\n environments.push(candidate);\n }\n\n const stepsEnv = candidate.stepsEnvironment;\n if (isStepsEnvironment(stepsEnv)) {\n environments.push(stepsEnv);\n }\n\n const defaultExport = candidate.default;\n if (isStepsEnvironment(defaultExport)) {\n environments.push(defaultExport);\n }\n\n return environments;\n }\n\n function collectStepsEnvironments(modules) {\n const environments = new Set();\n for (const moduleExports of Object.values(modules)) {\n for (const candidate of collectCandidateModules(moduleExports)) {\n for (const env of extractStepsEnvironments(candidate)) {\n environments.add(env);\n }\n }\n }\n return Array.from(environments);\n }\n\n function __normalizePathSegments(input) {\n return String(input).replace(/\\\\/g, '/').split('/').filter(Boolean);\n }\n\n function __startsWithSegments(haystack, needle) {\n if (needle.length > haystack.length) return false;\n for (let i = 0; i < needle.length; i += 1) {\n if (haystack[i] !== needle[i]) return false;\n }\n return true;\n }\n\n function __isUnderRoot(fileAbs, rootAbs) {\n const rel = rootAbs ? __pathRelative(String(rootAbs), String(fileAbs)) : '';\n return rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\\\'));\n }\n\n function __selectHoistedFeatureRoot(fileAbs) {\n if (!Array.isArray(__AUTOMETA_HOISTED_FEATURE_ROOTS) || __AUTOMETA_HOISTED_FEATURE_ROOTS.length === 0) {\n return undefined;\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n let best;\n for (const rootAbs of __AUTOMETA_HOISTED_FEATURE_ROOTS) {\n if (!rootAbs) continue;\n if (__isUnderRoot(absoluteFile, rootAbs)) {\n if (!best || String(rootAbs).length > String(best).length) {\n best = rootAbs;\n }\n }\n }\n return best;\n }\n\n function __resolveHoistedDirectoryScope(fileAbs, groupIndexData) {\n if (!groupIndexData || !Array.isArray(groupIndexData.groups)) {\n return { kind: 'root' };\n }\n\n const rootAbs = __selectHoistedFeatureRoot(fileAbs);\n if (!rootAbs) {\n return { kind: 'root' };\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n const rel = __pathRelative(String(rootAbs), String(absoluteFile));\n const segments = __normalizePathSegments(rel);\n const dirSegments = segments.slice(0, -1);\n if (dirSegments.length === 0) {\n return { kind: 'root' };\n }\n\n const group = dirSegments[0];\n if (!group) {\n return { kind: 'root' };\n }\n\n const groupEntry = groupIndexData.groups.find((entry) => entry && entry.group === group);\n if (!groupEntry) {\n if (__AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT) {\n throw new Error(\n '[autometa] Feature \"' +\n absoluteFile +\n '\" is under \"' +\n rootAbs +\n '\" and implies group \"' +\n group +\n '\", but \"' +\n group +\n '\" is not declared in config.modules.groups.'\n );\n }\n return { kind: 'root' };\n }\n\n const moduleSegments = dirSegments.slice(1);\n if (moduleSegments.length === 0) {\n return { kind: 'group', group };\n }\n\n const modulePaths = groupEntry.modulePaths || [];\n for (const modulePath of modulePaths) {\n if (__startsWithSegments(moduleSegments, modulePath)) {\n return { kind: 'module', group, modulePath };\n }\n }\n\n if (__AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT) {\n throw new Error(\n '[autometa] Feature \"' +\n absoluteFile +\n '\" is under \"' +\n rootAbs +\n '\" and implies module \"' +\n group +\n ':' +\n moduleSegments.join(':') +\n '\", but no matching module is declared in config.modules.groups.' +\n group +\n '.modules.'\n );\n }\n\n return { kind: 'group', group };\n }\n\n function __resolveFileScope(fileAbs) {\n if (!__AUTOMETA_GROUP_INDEX || !__AUTOMETA_GROUP_INDEX.groups) {\n return { kind: 'root' };\n }\n if (String(fileAbs).startsWith('node:')) {\n return { kind: 'root' };\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n for (const entry of __AUTOMETA_GROUP_INDEX.groups) {\n const rootAbs = entry.rootAbs;\n const rel = rootAbs ? __pathRelative(rootAbs, absoluteFile) : '';\n if (rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\\\'))) {\n const segments = __normalizePathSegments(rel);\n const modulePaths = entry.modulePaths || [];\n for (const modulePath of modulePaths) {\n if (__startsWithSegments(segments, modulePath)) {\n return { kind: 'module', group: entry.group, modulePath };\n }\n }\n return { kind: 'group', group: entry.group };\n }\n }\n return { kind: 'root' };\n }\n\n function __parseScopeOverrideTag(tags) {\n if (!Array.isArray(tags)) return undefined;\n for (const tag of tags) {\n const match = String(tag).match(/^@scope(?::|=|\\()(.+?)(?:\\))?$/u);\n if (!match) continue;\n const raw = String(match[1] ?? '').trim();\n if (!raw) continue;\n const normalized = raw.replace(/\\//g, ':');\n const parts = normalized.split(':').filter(Boolean);\n const group = parts[0];\n const rest = parts.slice(1);\n if (!group) continue;\n return rest.length > 0 ? { group, modulePath: rest } : { group };\n }\n return undefined;\n }\n\n function __resolveFeatureScope(feature) {\n const override = __parseScopeOverrideTag(feature.tags);\n if (override) {\n if (override.modulePath && override.modulePath.length > 0) {\n return { kind: 'module', group: override.group, modulePath: override.modulePath };\n }\n return { kind: 'group', group: override.group };\n }\n const fileScope = __resolveFileScope(__AUTOMETA_FEATURE_FILE);\n if (fileScope.kind !== 'root') {\n return fileScope;\n }\n if (__AUTOMETA_HOISTED_FEATURE_SCOPING_MODE !== 'directory') {\n return fileScope;\n }\n return __resolveHoistedDirectoryScope(__AUTOMETA_FEATURE_FILE, __AUTOMETA_GROUP_INDEX);\n }\n\n function __inferEnvironmentGroup(environment) {\n const meta = environment && typeof environment === 'object'\n ? environment[STEPS_ENVIRONMENT_META]\n : undefined;\n if (meta && typeof meta === 'object') {\n if (meta.kind === 'group' && typeof meta.group === 'string' && meta.group.trim().length > 0) {\n return { kind: 'group', group: meta.group };\n }\n if (meta.kind === 'root') {\n return { kind: 'root' };\n }\n }\n\n const plan = environment.getPlan();\n const groups = new Set();\n for (const def of plan.stepsById.values()) {\n const file = def && def.source ? def.source.file : undefined;\n if (!file) continue;\n const scope = __resolveFileScope(file);\n if (scope.kind === 'group' || scope.kind === 'module') {\n groups.add(scope.group);\n if (groups.size > 1) return { kind: 'ambiguous' };\n }\n }\n const only = Array.from(groups.values())[0];\n return only ? { kind: 'group', group: only } : { kind: 'root' };\n }\n\n function selectStepsEnvironment(environments, feature) {\n if (!Array.isArray(environments) || environments.length === 0) {\n return undefined;\n }\n if (environments.length === 1) {\n return environments[0];\n }\n\n const featureScope = __resolveFeatureScope(feature);\n const indexed = environments\n .map((env) => ({ env, inferred: __inferEnvironmentGroup(env) }))\n .filter((entry) => entry.inferred.kind !== 'ambiguous');\n\n if (featureScope.kind === 'root') {\n const root = indexed.find((entry) => entry.inferred.kind === 'root');\n return (root ? root.env : indexed[0]?.env) ?? environments[0];\n }\n\n const match = indexed.find((entry) => entry.inferred.kind === 'group' && entry.inferred.group === featureScope.group);\n return match ? match.env : undefined;\n }\n\n function isScenario(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'steps' in element &&\n !('exampleGroups' in element) &&\n !('elements' in element)\n );\n }\n\n function isScenarioOutline(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'steps' in element &&\n 'exampleGroups' in element\n );\n }\n\n function isRule(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'elements' in element &&\n Array.isArray(element.elements)\n );\n }\n\n function createFeatureScopePlan(feature, basePlan) {\n const allSteps = Array.from(basePlan.stepsById.values());\n\n function normalizePathSegments(input) {\n return String(input).replace(/\\\\/g, '/').split('/').filter(Boolean);\n }\n\n function startsWithSegments(haystack, needle) {\n if (needle.length > haystack.length) return false;\n for (let i = 0; i < needle.length; i += 1) {\n if (haystack[i] !== needle[i]) return false;\n }\n return true;\n }\n\n function resolveFileScope(fileAbs) {\n if (!__AUTOMETA_STEP_SCOPING || !__AUTOMETA_STEP_SCOPING.groups) {\n return { kind: 'root' };\n }\n if (String(fileAbs).startsWith('node:')) {\n return { kind: 'root' };\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n for (const entry of __AUTOMETA_STEP_SCOPING.groups) {\n const rootAbs = entry.rootAbs;\n const rel = rootAbs ? __pathRelative(rootAbs, absoluteFile) : '';\n if (rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\\\'))) {\n const segments = normalizePathSegments(rel);\n const modulePaths = entry.modulePaths || [];\n for (const modulePath of modulePaths) {\n if (startsWithSegments(segments, modulePath)) {\n return { kind: 'module', group: entry.group, modulePath };\n }\n }\n return { kind: 'group', group: entry.group };\n }\n }\n return { kind: 'root' };\n }\n\n function parseScopeOverrideTag(tags) {\n if (!Array.isArray(tags)) return undefined;\n for (const tag of tags) {\n const match = String(tag).match(/^@scope(?::|=|\\()(.+?)(?:\\))?$/u);\n if (!match) continue;\n const raw = String(match[1] ?? '').trim();\n if (!raw) continue;\n const normalized = raw.replace(/\\//g, ':');\n const parts = normalized.split(':').filter(Boolean);\n const group = parts[0];\n const rest = parts.slice(1);\n if (!group) continue;\n return rest.length > 0 ? { group, modulePath: rest } : { group };\n }\n return undefined;\n }\n\n function resolveFeatureScope() {\n const override = parseScopeOverrideTag(feature.tags);\n if (override) {\n if (override.modulePath && override.modulePath.length > 0) {\n return { kind: 'module', group: override.group, modulePath: override.modulePath };\n }\n return { kind: 'group', group: override.group };\n }\n const fileScope = resolveFileScope(__AUTOMETA_FEATURE_FILE);\n if (fileScope.kind !== 'root') {\n return fileScope;\n }\n if (__AUTOMETA_HOISTED_FEATURE_SCOPING_MODE !== 'directory') {\n return fileScope;\n }\n return __resolveHoistedDirectoryScope(__AUTOMETA_FEATURE_FILE, __AUTOMETA_STEP_SCOPING);\n }\n\n function isVisibleStepScope(stepScope, featureScope) {\n if (featureScope.kind === 'root') {\n return stepScope.kind === 'root';\n }\n if (featureScope.kind === 'group') {\n if (stepScope.kind === 'root') return true;\n return stepScope.kind === 'group' && stepScope.group === featureScope.group;\n }\n // module\n if (stepScope.kind === 'root') return true;\n if (stepScope.kind === 'group') return stepScope.group === featureScope.group;\n if (stepScope.kind === 'module') {\n return stepScope.group === featureScope.group && startsWithSegments(featureScope.modulePath, stepScope.modulePath);\n }\n return false;\n }\n\n function stepScopeRank(scope) {\n if (scope.kind === 'module') return 200 + (scope.modulePath ? scope.modulePath.length : 0);\n if (scope.kind === 'group') return 100;\n return 0;\n }\n\n const useScopedSteps = __AUTOMETA_STEP_SCOPING_MODE === 'scoped' && __AUTOMETA_STEP_SCOPING && __AUTOMETA_STEP_SCOPING.groups;\n const featureVisibilityScope = useScopedSteps ? resolveFeatureScope() : { kind: 'root' };\n const visibleSteps = useScopedSteps\n ? allSteps\n .filter((definition) => {\n const file = definition && definition.source ? definition.source.file : undefined;\n const scope = file ? resolveFileScope(file) : { kind: 'root' };\n return isVisibleStepScope(scope, featureVisibilityScope);\n })\n .sort((a, b) => {\n const aFile = a && a.source ? a.source.file : undefined;\n const bFile = b && b.source ? b.source.file : undefined;\n const aScope = aFile ? resolveFileScope(aFile) : { kind: 'root' };\n const bScope = bFile ? resolveFileScope(bFile) : { kind: 'root' };\n const delta = stepScopeRank(bScope) - stepScopeRank(aScope);\n return delta !== 0 ? delta : String(a.id).localeCompare(String(b.id));\n })\n : allSteps;\n\n const scopedStepsById = useScopedSteps\n ? (() => {\n const allowed = new Set(visibleSteps.map((s) => s.id));\n const next = new Map();\n for (const [id, def] of basePlan.stepsById.entries()) {\n if (allowed.has(id)) next.set(id, def);\n }\n return next;\n })()\n : basePlan.stepsById;\n const featureChildren = [];\n const scopesById = new Map(basePlan.scopesById);\n\n for (const element of feature.elements ?? []) {\n if (isScenario(element) || isScenarioOutline(element)) {\n const scenarioScope = {\n id: element.id ?? element.name,\n kind: isScenarioOutline(element) ? 'scenarioOutline' : 'scenario',\n name: element.name,\n mode: 'default',\n tags: element.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: [],\n pending: false,\n };\n featureChildren.push(scenarioScope);\n scopesById.set(scenarioScope.id, scenarioScope);\n continue;\n }\n\n if (isRule(element)) {\n const ruleChildren = [];\n for (const ruleElement of element.elements ?? []) {\n if (isScenario(ruleElement) || isScenarioOutline(ruleElement)) {\n const scenarioScope = {\n id: ruleElement.id ?? ruleElement.name,\n kind: isScenarioOutline(ruleElement) ? 'scenarioOutline' : 'scenario',\n name: ruleElement.name,\n mode: 'default',\n tags: ruleElement.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: [],\n pending: false,\n };\n ruleChildren.push(scenarioScope);\n scopesById.set(scenarioScope.id, scenarioScope);\n }\n }\n\n const ruleScope = {\n id: element.id ?? element.name,\n kind: 'rule',\n name: element.name,\n mode: 'default',\n tags: element.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: ruleChildren,\n pending: false,\n };\n featureChildren.push(ruleScope);\n scopesById.set(ruleScope.id, ruleScope);\n }\n }\n\n const featureScope = {\n id: feature.uri ?? feature.name,\n kind: 'feature',\n name: feature.name,\n mode: 'default',\n tags: feature.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: featureChildren,\n pending: false,\n };\n\n const existingRoot = basePlan.root;\n const updatedRoot = {\n ...existingRoot,\n children: [...existingRoot.children, featureScope],\n };\n\n scopesById.set(featureScope.id, featureScope);\n scopesById.set(updatedRoot.id, updatedRoot);\n\n const scopePlan = {\n root: updatedRoot,\n stepsById: scopedStepsById,\n hooksById: basePlan.hooksById,\n scopesById,\n };\n\n if (basePlan.worldFactory) {\n scopePlan.worldFactory = basePlan.worldFactory;\n }\n\n if (basePlan.parameterRegistry) {\n scopePlan.parameterRegistry = basePlan.parameterRegistry;\n }\n\n return scopePlan;\n }\n\n const gherkin = ${JSON.stringify(code)};\n const feature = parseGherkin(gherkin);\n const environments = collectStepsEnvironments(stepModules);\n const steps = selectStepsEnvironment(environments, feature);\n\n if (!steps) {\n throw new Error('Autometa could not find a steps environment for this feature. If you are using per-group environments, ensure each group exports a steps environment (\"stepsEnvironment\" or default export) under the configured step roots.');\n }\n\n CucumberRunner.setSteps(steps);\n\n describe(feature.name, () => {\n const basePlan = steps.getPlan();\n const scopedPlan = createFeatureScopePlan(feature, basePlan);\n const { plan, adapter } = coordinateRunnerFeature({\n feature,\n environment: steps,\n config: ${runtimeConfig},\n plan: scopedPlan\n });\n\n execute({ plan, adapter, config: ${runtimeConfig} });\n });\n `,\n map: null,\n };\n },\n };\n}\n\ninterface StepGlobOptions {\n readonly configDir: string;\n readonly projectRoot: string;\n}\n\nfunction buildFeatureRootDirs(\n entries: readonly string[] | undefined,\n options: StepGlobOptions\n): string[] {\n if (!entries || entries.length === 0) {\n return [];\n }\n\n const roots = new Set<string>();\n for (const entry of entries) {\n const normalized = entry.trim();\n if (!normalized) {\n continue;\n }\n\n for (const candidate of toPatterns(normalized, FEATURE_FALLBACK_GLOB)) {\n const base = inferGlobBaseDirectory(candidate);\n const absolute = isAbsolute(base)\n ? normalizeSlashes(base)\n : normalizeSlashes(resolve(options.configDir, base));\n roots.add(absolute.replace(/\\/+$/u, \"\"));\n }\n }\n\n return Array.from(roots).sort((a, b) => b.length - a.length);\n}\n\nfunction buildStepGlobs(\n entries: readonly string[] | undefined,\n options: StepGlobOptions\n): string[] {\n if (!entries || entries.length === 0) {\n return [];\n }\n\n const patterns = new Set<string>();\n for (const entry of entries) {\n const normalized = entry.trim();\n if (!normalized) {\n continue;\n }\n\n for (const candidate of toPatterns(normalized, STEP_FALLBACK_GLOB)) {\n const absolute = isAbsolute(candidate)\n ? normalizeSlashes(candidate)\n : normalizeSlashes(resolve(options.configDir, candidate));\n const rootRelative = toRootRelativeGlob(absolute, options.projectRoot);\n patterns.add(rootRelative);\n }\n }\n\n return Array.from(patterns);\n}\n\nfunction toPatterns(entry: string, fallbackGlob: string): string[] {\n if (hasGlobMagic(entry) || hasFileExtension(entry)) {\n return [entry];\n }\n return [appendGlob(entry, fallbackGlob)];\n}\n\nfunction inferGlobBaseDirectory(pattern: string): string {\n const normalized = normalizeSlashes(pattern).replace(/^!/u, \"\");\n\n for (let i = 0; i < normalized.length; i += 1) {\n if (!hasGlobMagic(normalized[i] ?? \"\")) {\n continue;\n }\n\n const prefix = normalized.slice(0, i);\n const lastSlash = prefix.lastIndexOf(\"/\");\n if (lastSlash <= 0) {\n return \".\";\n }\n const base = prefix.slice(0, lastSlash);\n return base || \".\";\n }\n\n if (hasFileExtension(normalized)) {\n return dirname(normalized);\n }\n\n return normalized || \".\";\n}\n\nfunction hasGlobMagic(input: string): boolean {\n return /[*?{}()[\\]!,@+]/u.test(input);\n}\n\nfunction hasFileExtension(input: string): boolean {\n const normalized = normalizeSlashes(input);\n const trimmed = normalized === \"/\" ? normalized : normalized.replace(/\\/+$/u, \"\");\n if (!trimmed || trimmed === \".\" || trimmed === \"..\") {\n return false;\n }\n return Boolean(extname(trimmed));\n}\n\nfunction appendGlob(entry: string, glob: string): string {\n const normalized = normalizeSlashes(entry);\n const trimmed = normalized === \"/\" ? normalized : normalized.replace(/\\/+$/u, \"\");\n if (!trimmed || trimmed === \".\") {\n return glob;\n }\n if (trimmed === \"/\") {\n return `/${glob}`;\n }\n return `${trimmed}/${glob}`;\n}\n\nfunction toRootRelativeGlob(pattern: string, rootDir: string): string {\n const normalizedRoot = normalizeSlashes(rootDir);\n const normalizedPattern = normalizeSlashes(pattern);\n let relativePattern = normalizeSlashes(relative(normalizedRoot, normalizedPattern));\n\n if (!relativePattern || relativePattern === \".\") {\n relativePattern = \"\";\n }\n\n if (!relativePattern || relativePattern.startsWith(\"..\")) {\n return ensureGlobPrefix(normalizedPattern);\n }\n\n return ensureGlobPrefix(relativePattern);\n}\n\nfunction ensureGlobPrefix(pattern: string): string {\n if (/^[A-Za-z]:\\//u.test(pattern)) {\n return pattern;\n }\n if (pattern.startsWith(\"/\")) {\n return pattern;\n }\n return `/${pattern.replace(/^\\/+/, \"\")}`;\n}\n\nfunction normalizeSlashes(pathname: string): string {\n return pathname.replace(/\\\\/gu, \"/\");\n}\n\nasync function loadAutometaConfig(root: string): Promise<{ config: Config; path: string }> {\n const _jiti = jiti(root, { interopDefault: true });\n\n const candidates = [\n \"autometa.config.ts\",\n \"autometa.config.js\",\n \"autometa.config.mts\",\n \"autometa.config.mjs\",\n \"autometa.config.cts\",\n \"autometa.config.cjs\",\n ];\n\n for (const candidate of candidates) {\n const path = resolve(root, candidate);\n if (!existsSync(path)) {\n continue;\n }\n\n try {\n const mod = _jiti(path);\n const config = mod.default || mod;\n\n if (isConfig(config)) {\n return { config, path };\n } else {\n console.warn(`Found config at ${path} but it does not export a Config instance.`);\n }\n } catch (error: unknown) {\n console.error(`Error loading config at ${path}:`, error);\n throw error;\n }\n }\n throw new Error(\"Could not find autometa.config.{ts,js,mjs,cjs} in \" + root);\n}\n\nfunction isConfig(config: unknown): config is Config {\n return (\n typeof config === \"object\" &&\n config !== null &&\n \"resolve\" in config &&\n typeof (config as Config).resolve === \"function\" &&\n \"current\" in config &&\n typeof (config as Config).current === \"function\"\n );\n}\n"]}
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import jiti from 'jiti';
4
4
 
5
5
  // src/index.ts
6
6
  var STEP_FALLBACK_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}";
7
+ var FEATURE_FALLBACK_GLOB = "**/*.feature";
7
8
  function flattenModuleDeclarations(declarations, prefix = []) {
8
9
  if (!declarations || declarations.length === 0) {
9
10
  return [];
@@ -82,8 +83,14 @@ function autometa() {
82
83
  });
83
84
  const runtimeConfig = JSON.stringify(resolved.config);
84
85
  const stepScopingMode = resolved.config.modules?.stepScoping ?? "global";
86
+ const hoistedFeatureScopingMode = resolved.config.modules?.hoistedFeatures?.scope ?? "tag";
87
+ const hoistedFeatureScopingStrict = resolved.config.modules?.hoistedFeatures?.strict ?? true;
85
88
  const groupIndexData = buildStepScopingData(resolved.config, rootDir);
86
89
  const stepScopingData = stepScopingMode === "scoped" ? groupIndexData : null;
90
+ const featureRootDirs = buildFeatureRootDirs(resolved.config.roots.features, {
91
+ configDir,
92
+ projectRoot: rootDir
93
+ });
87
94
  const featureFile = cleanId;
88
95
  return {
89
96
  code: String.raw`
@@ -97,6 +104,9 @@ function autometa() {
97
104
  const __AUTOMETA_GROUP_INDEX = ${JSON.stringify(groupIndexData)};
98
105
  const __AUTOMETA_STEP_SCOPING = ${JSON.stringify(stepScopingData)};
99
106
  const __AUTOMETA_FEATURE_FILE = ${JSON.stringify(featureFile)};
107
+ const __AUTOMETA_HOISTED_FEATURE_SCOPING_MODE = ${JSON.stringify(hoistedFeatureScopingMode)};
108
+ const __AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT = ${JSON.stringify(hoistedFeatureScopingStrict)};
109
+ const __AUTOMETA_HOISTED_FEATURE_ROOTS = ${JSON.stringify(featureRootDirs)};
100
110
 
101
111
  const __AUTOMETA_EVENT_MODULES = ${eventGlobs.length > 0 ? `import.meta.glob(${JSON.stringify(eventGlobs)}, { eager: true })` : "{}"};
102
112
  const stepModules = import.meta.glob(${JSON.stringify(stepGlobs)}, { eager: true });
@@ -192,6 +202,107 @@ function autometa() {
192
202
  return true;
193
203
  }
194
204
 
205
+ function __isUnderRoot(fileAbs, rootAbs) {
206
+ const rel = rootAbs ? __pathRelative(String(rootAbs), String(fileAbs)) : '';
207
+ return rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\'));
208
+ }
209
+
210
+ function __selectHoistedFeatureRoot(fileAbs) {
211
+ if (!Array.isArray(__AUTOMETA_HOISTED_FEATURE_ROOTS) || __AUTOMETA_HOISTED_FEATURE_ROOTS.length === 0) {
212
+ return undefined;
213
+ }
214
+
215
+ const absoluteFile = __pathIsAbsolute(String(fileAbs))
216
+ ? String(fileAbs)
217
+ : __pathResolve(process.cwd(), String(fileAbs));
218
+
219
+ let best;
220
+ for (const rootAbs of __AUTOMETA_HOISTED_FEATURE_ROOTS) {
221
+ if (!rootAbs) continue;
222
+ if (__isUnderRoot(absoluteFile, rootAbs)) {
223
+ if (!best || String(rootAbs).length > String(best).length) {
224
+ best = rootAbs;
225
+ }
226
+ }
227
+ }
228
+ return best;
229
+ }
230
+
231
+ function __resolveHoistedDirectoryScope(fileAbs, groupIndexData) {
232
+ if (!groupIndexData || !Array.isArray(groupIndexData.groups)) {
233
+ return { kind: 'root' };
234
+ }
235
+
236
+ const rootAbs = __selectHoistedFeatureRoot(fileAbs);
237
+ if (!rootAbs) {
238
+ return { kind: 'root' };
239
+ }
240
+
241
+ const absoluteFile = __pathIsAbsolute(String(fileAbs))
242
+ ? String(fileAbs)
243
+ : __pathResolve(process.cwd(), String(fileAbs));
244
+
245
+ const rel = __pathRelative(String(rootAbs), String(absoluteFile));
246
+ const segments = __normalizePathSegments(rel);
247
+ const dirSegments = segments.slice(0, -1);
248
+ if (dirSegments.length === 0) {
249
+ return { kind: 'root' };
250
+ }
251
+
252
+ const group = dirSegments[0];
253
+ if (!group) {
254
+ return { kind: 'root' };
255
+ }
256
+
257
+ const groupEntry = groupIndexData.groups.find((entry) => entry && entry.group === group);
258
+ if (!groupEntry) {
259
+ if (__AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT) {
260
+ throw new Error(
261
+ '[autometa] Feature "' +
262
+ absoluteFile +
263
+ '" is under "' +
264
+ rootAbs +
265
+ '" and implies group "' +
266
+ group +
267
+ '", but "' +
268
+ group +
269
+ '" is not declared in config.modules.groups.'
270
+ );
271
+ }
272
+ return { kind: 'root' };
273
+ }
274
+
275
+ const moduleSegments = dirSegments.slice(1);
276
+ if (moduleSegments.length === 0) {
277
+ return { kind: 'group', group };
278
+ }
279
+
280
+ const modulePaths = groupEntry.modulePaths || [];
281
+ for (const modulePath of modulePaths) {
282
+ if (__startsWithSegments(moduleSegments, modulePath)) {
283
+ return { kind: 'module', group, modulePath };
284
+ }
285
+ }
286
+
287
+ if (__AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT) {
288
+ throw new Error(
289
+ '[autometa] Feature "' +
290
+ absoluteFile +
291
+ '" is under "' +
292
+ rootAbs +
293
+ '" and implies module "' +
294
+ group +
295
+ ':' +
296
+ moduleSegments.join(':') +
297
+ '", but no matching module is declared in config.modules.groups.' +
298
+ group +
299
+ '.modules.'
300
+ );
301
+ }
302
+
303
+ return { kind: 'group', group };
304
+ }
305
+
195
306
  function __resolveFileScope(fileAbs) {
196
307
  if (!__AUTOMETA_GROUP_INDEX || !__AUTOMETA_GROUP_INDEX.groups) {
197
308
  return { kind: 'root' };
@@ -246,7 +357,14 @@ function autometa() {
246
357
  }
247
358
  return { kind: 'group', group: override.group };
248
359
  }
249
- return __resolveFileScope(__AUTOMETA_FEATURE_FILE);
360
+ const fileScope = __resolveFileScope(__AUTOMETA_FEATURE_FILE);
361
+ if (fileScope.kind !== 'root') {
362
+ return fileScope;
363
+ }
364
+ if (__AUTOMETA_HOISTED_FEATURE_SCOPING_MODE !== 'directory') {
365
+ return fileScope;
366
+ }
367
+ return __resolveHoistedDirectoryScope(__AUTOMETA_FEATURE_FILE, __AUTOMETA_GROUP_INDEX);
250
368
  }
251
369
 
252
370
  function __inferEnvironmentGroup(environment) {
@@ -396,7 +514,14 @@ function autometa() {
396
514
  }
397
515
  return { kind: 'group', group: override.group };
398
516
  }
399
- return resolveFileScope(__AUTOMETA_FEATURE_FILE);
517
+ const fileScope = resolveFileScope(__AUTOMETA_FEATURE_FILE);
518
+ if (fileScope.kind !== 'root') {
519
+ return fileScope;
520
+ }
521
+ if (__AUTOMETA_HOISTED_FEATURE_SCOPING_MODE !== 'directory') {
522
+ return fileScope;
523
+ }
524
+ return __resolveHoistedDirectoryScope(__AUTOMETA_FEATURE_FILE, __AUTOMETA_STEP_SCOPING);
400
525
  }
401
526
 
402
527
  function isVisibleStepScope(stepScope, featureScope) {
@@ -576,6 +701,24 @@ function autometa() {
576
701
  }
577
702
  };
578
703
  }
704
+ function buildFeatureRootDirs(entries, options) {
705
+ if (!entries || entries.length === 0) {
706
+ return [];
707
+ }
708
+ const roots = /* @__PURE__ */ new Set();
709
+ for (const entry of entries) {
710
+ const normalized = entry.trim();
711
+ if (!normalized) {
712
+ continue;
713
+ }
714
+ for (const candidate of toPatterns(normalized, FEATURE_FALLBACK_GLOB)) {
715
+ const base = inferGlobBaseDirectory(candidate);
716
+ const absolute = isAbsolute(base) ? normalizeSlashes(base) : normalizeSlashes(resolve(options.configDir, base));
717
+ roots.add(absolute.replace(/\/+$/u, ""));
718
+ }
719
+ }
720
+ return Array.from(roots).sort((a, b) => b.length - a.length);
721
+ }
579
722
  function buildStepGlobs(entries, options) {
580
723
  if (!entries || entries.length === 0) {
581
724
  return [];
@@ -600,6 +743,25 @@ function toPatterns(entry, fallbackGlob) {
600
743
  }
601
744
  return [appendGlob(entry, fallbackGlob)];
602
745
  }
746
+ function inferGlobBaseDirectory(pattern) {
747
+ const normalized = normalizeSlashes(pattern).replace(/^!/u, "");
748
+ for (let i = 0; i < normalized.length; i += 1) {
749
+ if (!hasGlobMagic(normalized[i] ?? "")) {
750
+ continue;
751
+ }
752
+ const prefix = normalized.slice(0, i);
753
+ const lastSlash = prefix.lastIndexOf("/");
754
+ if (lastSlash <= 0) {
755
+ return ".";
756
+ }
757
+ const base = prefix.slice(0, lastSlash);
758
+ return base || ".";
759
+ }
760
+ if (hasFileExtension(normalized)) {
761
+ return dirname(normalized);
762
+ }
763
+ return normalized || ".";
764
+ }
603
765
  function hasGlobMagic(input) {
604
766
  return /[*?{}()[\]!,@+]/u.test(input);
605
767
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["next"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","sourcesContent":["import { resolve, isAbsolute, relative, dirname, extname } from \"path\";\nimport { existsSync } from \"fs\";\nimport type { Plugin } from \"vite\";\nimport type { Config } from \"@autometa/config\";\nimport jiti from \"jiti\";\n\nconst STEP_FALLBACK_GLOB = \"**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}\";\n\ntype ModuleDeclaration =\n | string\n | { readonly name: string; readonly submodules?: readonly ModuleDeclaration[] | undefined };\n\nfunction flattenModuleDeclarations(\n declarations: readonly ModuleDeclaration[] | undefined,\n prefix: readonly string[] = []\n): readonly (readonly string[])[] {\n if (!declarations || declarations.length === 0) {\n return [];\n }\n\n const results: string[][] = [];\n\n for (const entry of declarations) {\n if (typeof entry === \"string\") {\n const next = [...prefix, entry];\n results.push(next);\n continue;\n }\n\n const next = [...prefix, entry.name];\n results.push(next);\n\n const nested = flattenModuleDeclarations(entry.submodules ?? undefined, next);\n for (const path of nested) {\n results.push([...path]);\n }\n }\n\n const unique = new Map<string, readonly string[]>();\n for (const path of results) {\n unique.set(path.join(\"/\"), path);\n }\n\n return Array.from(unique.values()).sort((a, b) => b.length - a.length);\n}\n\nfunction buildStepScopingData(resolvedConfig: unknown, projectRoot: string): unknown {\n const groups = (resolvedConfig as { modules?: { groups?: unknown } } | null)?.modules?.groups;\n if (!groups || typeof groups !== \"object\") {\n return null;\n }\n\n const entries = Object.entries(groups as Record<string, unknown>).map(([group, groupConfig]) => {\n const configRecord =\n groupConfig && typeof groupConfig === \"object\" ? (groupConfig as Record<string, unknown>) : {};\n const rootAbs = resolve(projectRoot, String(configRecord.root ?? \"\"));\n const modules = configRecord.modules;\n const modulePaths = Array.isArray(modules)\n ? flattenModuleDeclarations(modules as ModuleDeclaration[])\n : [];\n return { group, rootAbs, modulePaths };\n });\n\n return { groups: entries };\n}\n\nexport function autometa(): Plugin {\n let autometaConfig: Config | undefined;\n let configPath: string | undefined;\n let projectRoot: string | undefined;\n\n return {\n name: \"autometa-vitest-plugin\",\n enforce: \"pre\",\n async configResolved(viteConfig) {\n projectRoot = viteConfig.root || process.cwd();\n const loaded = await loadAutometaConfig(projectRoot);\n autometaConfig = loaded.config;\n configPath = loaded.path;\n },\n transform(code, id) {\n const cleanId = id.split(\"?\", 1)[0] ?? id;\n\n if (!cleanId.endsWith(\".feature\")) {\n return;\n }\n\n if (!autometaConfig) {\n throw new Error(\"Autometa config not found\");\n }\n\n const resolved = autometaConfig.resolve();\n const stepRoots = resolved.config.roots.steps;\n const configDir = configPath ? dirname(configPath) : projectRoot ?? process.cwd();\n const rootDir = projectRoot ?? process.cwd();\n const stepGlobs = buildStepGlobs(stepRoots, {\n configDir,\n projectRoot: rootDir,\n });\n if (stepGlobs.length === 0) {\n throw new Error(\n \"Autometa config did not resolve any step files within the current project root.\"\n );\n }\n\n const eventGlobs = buildStepGlobs(resolved.config.events, {\n configDir,\n projectRoot: rootDir,\n });\n\n const runtimeConfig = JSON.stringify(resolved.config);\n const stepScopingMode = resolved.config.modules?.stepScoping ?? \"global\";\n\n // Group/module index data is useful even when step scoping is disabled,\n // since we may need it to select the correct steps environment.\n const groupIndexData = buildStepScopingData(resolved.config, rootDir);\n const stepScopingData = stepScopingMode === \"scoped\" ? groupIndexData : null;\n\n const featureFile = cleanId;\n\n return {\n code: String.raw`\n import { describe } from 'vitest';\n import { execute } from '@autometa/vitest-executor';\n import { coordinateRunnerFeature, CucumberRunner, STEPS_ENVIRONMENT_META } from '@autometa/runner';\n import { parseGherkin } from '@autometa/gherkin';\n import { relative as __pathRelative, resolve as __pathResolve, isAbsolute as __pathIsAbsolute } from 'node:path';\n\n const __AUTOMETA_STEP_SCOPING_MODE = ${JSON.stringify(stepScopingMode)};\n const __AUTOMETA_GROUP_INDEX = ${JSON.stringify(groupIndexData)};\n const __AUTOMETA_STEP_SCOPING = ${JSON.stringify(stepScopingData)};\n const __AUTOMETA_FEATURE_FILE = ${JSON.stringify(featureFile)};\n\n const __AUTOMETA_EVENT_MODULES = ${eventGlobs.length > 0\n ? `import.meta.glob(${JSON.stringify(eventGlobs)}, { eager: true })`\n : \"{}\"};\n const stepModules = import.meta.glob(${JSON.stringify(stepGlobs)}, { eager: true });\n\n function collectCandidateModules(imported) {\n if (!imported || typeof imported !== 'object') {\n return [];\n }\n\n const modules = new Set();\n modules.add(imported);\n\n const exportedModules = imported.modules;\n if (Array.isArray(exportedModules)) {\n for (const entry of exportedModules) {\n if (entry && typeof entry === 'object') {\n modules.add(entry);\n }\n }\n }\n\n const defaultExport = imported.default;\n if (Array.isArray(defaultExport)) {\n for (const entry of defaultExport) {\n if (entry && typeof entry === 'object') {\n modules.add(entry);\n }\n }\n } else if (defaultExport && typeof defaultExport === 'object') {\n modules.add(defaultExport);\n }\n\n return Array.from(modules);\n }\n\n function isStepsEnvironment(candidate) {\n return Boolean(\n candidate &&\n typeof candidate === 'object' &&\n typeof candidate.coordinateFeature === 'function' &&\n typeof candidate.getPlan === 'function' &&\n typeof candidate.Given === 'function' &&\n typeof candidate.When === 'function' &&\n typeof candidate.Then === 'function'\n );\n }\n\n function extractStepsEnvironments(candidate) {\n const environments = [];\n\n if (!candidate || typeof candidate !== 'object') {\n return environments;\n }\n\n if (isStepsEnvironment(candidate)) {\n environments.push(candidate);\n }\n\n const stepsEnv = candidate.stepsEnvironment;\n if (isStepsEnvironment(stepsEnv)) {\n environments.push(stepsEnv);\n }\n\n const defaultExport = candidate.default;\n if (isStepsEnvironment(defaultExport)) {\n environments.push(defaultExport);\n }\n\n return environments;\n }\n\n function collectStepsEnvironments(modules) {\n const environments = new Set();\n for (const moduleExports of Object.values(modules)) {\n for (const candidate of collectCandidateModules(moduleExports)) {\n for (const env of extractStepsEnvironments(candidate)) {\n environments.add(env);\n }\n }\n }\n return Array.from(environments);\n }\n\n function __normalizePathSegments(input) {\n return String(input).replace(/\\\\/g, '/').split('/').filter(Boolean);\n }\n\n function __startsWithSegments(haystack, needle) {\n if (needle.length > haystack.length) return false;\n for (let i = 0; i < needle.length; i += 1) {\n if (haystack[i] !== needle[i]) return false;\n }\n return true;\n }\n\n function __resolveFileScope(fileAbs) {\n if (!__AUTOMETA_GROUP_INDEX || !__AUTOMETA_GROUP_INDEX.groups) {\n return { kind: 'root' };\n }\n if (String(fileAbs).startsWith('node:')) {\n return { kind: 'root' };\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n for (const entry of __AUTOMETA_GROUP_INDEX.groups) {\n const rootAbs = entry.rootAbs;\n const rel = rootAbs ? __pathRelative(rootAbs, absoluteFile) : '';\n if (rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\\\'))) {\n const segments = __normalizePathSegments(rel);\n const modulePaths = entry.modulePaths || [];\n for (const modulePath of modulePaths) {\n if (__startsWithSegments(segments, modulePath)) {\n return { kind: 'module', group: entry.group, modulePath };\n }\n }\n return { kind: 'group', group: entry.group };\n }\n }\n return { kind: 'root' };\n }\n\n function __parseScopeOverrideTag(tags) {\n if (!Array.isArray(tags)) return undefined;\n for (const tag of tags) {\n const match = String(tag).match(/^@scope(?::|=|\\()(.+?)(?:\\))?$/u);\n if (!match) continue;\n const raw = String(match[1] ?? '').trim();\n if (!raw) continue;\n const normalized = raw.replace(/\\//g, ':');\n const parts = normalized.split(':').filter(Boolean);\n const group = parts[0];\n const rest = parts.slice(1);\n if (!group) continue;\n return rest.length > 0 ? { group, modulePath: rest } : { group };\n }\n return undefined;\n }\n\n function __resolveFeatureScope(feature) {\n const override = __parseScopeOverrideTag(feature.tags);\n if (override) {\n if (override.modulePath && override.modulePath.length > 0) {\n return { kind: 'module', group: override.group, modulePath: override.modulePath };\n }\n return { kind: 'group', group: override.group };\n }\n return __resolveFileScope(__AUTOMETA_FEATURE_FILE);\n }\n\n function __inferEnvironmentGroup(environment) {\n const meta = environment && typeof environment === 'object'\n ? environment[STEPS_ENVIRONMENT_META]\n : undefined;\n if (meta && typeof meta === 'object') {\n if (meta.kind === 'group' && typeof meta.group === 'string' && meta.group.trim().length > 0) {\n return { kind: 'group', group: meta.group };\n }\n if (meta.kind === 'root') {\n return { kind: 'root' };\n }\n }\n\n const plan = environment.getPlan();\n const groups = new Set();\n for (const def of plan.stepsById.values()) {\n const file = def && def.source ? def.source.file : undefined;\n if (!file) continue;\n const scope = __resolveFileScope(file);\n if (scope.kind === 'group' || scope.kind === 'module') {\n groups.add(scope.group);\n if (groups.size > 1) return { kind: 'ambiguous' };\n }\n }\n const only = Array.from(groups.values())[0];\n return only ? { kind: 'group', group: only } : { kind: 'root' };\n }\n\n function selectStepsEnvironment(environments, feature) {\n if (!Array.isArray(environments) || environments.length === 0) {\n return undefined;\n }\n if (environments.length === 1) {\n return environments[0];\n }\n\n const featureScope = __resolveFeatureScope(feature);\n const indexed = environments\n .map((env) => ({ env, inferred: __inferEnvironmentGroup(env) }))\n .filter((entry) => entry.inferred.kind !== 'ambiguous');\n\n if (featureScope.kind === 'root') {\n const root = indexed.find((entry) => entry.inferred.kind === 'root');\n return (root ? root.env : indexed[0]?.env) ?? environments[0];\n }\n\n const match = indexed.find((entry) => entry.inferred.kind === 'group' && entry.inferred.group === featureScope.group);\n return match ? match.env : undefined;\n }\n\n function isScenario(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'steps' in element &&\n !('exampleGroups' in element) &&\n !('elements' in element)\n );\n }\n\n function isScenarioOutline(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'steps' in element &&\n 'exampleGroups' in element\n );\n }\n\n function isRule(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'elements' in element &&\n Array.isArray(element.elements)\n );\n }\n\n function createFeatureScopePlan(feature, basePlan) {\n const allSteps = Array.from(basePlan.stepsById.values());\n\n function normalizePathSegments(input) {\n return String(input).replace(/\\\\/g, '/').split('/').filter(Boolean);\n }\n\n function startsWithSegments(haystack, needle) {\n if (needle.length > haystack.length) return false;\n for (let i = 0; i < needle.length; i += 1) {\n if (haystack[i] !== needle[i]) return false;\n }\n return true;\n }\n\n function resolveFileScope(fileAbs) {\n if (!__AUTOMETA_STEP_SCOPING || !__AUTOMETA_STEP_SCOPING.groups) {\n return { kind: 'root' };\n }\n if (String(fileAbs).startsWith('node:')) {\n return { kind: 'root' };\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n for (const entry of __AUTOMETA_STEP_SCOPING.groups) {\n const rootAbs = entry.rootAbs;\n const rel = rootAbs ? __pathRelative(rootAbs, absoluteFile) : '';\n if (rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\\\'))) {\n const segments = normalizePathSegments(rel);\n const modulePaths = entry.modulePaths || [];\n for (const modulePath of modulePaths) {\n if (startsWithSegments(segments, modulePath)) {\n return { kind: 'module', group: entry.group, modulePath };\n }\n }\n return { kind: 'group', group: entry.group };\n }\n }\n return { kind: 'root' };\n }\n\n function parseScopeOverrideTag(tags) {\n if (!Array.isArray(tags)) return undefined;\n for (const tag of tags) {\n const match = String(tag).match(/^@scope(?::|=|\\()(.+?)(?:\\))?$/u);\n if (!match) continue;\n const raw = String(match[1] ?? '').trim();\n if (!raw) continue;\n const normalized = raw.replace(/\\//g, ':');\n const parts = normalized.split(':').filter(Boolean);\n const group = parts[0];\n const rest = parts.slice(1);\n if (!group) continue;\n return rest.length > 0 ? { group, modulePath: rest } : { group };\n }\n return undefined;\n }\n\n function resolveFeatureScope() {\n const override = parseScopeOverrideTag(feature.tags);\n if (override) {\n if (override.modulePath && override.modulePath.length > 0) {\n return { kind: 'module', group: override.group, modulePath: override.modulePath };\n }\n return { kind: 'group', group: override.group };\n }\n return resolveFileScope(__AUTOMETA_FEATURE_FILE);\n }\n\n function isVisibleStepScope(stepScope, featureScope) {\n if (featureScope.kind === 'root') {\n return stepScope.kind === 'root';\n }\n if (featureScope.kind === 'group') {\n if (stepScope.kind === 'root') return true;\n return stepScope.kind === 'group' && stepScope.group === featureScope.group;\n }\n // module\n if (stepScope.kind === 'root') return true;\n if (stepScope.kind === 'group') return stepScope.group === featureScope.group;\n if (stepScope.kind === 'module') {\n return stepScope.group === featureScope.group && startsWithSegments(featureScope.modulePath, stepScope.modulePath);\n }\n return false;\n }\n\n function stepScopeRank(scope) {\n if (scope.kind === 'module') return 200 + (scope.modulePath ? scope.modulePath.length : 0);\n if (scope.kind === 'group') return 100;\n return 0;\n }\n\n const useScopedSteps = __AUTOMETA_STEP_SCOPING_MODE === 'scoped' && __AUTOMETA_STEP_SCOPING && __AUTOMETA_STEP_SCOPING.groups;\n const featureVisibilityScope = useScopedSteps ? resolveFeatureScope() : { kind: 'root' };\n const visibleSteps = useScopedSteps\n ? allSteps\n .filter((definition) => {\n const file = definition && definition.source ? definition.source.file : undefined;\n const scope = file ? resolveFileScope(file) : { kind: 'root' };\n return isVisibleStepScope(scope, featureVisibilityScope);\n })\n .sort((a, b) => {\n const aFile = a && a.source ? a.source.file : undefined;\n const bFile = b && b.source ? b.source.file : undefined;\n const aScope = aFile ? resolveFileScope(aFile) : { kind: 'root' };\n const bScope = bFile ? resolveFileScope(bFile) : { kind: 'root' };\n const delta = stepScopeRank(bScope) - stepScopeRank(aScope);\n return delta !== 0 ? delta : String(a.id).localeCompare(String(b.id));\n })\n : allSteps;\n\n const scopedStepsById = useScopedSteps\n ? (() => {\n const allowed = new Set(visibleSteps.map((s) => s.id));\n const next = new Map();\n for (const [id, def] of basePlan.stepsById.entries()) {\n if (allowed.has(id)) next.set(id, def);\n }\n return next;\n })()\n : basePlan.stepsById;\n const featureChildren = [];\n const scopesById = new Map(basePlan.scopesById);\n\n for (const element of feature.elements ?? []) {\n if (isScenario(element) || isScenarioOutline(element)) {\n const scenarioScope = {\n id: element.id ?? element.name,\n kind: isScenarioOutline(element) ? 'scenarioOutline' : 'scenario',\n name: element.name,\n mode: 'default',\n tags: element.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: [],\n pending: false,\n };\n featureChildren.push(scenarioScope);\n scopesById.set(scenarioScope.id, scenarioScope);\n continue;\n }\n\n if (isRule(element)) {\n const ruleChildren = [];\n for (const ruleElement of element.elements ?? []) {\n if (isScenario(ruleElement) || isScenarioOutline(ruleElement)) {\n const scenarioScope = {\n id: ruleElement.id ?? ruleElement.name,\n kind: isScenarioOutline(ruleElement) ? 'scenarioOutline' : 'scenario',\n name: ruleElement.name,\n mode: 'default',\n tags: ruleElement.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: [],\n pending: false,\n };\n ruleChildren.push(scenarioScope);\n scopesById.set(scenarioScope.id, scenarioScope);\n }\n }\n\n const ruleScope = {\n id: element.id ?? element.name,\n kind: 'rule',\n name: element.name,\n mode: 'default',\n tags: element.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: ruleChildren,\n pending: false,\n };\n featureChildren.push(ruleScope);\n scopesById.set(ruleScope.id, ruleScope);\n }\n }\n\n const featureScope = {\n id: feature.uri ?? feature.name,\n kind: 'feature',\n name: feature.name,\n mode: 'default',\n tags: feature.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: featureChildren,\n pending: false,\n };\n\n const existingRoot = basePlan.root;\n const updatedRoot = {\n ...existingRoot,\n children: [...existingRoot.children, featureScope],\n };\n\n scopesById.set(featureScope.id, featureScope);\n scopesById.set(updatedRoot.id, updatedRoot);\n\n const scopePlan = {\n root: updatedRoot,\n stepsById: scopedStepsById,\n hooksById: basePlan.hooksById,\n scopesById,\n };\n\n if (basePlan.worldFactory) {\n scopePlan.worldFactory = basePlan.worldFactory;\n }\n\n if (basePlan.parameterRegistry) {\n scopePlan.parameterRegistry = basePlan.parameterRegistry;\n }\n\n return scopePlan;\n }\n\n const gherkin = ${JSON.stringify(code)};\n const feature = parseGherkin(gherkin);\n const environments = collectStepsEnvironments(stepModules);\n const steps = selectStepsEnvironment(environments, feature);\n\n if (!steps) {\n throw new Error('Autometa could not find a steps environment for this feature. If you are using per-group environments, ensure each group exports a steps environment (\"stepsEnvironment\" or default export) under the configured step roots.');\n }\n\n CucumberRunner.setSteps(steps);\n\n describe(feature.name, () => {\n const basePlan = steps.getPlan();\n const scopedPlan = createFeatureScopePlan(feature, basePlan);\n const { plan, adapter } = coordinateRunnerFeature({\n feature,\n environment: steps,\n config: ${runtimeConfig},\n plan: scopedPlan\n });\n\n execute({ plan, adapter, config: ${runtimeConfig} });\n });\n `,\n map: null,\n };\n },\n };\n}\n\ninterface StepGlobOptions {\n readonly configDir: string;\n readonly projectRoot: string;\n}\n\nfunction buildStepGlobs(\n entries: readonly string[] | undefined,\n options: StepGlobOptions\n): string[] {\n if (!entries || entries.length === 0) {\n return [];\n }\n\n const patterns = new Set<string>();\n for (const entry of entries) {\n const normalized = entry.trim();\n if (!normalized) {\n continue;\n }\n\n for (const candidate of toPatterns(normalized, STEP_FALLBACK_GLOB)) {\n const absolute = isAbsolute(candidate)\n ? normalizeSlashes(candidate)\n : normalizeSlashes(resolve(options.configDir, candidate));\n const rootRelative = toRootRelativeGlob(absolute, options.projectRoot);\n patterns.add(rootRelative);\n }\n }\n\n return Array.from(patterns);\n}\n\nfunction toPatterns(entry: string, fallbackGlob: string): string[] {\n if (hasGlobMagic(entry) || hasFileExtension(entry)) {\n return [entry];\n }\n return [appendGlob(entry, fallbackGlob)];\n}\n\nfunction hasGlobMagic(input: string): boolean {\n return /[*?{}()[\\]!,@+]/u.test(input);\n}\n\nfunction hasFileExtension(input: string): boolean {\n const normalized = normalizeSlashes(input);\n const trimmed = normalized === \"/\" ? normalized : normalized.replace(/\\/+$/u, \"\");\n if (!trimmed || trimmed === \".\" || trimmed === \"..\") {\n return false;\n }\n return Boolean(extname(trimmed));\n}\n\nfunction appendGlob(entry: string, glob: string): string {\n const normalized = normalizeSlashes(entry);\n const trimmed = normalized === \"/\" ? normalized : normalized.replace(/\\/+$/u, \"\");\n if (!trimmed || trimmed === \".\") {\n return glob;\n }\n if (trimmed === \"/\") {\n return `/${glob}`;\n }\n return `${trimmed}/${glob}`;\n}\n\nfunction toRootRelativeGlob(pattern: string, rootDir: string): string {\n const normalizedRoot = normalizeSlashes(rootDir);\n const normalizedPattern = normalizeSlashes(pattern);\n let relativePattern = normalizeSlashes(relative(normalizedRoot, normalizedPattern));\n\n if (!relativePattern || relativePattern === \".\") {\n relativePattern = \"\";\n }\n\n if (!relativePattern || relativePattern.startsWith(\"..\")) {\n return ensureGlobPrefix(normalizedPattern);\n }\n\n return ensureGlobPrefix(relativePattern);\n}\n\nfunction ensureGlobPrefix(pattern: string): string {\n if (/^[A-Za-z]:\\//u.test(pattern)) {\n return pattern;\n }\n if (pattern.startsWith(\"/\")) {\n return pattern;\n }\n return `/${pattern.replace(/^\\/+/, \"\")}`;\n}\n\nfunction normalizeSlashes(pathname: string): string {\n return pathname.replace(/\\\\/gu, \"/\");\n}\n\nasync function loadAutometaConfig(root: string): Promise<{ config: Config; path: string }> {\n const _jiti = jiti(root, { interopDefault: true });\n\n const candidates = [\n \"autometa.config.ts\",\n \"autometa.config.js\",\n \"autometa.config.mts\",\n \"autometa.config.mjs\",\n \"autometa.config.cts\",\n \"autometa.config.cjs\",\n ];\n\n for (const candidate of candidates) {\n const path = resolve(root, candidate);\n if (!existsSync(path)) {\n continue;\n }\n\n try {\n const mod = _jiti(path);\n const config = mod.default || mod;\n\n if (isConfig(config)) {\n return { config, path };\n } else {\n console.warn(`Found config at ${path} but it does not export a Config instance.`);\n }\n } catch (error: unknown) {\n console.error(`Error loading config at ${path}:`, error);\n throw error;\n }\n }\n throw new Error(\"Could not find autometa.config.{ts,js,mjs,cjs} in \" + root);\n}\n\nfunction isConfig(config: unknown): config is Config {\n return (\n typeof config === \"object\" &&\n config !== null &&\n \"resolve\" in config &&\n typeof (config as Config).resolve === \"function\" &&\n \"current\" in config &&\n typeof (config as Config).current === \"function\"\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["next"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","sourcesContent":["import { resolve, isAbsolute, relative, dirname, extname } from \"path\";\nimport { existsSync } from \"fs\";\nimport type { Plugin } from \"vite\";\nimport type { Config } from \"@autometa/config\";\nimport jiti from \"jiti\";\n\nconst STEP_FALLBACK_GLOB = \"**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}\";\nconst FEATURE_FALLBACK_GLOB = \"**/*.feature\";\n\ntype ModuleDeclaration =\n | string\n | { readonly name: string; readonly submodules?: readonly ModuleDeclaration[] | undefined };\n\nfunction flattenModuleDeclarations(\n declarations: readonly ModuleDeclaration[] | undefined,\n prefix: readonly string[] = []\n): readonly (readonly string[])[] {\n if (!declarations || declarations.length === 0) {\n return [];\n }\n\n const results: string[][] = [];\n\n for (const entry of declarations) {\n if (typeof entry === \"string\") {\n const next = [...prefix, entry];\n results.push(next);\n continue;\n }\n\n const next = [...prefix, entry.name];\n results.push(next);\n\n const nested = flattenModuleDeclarations(entry.submodules ?? undefined, next);\n for (const path of nested) {\n results.push([...path]);\n }\n }\n\n const unique = new Map<string, readonly string[]>();\n for (const path of results) {\n unique.set(path.join(\"/\"), path);\n }\n\n return Array.from(unique.values()).sort((a, b) => b.length - a.length);\n}\n\nfunction buildStepScopingData(resolvedConfig: unknown, projectRoot: string): unknown {\n const groups = (resolvedConfig as { modules?: { groups?: unknown } } | null)?.modules?.groups;\n if (!groups || typeof groups !== \"object\") {\n return null;\n }\n\n const entries = Object.entries(groups as Record<string, unknown>).map(([group, groupConfig]) => {\n const configRecord =\n groupConfig && typeof groupConfig === \"object\" ? (groupConfig as Record<string, unknown>) : {};\n const rootAbs = resolve(projectRoot, String(configRecord.root ?? \"\"));\n const modules = configRecord.modules;\n const modulePaths = Array.isArray(modules)\n ? flattenModuleDeclarations(modules as ModuleDeclaration[])\n : [];\n return { group, rootAbs, modulePaths };\n });\n\n return { groups: entries };\n}\n\nexport function autometa(): Plugin {\n let autometaConfig: Config | undefined;\n let configPath: string | undefined;\n let projectRoot: string | undefined;\n\n return {\n name: \"autometa-vitest-plugin\",\n enforce: \"pre\",\n async configResolved(viteConfig) {\n projectRoot = viteConfig.root || process.cwd();\n const loaded = await loadAutometaConfig(projectRoot);\n autometaConfig = loaded.config;\n configPath = loaded.path;\n },\n transform(code, id) {\n const cleanId = id.split(\"?\", 1)[0] ?? id;\n\n if (!cleanId.endsWith(\".feature\")) {\n return;\n }\n\n if (!autometaConfig) {\n throw new Error(\"Autometa config not found\");\n }\n\n const resolved = autometaConfig.resolve();\n const stepRoots = resolved.config.roots.steps;\n const configDir = configPath ? dirname(configPath) : projectRoot ?? process.cwd();\n const rootDir = projectRoot ?? process.cwd();\n const stepGlobs = buildStepGlobs(stepRoots, {\n configDir,\n projectRoot: rootDir,\n });\n if (stepGlobs.length === 0) {\n throw new Error(\n \"Autometa config did not resolve any step files within the current project root.\"\n );\n }\n\n const eventGlobs = buildStepGlobs(resolved.config.events, {\n configDir,\n projectRoot: rootDir,\n });\n\n const runtimeConfig = JSON.stringify(resolved.config);\n const stepScopingMode = resolved.config.modules?.stepScoping ?? \"global\";\n const hoistedFeatureScopingMode = resolved.config.modules?.hoistedFeatures?.scope ?? \"tag\";\n const hoistedFeatureScopingStrict = resolved.config.modules?.hoistedFeatures?.strict ?? true;\n\n // Group/module index data is useful even when step scoping is disabled,\n // since we may need it to select the correct steps environment.\n const groupIndexData = buildStepScopingData(resolved.config, rootDir);\n const stepScopingData = stepScopingMode === \"scoped\" ? groupIndexData : null;\n const featureRootDirs = buildFeatureRootDirs(resolved.config.roots.features, {\n configDir,\n projectRoot: rootDir,\n });\n\n const featureFile = cleanId;\n\n return {\n code: String.raw`\n import { describe } from 'vitest';\n import { execute } from '@autometa/vitest-executor';\n import { coordinateRunnerFeature, CucumberRunner, STEPS_ENVIRONMENT_META } from '@autometa/runner';\n import { parseGherkin } from '@autometa/gherkin';\n import { relative as __pathRelative, resolve as __pathResolve, isAbsolute as __pathIsAbsolute } from 'node:path';\n\n const __AUTOMETA_STEP_SCOPING_MODE = ${JSON.stringify(stepScopingMode)};\n const __AUTOMETA_GROUP_INDEX = ${JSON.stringify(groupIndexData)};\n const __AUTOMETA_STEP_SCOPING = ${JSON.stringify(stepScopingData)};\n const __AUTOMETA_FEATURE_FILE = ${JSON.stringify(featureFile)};\n const __AUTOMETA_HOISTED_FEATURE_SCOPING_MODE = ${JSON.stringify(hoistedFeatureScopingMode)};\n const __AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT = ${JSON.stringify(hoistedFeatureScopingStrict)};\n const __AUTOMETA_HOISTED_FEATURE_ROOTS = ${JSON.stringify(featureRootDirs)};\n\n const __AUTOMETA_EVENT_MODULES = ${eventGlobs.length > 0\n ? `import.meta.glob(${JSON.stringify(eventGlobs)}, { eager: true })`\n : \"{}\"};\n const stepModules = import.meta.glob(${JSON.stringify(stepGlobs)}, { eager: true });\n\n function collectCandidateModules(imported) {\n if (!imported || typeof imported !== 'object') {\n return [];\n }\n\n const modules = new Set();\n modules.add(imported);\n\n const exportedModules = imported.modules;\n if (Array.isArray(exportedModules)) {\n for (const entry of exportedModules) {\n if (entry && typeof entry === 'object') {\n modules.add(entry);\n }\n }\n }\n\n const defaultExport = imported.default;\n if (Array.isArray(defaultExport)) {\n for (const entry of defaultExport) {\n if (entry && typeof entry === 'object') {\n modules.add(entry);\n }\n }\n } else if (defaultExport && typeof defaultExport === 'object') {\n modules.add(defaultExport);\n }\n\n return Array.from(modules);\n }\n\n function isStepsEnvironment(candidate) {\n return Boolean(\n candidate &&\n typeof candidate === 'object' &&\n typeof candidate.coordinateFeature === 'function' &&\n typeof candidate.getPlan === 'function' &&\n typeof candidate.Given === 'function' &&\n typeof candidate.When === 'function' &&\n typeof candidate.Then === 'function'\n );\n }\n\n function extractStepsEnvironments(candidate) {\n const environments = [];\n\n if (!candidate || typeof candidate !== 'object') {\n return environments;\n }\n\n if (isStepsEnvironment(candidate)) {\n environments.push(candidate);\n }\n\n const stepsEnv = candidate.stepsEnvironment;\n if (isStepsEnvironment(stepsEnv)) {\n environments.push(stepsEnv);\n }\n\n const defaultExport = candidate.default;\n if (isStepsEnvironment(defaultExport)) {\n environments.push(defaultExport);\n }\n\n return environments;\n }\n\n function collectStepsEnvironments(modules) {\n const environments = new Set();\n for (const moduleExports of Object.values(modules)) {\n for (const candidate of collectCandidateModules(moduleExports)) {\n for (const env of extractStepsEnvironments(candidate)) {\n environments.add(env);\n }\n }\n }\n return Array.from(environments);\n }\n\n function __normalizePathSegments(input) {\n return String(input).replace(/\\\\/g, '/').split('/').filter(Boolean);\n }\n\n function __startsWithSegments(haystack, needle) {\n if (needle.length > haystack.length) return false;\n for (let i = 0; i < needle.length; i += 1) {\n if (haystack[i] !== needle[i]) return false;\n }\n return true;\n }\n\n function __isUnderRoot(fileAbs, rootAbs) {\n const rel = rootAbs ? __pathRelative(String(rootAbs), String(fileAbs)) : '';\n return rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\\\'));\n }\n\n function __selectHoistedFeatureRoot(fileAbs) {\n if (!Array.isArray(__AUTOMETA_HOISTED_FEATURE_ROOTS) || __AUTOMETA_HOISTED_FEATURE_ROOTS.length === 0) {\n return undefined;\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n let best;\n for (const rootAbs of __AUTOMETA_HOISTED_FEATURE_ROOTS) {\n if (!rootAbs) continue;\n if (__isUnderRoot(absoluteFile, rootAbs)) {\n if (!best || String(rootAbs).length > String(best).length) {\n best = rootAbs;\n }\n }\n }\n return best;\n }\n\n function __resolveHoistedDirectoryScope(fileAbs, groupIndexData) {\n if (!groupIndexData || !Array.isArray(groupIndexData.groups)) {\n return { kind: 'root' };\n }\n\n const rootAbs = __selectHoistedFeatureRoot(fileAbs);\n if (!rootAbs) {\n return { kind: 'root' };\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n const rel = __pathRelative(String(rootAbs), String(absoluteFile));\n const segments = __normalizePathSegments(rel);\n const dirSegments = segments.slice(0, -1);\n if (dirSegments.length === 0) {\n return { kind: 'root' };\n }\n\n const group = dirSegments[0];\n if (!group) {\n return { kind: 'root' };\n }\n\n const groupEntry = groupIndexData.groups.find((entry) => entry && entry.group === group);\n if (!groupEntry) {\n if (__AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT) {\n throw new Error(\n '[autometa] Feature \"' +\n absoluteFile +\n '\" is under \"' +\n rootAbs +\n '\" and implies group \"' +\n group +\n '\", but \"' +\n group +\n '\" is not declared in config.modules.groups.'\n );\n }\n return { kind: 'root' };\n }\n\n const moduleSegments = dirSegments.slice(1);\n if (moduleSegments.length === 0) {\n return { kind: 'group', group };\n }\n\n const modulePaths = groupEntry.modulePaths || [];\n for (const modulePath of modulePaths) {\n if (__startsWithSegments(moduleSegments, modulePath)) {\n return { kind: 'module', group, modulePath };\n }\n }\n\n if (__AUTOMETA_HOISTED_FEATURE_SCOPING_STRICT) {\n throw new Error(\n '[autometa] Feature \"' +\n absoluteFile +\n '\" is under \"' +\n rootAbs +\n '\" and implies module \"' +\n group +\n ':' +\n moduleSegments.join(':') +\n '\", but no matching module is declared in config.modules.groups.' +\n group +\n '.modules.'\n );\n }\n\n return { kind: 'group', group };\n }\n\n function __resolveFileScope(fileAbs) {\n if (!__AUTOMETA_GROUP_INDEX || !__AUTOMETA_GROUP_INDEX.groups) {\n return { kind: 'root' };\n }\n if (String(fileAbs).startsWith('node:')) {\n return { kind: 'root' };\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n for (const entry of __AUTOMETA_GROUP_INDEX.groups) {\n const rootAbs = entry.rootAbs;\n const rel = rootAbs ? __pathRelative(rootAbs, absoluteFile) : '';\n if (rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\\\'))) {\n const segments = __normalizePathSegments(rel);\n const modulePaths = entry.modulePaths || [];\n for (const modulePath of modulePaths) {\n if (__startsWithSegments(segments, modulePath)) {\n return { kind: 'module', group: entry.group, modulePath };\n }\n }\n return { kind: 'group', group: entry.group };\n }\n }\n return { kind: 'root' };\n }\n\n function __parseScopeOverrideTag(tags) {\n if (!Array.isArray(tags)) return undefined;\n for (const tag of tags) {\n const match = String(tag).match(/^@scope(?::|=|\\()(.+?)(?:\\))?$/u);\n if (!match) continue;\n const raw = String(match[1] ?? '').trim();\n if (!raw) continue;\n const normalized = raw.replace(/\\//g, ':');\n const parts = normalized.split(':').filter(Boolean);\n const group = parts[0];\n const rest = parts.slice(1);\n if (!group) continue;\n return rest.length > 0 ? { group, modulePath: rest } : { group };\n }\n return undefined;\n }\n\n function __resolveFeatureScope(feature) {\n const override = __parseScopeOverrideTag(feature.tags);\n if (override) {\n if (override.modulePath && override.modulePath.length > 0) {\n return { kind: 'module', group: override.group, modulePath: override.modulePath };\n }\n return { kind: 'group', group: override.group };\n }\n const fileScope = __resolveFileScope(__AUTOMETA_FEATURE_FILE);\n if (fileScope.kind !== 'root') {\n return fileScope;\n }\n if (__AUTOMETA_HOISTED_FEATURE_SCOPING_MODE !== 'directory') {\n return fileScope;\n }\n return __resolveHoistedDirectoryScope(__AUTOMETA_FEATURE_FILE, __AUTOMETA_GROUP_INDEX);\n }\n\n function __inferEnvironmentGroup(environment) {\n const meta = environment && typeof environment === 'object'\n ? environment[STEPS_ENVIRONMENT_META]\n : undefined;\n if (meta && typeof meta === 'object') {\n if (meta.kind === 'group' && typeof meta.group === 'string' && meta.group.trim().length > 0) {\n return { kind: 'group', group: meta.group };\n }\n if (meta.kind === 'root') {\n return { kind: 'root' };\n }\n }\n\n const plan = environment.getPlan();\n const groups = new Set();\n for (const def of plan.stepsById.values()) {\n const file = def && def.source ? def.source.file : undefined;\n if (!file) continue;\n const scope = __resolveFileScope(file);\n if (scope.kind === 'group' || scope.kind === 'module') {\n groups.add(scope.group);\n if (groups.size > 1) return { kind: 'ambiguous' };\n }\n }\n const only = Array.from(groups.values())[0];\n return only ? { kind: 'group', group: only } : { kind: 'root' };\n }\n\n function selectStepsEnvironment(environments, feature) {\n if (!Array.isArray(environments) || environments.length === 0) {\n return undefined;\n }\n if (environments.length === 1) {\n return environments[0];\n }\n\n const featureScope = __resolveFeatureScope(feature);\n const indexed = environments\n .map((env) => ({ env, inferred: __inferEnvironmentGroup(env) }))\n .filter((entry) => entry.inferred.kind !== 'ambiguous');\n\n if (featureScope.kind === 'root') {\n const root = indexed.find((entry) => entry.inferred.kind === 'root');\n return (root ? root.env : indexed[0]?.env) ?? environments[0];\n }\n\n const match = indexed.find((entry) => entry.inferred.kind === 'group' && entry.inferred.group === featureScope.group);\n return match ? match.env : undefined;\n }\n\n function isScenario(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'steps' in element &&\n !('exampleGroups' in element) &&\n !('elements' in element)\n );\n }\n\n function isScenarioOutline(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'steps' in element &&\n 'exampleGroups' in element\n );\n }\n\n function isRule(element) {\n return Boolean(\n element &&\n typeof element === 'object' &&\n 'elements' in element &&\n Array.isArray(element.elements)\n );\n }\n\n function createFeatureScopePlan(feature, basePlan) {\n const allSteps = Array.from(basePlan.stepsById.values());\n\n function normalizePathSegments(input) {\n return String(input).replace(/\\\\/g, '/').split('/').filter(Boolean);\n }\n\n function startsWithSegments(haystack, needle) {\n if (needle.length > haystack.length) return false;\n for (let i = 0; i < needle.length; i += 1) {\n if (haystack[i] !== needle[i]) return false;\n }\n return true;\n }\n\n function resolveFileScope(fileAbs) {\n if (!__AUTOMETA_STEP_SCOPING || !__AUTOMETA_STEP_SCOPING.groups) {\n return { kind: 'root' };\n }\n if (String(fileAbs).startsWith('node:')) {\n return { kind: 'root' };\n }\n\n const absoluteFile = __pathIsAbsolute(String(fileAbs))\n ? String(fileAbs)\n : __pathResolve(process.cwd(), String(fileAbs));\n\n for (const entry of __AUTOMETA_STEP_SCOPING.groups) {\n const rootAbs = entry.rootAbs;\n const rel = rootAbs ? __pathRelative(rootAbs, absoluteFile) : '';\n if (rel === '' || (!rel.startsWith('..') && !rel.startsWith('../') && !rel.startsWith('..\\\\'))) {\n const segments = normalizePathSegments(rel);\n const modulePaths = entry.modulePaths || [];\n for (const modulePath of modulePaths) {\n if (startsWithSegments(segments, modulePath)) {\n return { kind: 'module', group: entry.group, modulePath };\n }\n }\n return { kind: 'group', group: entry.group };\n }\n }\n return { kind: 'root' };\n }\n\n function parseScopeOverrideTag(tags) {\n if (!Array.isArray(tags)) return undefined;\n for (const tag of tags) {\n const match = String(tag).match(/^@scope(?::|=|\\()(.+?)(?:\\))?$/u);\n if (!match) continue;\n const raw = String(match[1] ?? '').trim();\n if (!raw) continue;\n const normalized = raw.replace(/\\//g, ':');\n const parts = normalized.split(':').filter(Boolean);\n const group = parts[0];\n const rest = parts.slice(1);\n if (!group) continue;\n return rest.length > 0 ? { group, modulePath: rest } : { group };\n }\n return undefined;\n }\n\n function resolveFeatureScope() {\n const override = parseScopeOverrideTag(feature.tags);\n if (override) {\n if (override.modulePath && override.modulePath.length > 0) {\n return { kind: 'module', group: override.group, modulePath: override.modulePath };\n }\n return { kind: 'group', group: override.group };\n }\n const fileScope = resolveFileScope(__AUTOMETA_FEATURE_FILE);\n if (fileScope.kind !== 'root') {\n return fileScope;\n }\n if (__AUTOMETA_HOISTED_FEATURE_SCOPING_MODE !== 'directory') {\n return fileScope;\n }\n return __resolveHoistedDirectoryScope(__AUTOMETA_FEATURE_FILE, __AUTOMETA_STEP_SCOPING);\n }\n\n function isVisibleStepScope(stepScope, featureScope) {\n if (featureScope.kind === 'root') {\n return stepScope.kind === 'root';\n }\n if (featureScope.kind === 'group') {\n if (stepScope.kind === 'root') return true;\n return stepScope.kind === 'group' && stepScope.group === featureScope.group;\n }\n // module\n if (stepScope.kind === 'root') return true;\n if (stepScope.kind === 'group') return stepScope.group === featureScope.group;\n if (stepScope.kind === 'module') {\n return stepScope.group === featureScope.group && startsWithSegments(featureScope.modulePath, stepScope.modulePath);\n }\n return false;\n }\n\n function stepScopeRank(scope) {\n if (scope.kind === 'module') return 200 + (scope.modulePath ? scope.modulePath.length : 0);\n if (scope.kind === 'group') return 100;\n return 0;\n }\n\n const useScopedSteps = __AUTOMETA_STEP_SCOPING_MODE === 'scoped' && __AUTOMETA_STEP_SCOPING && __AUTOMETA_STEP_SCOPING.groups;\n const featureVisibilityScope = useScopedSteps ? resolveFeatureScope() : { kind: 'root' };\n const visibleSteps = useScopedSteps\n ? allSteps\n .filter((definition) => {\n const file = definition && definition.source ? definition.source.file : undefined;\n const scope = file ? resolveFileScope(file) : { kind: 'root' };\n return isVisibleStepScope(scope, featureVisibilityScope);\n })\n .sort((a, b) => {\n const aFile = a && a.source ? a.source.file : undefined;\n const bFile = b && b.source ? b.source.file : undefined;\n const aScope = aFile ? resolveFileScope(aFile) : { kind: 'root' };\n const bScope = bFile ? resolveFileScope(bFile) : { kind: 'root' };\n const delta = stepScopeRank(bScope) - stepScopeRank(aScope);\n return delta !== 0 ? delta : String(a.id).localeCompare(String(b.id));\n })\n : allSteps;\n\n const scopedStepsById = useScopedSteps\n ? (() => {\n const allowed = new Set(visibleSteps.map((s) => s.id));\n const next = new Map();\n for (const [id, def] of basePlan.stepsById.entries()) {\n if (allowed.has(id)) next.set(id, def);\n }\n return next;\n })()\n : basePlan.stepsById;\n const featureChildren = [];\n const scopesById = new Map(basePlan.scopesById);\n\n for (const element of feature.elements ?? []) {\n if (isScenario(element) || isScenarioOutline(element)) {\n const scenarioScope = {\n id: element.id ?? element.name,\n kind: isScenarioOutline(element) ? 'scenarioOutline' : 'scenario',\n name: element.name,\n mode: 'default',\n tags: element.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: [],\n pending: false,\n };\n featureChildren.push(scenarioScope);\n scopesById.set(scenarioScope.id, scenarioScope);\n continue;\n }\n\n if (isRule(element)) {\n const ruleChildren = [];\n for (const ruleElement of element.elements ?? []) {\n if (isScenario(ruleElement) || isScenarioOutline(ruleElement)) {\n const scenarioScope = {\n id: ruleElement.id ?? ruleElement.name,\n kind: isScenarioOutline(ruleElement) ? 'scenarioOutline' : 'scenario',\n name: ruleElement.name,\n mode: 'default',\n tags: ruleElement.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: [],\n pending: false,\n };\n ruleChildren.push(scenarioScope);\n scopesById.set(scenarioScope.id, scenarioScope);\n }\n }\n\n const ruleScope = {\n id: element.id ?? element.name,\n kind: 'rule',\n name: element.name,\n mode: 'default',\n tags: element.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: ruleChildren,\n pending: false,\n };\n featureChildren.push(ruleScope);\n scopesById.set(ruleScope.id, ruleScope);\n }\n }\n\n const featureScope = {\n id: feature.uri ?? feature.name,\n kind: 'feature',\n name: feature.name,\n mode: 'default',\n tags: feature.tags ?? [],\n steps: visibleSteps,\n hooks: [],\n children: featureChildren,\n pending: false,\n };\n\n const existingRoot = basePlan.root;\n const updatedRoot = {\n ...existingRoot,\n children: [...existingRoot.children, featureScope],\n };\n\n scopesById.set(featureScope.id, featureScope);\n scopesById.set(updatedRoot.id, updatedRoot);\n\n const scopePlan = {\n root: updatedRoot,\n stepsById: scopedStepsById,\n hooksById: basePlan.hooksById,\n scopesById,\n };\n\n if (basePlan.worldFactory) {\n scopePlan.worldFactory = basePlan.worldFactory;\n }\n\n if (basePlan.parameterRegistry) {\n scopePlan.parameterRegistry = basePlan.parameterRegistry;\n }\n\n return scopePlan;\n }\n\n const gherkin = ${JSON.stringify(code)};\n const feature = parseGherkin(gherkin);\n const environments = collectStepsEnvironments(stepModules);\n const steps = selectStepsEnvironment(environments, feature);\n\n if (!steps) {\n throw new Error('Autometa could not find a steps environment for this feature. If you are using per-group environments, ensure each group exports a steps environment (\"stepsEnvironment\" or default export) under the configured step roots.');\n }\n\n CucumberRunner.setSteps(steps);\n\n describe(feature.name, () => {\n const basePlan = steps.getPlan();\n const scopedPlan = createFeatureScopePlan(feature, basePlan);\n const { plan, adapter } = coordinateRunnerFeature({\n feature,\n environment: steps,\n config: ${runtimeConfig},\n plan: scopedPlan\n });\n\n execute({ plan, adapter, config: ${runtimeConfig} });\n });\n `,\n map: null,\n };\n },\n };\n}\n\ninterface StepGlobOptions {\n readonly configDir: string;\n readonly projectRoot: string;\n}\n\nfunction buildFeatureRootDirs(\n entries: readonly string[] | undefined,\n options: StepGlobOptions\n): string[] {\n if (!entries || entries.length === 0) {\n return [];\n }\n\n const roots = new Set<string>();\n for (const entry of entries) {\n const normalized = entry.trim();\n if (!normalized) {\n continue;\n }\n\n for (const candidate of toPatterns(normalized, FEATURE_FALLBACK_GLOB)) {\n const base = inferGlobBaseDirectory(candidate);\n const absolute = isAbsolute(base)\n ? normalizeSlashes(base)\n : normalizeSlashes(resolve(options.configDir, base));\n roots.add(absolute.replace(/\\/+$/u, \"\"));\n }\n }\n\n return Array.from(roots).sort((a, b) => b.length - a.length);\n}\n\nfunction buildStepGlobs(\n entries: readonly string[] | undefined,\n options: StepGlobOptions\n): string[] {\n if (!entries || entries.length === 0) {\n return [];\n }\n\n const patterns = new Set<string>();\n for (const entry of entries) {\n const normalized = entry.trim();\n if (!normalized) {\n continue;\n }\n\n for (const candidate of toPatterns(normalized, STEP_FALLBACK_GLOB)) {\n const absolute = isAbsolute(candidate)\n ? normalizeSlashes(candidate)\n : normalizeSlashes(resolve(options.configDir, candidate));\n const rootRelative = toRootRelativeGlob(absolute, options.projectRoot);\n patterns.add(rootRelative);\n }\n }\n\n return Array.from(patterns);\n}\n\nfunction toPatterns(entry: string, fallbackGlob: string): string[] {\n if (hasGlobMagic(entry) || hasFileExtension(entry)) {\n return [entry];\n }\n return [appendGlob(entry, fallbackGlob)];\n}\n\nfunction inferGlobBaseDirectory(pattern: string): string {\n const normalized = normalizeSlashes(pattern).replace(/^!/u, \"\");\n\n for (let i = 0; i < normalized.length; i += 1) {\n if (!hasGlobMagic(normalized[i] ?? \"\")) {\n continue;\n }\n\n const prefix = normalized.slice(0, i);\n const lastSlash = prefix.lastIndexOf(\"/\");\n if (lastSlash <= 0) {\n return \".\";\n }\n const base = prefix.slice(0, lastSlash);\n return base || \".\";\n }\n\n if (hasFileExtension(normalized)) {\n return dirname(normalized);\n }\n\n return normalized || \".\";\n}\n\nfunction hasGlobMagic(input: string): boolean {\n return /[*?{}()[\\]!,@+]/u.test(input);\n}\n\nfunction hasFileExtension(input: string): boolean {\n const normalized = normalizeSlashes(input);\n const trimmed = normalized === \"/\" ? normalized : normalized.replace(/\\/+$/u, \"\");\n if (!trimmed || trimmed === \".\" || trimmed === \"..\") {\n return false;\n }\n return Boolean(extname(trimmed));\n}\n\nfunction appendGlob(entry: string, glob: string): string {\n const normalized = normalizeSlashes(entry);\n const trimmed = normalized === \"/\" ? normalized : normalized.replace(/\\/+$/u, \"\");\n if (!trimmed || trimmed === \".\") {\n return glob;\n }\n if (trimmed === \"/\") {\n return `/${glob}`;\n }\n return `${trimmed}/${glob}`;\n}\n\nfunction toRootRelativeGlob(pattern: string, rootDir: string): string {\n const normalizedRoot = normalizeSlashes(rootDir);\n const normalizedPattern = normalizeSlashes(pattern);\n let relativePattern = normalizeSlashes(relative(normalizedRoot, normalizedPattern));\n\n if (!relativePattern || relativePattern === \".\") {\n relativePattern = \"\";\n }\n\n if (!relativePattern || relativePattern.startsWith(\"..\")) {\n return ensureGlobPrefix(normalizedPattern);\n }\n\n return ensureGlobPrefix(relativePattern);\n}\n\nfunction ensureGlobPrefix(pattern: string): string {\n if (/^[A-Za-z]:\\//u.test(pattern)) {\n return pattern;\n }\n if (pattern.startsWith(\"/\")) {\n return pattern;\n }\n return `/${pattern.replace(/^\\/+/, \"\")}`;\n}\n\nfunction normalizeSlashes(pathname: string): string {\n return pathname.replace(/\\\\/gu, \"/\");\n}\n\nasync function loadAutometaConfig(root: string): Promise<{ config: Config; path: string }> {\n const _jiti = jiti(root, { interopDefault: true });\n\n const candidates = [\n \"autometa.config.ts\",\n \"autometa.config.js\",\n \"autometa.config.mts\",\n \"autometa.config.mjs\",\n \"autometa.config.cts\",\n \"autometa.config.cjs\",\n ];\n\n for (const candidate of candidates) {\n const path = resolve(root, candidate);\n if (!existsSync(path)) {\n continue;\n }\n\n try {\n const mod = _jiti(path);\n const config = mod.default || mod;\n\n if (isConfig(config)) {\n return { config, path };\n } else {\n console.warn(`Found config at ${path} but it does not export a Config instance.`);\n }\n } catch (error: unknown) {\n console.error(`Error loading config at ${path}:`, error);\n throw error;\n }\n }\n throw new Error(\"Could not find autometa.config.{ts,js,mjs,cjs} in \" + root);\n}\n\nfunction isConfig(config: unknown): config is Config {\n return (\n typeof config === \"object\" &&\n config !== null &&\n \"resolve\" in config &&\n typeof (config as Config).resolve === \"function\" &&\n \"current\" in config &&\n typeof (config as Config).current === \"function\"\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autometa/vitest-plugins",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.4",
4
4
  "description": "Vitest plugin for Autometa - transforms .feature files into test suites",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -18,7 +18,7 @@
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
20
  "jiti": "^1.21.0",
21
- "@autometa/config": "1.0.0-rc.2"
21
+ "@autometa/config": "1.0.0-rc.3"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^18.11.18",
@@ -28,16 +28,16 @@
28
28
  "typescript": "^4.9.5",
29
29
  "vite": "^5.0.0",
30
30
  "vitest": "1.4.0",
31
- "eslint-config-custom": "0.6.0",
32
31
  "tsconfig": "0.7.0",
32
+ "eslint-config-custom": "0.6.0",
33
33
  "tsup-config": "0.1.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "vite": "^5.0.0",
37
37
  "vitest": "^1.0.0",
38
- "@autometa/vitest-executor": "1.0.0-rc.2",
39
38
  "@autometa/gherkin": "1.0.0-rc.2",
40
- "@autometa/runner": "1.0.0-rc.2"
39
+ "@autometa/vitest-executor": "1.0.0-rc.4",
40
+ "@autometa/runner": "1.0.0-rc.4"
41
41
  },
42
42
  "scripts": {
43
43
  "type-check": "tsc --noEmit",