// Extract step definitions from feature files and generate a data file with source code import * as fs from 'fs'; import * as path from 'path'; interface StepDefinition { pattern: string; keyword: 'Given' | 'When' | 'Then'; file: string; sourceCode: string; lineNumber: number; } import { Glob } from 'bun'; // Discover all step definition files: shared + module-specific function discoverStepFiles(): string[] { const files: string[] = []; // Shared steps for (const f of new Glob('src/shared/steps/**/*.steps.ts').scanSync('.')) { files.push(f); } // Module steps for (const f of new Glob('src/modules/*/steps/**/*.steps.ts').scanSync('.')) { files.push(f); } return files.sort(); } const stepFiles = discoverStepFiles(); function extractStepDefinitions(): StepDefinition[] { const definitions: StepDefinition[] = []; for (const filePath of stepFiles) { const fullPath = path.join(process.cwd(), filePath); if (!fs.existsSync(fullPath)) continue; const content = fs.readFileSync(fullPath, 'utf-8'); const lines = content.split('\n'); const fileName = path.basename(filePath); // Find step definitions line by line for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Match Given/When/Then at the start of a line // Handle escaped quotes in patterns (e.g., 'l\'écran contient') const match = line?.match(/^(Given|When|Then)\s*\(\s*(['"`])((?:[^\\]|\\.)*?)\2/); if (match && match[3]) { const keyword = match[1] as 'Given' | 'When' | 'Then'; // Unescape the pattern (remove backslashes before quotes) const pattern = match[3].replace(/\\(['"`])/g, '$1'); // Extract the full function body const sourceCode = extractFunctionBody(lines, i); definitions.push({ pattern, keyword, file: fileName, sourceCode, lineNumber: i + 1, }); } } } return definitions; } function extractFunctionBody(lines: string[], startLine: number): string { // Look for the closing }); which marks the end of a step definition for (let i = startLine; i < lines.length; i++) { const line = lines[i]?.trim(); if (line === '});' || line?.endsWith('});')) { const extracted = lines.slice(startLine, i + 1); return extracted.join('\n'); } } // Fallback: return just the start line if we couldn't find the end return lines[startLine] || ''; } async function generateStepDefinitionsFile() { const definitions = extractStepDefinitions(); const findFunctionCode = `export function findStepDefinition(stepText: string): StepDefinitionInfo | null { for (const def of stepDefinitions) { // Convert Cucumber expression to regex // {string} -> "[^"]+" // {int} -> \\\\d+ const regexPattern = def.pattern .replace(/\\{string\\}/g, '"[^"]+"') .replace(/\\{int\\}/g, '\\\\d+'); try { const regex = new RegExp(regexPattern); if (regex.test(stepText)) { return def; } } catch { // If pattern fails, try simple includes const simplified = def.pattern.replace(/\\{string\\}/g, '').replace(/\\{int\\}/g, '').trim(); if (stepText.includes(simplified)) { return def; } } } return null; }`; const output = `// Auto-generated by scripts/extract-step-definitions.ts // Do not edit manually - run "bun run steps:extract" to regenerate export interface StepDefinitionInfo { pattern: string; keyword: 'Given' | 'When' | 'Then'; file: string; sourceCode: string; lineNumber: number; } export const stepDefinitions: StepDefinitionInfo[] = ${JSON.stringify(definitions, null, 2)}; ${findFunctionCode} `; await Bun.write('src/shared/data/stepDefinitions.ts', output); console.log(`Generated ${definitions.length} step definitions`); } generateStepDefinitionsFile().catch(console.error);