@cldmv/fix-headers 1.0.0 → 1.1.0
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 +11 -3
- package/index.cjs +4 -4
- package/index.mjs +13 -0
- package/package.json +1 -1
- package/src/cli.mjs +94 -2
- package/src/constants.mjs +13 -0
- package/src/core/file-discovery.mjs +13 -0
- package/src/core/fix-headers.mjs +183 -9
- package/src/detect/project.mjs +8 -18
- package/src/detectors/css.mjs +13 -0
- package/src/detectors/go.mjs +13 -0
- package/src/detectors/html.mjs +13 -0
- package/src/detectors/index.mjs +38 -11
- package/src/detectors/node.mjs +27 -0
- package/src/detectors/php.mjs +13 -0
- package/src/detectors/python.mjs +27 -0
- package/src/detectors/rust.mjs +13 -0
- package/src/detectors/shared.mjs +13 -0
- package/src/detectors/yaml.mjs +79 -0
- package/src/fix-header.mjs +4 -4
- package/src/header/parser.mjs +79 -20
- package/src/header/syntax.mjs +17 -2
- package/src/header/template.mjs +26 -5
- package/src/utils/fs.mjs +13 -0
- package/src/utils/git.mjs +39 -1
- package/src/utils/time.mjs +13 -0
- package/types/src/core/fix-headers.d.mts +36 -39
- package/types/src/detect/project.d.mts +2 -0
- package/types/src/detectors/index.d.mts +24 -6
- package/types/src/detectors/node.d.mts +1 -0
- package/types/src/detectors/python.d.mts +1 -0
- package/types/src/detectors/yaml.d.mts +23 -0
- package/types/src/header/parser.d.mts +4 -2
- package/types/src/header/syntax.d.mts +4 -1
- package/types/src/header/template.d.mts +10 -1
- package/types/src/utils/git.d.mts +4 -1
- package/types/src/utils/time.d.mts +12 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Multi-language source header normalizer for Node.js projects.
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- Auto-detects project type by marker files (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `composer.json`)
|
|
13
|
+
- Auto-detects project type by marker files (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `composer.json`) and YAML files (`.yaml`, `.yml`)
|
|
14
14
|
- Auto-detects author and email from git config/commit history
|
|
15
15
|
- Supports per-run overrides for every detected value
|
|
16
16
|
- Supports folder inclusion and exclusion configuration
|
|
@@ -52,6 +52,9 @@ Common CLI options:
|
|
|
52
52
|
|
|
53
53
|
- `--dry-run`
|
|
54
54
|
- `--json`
|
|
55
|
+
- `--sample-output`
|
|
56
|
+
- `--force-author-update`
|
|
57
|
+
- `--use-gpg-signer-author`
|
|
55
58
|
- `--cwd <path>`
|
|
56
59
|
- `--input <path>`
|
|
57
60
|
- `--include-folder <path>` (repeatable)
|
|
@@ -83,11 +86,12 @@ Important options:
|
|
|
83
86
|
- `cwd?: string` - start directory for project detection
|
|
84
87
|
- `input?: string` - explicit single file or folder path to process
|
|
85
88
|
- `dryRun?: boolean` - compute changes without writing files
|
|
89
|
+
- `sampleOutput?: boolean` - include previous/new header sample text for changed files
|
|
86
90
|
- `configFile?: string` - load JSON options from file (resolved from `cwd`)
|
|
87
91
|
- `includeExtensions?: string[]` - file extensions to process
|
|
88
92
|
- `enabledDetectors?: string[]` - detector ids to enable (defaults to all)
|
|
89
93
|
- `disabledDetectors?: string[]` - detector ids to disable
|
|
90
|
-
- `detectorSyntaxOverrides?: Record<string, { linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }>` - override detector comment syntax tokens
|
|
94
|
+
- `detectorSyntaxOverrides?: Record<string, { linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }>` - override detector comment syntax tokens
|
|
91
95
|
- `includeFolders?: string[]` - project-relative folders to scan
|
|
92
96
|
- `excludeFolders?: string[]` - folder names or relative paths to exclude
|
|
93
97
|
- `projectName?: string`
|
|
@@ -96,6 +100,8 @@ Important options:
|
|
|
96
100
|
- `marker?: string | null`
|
|
97
101
|
- `authorName?: string`
|
|
98
102
|
- `authorEmail?: string`
|
|
103
|
+
- `forceAuthorUpdate?: boolean` - force update `@Author`/`@Email` to detected or overridden current values
|
|
104
|
+
- `useGpgSignerAuthor?: boolean` - use signed-commit UID (`%GS`) for detected `@Author` (includes signer comment when present)
|
|
99
105
|
- `companyName?: string` (default: `Catalyzed Motivation Inc.`)
|
|
100
106
|
- `copyrightStartYear?: number` (default: current year)
|
|
101
107
|
|
|
@@ -115,7 +121,8 @@ const result = await fixHeaders({
|
|
|
115
121
|
blockEnd: " */"
|
|
116
122
|
},
|
|
117
123
|
python: {
|
|
118
|
-
linePrefix: ";;"
|
|
124
|
+
linePrefix: ";;",
|
|
125
|
+
lineSeparator: " "
|
|
119
126
|
}
|
|
120
127
|
},
|
|
121
128
|
projectName: "@scope/my-package",
|
|
@@ -128,6 +135,7 @@ const result = await fixHeaders({
|
|
|
128
135
|
|
|
129
136
|
- `excludeFolders` supports both folder-name and nested path matching.
|
|
130
137
|
- For monorepos, each file resolves metadata from the closest detector config in its parent tree.
|
|
138
|
+
- With `sampleOutput` enabled, each changed file includes `previousValue`, `newValue`, and `detectedValues` in results.
|
|
131
139
|
|
|
132
140
|
## License
|
|
133
141
|
|
package/index.cjs
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* @Project: @cldmv/fix-headers
|
|
3
3
|
* @Filename: /index.cjs
|
|
4
4
|
* @Date: 2026-03-01 13:29:45 -08:00 (1772400585)
|
|
5
|
-
* @Author: Nate
|
|
5
|
+
* @Author: Nate Corcoran <CLDMV>
|
|
6
6
|
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
7
|
* -----
|
|
8
|
-
* @Last modified by: Nate
|
|
9
|
-
* @Last modified time: 2026-03-
|
|
8
|
+
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
10
10
|
* -----
|
|
11
|
-
* @Copyright: Copyright (c)
|
|
11
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
package/index.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /index.mjs
|
|
4
|
+
* @Date: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
5
|
+
* @Author: Nate Corcoran <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
/**
|
|
2
15
|
* @fileoverview ESM shim for the fix-headers package API.
|
|
3
16
|
* @module fix-headers/esm-shim
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @Project: @cldmv/fix-headers
|
|
4
|
+
* @Filename: /src/cli.mjs
|
|
5
|
+
* @Date: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
6
|
+
* @Author: Nate Corcoran <CLDMV>
|
|
7
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
8
|
+
* -----
|
|
9
|
+
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
|
|
10
|
+
* @Last modified time: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
11
|
+
* -----
|
|
12
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
13
|
+
*/
|
|
2
14
|
|
|
3
15
|
import { readFile } from "node:fs/promises";
|
|
4
16
|
import { isAbsolute, join } from "node:path";
|
|
@@ -10,7 +22,7 @@ import fixHeaders from "./fix-header.mjs";
|
|
|
10
22
|
* @module fix-headers/cli
|
|
11
23
|
*/
|
|
12
24
|
|
|
13
|
-
const HELP_TEXT = `fix-headers CLI\n\nUsage:\n fix-headers [options]\n\nOptions:\n -h, --help Show help\n --dry-run Compute changes without writing files\n --json Print JSON output\n --cwd <path> Working directory for project detection\n --input <path> Single file or folder input\n --include-folder <path> Include folder (repeatable)\n --exclude-folder <path> Exclude folder name/path (repeatable)\n --include-extension <ext> Include extension (repeatable)\n --enable-detector <id> Enable only specific detector (repeatable)\n --disable-detector <id> Disable detector by id (repeatable)\n --project-name <name> Override project name\n --language <id> Override language id\n --project-root <path> Override project root\n --marker <name|null> Override marker filename\n --author-name <name> Override author name\n --author-email <email> Override author email\n --company-name <name> Override company name\n --copyright-start-year <year> Override copyright start year\n --config <path> Load JSON options file\n\nExamples:\n fix-headers --dry-run --include-folder src\n fix-headers --project-name @scope/pkg --company-name "Catalyzed Motivation Inc."\n`;
|
|
25
|
+
const HELP_TEXT = `fix-headers CLI\n\nUsage:\n fix-headers [options]\n\nOptions:\n -h, --help Show help\n --dry-run Compute changes without writing files\n --json Print JSON output\n --verbose Print updated file paths in summary mode\n --sample-output Show previous/new header sample for changed files\n --force-author-update Always update @Author/@Email to detected/current values\n --use-gpg-signer-author Use signed-commit UID (%GS) for detected @Author\n --cwd <path> Working directory for project detection\n --input <path> Single file or folder input\n --include-folder <path> Include folder (repeatable)\n --exclude-folder <path> Exclude folder name/path (repeatable)\n --include-extension <ext> Include extension (repeatable)\n --enable-detector <id> Enable only specific detector (repeatable)\n --disable-detector <id> Disable detector by id (repeatable)\n --project-name <name> Override project name\n --language <id> Override language id\n --project-root <path> Override project root\n --marker <name|null> Override marker filename\n --author-name <name> Override author name\n --author-email <email> Override author email\n --company-name <name> Override company name\n --copyright-start-year <year> Override copyright start year\n --config <path> Load JSON options file\n\nExamples:\n fix-headers --dry-run --include-folder src\n fix-headers --project-name @scope/pkg --company-name "Catalyzed Motivation Inc."\n`;
|
|
14
26
|
|
|
15
27
|
/**
|
|
16
28
|
* Converts CLI flag token to camelCase key.
|
|
@@ -65,10 +77,26 @@ export function parseCliArgs(argv) {
|
|
|
65
77
|
control.json = true;
|
|
66
78
|
continue;
|
|
67
79
|
}
|
|
80
|
+
if (arg === "--verbose") {
|
|
81
|
+
options.verbose = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
68
84
|
if (arg === "--dry-run") {
|
|
69
85
|
options.dryRun = true;
|
|
70
86
|
continue;
|
|
71
87
|
}
|
|
88
|
+
if (arg === "--sample-output") {
|
|
89
|
+
options.sampleOutput = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (arg === "--force-author-update") {
|
|
93
|
+
options.forceAuthorUpdate = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (arg === "--use-gpg-signer-author") {
|
|
97
|
+
options.useGpgSignerAuthor = true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
72
100
|
if (!arg.startsWith("--")) {
|
|
73
101
|
throw new Error(`Unexpected argument: ${arg}`);
|
|
74
102
|
}
|
|
@@ -82,7 +110,7 @@ export function parseCliArgs(argv) {
|
|
|
82
110
|
index += 1;
|
|
83
111
|
const key = multiMap[flag];
|
|
84
112
|
const list = Array.isArray(options[key]) ? options[key] : [];
|
|
85
|
-
options[key] = [...list, value];
|
|
113
|
+
options[key] = Array.from(new Set([...list, value]));
|
|
86
114
|
continue;
|
|
87
115
|
}
|
|
88
116
|
|
|
@@ -147,6 +175,58 @@ export async function applyConfigFile(options) {
|
|
|
147
175
|
return output;
|
|
148
176
|
}
|
|
149
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Prints per-file previous/new header samples when available.
|
|
180
|
+
* @param {(message: string) => void} stdout - Standard output writer.
|
|
181
|
+
* @param {unknown} result - Runner result object.
|
|
182
|
+
* @returns {void}
|
|
183
|
+
*/
|
|
184
|
+
function printSampleOutput(stdout, result) {
|
|
185
|
+
if (!result || typeof result !== "object") {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const report =
|
|
190
|
+
/** @type {{changes?: Array<{file?: string, changed?: boolean, sample?: { previousValue?: string | null, newValue?: string, detectedValues?: Record<string, unknown> }}>}} */ (
|
|
191
|
+
result
|
|
192
|
+
);
|
|
193
|
+
const changes = Array.isArray(report.changes) ? report.changes : [];
|
|
194
|
+
for (const change of changes) {
|
|
195
|
+
if (!change || change.changed !== true || !change.sample || typeof change.sample.newValue !== "string") {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
stdout(`sample: ${change.file || "<unknown-file>"}`);
|
|
200
|
+
stdout("previous:");
|
|
201
|
+
stdout(change.sample.previousValue === null ? "(none)" : String(change.sample.previousValue));
|
|
202
|
+
stdout("new:");
|
|
203
|
+
stdout(change.sample.newValue);
|
|
204
|
+
if (change.sample.detectedValues && typeof change.sample.detectedValues === "object") {
|
|
205
|
+
stdout("detected-values:");
|
|
206
|
+
stdout(JSON.stringify(change.sample.detectedValues, null, 2));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Prints changed file paths from result payload.
|
|
213
|
+
* @param {(message: string) => void} stdout - Standard output writer.
|
|
214
|
+
* @param {unknown} result - Runner result object.
|
|
215
|
+
* @returns {void}
|
|
216
|
+
*/
|
|
217
|
+
function printChangedFiles(stdout, result) {
|
|
218
|
+
if (!result || typeof result !== "object") {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const report = /** @type {{changes?: Array<{file?: string, changed?: boolean}>}} */ (result);
|
|
223
|
+
const changes = Array.isArray(report.changes) ? report.changes : [];
|
|
224
|
+
const updatedFiles = changes.filter((change) => change && change.changed === true).map((change) => change.file || "<unknown-file>");
|
|
225
|
+
for (const file of updatedFiles) {
|
|
226
|
+
stdout(`updated: ${file}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
150
230
|
/**
|
|
151
231
|
* Executes CLI flow and returns process-like exit code.
|
|
152
232
|
* @param {string[]} argv - CLI arguments.
|
|
@@ -182,7 +262,19 @@ export async function runCli(argv, deps = {}) {
|
|
|
182
262
|
stdout(
|
|
183
263
|
`fix-headers complete: scanned=${report.filesScanned ?? 0}, updated=${report.filesUpdated ?? 0}, dryRun=${report.dryRun === true}`
|
|
184
264
|
);
|
|
265
|
+
if (finalOptions.verbose === true) {
|
|
266
|
+
printChangedFiles(stdout, result);
|
|
267
|
+
}
|
|
268
|
+
if (finalOptions.sampleOutput === true) {
|
|
269
|
+
printSampleOutput(stdout, result);
|
|
270
|
+
}
|
|
185
271
|
} else {
|
|
272
|
+
if (finalOptions.verbose === true) {
|
|
273
|
+
printChangedFiles(stdout, result);
|
|
274
|
+
}
|
|
275
|
+
if (finalOptions.sampleOutput === true) {
|
|
276
|
+
printSampleOutput(stdout, result);
|
|
277
|
+
}
|
|
186
278
|
stdout("fix-headers complete");
|
|
187
279
|
}
|
|
188
280
|
return 0;
|
package/src/constants.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/constants.mjs
|
|
4
|
+
* @Date: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
5
|
+
* @Author: Nate Corcoran <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
/**
|
|
2
15
|
* @fileoverview Shared constants for project/language detection and header defaults.
|
|
3
16
|
* @module fix-headers/constants
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/core/file-discovery.mjs
|
|
4
|
+
* @Date: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
5
|
+
* @Author: Nate Corcoran <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
import { join, relative, resolve } from "node:path";
|
|
2
15
|
import { DEFAULT_IGNORE_FOLDERS } from "../constants.mjs";
|
|
3
16
|
import { getAllowedExtensions } from "../detectors/index.mjs";
|
package/src/core/fix-headers.mjs
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/core/fix-headers.mjs
|
|
4
|
+
* @Date: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
5
|
+
* @Author: Nate Corcoran <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
2
15
|
import { relative, resolve } from "node:path";
|
|
3
16
|
import { discoverFiles } from "./file-discovery.mjs";
|
|
4
17
|
import { resolveProjectMetadata } from "../detect/project.mjs";
|
|
5
18
|
import { buildHeader } from "../header/template.mjs";
|
|
6
|
-
import { replaceOrInsertHeader } from "../header/parser.mjs";
|
|
19
|
+
import { findProjectHeader, replaceOrInsertHeader } from "../header/parser.mjs";
|
|
7
20
|
import { readFileDates } from "../utils/fs.mjs";
|
|
8
21
|
import { getGitCreationDate, getGitLastModifiedDate } from "../utils/git.mjs";
|
|
9
22
|
import { toDatePayload } from "../utils/time.mjs";
|
|
@@ -14,9 +27,12 @@ import { toDatePayload } from "../utils/time.mjs";
|
|
|
14
27
|
* input?: string,
|
|
15
28
|
* dryRun?: boolean,
|
|
16
29
|
* configFile?: string,
|
|
30
|
+
* sampleOutput?: boolean,
|
|
31
|
+
* forceAuthorUpdate?: boolean,
|
|
32
|
+
* useGpgSignerAuthor?: boolean,
|
|
17
33
|
* enabledDetectors?: string[],
|
|
18
34
|
* disabledDetectors?: string[],
|
|
19
|
-
* detectorSyntaxOverrides?: Record<string, { linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }>,
|
|
35
|
+
* detectorSyntaxOverrides?: Record<string, { linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }>,
|
|
20
36
|
* includeFolders?: string[],
|
|
21
37
|
* excludeFolders?: string[],
|
|
22
38
|
* includeExtensions?: string[],
|
|
@@ -47,7 +63,20 @@ import { toDatePayload } from "../utils/time.mjs";
|
|
|
47
63
|
* filesScanned: number,
|
|
48
64
|
* filesUpdated: number,
|
|
49
65
|
* dryRun: boolean,
|
|
50
|
-
* changes: Array<{file: string, changed: boolean
|
|
66
|
+
* changes: Array<{file: string, changed: boolean, sample?: { previousValue: string | null, newValue: string, detectedValues?: {
|
|
67
|
+
* projectName: string,
|
|
68
|
+
* language: string,
|
|
69
|
+
* projectRoot: string,
|
|
70
|
+
* marker: string | null,
|
|
71
|
+
* authorName: string,
|
|
72
|
+
* authorEmail: string,
|
|
73
|
+
* companyName: string,
|
|
74
|
+
* copyrightStartYear: number,
|
|
75
|
+
* createdAtSource: string,
|
|
76
|
+
* lastModifiedAtSource: string,
|
|
77
|
+
* createdAt: {date: string, timestamp: number},
|
|
78
|
+
* lastModifiedAt: {date: string, timestamp: number}
|
|
79
|
+
* } }}>
|
|
51
80
|
* }} FixHeadersResult
|
|
52
81
|
*/
|
|
53
82
|
|
|
@@ -83,6 +112,65 @@ async function resolveRuntimeOptions(options) {
|
|
|
83
112
|
return output;
|
|
84
113
|
}
|
|
85
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Extracts original author identity from an existing header block.
|
|
117
|
+
* @param {string} headerText - Existing header content.
|
|
118
|
+
* @returns {{ authorName?: string, authorEmail?: string }} Parsed identity values.
|
|
119
|
+
*/
|
|
120
|
+
function extractHeaderAuthorIdentity(headerText) {
|
|
121
|
+
const authorMatch = headerText.match(/@Author:\s*(.+)$/m);
|
|
122
|
+
const emailMatch = headerText.match(/@Email:\s*<([^>\n]+)>/m);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
authorName: authorMatch?.[1]?.trim(),
|
|
126
|
+
authorEmail: emailMatch?.[1]?.trim()
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Extracts original created-at payload from an existing header block.
|
|
132
|
+
* @param {string} headerText - Existing header content.
|
|
133
|
+
* @returns {{date: string, timestamp: number} | null} Parsed created-at value.
|
|
134
|
+
*/
|
|
135
|
+
function extractHeaderCreatedAt(headerText) {
|
|
136
|
+
const dateMatch = headerText.match(/@Date:\s*(.+?)\s*\((\d+)\)$/m);
|
|
137
|
+
if (!dateMatch || !dateMatch[1] || !dateMatch[2]) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const timestamp = Number.parseInt(dateMatch[2], 10);
|
|
142
|
+
if (Number.isNaN(timestamp)) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
date: dateMatch[1].trim(),
|
|
148
|
+
timestamp
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Extracts original last-modified payload from an existing header block.
|
|
154
|
+
* @param {string} headerText - Existing header content.
|
|
155
|
+
* @returns {{date: string, timestamp: number} | null} Parsed last-modified value.
|
|
156
|
+
*/
|
|
157
|
+
function extractHeaderLastModifiedAt(headerText) {
|
|
158
|
+
const modifiedMatch = headerText.match(/@Last modified time:\s*(.+?)\s*\((\d+)\)$/m);
|
|
159
|
+
if (!modifiedMatch || !modifiedMatch[1] || !modifiedMatch[2]) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const timestamp = Number.parseInt(modifiedMatch[2], 10);
|
|
164
|
+
if (Number.isNaN(timestamp)) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
date: modifiedMatch[1].trim(),
|
|
170
|
+
timestamp
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
86
174
|
/**
|
|
87
175
|
* @fileoverview Main header-fixing engine with auto-detection and override support.
|
|
88
176
|
* @module fix-headers/core/fix-headers
|
|
@@ -138,6 +226,7 @@ export async function fixHeaders(options = {}) {
|
|
|
138
226
|
}
|
|
139
227
|
|
|
140
228
|
const currentYear = new Date().getFullYear();
|
|
229
|
+
/** @type {FixHeadersResult["changes"]} */
|
|
141
230
|
const changes = [];
|
|
142
231
|
const detectedProjects = new Set();
|
|
143
232
|
let filesUpdated = 0;
|
|
@@ -152,16 +241,33 @@ export async function fixHeaders(options = {}) {
|
|
|
152
241
|
detectedProjects.add(`${fileMetadata.language}:${fileMetadata.projectRoot}`);
|
|
153
242
|
const relativePath = relative(scanRoot, filePath);
|
|
154
243
|
const original = await readFile(filePath, "utf8");
|
|
244
|
+
const existingHeader = findProjectHeader(original, filePath, {
|
|
245
|
+
language: fileMetadata.language,
|
|
246
|
+
enabledDetectors: effectiveOptions.enabledDetectors,
|
|
247
|
+
disabledDetectors: effectiveOptions.disabledDetectors,
|
|
248
|
+
detectorSyntaxOverrides: effectiveOptions.detectorSyntaxOverrides
|
|
249
|
+
});
|
|
250
|
+
const existingHeaderText = existingHeader ? original.slice(existingHeader.start, existingHeader.end) : "";
|
|
251
|
+
const existingIdentity = existingHeaderText.length > 0 ? extractHeaderAuthorIdentity(existingHeaderText) : {};
|
|
252
|
+
const existingCreatedAt = existingHeaderText.length > 0 ? extractHeaderCreatedAt(existingHeaderText) : null;
|
|
253
|
+
const existingLastModifiedAt = existingHeaderText.length > 0 ? extractHeaderLastModifiedAt(existingHeaderText) : null;
|
|
155
254
|
const filesystemDates = await readFileDates(filePath);
|
|
156
255
|
|
|
157
256
|
const metadataRelativePath = relative(fileMetadata.projectRoot, filePath);
|
|
158
257
|
const gitCreated = await getGitCreationDate(fileMetadata.projectRoot, metadataRelativePath);
|
|
159
258
|
const gitLastUpdated = await getGitLastModifiedDate(fileMetadata.projectRoot, metadataRelativePath);
|
|
160
259
|
|
|
161
|
-
const
|
|
162
|
-
const
|
|
260
|
+
const createdAtSource = existingCreatedAt ? "existing-header" : gitCreated ? "git-created" : "filesystem-created";
|
|
261
|
+
const comparisonLastModifiedAtSource = existingLastModifiedAt
|
|
262
|
+
? "existing-header"
|
|
263
|
+
: gitLastUpdated
|
|
264
|
+
? "git-last-modified"
|
|
265
|
+
: "filesystem-updated";
|
|
266
|
+
const createdAt = existingCreatedAt || gitCreated || toDatePayload(filesystemDates.createdAt);
|
|
267
|
+
const comparisonLastModifiedAt = existingLastModifiedAt || gitLastUpdated || toDatePayload(filesystemDates.updatedAt);
|
|
268
|
+
const shouldForceAuthorUpdate = effectiveOptions.forceAuthorUpdate === true;
|
|
163
269
|
|
|
164
|
-
const
|
|
270
|
+
const comparisonHeader = buildHeader({
|
|
165
271
|
absoluteFilePath: filePath,
|
|
166
272
|
language: fileMetadata.language,
|
|
167
273
|
syntaxOptions: {
|
|
@@ -172,22 +278,90 @@ export async function fixHeaders(options = {}) {
|
|
|
172
278
|
},
|
|
173
279
|
projectRoot: fileMetadata.projectRoot,
|
|
174
280
|
projectName: fileMetadata.projectName,
|
|
281
|
+
createdByName: shouldForceAuthorUpdate ? fileMetadata.authorName : existingIdentity.authorName || fileMetadata.authorName,
|
|
282
|
+
createdByEmail: shouldForceAuthorUpdate ? fileMetadata.authorEmail : existingIdentity.authorEmail || fileMetadata.authorEmail,
|
|
283
|
+
lastModifiedByName: fileMetadata.authorName,
|
|
284
|
+
lastModifiedByEmail: fileMetadata.authorEmail,
|
|
175
285
|
authorName: fileMetadata.authorName,
|
|
176
286
|
authorEmail: fileMetadata.authorEmail,
|
|
177
287
|
createdAt,
|
|
178
|
-
lastModifiedAt,
|
|
288
|
+
lastModifiedAt: comparisonLastModifiedAt,
|
|
179
289
|
copyrightStartYear: fileMetadata.copyrightStartYear,
|
|
180
290
|
companyName: fileMetadata.companyName,
|
|
181
291
|
currentYear
|
|
182
292
|
});
|
|
183
293
|
|
|
184
|
-
const
|
|
294
|
+
const comparisonReplacement = replaceOrInsertHeader(original, comparisonHeader, filePath, {
|
|
185
295
|
language: fileMetadata.language,
|
|
186
296
|
enabledDetectors: effectiveOptions.enabledDetectors,
|
|
187
297
|
disabledDetectors: effectiveOptions.disabledDetectors,
|
|
188
298
|
detectorSyntaxOverrides: effectiveOptions.detectorSyntaxOverrides
|
|
189
299
|
});
|
|
190
|
-
|
|
300
|
+
const needsUpdate = comparisonReplacement.changed;
|
|
301
|
+
const finalLastModifiedAt = needsUpdate ? toDatePayload(new Date()) : comparisonLastModifiedAt;
|
|
302
|
+
const lastModifiedAtSource = needsUpdate ? "current-time-on-change" : comparisonLastModifiedAtSource;
|
|
303
|
+
|
|
304
|
+
const header = needsUpdate
|
|
305
|
+
? buildHeader({
|
|
306
|
+
absoluteFilePath: filePath,
|
|
307
|
+
language: fileMetadata.language,
|
|
308
|
+
syntaxOptions: {
|
|
309
|
+
language: fileMetadata.language,
|
|
310
|
+
enabledDetectors: effectiveOptions.enabledDetectors,
|
|
311
|
+
disabledDetectors: effectiveOptions.disabledDetectors,
|
|
312
|
+
detectorSyntaxOverrides: effectiveOptions.detectorSyntaxOverrides
|
|
313
|
+
},
|
|
314
|
+
projectRoot: fileMetadata.projectRoot,
|
|
315
|
+
projectName: fileMetadata.projectName,
|
|
316
|
+
createdByName: shouldForceAuthorUpdate ? fileMetadata.authorName : existingIdentity.authorName || fileMetadata.authorName,
|
|
317
|
+
createdByEmail: shouldForceAuthorUpdate ? fileMetadata.authorEmail : existingIdentity.authorEmail || fileMetadata.authorEmail,
|
|
318
|
+
lastModifiedByName: fileMetadata.authorName,
|
|
319
|
+
lastModifiedByEmail: fileMetadata.authorEmail,
|
|
320
|
+
authorName: fileMetadata.authorName,
|
|
321
|
+
authorEmail: fileMetadata.authorEmail,
|
|
322
|
+
createdAt,
|
|
323
|
+
lastModifiedAt: finalLastModifiedAt,
|
|
324
|
+
copyrightStartYear: fileMetadata.copyrightStartYear,
|
|
325
|
+
companyName: fileMetadata.companyName,
|
|
326
|
+
currentYear
|
|
327
|
+
})
|
|
328
|
+
: comparisonHeader;
|
|
329
|
+
|
|
330
|
+
const replacement = needsUpdate
|
|
331
|
+
? replaceOrInsertHeader(original, header, filePath, {
|
|
332
|
+
language: fileMetadata.language,
|
|
333
|
+
enabledDetectors: effectiveOptions.enabledDetectors,
|
|
334
|
+
disabledDetectors: effectiveOptions.disabledDetectors,
|
|
335
|
+
detectorSyntaxOverrides: effectiveOptions.detectorSyntaxOverrides
|
|
336
|
+
})
|
|
337
|
+
: comparisonReplacement;
|
|
338
|
+
const changeEntry = {
|
|
339
|
+
file: relativePath,
|
|
340
|
+
changed: replacement.changed
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
if (effectiveOptions.sampleOutput === true && replacement.changed) {
|
|
344
|
+
changeEntry.sample = {
|
|
345
|
+
previousValue: existingHeaderText.length > 0 ? existingHeaderText.trimEnd() : null,
|
|
346
|
+
newValue: header,
|
|
347
|
+
detectedValues: {
|
|
348
|
+
projectName: fileMetadata.projectName,
|
|
349
|
+
language: fileMetadata.language,
|
|
350
|
+
projectRoot: fileMetadata.projectRoot,
|
|
351
|
+
marker: fileMetadata.marker,
|
|
352
|
+
authorName: fileMetadata.authorName,
|
|
353
|
+
authorEmail: fileMetadata.authorEmail,
|
|
354
|
+
companyName: fileMetadata.companyName,
|
|
355
|
+
copyrightStartYear: fileMetadata.copyrightStartYear,
|
|
356
|
+
createdAtSource,
|
|
357
|
+
lastModifiedAtSource,
|
|
358
|
+
createdAt,
|
|
359
|
+
lastModifiedAt: finalLastModifiedAt
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
changes.push(changeEntry);
|
|
191
365
|
|
|
192
366
|
if (!replacement.changed) {
|
|
193
367
|
continue;
|
package/src/detect/project.mjs
CHANGED
|
@@ -2,26 +2,13 @@
|
|
|
2
2
|
* @Project: @cldmv/fix-headers
|
|
3
3
|
* @Filename: /src/detect/project.mjs
|
|
4
4
|
* @Date: 2026-03-01 13:32:57 -08:00 (1772400777)
|
|
5
|
-
* @Author: Nate
|
|
5
|
+
* @Author: Nate Corcoran <CLDMV>
|
|
6
6
|
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
7
|
* -----
|
|
8
|
-
* @Last modified by: Nate
|
|
9
|
-
* @Last modified time: 2026-03-
|
|
8
|
+
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
10
10
|
* -----
|
|
11
|
-
* @Copyright: Copyright (c)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @Project: @cldmv/fix-headers
|
|
16
|
-
* @Filename: /src/detect/project.mjs
|
|
17
|
-
* @Date: 2026-03-01 13:32:57 -08:00 (1772400777)
|
|
18
|
-
* @Author: Nate Hyson <CLDMV>
|
|
19
|
-
* @Email: <Shinrai@users.noreply.github.com>
|
|
20
|
-
* -----
|
|
21
|
-
* @Last modified by: Nate Hyson <CLDMV> (Shinrai@users.noreply.github.com)
|
|
22
|
-
* @Last modified time: 2026-03-01 16:27:47 -08:00 (1772411267)
|
|
23
|
-
* -----
|
|
24
|
-
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
11
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
25
12
|
*/
|
|
26
13
|
|
|
27
14
|
import { dirname, extname, join, resolve } from "node:path";
|
|
@@ -136,6 +123,7 @@ export async function detectProjectFromMarkers(cwd, options = {}) {
|
|
|
136
123
|
* language?: string,
|
|
137
124
|
* projectRoot?: string,
|
|
138
125
|
* marker?: string | null,
|
|
126
|
+
* useGpgSignerAuthor?: boolean,
|
|
139
127
|
* authorName?: string,
|
|
140
128
|
* authorEmail?: string,
|
|
141
129
|
* companyName?: string,
|
|
@@ -159,7 +147,9 @@ export async function resolveProjectMetadata(options = {}) {
|
|
|
159
147
|
const detectFrom = options.targetFilePath ? dirname(cwd) : cwd;
|
|
160
148
|
const preferredExtension = options.targetFilePath ? extname(cwd).toLowerCase() : "";
|
|
161
149
|
const detected = await detectProjectFromMarkers(detectFrom, { detectors, preferredExtension });
|
|
162
|
-
const gitAuthor = await detectGitAuthor(detected.rootDir
|
|
150
|
+
const gitAuthor = await detectGitAuthor(detected.rootDir, {
|
|
151
|
+
useGpgSignerAuthor: options.useGpgSignerAuthor === true
|
|
152
|
+
});
|
|
163
153
|
const currentYear = new Date().getFullYear();
|
|
164
154
|
|
|
165
155
|
return {
|
package/src/detectors/css.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/detectors/css.mjs
|
|
4
|
+
* @Date: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
5
|
+
* @Author: Nate Corcoran <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
import { extname } from "node:path";
|
|
2
15
|
import { findNearestMarker } from "./shared.mjs";
|
|
3
16
|
|
package/src/detectors/go.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/detectors/go.mjs
|
|
4
|
+
* @Date: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
5
|
+
* @Author: Nate Corcoran <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
import { extname } from "node:path";
|
|
2
15
|
import { findNearestMarker } from "./shared.mjs";
|
|
3
16
|
|
package/src/detectors/html.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/detectors/html.mjs
|
|
4
|
+
* @Date: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
5
|
+
* @Author: Nate Corcoran <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-03-01T17:59:32-08:00 (1772416772)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
import { extname } from "node:path";
|
|
2
15
|
import { findNearestMarker } from "./shared.mjs";
|
|
3
16
|
|