diff --git a/package.json b/package.json index 368ebc2e8fc..4e4b49777be 100644 --- a/package.json +++ b/package.json @@ -1324,8 +1324,8 @@ "docs:list": "node scripts/docs-list.js", "docs:spellcheck": "bash scripts/docs-spellcheck.sh", "docs:spellcheck:fix": "bash scripts/docs-spellcheck.sh --write", - "dup:check": "jscpd src extensions scripts packages ui apps qa openclaw.mjs knip.config.ts tsdown.config.ts vitest.config.ts --format typescript,javascript --pattern \"**/*.{ts,tsx,js,mjs,cjs}\" --gitignore --noSymlinks --ignore \"**/*.test.ts,**/*.test.tsx,**/*.test.js,**/*.e2e.test.ts,**/*.live.test.ts,extensions/qa-matrix/src/shared/**,extensions/qa-matrix/src/report.ts,extensions/qa-matrix/src/docker-runtime.ts,extensions/qa-matrix/src/cli-paths.ts,**/node_modules/**,**/dist/**,**/.git/**,**/coverage/**,**/build/**,**/.build/**,**/.artifacts/**,vendor/**\" --min-lines 50 --min-tokens 300 --reporters console", - "dup:check:json": "jscpd src extensions scripts packages ui apps qa openclaw.mjs knip.config.ts tsdown.config.ts vitest.config.ts --format typescript,javascript --pattern \"**/*.{ts,tsx,js,mjs,cjs}\" --gitignore --noSymlinks --ignore \"**/*.test.ts,**/*.test.tsx,**/*.test.js,**/*.e2e.test.ts,**/*.live.test.ts,extensions/qa-matrix/src/shared/**,extensions/qa-matrix/src/report.ts,extensions/qa-matrix/src/docker-runtime.ts,extensions/qa-matrix/src/cli-paths.ts,**/node_modules/**,**/dist/**,**/.git/**,**/coverage/**,**/build/**,**/.build/**,**/.artifacts/**,vendor/**\" --min-lines 50 --min-tokens 300 --reporters json --output .artifacts/jscpd", + "dup:check": "node scripts/check-duplicates.mjs", + "dup:check:json": "node scripts/check-duplicates.mjs --json", "format": "oxfmt --write --threads=1", "format:all": "pnpm format && pnpm format:swift", "format:check": "oxfmt --check --threads=1", diff --git a/scripts/check-duplicates.mjs b/scripts/check-duplicates.mjs new file mode 100644 index 00000000000..ab3cc87209e --- /dev/null +++ b/scripts/check-duplicates.mjs @@ -0,0 +1,112 @@ +#!/usr/bin/env node +import { spawnSync } from "node:child_process"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const jscpdBin = path.join(repoRoot, "node_modules", "jscpd", "bin", "jscpd"); + +const targets = [ + "src", + "extensions", + "scripts", + "packages", + "ui", + "apps", + "qa", + "openclaw.mjs", + "knip.config.ts", + "tsdown.config.ts", + "vitest.config.ts", +]; + +const generatedIgnores = [ + "extensions/qa-matrix/src/shared/**", + "extensions/qa-matrix/src/report.ts", + "extensions/qa-matrix/src/docker-runtime.ts", + "extensions/qa-matrix/src/cli-paths.ts", + "**/node_modules/**", + "**/dist/**", + "**/.git/**", + "**/coverage/**", + "**/build/**", + "**/.build/**", + "**/.artifacts/**", + "vendor/**", +]; + +const testIgnores = [ + "**/*.test.ts", + "**/*.test.tsx", + "**/*.test.js", + "**/*.e2e.test.ts", + "**/*.live.test.ts", +]; + +const commonArgs = [ + "--format", + "typescript,javascript", + "--gitignore", + "--noSymlinks", + "--min-lines", + "50", + "--min-tokens", + "300", +]; + +const json = process.argv.includes("--json"); + +function reportArgs(name) { + if (!json) { + return ["--reporters", "console"]; + } + return ["--reporters", "json", "--output", path.join(".artifacts", "jscpd", name)]; +} + +const scans = [ + { + name: "production", + pattern: "**/*.{ts,tsx,js,mjs,cjs}", + ignore: [...testIgnores, ...generatedIgnores], + }, + { + name: "tests", + pattern: "**/*.{test,e2e.test,live.test}.{ts,tsx,js}", + ignore: generatedIgnores, + }, +]; + +let failed = false; +for (const scan of scans) { + console.log(`\n[dup:check] ${scan.name}`); + const result = spawnSync( + process.execPath, + [ + "--max-old-space-size=8192", + jscpdBin, + ...targets, + ...commonArgs, + "--pattern", + scan.pattern, + "--ignore", + scan.ignore.join(","), + ...reportArgs(scan.name), + ], + { + cwd: repoRoot, + env: process.env, + stdio: "inherit", + }, + ); + if (result.status !== 0) { + failed = true; + } + if (result.error) { + console.error(result.error.message); + failed = true; + } +} + +if (failed) { + process.exit(1); +}