@cldmv/fix-headers 1.0.0 → 1.1.1
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 +106 -4
- 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/src/detectors/index.mjs
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* @Project: @cldmv/fix-headers
|
|
3
3
|
* @Filename: /src/detectors/index.mjs
|
|
4
4
|
* @Date: 2026-03-01 16:34:41 -08:00 (1772411681)
|
|
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
|
import { readdir } from "node:fs/promises";
|
|
@@ -29,7 +29,8 @@ import { dirname, join } from "node:path";
|
|
|
29
29
|
* enabledByDefault: boolean,
|
|
30
30
|
* findNearestConfig: (startPath: string) => Promise<{root: string, marker: string} | null>,
|
|
31
31
|
* parseProjectName: (marker: string, markerContent: string, rootDirName: string) => string,
|
|
32
|
-
* resolveCommentSyntax: (filePath: string) => ({kind: "block" | "line" | "html", linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string} | null),
|
|
32
|
+
* resolveCommentSyntax: (filePath: string) => ({kind: "block" | "line" | "html", linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string} | null),
|
|
33
|
+
* resolvePreservedPrefix?: (filePath: string, content: string) => string,
|
|
33
34
|
* priority?: number
|
|
34
35
|
* }} DetectorProfile
|
|
35
36
|
*/
|
|
@@ -63,9 +64,9 @@ const detectorMap = new Map(DETECTOR_PROFILES.map((detector) => [detector.id, de
|
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* Applies runtime syntax overrides to a detector-provided syntax descriptor.
|
|
66
|
-
* @param {{kind: "block" | "line" | "html", linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string}} syntax - Base syntax descriptor.
|
|
67
|
-
* @param {{ linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string } | undefined} override - Override descriptor.
|
|
68
|
-
* @returns {{kind: "block" | "line" | "html", linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string}} Effective descriptor.
|
|
67
|
+
* @param {{kind: "block" | "line" | "html", linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string}} syntax - Base syntax descriptor.
|
|
68
|
+
* @param {{ linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string } | undefined} override - Override descriptor.
|
|
69
|
+
* @returns {{kind: "block" | "line" | "html", linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string}} Effective descriptor.
|
|
69
70
|
*/
|
|
70
71
|
function applySyntaxOverride(syntax, override) {
|
|
71
72
|
if (!override || typeof override !== "object") {
|
|
@@ -75,7 +76,8 @@ function applySyntaxOverride(syntax, override) {
|
|
|
75
76
|
if (syntax.kind === "line") {
|
|
76
77
|
return {
|
|
77
78
|
...syntax,
|
|
78
|
-
linePrefix: typeof override.linePrefix === "string" && override.linePrefix.length > 0 ? override.linePrefix : syntax.linePrefix
|
|
79
|
+
linePrefix: typeof override.linePrefix === "string" && override.linePrefix.length > 0 ? override.linePrefix : syntax.linePrefix,
|
|
80
|
+
lineSeparator: typeof override.lineSeparator === "string" ? override.lineSeparator : syntax.lineSeparator
|
|
79
81
|
};
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -138,8 +140,8 @@ export function getDetectorById(id) {
|
|
|
138
140
|
/**
|
|
139
141
|
* Resolves comment syntax for a file path using detector-specific templates.
|
|
140
142
|
* @param {string} filePath - File path.
|
|
141
|
-
* @param {{ language?: string, enabledDetectors?: string[], disabledDetectors?: string[], detectorSyntaxOverrides?: Record<string, { linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }> }} [options={}] - Runtime options.
|
|
142
|
-
* @returns {{kind: "block" | "line" | "html", linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string}} Syntax descriptor.
|
|
143
|
+
* @param {{ language?: string, enabledDetectors?: string[], disabledDetectors?: string[], detectorSyntaxOverrides?: Record<string, { linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }> }} [options={}] - Runtime options.
|
|
144
|
+
* @returns {{kind: "block" | "line" | "html", linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string}} Syntax descriptor.
|
|
143
145
|
*/
|
|
144
146
|
export function getCommentSyntaxForFile(filePath, options = {}) {
|
|
145
147
|
const extension = extname(filePath).toLowerCase();
|
|
@@ -158,3 +160,28 @@ export function getCommentSyntaxForFile(filePath, options = {}) {
|
|
|
158
160
|
|
|
159
161
|
return { kind: "block", blockStart: "/**", blockLinePrefix: " *\t", blockEnd: " */" };
|
|
160
162
|
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Resolves detector-specific leading content that must be preserved above inserted headers.
|
|
166
|
+
* @param {string} filePath - File path.
|
|
167
|
+
* @param {string} content - Full file content.
|
|
168
|
+
* @param {{ language?: string, enabledDetectors?: string[], disabledDetectors?: string[] }} [options={}] - Runtime options.
|
|
169
|
+
* @returns {string} Preserved prefix (possibly empty).
|
|
170
|
+
*/
|
|
171
|
+
export function getPreservedPrefixForFile(filePath, content, options = {}) {
|
|
172
|
+
const extension = extname(filePath).toLowerCase();
|
|
173
|
+
const detectors = getEnabledDetectors(options);
|
|
174
|
+
for (const detector of detectors) {
|
|
175
|
+
if (!detector.extensions.includes(extension)) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (typeof detector.resolvePreservedPrefix === "function") {
|
|
180
|
+
return detector.resolvePreservedPrefix(filePath, content);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return "";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return "";
|
|
187
|
+
}
|
package/src/detectors/node.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/detectors/node.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
|
|
|
@@ -45,6 +58,17 @@ function resolveNodeCommentSyntax(filePath) {
|
|
|
45
58
|
return null;
|
|
46
59
|
}
|
|
47
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Resolves preserved leading prefix for Node files (for example: shebang line).
|
|
63
|
+
* @param {string} _filePath - File path.
|
|
64
|
+
* @param {string} content - File content.
|
|
65
|
+
* @returns {string} Preserved prefix.
|
|
66
|
+
*/
|
|
67
|
+
function resolveNodePreservedPrefix(_filePath, content) {
|
|
68
|
+
const shebangMatch = content.match(/^#!.*\b(node|bun|deno|tsx|ts-node)\b.*(?:\r?\n|$)/);
|
|
69
|
+
return shebangMatch ? shebangMatch[0] : "";
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
export const detector = {
|
|
49
73
|
id: "node",
|
|
50
74
|
priority: 100,
|
|
@@ -57,6 +81,9 @@ export const detector = {
|
|
|
57
81
|
parseProjectName(_marker, markerContent, rootDirName) {
|
|
58
82
|
return parseNodeProjectName(markerContent, rootDirName);
|
|
59
83
|
},
|
|
84
|
+
resolvePreservedPrefix(filePath, content) {
|
|
85
|
+
return resolveNodePreservedPrefix(filePath, content);
|
|
86
|
+
},
|
|
60
87
|
resolveCommentSyntax(filePath) {
|
|
61
88
|
return resolveNodeCommentSyntax(filePath);
|
|
62
89
|
}
|
package/src/detectors/php.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/detectors/php.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/python.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/detectors/python.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
|
|
|
@@ -23,6 +36,17 @@ function parsePythonProjectName(markerContent, rootDirName) {
|
|
|
23
36
|
return rootDirName;
|
|
24
37
|
}
|
|
25
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Resolves preserved leading prefix for Python files (for example: shebang line).
|
|
41
|
+
* @param {string} _filePath - File path.
|
|
42
|
+
* @param {string} content - File content.
|
|
43
|
+
* @returns {string} Preserved prefix.
|
|
44
|
+
*/
|
|
45
|
+
function resolvePythonPreservedPrefix(_filePath, content) {
|
|
46
|
+
const shebangMatch = content.match(/^#!.*\bpython(?:\d+(?:\.\d+)*)?\b.*(?:\r?\n|$)/);
|
|
47
|
+
return shebangMatch ? shebangMatch[0] : "";
|
|
48
|
+
}
|
|
49
|
+
|
|
26
50
|
export const detector = {
|
|
27
51
|
id: "python",
|
|
28
52
|
priority: 80,
|
|
@@ -35,6 +59,9 @@ export const detector = {
|
|
|
35
59
|
parseProjectName(_marker, markerContent, rootDirName) {
|
|
36
60
|
return parsePythonProjectName(markerContent, rootDirName);
|
|
37
61
|
},
|
|
62
|
+
resolvePreservedPrefix(filePath, content) {
|
|
63
|
+
return resolvePythonPreservedPrefix(filePath, content);
|
|
64
|
+
},
|
|
38
65
|
resolveCommentSyntax(filePath) {
|
|
39
66
|
const extension = extname(filePath).toLowerCase();
|
|
40
67
|
if (extensions.includes(extension)) {
|
package/src/detectors/rust.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/detectors/rust.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/shared.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/detectors/shared.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 { dirname, join, resolve } from "node:path";
|
|
2
15
|
import { pathExists } from "../utils/fs.mjs";
|
|
3
16
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/detectors/yaml.mjs
|
|
4
|
+
* @Date: 2026-03-01 18:28:31 -08:00 (1772418511)
|
|
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-01 19:32:27 -08:00 (1772422347)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2026-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { extname } from "node:path";
|
|
15
|
+
import { findNearestMarker } from "./shared.mjs";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @fileoverview YAML detector implementation.
|
|
19
|
+
* @module fix-headers/detectors/yaml
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const markers = ["package.json", ".git"];
|
|
23
|
+
const extensions = [".yaml", ".yml"];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parses YAML project name from nearest marker content when available.
|
|
27
|
+
* @param {string} marker - Marker filename.
|
|
28
|
+
* @param {string} markerContent - Marker content.
|
|
29
|
+
* @param {string} rootDirName - Fallback root directory name.
|
|
30
|
+
* @returns {string} Project name.
|
|
31
|
+
*/
|
|
32
|
+
function parseYamlProjectName(marker, markerContent, rootDirName) {
|
|
33
|
+
if (marker !== "package.json") {
|
|
34
|
+
return rootDirName;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(markerContent);
|
|
39
|
+
if (typeof parsed.name === "string" && parsed.name.trim().length > 0) {
|
|
40
|
+
return parsed.name.trim();
|
|
41
|
+
}
|
|
42
|
+
return rootDirName;
|
|
43
|
+
} catch {
|
|
44
|
+
return rootDirName;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolves comment syntax for YAML file extensions.
|
|
50
|
+
* @param {string} filePath - File path.
|
|
51
|
+
* @returns {{kind: "line", linePrefix: string} | null} Syntax descriptor.
|
|
52
|
+
*/
|
|
53
|
+
function resolveYamlCommentSyntax(filePath) {
|
|
54
|
+
const extension = extname(filePath).toLowerCase();
|
|
55
|
+
if (extensions.includes(extension)) {
|
|
56
|
+
return {
|
|
57
|
+
kind: "line",
|
|
58
|
+
linePrefix: "#"
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const detector = {
|
|
65
|
+
id: "yaml",
|
|
66
|
+
priority: 60,
|
|
67
|
+
markers,
|
|
68
|
+
extensions,
|
|
69
|
+
enabledByDefault: true,
|
|
70
|
+
findNearestConfig(startPath) {
|
|
71
|
+
return findNearestMarker(startPath, markers);
|
|
72
|
+
},
|
|
73
|
+
parseProjectName(marker, markerContent, rootDirName) {
|
|
74
|
+
return parseYamlProjectName(marker, markerContent, rootDirName);
|
|
75
|
+
},
|
|
76
|
+
resolveCommentSyntax(filePath) {
|
|
77
|
+
return resolveYamlCommentSyntax(filePath);
|
|
78
|
+
}
|
|
79
|
+
};
|
package/src/fix-header.mjs
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* @Project: @cldmv/fix-headers
|
|
3
3
|
* @Filename: /src/fix-header.mjs
|
|
4
4
|
* @Date: 2026-03-01 13:34:00 -08:00 (1772400840)
|
|
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
|
import { fixHeaders as fixHeadersCore } from "./core/fix-headers.mjs";
|
package/src/header/parser.mjs
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/header/parser.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 { DEFAULT_MAX_HEADER_SCAN_LINES } from "../constants.mjs";
|
|
15
|
+
import { getPreservedPrefixForFile } from "../detectors/index.mjs";
|
|
2
16
|
import { getHeaderSyntaxForFile } from "./syntax.mjs";
|
|
3
17
|
|
|
4
18
|
/**
|
|
@@ -16,41 +30,85 @@ function escapeRegex(text) {
|
|
|
16
30
|
}
|
|
17
31
|
|
|
18
32
|
/**
|
|
19
|
-
*
|
|
33
|
+
* Splits detector-defined preserved prefix from file content when present.
|
|
34
|
+
* @param {string} filePath - File path used for detector selection.
|
|
20
35
|
* @param {string} content - File content.
|
|
21
|
-
* @param {string
|
|
22
|
-
* @
|
|
23
|
-
* @returns {{start: number, end: number} | null} Header location.
|
|
36
|
+
* @param {{ language?: string, enabledDetectors?: string[], disabledDetectors?: string[], detectorSyntaxOverrides?: Record<string, { linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }> }} [syntaxOptions={}] - Syntax/detector options.
|
|
37
|
+
* @returns {{prefix: string, body: string}} Shebang prefix and remaining body.
|
|
24
38
|
*/
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
39
|
+
function splitPreservedPrefix(filePath, content, syntaxOptions = {}) {
|
|
40
|
+
const prefix = getPreservedPrefixForFile(filePath, content, syntaxOptions);
|
|
41
|
+
if (prefix.length === 0) {
|
|
42
|
+
return { prefix: "", body: content };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
prefix,
|
|
47
|
+
body: content.slice(prefix.length)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
28
50
|
|
|
29
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Matches a top-of-content header block for a given syntax.
|
|
53
|
+
* @param {string} content - Content slice to match from start.
|
|
54
|
+
* @param {{kind: "block" | "line" | "html", linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string}} syntax - Header syntax descriptor.
|
|
55
|
+
* @returns {RegExpMatchArray | null} Matched header segment.
|
|
56
|
+
*/
|
|
57
|
+
function matchHeaderSegment(content, syntax) {
|
|
30
58
|
if (syntax.kind === "html") {
|
|
31
59
|
const blockStart = escapeRegex(syntax.blockStart || "<!--");
|
|
32
60
|
const blockEnd = escapeRegex(syntax.blockEnd || "-->");
|
|
33
|
-
|
|
34
|
-
}
|
|
61
|
+
return content.match(new RegExp(`^${blockStart}[\\s\\S]*?${blockEnd}\\n*`));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (syntax.kind === "line") {
|
|
35
65
|
const linePrefix = escapeRegex(syntax.linePrefix || "#");
|
|
36
|
-
|
|
37
|
-
} else {
|
|
38
|
-
const blockStart = escapeRegex(syntax.blockStart || "/**");
|
|
39
|
-
const blockEnd = escapeRegex(syntax.blockEnd || " */");
|
|
40
|
-
match = scanLines.match(new RegExp(`^${blockStart}[\\s\\S]*?${blockEnd}\\n*`));
|
|
66
|
+
return content.match(new RegExp(`^(?:${linePrefix}.*\\n)+\\n*`));
|
|
41
67
|
}
|
|
42
68
|
|
|
69
|
+
const blockStart = escapeRegex(syntax.blockStart || "/**");
|
|
70
|
+
const blockEnd = escapeRegex(syntax.blockEnd || " */");
|
|
71
|
+
return content.match(new RegExp(`^${blockStart}[\\s\\S]*?${blockEnd}\\n*`));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Finds the first top-level project header block in a file.
|
|
76
|
+
* @param {string} content - File content.
|
|
77
|
+
* @param {string} [filePath=""] - File path used for syntax selection.
|
|
78
|
+
* @param {{ language?: string, enabledDetectors?: string[], disabledDetectors?: string[], detectorSyntaxOverrides?: Record<string, { linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }> }} [syntaxOptions={}] - Syntax resolution options.
|
|
79
|
+
* @returns {{start: number, end: number} | null} Header location.
|
|
80
|
+
*/
|
|
81
|
+
export function findProjectHeader(content, filePath = "", syntaxOptions = {}) {
|
|
82
|
+
const { prefix, body } = splitPreservedPrefix(filePath, content, syntaxOptions);
|
|
83
|
+
const scanLines = body.split("\n").slice(0, DEFAULT_MAX_HEADER_SCAN_LINES).join("\n");
|
|
84
|
+
const syntax = getHeaderSyntaxForFile(filePath, syntaxOptions);
|
|
85
|
+
const match = matchHeaderSegment(scanLines, syntax);
|
|
86
|
+
|
|
43
87
|
if (!match) {
|
|
44
88
|
return null;
|
|
45
89
|
}
|
|
46
90
|
|
|
47
|
-
|
|
91
|
+
let offset = 0;
|
|
92
|
+
let end = 0;
|
|
93
|
+
while (true) {
|
|
94
|
+
const remaining = scanLines.slice(offset);
|
|
95
|
+
const segment = matchHeaderSegment(remaining, syntax);
|
|
96
|
+
|
|
97
|
+
if (!segment || !segment[0].includes("@Project:")) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
offset += segment[0].length;
|
|
102
|
+
end = offset;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (end === 0) {
|
|
48
106
|
return null;
|
|
49
107
|
}
|
|
50
108
|
|
|
51
109
|
return {
|
|
52
|
-
start:
|
|
53
|
-
end:
|
|
110
|
+
start: prefix.length,
|
|
111
|
+
end: prefix.length + end
|
|
54
112
|
};
|
|
55
113
|
}
|
|
56
114
|
|
|
@@ -59,13 +117,14 @@ export function findProjectHeader(content, filePath = "", syntaxOptions = {}) {
|
|
|
59
117
|
* @param {string} content - Original file content.
|
|
60
118
|
* @param {string} newHeader - Generated header text.
|
|
61
119
|
* @param {string} [filePath=""] - File path used for syntax selection.
|
|
62
|
-
* @param {{ language?: string, enabledDetectors?: string[], disabledDetectors?: string[], detectorSyntaxOverrides?: Record<string, { linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }> }} [syntaxOptions={}] - Syntax resolution options.
|
|
120
|
+
* @param {{ language?: string, enabledDetectors?: string[], disabledDetectors?: string[], detectorSyntaxOverrides?: Record<string, { linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }> }} [syntaxOptions={}] - Syntax resolution options.
|
|
63
121
|
* @returns {{nextContent: string, changed: boolean}} Updated content result.
|
|
64
122
|
*/
|
|
65
123
|
export function replaceOrInsertHeader(content, newHeader, filePath = "", syntaxOptions = {}) {
|
|
66
124
|
const existing = findProjectHeader(content, filePath, syntaxOptions);
|
|
67
125
|
if (!existing) {
|
|
68
|
-
const
|
|
126
|
+
const { prefix, body } = splitPreservedPrefix(filePath, content, syntaxOptions);
|
|
127
|
+
const nextContent = `${prefix}${newHeader}\n\n${body.replace(/^\n+/, "")}`;
|
|
69
128
|
return { nextContent, changed: nextContent !== content };
|
|
70
129
|
}
|
|
71
130
|
|
package/src/header/syntax.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/header/syntax.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 { getCommentSyntaxForFile } from "../detectors/index.mjs";
|
|
3
16
|
|
|
@@ -10,6 +23,7 @@ import { getCommentSyntaxForFile } from "../detectors/index.mjs";
|
|
|
10
23
|
* @typedef {{
|
|
11
24
|
* kind: "block" | "line" | "html",
|
|
12
25
|
* linePrefix?: string,
|
|
26
|
+
* lineSeparator?: string,
|
|
13
27
|
* blockStart?: string,
|
|
14
28
|
* blockLinePrefix?: string,
|
|
15
29
|
* blockEnd?: string
|
|
@@ -19,7 +33,7 @@ import { getCommentSyntaxForFile } from "../detectors/index.mjs";
|
|
|
19
33
|
/**
|
|
20
34
|
* Resolves comment syntax for a file path based on extension.
|
|
21
35
|
* @param {string} filePath - Absolute or relative file path.
|
|
22
|
-
* @param {{ language?: string, enabledDetectors?: string[], disabledDetectors?: string[], detectorSyntaxOverrides?: Record<string, { linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }> }} [options={}] - Syntax resolution options.
|
|
36
|
+
* @param {{ language?: string, enabledDetectors?: string[], disabledDetectors?: string[], detectorSyntaxOverrides?: Record<string, { linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }> }} [options={}] - Syntax resolution options.
|
|
23
37
|
* @returns {HeaderSyntax} Header syntax descriptor.
|
|
24
38
|
*/
|
|
25
39
|
export function getHeaderSyntaxForFile(filePath, options = {}) {
|
|
@@ -39,7 +53,8 @@ export function getHeaderSyntaxForFile(filePath, options = {}) {
|
|
|
39
53
|
*/
|
|
40
54
|
export function renderHeaderLines(syntax, lines) {
|
|
41
55
|
if (syntax.kind === "line") {
|
|
42
|
-
|
|
56
|
+
const lineSeparator = typeof syntax.lineSeparator === "string" ? syntax.lineSeparator : "\t";
|
|
57
|
+
return lines.map((line) => `${syntax.linePrefix || "#"}${lineSeparator}${line}`).join("\n");
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
const blockStart = syntax.blockStart || (syntax.kind === "html" ? "<!--" : "/**");
|
package/src/header/template.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/header/template.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 { relative } from "node:path";
|
|
2
15
|
import { getHeaderSyntaxForFile, renderHeaderLines } from "./syntax.mjs";
|
|
3
16
|
|
|
@@ -11,8 +24,12 @@ import { getHeaderSyntaxForFile, renderHeaderLines } from "./syntax.mjs";
|
|
|
11
24
|
* @param {{
|
|
12
25
|
* absoluteFilePath: string,
|
|
13
26
|
* language?: string,
|
|
14
|
-
* syntaxOptions?: { language?: string, enabledDetectors?: string[], disabledDetectors?: string[], detectorSyntaxOverrides?: Record<string, { linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }> },
|
|
27
|
+
* syntaxOptions?: { language?: string, enabledDetectors?: string[], disabledDetectors?: string[], detectorSyntaxOverrides?: Record<string, { linePrefix?: string, lineSeparator?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }> },
|
|
15
28
|
* projectRoot: string,
|
|
29
|
+
* createdByName?: string,
|
|
30
|
+
* createdByEmail?: string,
|
|
31
|
+
* lastModifiedByName?: string,
|
|
32
|
+
* lastModifiedByEmail?: string,
|
|
16
33
|
* projectName: string,
|
|
17
34
|
* authorName: string,
|
|
18
35
|
* authorEmail: string,
|
|
@@ -27,17 +44,21 @@ import { getHeaderSyntaxForFile, renderHeaderLines } from "./syntax.mjs";
|
|
|
27
44
|
export function buildHeader(data) {
|
|
28
45
|
const relativePath = `/${relative(data.projectRoot, data.absoluteFilePath).replace(/\\/g, "/")}`;
|
|
29
46
|
const syntax = getHeaderSyntaxForFile(data.absoluteFilePath, data.syntaxOptions || { language: data.language });
|
|
47
|
+
const createdByName = data.createdByName || data.authorName;
|
|
48
|
+
const createdByEmail = data.createdByEmail || data.authorEmail;
|
|
49
|
+
const lastModifiedByName = data.lastModifiedByName || data.authorName;
|
|
50
|
+
const lastModifiedByEmail = data.lastModifiedByEmail || data.authorEmail;
|
|
30
51
|
const headerLines = [
|
|
31
52
|
`@Project: ${data.projectName}`,
|
|
32
53
|
`@Filename: ${relativePath}`,
|
|
33
54
|
`@Date: ${data.createdAt.date} (${data.createdAt.timestamp})`,
|
|
34
|
-
`@Author: ${
|
|
35
|
-
`@Email: <${
|
|
55
|
+
`@Author: ${createdByName}`,
|
|
56
|
+
`@Email: <${createdByEmail}>`,
|
|
36
57
|
"-----",
|
|
37
|
-
`@Last modified by: ${
|
|
58
|
+
`@Last modified by: ${lastModifiedByName} (${lastModifiedByEmail})`,
|
|
38
59
|
`@Last modified time: ${data.lastModifiedAt.date} (${data.lastModifiedAt.timestamp})`,
|
|
39
60
|
"-----",
|
|
40
|
-
`@Copyright: Copyright (c) ${data.copyrightStartYear}-${data.currentYear} ${data.companyName}
|
|
61
|
+
`@Copyright: Copyright (c) ${data.copyrightStartYear}-${data.currentYear} ${data.companyName} All rights reserved.`
|
|
41
62
|
];
|
|
42
63
|
|
|
43
64
|
return renderHeaderLines(syntax, headerLines);
|
package/src/utils/fs.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/utils/fs.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 { access, readdir, readFile, stat } from "node:fs/promises";
|
|
2
15
|
import { constants as fsConstants } from "node:fs";
|
|
3
16
|
import { dirname, join, resolve } from "node:path";
|
package/src/utils/git.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/utils/git.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 { execFile } from "node:child_process";
|
|
2
15
|
import { promisify } from "node:util";
|
|
3
16
|
|
|
@@ -24,14 +37,39 @@ export async function runGit(cwd, args) {
|
|
|
24
37
|
}
|
|
25
38
|
}
|
|
26
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Parses a signer UID string into author name and optional email.
|
|
42
|
+
* @param {string} signerUid - Raw signer UID (for example: "Name (Comment) <email@example.com>").
|
|
43
|
+
* @returns {{authorName: string | null, authorEmail: string | null}} Parsed signer identity.
|
|
44
|
+
*/
|
|
45
|
+
function parseSignerUid(signerUid) {
|
|
46
|
+
const trimmed = signerUid.trim();
|
|
47
|
+
if (trimmed.length === 0) {
|
|
48
|
+
return { authorName: null, authorEmail: null };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const emailMatch = trimmed.match(/<([^>\n]+)>\s*$/);
|
|
52
|
+
const authorEmail = emailMatch?.[1]?.trim() || null;
|
|
53
|
+
const authorName = (emailMatch ? trimmed.slice(0, emailMatch.index) : trimmed).trim() || null;
|
|
54
|
+
|
|
55
|
+
return { authorName, authorEmail };
|
|
56
|
+
}
|
|
57
|
+
|
|
27
58
|
/**
|
|
28
59
|
* Detects git author name and email from config or commit history.
|
|
29
60
|
* @param {string} cwd - Project directory.
|
|
61
|
+
* @param {{useGpgSignerAuthor?: boolean}} [options={}] - Detection options.
|
|
30
62
|
* @returns {Promise<{authorName: string | null, authorEmail: string | null}>} Author information.
|
|
31
63
|
*/
|
|
32
|
-
export async function detectGitAuthor(cwd) {
|
|
64
|
+
export async function detectGitAuthor(cwd, options = {}) {
|
|
33
65
|
let authorName = await runGit(cwd, ["config", "--get", "user.name"]);
|
|
34
66
|
let authorEmail = await runGit(cwd, ["config", "--get", "user.email"]);
|
|
67
|
+
if (options.useGpgSignerAuthor === true) {
|
|
68
|
+
const signerUid = await runGit(cwd, ["log", "-1", "--format=%GS"]);
|
|
69
|
+
const signerIdentity = parseSignerUid(signerUid || "");
|
|
70
|
+
authorName = signerIdentity.authorName || authorName;
|
|
71
|
+
authorEmail = authorEmail || signerIdentity.authorEmail;
|
|
72
|
+
}
|
|
35
73
|
|
|
36
74
|
if (!authorName || !authorEmail) {
|
|
37
75
|
const fallback = await runGit(cwd, ["log", "-1", "--format=%an|%ae"]);
|
package/src/utils/time.mjs
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/fix-headers
|
|
3
|
+
* @Filename: /src/utils/time.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 Date/time helpers used for header timestamp formatting.
|
|
3
16
|
* @module fix-headers/utils/time
|