devsplain 2.2.2 → 2.2.3
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/bin/cli.js +34 -5
- package/bin/post-commit.js +10 -10
- package/bin/setup-hook.js +22 -4
- package/lib/config.js +11 -4
- package/lib/llm.js +61 -19
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -231,6 +231,7 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
|
|
|
231
231
|
if (resetsAtLineEnd) {
|
|
232
232
|
inSingle = false;
|
|
233
233
|
inDouble = false;
|
|
234
|
+
inRegex = false;
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
return inBacktick || inTripleDouble || inTripleSingle || inSingle || inDouble || inCppRawString || inRegex;
|
|
@@ -455,6 +456,7 @@ function analyzeComments(lines, ext = '') {
|
|
|
455
456
|
if (resetsAtLineEnd) {
|
|
456
457
|
inSingle = false;
|
|
457
458
|
inDouble = false;
|
|
459
|
+
inRegex = false;
|
|
458
460
|
}
|
|
459
461
|
const isEntirelyInsideBlock = isInsideBlockStart && (inBlockJS || inBlockHTML || (commentStartIndex === -1));
|
|
460
462
|
let isPureComment = false;
|
|
@@ -663,6 +665,33 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
663
665
|
const filteredText = filtered.map(line => line.text);
|
|
664
666
|
const filteredIndices = filtered.map(line => line.originalIndex);
|
|
665
667
|
|
|
668
|
+
// Validate that all inserted lines (originalIndex === -1) are valid comments or empty lines [ds]
|
|
669
|
+
const insertedLines = annotated.filter(line => line.originalIndex === -1);
|
|
670
|
+
let inInsertedBlock = false;
|
|
671
|
+
for (const item of insertedLines) {
|
|
672
|
+
const trimmed = item.text.trim();
|
|
673
|
+
if (!trimmed) continue;
|
|
674
|
+
if (inInsertedBlock) {
|
|
675
|
+
if (trimmed.includes('*/') || trimmed.includes('-->')) {
|
|
676
|
+
inInsertedBlock = false;
|
|
677
|
+
}
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
const isValidComment =
|
|
681
|
+
trimmed.startsWith('//') ||
|
|
682
|
+
trimmed.startsWith('/*') ||
|
|
683
|
+
trimmed.startsWith('*') ||
|
|
684
|
+
trimmed.startsWith('#') ||
|
|
685
|
+
trimmed.startsWith('<!--') ||
|
|
686
|
+
trimmed.startsWith('--');
|
|
687
|
+
if (!isValidComment) {
|
|
688
|
+
throw new Error(`Safety Assertion Failed: Refused to insert non-comment code: "${trimmed}"`);
|
|
689
|
+
}
|
|
690
|
+
if ((trimmed.startsWith('/*') && !trimmed.includes('*/')) || (trimmed.startsWith('<!--') && !trimmed.includes('-->'))) {
|
|
691
|
+
inInsertedBlock = true;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
666
695
|
const textEqual = filteredText.every((text, idx) => {
|
|
667
696
|
const origIdx = filteredIndices[idx];
|
|
668
697
|
const originalLine = originalLines[origIdx];
|
|
@@ -710,8 +739,7 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
710
739
|
}
|
|
711
740
|
|
|
712
741
|
if (!textEqual || !indicesSequential) {
|
|
713
|
-
|
|
714
|
-
process.exit(1);
|
|
742
|
+
throw new Error("Safety Assertion Failed: Spliced code does not match original code minus comments!");
|
|
715
743
|
}
|
|
716
744
|
|
|
717
745
|
return annotated.map(line => line.text).join(lineEnding);
|
|
@@ -818,7 +846,7 @@ Options:
|
|
|
818
846
|
const hasOverwriteFlag = args.includes('--overwrite');
|
|
819
847
|
const hasKeepFlag = args.includes('--keep');
|
|
820
848
|
|
|
821
|
-
if (process.env.NODE_ENV !== 'test' && isGitDirty() && !isForce) {
|
|
849
|
+
if (process.env.NODE_ENV !== 'test' && isGitDirty() && !isForce && !isDryRun) {
|
|
822
850
|
console.error("Error: Git working tree is dirty. Please commit or stash your changes, or use --force to bypass this check.");
|
|
823
851
|
rl.close();
|
|
824
852
|
process.exit(1);
|
|
@@ -871,9 +899,10 @@ Options:
|
|
|
871
899
|
|
|
872
900
|
function isPathIgnored(targetPath) {
|
|
873
901
|
const filename = path.basename(targetPath);
|
|
902
|
+
const relPath = path.relative(process.cwd(), targetPath).replace(/\\/g, '/');
|
|
874
903
|
for (const pattern of allIgnored) {
|
|
875
904
|
const cleanPattern = pattern.replace(/\/$/, '').replace(/\\$/, '');
|
|
876
|
-
if (filename === cleanPattern) return true;
|
|
905
|
+
if (filename === cleanPattern || relPath === cleanPattern || relPath.startsWith(cleanPattern + '/')) return true;
|
|
877
906
|
if (pattern.startsWith('*') && filename.endsWith(pattern.slice(1))) return true;
|
|
878
907
|
}
|
|
879
908
|
return false;
|
|
@@ -899,7 +928,7 @@ Options:
|
|
|
899
928
|
const validExtensions = [
|
|
900
929
|
'.js', '.jsx', '.ts', '.tsx', '.html', '.css', '.scss', '.vue', '.svelte',
|
|
901
930
|
'.py', '.java', '.c', '.cpp', '.cs', '.go', '.rb', '.php', '.rs',
|
|
902
|
-
'.swift', '.kt', '.dart', '.sh'
|
|
931
|
+
'.swift', '.kt', '.dart', '.sh', '.sql'
|
|
903
932
|
];
|
|
904
933
|
|
|
905
934
|
if (!validExtensions.includes(ext)) {
|
package/bin/post-commit.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { execSync } = require('child_process');
|
|
1
|
+
const { execSync, execFileSync } = require('child_process');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { spliceComments } = require('./cli');
|
|
@@ -30,7 +30,7 @@ try {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// Get a list of changed files in the last commit [ds]
|
|
33
|
-
const changedFilesStr = execSync('git diff-tree --no-commit-id --name-only -r HEAD', { encoding: 'utf8' }).trim();
|
|
33
|
+
const changedFilesStr = execSync('git diff-tree --no-commit-id --name-only -r --root HEAD', { encoding: 'utf8' }).trim();
|
|
34
34
|
if (!changedFilesStr) {
|
|
35
35
|
process.exit(0);
|
|
36
36
|
}
|
|
@@ -40,7 +40,7 @@ try {
|
|
|
40
40
|
const validExtensions = [
|
|
41
41
|
'.js', '.jsx', '.ts', '.tsx', '.html', '.css', '.scss', '.vue', '.svelte',
|
|
42
42
|
'.py', '.java', '.c', '.cpp', '.cs', '.go', '.rb', '.php', '.rs',
|
|
43
|
-
'.swift', '.kt', '.dart', '.sh'
|
|
43
|
+
'.swift', '.kt', '.dart', '.sh', '.sql'
|
|
44
44
|
];
|
|
45
45
|
|
|
46
46
|
/** Filter function to determine which files to auto-comment [ds] */
|
|
@@ -97,13 +97,13 @@ try {
|
|
|
97
97
|
// Log and attempt to auto-comment the current file [ds]
|
|
98
98
|
console.log(`[devsplain] Automatically commenting file: ${file}`);
|
|
99
99
|
try {
|
|
100
|
-
let extraFlags = '';
|
|
101
|
-
if (process.env.DS_OVER) extraFlags += ' --overwrite';
|
|
102
|
-
if (process.env.DS_KEEP) extraFlags += ' --keep';
|
|
103
|
-
|
|
104
|
-
// Construct the command to run the auto-commenting CLI [ds]
|
|
105
100
|
const cliPath = path.join(__dirname, 'cli.js');
|
|
106
|
-
|
|
101
|
+
const cliArgs = [cliPath, file, '--force'];
|
|
102
|
+
if (modeFlag.trim()) cliArgs.push(modeFlag.trim());
|
|
103
|
+
if (process.env.DS_OVER) cliArgs.push('--overwrite');
|
|
104
|
+
if (process.env.DS_KEEP) cliArgs.push('--keep');
|
|
105
|
+
|
|
106
|
+
execFileSync(process.execPath, cliArgs, { stdio: 'inherit' });
|
|
107
107
|
commentedAny = true;
|
|
108
108
|
successfullyCommentedFiles.push(file);
|
|
109
109
|
} catch (err) {
|
|
@@ -115,7 +115,7 @@ try {
|
|
|
115
115
|
if (commentedAny) {
|
|
116
116
|
for (const file of successfullyCommentedFiles) {
|
|
117
117
|
try {
|
|
118
|
-
|
|
118
|
+
execFileSync('git', ['add', file]);
|
|
119
119
|
} catch (addErr) {}
|
|
120
120
|
}
|
|
121
121
|
|
package/bin/setup-hook.js
CHANGED
|
@@ -57,8 +57,17 @@ if [ -f package.json ] && grep -q '"test"' package.json 2>/dev/null; then
|
|
|
57
57
|
npm test || exit 1
|
|
58
58
|
fi
|
|
59
59
|
`;
|
|
60
|
-
// Write pre-commit hook
|
|
61
|
-
fs.
|
|
60
|
+
// Write pre-commit hook safely [ds]
|
|
61
|
+
if (fs.existsSync(preCommitHookPath)) {
|
|
62
|
+
const existing = fs.readFileSync(preCommitHookPath, 'utf8');
|
|
63
|
+
if (!existing.includes('# devsplain native pre-commit hook')) {
|
|
64
|
+
fs.appendFileSync(preCommitHookPath, '\n' + preCommitContent);
|
|
65
|
+
} else {
|
|
66
|
+
fs.writeFileSync(preCommitHookPath, preCommitContent);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
fs.writeFileSync(preCommitHookPath, preCommitContent);
|
|
70
|
+
}
|
|
62
71
|
try {
|
|
63
72
|
fs.chmodSync(preCommitHookPath, 0o755);
|
|
64
73
|
} catch (err) {}
|
|
@@ -73,8 +82,17 @@ fi
|
|
|
73
82
|
echo "Auto-generating comments for files in the last commit..."
|
|
74
83
|
node "${postCommitScript}"${modeArgs} || exit 1
|
|
75
84
|
`;
|
|
76
|
-
// Write post-commit hook
|
|
77
|
-
fs.
|
|
85
|
+
// Write post-commit hook safely [ds]
|
|
86
|
+
if (fs.existsSync(postCommitHookPath)) {
|
|
87
|
+
const existing = fs.readFileSync(postCommitHookPath, 'utf8');
|
|
88
|
+
if (!existing.includes('# devsplain native post-commit hook')) {
|
|
89
|
+
fs.appendFileSync(postCommitHookPath, '\n' + postCommitContent);
|
|
90
|
+
} else {
|
|
91
|
+
fs.writeFileSync(postCommitHookPath, postCommitContent);
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
fs.writeFileSync(postCommitHookPath, postCommitContent);
|
|
95
|
+
}
|
|
78
96
|
try {
|
|
79
97
|
fs.chmodSync(postCommitHookPath, 0o755);
|
|
80
98
|
} catch (err) {}
|
package/lib/config.js
CHANGED
|
@@ -22,8 +22,14 @@ function askSecret(query) {
|
|
|
22
22
|
process.stdout.write('\n');
|
|
23
23
|
rl.close();
|
|
24
24
|
resolve(key);
|
|
25
|
+
} else if (char === '\u0008' || char === '\u007f') { // Backspace
|
|
26
|
+
if (key.length > 0) {
|
|
27
|
+
key = key.slice(0, -1);
|
|
28
|
+
process.stdout.write('\b \b');
|
|
29
|
+
}
|
|
25
30
|
} else if (char === '\u0003') { // Ctrl+C
|
|
26
|
-
process.
|
|
31
|
+
process.stdin.setRawMode(false);
|
|
32
|
+
process.exit(130);
|
|
27
33
|
} else {
|
|
28
34
|
key += char;
|
|
29
35
|
process.stdout.write('*');
|
|
@@ -35,7 +41,7 @@ function askSecret(query) {
|
|
|
35
41
|
|
|
36
42
|
// Migrates old config to the new format [ds]
|
|
37
43
|
function migrateConfig(oldConfig) {
|
|
38
|
-
if (oldConfig.provider && !oldConfig.providers) {
|
|
44
|
+
if (oldConfig && typeof oldConfig === 'object' && oldConfig.provider && !oldConfig.providers) {
|
|
39
45
|
return {
|
|
40
46
|
activeProvider: oldConfig.provider,
|
|
41
47
|
providers: {
|
|
@@ -75,7 +81,7 @@ async function getConfig(forceWizard = false) {
|
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
if (!fileConfig || forceWizard) {
|
|
84
|
+
if (!fileConfig || !fileConfig.activeProvider || !fileConfig.providers || !fileConfig.providers[fileConfig.activeProvider] || forceWizard) {
|
|
79
85
|
let rl = readline.createInterface({
|
|
80
86
|
input: process.stdin,
|
|
81
87
|
output: process.stdout
|
|
@@ -274,9 +280,10 @@ async function getConfig(forceWizard = false) {
|
|
|
274
280
|
...config.providers[config.activeProvider]
|
|
275
281
|
};
|
|
276
282
|
} else {
|
|
283
|
+
const active = fileConfig.providers[fileConfig.activeProvider] || {};
|
|
277
284
|
return {
|
|
278
285
|
provider: fileConfig.activeProvider,
|
|
279
|
-
...
|
|
286
|
+
...active
|
|
280
287
|
};
|
|
281
288
|
}
|
|
282
289
|
}
|
package/lib/llm.js
CHANGED
|
@@ -4,13 +4,16 @@ async function fetchWithRetry(url, options, maxRetries = 3, initialDelay = 1000)
|
|
|
4
4
|
// Loop through each attempt, up to the maximum number of retries [ds]
|
|
5
5
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
6
6
|
const controller = new AbortController();
|
|
7
|
-
const timeoutId = setTimeout(() => controller.abort(),
|
|
7
|
+
const timeoutId = setTimeout(() => controller.abort(), 45000);
|
|
8
8
|
try {
|
|
9
9
|
const response = await fetch(url, {
|
|
10
10
|
...options,
|
|
11
11
|
signal: controller.signal
|
|
12
12
|
});
|
|
13
13
|
clearTimeout(timeoutId);
|
|
14
|
+
if (!response) {
|
|
15
|
+
throw new Error("No response received from fetch");
|
|
16
|
+
}
|
|
14
17
|
if (response.ok) {
|
|
15
18
|
return response;
|
|
16
19
|
}
|
|
@@ -22,7 +25,7 @@ async function fetchWithRetry(url, options, maxRetries = 3, initialDelay = 1000)
|
|
|
22
25
|
} catch (err) {
|
|
23
26
|
clearTimeout(timeoutId);
|
|
24
27
|
if (err.name === 'AbortError') {
|
|
25
|
-
lastError = new Error("Request timed out after
|
|
28
|
+
lastError = new Error("Request timed out after 45 seconds");
|
|
26
29
|
} else {
|
|
27
30
|
lastError = err;
|
|
28
31
|
}
|
|
@@ -46,8 +49,9 @@ async function getComments(code, language, config, mode = 'default') {
|
|
|
46
49
|
const extMatch = language.match(/\.[0-9a-z]+$/i);
|
|
47
50
|
const ext = extMatch ? extMatch[0].toLowerCase() : '';
|
|
48
51
|
const isPython = ext === '.py';
|
|
49
|
-
const isRubyOrShell = ['.rb', '.sh'
|
|
52
|
+
const isRubyOrShell = ['.rb', '.sh'].includes(ext);
|
|
50
53
|
const isHTML = ['.html', '.vue', '.svelte'].includes(ext);
|
|
54
|
+
const isCss = ['.css', '.scss'].includes(ext);
|
|
51
55
|
const isSql = ext === '.sql';
|
|
52
56
|
|
|
53
57
|
// Define the single-line comment token and examples [ds]
|
|
@@ -63,6 +67,10 @@ async function getComments(code, language, config, mode = 'default') {
|
|
|
63
67
|
singleLineToken = '<!--';
|
|
64
68
|
blockExample = '<!-- Calculates the total price -->';
|
|
65
69
|
inlineExample = '<!-- Check for null values -->';
|
|
70
|
+
} else if (isCss) {
|
|
71
|
+
singleLineToken = '/*';
|
|
72
|
+
blockExample = '/* Calculates the total price */';
|
|
73
|
+
inlineExample = '/* Check for null values */';
|
|
66
74
|
} else if (isSql) {
|
|
67
75
|
singleLineToken = '--';
|
|
68
76
|
blockExample = '-- Calculates the total price';
|
|
@@ -77,6 +85,11 @@ async function getComments(code, language, config, mode = 'default') {
|
|
|
77
85
|
instruction = `Provide highly detailed block comments above functions, and exhaustive step-by-step inline comments explaining every conditional branch, loop, variable assignment, and logical block inside function bodies. Do not be sparse; explain the code's execution flow in detail.`;
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
let rule5 = `5. IMPORTANT: Use ONLY ${singleLineToken} for comments. DO NOT use docstrings or multi-line string literals like """ or ''' for comments.`;
|
|
89
|
+
if (isCss) {
|
|
90
|
+
rule5 = `5. IMPORTANT: In CSS/SCSS, you MUST use /* ... */ for comments. DO NOT use // comments under any circumstances.`;
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
let prompt = `
|
|
81
94
|
You are a code documentation engine. Analyze the following ${language} code which has line numbers prepended to it.
|
|
82
95
|
${instruction}
|
|
@@ -86,7 +99,7 @@ CRITICAL RULES:
|
|
|
86
99
|
2. Each object must have exactly two properties: "line" (the integer line number where the comment should be inserted ABOVE) and "comment" (the text of the comment itself).
|
|
87
100
|
3. Do NOT include the original code in your response.
|
|
88
101
|
4. If no comments are needed, return an empty array: [].
|
|
89
|
-
|
|
102
|
+
${rule5}
|
|
90
103
|
|
|
91
104
|
Example Output:
|
|
92
105
|
[
|
|
@@ -116,10 +129,15 @@ ${numberedCode}
|
|
|
116
129
|
});
|
|
117
130
|
data = await response.json();
|
|
118
131
|
} catch (error) {
|
|
119
|
-
throw new Error(
|
|
132
|
+
throw new Error(`AI Provider Request Failed: ${error.message}`);
|
|
120
133
|
}
|
|
121
134
|
if (data.error) {
|
|
122
|
-
|
|
135
|
+
const msg = data.error.message || (typeof data.error === 'string' ? data.error : JSON.stringify(data.error));
|
|
136
|
+
throw new Error(`API Error: ${msg}`);
|
|
137
|
+
}
|
|
138
|
+
if (!data.candidates || !data.candidates[0] || !data.candidates[0].content || !data.candidates[0].content.parts || !data.candidates[0].content.parts[0]) {
|
|
139
|
+
const reason = data.candidates?.[0]?.finishReason || 'Unknown error';
|
|
140
|
+
throw new Error(`AI Provider returned no content (finish reason: ${reason})`);
|
|
123
141
|
}
|
|
124
142
|
textResponse = data.candidates[0].content.parts[0].text;
|
|
125
143
|
} else if (config.provider === 'claude') {
|
|
@@ -145,10 +163,14 @@ ${numberedCode}
|
|
|
145
163
|
});
|
|
146
164
|
data = await response.json();
|
|
147
165
|
} catch (error) {
|
|
148
|
-
throw new Error(
|
|
166
|
+
throw new Error(`AI Provider Request Failed: ${error.message}`);
|
|
149
167
|
}
|
|
150
168
|
if (data.error) {
|
|
151
|
-
|
|
169
|
+
const msg = data.error.message || (typeof data.error === 'string' ? data.error : JSON.stringify(data.error));
|
|
170
|
+
throw new Error(`API Error: ${msg}`);
|
|
171
|
+
}
|
|
172
|
+
if (!data.content || !data.content[0] || typeof data.content[0].text !== 'string') {
|
|
173
|
+
throw new Error(`AI Provider returned an unexpected response structure: ${JSON.stringify(data)}`);
|
|
152
174
|
}
|
|
153
175
|
textResponse = data.content[0].text;
|
|
154
176
|
}
|
|
@@ -166,6 +188,7 @@ ${numberedCode}
|
|
|
166
188
|
},
|
|
167
189
|
body: JSON.stringify({
|
|
168
190
|
"model": config.model,
|
|
191
|
+
"max_tokens": 8192,
|
|
169
192
|
"messages": [{
|
|
170
193
|
"role": "user",
|
|
171
194
|
"content": prompt
|
|
@@ -174,10 +197,14 @@ ${numberedCode}
|
|
|
174
197
|
});
|
|
175
198
|
data = await response.json();
|
|
176
199
|
} catch (error) {
|
|
177
|
-
throw new Error(
|
|
200
|
+
throw new Error(`AI Provider Request Failed: ${error.message}`);
|
|
178
201
|
}
|
|
179
202
|
if (data.error) {
|
|
180
|
-
|
|
203
|
+
const msg = data.error.message || (typeof data.error === 'string' ? data.error : JSON.stringify(data.error));
|
|
204
|
+
throw new Error(`API Error: ${msg}`);
|
|
205
|
+
}
|
|
206
|
+
if (!data.choices || !data.choices[0] || !data.choices[0].message || typeof data.choices[0].message.content !== 'string') {
|
|
207
|
+
throw new Error(`AI Provider returned an unexpected response structure: ${JSON.stringify(data)}`);
|
|
181
208
|
}
|
|
182
209
|
textResponse = data.choices[0].message.content;
|
|
183
210
|
}
|
|
@@ -220,15 +247,30 @@ ${numberedCode}
|
|
|
220
247
|
}
|
|
221
248
|
|
|
222
249
|
const trimmedComment = item.comment.trim();
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
250
|
+
const commentLines = trimmedComment.split(/\r?\n/);
|
|
251
|
+
let inBlock = false;
|
|
252
|
+
for (const cl of commentLines) {
|
|
253
|
+
const tcl = cl.trim();
|
|
254
|
+
if (!tcl) continue;
|
|
255
|
+
if (inBlock) {
|
|
256
|
+
if (tcl.includes('*/') || tcl.includes('-->')) {
|
|
257
|
+
inBlock = false;
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const startsWithMarker =
|
|
262
|
+
tcl.startsWith('//') ||
|
|
263
|
+
tcl.startsWith('/*') ||
|
|
264
|
+
tcl.startsWith('*') ||
|
|
265
|
+
tcl.startsWith('#') ||
|
|
266
|
+
tcl.startsWith('<!--') ||
|
|
267
|
+
tcl.startsWith('--');
|
|
268
|
+
if (!startsWithMarker) {
|
|
269
|
+
throw new Error(`Security Error: Comment on line ${item.line} contains invalid non-comment line: "${tcl}"`);
|
|
270
|
+
}
|
|
271
|
+
if ((tcl.startsWith('/*') && !tcl.includes('*/')) || (tcl.startsWith('<!--') && !tcl.includes('-->'))) {
|
|
272
|
+
inBlock = true;
|
|
273
|
+
}
|
|
232
274
|
}
|
|
233
275
|
}
|
|
234
276
|
}
|
package/package.json
CHANGED