Skip to content

Commit 138e6ea

Browse files
committed
build(tools): skip JSON/Markdown; normalize SPDX headers safely
1 parent 68ae1b1 commit 138e6ea

2 files changed

Lines changed: 48 additions & 25 deletions

File tree

.eslintrc.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# Copyright (C) 2025 Dimitrios S. Sfyris
2-
# SPDX-License-Identifier: GPL-3.0-or-later
31
{
42
"root": true,
53
"env": { "browser": true, "es2022": true, "node": true, "jest": true },

tools/copyright-headers.mjs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515

1616
import { execSync } from "node:child_process";
1717
import { readFileSync, writeFileSync } from "node:fs";
18-
import { extname } from "node:path";
18+
import { extname, basename } from "node:path";
1919

2020
const args = parseArgs(process.argv.slice(2), {
2121
owner: { type: "string", required: true },
22-
year: { type: "string" }, // force single year
23-
"default-start": { type: "string" }, // fallback start year if git history missing
22+
year: { type: "string" }, // force single year
23+
"default-start": { type: "string" }, // fallback start year if git history missing
2424
"spdx-id": { type: "string", default: "GPL-3.0-or-later" },
2525
"no-range": { type: "boolean", default: false }, // when true, only use single year
26-
check: { type: "boolean", default: false } // dry-run: report missing/mismatch
26+
check: { type: "boolean", default: false } // dry-run: report missing/mismatch
2727
});
2828

2929
const OWNER = args.owner;
@@ -33,6 +33,7 @@ const DEFAULT_START = args["default-start"] ? String(args["default-start"]) : nu
3333
const USE_RANGE = !args["no-range"];
3434
const CURRENT_YEAR = new Date().getFullYear();
3535

36+
/** Comment styles per extension (NO markdown here) */
3637
const EXT_STYLE = {
3738
".js": "block",
3839
".jsx": "block",
@@ -52,9 +53,27 @@ const EXT_STYLE = {
5253
".env": "line",
5354
".env.local": "line",
5455
".env.example": "line",
55-
".txt": "line",
56+
".txt": "line"
5657
};
5758

59+
/** Never touch these extensions / files */
60+
const SKIP_EXT = new Set([".json", ".jsonc", ".json5", ".md"]);
61+
const SKIP_FILES = new Set([
62+
// common JSON configs
63+
".eslintrc.json",
64+
"package.json",
65+
"package-lock.json",
66+
"tsconfig.json",
67+
"jsconfig.json",
68+
".prettierrc",
69+
".prettierrc.json",
70+
".babelrc",
71+
".babelrc.json",
72+
// generated or locky bits (even if extless)
73+
"pnpm-lock.yaml",
74+
"yarn.lock"
75+
]);
76+
5877
function main() {
5978
const files = listTrackedFiles().filter(includeFile);
6079
let missing = 0, updated = 0, unchanged = 0, skipped = 0;
@@ -95,17 +114,30 @@ function listTrackedFiles() {
95114

96115
function includeFile(file) {
97116
if (/^(node_modules|build|dist)\//.test(file)) return false;
117+
98118
const ext = extname(file).toLowerCase();
119+
const base = basename(file);
120+
121+
// hard skips
122+
if (SKIP_EXT.has(ext)) return false;
123+
if (SKIP_FILES.has(base)) return false;
124+
125+
// known styles
99126
if (EXT_STYLE[ext]) return true;
100127

101-
// dotfiles without ext (e.g., .gitignore, .env)
102-
if (/^\./.test(file) && !/\.(png|jpg|jpeg|gif|ico|lock|map|pdf|zip|gz|svgz)$/i.test(file)) return true;
128+
// dotfiles without ext (e.g., .gitignore, .env) — allow texty ones
129+
if (/^\./.test(file) && !/\.(png|jpe?g|gif|ico|lock|map|pdf|zip|gz|svgz|woff2?|ttf|eot|bin|exe)$/i.test(file)) {
130+
// but don't let dotfile JSON/MD sneak through
131+
if (SKIP_EXT.has(ext) || SKIP_FILES.has(base)) return false;
132+
return true;
133+
}
103134

104135
return false;
105136
}
106137

107138
function pickStyle(file) {
108139
const ext = extname(file).toLowerCase();
140+
if (SKIP_EXT.has(ext)) return null;
109141
return EXT_STYLE[ext] || (file.startsWith(".") ? "line" : null);
110142
}
111143

@@ -147,16 +179,14 @@ function formatHeader(lines, style) {
147179
return "<!--\n " + lines.join("\n ") + "\n-->\n";
148180
case "xml":
149181
return "<!--\n " + lines.join("\n ") + "\n-->\n";
150-
case "md":
151-
return "> " + lines[0] + " — " + lines[1] + "\n\n";
152182
case "line":
153183
default:
154184
return "# " + lines[0] + "\n# " + lines[1] + "\n";
155185
}
156186
}
157187

158188
function applyHeader(file, content, header, style) {
159-
// Special-case XML prolog: insert after <?xml ...?>
189+
// XML prolog: insert after <?xml ...?>
160190
if (style === "xml") {
161191
const trimmed = content.trimStart();
162192
if (/^<\?xml\b/.test(trimmed)) {
@@ -166,12 +196,12 @@ function applyHeader(file, content, header, style) {
166196
const withoutHeader = stripExistingHeader(existing);
167197
const newContent = content.slice(0, after) + "\n" + header + withoutHeader;
168198
const hasHeader = existing !== withoutHeader;
169-
const needsUpdate = hasHeader; // we rewrote it
199+
const needsUpdate = hasHeader;
170200
return { hasHeader, needsUpdate, newContent };
171201
}
172202
}
173203

174-
// Special-case shebang for shell or scripts: keep shebang on top
204+
// Shebang: keep it first
175205
if (style === "line" && content.startsWith("#!")) {
176206
const nl = content.indexOf("\n");
177207
const shebang = nl >= 0 ? content.slice(0, nl + 1) : content + "\n";
@@ -185,22 +215,17 @@ function applyHeader(file, content, header, style) {
185215

186216
const withoutHeader = stripExistingHeader(content);
187217
const hasHeader = withoutHeader.length !== content.length;
188-
const needsUpdate = hasHeader; // simplest: if header exists, we replace to be sure
218+
const needsUpdate = hasHeader; // replace if present to normalize
189219
const newContent = header + withoutHeader;
190220
return { hasHeader, needsUpdate, newContent };
191221
}
192222

193223
function stripExistingHeader(content) {
194-
// Remove our recognizable headers at the very top only
224+
// Remove recognizable headers only at the very top
195225
const patterns = [
196-
// block comment
197-
/^\/\*[\s\S]*?\*\/\s*/,
198-
// HTML/XML comment
199-
/^<!--[\s\S]*?-->\s*/,
200-
// line comments (# ...)
201-
/^(?:# .*\n)+/,
202-
// markdown quote
203-
/^(?:> .*\n)+/
226+
/^\/\*[\s\S]*?\*\/\s*/, // block comment
227+
/^<!--[\s\S]*?-->\s*/, // HTML/XML comment
228+
/^(?:# .*\n)+/ // line comments (# ...)
204229
];
205230

206231
let out = content;
@@ -244,7 +269,7 @@ function parseArgs(argv, schema) {
244269
return res;
245270
}
246271

247-
// Top-level execution with try/catch (avoids .catch on undefined if main is sync)
272+
// Top-level execution (works if main is sync or async)
248273
try {
249274
await main();
250275
} catch (e) {

0 commit comments

Comments
 (0)