@vijaypjavvadi/bdd2pw 3.10.0 → 4.0.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.
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ /**
3
+ * v4.0.0 — DataLoader.
4
+ *
5
+ * Reads CSV / JSON / XLSX into a normalized `DataRow[]` for injection
6
+ * into Scenario Outline Examples tables.
7
+ *
8
+ * Normalization contract:
9
+ * - All values are coerced to strings (Gherkin Examples tables are
10
+ * always string-typed; the test code does the parsing).
11
+ * - Headers are trimmed; empty headers are rejected up-front so the
12
+ * placeholder-to-column matching downstream is deterministic.
13
+ * - Empty rows (all-blank cells) are dropped silently — they're
14
+ * a frequent artifact of hand-edited CSVs.
15
+ *
16
+ * Format detection is by file extension. .xlsx loading is lazy via
17
+ * the `xlsx` optional dep — if missing, the user gets a clear
18
+ * `npm install xlsx` error instead of a module-resolution crash.
19
+ *
20
+ * Error model: every loader function throws `DataLoaderError` with
21
+ * a human-readable message. The orchestrator in `scaffold()` catches
22
+ * this and surfaces it as a `ReviewItem` so the failure shows up in
23
+ * BDD_REVIEW.md alongside other diagnostics — not just on stderr.
24
+ */
25
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ var desc = Object.getOwnPropertyDescriptor(m, k);
28
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
29
+ desc = { enumerable: true, get: function() { return m[k]; } };
30
+ }
31
+ Object.defineProperty(o, k2, desc);
32
+ }) : (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ o[k2] = m[k];
35
+ }));
36
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
37
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
38
+ }) : function(o, v) {
39
+ o["default"] = v;
40
+ });
41
+ var __importStar = (this && this.__importStar) || (function () {
42
+ var ownKeys = function(o) {
43
+ ownKeys = Object.getOwnPropertyNames || function (o) {
44
+ var ar = [];
45
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
46
+ return ar;
47
+ };
48
+ return ownKeys(o);
49
+ };
50
+ return function (mod) {
51
+ if (mod && mod.__esModule) return mod;
52
+ var result = {};
53
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
54
+ __setModuleDefault(result, mod);
55
+ return result;
56
+ };
57
+ })();
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.DataLoaderError = void 0;
60
+ exports.loadDataFile = loadDataFile;
61
+ exports.parseCsv = parseCsv;
62
+ exports.parseJson = parseJson;
63
+ exports.parseXlsx = parseXlsx;
64
+ exports.validateColumnsForPlaceholders = validateColumnsForPlaceholders;
65
+ const fs = __importStar(require("fs"));
66
+ const path = __importStar(require("path"));
67
+ class DataLoaderError extends Error {
68
+ constructor(message, file) {
69
+ super(message);
70
+ this.file = file;
71
+ this.name = "DataLoaderError";
72
+ }
73
+ }
74
+ exports.DataLoaderError = DataLoaderError;
75
+ /**
76
+ * Entry point. Routes by extension to the right parser.
77
+ *
78
+ * @param filePath Absolute or relative path to a .csv / .json / .xlsx file.
79
+ * @returns Array of rows, each a string-keyed dict.
80
+ * @throws DataLoaderError on any read / parse / shape error.
81
+ */
82
+ function loadDataFile(filePath) {
83
+ const abs = path.resolve(filePath);
84
+ if (!fs.existsSync(abs)) {
85
+ throw new DataLoaderError(`Data file not found: ${filePath}`, filePath);
86
+ }
87
+ const ext = path.extname(abs).toLowerCase();
88
+ switch (ext) {
89
+ case ".csv":
90
+ return parseCsv(fs.readFileSync(abs, "utf-8"), filePath);
91
+ case ".json":
92
+ return parseJson(fs.readFileSync(abs, "utf-8"), filePath);
93
+ case ".xlsx":
94
+ case ".xls":
95
+ return parseXlsx(abs, filePath);
96
+ default:
97
+ throw new DataLoaderError(`Unsupported data file extension "${ext}". Supported: .csv, .json, .xlsx`, filePath);
98
+ }
99
+ }
100
+ /**
101
+ * CSV parser — RFC 4180 compliant for the practical subset:
102
+ * - Quoted fields with embedded commas
103
+ * - Quoted fields with embedded newlines
104
+ * - Escaped quotes (`""` inside a quoted field)
105
+ * - Mixed CRLF / LF line endings
106
+ *
107
+ * Does NOT delegate to papaparse — keeping this dependency-free since
108
+ * the format is small enough to handle inline and we want bdd2pw to
109
+ * scaffold without an `npm install` step on the user's machine.
110
+ */
111
+ function parseCsv(text, fileLabel) {
112
+ // Strip UTF-8 BOM if present. PowerShell's `Out-File -Encoding utf8`
113
+ // adds one by default, as do several Windows editors. Without this
114
+ // strip, the first header gets a U+FEFF prefix and downstream column
115
+ // matching silently fails.
116
+ if (text.charCodeAt(0) === 0xfeff)
117
+ text = text.slice(1);
118
+ // Normalize line endings, drop a trailing newline if present.
119
+ const normalized = text.replace(/\r\n?/g, "\n").replace(/\n$/, "");
120
+ if (normalized.length === 0) {
121
+ throw new DataLoaderError("CSV file is empty", fileLabel);
122
+ }
123
+ const lines = [];
124
+ let current = [];
125
+ let field = "";
126
+ let inQuotes = false;
127
+ let i = 0;
128
+ while (i < normalized.length) {
129
+ const c = normalized[i];
130
+ if (inQuotes) {
131
+ if (c === '"') {
132
+ if (normalized[i + 1] === '"') {
133
+ field += '"';
134
+ i += 2;
135
+ continue;
136
+ }
137
+ inQuotes = false;
138
+ i += 1;
139
+ continue;
140
+ }
141
+ field += c;
142
+ i += 1;
143
+ }
144
+ else {
145
+ if (c === '"') {
146
+ inQuotes = true;
147
+ i += 1;
148
+ }
149
+ else if (c === ",") {
150
+ current.push(field);
151
+ field = "";
152
+ i += 1;
153
+ }
154
+ else if (c === "\n") {
155
+ current.push(field);
156
+ field = "";
157
+ lines.push(current);
158
+ current = [];
159
+ i += 1;
160
+ }
161
+ else {
162
+ field += c;
163
+ i += 1;
164
+ }
165
+ }
166
+ }
167
+ // Flush last field + last row.
168
+ current.push(field);
169
+ lines.push(current);
170
+ if (lines.length < 2) {
171
+ throw new DataLoaderError("CSV must have at least a header row and one data row", fileLabel);
172
+ }
173
+ const headers = lines[0].map((h) => h.trim());
174
+ validateHeaders(headers, fileLabel);
175
+ const rows = [];
176
+ for (let r = 1; r < lines.length; r++) {
177
+ const cells = lines[r];
178
+ if (isBlankRow(cells))
179
+ continue;
180
+ const row = {};
181
+ for (let c = 0; c < headers.length; c++) {
182
+ row[headers[c]] = (cells[c] ?? "").trim();
183
+ }
184
+ rows.push(row);
185
+ }
186
+ if (rows.length === 0) {
187
+ throw new DataLoaderError("CSV had a header row but no non-empty data rows", fileLabel);
188
+ }
189
+ return rows;
190
+ }
191
+ /**
192
+ * JSON parser — accepts either:
193
+ * 1. Array of objects: `[{a:1,b:2}, {a:3,b:4}]` — most natural shape.
194
+ * 2. Object with `rows`: `{rows: [{...}, {...}]}` — useful for files
195
+ * that also carry metadata (generator seed, source URL, etc).
196
+ *
197
+ * Numbers and booleans are stringified for Gherkin Examples compatibility.
198
+ * Null values become empty strings.
199
+ */
200
+ function parseJson(text, fileLabel) {
201
+ // Strip UTF-8 BOM (see parseCsv for rationale).
202
+ if (text.charCodeAt(0) === 0xfeff)
203
+ text = text.slice(1);
204
+ let parsed;
205
+ try {
206
+ parsed = JSON.parse(text);
207
+ }
208
+ catch (err) {
209
+ const msg = err instanceof Error ? err.message : String(err);
210
+ throw new DataLoaderError(`JSON parse error: ${msg}`, fileLabel);
211
+ }
212
+ let raw;
213
+ if (Array.isArray(parsed)) {
214
+ raw = parsed;
215
+ }
216
+ else if (parsed &&
217
+ typeof parsed === "object" &&
218
+ Array.isArray(parsed.rows)) {
219
+ raw = parsed.rows;
220
+ }
221
+ else {
222
+ throw new DataLoaderError('JSON must be an array of objects OR an object with a "rows" array', fileLabel);
223
+ }
224
+ if (raw.length === 0) {
225
+ throw new DataLoaderError("JSON contained zero rows", fileLabel);
226
+ }
227
+ const rows = [];
228
+ let headersSeen = null;
229
+ for (const item of raw) {
230
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
231
+ throw new DataLoaderError(`Every JSON row must be a plain object; got: ${JSON.stringify(item)?.slice(0, 80)}`, fileLabel);
232
+ }
233
+ const obj = item;
234
+ const row = {};
235
+ for (const [k, v] of Object.entries(obj)) {
236
+ row[k] = v === null || v === undefined ? "" : String(v);
237
+ }
238
+ if (!headersSeen) {
239
+ headersSeen = new Set(Object.keys(row));
240
+ validateHeaders(Array.from(headersSeen), fileLabel);
241
+ }
242
+ rows.push(row);
243
+ }
244
+ return rows;
245
+ }
246
+ /**
247
+ * XLSX parser — uses the optional `xlsx` dep. Reads the FIRST sheet
248
+ * only; users with multi-sheet workbooks should export the sheet they
249
+ * want to CSV.
250
+ *
251
+ * Lazy-loaded so users who never use XLSX don't need the dep installed.
252
+ */
253
+ function parseXlsx(absPath, fileLabel) {
254
+ let xlsx;
255
+ try {
256
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
257
+ xlsx = require("xlsx");
258
+ }
259
+ catch {
260
+ throw new DataLoaderError("xlsx package not installed. Run: npm install xlsx", fileLabel);
261
+ }
262
+ const wb = xlsx.readFile(absPath);
263
+ const firstSheetName = wb.SheetNames[0];
264
+ if (!firstSheetName) {
265
+ throw new DataLoaderError("XLSX has no sheets", fileLabel);
266
+ }
267
+ const sheet = wb.Sheets[firstSheetName];
268
+ // sheet_to_json with header:1 returns array of arrays — we then
269
+ // promote first row to headers ourselves so the validation flow
270
+ // matches the CSV path exactly.
271
+ const aoa = xlsx.utils.sheet_to_json(sheet, {
272
+ header: 1,
273
+ defval: "",
274
+ blankrows: false,
275
+ raw: false,
276
+ });
277
+ if (aoa.length < 2) {
278
+ throw new DataLoaderError("XLSX must have at least a header row and one data row", fileLabel);
279
+ }
280
+ const headers = aoa[0].map((h) => String(h ?? "").trim());
281
+ validateHeaders(headers, fileLabel);
282
+ const rows = [];
283
+ for (let r = 1; r < aoa.length; r++) {
284
+ const cells = aoa[r];
285
+ if (isBlankRow(cells.map((c) => String(c ?? ""))))
286
+ continue;
287
+ const row = {};
288
+ for (let c = 0; c < headers.length; c++) {
289
+ row[headers[c]] = String(cells[c] ?? "").trim();
290
+ }
291
+ rows.push(row);
292
+ }
293
+ if (rows.length === 0) {
294
+ throw new DataLoaderError("XLSX had a header row but no non-empty data rows", fileLabel);
295
+ }
296
+ return rows;
297
+ }
298
+ // --- helpers ----------------------------------------------------------------
299
+ function validateHeaders(headers, fileLabel) {
300
+ if (headers.length === 0) {
301
+ throw new DataLoaderError("No headers found", fileLabel);
302
+ }
303
+ const blanks = headers
304
+ .map((h, i) => (h.length === 0 ? i : -1))
305
+ .filter((i) => i >= 0);
306
+ if (blanks.length > 0) {
307
+ throw new DataLoaderError(`Header row has blank column(s) at index: ${blanks.join(", ")}`, fileLabel);
308
+ }
309
+ const seen = new Set();
310
+ const dupes = [];
311
+ for (const h of headers) {
312
+ if (seen.has(h))
313
+ dupes.push(h);
314
+ seen.add(h);
315
+ }
316
+ if (dupes.length > 0) {
317
+ throw new DataLoaderError(`Duplicate header(s): ${[...new Set(dupes)].join(", ")}`, fileLabel);
318
+ }
319
+ }
320
+ function isBlankRow(cells) {
321
+ return cells.every((c) => (c ?? "").toString().trim() === "");
322
+ }
323
+ /**
324
+ * Validates that the loaded data rows have every column the Scenario
325
+ * Outline placeholders reference. Returns the list of missing column
326
+ * names; empty array means OK.
327
+ *
328
+ * Called by the scaffold orchestrator after parsing the .feature file.
329
+ * If anything is missing, the orchestrator surfaces it as a
330
+ * `ReviewItem` and skips Examples injection for that scenario rather
331
+ * than producing a broken spec.
332
+ */
333
+ function validateColumnsForPlaceholders(rows, placeholders) {
334
+ if (rows.length === 0)
335
+ return placeholders;
336
+ const presentColumns = new Set(Object.keys(rows[0]));
337
+ return placeholders.filter((p) => !presentColumns.has(p));
338
+ }
339
+ //# sourceMappingURL=dataLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataLoader.js","sourceRoot":"","sources":["../../src/data/dataLoader.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBH,oCAoBC;AAaD,4BAoFC;AAWD,8BAoDC;AASD,8BAmDC;AA6CD,wEAOC;AA3TD,uCAAyB;AACzB,2CAA6B;AAK7B,MAAa,eAAgB,SAAQ,KAAK;IACxC,YACE,OAAe,EACC,IAAa;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,SAAI,GAAJ,IAAI,CAAS;QAG7B,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AARD,0CAQC;AAED;;;;;;GAMG;AACH,SAAgB,YAAY,CAAC,QAAgB;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,eAAe,CAAC,wBAAwB,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5C,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM;YACT,OAAO,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC3D,KAAK,OAAO;YACV,OAAO,SAAS,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC5D,KAAK,OAAO,CAAC;QACb,KAAK,MAAM;YACT,OAAO,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAClC;YACE,MAAM,IAAI,eAAe,CACvB,oCAAoC,GAAG,kCAAkC,EACzE,QAAQ,CACT,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,QAAQ,CAAC,IAAY,EAAE,SAAkB;IACvD,qEAAqE;IACrE,mEAAmE;IACnE,qEAAqE;IACrE,2BAA2B;IAC3B,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,MAAM;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxD,8DAA8D;IAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACnE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,eAAe,CAAC,mBAAmB,EAAE,SAAS,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,IAAI,OAAO,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,CAAC,GAAG,CAAC,CAAC;IAEV,OAAO,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;gBACd,IAAI,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBAC9B,KAAK,IAAI,GAAG,CAAC;oBACb,CAAC,IAAI,CAAC,CAAC;oBACP,SAAS;gBACX,CAAC;gBACD,QAAQ,GAAG,KAAK,CAAC;gBACjB,CAAC,IAAI,CAAC,CAAC;gBACP,SAAS;YACX,CAAC;YACD,KAAK,IAAI,CAAC,CAAC;YACX,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;gBACd,QAAQ,GAAG,IAAI,CAAC;gBAChB,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;iBAAM,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACpB,KAAK,GAAG,EAAE,CAAC;gBACX,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;iBAAM,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;gBACtB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACpB,KAAK,GAAG,EAAE,CAAC;gBACX,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACpB,OAAO,GAAG,EAAE,CAAC;gBACb,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;iBAAM,CAAC;gBACN,KAAK,IAAI,CAAC,CAAC;gBACX,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;QACH,CAAC;IACH,CAAC;IACD,+BAA+B;IAC/B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEpB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,eAAe,CACvB,sDAAsD,EACtD,SAAS,CACV,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9C,eAAe,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAEpC,MAAM,IAAI,GAAc,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,UAAU,CAAC,KAAK,CAAC;YAAE,SAAS;QAChC,MAAM,GAAG,GAAY,EAAE,CAAC;QACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5C,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,eAAe,CACvB,iDAAiD,EACjD,SAAS,CACV,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,SAAS,CAAC,IAAY,EAAE,SAAkB;IACxD,gDAAgD;IAChD,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,MAAM;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,eAAe,CAAC,qBAAqB,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,GAAc,CAAC;IACnB,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;SAAM,IACL,MAAM;QACN,OAAO,MAAM,KAAK,QAAQ;QAC1B,KAAK,CAAC,OAAO,CAAE,MAA6B,CAAC,IAAI,CAAC,EAClD,CAAC;QACD,GAAG,GAAI,MAA8B,CAAC,IAAI,CAAC;IAC7C,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,eAAe,CACvB,mEAAmE,EACnE,SAAS,CACV,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,eAAe,CACvB,0BAA0B,EAC1B,SAAS,CACV,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAc,EAAE,CAAC;IAC3B,IAAI,WAAW,GAAuB,IAAI,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7D,MAAM,IAAI,eAAe,CACvB,+CAA+C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EACnF,SAAS,CACV,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,IAA+B,CAAC;QAC5C,MAAM,GAAG,GAAY,EAAE,CAAC;QACxB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACzC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YACxC,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,SAAS,CAAC,OAAe,EAAE,SAAkB;IAC3D,IAAI,IAAS,CAAC;IACd,IAAI,CAAC;QACH,8DAA8D;QAC9D,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,eAAe,CACvB,mDAAmD,EACnD,SAAS,CACV,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,cAAc,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACxC,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,eAAe,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAC;IAC7D,CAAC;IACD,MAAM,KAAK,GAAG,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IACxC,gEAAgE;IAChE,gEAAgE;IAChE,gCAAgC;IAChC,MAAM,GAAG,GAAgB,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE;QACvD,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,EAAE;QACV,SAAS,EAAE,KAAK;QAChB,GAAG,EAAE,KAAK;KACX,CAAC,CAAC;IACH,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,eAAe,CACvB,uDAAuD,EACvD,SAAS,CACV,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1D,eAAe,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACpC,MAAM,IAAI,GAAc,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAAE,SAAS;QAC5D,MAAM,GAAG,GAAY,EAAE,CAAC;QACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAClD,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,eAAe,CACvB,kDAAkD,EAClD,SAAS,CACV,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+EAA+E;AAE/E,SAAS,eAAe,CAAC,OAAiB,EAAE,SAAkB;IAC5D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,eAAe,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,MAAM,GAAG,OAAO;SACnB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACxC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACzB,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,eAAe,CACvB,4CAA4C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAC/D,SAAS,CACV,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACd,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,eAAe,CACvB,wBAAwB,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EACxD,SAAS,CACV,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAe;IACjC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,8BAA8B,CAC5C,IAAe,EACf,YAAsB;IAEtB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,YAAY,CAAC;IAC3C,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * v4.0.0 — ExamplesInjector.
3
+ *
4
+ * Mutates a parsed `FeatureIR` in place by replacing the `examples`
5
+ * array on every Scenario Outline with externally-sourced rows
6
+ * (from a CSV/JSON/XLSX file via DataLoader, or from a synthetic
7
+ * generator via SynthGenerator).
8
+ *
9
+ * Design decisions (locked in during v4.0.0 planning):
10
+ *
11
+ * 1. External rows OVERRIDE inline Examples entirely. Inline Examples
12
+ * in the .feature file become the no-data fallback. When --data is
13
+ * passed, every Scenario Outline gets its examples swapped.
14
+ *
15
+ * 2. Scenarios without Outline syntax (no <placeholder>) are skipped.
16
+ * A vanilla Scenario doesn't care about data injection.
17
+ *
18
+ * 3. If a scenario's placeholders reference columns NOT present in
19
+ * the data, that scenario is LEFT ALONE (keeps inline Examples)
20
+ * and the gap is logged as a `ReviewItem` so BDD_REVIEW.md surfaces
21
+ * the mismatch. Partial column overrides are not supported on
22
+ * purpose — they produce confusing test output.
23
+ *
24
+ * 4. On synth-generator failure (LLM down, schema invalid, Faker
25
+ * missing), we leave inline Examples in place and surface a
26
+ * `ReviewItem` warning. Same graceful-degradation philosophy as
27
+ * the LLM-fallback failure mode.
28
+ */
29
+ import type { FeatureIR, ReviewItem, ScenarioIR } from "../types";
30
+ import type { DataRow } from "./dataLoader";
31
+ /**
32
+ * Discover the placeholder names referenced anywhere in a scenario's
33
+ * step text. Gherkin syntax: `<name>` substitutes a value from the
34
+ * row keyed by `name`.
35
+ *
36
+ * Returns a deduped, sorted list. Empty list = vanilla Scenario, not
37
+ * an Outline.
38
+ */
39
+ export declare function extractPlaceholders(scenario: ScenarioIR): string[];
40
+ /**
41
+ * Inject `rows` into every Scenario Outline of `feature`. Returns
42
+ * a report describing what was swapped and what was skipped (so the
43
+ * caller can emit `ReviewItem`s and pino logs).
44
+ *
45
+ * Mutates `feature` in place — the caller passes the already-parsed
46
+ * IR and gets back a mutation summary.
47
+ */
48
+ export interface InjectionReport {
49
+ totalScenarios: number;
50
+ swappedScenarios: number;
51
+ skippedScenarios: Array<{
52
+ name: string;
53
+ reason: string;
54
+ }>;
55
+ rowsPerSwap: number;
56
+ }
57
+ export declare function injectExamples(feature: FeatureIR, rows: DataRow[]): InjectionReport;
58
+ /**
59
+ * Convert an `InjectionReport` into `ReviewItem`s ready to push onto
60
+ * the scaffold's review queue. One info line per swap, one warning
61
+ * per skip whose reason indicates a real misconfiguration (missing
62
+ * columns) — skips for "vanilla Scenario" or "zero rows" are info-only.
63
+ */
64
+ export declare function reportToReviewItems(report: InjectionReport, source: string): ReviewItem[];
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * v4.0.0 — ExamplesInjector.
4
+ *
5
+ * Mutates a parsed `FeatureIR` in place by replacing the `examples`
6
+ * array on every Scenario Outline with externally-sourced rows
7
+ * (from a CSV/JSON/XLSX file via DataLoader, or from a synthetic
8
+ * generator via SynthGenerator).
9
+ *
10
+ * Design decisions (locked in during v4.0.0 planning):
11
+ *
12
+ * 1. External rows OVERRIDE inline Examples entirely. Inline Examples
13
+ * in the .feature file become the no-data fallback. When --data is
14
+ * passed, every Scenario Outline gets its examples swapped.
15
+ *
16
+ * 2. Scenarios without Outline syntax (no <placeholder>) are skipped.
17
+ * A vanilla Scenario doesn't care about data injection.
18
+ *
19
+ * 3. If a scenario's placeholders reference columns NOT present in
20
+ * the data, that scenario is LEFT ALONE (keeps inline Examples)
21
+ * and the gap is logged as a `ReviewItem` so BDD_REVIEW.md surfaces
22
+ * the mismatch. Partial column overrides are not supported on
23
+ * purpose — they produce confusing test output.
24
+ *
25
+ * 4. On synth-generator failure (LLM down, schema invalid, Faker
26
+ * missing), we leave inline Examples in place and surface a
27
+ * `ReviewItem` warning. Same graceful-degradation philosophy as
28
+ * the LLM-fallback failure mode.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.extractPlaceholders = extractPlaceholders;
32
+ exports.injectExamples = injectExamples;
33
+ exports.reportToReviewItems = reportToReviewItems;
34
+ /**
35
+ * Discover the placeholder names referenced anywhere in a scenario's
36
+ * step text. Gherkin syntax: `<name>` substitutes a value from the
37
+ * row keyed by `name`.
38
+ *
39
+ * Returns a deduped, sorted list. Empty list = vanilla Scenario, not
40
+ * an Outline.
41
+ */
42
+ function extractPlaceholders(scenario) {
43
+ const found = new Set();
44
+ const re = /<([A-Za-z_][\w-]*)>/g;
45
+ for (const step of scenario.steps) {
46
+ let m;
47
+ while ((m = re.exec(step.text)) !== null)
48
+ found.add(m[1]);
49
+ // Also scan doc-string and data-table arguments.
50
+ if (typeof step.argument === "string") {
51
+ while ((m = re.exec(step.argument)) !== null)
52
+ found.add(m[1]);
53
+ }
54
+ else if (Array.isArray(step.argument)) {
55
+ for (const row of step.argument) {
56
+ for (const cell of row) {
57
+ while ((m = re.exec(cell)) !== null)
58
+ found.add(m[1]);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ // Also walk the existing examples table headers, since some
64
+ // scenarios reference a placeholder only via the row title and not
65
+ // in step text. (Rare, but legal Gherkin.)
66
+ if (scenario.examples && scenario.examples.length > 0) {
67
+ for (const k of Object.keys(scenario.examples[0])) {
68
+ // Headers don't need a `< >` wrapper in the parsed IR — we
69
+ // include them so column matching is symmetric with whatever
70
+ // the .feature file declared.
71
+ found.add(k);
72
+ }
73
+ }
74
+ return Array.from(found).sort();
75
+ }
76
+ function injectExamples(feature, rows) {
77
+ const report = {
78
+ totalScenarios: feature.scenarios.length,
79
+ swappedScenarios: 0,
80
+ skippedScenarios: [],
81
+ rowsPerSwap: rows.length,
82
+ };
83
+ if (rows.length === 0) {
84
+ // Nothing to inject — leave inline Examples untouched.
85
+ for (const s of feature.scenarios) {
86
+ report.skippedScenarios.push({
87
+ name: s.name,
88
+ reason: "data file had zero rows",
89
+ });
90
+ }
91
+ return report;
92
+ }
93
+ const dataColumns = new Set(Object.keys(rows[0]));
94
+ for (const scenario of feature.scenarios) {
95
+ const placeholders = extractPlaceholders(scenario);
96
+ if (placeholders.length === 0) {
97
+ // Vanilla Scenario — no placeholders, no injection needed.
98
+ report.skippedScenarios.push({
99
+ name: scenario.name,
100
+ reason: "vanilla Scenario (no <placeholders>)",
101
+ });
102
+ continue;
103
+ }
104
+ const missing = placeholders.filter((p) => !dataColumns.has(p));
105
+ if (missing.length > 0) {
106
+ report.skippedScenarios.push({
107
+ name: scenario.name,
108
+ reason: `data file is missing column(s): ${missing.join(", ")}`,
109
+ });
110
+ continue;
111
+ }
112
+ // Swap. Keep only the columns the scenario actually references —
113
+ // extra columns in the data file are silently dropped per
114
+ // scenario, so a single 50-column data file can feed many narrow
115
+ // scenarios without each one pulling in noise.
116
+ scenario.examples = rows.map((row) => {
117
+ const filtered = {};
118
+ for (const p of placeholders)
119
+ filtered[p] = row[p];
120
+ return filtered;
121
+ });
122
+ report.swappedScenarios += 1;
123
+ }
124
+ return report;
125
+ }
126
+ /**
127
+ * Convert an `InjectionReport` into `ReviewItem`s ready to push onto
128
+ * the scaffold's review queue. One info line per swap, one warning
129
+ * per skip whose reason indicates a real misconfiguration (missing
130
+ * columns) — skips for "vanilla Scenario" or "zero rows" are info-only.
131
+ */
132
+ function reportToReviewItems(report, source) {
133
+ const items = [];
134
+ if (report.swappedScenarios > 0) {
135
+ items.push({
136
+ severity: "info",
137
+ message: `Data injection from ${source}: swapped Examples in ${report.swappedScenarios}/${report.totalScenarios} scenario(s), ${report.rowsPerSwap} rows each.`,
138
+ });
139
+ }
140
+ for (const skip of report.skippedScenarios) {
141
+ const isMisconfig = skip.reason.startsWith("data file is missing");
142
+ items.push({
143
+ severity: isMisconfig ? "warn" : "info",
144
+ message: `Data injection skipped scenario "${skip.name}": ${skip.reason}`,
145
+ });
146
+ }
147
+ return items;
148
+ }
149
+ //# sourceMappingURL=examplesInjector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"examplesInjector.js","sourceRoot":"","sources":["../../src/data/examplesInjector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * v4.0.0 — Synthetic data generator.
3
+ *
4
+ * Reads a schema like:
5
+ *
6
+ * {
7
+ * "email": "faker.internet.email",
8
+ * "password": "faker.internet.password",
9
+ * "url_fragment": "inventory.html", // literal
10
+ * "claim_desc": "llm:auto insurance claim, one sentence"
11
+ * }
12
+ *
13
+ * Produces N `DataRow`s. Each row's fields are filled by routing the
14
+ * source string:
15
+ *
16
+ * - "faker.X.Y[.Z]..." → dynamic dotted-path lookup on faker, called
17
+ * with no args. Standard faker fields cover
18
+ * >95% of test data needs.
19
+ * - "llm:<prompt>" → ONE batched LLM call generates an array of
20
+ * N values for the whole column. Cost-optimal:
21
+ * three LLM-driven fields = three batched
22
+ * calls regardless of row count.
23
+ * - anything else → literal string, same value in every row.
24
+ *
25
+ * Determinism: faker is seeded with `opts.seed` (default 42). Same
26
+ * seed + same schema + same row count = byte-identical output across
27
+ * runs. LLM fields are NOT deterministic in the same way (temperature
28
+ * is fixed at 0 but provider sampling has residual nondeterminism),
29
+ * which is intentional — LLM-generated domain data should look
30
+ * varied each time you regenerate.
31
+ *
32
+ * Why batched LLM calls per column instead of one big call for the
33
+ * whole table: the prompt stays simple ("generate N car insurance
34
+ * claim descriptions"), the response stays a flat JSON array (easy
35
+ * to parse), and a column failure doesn't poison the other columns.
36
+ */
37
+ import type { DataRow } from "./dataLoader";
38
+ import type { LLMClient } from "../llm/types";
39
+ export interface SynthSchema {
40
+ /** field name → source spec. See module doc for syntax. */
41
+ [columnName: string]: string;
42
+ }
43
+ export interface SynthOptions {
44
+ /** How many rows to generate. */
45
+ rows: number;
46
+ /** Faker RNG seed for reproducibility. Default 42. */
47
+ seed?: number;
48
+ /**
49
+ * LLM client to use for "llm:..." fields. Optional — if a schema
50
+ * has no LLM fields you can omit this. If a schema HAS LLM fields
51
+ * and this is undefined, those fields will be filled with the
52
+ * source-string literal as a fallback (visible in output so the
53
+ * user notices the gap).
54
+ */
55
+ llm?: LLMClient;
56
+ /** Optional schema-relative model override for LLM calls. */
57
+ llmModel?: string;
58
+ }
59
+ export declare class SynthGeneratorError extends Error {
60
+ constructor(message: string);
61
+ }
62
+ /**
63
+ * Generate N rows. Returns `DataRow[]` with the same shape as
64
+ * `loadDataFile` so downstream Examples injection works identically.
65
+ */
66
+ export declare function generateSyntheticRows(schema: SynthSchema, opts: SynthOptions): Promise<DataRow[]>;
67
+ /**
68
+ * Convenience: load a schema JSON from disk and validate it has at
69
+ * least one column. Throws SynthGeneratorError on any read/parse error.
70
+ */
71
+ export declare function loadSchema(schemaPath: string): SynthSchema;