@cldmv/fix-headers 1.0.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.
Files changed (47) hide show
  1. package/README.md +157 -0
  2. package/index.cjs +39 -0
  3. package/index.mjs +7 -0
  4. package/package.json +56 -0
  5. package/src/cli.mjs +216 -0
  6. package/src/constants.mjs +15 -0
  7. package/src/core/file-discovery.mjs +133 -0
  8. package/src/core/fix-headers.mjs +210 -0
  9. package/src/detect/project.mjs +175 -0
  10. package/src/detectors/css.mjs +54 -0
  11. package/src/detectors/go.mjs +52 -0
  12. package/src/detectors/html.mjs +54 -0
  13. package/src/detectors/index.mjs +160 -0
  14. package/src/detectors/node.mjs +63 -0
  15. package/src/detectors/php.mjs +53 -0
  16. package/src/detectors/python.mjs +48 -0
  17. package/src/detectors/rust.mjs +49 -0
  18. package/src/detectors/shared.mjs +32 -0
  19. package/src/fix-header.mjs +30 -0
  20. package/src/header/parser.mjs +76 -0
  21. package/src/header/syntax.mjs +54 -0
  22. package/src/header/template.mjs +44 -0
  23. package/src/utils/fs.mjs +125 -0
  24. package/src/utils/git.mjs +97 -0
  25. package/src/utils/time.mjs +31 -0
  26. package/types/index.d.mts +2 -0
  27. package/types/src/cli.d.mts +44 -0
  28. package/types/src/constants.d.mts +7 -0
  29. package/types/src/core/file-discovery.d.mts +22 -0
  30. package/types/src/core/fix-headers.d.mts +86 -0
  31. package/types/src/detect/project.d.mts +81 -0
  32. package/types/src/detectors/css.d.mts +25 -0
  33. package/types/src/detectors/go.d.mts +25 -0
  34. package/types/src/detectors/html.d.mts +25 -0
  35. package/types/src/detectors/index.d.mts +66 -0
  36. package/types/src/detectors/node.d.mts +25 -0
  37. package/types/src/detectors/php.d.mts +25 -0
  38. package/types/src/detectors/python.d.mts +23 -0
  39. package/types/src/detectors/rust.d.mts +25 -0
  40. package/types/src/detectors/shared.d.mts +14 -0
  41. package/types/src/fix-header.d.mts +12 -0
  42. package/types/src/header/parser.d.mts +43 -0
  43. package/types/src/header/syntax.d.mts +44 -0
  44. package/types/src/header/template.d.mts +52 -0
  45. package/types/src/utils/fs.d.mts +50 -0
  46. package/types/src/utils/git.d.mts +40 -0
  47. package/types/src/utils/time.d.mts +19 -0
@@ -0,0 +1,210 @@
1
+ import { readFile, stat, writeFile } from "node:fs/promises";
2
+ import { relative, resolve } from "node:path";
3
+ import { discoverFiles } from "./file-discovery.mjs";
4
+ import { resolveProjectMetadata } from "../detect/project.mjs";
5
+ import { buildHeader } from "../header/template.mjs";
6
+ import { replaceOrInsertHeader } from "../header/parser.mjs";
7
+ import { readFileDates } from "../utils/fs.mjs";
8
+ import { getGitCreationDate, getGitLastModifiedDate } from "../utils/git.mjs";
9
+ import { toDatePayload } from "../utils/time.mjs";
10
+
11
+ /**
12
+ * @typedef {{
13
+ * cwd?: string,
14
+ * input?: string,
15
+ * dryRun?: boolean,
16
+ * configFile?: string,
17
+ * enabledDetectors?: string[],
18
+ * disabledDetectors?: string[],
19
+ * detectorSyntaxOverrides?: Record<string, { linePrefix?: string, blockStart?: string, blockLinePrefix?: string, blockEnd?: string }>,
20
+ * includeFolders?: string[],
21
+ * excludeFolders?: string[],
22
+ * includeExtensions?: string[],
23
+ * projectName?: string,
24
+ * language?: string,
25
+ * projectRoot?: string,
26
+ * marker?: string | null,
27
+ * authorName?: string,
28
+ * authorEmail?: string,
29
+ * companyName?: string,
30
+ * copyrightStartYear?: number
31
+ * }} FixHeadersOptions
32
+ */
33
+
34
+ /**
35
+ * @typedef {{
36
+ * metadata: {
37
+ * projectName: string,
38
+ * language: string,
39
+ * projectRoot: string,
40
+ * marker: string | null,
41
+ * authorName: string,
42
+ * authorEmail: string,
43
+ * companyName: string,
44
+ * copyrightStartYear: number
45
+ * },
46
+ * detectedProjects: string[],
47
+ * filesScanned: number,
48
+ * filesUpdated: number,
49
+ * dryRun: boolean,
50
+ * changes: Array<{file: string, changed: boolean}>
51
+ * }} FixHeadersResult
52
+ */
53
+
54
+ /**
55
+ * Resolves runtime options including optional JSON config file loading.
56
+ * @param {FixHeadersOptions} options - Runtime options.
57
+ * @returns {Promise<FixHeadersOptions>} Effective runtime options.
58
+ */
59
+ async function resolveRuntimeOptions(options) {
60
+ const configFile = typeof options.configFile === "string" && options.configFile.trim().length > 0 ? options.configFile.trim() : null;
61
+
62
+ if (!configFile) {
63
+ return options;
64
+ }
65
+
66
+ const baseCwd = typeof options.cwd === "string" && options.cwd.length > 0 ? options.cwd : process.cwd();
67
+ const absoluteConfigPath = resolve(baseCwd, configFile);
68
+ const raw = await readFile(absoluteConfigPath, "utf8");
69
+ const parsed = JSON.parse(raw);
70
+
71
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
72
+ throw new Error(`Config file must contain a JSON object: ${configFile}`);
73
+ }
74
+
75
+ const merged = {
76
+ ...parsed,
77
+ ...options
78
+ };
79
+
80
+ delete merged.configFile;
81
+ /** @type {FixHeadersOptions} */
82
+ const output = merged;
83
+ return output;
84
+ }
85
+
86
+ /**
87
+ * @fileoverview Main header-fixing engine with auto-detection and override support.
88
+ * @module fix-headers/core/fix-headers
89
+ */
90
+
91
+ /**
92
+ * Fixes headers in a project using auto-detected metadata unless overridden.
93
+ * @param {FixHeadersOptions} [options={}] - Runtime options.
94
+ * @returns {Promise<FixHeadersResult>} Process report.
95
+ */
96
+ export async function fixHeaders(options = {}) {
97
+ const effectiveOptions = await resolveRuntimeOptions(options);
98
+ const scanRoot = resolve(effectiveOptions.projectRoot || effectiveOptions.cwd || process.cwd());
99
+ const metadata = await resolveProjectMetadata({
100
+ ...effectiveOptions,
101
+ cwd: scanRoot
102
+ });
103
+ const dryRun = effectiveOptions.dryRun === true;
104
+
105
+ let files = [];
106
+ if (typeof effectiveOptions.input === "string" && effectiveOptions.input.trim().length > 0) {
107
+ const inputPath = resolve(scanRoot, effectiveOptions.input);
108
+ const targetStats = await stat(inputPath).catch(() => null);
109
+ if (!targetStats) {
110
+ throw new Error(`Input path does not exist: ${effectiveOptions.input}`);
111
+ }
112
+
113
+ if (targetStats.isFile()) {
114
+ files = [inputPath];
115
+ } else if (targetStats.isDirectory()) {
116
+ files = await discoverFiles({
117
+ projectRoot: inputPath,
118
+ language: metadata.language,
119
+ enabledDetectors: effectiveOptions.enabledDetectors,
120
+ disabledDetectors: effectiveOptions.disabledDetectors,
121
+ includeFolders: effectiveOptions.includeFolders,
122
+ excludeFolders: effectiveOptions.excludeFolders,
123
+ includeExtensions: effectiveOptions.includeExtensions
124
+ });
125
+ } else {
126
+ throw new Error(`Input path must be a file or directory: ${effectiveOptions.input}`);
127
+ }
128
+ } else {
129
+ files = await discoverFiles({
130
+ projectRoot: scanRoot,
131
+ language: metadata.language,
132
+ enabledDetectors: effectiveOptions.enabledDetectors,
133
+ disabledDetectors: effectiveOptions.disabledDetectors,
134
+ includeFolders: effectiveOptions.includeFolders,
135
+ excludeFolders: effectiveOptions.excludeFolders,
136
+ includeExtensions: effectiveOptions.includeExtensions
137
+ });
138
+ }
139
+
140
+ const currentYear = new Date().getFullYear();
141
+ const changes = [];
142
+ const detectedProjects = new Set();
143
+ let filesUpdated = 0;
144
+
145
+ for (const filePath of files) {
146
+ const fileMetadata = await resolveProjectMetadata({
147
+ ...effectiveOptions,
148
+ cwd: scanRoot,
149
+ targetFilePath: filePath
150
+ });
151
+
152
+ detectedProjects.add(`${fileMetadata.language}:${fileMetadata.projectRoot}`);
153
+ const relativePath = relative(scanRoot, filePath);
154
+ const original = await readFile(filePath, "utf8");
155
+ const filesystemDates = await readFileDates(filePath);
156
+
157
+ const metadataRelativePath = relative(fileMetadata.projectRoot, filePath);
158
+ const gitCreated = await getGitCreationDate(fileMetadata.projectRoot, metadataRelativePath);
159
+ const gitLastUpdated = await getGitLastModifiedDate(fileMetadata.projectRoot, metadataRelativePath);
160
+
161
+ const createdAt = gitCreated || toDatePayload(filesystemDates.createdAt);
162
+ const lastModifiedAt = gitLastUpdated || toDatePayload(filesystemDates.updatedAt);
163
+
164
+ const header = buildHeader({
165
+ absoluteFilePath: filePath,
166
+ language: fileMetadata.language,
167
+ syntaxOptions: {
168
+ language: fileMetadata.language,
169
+ enabledDetectors: effectiveOptions.enabledDetectors,
170
+ disabledDetectors: effectiveOptions.disabledDetectors,
171
+ detectorSyntaxOverrides: effectiveOptions.detectorSyntaxOverrides
172
+ },
173
+ projectRoot: fileMetadata.projectRoot,
174
+ projectName: fileMetadata.projectName,
175
+ authorName: fileMetadata.authorName,
176
+ authorEmail: fileMetadata.authorEmail,
177
+ createdAt,
178
+ lastModifiedAt,
179
+ copyrightStartYear: fileMetadata.copyrightStartYear,
180
+ companyName: fileMetadata.companyName,
181
+ currentYear
182
+ });
183
+
184
+ const replacement = replaceOrInsertHeader(original, header, filePath, {
185
+ language: fileMetadata.language,
186
+ enabledDetectors: effectiveOptions.enabledDetectors,
187
+ disabledDetectors: effectiveOptions.disabledDetectors,
188
+ detectorSyntaxOverrides: effectiveOptions.detectorSyntaxOverrides
189
+ });
190
+ changes.push({ file: relativePath, changed: replacement.changed });
191
+
192
+ if (!replacement.changed) {
193
+ continue;
194
+ }
195
+
196
+ filesUpdated += 1;
197
+ if (!dryRun) {
198
+ await writeFile(filePath, replacement.nextContent, "utf8");
199
+ }
200
+ }
201
+
202
+ return {
203
+ metadata,
204
+ detectedProjects: Array.from(detectedProjects),
205
+ filesScanned: files.length,
206
+ filesUpdated,
207
+ dryRun,
208
+ changes
209
+ };
210
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @Project: @cldmv/fix-headers
3
+ * @Filename: /src/detect/project.mjs
4
+ * @Date: 2026-03-01 13:32:57 -08:00 (1772400777)
5
+ * @Author: Nate Hyson <CLDMV>
6
+ * @Email: <Shinrai@users.noreply.github.com>
7
+ * -----
8
+ * @Last modified by: Nate Hyson <CLDMV> (Shinrai@users.noreply.github.com)
9
+ * @Last modified time: 2026-03-01 16:29:34 -08:00 (1772411374)
10
+ * -----
11
+ * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
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.
25
+ */
26
+
27
+ import { dirname, extname, join, resolve } from "node:path";
28
+ import { DEFAULT_COMPANY_NAME } from "../constants.mjs";
29
+ import { getEnabledDetectors } from "../detectors/index.mjs";
30
+ import { readTextIfExists } from "../utils/fs.mjs";
31
+ import { detectGitAuthor } from "../utils/git.mjs";
32
+
33
+ /**
34
+ * @fileoverview Auto-detects project metadata from known language markers and git config.
35
+ * @module fix-headers/detect/project
36
+ */
37
+
38
+ async function findClosestDetectorMatch(startPath, detectors, preferredExtension = "") {
39
+ const extension = typeof preferredExtension === "string" ? preferredExtension.trim().toLowerCase() : "";
40
+ const extensionMatched =
41
+ extension.length > 0
42
+ ? detectors.filter((detector) => Array.isArray(detector.extensions) && detector.extensions.includes(extension))
43
+ : [];
44
+ const detectorsToSearch = extensionMatched.length > 0 ? extensionMatched : detectors;
45
+
46
+ const matches = await Promise.all(
47
+ detectorsToSearch.map(async (detector) => {
48
+ if (typeof detector.findNearestConfig !== "function") {
49
+ return null;
50
+ }
51
+
52
+ const located = await detector.findNearestConfig(startPath);
53
+ if (!located) {
54
+ return null;
55
+ }
56
+
57
+ return {
58
+ detector,
59
+ root: located.root,
60
+ marker: located.marker
61
+ };
62
+ })
63
+ );
64
+
65
+ const validMatches = matches.filter((item) => item !== null);
66
+ if (validMatches.length === 0) {
67
+ return null;
68
+ }
69
+
70
+ let closest = validMatches[0];
71
+ for (const candidate of validMatches.slice(1)) {
72
+ if (candidate.root.length > closest.root.length) {
73
+ closest = candidate;
74
+ continue;
75
+ }
76
+
77
+ if (candidate.root.length === closest.root.length) {
78
+ const candidatePriority = Number.isFinite(candidate.detector.priority) ? candidate.detector.priority : 0;
79
+ const closestPriority = Number.isFinite(closest.detector.priority) ? closest.detector.priority : 0;
80
+ if (candidatePriority > closestPriority) {
81
+ closest = candidate;
82
+ }
83
+ }
84
+ }
85
+
86
+ return closest;
87
+ }
88
+
89
+ /**
90
+ * Detects project root and language by scanning known marker files.
91
+ * @param {string} cwd - Starting working directory.
92
+ * @param {{ detectors?: { id: string, extensions: string[], priority?: number, findNearestConfig: (path: string) => Promise<{ root: string, marker: string } | null>, parseProjectName: (marker: string, content: string, rootDirName: string) => string }[], enabledDetectors?: string[], disabledDetectors?: string[], preferredExtension?: string }} [options={}] - Detection options.
93
+ * @returns {Promise<{
94
+ * language: string,
95
+ * rootDir: string,
96
+ * marker: string | null,
97
+ * projectName: string
98
+ * }>} Detection result.
99
+ */
100
+ export async function detectProjectFromMarkers(cwd, options = {}) {
101
+ const detectors = Array.isArray(options.detectors) ? options.detectors : getEnabledDetectors(options);
102
+ const located = await findClosestDetectorMatch(cwd, detectors, options.preferredExtension);
103
+
104
+ if (located) {
105
+ const markerPath = join(located.root, located.marker);
106
+ const markerContent = (await readTextIfExists(markerPath)) || "";
107
+ const rootDirName = located.root.split("/").filter(Boolean).at(-1) || "project";
108
+ const projectName = located.detector.parseProjectName(located.marker, markerContent, rootDirName);
109
+
110
+ return {
111
+ language: located.detector.id,
112
+ rootDir: located.root,
113
+ marker: located.marker,
114
+ projectName
115
+ };
116
+ }
117
+
118
+ const fallbackRoot = resolve(cwd);
119
+ const fallbackName = fallbackRoot.split("/").filter(Boolean).at(-1) || "project";
120
+ return {
121
+ language: "unknown",
122
+ rootDir: fallbackRoot,
123
+ marker: null,
124
+ projectName: fallbackName
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Resolves project metadata with override support for every auto-detected field.
130
+ * @param {{
131
+ * cwd?: string,
132
+ * targetFilePath?: string,
133
+ * enabledDetectors?: string[],
134
+ * disabledDetectors?: string[],
135
+ * projectName?: string,
136
+ * language?: string,
137
+ * projectRoot?: string,
138
+ * marker?: string | null,
139
+ * authorName?: string,
140
+ * authorEmail?: string,
141
+ * companyName?: string,
142
+ * copyrightStartYear?: number
143
+ * }} [options={}] - Detection options and overrides.
144
+ * @returns {Promise<{
145
+ * projectName: string,
146
+ * language: string,
147
+ * projectRoot: string,
148
+ * marker: string | null,
149
+ * authorName: string,
150
+ * authorEmail: string,
151
+ * companyName: string,
152
+ * copyrightStartYear: number
153
+ * }>} Final metadata.
154
+ */
155
+ export async function resolveProjectMetadata(options = {}) {
156
+ const basePath = options.targetFilePath || options.cwd || process.cwd();
157
+ const cwd = resolve(basePath);
158
+ const detectors = getEnabledDetectors(options);
159
+ const detectFrom = options.targetFilePath ? dirname(cwd) : cwd;
160
+ const preferredExtension = options.targetFilePath ? extname(cwd).toLowerCase() : "";
161
+ const detected = await detectProjectFromMarkers(detectFrom, { detectors, preferredExtension });
162
+ const gitAuthor = await detectGitAuthor(detected.rootDir);
163
+ const currentYear = new Date().getFullYear();
164
+
165
+ return {
166
+ projectName: options.projectName || detected.projectName,
167
+ language: options.language || detected.language,
168
+ projectRoot: options.projectRoot || detected.rootDir,
169
+ marker: options.marker === undefined ? detected.marker : options.marker,
170
+ authorName: options.authorName || gitAuthor.authorName || "Unknown Author",
171
+ authorEmail: options.authorEmail || gitAuthor.authorEmail || "unknown@example.com",
172
+ companyName: options.companyName || DEFAULT_COMPANY_NAME,
173
+ copyrightStartYear: Number.isInteger(options.copyrightStartYear) ? Number(options.copyrightStartYear) : currentYear
174
+ };
175
+ }
@@ -0,0 +1,54 @@
1
+ import { extname } from "node:path";
2
+ import { findNearestMarker } from "./shared.mjs";
3
+
4
+ /**
5
+ * @fileoverview CSS detector implementation.
6
+ * @module fix-headers/detectors/css
7
+ */
8
+
9
+ const markers = ["package.json", "postcss.config.js", "postcss.config.cjs", "postcss.config.mjs"];
10
+ const extensions = [".css"];
11
+
12
+ /**
13
+ * Parses CSS project name from marker context.
14
+ * @param {string} rootDirName - Fallback directory name.
15
+ * @returns {string} Project name.
16
+ */
17
+ function parseCssProjectName(rootDirName) {
18
+ return rootDirName;
19
+ }
20
+
21
+ /**
22
+ * Resolves block comment syntax for CSS files.
23
+ * @param {string} filePath - File path.
24
+ * @returns {{kind: "block", blockStart: string, blockLinePrefix: string, blockEnd: string} | null} Syntax descriptor.
25
+ */
26
+ function resolveCssCommentSyntax(filePath) {
27
+ const extension = extname(filePath).toLowerCase();
28
+ if (extensions.includes(extension)) {
29
+ return {
30
+ kind: "block",
31
+ blockStart: "/*",
32
+ blockLinePrefix: " *\t",
33
+ blockEnd: " */"
34
+ };
35
+ }
36
+ return null;
37
+ }
38
+
39
+ export const detector = {
40
+ id: "css",
41
+ priority: 70,
42
+ markers,
43
+ extensions,
44
+ enabledByDefault: true,
45
+ findNearestConfig(startPath) {
46
+ return findNearestMarker(startPath, markers);
47
+ },
48
+ parseProjectName(_marker, _markerContent, rootDirName) {
49
+ return parseCssProjectName(rootDirName);
50
+ },
51
+ resolveCommentSyntax(filePath) {
52
+ return resolveCssCommentSyntax(filePath);
53
+ }
54
+ };
@@ -0,0 +1,52 @@
1
+ import { extname } from "node:path";
2
+ import { findNearestMarker } from "./shared.mjs";
3
+
4
+ /**
5
+ * @fileoverview Go detector implementation.
6
+ * @module fix-headers/detectors/go
7
+ */
8
+
9
+ const markers = ["go.mod"];
10
+ const extensions = [".go"];
11
+
12
+ /**
13
+ * Resolves Go comment syntax.
14
+ * @param {string} filePath - File path.
15
+ * @returns {{kind: "block", blockStart: string, blockLinePrefix: string, blockEnd: string} | null} Syntax descriptor.
16
+ */
17
+ function resolveGoCommentSyntax(filePath) {
18
+ const extension = extname(filePath).toLowerCase();
19
+ if (extensions.includes(extension)) {
20
+ return {
21
+ kind: "block",
22
+ blockStart: "/**",
23
+ blockLinePrefix: " *\t",
24
+ blockEnd: " */"
25
+ };
26
+ }
27
+ return null;
28
+ }
29
+
30
+ export const detector = {
31
+ id: "go",
32
+ priority: 80,
33
+ markers,
34
+ extensions,
35
+ enabledByDefault: true,
36
+ findNearestConfig(startPath) {
37
+ return findNearestMarker(startPath, markers);
38
+ },
39
+ parseProjectName(_marker, markerContent, rootDirName) {
40
+ const moduleMatch = markerContent.match(/^module\s+(.+)$/m);
41
+ if (moduleMatch?.[1]) {
42
+ const value = moduleMatch[1].trim();
43
+ if (value.length > 0) {
44
+ return value;
45
+ }
46
+ }
47
+ return rootDirName;
48
+ },
49
+ resolveCommentSyntax(filePath) {
50
+ return resolveGoCommentSyntax(filePath);
51
+ }
52
+ };
@@ -0,0 +1,54 @@
1
+ import { extname } from "node:path";
2
+ import { findNearestMarker } from "./shared.mjs";
3
+
4
+ /**
5
+ * @fileoverview HTML detector implementation.
6
+ * @module fix-headers/detectors/html
7
+ */
8
+
9
+ const markers = ["index.html", "vite.config.js", "vite.config.mjs", "next.config.js", "next.config.mjs"];
10
+ const extensions = [".html", ".htm"];
11
+
12
+ /**
13
+ * Parses HTML project name from marker context.
14
+ * @param {string} rootDirName - Fallback directory name.
15
+ * @returns {string} Project name.
16
+ */
17
+ function parseHtmlProjectName(rootDirName) {
18
+ return rootDirName;
19
+ }
20
+
21
+ /**
22
+ * Resolves HTML comment syntax for HTML-like files.
23
+ * @param {string} filePath - File path.
24
+ * @returns {{kind: "html", blockStart: string, blockLinePrefix: string, blockEnd: string} | null} Syntax descriptor.
25
+ */
26
+ function resolveHtmlCommentSyntax(filePath) {
27
+ const extension = extname(filePath).toLowerCase();
28
+ if (extensions.includes(extension)) {
29
+ return {
30
+ kind: "html",
31
+ blockStart: "<!--",
32
+ blockLinePrefix: "\t",
33
+ blockEnd: "-->"
34
+ };
35
+ }
36
+ return null;
37
+ }
38
+
39
+ export const detector = {
40
+ id: "html",
41
+ priority: 70,
42
+ markers,
43
+ extensions,
44
+ enabledByDefault: true,
45
+ findNearestConfig(startPath) {
46
+ return findNearestMarker(startPath, markers);
47
+ },
48
+ parseProjectName(_marker, _markerContent, rootDirName) {
49
+ return parseHtmlProjectName(rootDirName);
50
+ },
51
+ resolveCommentSyntax(filePath) {
52
+ return resolveHtmlCommentSyntax(filePath);
53
+ }
54
+ };