

























@@ -23,6 +23,12 @@ const targets = [
2323"vitest.config.ts",
2424];
252526+const sourceExtensions = new Set([".ts", ".tsx", ".js", ".mjs", ".cjs"]);
27+const sourcePattern = "**/*.{ts,tsx,js,mjs,cjs}";
28+const testPattern = "**/*.{test,e2e.test,live.test}.{ts,tsx,js,mjs,cjs}";
29+// Keep local agent support trees and vendored snapshots classified but outside jscpd.
30+const intentionallyUnscannedPrefixes = [".agents/", ".pi/", "vendor/"];
31+2632const generatedIgnores = [
2733"extensions/qa-matrix/src/shared/**",
2834"extensions/qa-matrix/src/report.ts",
@@ -42,8 +48,18 @@ const testIgnores = [
4248"**/*.test.ts",
4349"**/*.test.tsx",
4450"**/*.test.js",
51+"**/*.test.mjs",
52+"**/*.test.cjs",
4553"**/*.e2e.test.ts",
54+"**/*.e2e.test.tsx",
55+"**/*.e2e.test.js",
56+"**/*.e2e.test.mjs",
57+"**/*.e2e.test.cjs",
4658"**/*.live.test.ts",
59+"**/*.live.test.tsx",
60+"**/*.live.test.js",
61+"**/*.live.test.mjs",
62+"**/*.live.test.cjs",
4763];
48644965const commonArgs = [
@@ -58,6 +74,58 @@ const commonArgs = [
5874];
59756076const json = process.argv.includes("--json");
77+const coverageOnly = process.argv.includes("--coverage");
78+79+function normalizeRepoPath(value) {
80+return value.split(path.sep).join("/");
81+}
82+83+function isUnderPrefix(value, prefix) {
84+return value === prefix.slice(0, -1) || value.startsWith(prefix);
85+}
86+87+function isCoveredByTargets(file) {
88+return targets.some((target) => {
89+const normalizedTarget = normalizeRepoPath(target);
90+if (file === normalizedTarget) {
91+return true;
92+}
93+return file.startsWith(`${normalizedTarget}/`);
94+});
95+}
96+97+function listTrackedSourceFiles() {
98+const result = spawnSync("git", ["ls-files", "-z"], {
99+cwd: repoRoot,
100+encoding: "utf8",
101+maxBuffer: 64 * 1024 * 1024,
102+});
103+if (result.status !== 0) {
104+throw new Error(result.stderr || "git ls-files failed");
105+}
106+return result.stdout
107+.split("\0")
108+.filter(Boolean)
109+.map(normalizeRepoPath)
110+.filter((file) => sourceExtensions.has(path.extname(file)))
111+.filter((file) => !intentionallyUnscannedPrefixes.some((prefix) => isUnderPrefix(file, prefix)))
112+.toSorted((left, right) => left.localeCompare(right));
113+}
114+115+function assertTargetCoverage() {
116+const uncovered = listTrackedSourceFiles().filter((file) => !isCoveredByTargets(file));
117+if (uncovered.length === 0) {
118+console.log(`[dup:check] target coverage ok`);
119+return true;
120+}
121+console.error(
122+"[dup:check] tracked duplicate-scan source files are outside scan targets or intentional excludes:",
123+);
124+for (const file of uncovered) {
125+console.error(` - ${file}`);
126+}
127+return false;
128+}
6112962130function reportArgs(name) {
63131if (!json) {
@@ -70,36 +138,39 @@ const scans = [
70138{
71139name: "production",
72140 targets,
73-pattern: "**/*.{ts,tsx,js,mjs,cjs}",
141+pattern: sourcePattern,
74142ignore: [...testIgnores, ...generatedIgnores],
75143},
76144{
77145name: "tests",
78146 targets,
79-pattern: "**/*.{test,e2e.test,live.test}.{ts,tsx,js}",
147+pattern: testPattern,
80148ignore: generatedIgnores,
81149},
82150{
83151name: "src-mixed",
84152targets: ["src"],
85-pattern: "**/*.{ts,tsx,js,mjs,cjs}",
153+pattern: sourcePattern,
86154ignore: generatedIgnores,
87155},
88156{
89157name: "extensions-mixed",
90158targets: ["extensions"],
91-pattern: "**/*.{ts,tsx,js,mjs,cjs}",
159+pattern: sourcePattern,
92160ignore: generatedIgnores,
93161},
94162{
95163name: "test-mixed",
96164targets: ["test"],
97-pattern: "**/*.{ts,tsx,js,mjs,cjs}",
165+pattern: sourcePattern,
98166ignore: generatedIgnores,
99167},
100168];
101169102-let failed = false;
170+let failed = !assertTargetCoverage();
171+if (coverageOnly) {
172+process.exit(failed ? 1 : 0);
173+}
103174for (const scan of scans) {
104175console.log(`\n[dup:check] ${scan.name}`);
105176const result = spawnSync(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。