qfai 0.3.8 → 0.4.2
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 +12 -0
- package/assets/init/.qfai/README.md +5 -0
- package/assets/init/.qfai/prompts/README.md +1 -0
- package/assets/init/.qfai/prompts/qfai-generate-test-globs.md +29 -0
- package/assets/init/.qfai/specs/README.md +1 -1
- package/assets/init/root/qfai.config.yaml +8 -0
- package/assets/init/root/tests/qfai-traceability.sample.test.ts +2 -0
- package/dist/cli/index.cjs +717 -364
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.mjs +719 -366
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +710 -351
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -3
- package/dist/index.d.ts +156 -2
- package/dist/index.mjs +714 -354
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/dist/cli/commands/init.d.ts +0 -8
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/init.js +0 -30
- package/dist/cli/commands/init.js.map +0 -1
- package/dist/cli/commands/report.d.ts +0 -7
- package/dist/cli/commands/report.d.ts.map +0 -1
- package/dist/cli/commands/report.js +0 -80
- package/dist/cli/commands/report.js.map +0 -1
- package/dist/cli/commands/validate.d.ts +0 -9
- package/dist/cli/commands/validate.d.ts.map +0 -1
- package/dist/cli/commands/validate.js +0 -57
- package/dist/cli/commands/validate.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -7
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/lib/args.d.ts +0 -18
- package/dist/cli/lib/args.d.ts.map +0 -1
- package/dist/cli/lib/args.js +0 -98
- package/dist/cli/lib/args.js.map +0 -1
- package/dist/cli/lib/assets.d.ts +0 -2
- package/dist/cli/lib/assets.d.ts.map +0 -1
- package/dist/cli/lib/assets.js +0 -24
- package/dist/cli/lib/assets.js.map +0 -1
- package/dist/cli/lib/failOn.d.ts +0 -5
- package/dist/cli/lib/failOn.d.ts.map +0 -1
- package/dist/cli/lib/failOn.js +0 -10
- package/dist/cli/lib/failOn.js.map +0 -1
- package/dist/cli/lib/fs.d.ts +0 -11
- package/dist/cli/lib/fs.d.ts.map +0 -1
- package/dist/cli/lib/fs.js +0 -91
- package/dist/cli/lib/fs.js.map +0 -1
- package/dist/cli/lib/logger.d.ts +0 -4
- package/dist/cli/lib/logger.d.ts.map +0 -1
- package/dist/cli/lib/logger.js +0 -10
- package/dist/cli/lib/logger.js.map +0 -1
- package/dist/cli/main.d.ts +0 -2
- package/dist/cli/main.d.ts.map +0 -1
- package/dist/cli/main.js +0 -66
- package/dist/cli/main.js.map +0 -1
- package/dist/core/config.d.ts +0 -44
- package/dist/core/config.d.ts.map +0 -1
- package/dist/core/config.js +0 -218
- package/dist/core/config.js.map +0 -1
- package/dist/core/contractIndex.d.ts +0 -13
- package/dist/core/contractIndex.d.ts.map +0 -1
- package/dist/core/contractIndex.js +0 -66
- package/dist/core/contractIndex.js.map +0 -1
- package/dist/core/contracts.d.ts +0 -5
- package/dist/core/contracts.d.ts.map +0 -1
- package/dist/core/contracts.js +0 -42
- package/dist/core/contracts.js.map +0 -1
- package/dist/core/discovery.d.ts +0 -14
- package/dist/core/discovery.d.ts.map +0 -1
- package/dist/core/discovery.js +0 -55
- package/dist/core/discovery.js.map +0 -1
- package/dist/core/fs.d.ts +0 -6
- package/dist/core/fs.d.ts.map +0 -1
- package/dist/core/fs.js +0 -55
- package/dist/core/fs.js.map +0 -1
- package/dist/core/gherkin/parse.d.ts +0 -7
- package/dist/core/gherkin/parse.d.ts.map +0 -1
- package/dist/core/gherkin/parse.js +0 -25
- package/dist/core/gherkin/parse.js.map +0 -1
- package/dist/core/ids.d.ts +0 -6
- package/dist/core/ids.d.ts.map +0 -1
- package/dist/core/ids.js +0 -52
- package/dist/core/ids.js.map +0 -1
- package/dist/core/index.d.ts +0 -13
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -13
- package/dist/core/index.js.map +0 -1
- package/dist/core/parse/adr.d.ts +0 -13
- package/dist/core/parse/adr.d.ts.map +0 -1
- package/dist/core/parse/adr.js +0 -33
- package/dist/core/parse/adr.js.map +0 -1
- package/dist/core/parse/gherkin.d.ts +0 -12
- package/dist/core/parse/gherkin.d.ts.map +0 -1
- package/dist/core/parse/gherkin.js +0 -22
- package/dist/core/parse/gherkin.js.map +0 -1
- package/dist/core/parse/markdown.d.ts +0 -14
- package/dist/core/parse/markdown.d.ts.map +0 -1
- package/dist/core/parse/markdown.js +0 -45
- package/dist/core/parse/markdown.js.map +0 -1
- package/dist/core/parse/spec.d.ts +0 -28
- package/dist/core/parse/spec.d.ts.map +0 -1
- package/dist/core/parse/spec.js +0 -80
- package/dist/core/parse/spec.js.map +0 -1
- package/dist/core/report.d.ts +0 -39
- package/dist/core/report.d.ts.map +0 -1
- package/dist/core/report.js +0 -226
- package/dist/core/report.js.map +0 -1
- package/dist/core/scenarioModel.d.ts +0 -33
- package/dist/core/scenarioModel.d.ts.map +0 -1
- package/dist/core/scenarioModel.js +0 -130
- package/dist/core/scenarioModel.js.map +0 -1
- package/dist/core/specLayout.d.ts +0 -8
- package/dist/core/specLayout.d.ts.map +0 -1
- package/dist/core/specLayout.js +0 -36
- package/dist/core/specLayout.js.map +0 -1
- package/dist/core/types.d.ts +0 -25
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -2
- package/dist/core/types.js.map +0 -1
- package/dist/core/validate.d.ts +0 -4
- package/dist/core/validate.d.ts.map +0 -1
- package/dist/core/validate.js +0 -34
- package/dist/core/validate.js.map +0 -1
- package/dist/core/validators/contracts.d.ts +0 -5
- package/dist/core/validators/contracts.d.ts.map +0 -1
- package/dist/core/validators/contracts.js +0 -162
- package/dist/core/validators/contracts.js.map +0 -1
- package/dist/core/validators/delta.d.ts +0 -4
- package/dist/core/validators/delta.d.ts.map +0 -1
- package/dist/core/validators/delta.js +0 -68
- package/dist/core/validators/delta.js.map +0 -1
- package/dist/core/validators/ids.d.ts +0 -4
- package/dist/core/validators/ids.d.ts.map +0 -1
- package/dist/core/validators/ids.js +0 -88
- package/dist/core/validators/ids.js.map +0 -1
- package/dist/core/validators/scenario.d.ts +0 -5
- package/dist/core/validators/scenario.d.ts.map +0 -1
- package/dist/core/validators/scenario.js +0 -140
- package/dist/core/validators/scenario.js.map +0 -1
- package/dist/core/validators/spec.d.ts +0 -5
- package/dist/core/validators/spec.d.ts.map +0 -1
- package/dist/core/validators/spec.js +0 -94
- package/dist/core/validators/spec.js.map +0 -1
- package/dist/core/validators/traceability.d.ts +0 -4
- package/dist/core/validators/traceability.d.ts.map +0 -1
- package/dist/core/validators/traceability.js +0 -180
- package/dist/core/validators/traceability.js.map +0 -1
- package/dist/core/version.d.ts +0 -2
- package/dist/core/version.d.ts.map +0 -1
- package/dist/core/version.js +0 -25
- package/dist/core/version.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -177,8 +177,8 @@ function report(copied, skipped, dryRun, label) {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
// src/cli/commands/report.ts
|
|
180
|
-
var
|
|
181
|
-
var
|
|
180
|
+
var import_promises16 = require("fs/promises");
|
|
181
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
182
182
|
|
|
183
183
|
// src/core/config.ts
|
|
184
184
|
var import_promises2 = require("fs/promises");
|
|
@@ -210,6 +210,10 @@ var defaultConfig = {
|
|
|
210
210
|
traceability: {
|
|
211
211
|
brMustHaveSc: true,
|
|
212
212
|
scMustTouchContracts: true,
|
|
213
|
+
scMustHaveTest: true,
|
|
214
|
+
testFileGlobs: [],
|
|
215
|
+
testFileExcludeGlobs: [],
|
|
216
|
+
scNoTestSeverity: "error",
|
|
213
217
|
allowOrphanContracts: false,
|
|
214
218
|
unknownContractIdSeverity: "error"
|
|
215
219
|
}
|
|
@@ -389,6 +393,34 @@ function normalizeValidation(raw, configPath, issues) {
|
|
|
389
393
|
configPath,
|
|
390
394
|
issues
|
|
391
395
|
),
|
|
396
|
+
scMustHaveTest: readBoolean(
|
|
397
|
+
traceabilityRaw?.scMustHaveTest,
|
|
398
|
+
base.traceability.scMustHaveTest,
|
|
399
|
+
"validation.traceability.scMustHaveTest",
|
|
400
|
+
configPath,
|
|
401
|
+
issues
|
|
402
|
+
),
|
|
403
|
+
testFileGlobs: readStringArray(
|
|
404
|
+
traceabilityRaw?.testFileGlobs,
|
|
405
|
+
base.traceability.testFileGlobs,
|
|
406
|
+
"validation.traceability.testFileGlobs",
|
|
407
|
+
configPath,
|
|
408
|
+
issues
|
|
409
|
+
),
|
|
410
|
+
testFileExcludeGlobs: readStringArray(
|
|
411
|
+
traceabilityRaw?.testFileExcludeGlobs,
|
|
412
|
+
base.traceability.testFileExcludeGlobs,
|
|
413
|
+
"validation.traceability.testFileExcludeGlobs",
|
|
414
|
+
configPath,
|
|
415
|
+
issues
|
|
416
|
+
),
|
|
417
|
+
scNoTestSeverity: readTraceabilitySeverity(
|
|
418
|
+
traceabilityRaw?.scNoTestSeverity,
|
|
419
|
+
base.traceability.scNoTestSeverity,
|
|
420
|
+
"validation.traceability.scNoTestSeverity",
|
|
421
|
+
configPath,
|
|
422
|
+
issues
|
|
423
|
+
),
|
|
392
424
|
allowOrphanContracts: readBoolean(
|
|
393
425
|
traceabilityRaw?.allowOrphanContracts,
|
|
394
426
|
base.traceability.allowOrphanContracts,
|
|
@@ -514,8 +546,8 @@ function isRecord(value) {
|
|
|
514
546
|
}
|
|
515
547
|
|
|
516
548
|
// src/core/report.ts
|
|
517
|
-
var
|
|
518
|
-
var
|
|
549
|
+
var import_promises15 = require("fs/promises");
|
|
550
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
519
551
|
|
|
520
552
|
// src/core/discovery.ts
|
|
521
553
|
var import_promises5 = require("fs/promises");
|
|
@@ -523,6 +555,7 @@ var import_promises5 = require("fs/promises");
|
|
|
523
555
|
// src/core/fs.ts
|
|
524
556
|
var import_promises3 = require("fs/promises");
|
|
525
557
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
558
|
+
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
526
559
|
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
527
560
|
"node_modules",
|
|
528
561
|
".git",
|
|
@@ -544,6 +577,18 @@ async function collectFiles(root, options = {}) {
|
|
|
544
577
|
await walk(root, root, ignoreDirs, extensions, entries);
|
|
545
578
|
return entries;
|
|
546
579
|
}
|
|
580
|
+
async function collectFilesByGlobs(root, options) {
|
|
581
|
+
if (options.globs.length === 0) {
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
584
|
+
return (0, import_fast_glob.default)(options.globs, {
|
|
585
|
+
cwd: root,
|
|
586
|
+
ignore: options.ignore ?? [],
|
|
587
|
+
onlyFiles: true,
|
|
588
|
+
absolute: true,
|
|
589
|
+
unique: true
|
|
590
|
+
});
|
|
591
|
+
}
|
|
547
592
|
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
548
593
|
const items = await (0, import_promises3.readdir)(current, { withFileTypes: true });
|
|
549
594
|
for (const item of items) {
|
|
@@ -708,20 +753,331 @@ function isValidId(value, prefix) {
|
|
|
708
753
|
return strict.test(value);
|
|
709
754
|
}
|
|
710
755
|
|
|
711
|
-
// src/core/
|
|
712
|
-
var VALIDATION_SCHEMA_VERSION = "0.2";
|
|
713
|
-
|
|
714
|
-
// src/core/version.ts
|
|
756
|
+
// src/core/traceability.ts
|
|
715
757
|
var import_promises6 = require("fs/promises");
|
|
716
758
|
var import_node_path7 = __toESM(require("path"), 1);
|
|
759
|
+
|
|
760
|
+
// src/core/gherkin/parse.ts
|
|
761
|
+
var import_gherkin = require("@cucumber/gherkin");
|
|
762
|
+
var import_node_crypto = require("crypto");
|
|
763
|
+
function parseGherkin(source, uri) {
|
|
764
|
+
const errors = [];
|
|
765
|
+
const uuidFn = () => (0, import_node_crypto.randomUUID)();
|
|
766
|
+
const builder = new import_gherkin.AstBuilder(uuidFn);
|
|
767
|
+
const matcher = new import_gherkin.GherkinClassicTokenMatcher();
|
|
768
|
+
const parser = new import_gherkin.Parser(builder, matcher);
|
|
769
|
+
try {
|
|
770
|
+
const gherkinDocument = parser.parse(source);
|
|
771
|
+
gherkinDocument.uri = uri;
|
|
772
|
+
return { gherkinDocument, errors };
|
|
773
|
+
} catch (error2) {
|
|
774
|
+
errors.push(formatError2(error2));
|
|
775
|
+
return { gherkinDocument: null, errors };
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function formatError2(error2) {
|
|
779
|
+
if (error2 instanceof Error) {
|
|
780
|
+
return error2.message;
|
|
781
|
+
}
|
|
782
|
+
return String(error2);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// src/core/scenarioModel.ts
|
|
786
|
+
var SPEC_TAG_RE = /^SPEC-\d{4}$/;
|
|
787
|
+
var SC_TAG_RE = /^SC-\d{4}$/;
|
|
788
|
+
var BR_TAG_RE = /^BR-\d{4}$/;
|
|
789
|
+
var UI_TAG_RE = /^UI-\d{4}$/;
|
|
790
|
+
var API_TAG_RE = /^API-\d{4}$/;
|
|
791
|
+
var DATA_TAG_RE = /^DATA-\d{4}$/;
|
|
792
|
+
function parseScenarioDocument(text, uri) {
|
|
793
|
+
const { gherkinDocument, errors } = parseGherkin(text, uri);
|
|
794
|
+
if (!gherkinDocument) {
|
|
795
|
+
return { document: null, errors };
|
|
796
|
+
}
|
|
797
|
+
const feature = gherkinDocument.feature;
|
|
798
|
+
if (!feature) {
|
|
799
|
+
return {
|
|
800
|
+
document: { uri, featureTags: [], scenarios: [] },
|
|
801
|
+
errors
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
const featureTags = collectTagNames(feature.tags);
|
|
805
|
+
const scenarios = collectScenarioNodes(feature, featureTags);
|
|
806
|
+
return {
|
|
807
|
+
document: {
|
|
808
|
+
uri,
|
|
809
|
+
featureName: feature.name,
|
|
810
|
+
featureTags,
|
|
811
|
+
scenarios
|
|
812
|
+
},
|
|
813
|
+
errors
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
function buildScenarioAtoms(document) {
|
|
817
|
+
return document.scenarios.map((scenario) => {
|
|
818
|
+
const specIds = scenario.tags.filter((tag) => SPEC_TAG_RE.test(tag));
|
|
819
|
+
const scIds = scenario.tags.filter((tag) => SC_TAG_RE.test(tag));
|
|
820
|
+
const brIds = unique2(scenario.tags.filter((tag) => BR_TAG_RE.test(tag)));
|
|
821
|
+
const contractIds = /* @__PURE__ */ new Set();
|
|
822
|
+
scenario.tags.forEach((tag) => {
|
|
823
|
+
if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) || DATA_TAG_RE.test(tag)) {
|
|
824
|
+
contractIds.add(tag);
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
for (const step of scenario.steps) {
|
|
828
|
+
for (const text of collectStepTexts(step)) {
|
|
829
|
+
extractIds(text, "UI").forEach((id) => contractIds.add(id));
|
|
830
|
+
extractIds(text, "API").forEach((id) => contractIds.add(id));
|
|
831
|
+
extractIds(text, "DATA").forEach((id) => contractIds.add(id));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const atom = {
|
|
835
|
+
uri: document.uri,
|
|
836
|
+
featureName: document.featureName ?? "",
|
|
837
|
+
scenarioName: scenario.name,
|
|
838
|
+
kind: scenario.kind,
|
|
839
|
+
brIds,
|
|
840
|
+
contractIds: Array.from(contractIds).sort()
|
|
841
|
+
};
|
|
842
|
+
if (scenario.line !== void 0) {
|
|
843
|
+
atom.line = scenario.line;
|
|
844
|
+
}
|
|
845
|
+
if (specIds.length === 1) {
|
|
846
|
+
const specId = specIds[0];
|
|
847
|
+
if (specId) {
|
|
848
|
+
atom.specId = specId;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (scIds.length === 1) {
|
|
852
|
+
const scId = scIds[0];
|
|
853
|
+
if (scId) {
|
|
854
|
+
atom.scId = scId;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return atom;
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
function collectScenarioNodes(feature, featureTags) {
|
|
861
|
+
const scenarios = [];
|
|
862
|
+
for (const child of feature.children) {
|
|
863
|
+
if (child.scenario) {
|
|
864
|
+
scenarios.push(buildScenarioNode(child.scenario, featureTags, []));
|
|
865
|
+
}
|
|
866
|
+
if (child.rule) {
|
|
867
|
+
const ruleTags = collectTagNames(child.rule.tags);
|
|
868
|
+
for (const ruleChild of child.rule.children) {
|
|
869
|
+
if (ruleChild.scenario) {
|
|
870
|
+
scenarios.push(
|
|
871
|
+
buildScenarioNode(ruleChild.scenario, featureTags, ruleTags)
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return scenarios;
|
|
878
|
+
}
|
|
879
|
+
function buildScenarioNode(scenario, featureTags, ruleTags) {
|
|
880
|
+
const tags = [...featureTags, ...ruleTags, ...collectTagNames(scenario.tags)];
|
|
881
|
+
const kind = scenario.examples.length > 0 ? "ScenarioOutline" : "Scenario";
|
|
882
|
+
return {
|
|
883
|
+
name: scenario.name,
|
|
884
|
+
kind,
|
|
885
|
+
line: scenario.location?.line,
|
|
886
|
+
tags,
|
|
887
|
+
steps: scenario.steps
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function collectTagNames(tags) {
|
|
891
|
+
return tags.map((tag) => tag.name.replace(/^@/, ""));
|
|
892
|
+
}
|
|
893
|
+
function collectStepTexts(step) {
|
|
894
|
+
const texts = [];
|
|
895
|
+
if (step.text) {
|
|
896
|
+
texts.push(step.text);
|
|
897
|
+
}
|
|
898
|
+
if (step.docString?.content) {
|
|
899
|
+
texts.push(step.docString.content);
|
|
900
|
+
}
|
|
901
|
+
if (step.dataTable?.rows) {
|
|
902
|
+
for (const row of step.dataTable.rows) {
|
|
903
|
+
for (const cell of row.cells) {
|
|
904
|
+
texts.push(cell.value);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return texts;
|
|
909
|
+
}
|
|
910
|
+
function unique2(values) {
|
|
911
|
+
return Array.from(new Set(values));
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/core/traceability.ts
|
|
915
|
+
var SC_TAG_RE2 = /^SC-\d{4}$/;
|
|
916
|
+
var SC_TEST_ANNOTATION_RE = /\bQFAI:SC-(\d{4})\b/g;
|
|
917
|
+
var DEFAULT_TEST_FILE_EXCLUDE_GLOBS = [
|
|
918
|
+
"**/node_modules/**",
|
|
919
|
+
"**/.git/**",
|
|
920
|
+
"**/.qfai/**",
|
|
921
|
+
"**/dist/**",
|
|
922
|
+
"**/build/**",
|
|
923
|
+
"**/coverage/**",
|
|
924
|
+
"**/.next/**",
|
|
925
|
+
"**/out/**"
|
|
926
|
+
];
|
|
927
|
+
function extractAnnotatedScIds(text) {
|
|
928
|
+
const ids = /* @__PURE__ */ new Set();
|
|
929
|
+
for (const match of text.matchAll(SC_TEST_ANNOTATION_RE)) {
|
|
930
|
+
const suffix = match[1];
|
|
931
|
+
if (suffix) {
|
|
932
|
+
ids.add(`SC-${suffix}`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return Array.from(ids);
|
|
936
|
+
}
|
|
937
|
+
async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
938
|
+
const scIds = /* @__PURE__ */ new Set();
|
|
939
|
+
for (const file of scenarioFiles) {
|
|
940
|
+
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
941
|
+
const { document, errors } = parseScenarioDocument(text, file);
|
|
942
|
+
if (!document || errors.length > 0) {
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
for (const scenario of document.scenarios) {
|
|
946
|
+
for (const tag of scenario.tags) {
|
|
947
|
+
if (SC_TAG_RE2.test(tag)) {
|
|
948
|
+
scIds.add(tag);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return scIds;
|
|
954
|
+
}
|
|
955
|
+
async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
|
|
956
|
+
const sources = /* @__PURE__ */ new Map();
|
|
957
|
+
for (const file of scenarioFiles) {
|
|
958
|
+
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
959
|
+
const { document, errors } = parseScenarioDocument(text, file);
|
|
960
|
+
if (!document || errors.length > 0) {
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
for (const scenario of document.scenarios) {
|
|
964
|
+
for (const tag of scenario.tags) {
|
|
965
|
+
if (!SC_TAG_RE2.test(tag)) {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
const current = sources.get(tag) ?? /* @__PURE__ */ new Set();
|
|
969
|
+
current.add(file);
|
|
970
|
+
sources.set(tag, current);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return sources;
|
|
975
|
+
}
|
|
976
|
+
async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
977
|
+
const refs = /* @__PURE__ */ new Map();
|
|
978
|
+
const normalizedGlobs = normalizeGlobs(globs);
|
|
979
|
+
const normalizedExcludeGlobs = normalizeGlobs(excludeGlobs);
|
|
980
|
+
const mergedExcludeGlobs = Array.from(
|
|
981
|
+
/* @__PURE__ */ new Set([...DEFAULT_TEST_FILE_EXCLUDE_GLOBS, ...normalizedExcludeGlobs])
|
|
982
|
+
);
|
|
983
|
+
if (normalizedGlobs.length === 0) {
|
|
984
|
+
return {
|
|
985
|
+
refs,
|
|
986
|
+
scan: {
|
|
987
|
+
globs: normalizedGlobs,
|
|
988
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
989
|
+
matchedFileCount: 0
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
let files = [];
|
|
994
|
+
try {
|
|
995
|
+
files = await collectFilesByGlobs(root, {
|
|
996
|
+
globs: normalizedGlobs,
|
|
997
|
+
ignore: mergedExcludeGlobs
|
|
998
|
+
});
|
|
999
|
+
} catch (error2) {
|
|
1000
|
+
return {
|
|
1001
|
+
refs,
|
|
1002
|
+
scan: {
|
|
1003
|
+
globs: normalizedGlobs,
|
|
1004
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
1005
|
+
matchedFileCount: 0
|
|
1006
|
+
},
|
|
1007
|
+
error: formatError3(error2)
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
const normalizedFiles = Array.from(
|
|
1011
|
+
new Set(files.map((file) => import_node_path7.default.normalize(file)))
|
|
1012
|
+
);
|
|
1013
|
+
for (const file of normalizedFiles) {
|
|
1014
|
+
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
1015
|
+
const scIds = extractAnnotatedScIds(text);
|
|
1016
|
+
if (scIds.length === 0) {
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
for (const scId of scIds) {
|
|
1020
|
+
const current = refs.get(scId) ?? /* @__PURE__ */ new Set();
|
|
1021
|
+
current.add(file);
|
|
1022
|
+
refs.set(scId, current);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return {
|
|
1026
|
+
refs,
|
|
1027
|
+
scan: {
|
|
1028
|
+
globs: normalizedGlobs,
|
|
1029
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
1030
|
+
matchedFileCount: normalizedFiles.length
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
function buildScCoverage(scIds, refs) {
|
|
1035
|
+
const sortedScIds = toSortedArray(scIds);
|
|
1036
|
+
const refsRecord = {};
|
|
1037
|
+
const missingIds = [];
|
|
1038
|
+
let covered = 0;
|
|
1039
|
+
for (const scId of sortedScIds) {
|
|
1040
|
+
const files = refs.get(scId);
|
|
1041
|
+
const sortedFiles = files ? toSortedArray(files) : [];
|
|
1042
|
+
refsRecord[scId] = sortedFiles;
|
|
1043
|
+
if (sortedFiles.length === 0) {
|
|
1044
|
+
missingIds.push(scId);
|
|
1045
|
+
} else {
|
|
1046
|
+
covered += 1;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
total: sortedScIds.length,
|
|
1051
|
+
covered,
|
|
1052
|
+
missing: missingIds.length,
|
|
1053
|
+
missingIds,
|
|
1054
|
+
refs: refsRecord
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function toSortedArray(values) {
|
|
1058
|
+
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
1059
|
+
}
|
|
1060
|
+
function normalizeGlobs(globs) {
|
|
1061
|
+
return globs.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
|
|
1062
|
+
}
|
|
1063
|
+
function formatError3(error2) {
|
|
1064
|
+
if (error2 instanceof Error) {
|
|
1065
|
+
return error2.message;
|
|
1066
|
+
}
|
|
1067
|
+
return String(error2);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// src/core/version.ts
|
|
1071
|
+
var import_promises7 = require("fs/promises");
|
|
1072
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
717
1073
|
var import_node_url2 = require("url");
|
|
718
1074
|
async function resolveToolVersion() {
|
|
719
|
-
if ("0.
|
|
720
|
-
return "0.
|
|
1075
|
+
if ("0.4.2".length > 0) {
|
|
1076
|
+
return "0.4.2";
|
|
721
1077
|
}
|
|
722
1078
|
try {
|
|
723
1079
|
const packagePath = resolvePackageJsonPath();
|
|
724
|
-
const raw = await (0,
|
|
1080
|
+
const raw = await (0, import_promises7.readFile)(packagePath, "utf-8");
|
|
725
1081
|
const parsed = JSON.parse(raw);
|
|
726
1082
|
const version = typeof parsed.version === "string" ? parsed.version : "";
|
|
727
1083
|
return version.length > 0 ? version : "unknown";
|
|
@@ -732,18 +1088,18 @@ async function resolveToolVersion() {
|
|
|
732
1088
|
function resolvePackageJsonPath() {
|
|
733
1089
|
const base = __filename;
|
|
734
1090
|
const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
|
|
735
|
-
return
|
|
1091
|
+
return import_node_path8.default.resolve(import_node_path8.default.dirname(basePath), "../../package.json");
|
|
736
1092
|
}
|
|
737
1093
|
|
|
738
1094
|
// src/core/validators/contracts.ts
|
|
739
|
-
var
|
|
740
|
-
var
|
|
1095
|
+
var import_promises8 = require("fs/promises");
|
|
1096
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
741
1097
|
|
|
742
1098
|
// src/core/contracts.ts
|
|
743
|
-
var
|
|
1099
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
744
1100
|
var import_yaml2 = require("yaml");
|
|
745
1101
|
function parseStructuredContract(file, text) {
|
|
746
|
-
const ext =
|
|
1102
|
+
const ext = import_node_path9.default.extname(file).toLowerCase();
|
|
747
1103
|
if (ext === ".json") {
|
|
748
1104
|
return JSON.parse(text);
|
|
749
1105
|
}
|
|
@@ -794,9 +1150,9 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
794
1150
|
async function validateContracts(root, config) {
|
|
795
1151
|
const issues = [];
|
|
796
1152
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
797
|
-
issues.push(...await validateUiContracts(
|
|
798
|
-
issues.push(...await validateApiContracts(
|
|
799
|
-
issues.push(...await validateDataContracts(
|
|
1153
|
+
issues.push(...await validateUiContracts(import_node_path10.default.join(contractsRoot, "ui")));
|
|
1154
|
+
issues.push(...await validateApiContracts(import_node_path10.default.join(contractsRoot, "api")));
|
|
1155
|
+
issues.push(...await validateDataContracts(import_node_path10.default.join(contractsRoot, "db")));
|
|
800
1156
|
return issues;
|
|
801
1157
|
}
|
|
802
1158
|
async function validateUiContracts(uiRoot) {
|
|
@@ -814,7 +1170,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
814
1170
|
}
|
|
815
1171
|
const issues = [];
|
|
816
1172
|
for (const file of files) {
|
|
817
|
-
const text = await (0,
|
|
1173
|
+
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
818
1174
|
const invalidIds = extractInvalidIds(text, [
|
|
819
1175
|
"SPEC",
|
|
820
1176
|
"BR",
|
|
@@ -843,7 +1199,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
843
1199
|
issues.push(
|
|
844
1200
|
issue(
|
|
845
1201
|
"QFAI-CONTRACT-001",
|
|
846
|
-
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${
|
|
1202
|
+
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error2)})`,
|
|
847
1203
|
"error",
|
|
848
1204
|
file,
|
|
849
1205
|
"contracts.ui.parse"
|
|
@@ -881,7 +1237,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
881
1237
|
}
|
|
882
1238
|
const issues = [];
|
|
883
1239
|
for (const file of files) {
|
|
884
|
-
const text = await (0,
|
|
1240
|
+
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
885
1241
|
const invalidIds = extractInvalidIds(text, [
|
|
886
1242
|
"SPEC",
|
|
887
1243
|
"BR",
|
|
@@ -910,7 +1266,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
910
1266
|
issues.push(
|
|
911
1267
|
issue(
|
|
912
1268
|
"QFAI-CONTRACT-001",
|
|
913
|
-
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${
|
|
1269
|
+
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error2)})`,
|
|
914
1270
|
"error",
|
|
915
1271
|
file,
|
|
916
1272
|
"contracts.api.parse"
|
|
@@ -959,7 +1315,7 @@ async function validateDataContracts(dataRoot) {
|
|
|
959
1315
|
}
|
|
960
1316
|
const issues = [];
|
|
961
1317
|
for (const file of files) {
|
|
962
|
-
const text = await (0,
|
|
1318
|
+
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
963
1319
|
const invalidIds = extractInvalidIds(text, [
|
|
964
1320
|
"SPEC",
|
|
965
1321
|
"BR",
|
|
@@ -1005,7 +1361,7 @@ function lintSql(text, file) {
|
|
|
1005
1361
|
function hasOpenApi(doc) {
|
|
1006
1362
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
1007
1363
|
}
|
|
1008
|
-
function
|
|
1364
|
+
function formatError4(error2) {
|
|
1009
1365
|
if (error2 instanceof Error) {
|
|
1010
1366
|
return error2.message;
|
|
1011
1367
|
}
|
|
@@ -1030,8 +1386,8 @@ function issue(code, message, severity, file, rule, refs) {
|
|
|
1030
1386
|
}
|
|
1031
1387
|
|
|
1032
1388
|
// src/core/validators/delta.ts
|
|
1033
|
-
var
|
|
1034
|
-
var
|
|
1389
|
+
var import_promises9 = require("fs/promises");
|
|
1390
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
1035
1391
|
var SECTION_RE = /^##\s+変更区分/m;
|
|
1036
1392
|
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
1037
1393
|
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
@@ -1045,10 +1401,10 @@ async function validateDeltas(root, config) {
|
|
|
1045
1401
|
}
|
|
1046
1402
|
const issues = [];
|
|
1047
1403
|
for (const pack of packs) {
|
|
1048
|
-
const deltaPath =
|
|
1404
|
+
const deltaPath = import_node_path11.default.join(pack, "delta.md");
|
|
1049
1405
|
let text;
|
|
1050
1406
|
try {
|
|
1051
|
-
text = await (0,
|
|
1407
|
+
text = await (0, import_promises9.readFile)(deltaPath, "utf-8");
|
|
1052
1408
|
} catch (error2) {
|
|
1053
1409
|
if (isMissingFileError2(error2)) {
|
|
1054
1410
|
issues.push(
|
|
@@ -1120,17 +1476,17 @@ function issue2(code, message, severity, file, rule, refs) {
|
|
|
1120
1476
|
}
|
|
1121
1477
|
|
|
1122
1478
|
// src/core/validators/ids.ts
|
|
1123
|
-
var
|
|
1124
|
-
var
|
|
1479
|
+
var import_promises11 = require("fs/promises");
|
|
1480
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
1125
1481
|
|
|
1126
1482
|
// src/core/contractIndex.ts
|
|
1127
|
-
var
|
|
1128
|
-
var
|
|
1483
|
+
var import_promises10 = require("fs/promises");
|
|
1484
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1129
1485
|
async function buildContractIndex(root, config) {
|
|
1130
1486
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1131
|
-
const uiRoot =
|
|
1132
|
-
const apiRoot =
|
|
1133
|
-
const dataRoot =
|
|
1487
|
+
const uiRoot = import_node_path12.default.join(contractsRoot, "ui");
|
|
1488
|
+
const apiRoot = import_node_path12.default.join(contractsRoot, "api");
|
|
1489
|
+
const dataRoot = import_node_path12.default.join(contractsRoot, "db");
|
|
1134
1490
|
const [uiFiles, apiFiles, dataFiles] = await Promise.all([
|
|
1135
1491
|
collectUiContractFiles(uiRoot),
|
|
1136
1492
|
collectApiContractFiles(apiRoot),
|
|
@@ -1149,7 +1505,7 @@ async function buildContractIndex(root, config) {
|
|
|
1149
1505
|
}
|
|
1150
1506
|
async function indexUiContracts(files, index) {
|
|
1151
1507
|
for (const file of files) {
|
|
1152
|
-
const text = await (0,
|
|
1508
|
+
const text = await (0, import_promises10.readFile)(file, "utf-8");
|
|
1153
1509
|
try {
|
|
1154
1510
|
const doc = parseStructuredContract(file, text);
|
|
1155
1511
|
extractUiContractIds(doc).forEach((id) => record(index, id, file));
|
|
@@ -1161,7 +1517,7 @@ async function indexUiContracts(files, index) {
|
|
|
1161
1517
|
}
|
|
1162
1518
|
async function indexApiContracts(files, index) {
|
|
1163
1519
|
for (const file of files) {
|
|
1164
|
-
const text = await (0,
|
|
1520
|
+
const text = await (0, import_promises10.readFile)(file, "utf-8");
|
|
1165
1521
|
try {
|
|
1166
1522
|
const doc = parseStructuredContract(file, text);
|
|
1167
1523
|
extractApiContractIds(doc).forEach((id) => record(index, id, file));
|
|
@@ -1173,7 +1529,7 @@ async function indexApiContracts(files, index) {
|
|
|
1173
1529
|
}
|
|
1174
1530
|
async function indexDataContracts(files, index) {
|
|
1175
1531
|
for (const file of files) {
|
|
1176
|
-
const text = await (0,
|
|
1532
|
+
const text = await (0, import_promises10.readFile)(file, "utf-8");
|
|
1177
1533
|
extractIds(text, "DATA").forEach((id) => record(index, id, file));
|
|
1178
1534
|
}
|
|
1179
1535
|
}
|
|
@@ -1221,243 +1577,89 @@ function extractH2Sections(md) {
|
|
|
1221
1577
|
endLine,
|
|
1222
1578
|
body
|
|
1223
1579
|
});
|
|
1224
|
-
}
|
|
1225
|
-
return sections;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
// src/core/parse/spec.ts
|
|
1229
|
-
var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
|
|
1230
|
-
var BR_LINE_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[0-3])\]\s*(.+)$/;
|
|
1231
|
-
var BR_LINE_ANY_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[^\]]+)\]\s*(.+)$/;
|
|
1232
|
-
var BR_LINE_NO_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\](?!\s*\[P)\s*(.*\S.*)$/;
|
|
1233
|
-
var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
|
|
1234
|
-
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
|
|
1235
|
-
function parseSpec(md, file) {
|
|
1236
|
-
const headings = parseHeadings(md);
|
|
1237
|
-
const h1 = headings.find((heading) => heading.level === 1);
|
|
1238
|
-
const specId = h1?.title.match(SPEC_ID_RE)?.[0];
|
|
1239
|
-
const sections = extractH2Sections(md);
|
|
1240
|
-
const sectionNames = new Set(Array.from(sections.keys()));
|
|
1241
|
-
const brSection = sections.get(BR_SECTION_TITLE);
|
|
1242
|
-
const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
|
|
1243
|
-
const startLine = brSection?.startLine ?? 1;
|
|
1244
|
-
const brs = [];
|
|
1245
|
-
const brsWithoutPriority = [];
|
|
1246
|
-
const brsWithInvalidPriority = [];
|
|
1247
|
-
for (let i = 0; i < brLines.length; i++) {
|
|
1248
|
-
const lineText = brLines[i] ?? "";
|
|
1249
|
-
const lineNumber = startLine + i;
|
|
1250
|
-
const validMatch = lineText.match(BR_LINE_RE);
|
|
1251
|
-
if (validMatch) {
|
|
1252
|
-
const id = validMatch[1];
|
|
1253
|
-
const priority = validMatch[2];
|
|
1254
|
-
const text = validMatch[3];
|
|
1255
|
-
if (!id || !priority || !text) continue;
|
|
1256
|
-
brs.push({
|
|
1257
|
-
id,
|
|
1258
|
-
priority,
|
|
1259
|
-
text: text.trim(),
|
|
1260
|
-
line: lineNumber
|
|
1261
|
-
});
|
|
1262
|
-
continue;
|
|
1263
|
-
}
|
|
1264
|
-
const anyPriorityMatch = lineText.match(BR_LINE_ANY_PRIORITY_RE);
|
|
1265
|
-
if (anyPriorityMatch) {
|
|
1266
|
-
const id = anyPriorityMatch[1];
|
|
1267
|
-
const priority = anyPriorityMatch[2];
|
|
1268
|
-
const text = anyPriorityMatch[3];
|
|
1269
|
-
if (!id || !priority || !text) continue;
|
|
1270
|
-
if (!VALID_PRIORITIES.has(priority)) {
|
|
1271
|
-
brsWithInvalidPriority.push({
|
|
1272
|
-
id,
|
|
1273
|
-
priority,
|
|
1274
|
-
text: text.trim(),
|
|
1275
|
-
line: lineNumber
|
|
1276
|
-
});
|
|
1277
|
-
}
|
|
1278
|
-
continue;
|
|
1279
|
-
}
|
|
1280
|
-
const noPriorityMatch = lineText.match(BR_LINE_NO_PRIORITY_RE);
|
|
1281
|
-
if (noPriorityMatch) {
|
|
1282
|
-
const id = noPriorityMatch[1];
|
|
1283
|
-
const text = noPriorityMatch[2];
|
|
1284
|
-
if (!id || !text) continue;
|
|
1285
|
-
brsWithoutPriority.push({
|
|
1286
|
-
id,
|
|
1287
|
-
text: text.trim(),
|
|
1288
|
-
line: lineNumber
|
|
1289
|
-
});
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
const parsed = {
|
|
1293
|
-
file,
|
|
1294
|
-
sections: sectionNames,
|
|
1295
|
-
brs,
|
|
1296
|
-
brsWithoutPriority,
|
|
1297
|
-
brsWithInvalidPriority
|
|
1298
|
-
};
|
|
1299
|
-
if (specId) {
|
|
1300
|
-
parsed.specId = specId;
|
|
1301
|
-
}
|
|
1302
|
-
return parsed;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
// src/core/gherkin/parse.ts
|
|
1306
|
-
var import_gherkin = require("@cucumber/gherkin");
|
|
1307
|
-
var import_node_crypto = require("crypto");
|
|
1308
|
-
function parseGherkin(source, uri) {
|
|
1309
|
-
const errors = [];
|
|
1310
|
-
const uuidFn = () => (0, import_node_crypto.randomUUID)();
|
|
1311
|
-
const builder = new import_gherkin.AstBuilder(uuidFn);
|
|
1312
|
-
const matcher = new import_gherkin.GherkinClassicTokenMatcher();
|
|
1313
|
-
const parser = new import_gherkin.Parser(builder, matcher);
|
|
1314
|
-
try {
|
|
1315
|
-
const gherkinDocument = parser.parse(source);
|
|
1316
|
-
gherkinDocument.uri = uri;
|
|
1317
|
-
return { gherkinDocument, errors };
|
|
1318
|
-
} catch (error2) {
|
|
1319
|
-
errors.push(formatError3(error2));
|
|
1320
|
-
return { gherkinDocument: null, errors };
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
function formatError3(error2) {
|
|
1324
|
-
if (error2 instanceof Error) {
|
|
1325
|
-
return error2.message;
|
|
1326
|
-
}
|
|
1327
|
-
return String(error2);
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
// src/core/scenarioModel.ts
|
|
1331
|
-
var SPEC_TAG_RE = /^SPEC-\d{4}$/;
|
|
1332
|
-
var SC_TAG_RE = /^SC-\d{4}$/;
|
|
1333
|
-
var BR_TAG_RE = /^BR-\d{4}$/;
|
|
1334
|
-
var UI_TAG_RE = /^UI-\d{4}$/;
|
|
1335
|
-
var API_TAG_RE = /^API-\d{4}$/;
|
|
1336
|
-
var DATA_TAG_RE = /^DATA-\d{4}$/;
|
|
1337
|
-
function parseScenarioDocument(text, uri) {
|
|
1338
|
-
const { gherkinDocument, errors } = parseGherkin(text, uri);
|
|
1339
|
-
if (!gherkinDocument) {
|
|
1340
|
-
return { document: null, errors };
|
|
1341
|
-
}
|
|
1342
|
-
const feature = gherkinDocument.feature;
|
|
1343
|
-
if (!feature) {
|
|
1344
|
-
return {
|
|
1345
|
-
document: { uri, featureTags: [], scenarios: [] },
|
|
1346
|
-
errors
|
|
1347
|
-
};
|
|
1348
|
-
}
|
|
1349
|
-
const featureTags = collectTagNames(feature.tags);
|
|
1350
|
-
const scenarios = collectScenarioNodes(feature, featureTags);
|
|
1351
|
-
return {
|
|
1352
|
-
document: {
|
|
1353
|
-
uri,
|
|
1354
|
-
featureName: feature.name,
|
|
1355
|
-
featureTags,
|
|
1356
|
-
scenarios
|
|
1357
|
-
},
|
|
1358
|
-
errors
|
|
1359
|
-
};
|
|
1360
|
-
}
|
|
1361
|
-
function buildScenarioAtoms(document) {
|
|
1362
|
-
return document.scenarios.map((scenario) => {
|
|
1363
|
-
const specIds = scenario.tags.filter((tag) => SPEC_TAG_RE.test(tag));
|
|
1364
|
-
const scIds = scenario.tags.filter((tag) => SC_TAG_RE.test(tag));
|
|
1365
|
-
const brIds = unique2(scenario.tags.filter((tag) => BR_TAG_RE.test(tag)));
|
|
1366
|
-
const contractIds = /* @__PURE__ */ new Set();
|
|
1367
|
-
scenario.tags.forEach((tag) => {
|
|
1368
|
-
if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) || DATA_TAG_RE.test(tag)) {
|
|
1369
|
-
contractIds.add(tag);
|
|
1370
|
-
}
|
|
1371
|
-
});
|
|
1372
|
-
for (const step of scenario.steps) {
|
|
1373
|
-
for (const text of collectStepTexts(step)) {
|
|
1374
|
-
extractIds(text, "UI").forEach((id) => contractIds.add(id));
|
|
1375
|
-
extractIds(text, "API").forEach((id) => contractIds.add(id));
|
|
1376
|
-
extractIds(text, "DATA").forEach((id) => contractIds.add(id));
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
const atom = {
|
|
1380
|
-
uri: document.uri,
|
|
1381
|
-
featureName: document.featureName ?? "",
|
|
1382
|
-
scenarioName: scenario.name,
|
|
1383
|
-
kind: scenario.kind,
|
|
1384
|
-
brIds,
|
|
1385
|
-
contractIds: Array.from(contractIds).sort()
|
|
1386
|
-
};
|
|
1387
|
-
if (scenario.line !== void 0) {
|
|
1388
|
-
atom.line = scenario.line;
|
|
1389
|
-
}
|
|
1390
|
-
if (specIds.length === 1) {
|
|
1391
|
-
const specId = specIds[0];
|
|
1392
|
-
if (specId) {
|
|
1393
|
-
atom.specId = specId;
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
if (scIds.length === 1) {
|
|
1397
|
-
const scId = scIds[0];
|
|
1398
|
-
if (scId) {
|
|
1399
|
-
atom.scId = scId;
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
return atom;
|
|
1403
|
-
});
|
|
1580
|
+
}
|
|
1581
|
+
return sections;
|
|
1404
1582
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1583
|
+
|
|
1584
|
+
// src/core/parse/spec.ts
|
|
1585
|
+
var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
|
|
1586
|
+
var BR_LINE_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[0-3])\]\s*(.+)$/;
|
|
1587
|
+
var BR_LINE_ANY_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[^\]]+)\]\s*(.+)$/;
|
|
1588
|
+
var BR_LINE_NO_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\](?!\s*\[P)\s*(.*\S.*)$/;
|
|
1589
|
+
var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
|
|
1590
|
+
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
|
|
1591
|
+
function parseSpec(md, file) {
|
|
1592
|
+
const headings = parseHeadings(md);
|
|
1593
|
+
const h1 = headings.find((heading) => heading.level === 1);
|
|
1594
|
+
const specId = h1?.title.match(SPEC_ID_RE)?.[0];
|
|
1595
|
+
const sections = extractH2Sections(md);
|
|
1596
|
+
const sectionNames = new Set(Array.from(sections.keys()));
|
|
1597
|
+
const brSection = sections.get(BR_SECTION_TITLE);
|
|
1598
|
+
const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
|
|
1599
|
+
const startLine = brSection?.startLine ?? 1;
|
|
1600
|
+
const brs = [];
|
|
1601
|
+
const brsWithoutPriority = [];
|
|
1602
|
+
const brsWithInvalidPriority = [];
|
|
1603
|
+
for (let i = 0; i < brLines.length; i++) {
|
|
1604
|
+
const lineText = brLines[i] ?? "";
|
|
1605
|
+
const lineNumber = startLine + i;
|
|
1606
|
+
const validMatch = lineText.match(BR_LINE_RE);
|
|
1607
|
+
if (validMatch) {
|
|
1608
|
+
const id = validMatch[1];
|
|
1609
|
+
const priority = validMatch[2];
|
|
1610
|
+
const text = validMatch[3];
|
|
1611
|
+
if (!id || !priority || !text) continue;
|
|
1612
|
+
brs.push({
|
|
1613
|
+
id,
|
|
1614
|
+
priority,
|
|
1615
|
+
text: text.trim(),
|
|
1616
|
+
line: lineNumber
|
|
1617
|
+
});
|
|
1618
|
+
continue;
|
|
1410
1619
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1620
|
+
const anyPriorityMatch = lineText.match(BR_LINE_ANY_PRIORITY_RE);
|
|
1621
|
+
if (anyPriorityMatch) {
|
|
1622
|
+
const id = anyPriorityMatch[1];
|
|
1623
|
+
const priority = anyPriorityMatch[2];
|
|
1624
|
+
const text = anyPriorityMatch[3];
|
|
1625
|
+
if (!id || !priority || !text) continue;
|
|
1626
|
+
if (!VALID_PRIORITIES.has(priority)) {
|
|
1627
|
+
brsWithInvalidPriority.push({
|
|
1628
|
+
id,
|
|
1629
|
+
priority,
|
|
1630
|
+
text: text.trim(),
|
|
1631
|
+
line: lineNumber
|
|
1632
|
+
});
|
|
1419
1633
|
}
|
|
1634
|
+
continue;
|
|
1635
|
+
}
|
|
1636
|
+
const noPriorityMatch = lineText.match(BR_LINE_NO_PRIORITY_RE);
|
|
1637
|
+
if (noPriorityMatch) {
|
|
1638
|
+
const id = noPriorityMatch[1];
|
|
1639
|
+
const text = noPriorityMatch[2];
|
|
1640
|
+
if (!id || !text) continue;
|
|
1641
|
+
brsWithoutPriority.push({
|
|
1642
|
+
id,
|
|
1643
|
+
text: text.trim(),
|
|
1644
|
+
line: lineNumber
|
|
1645
|
+
});
|
|
1420
1646
|
}
|
|
1421
1647
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
name: scenario.name,
|
|
1429
|
-
kind,
|
|
1430
|
-
line: scenario.location?.line,
|
|
1431
|
-
tags,
|
|
1432
|
-
steps: scenario.steps
|
|
1648
|
+
const parsed = {
|
|
1649
|
+
file,
|
|
1650
|
+
sections: sectionNames,
|
|
1651
|
+
brs,
|
|
1652
|
+
brsWithoutPriority,
|
|
1653
|
+
brsWithInvalidPriority
|
|
1433
1654
|
};
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
return tags.map((tag) => tag.name.replace(/^@/, ""));
|
|
1437
|
-
}
|
|
1438
|
-
function collectStepTexts(step) {
|
|
1439
|
-
const texts = [];
|
|
1440
|
-
if (step.text) {
|
|
1441
|
-
texts.push(step.text);
|
|
1442
|
-
}
|
|
1443
|
-
if (step.docString?.content) {
|
|
1444
|
-
texts.push(step.docString.content);
|
|
1445
|
-
}
|
|
1446
|
-
if (step.dataTable?.rows) {
|
|
1447
|
-
for (const row of step.dataTable.rows) {
|
|
1448
|
-
for (const cell of row.cells) {
|
|
1449
|
-
texts.push(cell.value);
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1655
|
+
if (specId) {
|
|
1656
|
+
parsed.specId = specId;
|
|
1452
1657
|
}
|
|
1453
|
-
return
|
|
1454
|
-
}
|
|
1455
|
-
function unique2(values) {
|
|
1456
|
-
return Array.from(new Set(values));
|
|
1658
|
+
return parsed;
|
|
1457
1659
|
}
|
|
1458
1660
|
|
|
1459
1661
|
// src/core/validators/ids.ts
|
|
1460
|
-
var
|
|
1662
|
+
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
1461
1663
|
async function validateDefinedIds(root, config) {
|
|
1462
1664
|
const issues = [];
|
|
1463
1665
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
@@ -1491,7 +1693,7 @@ async function validateDefinedIds(root, config) {
|
|
|
1491
1693
|
}
|
|
1492
1694
|
async function collectSpecDefinitionIds(files, out) {
|
|
1493
1695
|
for (const file of files) {
|
|
1494
|
-
const text = await (0,
|
|
1696
|
+
const text = await (0, import_promises11.readFile)(file, "utf-8");
|
|
1495
1697
|
const parsed = parseSpec(text, file);
|
|
1496
1698
|
if (parsed.specId) {
|
|
1497
1699
|
recordId(out, parsed.specId, file);
|
|
@@ -1501,14 +1703,14 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
1501
1703
|
}
|
|
1502
1704
|
async function collectScenarioDefinitionIds(files, out) {
|
|
1503
1705
|
for (const file of files) {
|
|
1504
|
-
const text = await (0,
|
|
1706
|
+
const text = await (0, import_promises11.readFile)(file, "utf-8");
|
|
1505
1707
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
1506
1708
|
if (!document || errors.length > 0) {
|
|
1507
1709
|
continue;
|
|
1508
1710
|
}
|
|
1509
1711
|
for (const scenario of document.scenarios) {
|
|
1510
1712
|
for (const tag of scenario.tags) {
|
|
1511
|
-
if (
|
|
1713
|
+
if (SC_TAG_RE3.test(tag)) {
|
|
1512
1714
|
recordId(out, tag, file);
|
|
1513
1715
|
}
|
|
1514
1716
|
}
|
|
@@ -1522,7 +1724,7 @@ function recordId(out, id, file) {
|
|
|
1522
1724
|
}
|
|
1523
1725
|
function formatFileList(files, root) {
|
|
1524
1726
|
return files.map((file) => {
|
|
1525
|
-
const relative =
|
|
1727
|
+
const relative = import_node_path13.default.relative(root, file);
|
|
1526
1728
|
return relative.length > 0 ? relative : file;
|
|
1527
1729
|
}).join(", ");
|
|
1528
1730
|
}
|
|
@@ -1545,13 +1747,12 @@ function issue3(code, message, severity, file, rule, refs) {
|
|
|
1545
1747
|
}
|
|
1546
1748
|
|
|
1547
1749
|
// src/core/validators/scenario.ts
|
|
1548
|
-
var
|
|
1750
|
+
var import_promises12 = require("fs/promises");
|
|
1549
1751
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
1550
1752
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
1551
1753
|
var THEN_PATTERN = /\bThen\b/;
|
|
1552
|
-
var
|
|
1754
|
+
var SC_TAG_RE4 = /^SC-\d{4}$/;
|
|
1553
1755
|
var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
|
|
1554
|
-
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
1555
1756
|
async function validateScenarios(root, config) {
|
|
1556
1757
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1557
1758
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -1572,7 +1773,7 @@ async function validateScenarios(root, config) {
|
|
|
1572
1773
|
for (const entry of entries) {
|
|
1573
1774
|
let text;
|
|
1574
1775
|
try {
|
|
1575
|
-
text = await (0,
|
|
1776
|
+
text = await (0, import_promises12.readFile)(entry.scenarioPath, "utf-8");
|
|
1576
1777
|
} catch (error2) {
|
|
1577
1778
|
if (isMissingFileError3(error2)) {
|
|
1578
1779
|
issues.push(
|
|
@@ -1631,17 +1832,7 @@ function validateScenarioContent(text, file) {
|
|
|
1631
1832
|
const featureSpecTags = document.featureTags.filter(
|
|
1632
1833
|
(tag) => SPEC_TAG_RE2.test(tag)
|
|
1633
1834
|
);
|
|
1634
|
-
if (featureSpecTags.length
|
|
1635
|
-
issues.push(
|
|
1636
|
-
issue4(
|
|
1637
|
-
"QFAI-SC-009",
|
|
1638
|
-
"Feature \u30BF\u30B0\u306B SPEC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1639
|
-
"error",
|
|
1640
|
-
file,
|
|
1641
|
-
"scenario.featureSpec"
|
|
1642
|
-
)
|
|
1643
|
-
);
|
|
1644
|
-
} else if (featureSpecTags.length > 1) {
|
|
1835
|
+
if (featureSpecTags.length > 1) {
|
|
1645
1836
|
issues.push(
|
|
1646
1837
|
issue4(
|
|
1647
1838
|
"QFAI-SC-009",
|
|
@@ -1683,18 +1874,12 @@ function validateScenarioContent(text, file) {
|
|
|
1683
1874
|
continue;
|
|
1684
1875
|
}
|
|
1685
1876
|
const missingTags = [];
|
|
1686
|
-
const scTags = scenario.tags.filter((tag) =>
|
|
1877
|
+
const scTags = scenario.tags.filter((tag) => SC_TAG_RE4.test(tag));
|
|
1687
1878
|
if (scTags.length === 0) {
|
|
1688
1879
|
missingTags.push("SC(0\u4EF6)");
|
|
1689
1880
|
} else if (scTags.length > 1) {
|
|
1690
1881
|
missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
|
|
1691
1882
|
}
|
|
1692
|
-
if (!scenario.tags.some((tag) => SPEC_TAG_RE2.test(tag))) {
|
|
1693
|
-
missingTags.push("SPEC");
|
|
1694
|
-
}
|
|
1695
|
-
if (!scenario.tags.some((tag) => BR_TAG_RE2.test(tag))) {
|
|
1696
|
-
missingTags.push("BR");
|
|
1697
|
-
}
|
|
1698
1883
|
if (missingTags.length > 0) {
|
|
1699
1884
|
issues.push(
|
|
1700
1885
|
issue4(
|
|
@@ -1758,7 +1943,7 @@ function isMissingFileError3(error2) {
|
|
|
1758
1943
|
}
|
|
1759
1944
|
|
|
1760
1945
|
// src/core/validators/spec.ts
|
|
1761
|
-
var
|
|
1946
|
+
var import_promises13 = require("fs/promises");
|
|
1762
1947
|
async function validateSpecs(root, config) {
|
|
1763
1948
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1764
1949
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -1779,7 +1964,7 @@ async function validateSpecs(root, config) {
|
|
|
1779
1964
|
for (const entry of entries) {
|
|
1780
1965
|
let text;
|
|
1781
1966
|
try {
|
|
1782
|
-
text = await (0,
|
|
1967
|
+
text = await (0, import_promises13.readFile)(entry.specPath, "utf-8");
|
|
1783
1968
|
} catch (error2) {
|
|
1784
1969
|
if (isMissingFileError4(error2)) {
|
|
1785
1970
|
issues.push(
|
|
@@ -1928,10 +2113,9 @@ function isMissingFileError4(error2) {
|
|
|
1928
2113
|
}
|
|
1929
2114
|
|
|
1930
2115
|
// src/core/validators/traceability.ts
|
|
1931
|
-
var
|
|
1932
|
-
var SC_TAG_RE4 = /^SC-\d{4}$/;
|
|
2116
|
+
var import_promises14 = require("fs/promises");
|
|
1933
2117
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
1934
|
-
var
|
|
2118
|
+
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
1935
2119
|
async function validateTraceability(root, config) {
|
|
1936
2120
|
const issues = [];
|
|
1937
2121
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
@@ -1950,7 +2134,7 @@ async function validateTraceability(root, config) {
|
|
|
1950
2134
|
const contractIndex = await buildContractIndex(root, config);
|
|
1951
2135
|
const contractIds = contractIndex.ids;
|
|
1952
2136
|
for (const file of specFiles) {
|
|
1953
|
-
const text = await (0,
|
|
2137
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
1954
2138
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1955
2139
|
const parsed = parseSpec(text, file);
|
|
1956
2140
|
if (parsed.specId) {
|
|
@@ -1958,28 +2142,6 @@ async function validateTraceability(root, config) {
|
|
|
1958
2142
|
}
|
|
1959
2143
|
const brIds = parsed.brs.map((br) => br.id);
|
|
1960
2144
|
brIds.forEach((id) => brIdsInSpecs.add(id));
|
|
1961
|
-
const referencedContractIds = /* @__PURE__ */ new Set([
|
|
1962
|
-
...extractIds(text, "UI"),
|
|
1963
|
-
...extractIds(text, "API"),
|
|
1964
|
-
...extractIds(text, "DATA")
|
|
1965
|
-
]);
|
|
1966
|
-
const unknownContractIds = Array.from(referencedContractIds).filter(
|
|
1967
|
-
(id) => !contractIds.has(id)
|
|
1968
|
-
);
|
|
1969
|
-
if (unknownContractIds.length > 0) {
|
|
1970
|
-
issues.push(
|
|
1971
|
-
issue6(
|
|
1972
|
-
"QFAI-TRACE-009",
|
|
1973
|
-
`Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
1974
|
-
", "
|
|
1975
|
-
)}`,
|
|
1976
|
-
"error",
|
|
1977
|
-
file,
|
|
1978
|
-
"traceability.specContractExists",
|
|
1979
|
-
unknownContractIds
|
|
1980
|
-
)
|
|
1981
|
-
);
|
|
1982
|
-
}
|
|
1983
2145
|
if (parsed.specId) {
|
|
1984
2146
|
const current = specToBrIds.get(parsed.specId) ?? /* @__PURE__ */ new Set();
|
|
1985
2147
|
brIds.forEach((id) => current.add(id));
|
|
@@ -1987,23 +2149,49 @@ async function validateTraceability(root, config) {
|
|
|
1987
2149
|
}
|
|
1988
2150
|
}
|
|
1989
2151
|
for (const file of scenarioFiles) {
|
|
1990
|
-
const text = await (0,
|
|
2152
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
1991
2153
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1992
2154
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
1993
2155
|
if (!document || errors.length > 0) {
|
|
1994
2156
|
continue;
|
|
1995
2157
|
}
|
|
1996
2158
|
const atoms = buildScenarioAtoms(document);
|
|
2159
|
+
const scIdsInFile = /* @__PURE__ */ new Set();
|
|
1997
2160
|
for (const [index, scenario] of document.scenarios.entries()) {
|
|
1998
2161
|
const atom = atoms[index];
|
|
1999
2162
|
if (!atom) {
|
|
2000
2163
|
continue;
|
|
2001
2164
|
}
|
|
2002
2165
|
const specTags = scenario.tags.filter((tag) => SPEC_TAG_RE3.test(tag));
|
|
2003
|
-
const brTags = scenario.tags.filter((tag) =>
|
|
2004
|
-
const scTags = scenario.tags.filter((tag) =>
|
|
2166
|
+
const brTags = scenario.tags.filter((tag) => BR_TAG_RE2.test(tag));
|
|
2167
|
+
const scTags = scenario.tags.filter((tag) => SC_TAG_RE2.test(tag));
|
|
2168
|
+
if (specTags.length === 0) {
|
|
2169
|
+
issues.push(
|
|
2170
|
+
issue6(
|
|
2171
|
+
"QFAI-TRACE-014",
|
|
2172
|
+
`Scenario \u304C SPEC \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
|
|
2173
|
+
"error",
|
|
2174
|
+
file,
|
|
2175
|
+
"traceability.scenarioSpecRequired"
|
|
2176
|
+
)
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
if (brTags.length === 0) {
|
|
2180
|
+
issues.push(
|
|
2181
|
+
issue6(
|
|
2182
|
+
"QFAI-TRACE-015",
|
|
2183
|
+
`Scenario \u304C BR \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
|
|
2184
|
+
"error",
|
|
2185
|
+
file,
|
|
2186
|
+
"traceability.scenarioBrRequired"
|
|
2187
|
+
)
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2005
2190
|
brTags.forEach((id) => brIdsInScenarios.add(id));
|
|
2006
|
-
scTags.forEach((id) =>
|
|
2191
|
+
scTags.forEach((id) => {
|
|
2192
|
+
scIdsInScenarios.add(id);
|
|
2193
|
+
scIdsInFile.add(id);
|
|
2194
|
+
});
|
|
2007
2195
|
atom.contractIds.forEach((id) => scenarioContractIds.add(id));
|
|
2008
2196
|
if (atom.contractIds.length > 0) {
|
|
2009
2197
|
scTags.forEach((id) => scWithContracts.add(id));
|
|
@@ -2081,6 +2269,22 @@ async function validateTraceability(root, config) {
|
|
|
2081
2269
|
}
|
|
2082
2270
|
}
|
|
2083
2271
|
}
|
|
2272
|
+
if (scIdsInFile.size !== 1) {
|
|
2273
|
+
const invalidScIds = Array.from(scIdsInFile).sort(
|
|
2274
|
+
(a, b) => a.localeCompare(b)
|
|
2275
|
+
);
|
|
2276
|
+
const detail = invalidScIds.length === 0 ? "SC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093" : `\u8907\u6570\u306E SC \u304C\u5B58\u5728\u3057\u307E\u3059: ${invalidScIds.join(", ")}`;
|
|
2277
|
+
issues.push(
|
|
2278
|
+
issue6(
|
|
2279
|
+
"QFAI-TRACE-012",
|
|
2280
|
+
`Spec entry \u304C Spec:SC=1:1 \u3092\u6E80\u305F\u3057\u3066\u3044\u307E\u305B\u3093: ${detail}`,
|
|
2281
|
+
"error",
|
|
2282
|
+
file,
|
|
2283
|
+
"traceability.specScOneToOne",
|
|
2284
|
+
invalidScIds
|
|
2285
|
+
)
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2084
2288
|
}
|
|
2085
2289
|
if (upstreamIds.size === 0) {
|
|
2086
2290
|
return [
|
|
@@ -2129,6 +2333,66 @@ async function validateTraceability(root, config) {
|
|
|
2129
2333
|
);
|
|
2130
2334
|
}
|
|
2131
2335
|
}
|
|
2336
|
+
const scRefsResult = await collectScTestReferences(
|
|
2337
|
+
root,
|
|
2338
|
+
config.validation.traceability.testFileGlobs,
|
|
2339
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2340
|
+
);
|
|
2341
|
+
const scTestRefs = scRefsResult.refs;
|
|
2342
|
+
const testFileScan = scRefsResult.scan;
|
|
2343
|
+
const hasScenarios = scIdsInScenarios.size > 0;
|
|
2344
|
+
const hasGlobConfig = testFileScan.globs.length > 0;
|
|
2345
|
+
const hasMatchedTests = testFileScan.matchedFileCount > 0;
|
|
2346
|
+
if (hasScenarios && (!hasGlobConfig || !hasMatchedTests || scRefsResult.error)) {
|
|
2347
|
+
const detail = scRefsResult.error ? `\uFF08\u8A73\u7D30: ${scRefsResult.error}\uFF09` : "";
|
|
2348
|
+
issues.push(
|
|
2349
|
+
issue6(
|
|
2350
|
+
"QFAI-TRACE-013",
|
|
2351
|
+
`\u30C6\u30B9\u30C8\u63A2\u7D22 glob \u304C\u672A\u8A2D\u5B9A/\u4E0D\u6B63/\u4E00\u81F4\u30D5\u30A1\u30A4\u30EB0\u306E\u305F\u3081 SC\u2192Test \u3092\u5224\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002${detail}`,
|
|
2352
|
+
"error",
|
|
2353
|
+
testsRoot,
|
|
2354
|
+
"traceability.testFileGlobs"
|
|
2355
|
+
)
|
|
2356
|
+
);
|
|
2357
|
+
} else {
|
|
2358
|
+
if (config.validation.traceability.scMustHaveTest && scIdsInScenarios.size) {
|
|
2359
|
+
const scWithoutTests = Array.from(scIdsInScenarios).filter((id) => {
|
|
2360
|
+
const refs = scTestRefs.get(id);
|
|
2361
|
+
return !refs || refs.size === 0;
|
|
2362
|
+
});
|
|
2363
|
+
if (scWithoutTests.length > 0) {
|
|
2364
|
+
issues.push(
|
|
2365
|
+
issue6(
|
|
2366
|
+
"QFAI-TRACE-010",
|
|
2367
|
+
`SC \u304C\u30C6\u30B9\u30C8\u3067\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${scWithoutTests.join(
|
|
2368
|
+
", "
|
|
2369
|
+
)}\u3002testFileGlobs \u306B\u4E00\u81F4\u3059\u308B\u30C6\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB\u3078 QFAI:SC-xxxx \u3092\u8A18\u8F09\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
|
|
2370
|
+
config.validation.traceability.scNoTestSeverity,
|
|
2371
|
+
testsRoot,
|
|
2372
|
+
"traceability.scMustHaveTest",
|
|
2373
|
+
scWithoutTests
|
|
2374
|
+
)
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
const unknownScIds = Array.from(scTestRefs.keys()).filter(
|
|
2379
|
+
(id) => !scIdsInScenarios.has(id)
|
|
2380
|
+
);
|
|
2381
|
+
if (unknownScIds.length > 0) {
|
|
2382
|
+
issues.push(
|
|
2383
|
+
issue6(
|
|
2384
|
+
"QFAI-TRACE-011",
|
|
2385
|
+
`\u30C6\u30B9\u30C8\u304C\u672A\u77E5\u306E SC \u3092\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownScIds.join(
|
|
2386
|
+
", "
|
|
2387
|
+
)}`,
|
|
2388
|
+
"error",
|
|
2389
|
+
testsRoot,
|
|
2390
|
+
"traceability.scUnknownInTests",
|
|
2391
|
+
unknownScIds
|
|
2392
|
+
)
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2132
2396
|
if (!config.validation.traceability.allowOrphanContracts) {
|
|
2133
2397
|
if (contractIds.size > 0) {
|
|
2134
2398
|
const orphanContracts = Array.from(contractIds).filter(
|
|
@@ -2177,7 +2441,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
2177
2441
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
2178
2442
|
let found = false;
|
|
2179
2443
|
for (const file of targetFiles) {
|
|
2180
|
-
const text = await (0,
|
|
2444
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
2181
2445
|
if (pattern.test(text)) {
|
|
2182
2446
|
found = true;
|
|
2183
2447
|
break;
|
|
@@ -2187,8 +2451,8 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
2187
2451
|
issues.push(
|
|
2188
2452
|
issue6(
|
|
2189
2453
|
"QFAI-TRACE-002",
|
|
2190
|
-
"\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
2191
|
-
"
|
|
2454
|
+
"\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\uFF08\u53C2\u8003\u60C5\u5831\uFF09\u3002",
|
|
2455
|
+
"info",
|
|
2192
2456
|
srcRoot,
|
|
2193
2457
|
"traceability.codeReferences"
|
|
2194
2458
|
)
|
|
@@ -2231,12 +2495,24 @@ async function validateProject(root, configResult) {
|
|
|
2231
2495
|
...await validateDefinedIds(root, config),
|
|
2232
2496
|
...await validateTraceability(root, config)
|
|
2233
2497
|
];
|
|
2498
|
+
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2499
|
+
const scenarioFiles = await collectScenarioFiles(specsRoot);
|
|
2500
|
+
const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
|
|
2501
|
+
const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
|
|
2502
|
+
root,
|
|
2503
|
+
config.validation.traceability.testFileGlobs,
|
|
2504
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2505
|
+
);
|
|
2506
|
+
const scCoverage = buildScCoverage(scIds, scTestRefs);
|
|
2234
2507
|
const toolVersion = await resolveToolVersion();
|
|
2235
2508
|
return {
|
|
2236
|
-
schemaVersion: VALIDATION_SCHEMA_VERSION,
|
|
2237
2509
|
toolVersion,
|
|
2238
2510
|
issues,
|
|
2239
|
-
counts: countIssues(issues)
|
|
2511
|
+
counts: countIssues(issues),
|
|
2512
|
+
traceability: {
|
|
2513
|
+
sc: scCoverage,
|
|
2514
|
+
testFiles
|
|
2515
|
+
}
|
|
2240
2516
|
};
|
|
2241
2517
|
}
|
|
2242
2518
|
function countIssues(issues) {
|
|
@@ -2257,9 +2533,9 @@ async function createReportData(root, validation, configResult) {
|
|
|
2257
2533
|
const configPath = resolved.configPath;
|
|
2258
2534
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2259
2535
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
2260
|
-
const apiRoot =
|
|
2261
|
-
const uiRoot =
|
|
2262
|
-
const dbRoot =
|
|
2536
|
+
const apiRoot = import_node_path14.default.join(contractsRoot, "api");
|
|
2537
|
+
const uiRoot = import_node_path14.default.join(contractsRoot, "ui");
|
|
2538
|
+
const dbRoot = import_node_path14.default.join(contractsRoot, "db");
|
|
2263
2539
|
const srcRoot = resolvePath(root, config, "srcDir");
|
|
2264
2540
|
const testsRoot = resolvePath(root, config, "testsDir");
|
|
2265
2541
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -2285,6 +2561,16 @@ async function createReportData(root, validation, configResult) {
|
|
|
2285
2561
|
srcRoot,
|
|
2286
2562
|
testsRoot
|
|
2287
2563
|
);
|
|
2564
|
+
const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
|
|
2565
|
+
const scRefsResult = await collectScTestReferences(
|
|
2566
|
+
root,
|
|
2567
|
+
config.validation.traceability.testFileGlobs,
|
|
2568
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2569
|
+
);
|
|
2570
|
+
const scCoverage = validation?.traceability?.sc ?? buildScCoverage(scIds, scRefsResult.refs);
|
|
2571
|
+
const testFiles = validation?.traceability?.testFiles ?? scRefsResult.scan;
|
|
2572
|
+
const scSources = await collectScIdSourcesFromScenarioFiles(scenarioFiles);
|
|
2573
|
+
const scSourceRecord = mapToSortedRecord(scSources);
|
|
2288
2574
|
const resolvedValidation = validation ?? await validateProject(root, resolved);
|
|
2289
2575
|
const version = await resolveToolVersion();
|
|
2290
2576
|
return {
|
|
@@ -2313,7 +2599,10 @@ async function createReportData(root, validation, configResult) {
|
|
|
2313
2599
|
},
|
|
2314
2600
|
traceability: {
|
|
2315
2601
|
upstreamIdsFound: upstreamIds.size,
|
|
2316
|
-
referencedInCodeOrTests: traceability
|
|
2602
|
+
referencedInCodeOrTests: traceability,
|
|
2603
|
+
sc: scCoverage,
|
|
2604
|
+
scSources: scSourceRecord,
|
|
2605
|
+
testFiles
|
|
2317
2606
|
},
|
|
2318
2607
|
issues: resolvedValidation.issues
|
|
2319
2608
|
};
|
|
@@ -2350,6 +2639,65 @@ function formatReportMarkdown(data) {
|
|
|
2350
2639
|
`- \u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u53C2\u7167: ${data.traceability.referencedInCodeOrTests ? "\u3042\u308A" : "\u306A\u3057"}`
|
|
2351
2640
|
);
|
|
2352
2641
|
lines.push("");
|
|
2642
|
+
lines.push("## SC\u30AB\u30D0\u30EC\u30C3\u30B8");
|
|
2643
|
+
lines.push(`- total: ${data.traceability.sc.total}`);
|
|
2644
|
+
lines.push(`- covered: ${data.traceability.sc.covered}`);
|
|
2645
|
+
lines.push(`- missing: ${data.traceability.sc.missing}`);
|
|
2646
|
+
lines.push(
|
|
2647
|
+
`- testFileGlobs: ${formatList(data.traceability.testFiles.globs)}`
|
|
2648
|
+
);
|
|
2649
|
+
lines.push(
|
|
2650
|
+
`- testFileExcludeGlobs: ${formatList(
|
|
2651
|
+
data.traceability.testFiles.excludeGlobs
|
|
2652
|
+
)}`
|
|
2653
|
+
);
|
|
2654
|
+
lines.push(
|
|
2655
|
+
`- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
|
|
2656
|
+
);
|
|
2657
|
+
if (data.traceability.sc.missingIds.length === 0) {
|
|
2658
|
+
lines.push("- missingIds: (none)");
|
|
2659
|
+
} else {
|
|
2660
|
+
const sources = data.traceability.scSources;
|
|
2661
|
+
const missingWithSources = data.traceability.sc.missingIds.map((id) => {
|
|
2662
|
+
const files = sources[id] ?? [];
|
|
2663
|
+
if (files.length === 0) {
|
|
2664
|
+
return id;
|
|
2665
|
+
}
|
|
2666
|
+
return `${id} (${files.join(", ")})`;
|
|
2667
|
+
});
|
|
2668
|
+
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
2669
|
+
}
|
|
2670
|
+
lines.push("");
|
|
2671
|
+
lines.push("## SC\u2192\u53C2\u7167\u30C6\u30B9\u30C8");
|
|
2672
|
+
const scRefs = data.traceability.sc.refs;
|
|
2673
|
+
const scIds = Object.keys(scRefs).sort((a, b) => a.localeCompare(b));
|
|
2674
|
+
if (scIds.length === 0) {
|
|
2675
|
+
lines.push("- (none)");
|
|
2676
|
+
} else {
|
|
2677
|
+
for (const scId of scIds) {
|
|
2678
|
+
const refs = scRefs[scId] ?? [];
|
|
2679
|
+
if (refs.length === 0) {
|
|
2680
|
+
lines.push(`- ${scId}: (none)`);
|
|
2681
|
+
} else {
|
|
2682
|
+
lines.push(`- ${scId}: ${refs.join(", ")}`);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
lines.push("");
|
|
2687
|
+
lines.push("## Spec:SC=1:1 \u9055\u53CD");
|
|
2688
|
+
const specScIssues = data.issues.filter(
|
|
2689
|
+
(item) => item.code === "QFAI-TRACE-012"
|
|
2690
|
+
);
|
|
2691
|
+
if (specScIssues.length === 0) {
|
|
2692
|
+
lines.push("- (none)");
|
|
2693
|
+
} else {
|
|
2694
|
+
for (const item of specScIssues) {
|
|
2695
|
+
const location = item.file ?? "(unknown)";
|
|
2696
|
+
const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
|
|
2697
|
+
lines.push(`- ${location}: ${refs}`);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
lines.push("");
|
|
2353
2701
|
lines.push("## Hotspots");
|
|
2354
2702
|
const hotspots = buildHotspots(data.issues);
|
|
2355
2703
|
if (hotspots.length === 0) {
|
|
@@ -2404,25 +2752,25 @@ async function collectIds(files) {
|
|
|
2404
2752
|
DATA: /* @__PURE__ */ new Set()
|
|
2405
2753
|
};
|
|
2406
2754
|
for (const file of files) {
|
|
2407
|
-
const text = await (0,
|
|
2755
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
2408
2756
|
for (const prefix of ID_PREFIXES2) {
|
|
2409
2757
|
const ids = extractIds(text, prefix);
|
|
2410
2758
|
ids.forEach((id) => result[prefix].add(id));
|
|
2411
2759
|
}
|
|
2412
2760
|
}
|
|
2413
2761
|
return {
|
|
2414
|
-
SPEC:
|
|
2415
|
-
BR:
|
|
2416
|
-
SC:
|
|
2417
|
-
UI:
|
|
2418
|
-
API:
|
|
2419
|
-
DATA:
|
|
2762
|
+
SPEC: toSortedArray2(result.SPEC),
|
|
2763
|
+
BR: toSortedArray2(result.BR),
|
|
2764
|
+
SC: toSortedArray2(result.SC),
|
|
2765
|
+
UI: toSortedArray2(result.UI),
|
|
2766
|
+
API: toSortedArray2(result.API),
|
|
2767
|
+
DATA: toSortedArray2(result.DATA)
|
|
2420
2768
|
};
|
|
2421
2769
|
}
|
|
2422
2770
|
async function collectUpstreamIds(files) {
|
|
2423
2771
|
const ids = /* @__PURE__ */ new Set();
|
|
2424
2772
|
for (const file of files) {
|
|
2425
|
-
const text = await (0,
|
|
2773
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
2426
2774
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
2427
2775
|
}
|
|
2428
2776
|
return ids;
|
|
@@ -2443,7 +2791,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
2443
2791
|
}
|
|
2444
2792
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
2445
2793
|
for (const file of targetFiles) {
|
|
2446
|
-
const text = await (0,
|
|
2794
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
2447
2795
|
if (pattern.test(text)) {
|
|
2448
2796
|
return true;
|
|
2449
2797
|
}
|
|
@@ -2460,9 +2808,22 @@ function formatIdLine(label, values) {
|
|
|
2460
2808
|
}
|
|
2461
2809
|
return `- ${label}: ${values.join(", ")}`;
|
|
2462
2810
|
}
|
|
2463
|
-
function
|
|
2811
|
+
function formatList(values) {
|
|
2812
|
+
if (values.length === 0) {
|
|
2813
|
+
return "(none)";
|
|
2814
|
+
}
|
|
2815
|
+
return values.join(", ");
|
|
2816
|
+
}
|
|
2817
|
+
function toSortedArray2(values) {
|
|
2464
2818
|
return Array.from(values).sort((a, b) => a.localeCompare(b));
|
|
2465
2819
|
}
|
|
2820
|
+
function mapToSortedRecord(values) {
|
|
2821
|
+
const record2 = {};
|
|
2822
|
+
for (const [key, files] of values.entries()) {
|
|
2823
|
+
record2[key] = Array.from(files).sort((a, b) => a.localeCompare(b));
|
|
2824
|
+
}
|
|
2825
|
+
return record2;
|
|
2826
|
+
}
|
|
2466
2827
|
function buildHotspots(issues) {
|
|
2467
2828
|
const map = /* @__PURE__ */ new Map();
|
|
2468
2829
|
for (const issue7 of issues) {
|
|
@@ -2487,10 +2848,10 @@ function buildHotspots(issues) {
|
|
|
2487
2848
|
|
|
2488
2849
|
// src/cli/commands/report.ts
|
|
2489
2850
|
async function runReport(options) {
|
|
2490
|
-
const root =
|
|
2851
|
+
const root = import_node_path15.default.resolve(options.root);
|
|
2491
2852
|
const configResult = await loadConfig(root);
|
|
2492
2853
|
const input = configResult.config.output.validateJsonPath;
|
|
2493
|
-
const inputPath =
|
|
2854
|
+
const inputPath = import_node_path15.default.isAbsolute(input) ? input : import_node_path15.default.resolve(root, input);
|
|
2494
2855
|
let validation;
|
|
2495
2856
|
try {
|
|
2496
2857
|
validation = await readValidationResult(inputPath);
|
|
@@ -2515,11 +2876,11 @@ async function runReport(options) {
|
|
|
2515
2876
|
const data = await createReportData(root, validation, configResult);
|
|
2516
2877
|
const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
|
|
2517
2878
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
2518
|
-
const defaultOut = options.format === "json" ?
|
|
2879
|
+
const defaultOut = options.format === "json" ? import_node_path15.default.join(outRoot, "report.json") : import_node_path15.default.join(outRoot, "report.md");
|
|
2519
2880
|
const out = options.outPath ?? defaultOut;
|
|
2520
|
-
const outPath =
|
|
2521
|
-
await (0,
|
|
2522
|
-
await (0,
|
|
2881
|
+
const outPath = import_node_path15.default.isAbsolute(out) ? out : import_node_path15.default.resolve(root, out);
|
|
2882
|
+
await (0, import_promises16.mkdir)(import_node_path15.default.dirname(outPath), { recursive: true });
|
|
2883
|
+
await (0, import_promises16.writeFile)(outPath, `${output}
|
|
2523
2884
|
`, "utf-8");
|
|
2524
2885
|
info(
|
|
2525
2886
|
`report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
|
|
@@ -2527,16 +2888,11 @@ async function runReport(options) {
|
|
|
2527
2888
|
info(`wrote report: ${outPath}`);
|
|
2528
2889
|
}
|
|
2529
2890
|
async function readValidationResult(inputPath) {
|
|
2530
|
-
const raw = await (0,
|
|
2891
|
+
const raw = await (0, import_promises16.readFile)(inputPath, "utf-8");
|
|
2531
2892
|
const parsed = JSON.parse(raw);
|
|
2532
2893
|
if (!isValidationResult(parsed)) {
|
|
2533
2894
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
2534
2895
|
}
|
|
2535
|
-
if (parsed.schemaVersion !== VALIDATION_SCHEMA_VERSION) {
|
|
2536
|
-
throw new Error(
|
|
2537
|
-
`validate.json \u306E schemaVersion \u304C\u4E0D\u4E00\u81F4\u3067\u3059: expected ${VALIDATION_SCHEMA_VERSION}, actual ${parsed.schemaVersion}`
|
|
2538
|
-
);
|
|
2539
|
-
}
|
|
2540
2896
|
return parsed;
|
|
2541
2897
|
}
|
|
2542
2898
|
function isValidationResult(value) {
|
|
@@ -2544,9 +2900,6 @@ function isValidationResult(value) {
|
|
|
2544
2900
|
return false;
|
|
2545
2901
|
}
|
|
2546
2902
|
const record2 = value;
|
|
2547
|
-
if (typeof record2.schemaVersion !== "string") {
|
|
2548
|
-
return false;
|
|
2549
|
-
}
|
|
2550
2903
|
if (typeof record2.toolVersion !== "string") {
|
|
2551
2904
|
return false;
|
|
2552
2905
|
}
|
|
@@ -2568,8 +2921,8 @@ function isMissingFileError5(error2) {
|
|
|
2568
2921
|
}
|
|
2569
2922
|
|
|
2570
2923
|
// src/cli/commands/validate.ts
|
|
2571
|
-
var
|
|
2572
|
-
var
|
|
2924
|
+
var import_promises17 = require("fs/promises");
|
|
2925
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2573
2926
|
|
|
2574
2927
|
// src/cli/lib/failOn.ts
|
|
2575
2928
|
function shouldFail(result, failOn) {
|
|
@@ -2584,7 +2937,7 @@ function shouldFail(result, failOn) {
|
|
|
2584
2937
|
|
|
2585
2938
|
// src/cli/commands/validate.ts
|
|
2586
2939
|
async function runValidate(options) {
|
|
2587
|
-
const root =
|
|
2940
|
+
const root = import_node_path16.default.resolve(options.root);
|
|
2588
2941
|
const configResult = await loadConfig(root);
|
|
2589
2942
|
const result = await validateProject(root, configResult);
|
|
2590
2943
|
const format = options.format ?? "text";
|
|
@@ -2633,9 +2986,9 @@ function emitGitHub(issue7) {
|
|
|
2633
2986
|
);
|
|
2634
2987
|
}
|
|
2635
2988
|
async function emitJson(result, root, jsonPath) {
|
|
2636
|
-
const abs =
|
|
2637
|
-
await (0,
|
|
2638
|
-
await (0,
|
|
2989
|
+
const abs = import_node_path16.default.isAbsolute(jsonPath) ? jsonPath : import_node_path16.default.resolve(root, jsonPath);
|
|
2990
|
+
await (0, import_promises17.mkdir)(import_node_path16.default.dirname(abs), { recursive: true });
|
|
2991
|
+
await (0, import_promises17.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
2639
2992
|
`, "utf-8");
|
|
2640
2993
|
}
|
|
2641
2994
|
|