@esimplicitylabs/katalyst-xspec 0.6.0
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/LICENSE +7 -0
- package/README.md +69 -0
- package/bin/katalyst-xspec.cjs +54 -0
- package/cli/init.cjs +679 -0
- package/cli/stubs.cjs +365 -0
- package/cli/upgrade.cjs +1014 -0
- package/dist/chunk-ACAXOGKZ.js +1611 -0
- package/dist/index.d.ts +881 -0
- package/dist/index.js +1091 -0
- package/dist/steps/index.d.ts +151 -0
- package/dist/steps/index.js +50 -0
- package/package.json +80 -0
- package/scripts/postinstall.cjs +85 -0
- package/skills/katalyst-bdd-architecture/SKILL.md +517 -0
- package/skills/katalyst-bdd-architecture/references/adapters.md +310 -0
- package/skills/katalyst-bdd-architecture/references/custom-steps.md +360 -0
- package/skills/katalyst-bdd-architecture/references/ports.md +256 -0
- package/skills/katalyst-bdd-create-test/SKILL.md +366 -0
- package/skills/katalyst-bdd-create-test/references/api-patterns.md +371 -0
- package/skills/katalyst-bdd-create-test/references/hybrid-patterns.md +420 -0
- package/skills/katalyst-bdd-create-test/references/tui-patterns.md +458 -0
- package/skills/katalyst-bdd-create-test/references/ui-patterns.md +415 -0
- package/skills/katalyst-bdd-quickstart/SKILL.md +292 -0
- package/skills/katalyst-bdd-step-reference/SKILL.md +147 -0
- package/skills/katalyst-bdd-step-reference/references/api-steps.md +247 -0
- package/skills/katalyst-bdd-step-reference/references/shared-steps.md +340 -0
- package/skills/katalyst-bdd-step-reference/references/tui-steps.md +483 -0
- package/skills/katalyst-bdd-step-reference/references/ui-steps.md +521 -0
- package/skills/katalyst-bdd-troubleshooting/SKILL.md +449 -0
package/cli/stubs.cjs
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Step Stub Generator
|
|
6
|
+
*
|
|
7
|
+
* Parses bddgen output to identify missing step definitions and generates
|
|
8
|
+
* stub implementations in a single file: features/steps/generated-stubs.ts
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npx katalyst-xspec stubs
|
|
12
|
+
* npx katalyst-xspec stubs --output custom-stubs.ts
|
|
13
|
+
* npx katalyst-xspec stubs --dry-run
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { execSync } = require('child_process');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
function parseArgs(args) {
|
|
21
|
+
const options = {
|
|
22
|
+
output: 'features/steps/generated-stubs.ts',
|
|
23
|
+
dryRun: false,
|
|
24
|
+
help: false,
|
|
25
|
+
verbose: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < args.length; i++) {
|
|
29
|
+
const arg = args[i];
|
|
30
|
+
if (arg === '--output' || arg === '-o') {
|
|
31
|
+
options.output = args[++i];
|
|
32
|
+
} else if (arg === '--dry-run') {
|
|
33
|
+
options.dryRun = true;
|
|
34
|
+
} else if (arg === '--verbose' || arg === '-v') {
|
|
35
|
+
options.verbose = true;
|
|
36
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
37
|
+
options.help = true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return options;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function showHelp() {
|
|
45
|
+
console.log(`
|
|
46
|
+
Usage: npx katalyst-xspec stubs [options]
|
|
47
|
+
|
|
48
|
+
Generate step definition stubs for missing steps detected by bddgen.
|
|
49
|
+
|
|
50
|
+
Options:
|
|
51
|
+
-o, --output <file> Output file (default: features/steps/generated-stubs.ts)
|
|
52
|
+
--dry-run Show what would be generated without writing
|
|
53
|
+
-v, --verbose Show detailed output
|
|
54
|
+
-h, --help Show this help message
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
npx katalyst-xspec stubs # Generate stubs
|
|
58
|
+
npx katalyst-xspec stubs --dry-run # Preview what would be generated
|
|
59
|
+
npx katalyst-xspec stubs -o my-stubs.ts # Custom output file
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run bddgen and capture output including missing steps
|
|
65
|
+
*/
|
|
66
|
+
function runBddgenAndCapture() {
|
|
67
|
+
try {
|
|
68
|
+
// Run bddgen and capture both stdout and stderr
|
|
69
|
+
const result = execSync('npx bddgen 2>&1', {
|
|
70
|
+
encoding: 'utf8',
|
|
71
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
|
72
|
+
});
|
|
73
|
+
return { output: result, success: true };
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// bddgen exits with non-zero when steps are missing, but we still get output
|
|
76
|
+
return { output: error.stdout || error.message, success: false };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse bddgen output to extract missing step definitions
|
|
82
|
+
*
|
|
83
|
+
* bddgen outputs missing steps in format:
|
|
84
|
+
* Given('the ClawMarket API is available', async ({}) => {
|
|
85
|
+
* When('I do something with {string}', async ({}, arg0: string) => {
|
|
86
|
+
*/
|
|
87
|
+
function parseMissingSteps(output) {
|
|
88
|
+
const steps = [];
|
|
89
|
+
const seen = new Set(); // Deduplicate
|
|
90
|
+
|
|
91
|
+
// Pattern 1: Full step definition format from bddgen
|
|
92
|
+
// Given('pattern', async ({}, ...) => {
|
|
93
|
+
const fullPattern = /(Given|When|Then)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*async\s*\(\s*\{[^}]*\}(?:\s*,\s*[^)]+)?\s*\)\s*=>\s*\{/g;
|
|
94
|
+
|
|
95
|
+
let match;
|
|
96
|
+
while ((match = fullPattern.exec(output)) !== null) {
|
|
97
|
+
const keyword = match[1];
|
|
98
|
+
const pattern = match[2];
|
|
99
|
+
const key = `${keyword}:${pattern}`;
|
|
100
|
+
|
|
101
|
+
if (!seen.has(key)) {
|
|
102
|
+
seen.add(key);
|
|
103
|
+
steps.push({ keyword, pattern });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Pattern 2: Simpler format (just in case)
|
|
108
|
+
// Given('pattern'
|
|
109
|
+
const simplePattern = /(Given|When|Then)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
110
|
+
|
|
111
|
+
while ((match = simplePattern.exec(output)) !== null) {
|
|
112
|
+
const keyword = match[1];
|
|
113
|
+
const pattern = match[2];
|
|
114
|
+
const key = `${keyword}:${pattern}`;
|
|
115
|
+
|
|
116
|
+
if (!seen.has(key)) {
|
|
117
|
+
seen.add(key);
|
|
118
|
+
steps.push({ keyword, pattern });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return steps;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Extract Cucumber expression parameters from a step pattern
|
|
127
|
+
* @param {string} pattern - e.g., "I click the {string} button"
|
|
128
|
+
* @returns {Array<{name: string, type: string}>}
|
|
129
|
+
*/
|
|
130
|
+
function extractParameters(pattern) {
|
|
131
|
+
const params = [];
|
|
132
|
+
const paramRegex = /\{([^}]+)\}/g;
|
|
133
|
+
let match;
|
|
134
|
+
let index = 0;
|
|
135
|
+
|
|
136
|
+
while ((match = paramRegex.exec(pattern)) !== null) {
|
|
137
|
+
const type = match[1];
|
|
138
|
+
let tsType = 'string';
|
|
139
|
+
let name = `arg${index}`;
|
|
140
|
+
|
|
141
|
+
switch (type) {
|
|
142
|
+
case 'int':
|
|
143
|
+
tsType = 'number';
|
|
144
|
+
name = `num${index}`;
|
|
145
|
+
break;
|
|
146
|
+
case 'float':
|
|
147
|
+
tsType = 'number';
|
|
148
|
+
name = `float${index}`;
|
|
149
|
+
break;
|
|
150
|
+
case 'string':
|
|
151
|
+
tsType = 'string';
|
|
152
|
+
name = `str${index}`;
|
|
153
|
+
break;
|
|
154
|
+
case 'word':
|
|
155
|
+
tsType = 'string';
|
|
156
|
+
name = `word${index}`;
|
|
157
|
+
break;
|
|
158
|
+
default:
|
|
159
|
+
// Custom type or any
|
|
160
|
+
tsType = 'string';
|
|
161
|
+
name = `${type}${index}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
params.push({ name, type: tsType, cucumberType: type });
|
|
165
|
+
index++;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return params;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Generate TypeScript stub for a single step
|
|
173
|
+
*/
|
|
174
|
+
function generateStepStub(step) {
|
|
175
|
+
const params = extractParameters(step.pattern);
|
|
176
|
+
|
|
177
|
+
// Build parameter list for the async function
|
|
178
|
+
let paramList = '{ world }';
|
|
179
|
+
if (params.length > 0) {
|
|
180
|
+
const paramDefs = params.map(p => `${p.name}: ${p.type}`).join(', ');
|
|
181
|
+
paramList = `{ world }, ${paramDefs}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Build the step definition
|
|
185
|
+
const stub = `
|
|
186
|
+
// TODO: Implement this step
|
|
187
|
+
// Pattern: ${step.pattern}
|
|
188
|
+
${step.keyword}('${step.pattern.replace(/'/g, "\\'")}', async (${paramList}) => {
|
|
189
|
+
throw new Error('Step not implemented: ${step.pattern.replace(/'/g, "\\'")}');
|
|
190
|
+
});`;
|
|
191
|
+
|
|
192
|
+
return stub;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generate the complete stubs file
|
|
197
|
+
*/
|
|
198
|
+
function generateStubsFile(steps) {
|
|
199
|
+
// Group steps by keyword for organization
|
|
200
|
+
const givenSteps = steps.filter(s => s.keyword === 'Given');
|
|
201
|
+
const whenSteps = steps.filter(s => s.keyword === 'When');
|
|
202
|
+
const thenSteps = steps.filter(s => s.keyword === 'Then');
|
|
203
|
+
|
|
204
|
+
let content = `/**
|
|
205
|
+
* Generated Step Stubs
|
|
206
|
+
*
|
|
207
|
+
* This file was auto-generated by katalyst-xspec stubs.
|
|
208
|
+
* It contains stub implementations for all missing step definitions.
|
|
209
|
+
*
|
|
210
|
+
* Instructions:
|
|
211
|
+
* 1. Review each stub and implement the actual logic
|
|
212
|
+
* 2. Move implemented steps to appropriate step files (e.g., my-steps.ts)
|
|
213
|
+
* 3. Delete this file or the implemented stubs
|
|
214
|
+
* 4. Add import to steps.ts: import './generated-stubs.js';
|
|
215
|
+
*
|
|
216
|
+
* Generated: ${new Date().toISOString()}
|
|
217
|
+
* Total stubs: ${steps.length}
|
|
218
|
+
*/
|
|
219
|
+
|
|
220
|
+
import { createBdd } from 'playwright-bdd';
|
|
221
|
+
import { test } from './fixtures.js';
|
|
222
|
+
|
|
223
|
+
const { Given, When, Then } = createBdd(test);
|
|
224
|
+
`;
|
|
225
|
+
|
|
226
|
+
// Add Given steps
|
|
227
|
+
if (givenSteps.length > 0) {
|
|
228
|
+
content += `
|
|
229
|
+
// ============================================================================
|
|
230
|
+
// GIVEN STEPS (${givenSteps.length})
|
|
231
|
+
// ============================================================================
|
|
232
|
+
`;
|
|
233
|
+
for (const step of givenSteps) {
|
|
234
|
+
content += generateStepStub(step);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Add When steps
|
|
239
|
+
if (whenSteps.length > 0) {
|
|
240
|
+
content += `
|
|
241
|
+
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// WHEN STEPS (${whenSteps.length})
|
|
244
|
+
// ============================================================================
|
|
245
|
+
`;
|
|
246
|
+
for (const step of whenSteps) {
|
|
247
|
+
content += generateStepStub(step);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Add Then steps
|
|
252
|
+
if (thenSteps.length > 0) {
|
|
253
|
+
content += `
|
|
254
|
+
|
|
255
|
+
// ============================================================================
|
|
256
|
+
// THEN STEPS (${thenSteps.length})
|
|
257
|
+
// ============================================================================
|
|
258
|
+
`;
|
|
259
|
+
for (const step of thenSteps) {
|
|
260
|
+
content += generateStepStub(step);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
content += '\n';
|
|
265
|
+
|
|
266
|
+
return content;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Main function
|
|
271
|
+
*/
|
|
272
|
+
async function main() {
|
|
273
|
+
const options = parseArgs(process.argv.slice(2));
|
|
274
|
+
|
|
275
|
+
if (options.help) {
|
|
276
|
+
showHelp();
|
|
277
|
+
process.exit(0);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log('Step Stub Generator');
|
|
281
|
+
console.log('═'.repeat(50));
|
|
282
|
+
console.log('');
|
|
283
|
+
|
|
284
|
+
// Run bddgen to get missing steps
|
|
285
|
+
console.log('Running bddgen to detect missing steps...');
|
|
286
|
+
const { output, success } = runBddgenAndCapture();
|
|
287
|
+
|
|
288
|
+
if (options.verbose) {
|
|
289
|
+
console.log('');
|
|
290
|
+
console.log('bddgen output:');
|
|
291
|
+
console.log('─'.repeat(50));
|
|
292
|
+
console.log(output);
|
|
293
|
+
console.log('─'.repeat(50));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Parse missing steps
|
|
297
|
+
const steps = parseMissingSteps(output);
|
|
298
|
+
|
|
299
|
+
if (steps.length === 0) {
|
|
300
|
+
console.log('');
|
|
301
|
+
if (success) {
|
|
302
|
+
console.log('No missing steps detected. All steps are implemented!');
|
|
303
|
+
} else {
|
|
304
|
+
console.log('Could not detect missing steps from bddgen output.');
|
|
305
|
+
console.log('Try running with --verbose to see the full output.');
|
|
306
|
+
}
|
|
307
|
+
process.exit(0);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log(`Found ${steps.length} missing step(s)`);
|
|
311
|
+
console.log('');
|
|
312
|
+
|
|
313
|
+
// Group by keyword for summary
|
|
314
|
+
const givenCount = steps.filter(s => s.keyword === 'Given').length;
|
|
315
|
+
const whenCount = steps.filter(s => s.keyword === 'When').length;
|
|
316
|
+
const thenCount = steps.filter(s => s.keyword === 'Then').length;
|
|
317
|
+
|
|
318
|
+
console.log('Summary:');
|
|
319
|
+
console.log(` Given: ${givenCount}`);
|
|
320
|
+
console.log(` When: ${whenCount}`);
|
|
321
|
+
console.log(` Then: ${thenCount}`);
|
|
322
|
+
console.log('');
|
|
323
|
+
|
|
324
|
+
// Generate stub file content
|
|
325
|
+
const stubContent = generateStubsFile(steps);
|
|
326
|
+
|
|
327
|
+
if (options.dryRun) {
|
|
328
|
+
console.log('Generated content (dry run):');
|
|
329
|
+
console.log('─'.repeat(50));
|
|
330
|
+
console.log(stubContent);
|
|
331
|
+
console.log('─'.repeat(50));
|
|
332
|
+
console.log('');
|
|
333
|
+
console.log('Run without --dry-run to write to file.');
|
|
334
|
+
process.exit(0);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Write the file
|
|
338
|
+
const outputPath = path.resolve(process.cwd(), options.output);
|
|
339
|
+
const outputDir = path.dirname(outputPath);
|
|
340
|
+
|
|
341
|
+
// Ensure directory exists
|
|
342
|
+
if (!fs.existsSync(outputDir)) {
|
|
343
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
fs.writeFileSync(outputPath, stubContent);
|
|
347
|
+
|
|
348
|
+
console.log(`Generated stubs written to: ${options.output}`);
|
|
349
|
+
console.log('');
|
|
350
|
+
console.log('Next steps:');
|
|
351
|
+
console.log(' 1. Add to steps.ts: import \'./generated-stubs.js\';');
|
|
352
|
+
console.log(' 2. Implement each stub (replace throw with actual logic)');
|
|
353
|
+
console.log(' 3. Move implemented steps to appropriate files');
|
|
354
|
+
console.log(' 4. Run: npm run gen');
|
|
355
|
+
console.log(' 5. Run: npm test');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (require.main === module) {
|
|
359
|
+
main().catch((err) => {
|
|
360
|
+
console.error('Error:', err.message);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
module.exports = { main };
|