Files
festipod/scripts/extract-step-definitions.ts
T
Sylvain Duchesne 901fd659df NextGraph integration (WIP), broker banner, and feature-based architecture
- Add NextGraph data layer with @ng-org/orm, SHEX shapes (Event, UserProfile,
  Participation), session management, and FestipodDataContext with dual-mode
  operation (connected via NextGraph or local seed data)
- Add BrokerBanner and NgStatus components showing connection status
- Refactor to feature-based architecture: organize code by business domain
  (event, user, home, auth, workshop, meeting, notification) instead of
  technical layer. Modules only import from shared/, never from each other
- Collocate BDD features and step definitions with their modules: event-specific
  steps in event/steps/, user steps in user/steps/, shared generic steps remain
  in shared/steps/
- Set up multi-layer BDD structure (frontend/backend/e2e steps per module)
- Add project documentation (AGENTS.md, .project/knowledge/)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 12:19:45 +01:00

135 lines
3.8 KiB
TypeScript

// 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);