@anselmdk/feature-spec-md 0.1.1 → 0.2.1
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/README.md +94 -50
- package/SPEC_FORMAT.md +200 -38
- package/dist/cli.js +178 -28
- package/dist/cli.js.map +1 -1
- package/dist/featureSpecs.d.ts +34 -0
- package/dist/featureSpecs.d.ts.map +1 -0
- package/dist/featureSpecs.js +240 -0
- package/dist/featureSpecs.js.map +1 -0
- package/dist/filePatterns.d.ts.map +1 -1
- package/dist/filePatterns.js +4 -0
- package/dist/filePatterns.js.map +1 -1
- package/dist/html.d.ts +3 -0
- package/dist/html.d.ts.map +1 -1
- package/dist/html.js +3 -0
- package/dist/html.js.map +1 -1
- package/dist/index.d.ts +3 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -338
- package/dist/index.js.map +1 -1
- package/dist/playwright.d.ts.map +1 -1
- package/dist/playwright.js +13 -21
- package/dist/playwright.js.map +1 -1
- package/dist/reportTemplate.d.ts +4 -1
- package/dist/reportTemplate.d.ts.map +1 -1
- package/dist/reportTemplate.js +251 -21
- package/dist/reportTemplate.js.map +1 -1
- package/dist/screenshots.d.ts.map +1 -1
- package/dist/screenshots.js +4 -0
- package/dist/screenshots.js.map +1 -1
- package/dist/specDocuments.d.ts +86 -0
- package/dist/specDocuments.d.ts.map +1 -0
- package/dist/specDocuments.js +351 -0
- package/dist/specDocuments.js.map +1 -0
- package/dist/specMarkdown.d.ts +26 -0
- package/dist/specMarkdown.d.ts.map +1 -0
- package/dist/specMarkdown.js +126 -0
- package/dist/specMarkdown.js.map +1 -0
- package/dist/testImplementationReport.d.ts +53 -0
- package/dist/testImplementationReport.d.ts.map +1 -0
- package/dist/testImplementationReport.js +160 -0
- package/dist/testImplementationReport.js.map +1 -0
- package/dist/types.d.ts +76 -11
- package/dist/types.d.ts.map +1 -1
- package/docs/releasing.md +107 -0
- package/docs/spec-driven-flow.md +175 -0
- package/package.json +12 -2
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Command-line interface for initializing, validating, reporting on, and
|
|
4
|
+
* measuring test coverage for Feature Spec Markdown documents.
|
|
5
|
+
*/
|
|
6
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
7
|
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
|
|
8
|
+
import { collectSpecScreenshots, renderHtmlReport, writeTextFile, } from "./index.js";
|
|
9
|
+
import { checkSpecDocuments } from "./specDocuments.js";
|
|
10
|
+
import { buildSpecImplementationReport, formatSpecImplementationReport, } from "./testImplementationReport.js";
|
|
11
|
+
const defaultSpecPattern = "specs/**/*.model.md,specs/**/*.feature.md,specs/**/*.stack.md,specs/**/*.design.md";
|
|
6
12
|
const defaultTestPattern = "tests/**/*.spec.ts";
|
|
7
13
|
main().catch((error) => {
|
|
8
14
|
console.error(error instanceof Error ? error.message : error);
|
|
@@ -15,13 +21,15 @@ async function main() {
|
|
|
15
21
|
return runCheck(options);
|
|
16
22
|
if (command === "report")
|
|
17
23
|
return runReport(options);
|
|
24
|
+
if (command === "coverage")
|
|
25
|
+
return runCoverage(options);
|
|
18
26
|
if (command === "init")
|
|
19
27
|
return runInit(options);
|
|
20
28
|
printHelp();
|
|
21
29
|
process.exit(command === "help" || command === "--help" || command === "-h" ? 0 : 1);
|
|
22
30
|
}
|
|
23
31
|
async function runCheck(options) {
|
|
24
|
-
const result = await
|
|
32
|
+
const result = await checkSpecDocuments({
|
|
25
33
|
specs: optionList(options.specs, defaultSpecPattern),
|
|
26
34
|
tests: options.tests === "" ? [] : optionList(options.tests, defaultTestPattern),
|
|
27
35
|
requireRuleCoverage: options["require-rule-coverage"] === "true",
|
|
@@ -30,12 +38,34 @@ async function runCheck(options) {
|
|
|
30
38
|
printIssues([...result.validationIssues, ...result.coverageIssues]);
|
|
31
39
|
if (!result.ok)
|
|
32
40
|
process.exit(1);
|
|
33
|
-
const
|
|
34
|
-
const ruleCount = result.
|
|
35
|
-
|
|
41
|
+
const modelItemCount = result.models.reduce((sum, spec) => sum + spec.modelItems.length, 0);
|
|
42
|
+
const ruleCount = result.documents.reduce((sum, spec) => sum + spec.rules.length, 0);
|
|
43
|
+
const scenarioCount = result.features.reduce((sum, spec) => sum + spec.scenarios.length, 0);
|
|
44
|
+
console.log(`Spec check passed: ${result.models.length} model(s), ${result.features.length} feature(s), ${result.stacks.length} stack(s), ${result.designs.length} design(s), ${modelItemCount} model item(s), ${ruleCount} rule(s), ${scenarioCount} scenario(s).`);
|
|
45
|
+
}
|
|
46
|
+
async function runCoverage(options) {
|
|
47
|
+
const result = await checkSpecDocuments({
|
|
48
|
+
specs: optionList(options.specs, defaultSpecPattern),
|
|
49
|
+
tests: options.tests === "" ? [] : optionList(options.tests, defaultTestPattern),
|
|
50
|
+
requireModelCoverage: false,
|
|
51
|
+
requireRuleCoverage: false,
|
|
52
|
+
requireScenarioCoverage: false,
|
|
53
|
+
});
|
|
54
|
+
printIssues(result.coverageIssues);
|
|
55
|
+
if (!result.coverage) {
|
|
56
|
+
console.log("Spec test implementation report");
|
|
57
|
+
console.log("");
|
|
58
|
+
console.log("No tests were scanned. Pass --tests or omit --tests to use the default tests/**/*.spec.ts pattern.");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const report = buildSpecImplementationReport(result.features, result.coverage, result.models);
|
|
62
|
+
console.log(formatSpecImplementationReport(report));
|
|
63
|
+
if (options["fail-on-missing"] === "true" && report.missingScenarios > 0) {
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
36
66
|
}
|
|
37
67
|
async function runReport(options) {
|
|
38
|
-
const result = await
|
|
68
|
+
const result = await checkSpecDocuments({
|
|
39
69
|
specs: optionList(options.specs, defaultSpecPattern),
|
|
40
70
|
tests: options.tests === "" ? [] : optionList(options.tests, defaultTestPattern),
|
|
41
71
|
requireRuleCoverage: options["require-rule-coverage"] === "true",
|
|
@@ -45,7 +75,11 @@ async function runReport(options) {
|
|
|
45
75
|
const screenshots = options.screenshots
|
|
46
76
|
? await collectSpecScreenshots(optionList(options.screenshots, ""))
|
|
47
77
|
: [];
|
|
48
|
-
await writeTextFile(out, renderHtmlReport(result.
|
|
78
|
+
await writeTextFile(out, renderHtmlReport(result.features, {
|
|
79
|
+
title: await reportTitle(),
|
|
80
|
+
models: result.models,
|
|
81
|
+
stacks: result.stacks,
|
|
82
|
+
designs: result.designs,
|
|
49
83
|
coverage: result.coverage,
|
|
50
84
|
screenshots,
|
|
51
85
|
validationIssues: [...result.validationIssues, ...result.coverageIssues],
|
|
@@ -54,9 +88,10 @@ async function runReport(options) {
|
|
|
54
88
|
}
|
|
55
89
|
async function runInit(options) {
|
|
56
90
|
const dir = options.dir ?? "specs";
|
|
57
|
-
const
|
|
91
|
+
const kind = options.kind ?? "feature";
|
|
92
|
+
const target = path.join(dir, fileNameForKind(kind));
|
|
58
93
|
await mkdir(dir, { recursive: true });
|
|
59
|
-
await writeFile(target,
|
|
94
|
+
await writeFile(target, exampleForKind(kind), "utf8");
|
|
60
95
|
console.log(`Created ${target}`);
|
|
61
96
|
}
|
|
62
97
|
function parseArgs(args) {
|
|
@@ -94,25 +129,82 @@ function printIssues(issues) {
|
|
|
94
129
|
console.error(`${issue.severity.toUpperCase()} ${issue.code}${location ? ` ${location}` : ""}: ${issue.message}`);
|
|
95
130
|
}
|
|
96
131
|
}
|
|
132
|
+
async function reportTitle() {
|
|
133
|
+
const packageName = await readPackageName();
|
|
134
|
+
return packageName
|
|
135
|
+
? `Feature Spec Report for ${packageName}`
|
|
136
|
+
: "Feature Spec Report";
|
|
137
|
+
}
|
|
138
|
+
async function readPackageName() {
|
|
139
|
+
try {
|
|
140
|
+
const raw = await readFile("package.json", "utf8");
|
|
141
|
+
const parsed = JSON.parse(raw);
|
|
142
|
+
return typeof parsed.name === "string" && parsed.name.trim()
|
|
143
|
+
? parsed.name.trim()
|
|
144
|
+
: undefined;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
97
150
|
function printHelp() {
|
|
98
151
|
console.log(`feature-spec-md
|
|
99
152
|
|
|
100
153
|
Usage:
|
|
101
|
-
feature-spec-md init [--dir specs]
|
|
102
|
-
feature-spec-md check [--specs "specs/**/*.feature.md"] [--tests "tests/**/*.spec.ts"]
|
|
103
|
-
feature-spec-md
|
|
154
|
+
feature-spec-md init [--kind feature|model|stack|design] [--dir specs]
|
|
155
|
+
feature-spec-md check [--specs "specs/**/*.model.md,specs/**/*.feature.md,specs/**/*.stack.md,specs/**/*.design.md"] [--tests "tests/**/*.spec.ts"]
|
|
156
|
+
feature-spec-md coverage [--specs "specs/**/*.model.md,specs/**/*.feature.md,specs/**/*.stack.md,specs/**/*.design.md"] [--tests "tests/**/*.spec.ts"] [--fail-on-missing]
|
|
157
|
+
feature-spec-md report [--specs "specs/**/*.model.md,specs/**/*.feature.md,specs/**/*.stack.md,specs/**/*.design.md"] [--tests "tests/**/*.spec.ts"] [--screenshots "test-results/spec-report/screenshots.json"] [--out test-results/feature-spec-report/index.html]
|
|
104
158
|
|
|
105
159
|
Options:
|
|
106
160
|
--require-rule-coverage Fail when rules have no matching test references.
|
|
107
161
|
--require-scenario-coverage Defaults to true for check. Use --require-scenario-coverage=false to disable.
|
|
162
|
+
--fail-on-missing Exit with status 1 when coverage finds missing scenario tests.
|
|
108
163
|
--screenshots Screenshot manifest JSON glob for report evidence.
|
|
109
164
|
--tests "" Disable test coverage lookup.
|
|
110
165
|
`);
|
|
111
166
|
}
|
|
112
|
-
|
|
167
|
+
function fileNameForKind(kind) {
|
|
168
|
+
if (kind === "model")
|
|
169
|
+
return "example.model.md";
|
|
170
|
+
if (kind === "stack")
|
|
171
|
+
return "example.stack.md";
|
|
172
|
+
if (kind === "design")
|
|
173
|
+
return "example.design.md";
|
|
174
|
+
return "account-access.feature.md";
|
|
175
|
+
}
|
|
176
|
+
function exampleForKind(kind) {
|
|
177
|
+
if (kind === "model")
|
|
178
|
+
return exampleModel;
|
|
179
|
+
if (kind === "stack")
|
|
180
|
+
return exampleStack;
|
|
181
|
+
if (kind === "design")
|
|
182
|
+
return exampleDesign;
|
|
183
|
+
return exampleFeature;
|
|
184
|
+
}
|
|
185
|
+
const exampleModel = `---
|
|
113
186
|
id: ACCOUNT
|
|
187
|
+
title: Account model
|
|
188
|
+
status: draft
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
# Account model
|
|
192
|
+
|
|
193
|
+
## Purpose
|
|
194
|
+
|
|
195
|
+
Define the shared account concepts used by account access features.
|
|
196
|
+
|
|
197
|
+
## Model
|
|
198
|
+
|
|
199
|
+
### ACCOUNT-M001: Account
|
|
200
|
+
|
|
201
|
+
An account represents one registered person.
|
|
202
|
+
`;
|
|
203
|
+
const exampleFeature = `---
|
|
204
|
+
id: ACCOUNT-ACCESS
|
|
114
205
|
title: Account access
|
|
115
206
|
status: draft
|
|
207
|
+
model: ACCOUNT
|
|
116
208
|
---
|
|
117
209
|
|
|
118
210
|
# Account access
|
|
@@ -123,24 +215,82 @@ People can access their own account after proving their identity.
|
|
|
123
215
|
|
|
124
216
|
## Rules
|
|
125
217
|
|
|
126
|
-
- ACCOUNT-R001: A person MUST prove control of a registered email address before accessing an account.
|
|
127
|
-
- ACCOUNT-R002: The system MUST NOT reveal whether an unknown email address belongs to an account.
|
|
128
|
-
- ACCOUNT-R003: A signed-in person SHOULD be returned to the page they originally requested.
|
|
218
|
+
- ACCOUNT-ACCESS-R001: A person MUST prove control of a registered email address before accessing an account.
|
|
219
|
+
- ACCOUNT-ACCESS-R002: The system MUST NOT reveal whether an unknown email address belongs to an account.
|
|
129
220
|
|
|
130
221
|
## Scenarios
|
|
131
222
|
|
|
132
|
-
### ACCOUNT-S001: Registered person signs in
|
|
223
|
+
### ACCOUNT-ACCESS-S001: Registered person signs in
|
|
224
|
+
|
|
225
|
+
Given a registered person is on the sign-in page
|
|
226
|
+
When they request and open a valid sign-in link
|
|
227
|
+
Then they are signed in
|
|
228
|
+
`;
|
|
229
|
+
const exampleStack = `---
|
|
230
|
+
id: ACCOUNT-STACK
|
|
231
|
+
title: Account stack
|
|
232
|
+
status: draft
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
# Account stack
|
|
236
|
+
|
|
237
|
+
## Purpose
|
|
238
|
+
|
|
239
|
+
Define the initial technical stack for the account access demo.
|
|
240
|
+
|
|
241
|
+
## Context
|
|
242
|
+
|
|
243
|
+
The demo is a small browser-based application with automated tests.
|
|
244
|
+
|
|
245
|
+
## Stack
|
|
246
|
+
|
|
247
|
+
| Area | Choice |
|
|
248
|
+
|---|---|
|
|
249
|
+
| Frontend | React |
|
|
250
|
+
| Language | TypeScript |
|
|
251
|
+
| Testing | Playwright |
|
|
252
|
+
|
|
253
|
+
## Rationale
|
|
254
|
+
|
|
255
|
+
React, TypeScript, and Playwright fit the interaction-heavy UI and scenario-based testing style.
|
|
256
|
+
|
|
257
|
+
## Consequences
|
|
258
|
+
|
|
259
|
+
The stack is optimized for a browser UI and static deployment.
|
|
260
|
+
`;
|
|
261
|
+
const exampleDesign = `---
|
|
262
|
+
id: ACCOUNT-DESIGN
|
|
263
|
+
title: Account design
|
|
264
|
+
status: draft
|
|
265
|
+
model: ACCOUNT
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
# Account design
|
|
269
|
+
|
|
270
|
+
## Purpose
|
|
271
|
+
|
|
272
|
+
Define the initial interaction and visual design direction for account access.
|
|
273
|
+
|
|
274
|
+
## Design
|
|
275
|
+
|
|
276
|
+
The account access flow should feel simple, calm, and direct.
|
|
277
|
+
|
|
278
|
+
## Principles
|
|
279
|
+
|
|
280
|
+
- Keep the sign-in flow short.
|
|
281
|
+
- Avoid revealing whether an email address exists.
|
|
282
|
+
- Make recovery states easy to understand.
|
|
283
|
+
|
|
284
|
+
## Layout
|
|
285
|
+
|
|
286
|
+
The sign-in page uses one primary form and one primary action.
|
|
287
|
+
|
|
288
|
+
## Interaction
|
|
133
289
|
|
|
134
|
-
|
|
135
|
-
When they request and open a valid sign-in link
|
|
136
|
-
Then they are signed in
|
|
137
|
-
And they are returned to the page they originally requested
|
|
290
|
+
Validation messages appear near the field they describe.
|
|
138
291
|
|
|
139
|
-
|
|
292
|
+
## Visual style
|
|
140
293
|
|
|
141
|
-
|
|
142
|
-
When they request a sign-in link
|
|
143
|
-
Then the response says to check their email
|
|
144
|
-
And the response does not reveal whether the email address is registered
|
|
294
|
+
Use a clean, low-distraction visual style.
|
|
145
295
|
`;
|
|
146
296
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CoverageSummary, FeatureSpec, TestReference, ValidationIssue } from "./types.js";
|
|
2
|
+
/** Parse one feature spec Markdown document into structured metadata, rules, and scenarios. */
|
|
3
|
+
export declare function parseFeatureSpec(source: string, options?: {
|
|
4
|
+
filePath?: string;
|
|
5
|
+
}): FeatureSpec;
|
|
6
|
+
/** Validate spec structure, ID prefixes, duplicate IDs, and required sections. */
|
|
7
|
+
export declare function validateFeatureSpec(spec: FeatureSpec): ValidationIssue[];
|
|
8
|
+
/** Find rule and scenario IDs referenced by one test source file. */
|
|
9
|
+
export declare function parseTestReferences(source: string, filePath?: string): TestReference[];
|
|
10
|
+
/** Expand test file globs and collect all rule and scenario references from them. */
|
|
11
|
+
export declare function collectTestReferences(patterns: string[]): Promise<TestReference[]>;
|
|
12
|
+
/** Build rule and scenario coverage from parsed specs and collected test references. */
|
|
13
|
+
export declare function buildCoverageSummary(specs: FeatureSpec[], references: TestReference[]): CoverageSummary;
|
|
14
|
+
/** Validate coverage expectations and orphaned references. */
|
|
15
|
+
export declare function validateCoverage(coverage: CoverageSummary, options?: {
|
|
16
|
+
requireRuleCoverage?: boolean;
|
|
17
|
+
requireScenarioCoverage?: boolean;
|
|
18
|
+
}): ValidationIssue[];
|
|
19
|
+
/** Load and parse all feature specs matching the provided globs. */
|
|
20
|
+
export declare function loadFeatureSpecs(patterns: string[]): Promise<FeatureSpec[]>;
|
|
21
|
+
/** Run spec validation and optional test coverage validation in one call. */
|
|
22
|
+
export declare function checkFeatureSpecs(options: {
|
|
23
|
+
specs: string[];
|
|
24
|
+
tests?: string[];
|
|
25
|
+
requireRuleCoverage?: boolean;
|
|
26
|
+
requireScenarioCoverage?: boolean;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
specs: FeatureSpec[];
|
|
29
|
+
validationIssues: ValidationIssue[];
|
|
30
|
+
coverage: CoverageSummary | undefined;
|
|
31
|
+
coverageIssues: ValidationIssue[];
|
|
32
|
+
ok: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
//# sourceMappingURL=featureSpecs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"featureSpecs.d.ts","sourceRoot":"","sources":["../src/featureSpecs.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAEV,eAAe,EAGf,WAAW,EAEX,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,+FAA+F;AAC/F,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,WAAW,CAab;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,EAAE,CA2GxE;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,SAAa,GACpB,aAAa,EAAE,CAyBjB;AAED,qFAAqF;AACrF,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,4BAM7D;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,WAAW,EAAE,EACpB,UAAU,EAAE,aAAa,EAAE,GAC1B,eAAe,CAkCjB;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,eAAe,EACzB,OAAO,GAAE;IACP,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAC9B,qBAmDP;AAED,oEAAoE;AACpE,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,0BAQxD;AAED,6EAA6E;AAC7E,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;;;;;;GAsBA"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature-spec parsing, validation, test-reference extraction, and coverage
|
|
3
|
+
* checks for `*.feature.md` documents.
|
|
4
|
+
*/
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { expandFilePatterns } from "./filePatterns.js";
|
|
7
|
+
import { dedupeTestReferences, lineForOffset, parseMarkdownDocument, parseRuleItems, ruleIdPattern, scenarioIdPattern, } from "./specMarkdown.js";
|
|
8
|
+
/** Parse one feature spec Markdown document into structured metadata, rules, and scenarios. */
|
|
9
|
+
export function parseFeatureSpec(source, options = {}) {
|
|
10
|
+
const filePath = options.filePath ?? "<inline>";
|
|
11
|
+
const parsed = parseMarkdownDocument(source, "Feature spec");
|
|
12
|
+
return {
|
|
13
|
+
filePath,
|
|
14
|
+
frontmatter: parsed.frontmatter,
|
|
15
|
+
title: parsed.title,
|
|
16
|
+
purpose: parsed.purpose,
|
|
17
|
+
rules: parseRuleItems(parsed.lines, parsed.bodyStartLine),
|
|
18
|
+
scenarios: parseScenarios(parsed.lines, parsed.bodyStartLine),
|
|
19
|
+
source,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Validate spec structure, ID prefixes, duplicate IDs, and required sections. */
|
|
23
|
+
export function validateFeatureSpec(spec) {
|
|
24
|
+
const issues = [];
|
|
25
|
+
const seen = new Map();
|
|
26
|
+
if (!/^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*$/.test(spec.frontmatter.id)) {
|
|
27
|
+
issues.push(issue(spec, "invalid-feature-id", "error", `Feature id "${spec.frontmatter.id}" must use uppercase words separated by hyphens.`));
|
|
28
|
+
}
|
|
29
|
+
if (spec.title !== spec.frontmatter.title) {
|
|
30
|
+
issues.push(issue(spec, "title-mismatch", "warning", "The H1 title should match frontmatter.title."));
|
|
31
|
+
}
|
|
32
|
+
if (!spec.purpose) {
|
|
33
|
+
issues.push(issue(spec, "missing-purpose", "error", "Feature spec must include a non-empty ## Purpose section."));
|
|
34
|
+
}
|
|
35
|
+
if (spec.scenarios.length === 0) {
|
|
36
|
+
issues.push(issue(spec, "missing-scenarios", "error", "Feature spec must include at least one scenario."));
|
|
37
|
+
}
|
|
38
|
+
for (const rule of spec.rules) {
|
|
39
|
+
if (!rule.id.startsWith(`${spec.frontmatter.id}-`)) {
|
|
40
|
+
issues.push(issue(spec, "wrong-rule-prefix", "error", `Rule id "${rule.id}" must start with ${spec.frontmatter.id}-.`, rule.line));
|
|
41
|
+
}
|
|
42
|
+
if (!rule.keyword) {
|
|
43
|
+
issues.push(issue(spec, "missing-rule-keyword", "warning", `Rule "${rule.id}" should use MUST, MUST NOT, SHOULD, SHOULD NOT, MAY, or OPTIONAL.`, rule.line));
|
|
44
|
+
}
|
|
45
|
+
registerId(spec, issues, seen, rule.id, rule.line);
|
|
46
|
+
}
|
|
47
|
+
for (const scenario of spec.scenarios) {
|
|
48
|
+
if (!scenario.id.startsWith(`${spec.frontmatter.id}-`)) {
|
|
49
|
+
issues.push(issue(spec, "wrong-scenario-prefix", "error", `Scenario id "${scenario.id}" must start with ${spec.frontmatter.id}-.`, scenario.line));
|
|
50
|
+
}
|
|
51
|
+
for (const keyword of ["Given", "When", "Then"]) {
|
|
52
|
+
if (!scenario.steps.some((step) => step.keyword === keyword)) {
|
|
53
|
+
issues.push(issue(spec, `missing-${keyword.toLowerCase()}`, "warning", `Scenario "${scenario.id}" should include a ${keyword} step.`, scenario.line));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
registerId(spec, issues, seen, scenario.id, scenario.line);
|
|
57
|
+
}
|
|
58
|
+
return issues;
|
|
59
|
+
}
|
|
60
|
+
/** Find rule and scenario IDs referenced by one test source file. */
|
|
61
|
+
export function parseTestReferences(source, filePath = "<inline>") {
|
|
62
|
+
const lines = source.split(/\r?\n/);
|
|
63
|
+
const refs = [];
|
|
64
|
+
for (const match of source.matchAll(scenarioIdPattern)) {
|
|
65
|
+
refs.push({
|
|
66
|
+
id: match[0],
|
|
67
|
+
filePath,
|
|
68
|
+
line: lineForOffset(lines, match.index ?? 0),
|
|
69
|
+
kind: "scenario",
|
|
70
|
+
source: sourceForMatch(source, match.index ?? 0),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
for (const match of source.matchAll(ruleIdPattern)) {
|
|
74
|
+
refs.push({
|
|
75
|
+
id: match[0],
|
|
76
|
+
filePath,
|
|
77
|
+
line: lineForOffset(lines, match.index ?? 0),
|
|
78
|
+
kind: "rule",
|
|
79
|
+
source: sourceForMatch(source, match.index ?? 0),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return dedupeTestReferences(refs);
|
|
83
|
+
}
|
|
84
|
+
/** Expand test file globs and collect all rule and scenario references from them. */
|
|
85
|
+
export async function collectTestReferences(patterns) {
|
|
86
|
+
const refs = [];
|
|
87
|
+
for (const file of await expandFilePatterns(patterns)) {
|
|
88
|
+
refs.push(...parseTestReferences(await readFile(file, "utf8"), file));
|
|
89
|
+
}
|
|
90
|
+
return refs;
|
|
91
|
+
}
|
|
92
|
+
/** Build rule and scenario coverage from parsed specs and collected test references. */
|
|
93
|
+
export function buildCoverageSummary(specs, references) {
|
|
94
|
+
const scenarios = new Map(specs.flatMap((spec) => spec.scenarios.map((scenario) => [scenario.id, { spec, scenario }])));
|
|
95
|
+
const rules = new Map(specs.flatMap((spec) => spec.rules.map((rule) => [rule.id, { spec, rule }])));
|
|
96
|
+
const scenarioRefs = references.filter((ref) => ref.kind === "scenario");
|
|
97
|
+
const ruleRefs = references.filter((ref) => ref.kind === "rule");
|
|
98
|
+
return {
|
|
99
|
+
scenarioCoverage: Array.from(scenarios.entries()).map(([id, value]) => item(id, value.scenario.title, value.spec.filePath, value.scenario.line, scenarioRefs)),
|
|
100
|
+
ruleCoverage: Array.from(rules.entries()).map(([id, value]) => item(id, value.rule.text, value.spec.filePath, value.rule.line, ruleRefs)),
|
|
101
|
+
orphanScenarioReferences: scenarioRefs.filter((ref) => !scenarios.has(ref.id)),
|
|
102
|
+
orphanRuleReferences: ruleRefs.filter((ref) => !rules.has(ref.id)),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Validate coverage expectations and orphaned references. */
|
|
106
|
+
export function validateCoverage(coverage, options = {}) {
|
|
107
|
+
const issues = [];
|
|
108
|
+
if (options.requireScenarioCoverage ?? true) {
|
|
109
|
+
for (const item of coverage.scenarioCoverage.filter((item) => !item.covered)) {
|
|
110
|
+
issues.push({
|
|
111
|
+
code: "missing-scenario-coverage",
|
|
112
|
+
severity: "error",
|
|
113
|
+
message: `Scenario "${item.id}" has no matching test reference.`,
|
|
114
|
+
filePath: item.filePath,
|
|
115
|
+
line: item.line,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (options.requireRuleCoverage ?? false) {
|
|
120
|
+
for (const item of coverage.ruleCoverage.filter((item) => !item.covered)) {
|
|
121
|
+
issues.push({
|
|
122
|
+
code: "missing-rule-coverage",
|
|
123
|
+
severity: "error",
|
|
124
|
+
message: `Rule "${item.id}" has no matching test reference.`,
|
|
125
|
+
filePath: item.filePath,
|
|
126
|
+
line: item.line,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const ref of coverage.orphanScenarioReferences) {
|
|
131
|
+
issues.push({
|
|
132
|
+
code: "orphan-scenario-reference",
|
|
133
|
+
severity: "error",
|
|
134
|
+
message: `Test references unknown scenario "${ref.id}".`,
|
|
135
|
+
filePath: ref.filePath,
|
|
136
|
+
line: ref.line,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
for (const ref of coverage.orphanRuleReferences) {
|
|
140
|
+
issues.push({
|
|
141
|
+
code: "orphan-rule-reference",
|
|
142
|
+
severity: "error",
|
|
143
|
+
message: `Test references unknown rule "${ref.id}".`,
|
|
144
|
+
filePath: ref.filePath,
|
|
145
|
+
line: ref.line,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return issues;
|
|
149
|
+
}
|
|
150
|
+
/** Load and parse all feature specs matching the provided globs. */
|
|
151
|
+
export async function loadFeatureSpecs(patterns) {
|
|
152
|
+
const specs = [];
|
|
153
|
+
for (const file of await expandFilePatterns(patterns)) {
|
|
154
|
+
specs.push(parseFeatureSpec(await readFile(file, "utf8"), { filePath: file }));
|
|
155
|
+
}
|
|
156
|
+
return specs;
|
|
157
|
+
}
|
|
158
|
+
/** Run spec validation and optional test coverage validation in one call. */
|
|
159
|
+
export async function checkFeatureSpecs(options) {
|
|
160
|
+
const specs = await loadFeatureSpecs(options.specs);
|
|
161
|
+
const validationIssues = specs.flatMap(validateFeatureSpec);
|
|
162
|
+
const coverage = options.tests?.length
|
|
163
|
+
? buildCoverageSummary(specs, await collectTestReferences(options.tests))
|
|
164
|
+
: undefined;
|
|
165
|
+
const coverageIssues = coverage
|
|
166
|
+
? validateCoverage(coverage, {
|
|
167
|
+
requireRuleCoverage: options.requireRuleCoverage,
|
|
168
|
+
requireScenarioCoverage: options.requireScenarioCoverage,
|
|
169
|
+
})
|
|
170
|
+
: [];
|
|
171
|
+
return {
|
|
172
|
+
specs,
|
|
173
|
+
validationIssues,
|
|
174
|
+
coverage,
|
|
175
|
+
coverageIssues,
|
|
176
|
+
ok: ![...validationIssues, ...coverageIssues].some((i) => i.severity === "error"),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function parseScenarios(lines, bodyStartLine) {
|
|
180
|
+
const scenarios = [];
|
|
181
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
182
|
+
const match = lines[i].match(/^###\s+([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*-S\d{3}):\s+(.+)$/);
|
|
183
|
+
if (!match)
|
|
184
|
+
continue;
|
|
185
|
+
const steps = [];
|
|
186
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
187
|
+
if (/^##?#\s+/.test(lines[j]))
|
|
188
|
+
break;
|
|
189
|
+
const step = lines[j].trim().match(/^(Given|When|Then|And|But)\s+(.+)$/);
|
|
190
|
+
if (step)
|
|
191
|
+
steps.push({
|
|
192
|
+
keyword: step[1],
|
|
193
|
+
text: step[2].trim(),
|
|
194
|
+
line: bodyStartLine + j,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
scenarios.push({
|
|
198
|
+
id: match[1],
|
|
199
|
+
title: match[2].trim(),
|
|
200
|
+
line: bodyStartLine + i,
|
|
201
|
+
steps,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return scenarios;
|
|
205
|
+
}
|
|
206
|
+
function issue(spec, code, severity, message, line) {
|
|
207
|
+
return { code, severity, message, filePath: spec.filePath, line };
|
|
208
|
+
}
|
|
209
|
+
function registerId(spec, issues, seen, id, line) {
|
|
210
|
+
const previous = seen.get(id);
|
|
211
|
+
if (previous)
|
|
212
|
+
issues.push(issue(spec, "duplicate-id", "error", `Duplicate id "${id}". First occurrence is on line ${previous}.`, line));
|
|
213
|
+
seen.set(id, line);
|
|
214
|
+
}
|
|
215
|
+
function item(id, title, filePath, line, refs) {
|
|
216
|
+
const references = refs.filter((ref) => ref.id === id);
|
|
217
|
+
return {
|
|
218
|
+
id,
|
|
219
|
+
title,
|
|
220
|
+
filePath,
|
|
221
|
+
line,
|
|
222
|
+
references,
|
|
223
|
+
covered: references.length > 0,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function sourceForMatch(source, offset) {
|
|
227
|
+
const before = source.slice(Math.max(0, offset - 80), offset);
|
|
228
|
+
const context = source.slice(Math.max(0, offset - 80), offset + 80);
|
|
229
|
+
if (/covers\s*:\s*\[[^\]]*$/.test(before))
|
|
230
|
+
return "covers";
|
|
231
|
+
if (/tag\s*:\s*\[[^\]]*$/.test(before) ||
|
|
232
|
+
/@/.test(source.slice(Math.max(0, offset - 2), offset + 2)))
|
|
233
|
+
return "tag";
|
|
234
|
+
if (/annotation/.test(context))
|
|
235
|
+
return "annotation";
|
|
236
|
+
if (/test|scenario/.test(before.slice(-40)))
|
|
237
|
+
return "title";
|
|
238
|
+
return "free-text";
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=featureSpecs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"featureSpecs.js","sourceRoot":"","sources":["../src/featureSpecs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,aAAa,EACb,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAY3B,+FAA+F;AAC/F,MAAM,UAAU,gBAAgB,CAC9B,MAAc,EACd,UAAiC,EAAE;IAEnC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,UAAU,CAAC;IAChD,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAE7D,OAAO;QACL,QAAQ;QACR,WAAW,EAAE,MAAM,CAAC,WAAiC;QACrD,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC;QACzD,SAAS,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC;QAC7D,MAAM;KACP,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,mBAAmB,CAAC,IAAiB;IACnD,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEvC,IAAI,CAAC,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;QACjE,MAAM,CAAC,IAAI,CACT,KAAK,CACH,IAAI,EACJ,oBAAoB,EACpB,OAAO,EACP,eAAe,IAAI,CAAC,WAAW,CAAC,EAAE,kDAAkD,CACrF,CACF,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QAC1C,MAAM,CAAC,IAAI,CACT,KAAK,CACH,IAAI,EACJ,gBAAgB,EAChB,SAAS,EACT,8CAA8C,CAC/C,CACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,CAAC,IAAI,CACT,KAAK,CACH,IAAI,EACJ,iBAAiB,EACjB,OAAO,EACP,2DAA2D,CAC5D,CACF,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,IAAI,CACT,KAAK,CACH,IAAI,EACJ,mBAAmB,EACnB,OAAO,EACP,kDAAkD,CACnD,CACF,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;YACnD,MAAM,CAAC,IAAI,CACT,KAAK,CACH,IAAI,EACJ,mBAAmB,EACnB,OAAO,EACP,YAAY,IAAI,CAAC,EAAE,qBAAqB,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAC/D,IAAI,CAAC,IAAI,CACV,CACF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,CACT,KAAK,CACH,IAAI,EACJ,sBAAsB,EACtB,SAAS,EACT,SAAS,IAAI,CAAC,EAAE,oEAAoE,EACpF,IAAI,CAAC,IAAI,CACV,CACF,CAAC;QACJ,CAAC;QAED,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACtC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,CAAC,IAAI,CACT,KAAK,CACH,IAAI,EACJ,uBAAuB,EACvB,OAAO,EACP,gBAAgB,QAAQ,CAAC,EAAE,qBAAqB,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EACvE,QAAQ,CAAC,IAAI,CACd,CACF,CAAC;QACJ,CAAC;QAED,KAAK,MAAM,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAU,EAAE,CAAC;YACzD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,KAAK,OAAO,CAAC,EAAE,CAAC;gBAC7D,MAAM,CAAC,IAAI,CACT,KAAK,CACH,IAAI,EACJ,WAAW,OAAO,CAAC,WAAW,EAAE,EAAE,EAClC,SAAS,EACT,aAAa,QAAQ,CAAC,EAAE,sBAAsB,OAAO,QAAQ,EAC7D,QAAQ,CAAC,IAAI,CACd,CACF,CAAC;YACJ,CAAC;QACH,CAAC;QAED,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,mBAAmB,CACjC,MAAc,EACd,QAAQ,GAAG,UAAU;IAErB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,IAAI,GAAoB,EAAE,CAAC;IAEjC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC,IAAI,CAAC;YACR,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;YACZ,QAAQ;YACR,IAAI,EAAE,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;YAC5C,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;SACjD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACnD,IAAI,CAAC,IAAI,CAAC;YACR,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;YACZ,QAAQ;YACR,IAAI,EAAE,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;YAC5C,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;SACjD,CAAC,CAAC;IACL,CAAC;IAED,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,QAAkB;IAC5D,MAAM,IAAI,GAAoB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,MAAM,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,oBAAoB,CAClC,KAAoB,EACpB,UAA2B;IAE3B,MAAM,SAAS,GAAG,IAAI,GAAG,CACvB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CACrB,IAAI,CAAC,SAAS,CAAC,GAAG,CAChB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAU,CACzD,CACF,CACF,CAAC;IACF,MAAM,KAAK,GAAG,IAAI,GAAG,CACnB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CACrB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAU,CAAC,CAC7D,CACF,CAAC;IACF,MAAM,YAAY,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IAEjE,OAAO;QACL,gBAAgB,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CACpE,IAAI,CACF,EAAE,EACF,KAAK,CAAC,QAAQ,CAAC,KAAK,EACpB,KAAK,CAAC,IAAI,CAAC,QAAQ,EACnB,KAAK,CAAC,QAAQ,CAAC,IAAI,EACnB,YAAY,CACb,CACF;QACD,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAC5D,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAC1E;QACD,wBAAwB,EAAE,YAAY,CAAC,MAAM,CAC3C,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAChC;QACD,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;KACnE,CAAC;AACJ,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,gBAAgB,CAC9B,QAAyB,EACzB,UAGI,EAAE;IAEN,MAAM,MAAM,GAAsB,EAAE,CAAC;IAErC,IAAI,OAAO,CAAC,uBAAuB,IAAI,IAAI,EAAE,CAAC;QAC5C,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,gBAAgB,CAAC,MAAM,CACjD,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CACxB,EAAE,CAAC;YACF,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,2BAA2B;gBACjC,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,aAAa,IAAI,CAAC,EAAE,mCAAmC;gBAChE,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,mBAAmB,IAAI,KAAK,EAAE,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACzE,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,uBAAuB;gBAC7B,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,SAAS,IAAI,CAAC,EAAE,mCAAmC;gBAC5D,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,wBAAwB,EAAE,CAAC;QACpD,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,2BAA2B;YACjC,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,qCAAqC,GAAG,CAAC,EAAE,IAAI;YACxD,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC,CAAC;IACL,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,oBAAoB,EAAE,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,uBAAuB;YAC7B,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,iCAAiC,GAAG,CAAC,EAAE,IAAI;YACpD,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,QAAkB;IACvD,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,MAAM,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,KAAK,CAAC,IAAI,CACR,gBAAgB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CACnE,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAKvC;IACC,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,gBAAgB,GAAG,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM;QACpC,CAAC,CAAC,oBAAoB,CAAC,KAAK,EAAE,MAAM,qBAAqB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACzE,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,cAAc,GAAG,QAAQ;QAC7B,CAAC,CAAC,gBAAgB,CAAC,QAAQ,EAAE;YACzB,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;YAChD,uBAAuB,EAAE,OAAO,CAAC,uBAAuB;SACzD,CAAC;QACJ,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;QACL,KAAK;QACL,gBAAgB;QAChB,QAAQ;QACR,cAAc;QACd,EAAE,EAAE,CAAC,CAAC,GAAG,gBAAgB,EAAE,GAAG,cAAc,CAAC,CAAC,IAAI,CAChD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAC9B;KACF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAe,EAAE,aAAqB;IAC5D,MAAM,SAAS,GAAsB,EAAE,CAAC;IAExC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAC1B,wDAAwD,CACzD,CAAC;QACF,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,KAAK,GAA6B,EAAE,CAAC;QAE3C,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAAE,MAAM;YACrC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;YACzE,IAAI,IAAI;gBACN,KAAK,CAAC,IAAI,CAAC;oBACT,OAAO,EAAE,IAAI,CAAC,CAAC,CAAgB;oBAC/B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;oBACpB,IAAI,EAAE,aAAa,GAAG,CAAC;iBACxB,CAAC,CAAC;QACP,CAAC;QAED,SAAS,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;YACZ,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;YACtB,IAAI,EAAE,aAAa,GAAG,CAAC;YACvB,KAAK;SACN,CAAC,CAAC;IACL,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,KAAK,CACZ,IAAiB,EACjB,IAAY,EACZ,QAAqC,EACrC,OAAe,EACf,IAAa;IAEb,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;AACpE,CAAC;AAED,SAAS,UAAU,CACjB,IAAiB,EACjB,MAAyB,EACzB,IAAyB,EACzB,EAAU,EACV,IAAY;IAEZ,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,IAAI,QAAQ;QACV,MAAM,CAAC,IAAI,CACT,KAAK,CACH,IAAI,EACJ,cAAc,EACd,OAAO,EACP,iBAAiB,EAAE,kCAAkC,QAAQ,GAAG,EAChE,IAAI,CACL,CACF,CAAC;IACJ,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,IAAI,CACX,EAAU,EACV,KAAa,EACb,QAAgB,EAChB,IAAY,EACZ,IAAqB;IAErB,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACvD,OAAO;QACL,EAAE;QACF,KAAK;QACL,QAAQ;QACR,IAAI;QACJ,UAAU;QACV,OAAO,EAAE,UAAU,CAAC,MAAM,GAAG,CAAC;KAC/B,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CACrB,MAAc,EACd,MAAc;IAEd,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;IACpE,IAAI,wBAAwB,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC3D,IACE,qBAAqB,CAAC,IAAI,CAAC,MAAM,CAAC;QAClC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;QAE3D,OAAO,KAAK,CAAC;IACf,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,YAAY,CAAC;IACpD,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC;IAC5D,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filePatterns.d.ts","sourceRoot":"","sources":["../src/filePatterns.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"filePatterns.d.ts","sourceRoot":"","sources":["../src/filePatterns.ts"],"names":[],"mappings":"AAMA,8EAA8E;AAC9E,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,qBAO1D;AAED,0EAA0E;AAC1E,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,qBAE9D"}
|
package/dist/filePatterns.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glob expansion helpers for source specs, tests, and generated artifact
|
|
3
|
+
* patterns while filtering directories that should never be scanned.
|
|
4
|
+
*/
|
|
1
5
|
import { glob } from "node:fs/promises";
|
|
2
6
|
/** Expand user-provided source globs while ignoring generated directories. */
|
|
3
7
|
export async function expandFilePatterns(patterns) {
|
package/dist/filePatterns.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filePatterns.js","sourceRoot":"","sources":["../src/filePatterns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAkB;IACzD,OAAO,cAAc,CAAC,QAAQ,EAAE;QAC9B,iBAAiB;QACjB,SAAS;QACT,SAAS;QACT,iBAAiB;KAClB,CAAC,CAAC;AACL,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,QAAkB;IAC7D,OAAO,cAAc,CAAC,QAAQ,EAAE,CAAC,iBAAiB,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,QAAkB,EAAE,OAAiB;IACjE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;YACpD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAClC,CAAC"}
|
|
1
|
+
{"version":3,"file":"filePatterns.js","sourceRoot":"","sources":["../src/filePatterns.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAkB;IACzD,OAAO,cAAc,CAAC,QAAQ,EAAE;QAC9B,iBAAiB;QACjB,SAAS;QACT,SAAS;QACT,iBAAiB;KAClB,CAAC,CAAC;AACL,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,QAAkB;IAC7D,OAAO,cAAc,CAAC,QAAQ,EAAE,CAAC,iBAAiB,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,QAAkB,EAAE,OAAiB;IACjE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;YACpD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAClC,CAAC"}
|
package/dist/html.d.ts
CHANGED
package/dist/html.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,UAOjC"}
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,UAOjC"}
|