901fd659df
- 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>
135 lines
3.8 KiB
TypeScript
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);
|