qfai 0.3.1 → 0.3.3
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 +7 -1
- package/assets/init/.qfai/README.md +2 -0
- package/assets/init/.qfai/prompts/README.md +6 -0
- package/assets/init/.qfai/prompts/require-to-spec.md +39 -0
- package/assets/init/.qfai/rules/pnpm.md +29 -0
- package/assets/init/.qfai/specs/README.md +5 -5
- package/assets/init/root/require/README.md +28 -0
- package/dist/cli/index.cjs +468 -242
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +440 -210
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +441 -232
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +420 -207
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- /package/assets/init/.qfai/specs/{spec-001 → spec-0001}/delta.md +0 -0
- /package/assets/init/.qfai/specs/{spec-001 → spec-0001}/scenario.md +0 -0
- /package/assets/init/.qfai/specs/{spec-001 → spec-0001}/spec.md +0 -0
package/dist/cli/index.mjs
CHANGED
|
@@ -86,12 +86,29 @@ async function exists(target) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// src/cli/lib/assets.ts
|
|
89
|
+
import { existsSync } from "fs";
|
|
89
90
|
import path2 from "path";
|
|
90
91
|
import { fileURLToPath } from "url";
|
|
91
92
|
function getInitAssetsDir() {
|
|
92
93
|
const base = import.meta.url;
|
|
93
94
|
const basePath = base.startsWith("file:") ? fileURLToPath(base) : base;
|
|
94
|
-
|
|
95
|
+
const baseDir = path2.dirname(basePath);
|
|
96
|
+
const candidates = [
|
|
97
|
+
path2.resolve(baseDir, "../../../assets/init"),
|
|
98
|
+
path2.resolve(baseDir, "../../assets/init")
|
|
99
|
+
];
|
|
100
|
+
for (const candidate of candidates) {
|
|
101
|
+
if (existsSync(candidate)) {
|
|
102
|
+
return candidate;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw new Error(
|
|
106
|
+
[
|
|
107
|
+
"init \u7528\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002Template assets not found.",
|
|
108
|
+
"\u78BA\u8A8D\u3057\u305F\u30D1\u30B9 / Checked paths:",
|
|
109
|
+
...candidates.map((candidate) => `- ${candidate}`)
|
|
110
|
+
].join("\n")
|
|
111
|
+
);
|
|
95
112
|
}
|
|
96
113
|
|
|
97
114
|
// src/cli/lib/logger.ts
|
|
@@ -478,7 +495,7 @@ import { readFile as readFile10 } from "fs/promises";
|
|
|
478
495
|
import path13 from "path";
|
|
479
496
|
|
|
480
497
|
// src/core/discovery.ts
|
|
481
|
-
import
|
|
498
|
+
import { access as access3 } from "fs/promises";
|
|
482
499
|
|
|
483
500
|
// src/core/fs.ts
|
|
484
501
|
import { access as access2, readdir as readdir2 } from "fs/promises";
|
|
@@ -535,25 +552,50 @@ async function exists2(target) {
|
|
|
535
552
|
}
|
|
536
553
|
}
|
|
537
554
|
|
|
538
|
-
// src/core/
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
555
|
+
// src/core/specLayout.ts
|
|
556
|
+
import { readdir as readdir3 } from "fs/promises";
|
|
557
|
+
import path6 from "path";
|
|
558
|
+
var SPEC_DIR_RE = /^spec-\d{4}$/;
|
|
559
|
+
async function collectSpecEntries(specsRoot) {
|
|
560
|
+
const dirs = await listSpecDirs(specsRoot);
|
|
561
|
+
const entries = dirs.map((dir) => ({
|
|
562
|
+
dir,
|
|
563
|
+
specPath: path6.join(dir, "spec.md"),
|
|
564
|
+
deltaPath: path6.join(dir, "delta.md"),
|
|
565
|
+
scenarioPath: path6.join(dir, "scenario.md")
|
|
566
|
+
}));
|
|
567
|
+
return entries.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
568
|
+
}
|
|
569
|
+
async function listSpecDirs(specsRoot) {
|
|
570
|
+
try {
|
|
571
|
+
const items = await readdir3(specsRoot, { withFileTypes: true });
|
|
572
|
+
return items.filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => SPEC_DIR_RE.test(name.toLowerCase())).map((name) => path6.join(specsRoot, name));
|
|
573
|
+
} catch (error2) {
|
|
574
|
+
if (isMissingFileError(error2)) {
|
|
575
|
+
return [];
|
|
546
576
|
}
|
|
577
|
+
throw error2;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function isMissingFileError(error2) {
|
|
581
|
+
if (!error2 || typeof error2 !== "object") {
|
|
582
|
+
return false;
|
|
547
583
|
}
|
|
548
|
-
return
|
|
584
|
+
return error2.code === "ENOENT";
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/core/discovery.ts
|
|
588
|
+
async function collectSpecPackDirs(specsRoot) {
|
|
589
|
+
const entries = await collectSpecEntries(specsRoot);
|
|
590
|
+
return entries.map((entry) => entry.dir);
|
|
549
591
|
}
|
|
550
592
|
async function collectSpecFiles(specsRoot) {
|
|
551
|
-
const
|
|
552
|
-
return
|
|
593
|
+
const entries = await collectSpecEntries(specsRoot);
|
|
594
|
+
return filterExisting(entries.map((entry) => entry.specPath));
|
|
553
595
|
}
|
|
554
596
|
async function collectScenarioFiles(specsRoot) {
|
|
555
|
-
const
|
|
556
|
-
return
|
|
597
|
+
const entries = await collectSpecEntries(specsRoot);
|
|
598
|
+
return filterExisting(entries.map((entry) => entry.scenarioPath));
|
|
557
599
|
}
|
|
558
600
|
async function collectUiContractFiles(uiRoot) {
|
|
559
601
|
return collectFiles(uiRoot, { extensions: [".yaml", ".yml"] });
|
|
@@ -572,12 +614,22 @@ async function collectContractFiles(uiRoot, apiRoot, dataRoot) {
|
|
|
572
614
|
]);
|
|
573
615
|
return { ui, api, db };
|
|
574
616
|
}
|
|
575
|
-
function
|
|
576
|
-
|
|
617
|
+
async function filterExisting(files) {
|
|
618
|
+
const existing = [];
|
|
619
|
+
for (const file of files) {
|
|
620
|
+
if (await exists3(file)) {
|
|
621
|
+
existing.push(file);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return existing;
|
|
625
|
+
}
|
|
626
|
+
async function exists3(target) {
|
|
627
|
+
try {
|
|
628
|
+
await access3(target);
|
|
629
|
+
return true;
|
|
630
|
+
} catch {
|
|
577
631
|
return false;
|
|
578
632
|
}
|
|
579
|
-
const dirName = path6.basename(path6.dirname(filePath)).toLowerCase();
|
|
580
|
-
return SPEC_PACK_DIR_PATTERN.test(dirName);
|
|
581
633
|
}
|
|
582
634
|
|
|
583
635
|
// src/core/ids.ts
|
|
@@ -641,8 +693,8 @@ import { readFile as readFile2 } from "fs/promises";
|
|
|
641
693
|
import path7 from "path";
|
|
642
694
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
643
695
|
async function resolveToolVersion() {
|
|
644
|
-
if ("0.3.
|
|
645
|
-
return "0.3.
|
|
696
|
+
if ("0.3.3".length > 0) {
|
|
697
|
+
return "0.3.3";
|
|
646
698
|
}
|
|
647
699
|
try {
|
|
648
700
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -975,7 +1027,7 @@ async function validateDeltas(root, config) {
|
|
|
975
1027
|
try {
|
|
976
1028
|
text = await readFile4(deltaPath, "utf-8");
|
|
977
1029
|
} catch (error2) {
|
|
978
|
-
if (
|
|
1030
|
+
if (isMissingFileError2(error2)) {
|
|
979
1031
|
issues.push(
|
|
980
1032
|
issue2(
|
|
981
1033
|
"QFAI-DELTA-001",
|
|
@@ -1020,7 +1072,7 @@ async function validateDeltas(root, config) {
|
|
|
1020
1072
|
}
|
|
1021
1073
|
return issues;
|
|
1022
1074
|
}
|
|
1023
|
-
function
|
|
1075
|
+
function isMissingFileError2(error2) {
|
|
1024
1076
|
if (!error2 || typeof error2 !== "object") {
|
|
1025
1077
|
return false;
|
|
1026
1078
|
}
|
|
@@ -1109,66 +1161,6 @@ function record(index, id, file) {
|
|
|
1109
1161
|
index.idToFiles.set(id, current);
|
|
1110
1162
|
}
|
|
1111
1163
|
|
|
1112
|
-
// src/core/parse/gherkin.ts
|
|
1113
|
-
var FEATURE_RE = /^\s*Feature:\s+/;
|
|
1114
|
-
var SCENARIO_RE = /^\s*Scenario(?: Outline)?:\s*(.+)\s*$/;
|
|
1115
|
-
var TAG_LINE_RE = /^\s*@/;
|
|
1116
|
-
function parseTags(line) {
|
|
1117
|
-
return line.trim().split(/\s+/).filter((tag) => tag.startsWith("@")).map((tag) => tag.replace(/^@/, ""));
|
|
1118
|
-
}
|
|
1119
|
-
function parseGherkinFeature(text, file) {
|
|
1120
|
-
const lines = text.split(/\r?\n/);
|
|
1121
|
-
const scenarios = [];
|
|
1122
|
-
let featurePresent = false;
|
|
1123
|
-
let featureTags = [];
|
|
1124
|
-
let pendingTags = [];
|
|
1125
|
-
let current = null;
|
|
1126
|
-
const flush = () => {
|
|
1127
|
-
if (!current) return;
|
|
1128
|
-
scenarios.push({
|
|
1129
|
-
...current,
|
|
1130
|
-
body: current.body.trim()
|
|
1131
|
-
});
|
|
1132
|
-
current = null;
|
|
1133
|
-
};
|
|
1134
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1135
|
-
const line = lines[i] ?? "";
|
|
1136
|
-
const trimmed = line.trim();
|
|
1137
|
-
if (TAG_LINE_RE.test(trimmed)) {
|
|
1138
|
-
pendingTags.push(...parseTags(trimmed));
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
if (FEATURE_RE.test(trimmed)) {
|
|
1142
|
-
featurePresent = true;
|
|
1143
|
-
featureTags = [...pendingTags];
|
|
1144
|
-
pendingTags = [];
|
|
1145
|
-
continue;
|
|
1146
|
-
}
|
|
1147
|
-
const match = trimmed.match(SCENARIO_RE);
|
|
1148
|
-
if (match) {
|
|
1149
|
-
const scenarioName = match[1]?.trim();
|
|
1150
|
-
if (!scenarioName) {
|
|
1151
|
-
continue;
|
|
1152
|
-
}
|
|
1153
|
-
flush();
|
|
1154
|
-
current = {
|
|
1155
|
-
name: scenarioName,
|
|
1156
|
-
line: i + 1,
|
|
1157
|
-
tags: [...featureTags, ...pendingTags],
|
|
1158
|
-
body: ""
|
|
1159
|
-
};
|
|
1160
|
-
pendingTags = [];
|
|
1161
|
-
continue;
|
|
1162
|
-
}
|
|
1163
|
-
if (current) {
|
|
1164
|
-
current.body += `${line}
|
|
1165
|
-
`;
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
flush();
|
|
1169
|
-
return { file, featurePresent, scenarios };
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
1164
|
// src/core/parse/markdown.ts
|
|
1173
1165
|
var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
1174
1166
|
function parseHeadings(md) {
|
|
@@ -1287,8 +1279,166 @@ function parseSpec(md, file) {
|
|
|
1287
1279
|
return parsed;
|
|
1288
1280
|
}
|
|
1289
1281
|
|
|
1290
|
-
// src/core/
|
|
1282
|
+
// src/core/gherkin/parse.ts
|
|
1283
|
+
import {
|
|
1284
|
+
AstBuilder,
|
|
1285
|
+
GherkinClassicTokenMatcher,
|
|
1286
|
+
Parser
|
|
1287
|
+
} from "@cucumber/gherkin";
|
|
1288
|
+
import { randomUUID } from "crypto";
|
|
1289
|
+
function parseGherkin(source, uri) {
|
|
1290
|
+
const errors = [];
|
|
1291
|
+
const uuidFn = () => randomUUID();
|
|
1292
|
+
const builder = new AstBuilder(uuidFn);
|
|
1293
|
+
const matcher = new GherkinClassicTokenMatcher();
|
|
1294
|
+
const parser = new Parser(builder, matcher);
|
|
1295
|
+
try {
|
|
1296
|
+
const gherkinDocument = parser.parse(source);
|
|
1297
|
+
gherkinDocument.uri = uri;
|
|
1298
|
+
return { gherkinDocument, errors };
|
|
1299
|
+
} catch (error2) {
|
|
1300
|
+
errors.push(formatError3(error2));
|
|
1301
|
+
return { gherkinDocument: null, errors };
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
function formatError3(error2) {
|
|
1305
|
+
if (error2 instanceof Error) {
|
|
1306
|
+
return error2.message;
|
|
1307
|
+
}
|
|
1308
|
+
return String(error2);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/core/scenarioModel.ts
|
|
1312
|
+
var SPEC_TAG_RE = /^SPEC-\d{4}$/;
|
|
1291
1313
|
var SC_TAG_RE = /^SC-\d{4}$/;
|
|
1314
|
+
var BR_TAG_RE = /^BR-\d{4}$/;
|
|
1315
|
+
var UI_TAG_RE = /^UI-\d{4}$/;
|
|
1316
|
+
var API_TAG_RE = /^API-\d{4}$/;
|
|
1317
|
+
var DATA_TAG_RE = /^DATA-\d{4}$/;
|
|
1318
|
+
function parseScenarioDocument(text, uri) {
|
|
1319
|
+
const { gherkinDocument, errors } = parseGherkin(text, uri);
|
|
1320
|
+
if (!gherkinDocument) {
|
|
1321
|
+
return { document: null, errors };
|
|
1322
|
+
}
|
|
1323
|
+
const feature = gherkinDocument.feature;
|
|
1324
|
+
if (!feature) {
|
|
1325
|
+
return {
|
|
1326
|
+
document: { uri, featureTags: [], scenarios: [] },
|
|
1327
|
+
errors
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
const featureTags = collectTagNames(feature.tags);
|
|
1331
|
+
const scenarios = collectScenarioNodes(feature, featureTags);
|
|
1332
|
+
return {
|
|
1333
|
+
document: {
|
|
1334
|
+
uri,
|
|
1335
|
+
featureName: feature.name,
|
|
1336
|
+
featureTags,
|
|
1337
|
+
scenarios
|
|
1338
|
+
},
|
|
1339
|
+
errors
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
function buildScenarioAtoms(document) {
|
|
1343
|
+
return document.scenarios.map((scenario) => {
|
|
1344
|
+
const specIds = scenario.tags.filter((tag) => SPEC_TAG_RE.test(tag));
|
|
1345
|
+
const scIds = scenario.tags.filter((tag) => SC_TAG_RE.test(tag));
|
|
1346
|
+
const brIds = unique2(scenario.tags.filter((tag) => BR_TAG_RE.test(tag)));
|
|
1347
|
+
const contractIds = /* @__PURE__ */ new Set();
|
|
1348
|
+
scenario.tags.forEach((tag) => {
|
|
1349
|
+
if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) || DATA_TAG_RE.test(tag)) {
|
|
1350
|
+
contractIds.add(tag);
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
for (const step of scenario.steps) {
|
|
1354
|
+
for (const text of collectStepTexts(step)) {
|
|
1355
|
+
extractIds(text, "UI").forEach((id) => contractIds.add(id));
|
|
1356
|
+
extractIds(text, "API").forEach((id) => contractIds.add(id));
|
|
1357
|
+
extractIds(text, "DATA").forEach((id) => contractIds.add(id));
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
const atom = {
|
|
1361
|
+
uri: document.uri,
|
|
1362
|
+
featureName: document.featureName ?? "",
|
|
1363
|
+
scenarioName: scenario.name,
|
|
1364
|
+
kind: scenario.kind,
|
|
1365
|
+
brIds,
|
|
1366
|
+
contractIds: Array.from(contractIds).sort()
|
|
1367
|
+
};
|
|
1368
|
+
if (scenario.line !== void 0) {
|
|
1369
|
+
atom.line = scenario.line;
|
|
1370
|
+
}
|
|
1371
|
+
if (specIds.length === 1) {
|
|
1372
|
+
const specId = specIds[0];
|
|
1373
|
+
if (specId) {
|
|
1374
|
+
atom.specId = specId;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (scIds.length === 1) {
|
|
1378
|
+
const scId = scIds[0];
|
|
1379
|
+
if (scId) {
|
|
1380
|
+
atom.scId = scId;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
return atom;
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
function collectScenarioNodes(feature, featureTags) {
|
|
1387
|
+
const scenarios = [];
|
|
1388
|
+
for (const child of feature.children) {
|
|
1389
|
+
if (child.scenario) {
|
|
1390
|
+
scenarios.push(buildScenarioNode(child.scenario, featureTags, []));
|
|
1391
|
+
}
|
|
1392
|
+
if (child.rule) {
|
|
1393
|
+
const ruleTags = collectTagNames(child.rule.tags);
|
|
1394
|
+
for (const ruleChild of child.rule.children) {
|
|
1395
|
+
if (ruleChild.scenario) {
|
|
1396
|
+
scenarios.push(
|
|
1397
|
+
buildScenarioNode(ruleChild.scenario, featureTags, ruleTags)
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
return scenarios;
|
|
1404
|
+
}
|
|
1405
|
+
function buildScenarioNode(scenario, featureTags, ruleTags) {
|
|
1406
|
+
const tags = [...featureTags, ...ruleTags, ...collectTagNames(scenario.tags)];
|
|
1407
|
+
const kind = scenario.examples.length > 0 ? "ScenarioOutline" : "Scenario";
|
|
1408
|
+
return {
|
|
1409
|
+
name: scenario.name,
|
|
1410
|
+
kind,
|
|
1411
|
+
line: scenario.location?.line,
|
|
1412
|
+
tags,
|
|
1413
|
+
steps: scenario.steps
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
function collectTagNames(tags) {
|
|
1417
|
+
return tags.map((tag) => tag.name.replace(/^@/, ""));
|
|
1418
|
+
}
|
|
1419
|
+
function collectStepTexts(step) {
|
|
1420
|
+
const texts = [];
|
|
1421
|
+
if (step.text) {
|
|
1422
|
+
texts.push(step.text);
|
|
1423
|
+
}
|
|
1424
|
+
if (step.docString?.content) {
|
|
1425
|
+
texts.push(step.docString.content);
|
|
1426
|
+
}
|
|
1427
|
+
if (step.dataTable?.rows) {
|
|
1428
|
+
for (const row of step.dataTable.rows) {
|
|
1429
|
+
for (const cell of row.cells) {
|
|
1430
|
+
texts.push(cell.value);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
return texts;
|
|
1435
|
+
}
|
|
1436
|
+
function unique2(values) {
|
|
1437
|
+
return Array.from(new Set(values));
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/core/validators/ids.ts
|
|
1441
|
+
var SC_TAG_RE2 = /^SC-\d{4}$/;
|
|
1292
1442
|
async function validateDefinedIds(root, config) {
|
|
1293
1443
|
const issues = [];
|
|
1294
1444
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
@@ -1333,10 +1483,13 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
1333
1483
|
async function collectScenarioDefinitionIds(files, out) {
|
|
1334
1484
|
for (const file of files) {
|
|
1335
1485
|
const text = await readFile6(file, "utf-8");
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1486
|
+
const { document, errors } = parseScenarioDocument(text, file);
|
|
1487
|
+
if (!document || errors.length > 0) {
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
for (const scenario of document.scenarios) {
|
|
1338
1491
|
for (const tag of scenario.tags) {
|
|
1339
|
-
if (
|
|
1492
|
+
if (SC_TAG_RE2.test(tag)) {
|
|
1340
1493
|
recordId(out, tag, file);
|
|
1341
1494
|
}
|
|
1342
1495
|
}
|
|
@@ -1377,17 +1530,19 @@ import { readFile as readFile7 } from "fs/promises";
|
|
|
1377
1530
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
1378
1531
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
1379
1532
|
var THEN_PATTERN = /\bThen\b/;
|
|
1380
|
-
var
|
|
1381
|
-
var
|
|
1382
|
-
var
|
|
1533
|
+
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
1534
|
+
var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
|
|
1535
|
+
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
1383
1536
|
async function validateScenarios(root, config) {
|
|
1384
1537
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1385
|
-
const
|
|
1386
|
-
if (
|
|
1538
|
+
const entries = await collectSpecEntries(specsRoot);
|
|
1539
|
+
if (entries.length === 0) {
|
|
1540
|
+
const expected = "spec-0001/scenario.md";
|
|
1541
|
+
const legacy = "spec-001/scenario.md";
|
|
1387
1542
|
return [
|
|
1388
1543
|
issue4(
|
|
1389
1544
|
"QFAI-SC-000",
|
|
1390
|
-
|
|
1545
|
+
`Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u914D\u7F6E\u5834\u6240: ${config.paths.specsDir} / \u671F\u5F85\u30D1\u30BF\u30FC\u30F3: ${expected} (${legacy} \u306F\u975E\u5BFE\u5FDC)`,
|
|
1391
1546
|
"info",
|
|
1392
1547
|
specsRoot,
|
|
1393
1548
|
"scenario.files"
|
|
@@ -1395,15 +1550,31 @@ async function validateScenarios(root, config) {
|
|
|
1395
1550
|
];
|
|
1396
1551
|
}
|
|
1397
1552
|
const issues = [];
|
|
1398
|
-
for (const
|
|
1399
|
-
|
|
1400
|
-
|
|
1553
|
+
for (const entry of entries) {
|
|
1554
|
+
let text;
|
|
1555
|
+
try {
|
|
1556
|
+
text = await readFile7(entry.scenarioPath, "utf-8");
|
|
1557
|
+
} catch (error2) {
|
|
1558
|
+
if (isMissingFileError3(error2)) {
|
|
1559
|
+
issues.push(
|
|
1560
|
+
issue4(
|
|
1561
|
+
"QFAI-SC-001",
|
|
1562
|
+
"scenario.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1563
|
+
"error",
|
|
1564
|
+
entry.scenarioPath,
|
|
1565
|
+
"scenario.exists"
|
|
1566
|
+
)
|
|
1567
|
+
);
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
throw error2;
|
|
1571
|
+
}
|
|
1572
|
+
issues.push(...validateScenarioContent(text, entry.scenarioPath));
|
|
1401
1573
|
}
|
|
1402
1574
|
return issues;
|
|
1403
1575
|
}
|
|
1404
1576
|
function validateScenarioContent(text, file) {
|
|
1405
1577
|
const issues = [];
|
|
1406
|
-
const parsed = parseGherkinFeature(text, file);
|
|
1407
1578
|
const invalidIds = extractInvalidIds(text, [
|
|
1408
1579
|
"SPEC",
|
|
1409
1580
|
"BR",
|
|
@@ -1425,9 +1596,47 @@ function validateScenarioContent(text, file) {
|
|
|
1425
1596
|
)
|
|
1426
1597
|
);
|
|
1427
1598
|
}
|
|
1599
|
+
const { document, errors } = parseScenarioDocument(text, file);
|
|
1600
|
+
if (!document || errors.length > 0) {
|
|
1601
|
+
issues.push(
|
|
1602
|
+
issue4(
|
|
1603
|
+
"QFAI-SC-010",
|
|
1604
|
+
`Gherkin \u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${errors.join(", ") || "unknown"}`,
|
|
1605
|
+
"error",
|
|
1606
|
+
file,
|
|
1607
|
+
"scenario.parse"
|
|
1608
|
+
)
|
|
1609
|
+
);
|
|
1610
|
+
return issues;
|
|
1611
|
+
}
|
|
1612
|
+
const featureSpecTags = document.featureTags.filter(
|
|
1613
|
+
(tag) => SPEC_TAG_RE2.test(tag)
|
|
1614
|
+
);
|
|
1615
|
+
if (featureSpecTags.length === 0) {
|
|
1616
|
+
issues.push(
|
|
1617
|
+
issue4(
|
|
1618
|
+
"QFAI-SC-009",
|
|
1619
|
+
"Feature \u30BF\u30B0\u306B SPEC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1620
|
+
"error",
|
|
1621
|
+
file,
|
|
1622
|
+
"scenario.featureSpec"
|
|
1623
|
+
)
|
|
1624
|
+
);
|
|
1625
|
+
} else if (featureSpecTags.length > 1) {
|
|
1626
|
+
issues.push(
|
|
1627
|
+
issue4(
|
|
1628
|
+
"QFAI-SC-009",
|
|
1629
|
+
`Feature \u306E SPEC \u30BF\u30B0\u304C\u8907\u6570\u3042\u308A\u307E\u3059: ${featureSpecTags.join(", ")}`,
|
|
1630
|
+
"error",
|
|
1631
|
+
file,
|
|
1632
|
+
"scenario.featureSpec",
|
|
1633
|
+
featureSpecTags
|
|
1634
|
+
)
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1428
1637
|
const missingStructure = [];
|
|
1429
|
-
if (!
|
|
1430
|
-
if (
|
|
1638
|
+
if (!document.featureName) missingStructure.push("Feature");
|
|
1639
|
+
if (document.scenarios.length === 0) missingStructure.push("Scenario");
|
|
1431
1640
|
if (missingStructure.length > 0) {
|
|
1432
1641
|
issues.push(
|
|
1433
1642
|
issue4(
|
|
@@ -1441,7 +1650,7 @@ function validateScenarioContent(text, file) {
|
|
|
1441
1650
|
)
|
|
1442
1651
|
);
|
|
1443
1652
|
}
|
|
1444
|
-
for (const scenario of
|
|
1653
|
+
for (const scenario of document.scenarios) {
|
|
1445
1654
|
if (scenario.tags.length === 0) {
|
|
1446
1655
|
issues.push(
|
|
1447
1656
|
issue4(
|
|
@@ -1455,16 +1664,16 @@ function validateScenarioContent(text, file) {
|
|
|
1455
1664
|
continue;
|
|
1456
1665
|
}
|
|
1457
1666
|
const missingTags = [];
|
|
1458
|
-
const scTags = scenario.tags.filter((tag) =>
|
|
1667
|
+
const scTags = scenario.tags.filter((tag) => SC_TAG_RE3.test(tag));
|
|
1459
1668
|
if (scTags.length === 0) {
|
|
1460
1669
|
missingTags.push("SC(0\u4EF6)");
|
|
1461
1670
|
} else if (scTags.length > 1) {
|
|
1462
1671
|
missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
|
|
1463
1672
|
}
|
|
1464
|
-
if (!scenario.tags.some((tag) =>
|
|
1673
|
+
if (!scenario.tags.some((tag) => SPEC_TAG_RE2.test(tag))) {
|
|
1465
1674
|
missingTags.push("SPEC");
|
|
1466
1675
|
}
|
|
1467
|
-
if (!scenario.tags.some((tag) =>
|
|
1676
|
+
if (!scenario.tags.some((tag) => BR_TAG_RE2.test(tag))) {
|
|
1468
1677
|
missingTags.push("BR");
|
|
1469
1678
|
}
|
|
1470
1679
|
if (missingTags.length > 0) {
|
|
@@ -1479,15 +1688,16 @@ function validateScenarioContent(text, file) {
|
|
|
1479
1688
|
);
|
|
1480
1689
|
}
|
|
1481
1690
|
}
|
|
1482
|
-
for (const scenario of
|
|
1691
|
+
for (const scenario of document.scenarios) {
|
|
1483
1692
|
const missingSteps = [];
|
|
1484
|
-
|
|
1693
|
+
const keywords = scenario.steps.map((step) => step.keyword.trim());
|
|
1694
|
+
if (!keywords.some((keyword) => GIVEN_PATTERN.test(keyword))) {
|
|
1485
1695
|
missingSteps.push("Given");
|
|
1486
1696
|
}
|
|
1487
|
-
if (!WHEN_PATTERN.test(
|
|
1697
|
+
if (!keywords.some((keyword) => WHEN_PATTERN.test(keyword))) {
|
|
1488
1698
|
missingSteps.push("When");
|
|
1489
1699
|
}
|
|
1490
|
-
if (!THEN_PATTERN.test(
|
|
1700
|
+
if (!keywords.some((keyword) => THEN_PATTERN.test(keyword))) {
|
|
1491
1701
|
missingSteps.push("Then");
|
|
1492
1702
|
}
|
|
1493
1703
|
if (missingSteps.length > 0) {
|
|
@@ -1521,18 +1731,25 @@ function issue4(code, message, severity, file, rule, refs) {
|
|
|
1521
1731
|
}
|
|
1522
1732
|
return issue7;
|
|
1523
1733
|
}
|
|
1734
|
+
function isMissingFileError3(error2) {
|
|
1735
|
+
if (!error2 || typeof error2 !== "object") {
|
|
1736
|
+
return false;
|
|
1737
|
+
}
|
|
1738
|
+
return error2.code === "ENOENT";
|
|
1739
|
+
}
|
|
1524
1740
|
|
|
1525
1741
|
// src/core/validators/spec.ts
|
|
1526
1742
|
import { readFile as readFile8 } from "fs/promises";
|
|
1527
1743
|
async function validateSpecs(root, config) {
|
|
1528
1744
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1529
|
-
const
|
|
1530
|
-
if (
|
|
1531
|
-
const expected = "spec-
|
|
1745
|
+
const entries = await collectSpecEntries(specsRoot);
|
|
1746
|
+
if (entries.length === 0) {
|
|
1747
|
+
const expected = "spec-0001/spec.md";
|
|
1748
|
+
const legacy = "spec-001/spec.md";
|
|
1532
1749
|
return [
|
|
1533
1750
|
issue5(
|
|
1534
1751
|
"QFAI-SPEC-000",
|
|
1535
|
-
`Spec \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u914D\u7F6E\u5834\u6240: ${config.paths.specsDir} / \u671F\u5F85\u30D1\u30BF\u30FC\u30F3: ${expected}`,
|
|
1752
|
+
`Spec \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u914D\u7F6E\u5834\u6240: ${config.paths.specsDir} / \u671F\u5F85\u30D1\u30BF\u30FC\u30F3: ${expected} (${legacy} \u306F\u975E\u5BFE\u5FDC)`,
|
|
1536
1753
|
"info",
|
|
1537
1754
|
specsRoot,
|
|
1538
1755
|
"spec.files"
|
|
@@ -1540,12 +1757,29 @@ async function validateSpecs(root, config) {
|
|
|
1540
1757
|
];
|
|
1541
1758
|
}
|
|
1542
1759
|
const issues = [];
|
|
1543
|
-
for (const
|
|
1544
|
-
|
|
1760
|
+
for (const entry of entries) {
|
|
1761
|
+
let text;
|
|
1762
|
+
try {
|
|
1763
|
+
text = await readFile8(entry.specPath, "utf-8");
|
|
1764
|
+
} catch (error2) {
|
|
1765
|
+
if (isMissingFileError4(error2)) {
|
|
1766
|
+
issues.push(
|
|
1767
|
+
issue5(
|
|
1768
|
+
"QFAI-SPEC-005",
|
|
1769
|
+
"spec.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1770
|
+
"error",
|
|
1771
|
+
entry.specPath,
|
|
1772
|
+
"spec.exists"
|
|
1773
|
+
)
|
|
1774
|
+
);
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
throw error2;
|
|
1778
|
+
}
|
|
1545
1779
|
issues.push(
|
|
1546
1780
|
...validateSpecContent(
|
|
1547
1781
|
text,
|
|
1548
|
-
|
|
1782
|
+
entry.specPath,
|
|
1549
1783
|
config.validation.require.specSections
|
|
1550
1784
|
)
|
|
1551
1785
|
);
|
|
@@ -1667,15 +1901,18 @@ function issue5(code, message, severity, file, rule, refs) {
|
|
|
1667
1901
|
}
|
|
1668
1902
|
return issue7;
|
|
1669
1903
|
}
|
|
1904
|
+
function isMissingFileError4(error2) {
|
|
1905
|
+
if (!error2 || typeof error2 !== "object") {
|
|
1906
|
+
return false;
|
|
1907
|
+
}
|
|
1908
|
+
return error2.code === "ENOENT";
|
|
1909
|
+
}
|
|
1670
1910
|
|
|
1671
1911
|
// src/core/validators/traceability.ts
|
|
1672
1912
|
import { readFile as readFile9 } from "fs/promises";
|
|
1673
|
-
var
|
|
1674
|
-
var
|
|
1675
|
-
var
|
|
1676
|
-
var UI_TAG_RE = /^UI-\d{4}$/;
|
|
1677
|
-
var API_TAG_RE = /^API-\d{4}$/;
|
|
1678
|
-
var DATA_TAG_RE = /^DATA-\d{4}$/;
|
|
1913
|
+
var SC_TAG_RE4 = /^SC-\d{4}$/;
|
|
1914
|
+
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
1915
|
+
var BR_TAG_RE3 = /^BR-\d{4}$/;
|
|
1679
1916
|
async function validateTraceability(root, config) {
|
|
1680
1917
|
const issues = [];
|
|
1681
1918
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
@@ -1733,104 +1970,97 @@ async function validateTraceability(root, config) {
|
|
|
1733
1970
|
for (const file of scenarioFiles) {
|
|
1734
1971
|
const text = await readFile9(file, "utf-8");
|
|
1735
1972
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
const scIds = /* @__PURE__ */ new Set();
|
|
1740
|
-
const scenarioIds = /* @__PURE__ */ new Set();
|
|
1741
|
-
for (const scenario of parsed.scenarios) {
|
|
1742
|
-
for (const tag of scenario.tags) {
|
|
1743
|
-
if (SPEC_TAG_RE2.test(tag)) {
|
|
1744
|
-
specIdsInScenario.add(tag);
|
|
1745
|
-
}
|
|
1746
|
-
if (BR_TAG_RE2.test(tag)) {
|
|
1747
|
-
brIds.add(tag);
|
|
1748
|
-
}
|
|
1749
|
-
if (SC_TAG_RE3.test(tag)) {
|
|
1750
|
-
scIds.add(tag);
|
|
1751
|
-
}
|
|
1752
|
-
if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) || DATA_TAG_RE.test(tag)) {
|
|
1753
|
-
scenarioIds.add(tag);
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
const specIdsList = Array.from(specIdsInScenario);
|
|
1758
|
-
const brIdsList = Array.from(brIds);
|
|
1759
|
-
const scIdsList = Array.from(scIds);
|
|
1760
|
-
const scenarioIdsList = Array.from(scenarioIds);
|
|
1761
|
-
brIdsList.forEach((id) => brIdsInScenarios.add(id));
|
|
1762
|
-
scIdsList.forEach((id) => scIdsInScenarios.add(id));
|
|
1763
|
-
scenarioIdsList.forEach((id) => scenarioContractIds.add(id));
|
|
1764
|
-
if (scenarioIdsList.length > 0) {
|
|
1765
|
-
scIdsList.forEach((id) => scWithContracts.add(id));
|
|
1766
|
-
}
|
|
1767
|
-
const unknownSpecIds = specIdsList.filter((id) => !specIds.has(id));
|
|
1768
|
-
if (unknownSpecIds.length > 0) {
|
|
1769
|
-
issues.push(
|
|
1770
|
-
issue6(
|
|
1771
|
-
"QFAI-TRACE-005",
|
|
1772
|
-
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(", ")}`,
|
|
1773
|
-
"error",
|
|
1774
|
-
file,
|
|
1775
|
-
"traceability.scenarioSpecExists",
|
|
1776
|
-
unknownSpecIds
|
|
1777
|
-
)
|
|
1778
|
-
);
|
|
1779
|
-
}
|
|
1780
|
-
const unknownBrIds = brIdsList.filter((id) => !brIdsInSpecs.has(id));
|
|
1781
|
-
if (unknownBrIds.length > 0) {
|
|
1782
|
-
issues.push(
|
|
1783
|
-
issue6(
|
|
1784
|
-
"QFAI-TRACE-006",
|
|
1785
|
-
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(", ")}`,
|
|
1786
|
-
"error",
|
|
1787
|
-
file,
|
|
1788
|
-
"traceability.scenarioBrExists",
|
|
1789
|
-
unknownBrIds
|
|
1790
|
-
)
|
|
1791
|
-
);
|
|
1792
|
-
}
|
|
1793
|
-
const unknownContractIds = scenarioIdsList.filter(
|
|
1794
|
-
(id) => !contractIds.has(id)
|
|
1795
|
-
);
|
|
1796
|
-
if (unknownContractIds.length > 0) {
|
|
1797
|
-
issues.push(
|
|
1798
|
-
issue6(
|
|
1799
|
-
"QFAI-TRACE-008",
|
|
1800
|
-
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
1801
|
-
", "
|
|
1802
|
-
)}`,
|
|
1803
|
-
config.validation.traceability.unknownContractIdSeverity,
|
|
1804
|
-
file,
|
|
1805
|
-
"traceability.scenarioContractExists",
|
|
1806
|
-
unknownContractIds
|
|
1807
|
-
)
|
|
1808
|
-
);
|
|
1973
|
+
const { document, errors } = parseScenarioDocument(text, file);
|
|
1974
|
+
if (!document || errors.length > 0) {
|
|
1975
|
+
continue;
|
|
1809
1976
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1977
|
+
const atoms = buildScenarioAtoms(document);
|
|
1978
|
+
for (const [index, scenario] of document.scenarios.entries()) {
|
|
1979
|
+
const atom = atoms[index];
|
|
1980
|
+
if (!atom) {
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
const specTags = scenario.tags.filter((tag) => SPEC_TAG_RE3.test(tag));
|
|
1984
|
+
const brTags = scenario.tags.filter((tag) => BR_TAG_RE3.test(tag));
|
|
1985
|
+
const scTags = scenario.tags.filter((tag) => SC_TAG_RE4.test(tag));
|
|
1986
|
+
brTags.forEach((id) => brIdsInScenarios.add(id));
|
|
1987
|
+
scTags.forEach((id) => scIdsInScenarios.add(id));
|
|
1988
|
+
atom.contractIds.forEach((id) => scenarioContractIds.add(id));
|
|
1989
|
+
if (atom.contractIds.length > 0) {
|
|
1990
|
+
scTags.forEach((id) => scWithContracts.add(id));
|
|
1818
1991
|
}
|
|
1819
|
-
const
|
|
1820
|
-
if (
|
|
1992
|
+
const unknownSpecIds = specTags.filter((id) => !specIds.has(id));
|
|
1993
|
+
if (unknownSpecIds.length > 0) {
|
|
1821
1994
|
issues.push(
|
|
1822
1995
|
issue6(
|
|
1823
|
-
"QFAI-TRACE-
|
|
1824
|
-
`Scenario \
|
|
1996
|
+
"QFAI-TRACE-005",
|
|
1997
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(
|
|
1825
1998
|
", "
|
|
1826
|
-
)} (
|
|
1999
|
+
)} (${scenario.name})`,
|
|
1827
2000
|
"error",
|
|
1828
2001
|
file,
|
|
1829
|
-
"traceability.
|
|
1830
|
-
|
|
2002
|
+
"traceability.scenarioSpecExists",
|
|
2003
|
+
unknownSpecIds
|
|
1831
2004
|
)
|
|
1832
2005
|
);
|
|
1833
2006
|
}
|
|
2007
|
+
const unknownBrIds = brTags.filter((id) => !brIdsInSpecs.has(id));
|
|
2008
|
+
if (unknownBrIds.length > 0) {
|
|
2009
|
+
issues.push(
|
|
2010
|
+
issue6(
|
|
2011
|
+
"QFAI-TRACE-006",
|
|
2012
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(
|
|
2013
|
+
", "
|
|
2014
|
+
)} (${scenario.name})`,
|
|
2015
|
+
"error",
|
|
2016
|
+
file,
|
|
2017
|
+
"traceability.scenarioBrExists",
|
|
2018
|
+
unknownBrIds
|
|
2019
|
+
)
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
const unknownContractIds = atom.contractIds.filter(
|
|
2023
|
+
(id) => !contractIds.has(id)
|
|
2024
|
+
);
|
|
2025
|
+
if (unknownContractIds.length > 0) {
|
|
2026
|
+
issues.push(
|
|
2027
|
+
issue6(
|
|
2028
|
+
"QFAI-TRACE-008",
|
|
2029
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
2030
|
+
", "
|
|
2031
|
+
)} (${scenario.name})`,
|
|
2032
|
+
config.validation.traceability.unknownContractIdSeverity,
|
|
2033
|
+
file,
|
|
2034
|
+
"traceability.scenarioContractExists",
|
|
2035
|
+
unknownContractIds
|
|
2036
|
+
)
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
if (specTags.length > 0 && brTags.length > 0) {
|
|
2040
|
+
const allowedBrIds = /* @__PURE__ */ new Set();
|
|
2041
|
+
for (const specId of specTags) {
|
|
2042
|
+
const brIdsForSpec = specToBrIds.get(specId);
|
|
2043
|
+
if (!brIdsForSpec) {
|
|
2044
|
+
continue;
|
|
2045
|
+
}
|
|
2046
|
+
brIdsForSpec.forEach((id) => allowedBrIds.add(id));
|
|
2047
|
+
}
|
|
2048
|
+
const invalidBrIds = brTags.filter((id) => !allowedBrIds.has(id));
|
|
2049
|
+
if (invalidBrIds.length > 0) {
|
|
2050
|
+
issues.push(
|
|
2051
|
+
issue6(
|
|
2052
|
+
"QFAI-TRACE-007",
|
|
2053
|
+
`Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
|
|
2054
|
+
", "
|
|
2055
|
+
)} (SPEC: ${specTags.join(", ")}) (${scenario.name})`,
|
|
2056
|
+
"error",
|
|
2057
|
+
file,
|
|
2058
|
+
"traceability.scenarioBrUnderSpec",
|
|
2059
|
+
invalidBrIds
|
|
2060
|
+
)
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
1834
2064
|
}
|
|
1835
2065
|
}
|
|
1836
2066
|
if (upstreamIds.size === 0) {
|
|
@@ -2246,7 +2476,7 @@ async function runReport(options) {
|
|
|
2246
2476
|
try {
|
|
2247
2477
|
validation = await readValidationResult(inputPath);
|
|
2248
2478
|
} catch (err) {
|
|
2249
|
-
if (
|
|
2479
|
+
if (isMissingFileError5(err)) {
|
|
2250
2480
|
error(
|
|
2251
2481
|
[
|
|
2252
2482
|
`qfai report: \u5165\u529B\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${inputPath}`,
|
|
@@ -2310,7 +2540,7 @@ function isValidationResult(value) {
|
|
|
2310
2540
|
}
|
|
2311
2541
|
return typeof counts.info === "number" && typeof counts.warning === "number" && typeof counts.error === "number";
|
|
2312
2542
|
}
|
|
2313
|
-
function
|
|
2543
|
+
function isMissingFileError5(error2) {
|
|
2314
2544
|
if (!error2 || typeof error2 !== "object") {
|
|
2315
2545
|
return false;
|
|
2316
2546
|
}
|