declapract-typescript-ehmpathy 0.47.31 → 0.47.33
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.
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
import { FileCheckType } from 'declapract';
|
|
1
|
+
import { FileCheckType, type FileFixFunction } from 'declapract';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
2
4
|
|
|
3
5
|
// check that the file contains the core structure
|
|
4
6
|
export const check = FileCheckType.CONTAINS;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* .what = replaces target file with expected content
|
|
10
|
+
* .why = ensures the skill file matches the best-practice version
|
|
11
|
+
*/
|
|
12
|
+
export const fix: FileFixFunction = () => {
|
|
13
|
+
const expectedContent = readFileSync(
|
|
14
|
+
join(dirname(__filename), 'use.apikeys.sh'),
|
|
15
|
+
'utf-8',
|
|
16
|
+
);
|
|
17
|
+
return { contents: expectedContent };
|
|
18
|
+
};
|
|
@@ -8,53 +8,126 @@ export const check: FileCheckType = FileCheckType.CONTAINS;
|
|
|
8
8
|
/**
|
|
9
9
|
* sections of gitattributes entries to ensure exist
|
|
10
10
|
*/
|
|
11
|
-
const SECTIONS
|
|
11
|
+
const SECTIONS: {
|
|
12
|
+
header: { latest: string; priors: string[] };
|
|
13
|
+
entries: { latest: string[]; priors: string[] };
|
|
14
|
+
}[] = [
|
|
12
15
|
{
|
|
13
|
-
header:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
header: {
|
|
17
|
+
latest:
|
|
18
|
+
'# exclude package locks from git diff; https://stackoverflow.com/a/72834452/3068233',
|
|
19
|
+
priors: [
|
|
20
|
+
'# exclude package-lock from git diff; https://stackoverflow.com/a/72834452/3068233',
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
entries: {
|
|
24
|
+
latest: ['pnpm-lock.yaml -diff', 'package-lock.json -diff'],
|
|
25
|
+
priors: ['pnpm-lock.json -diff'],
|
|
26
|
+
},
|
|
16
27
|
},
|
|
17
28
|
{
|
|
18
|
-
header:
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
header: {
|
|
30
|
+
latest:
|
|
31
|
+
'# auto-resolve lock file conflicts by taking theirs; run install after merge',
|
|
32
|
+
priors: [],
|
|
33
|
+
},
|
|
34
|
+
entries: {
|
|
35
|
+
latest: ['pnpm-lock.yaml merge=theirs', 'package-lock.json merge=theirs'],
|
|
36
|
+
priors: ['pnpm-lock.json merge=theirs'],
|
|
37
|
+
},
|
|
21
38
|
},
|
|
22
39
|
];
|
|
23
40
|
|
|
41
|
+
/**
|
|
42
|
+
* all latest entries managed by this practice (for deduplication)
|
|
43
|
+
*/
|
|
44
|
+
const ALL_LATEST_ENTRIES = SECTIONS.flatMap((s) => s.entries.latest);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* all prior entries that should be removed
|
|
48
|
+
*/
|
|
49
|
+
const ALL_PRIOR_ENTRIES = SECTIONS.flatMap((s) => s.entries.priors);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* all prior headers that should be removed
|
|
53
|
+
*/
|
|
54
|
+
const ALL_PRIOR_HEADERS = SECTIONS.flatMap((s) => s.header.priors);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* removes legacy headers, duplicate entries, and cleans up empty lines
|
|
58
|
+
*/
|
|
59
|
+
const cleanContent = (content: string): string => {
|
|
60
|
+
const lines = content.split('\n');
|
|
61
|
+
const seenEntries = new Set<string>();
|
|
62
|
+
const cleanedLines: string[] = [];
|
|
63
|
+
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
const trimmed = line.trim();
|
|
66
|
+
|
|
67
|
+
// skip prior headers
|
|
68
|
+
if (ALL_PRIOR_HEADERS.includes(trimmed)) continue;
|
|
69
|
+
|
|
70
|
+
// skip prior entries
|
|
71
|
+
if (ALL_PRIOR_ENTRIES.includes(trimmed)) continue;
|
|
72
|
+
|
|
73
|
+
// skip duplicate latest entries (keep first occurrence)
|
|
74
|
+
if (ALL_LATEST_ENTRIES.includes(trimmed)) {
|
|
75
|
+
if (seenEntries.has(trimmed)) continue;
|
|
76
|
+
seenEntries.add(trimmed);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
cleanedLines.push(line);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// collapse multiple consecutive empty lines into one
|
|
83
|
+
const collapsedLines: string[] = [];
|
|
84
|
+
let prevWasEmpty = false;
|
|
85
|
+
for (const line of cleanedLines) {
|
|
86
|
+
const isEmpty = line.trim() === '';
|
|
87
|
+
if (isEmpty && prevWasEmpty) continue;
|
|
88
|
+
collapsedLines.push(line);
|
|
89
|
+
prevWasEmpty = isEmpty;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return collapsedLines.join('\n').trim();
|
|
93
|
+
};
|
|
94
|
+
|
|
24
95
|
/**
|
|
25
96
|
* ensures a section with header and entries exists in the content
|
|
26
97
|
*/
|
|
27
98
|
const ensureSection = (
|
|
28
99
|
content: string,
|
|
29
|
-
section:
|
|
100
|
+
section: (typeof SECTIONS)[number],
|
|
30
101
|
): string => {
|
|
31
102
|
const lines = content.split('\n');
|
|
32
103
|
|
|
33
104
|
// find the header line index
|
|
34
|
-
const headerIndex = lines.findIndex(
|
|
105
|
+
const headerIndex = lines.findIndex(
|
|
106
|
+
(line) => line.trim() === section.header.latest,
|
|
107
|
+
);
|
|
35
108
|
|
|
36
109
|
// if header not found, append the whole section at the end
|
|
37
110
|
if (headerIndex === -1) {
|
|
38
111
|
return (
|
|
39
112
|
content.trimEnd() +
|
|
40
113
|
'\n\n' +
|
|
41
|
-
[section.header, ...section.entries].join('\n') +
|
|
114
|
+
[section.header.latest, ...section.entries.latest].join('\n') +
|
|
42
115
|
'\n'
|
|
43
116
|
);
|
|
44
117
|
}
|
|
45
118
|
|
|
46
|
-
// find which entries are
|
|
47
|
-
const
|
|
119
|
+
// find which entries are absent
|
|
120
|
+
const entriesAbsent = section.entries.latest.filter(
|
|
48
121
|
(entry) => !content.includes(entry),
|
|
49
122
|
);
|
|
50
123
|
|
|
51
|
-
// if
|
|
52
|
-
if (
|
|
124
|
+
// if all entries present, no fix required
|
|
125
|
+
if (entriesAbsent.length === 0) return content;
|
|
53
126
|
|
|
54
|
-
// insert
|
|
127
|
+
// insert absent entries right after the header
|
|
55
128
|
const newLines = [
|
|
56
129
|
...lines.slice(0, headerIndex + 1),
|
|
57
|
-
...
|
|
130
|
+
...entriesAbsent,
|
|
58
131
|
...lines.slice(headerIndex + 1),
|
|
59
132
|
];
|
|
60
133
|
|
|
@@ -62,22 +135,25 @@ const ensureSection = (
|
|
|
62
135
|
};
|
|
63
136
|
|
|
64
137
|
/**
|
|
65
|
-
* fix by
|
|
138
|
+
* fix by cleanup of legacy content and ensure all sections are present
|
|
66
139
|
*/
|
|
67
140
|
export const fix: FileFixFunction = (contents) => {
|
|
68
141
|
// if no contents, create the file with all sections
|
|
69
142
|
if (!contents) {
|
|
70
143
|
const allSections = SECTIONS.map((s) =>
|
|
71
|
-
[s.header, ...s.entries].join('\n'),
|
|
144
|
+
[s.header.latest, ...s.entries.latest].join('\n'),
|
|
72
145
|
).join('\n\n');
|
|
73
146
|
return { contents: allSections + '\n' };
|
|
74
147
|
}
|
|
75
148
|
|
|
149
|
+
// clean up legacy headers and duplicates first
|
|
150
|
+
let result = cleanContent(contents);
|
|
151
|
+
|
|
76
152
|
// ensure each section exists
|
|
77
|
-
let result = contents;
|
|
78
153
|
for (const section of SECTIONS) {
|
|
79
154
|
result = ensureSection(result, section);
|
|
80
155
|
}
|
|
81
156
|
|
|
82
|
-
|
|
157
|
+
// normalize to exactly one final newline
|
|
158
|
+
return { contents: result.trimEnd() + '\n' };
|
|
83
159
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "declapract-typescript-ehmpathy",
|
|
3
3
|
"author": "ehmpathy",
|
|
4
4
|
"description": "declapract best practices declarations for typescript",
|
|
5
|
-
"version": "0.47.
|
|
5
|
+
"version": "0.47.33",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"repository": "ehmpathy/declapract-typescript-ehmpathy",
|