@vijaypjavvadi/bdd2pw 3.12.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.
- package/CHANGELOG.md +98 -0
- package/README.md +9 -5
- package/dist/cli.js +29 -0
- package/dist/cli.js.map +1 -1
- package/dist/data/dataLoader.d.ts +78 -0
- package/dist/data/dataLoader.js +339 -0
- package/dist/data/dataLoader.js.map +1 -0
- package/dist/data/examplesInjector.d.ts +64 -0
- package/dist/data/examplesInjector.js +149 -0
- package/dist/data/examplesInjector.js.map +1 -0
- package/dist/data/synthGenerator.d.ts +71 -0
- package/dist/data/synthGenerator.js +265 -0
- package/dist/data/synthGenerator.js.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/anthropicClient.d.ts +11 -0
- package/dist/llm/anthropicClient.js +89 -0
- package/dist/llm/anthropicClient.js.map +1 -1
- package/dist/llm/geminiClient.d.ts +6 -0
- package/dist/llm/geminiClient.js +87 -0
- package/dist/llm/geminiClient.js.map +1 -1
- package/dist/llm/openaiClient.d.ts +6 -0
- package/dist/llm/openaiClient.js +82 -0
- package/dist/llm/openaiClient.js.map +1 -1
- package/dist/llm/types.d.ts +36 -0
- package/dist/types.d.ts +39 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,104 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
_Nothing yet._
|
|
11
11
|
|
|
12
|
+
## [4.0.0] — 2026-06-25
|
|
13
|
+
|
|
14
|
+
### Theme: Data-driven scaffolds — bring your own data or generate it
|
|
15
|
+
|
|
16
|
+
v4.0 is a **major version with no breaking changes**. The number bump
|
|
17
|
+
reflects the size of the feature bundle, not the risk to existing users.
|
|
18
|
+
Scaffolds that don't use the new `--data` / `--gen-data` flags behave
|
|
19
|
+
identically to v3.12.
|
|
20
|
+
|
|
21
|
+
### Added — `--data <path>` external data file injection
|
|
22
|
+
|
|
23
|
+
Pass a CSV, JSON, or XLSX file. Every Scenario Outline in the .feature
|
|
24
|
+
file gets its Examples table swapped with rows from the data file.
|
|
25
|
+
Inline Examples in the .feature become the no-data fallback.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
bdd2pw scaffold login.feature --data data/users.csv --url ... --page LoginPage --repo .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- CSV parser is dependency-free (RFC 4180 quoted fields, embedded
|
|
32
|
+
newlines, escaped quotes, mixed CRLF/LF).
|
|
33
|
+
- JSON accepts either array of objects or `{rows: [...]}` envelope.
|
|
34
|
+
- XLSX lazy-loads via the optional `xlsx` dep; clear `npm install xlsx`
|
|
35
|
+
hint if missing.
|
|
36
|
+
- Per-scenario column filtering — extra data columns are dropped per
|
|
37
|
+
scenario, so a single 50-column data file can feed many narrow
|
|
38
|
+
scenarios without each one pulling in noise.
|
|
39
|
+
- Scenarios whose placeholders reference columns NOT present in the
|
|
40
|
+
data are skipped with a warning in BDD_REVIEW.md (inline Examples
|
|
41
|
+
preserved).
|
|
42
|
+
|
|
43
|
+
### Added — `--gen-data` synthetic data generator
|
|
44
|
+
|
|
45
|
+
For when you need plausible test data but don't have a real dataset.
|
|
46
|
+
Pass a schema JSON listing each column's source:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"email": "faker.internet.email",
|
|
51
|
+
"password": "faker.internet.password",
|
|
52
|
+
"url_fragment": "inventory.html",
|
|
53
|
+
"description": "llm:auto insurance claim, one sentence"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
bdd2pw scaffold login.feature --gen-data --schema users.schema.json --rows 50 --url ... --page LoginPage --repo .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `faker.X.Y` paths resolve via `@faker-js/faker` (optional dep).
|
|
62
|
+
- `llm:<prompt>` routes through the configured LLM provider (Anthropic,
|
|
63
|
+
OpenAI, or Gemini) for context-aware domain-specific data.
|
|
64
|
+
- Literal strings repeat the same value in every row.
|
|
65
|
+
- Seeded by default (seed=42) so output is reproducible across runs.
|
|
66
|
+
Override with `--seed N`.
|
|
67
|
+
- Per-column LLM batching — one provider call per LLM-driven column
|
|
68
|
+
regardless of row count. 3 LLM fields × 50 rows = 3 calls.
|
|
69
|
+
|
|
70
|
+
### Added — `LLMClient.generateText()` API
|
|
71
|
+
|
|
72
|
+
Generic single-prompt text generation method on all three providers
|
|
73
|
+
(Anthropic, OpenAI, Gemini). Routes through the same governance
|
|
74
|
+
sanitisation, budget enforcement, timeout, and telemetry pipeline as
|
|
75
|
+
binding generation. Used by the synth data generator; available for
|
|
76
|
+
any custom downstream tooling that needs a clean LLM call.
|
|
77
|
+
|
|
78
|
+
### CLI
|
|
79
|
+
|
|
80
|
+
- `--data <path>` — load external data file
|
|
81
|
+
- `--gen-data` — enable synthetic generation
|
|
82
|
+
- `--schema <path>` — schema JSON for `--gen-data`
|
|
83
|
+
- `--rows <n>` — row count for `--gen-data` (default 20)
|
|
84
|
+
- `--seed <n>` — Faker seed (default 42)
|
|
85
|
+
|
|
86
|
+
Precedence: `--data` wins if both are passed; warning printed to stderr.
|
|
87
|
+
|
|
88
|
+
### Programmatic API
|
|
89
|
+
|
|
90
|
+
`ScaffoldOptions.dataSource: {type: "file"|"synthetic", path?, schemaPath?, rows?, seed?}`
|
|
91
|
+
- Failures fall back to inline Examples and surface as ReviewItems.
|
|
92
|
+
- Same graceful-degradation pattern as the v2.0 LLM fallback failure mode.
|
|
93
|
+
|
|
94
|
+
### New `optionalDependencies`
|
|
95
|
+
|
|
96
|
+
- `@faker-js/faker ^9.3.0` — synthetic data generation
|
|
97
|
+
- `xlsx ^0.18.5` — XLSX data file loading
|
|
98
|
+
|
|
99
|
+
Same defensive `require()` loading pattern as existing optional deps.
|
|
100
|
+
Users who only use Anthropic + CSV need neither.
|
|
101
|
+
|
|
102
|
+
### Tests
|
|
103
|
+
|
|
104
|
+
`tests/unit/v400Data.test.ts` — 28 cases across DataLoader (CSV/JSON
|
|
105
|
+
parsing, file routing, validation), SynthGenerator (literal/Faker/LLM
|
|
106
|
+
fields, error cases), and ExamplesInjector (extract placeholders,
|
|
107
|
+
swap, skip vanilla, missing-column warning, per-scenario filtering,
|
|
108
|
+
zero-row graceful handling).
|
|
109
|
+
|
|
12
110
|
## [3.12.0] — 2026-06-19
|
|
13
111
|
|
|
14
112
|
> Note: v3.11.0 was tagged in git but never published to npm. v3.12.0
|
package/README.md
CHANGED
|
@@ -42,10 +42,13 @@ Point it at a `.feature` file and a URL. It parses the Gherkin, scans the live p
|
|
|
42
42
|
|
|
43
43
|
> **The pitch.** Run `bdd2pw scaffold` and you get a Playwright TS repo where `npx playwright test` runs against the real site. For specific fixtures, all green. For vague ones, the 60-90% you'd otherwise hand-write is done; you finish the rest — or you let the governed LLM finish them.
|
|
44
44
|
|
|
45
|
-
## What's new in
|
|
45
|
+
## What's new in v4.0.0
|
|
46
46
|
|
|
47
|
-
- **NEW**
|
|
48
|
-
-
|
|
47
|
+
- **NEW** `--data <path>` — load CSV / JSON / XLSX file and inject rows into every Scenario Outline's Examples table. Drop in a 500-row dataset without touching the .feature file.
|
|
48
|
+
- **NEW** `--gen-data --schema <path> --rows N` — generate synthetic Examples rows. Schema picks per-column source: `faker.internet.email` for common fields, `llm:auto insurance claim description` for context-aware domain data. Seeded for reproducibility (`--seed 42`).
|
|
49
|
+
- **NEW** `LLMClient.generateText()` — generic single-prompt API on all three providers (Anthropic, OpenAI, Gemini). Same governance + budget + timeout + telemetry pipeline as binding generation.
|
|
50
|
+
- Gemini provider — three-provider parity complete. `--llm gemini` defaults to `gemini-2.5-flash` ($0.10/M input — cheapest across all three providers). (v3.12.0)
|
|
51
|
+
- OpenAI provider with full parity. `--llm openai` defaults to `gpt-4o-mini`. (v3.11.0)
|
|
49
52
|
- `bdd2pw heal-stats <repo>` CLI reads `<repo>/artefacts/heal-events.jsonl` and writes a heal-stats.json sidecar with heal rate, top failing fields, retry latency, candidate-selector promotions (v3.10.0).
|
|
50
53
|
- `llmStats: true` writes `<repo>/artefacts/llm-stats.json` with per-call latency, token counts, cache hit rate, and an estimated cost in USD (v3.9.0).
|
|
51
54
|
- Seven domain rule packs (banking, healthcare, insurance, retail, gov, education, telecom) — ~140 industry-specific patterns total.
|
|
@@ -354,14 +357,15 @@ output/
|
|
|
354
357
|
| v3.9.0 | Telemetry | `artefacts/llm-stats.json` sidecar — per-call latency + tokens + cache hit rate + estimated cost. Makes v3.5 batching ROI measurable per scaffold. |
|
|
355
358
|
| v3.10.0 | Heal stats | `bdd2pw heal-stats <repo>` CLI consumes `artefacts/heal-events.jsonl` and writes `heal-stats.json` with heal rate, top failing fields, top error patterns, retry latency, candidate-selector promotions. Self-healing ROI per test run. |
|
|
356
359
|
| v3.11.0 | OpenAI provider | `LLMClientOptions.provider: "openai"` lands with cache + governance + batching + telemetry parity. Default model `gpt-4o-mini` (~17x cheaper than `gpt-4o` for structured-JSON tasks). |
|
|
357
|
-
|
|
|
360
|
+
| v3.12.0 | Gemini provider | `LLMClientOptions.provider: "gemini"` lands with full parity. Default model `gemini-2.5-flash` ($0.10/M input — cheapest across all three providers). Three-provider parity complete. |
|
|
361
|
+
| **v4.0.0 (current)** | Data-driven scaffolds | `--data <path>` (CSV/JSON/XLSX) and `--gen-data --schema <path>` (Faker + LLM) inject Examples rows into Scenario Outlines. `LLMClient.generateText()` added to all three providers. **Additive only — no breaking changes.** |
|
|
358
362
|
|
|
359
363
|
### Next 6 months (Jun 2026 → Nov 2026, ~2 releases/month)
|
|
360
364
|
|
|
361
365
|
| Target | Version | Theme | Headline |
|
|
362
366
|
|---|---|---|---|
|
|
363
367
|
| Aug 2026 | **v3.13.0** | Auto-rules | `bdd2pw apply-proposals --interactive` — walks the human through each v3.6 proposal, validates the regex compiles + matches the sample texts, optionally appends to `stepMatcher.ts`. Closes the propose-rules loop. |
|
|
364
|
-
| Aug 2026 | **v4.
|
|
368
|
+
| Aug 2026 | **v4.1.0** | Modern defaults | Bump default models, retire v2.x deprecated APIs, flip opt-in defaults (domains, llm-stats, healing) to opt-out. Migration guide ships alongside. |
|
|
365
369
|
| Sep 2026 | **v4.1.0** | sel2pw merge | sel2pw migrated onto `@vijaypjavvadi/pw-emit` — three packages share one emitter. All future v3.x emitter improvements automatically flow to Selenium users. |
|
|
366
370
|
| Sep 2026 | **v4.2.0** | Domain packs | Three more opt-in packs: **fintech** (KYC, AML, trading desks), **real-estate** (MLS, listings, escrow), **hospitality** (PMS, reservations, OTA). ~60 more rules. |
|
|
367
371
|
| Oct 2026 | **v4.3.0** | API v2 | WebSocket assertion rules, multipart file-upload patterns, GraphQL query / mutation / subscription rules. Extends v3.0's API testing surface. |
|
package/dist/cli.js
CHANGED
|
@@ -77,6 +77,12 @@ program
|
|
|
77
77
|
.option("--force", "Overwrite existing spec files", false)
|
|
78
78
|
.option("--self-healing", "Wrap emitted locators in healOrThrow() and generate lib/heal.ts + tsconfig path alias. Locator events are logged to artefacts/heal-events.jsonl for the offline self-heal pipeline. Action-time healing is v1.2.", false)
|
|
79
79
|
.option("--llm-stats", "v3.9.0 — write <repo>/artefacts/llm-stats.json with per-call latency, token counts, cache hit rate, and estimated cost.", false)
|
|
80
|
+
// v4.0.0 — data-driven Examples injection.
|
|
81
|
+
.option("--data <path>", "v4.0.0 — load CSV / JSON / XLSX file and inject rows into every Scenario Outline's Examples table. Inline Examples are overridden when this is set. Failures fall back to inline Examples + warn in BDD_REVIEW.md.")
|
|
82
|
+
.option("--gen-data", "v4.0.0 — generate synthetic Examples rows via Faker + LLM. Requires --schema. Combine with --rows to set row count.", false)
|
|
83
|
+
.option("--schema <path>", "v4.0.0 — JSON schema for --gen-data. Object of {column: source} where source is 'faker.X.Y', 'llm:<prompt>', or a literal string.")
|
|
84
|
+
.option("--rows <n>", "v4.0.0 — row count for --gen-data. Default 20.", "20")
|
|
85
|
+
.option("--seed <n>", "v4.0.0 — Faker RNG seed for --gen-data reproducibility. Default 42.", "42")
|
|
80
86
|
.action(async (feature, opts) => {
|
|
81
87
|
try {
|
|
82
88
|
// v2.0 — wire the actual LLM config when --llm is passed. The legacy
|
|
@@ -107,6 +113,14 @@ program
|
|
|
107
113
|
: undefined,
|
|
108
114
|
})
|
|
109
115
|
: undefined;
|
|
116
|
+
// v4.0.0 — warn if user passed both data sources. --data wins.
|
|
117
|
+
if (opts.data && opts.genData) {
|
|
118
|
+
console.warn("[bdd2pw] both --data and --gen-data passed; --data wins. --gen-data ignored.");
|
|
119
|
+
}
|
|
120
|
+
// v4.0.0 — warn if --gen-data without --schema.
|
|
121
|
+
if (opts.genData && !opts.schema) {
|
|
122
|
+
console.warn("[bdd2pw] --gen-data requires --schema <path>. Ignoring --gen-data.");
|
|
123
|
+
}
|
|
110
124
|
const result = await (0, index_1.scaffold)({
|
|
111
125
|
feature: path.resolve(feature),
|
|
112
126
|
url: opts.url,
|
|
@@ -129,6 +143,21 @@ program
|
|
|
129
143
|
selfHealing: opts.selfHealing,
|
|
130
144
|
// v3.9.0 — telemetry sidecar
|
|
131
145
|
llmStats: opts.llmStats === true,
|
|
146
|
+
// v4.0.0 — data-driven Examples injection. --data wins over
|
|
147
|
+
// --gen-data when both are passed (and we warn in stderr so the
|
|
148
|
+
// user sees what happened).
|
|
149
|
+
dataSource: opts.data
|
|
150
|
+
? { type: "file", path: path.resolve(opts.data) }
|
|
151
|
+
: opts.genData
|
|
152
|
+
? {
|
|
153
|
+
type: "synthetic",
|
|
154
|
+
schemaPath: opts.schema
|
|
155
|
+
? path.resolve(opts.schema)
|
|
156
|
+
: undefined,
|
|
157
|
+
rows: opts.rows ? Number(opts.rows) : undefined,
|
|
158
|
+
seed: opts.seed ? Number(opts.seed) : undefined,
|
|
159
|
+
}
|
|
160
|
+
: undefined,
|
|
132
161
|
});
|
|
133
162
|
logger_1.logger.info({ result }, "scaffold complete");
|
|
134
163
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v4.0.0 — DataLoader.
|
|
3
|
+
*
|
|
4
|
+
* Reads CSV / JSON / XLSX into a normalized `DataRow[]` for injection
|
|
5
|
+
* into Scenario Outline Examples tables.
|
|
6
|
+
*
|
|
7
|
+
* Normalization contract:
|
|
8
|
+
* - All values are coerced to strings (Gherkin Examples tables are
|
|
9
|
+
* always string-typed; the test code does the parsing).
|
|
10
|
+
* - Headers are trimmed; empty headers are rejected up-front so the
|
|
11
|
+
* placeholder-to-column matching downstream is deterministic.
|
|
12
|
+
* - Empty rows (all-blank cells) are dropped silently — they're
|
|
13
|
+
* a frequent artifact of hand-edited CSVs.
|
|
14
|
+
*
|
|
15
|
+
* Format detection is by file extension. .xlsx loading is lazy via
|
|
16
|
+
* the `xlsx` optional dep — if missing, the user gets a clear
|
|
17
|
+
* `npm install xlsx` error instead of a module-resolution crash.
|
|
18
|
+
*
|
|
19
|
+
* Error model: every loader function throws `DataLoaderError` with
|
|
20
|
+
* a human-readable message. The orchestrator in `scaffold()` catches
|
|
21
|
+
* this and surfaces it as a `ReviewItem` so the failure shows up in
|
|
22
|
+
* BDD_REVIEW.md alongside other diagnostics — not just on stderr.
|
|
23
|
+
*/
|
|
24
|
+
/** One row of data. All values are strings (Gherkin convention). */
|
|
25
|
+
export type DataRow = Record<string, string>;
|
|
26
|
+
export declare class DataLoaderError extends Error {
|
|
27
|
+
readonly file?: string | undefined;
|
|
28
|
+
constructor(message: string, file?: string | undefined);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Entry point. Routes by extension to the right parser.
|
|
32
|
+
*
|
|
33
|
+
* @param filePath Absolute or relative path to a .csv / .json / .xlsx file.
|
|
34
|
+
* @returns Array of rows, each a string-keyed dict.
|
|
35
|
+
* @throws DataLoaderError on any read / parse / shape error.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadDataFile(filePath: string): DataRow[];
|
|
38
|
+
/**
|
|
39
|
+
* CSV parser — RFC 4180 compliant for the practical subset:
|
|
40
|
+
* - Quoted fields with embedded commas
|
|
41
|
+
* - Quoted fields with embedded newlines
|
|
42
|
+
* - Escaped quotes (`""` inside a quoted field)
|
|
43
|
+
* - Mixed CRLF / LF line endings
|
|
44
|
+
*
|
|
45
|
+
* Does NOT delegate to papaparse — keeping this dependency-free since
|
|
46
|
+
* the format is small enough to handle inline and we want bdd2pw to
|
|
47
|
+
* scaffold without an `npm install` step on the user's machine.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseCsv(text: string, fileLabel?: string): DataRow[];
|
|
50
|
+
/**
|
|
51
|
+
* JSON parser — accepts either:
|
|
52
|
+
* 1. Array of objects: `[{a:1,b:2}, {a:3,b:4}]` — most natural shape.
|
|
53
|
+
* 2. Object with `rows`: `{rows: [{...}, {...}]}` — useful for files
|
|
54
|
+
* that also carry metadata (generator seed, source URL, etc).
|
|
55
|
+
*
|
|
56
|
+
* Numbers and booleans are stringified for Gherkin Examples compatibility.
|
|
57
|
+
* Null values become empty strings.
|
|
58
|
+
*/
|
|
59
|
+
export declare function parseJson(text: string, fileLabel?: string): DataRow[];
|
|
60
|
+
/**
|
|
61
|
+
* XLSX parser — uses the optional `xlsx` dep. Reads the FIRST sheet
|
|
62
|
+
* only; users with multi-sheet workbooks should export the sheet they
|
|
63
|
+
* want to CSV.
|
|
64
|
+
*
|
|
65
|
+
* Lazy-loaded so users who never use XLSX don't need the dep installed.
|
|
66
|
+
*/
|
|
67
|
+
export declare function parseXlsx(absPath: string, fileLabel?: string): DataRow[];
|
|
68
|
+
/**
|
|
69
|
+
* Validates that the loaded data rows have every column the Scenario
|
|
70
|
+
* Outline placeholders reference. Returns the list of missing column
|
|
71
|
+
* names; empty array means OK.
|
|
72
|
+
*
|
|
73
|
+
* Called by the scaffold orchestrator after parsing the .feature file.
|
|
74
|
+
* If anything is missing, the orchestrator surfaces it as a
|
|
75
|
+
* `ReviewItem` and skips Examples injection for that scenario rather
|
|
76
|
+
* than producing a broken spec.
|
|
77
|
+
*/
|
|
78
|
+
export declare function validateColumnsForPlaceholders(rows: DataRow[], placeholders: string[]): string[];
|
|
@@ -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
|