devsplain 1.5.1 → 1.5.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 +15 -7
- package/bin/setup-hook.js +14 -12
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ function isGitDirty() {
|
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/** Determines if a specific line index
|
|
25
|
+
/** Determines if a specific line index falls within a string literal */
|
|
26
26
|
function isLineInsideString(lines, targetLineIndex, ext = '') {
|
|
27
27
|
const isPython = ext.toLowerCase() === '.py';
|
|
28
28
|
let inBacktick = false;
|
|
@@ -31,6 +31,7 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
|
|
|
31
31
|
let inSingle = false;
|
|
32
32
|
let inDouble = false;
|
|
33
33
|
|
|
34
|
+
// Iterate through lines prior to the target to track string/block state
|
|
34
35
|
for (let i = 0; i < targetLineIndex; i++) {
|
|
35
36
|
const line = lines[i];
|
|
36
37
|
let j = 0;
|
|
@@ -51,6 +52,7 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
} else {
|
|
55
|
+
// Check for unescaped backtick (JS template strings) or quotes
|
|
54
56
|
if (!inSingle && !inDouble && line[j] === '`') {
|
|
55
57
|
let escaped = false;
|
|
56
58
|
let k = j - 1;
|
|
@@ -96,7 +98,7 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
|
|
|
96
98
|
return inBacktick || inTripleDouble || inTripleSingle || inSingle || inDouble;
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
/**
|
|
101
|
+
/** Performs a lexical analysis to categorize code lines and comment blocks */
|
|
100
102
|
function analyzeComments(lines, ext = '') {
|
|
101
103
|
const isPython = ext.toLowerCase() === '.py';
|
|
102
104
|
const isHTML = ['.html', '.vue', '.svelte'].includes(ext.toLowerCase());
|
|
@@ -108,6 +110,7 @@ function analyzeComments(lines, ext = '') {
|
|
|
108
110
|
let inDouble = false;
|
|
109
111
|
let inBlockJS = false;
|
|
110
112
|
let inBlockHTML = false;
|
|
113
|
+
// Iterate through each line character by character to detect comment boundaries
|
|
111
114
|
for (let i = 0; i < lines.length; i++) {
|
|
112
115
|
const line = lines[i];
|
|
113
116
|
let commentStartIndex = -1;
|
|
@@ -247,8 +250,9 @@ function analyzeComments(lines, ext = '') {
|
|
|
247
250
|
return analysis;
|
|
248
251
|
}
|
|
249
252
|
|
|
250
|
-
/**
|
|
253
|
+
/** Splices generated comments into the source data or removes existing ones */
|
|
251
254
|
function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
255
|
+
// Determine platform-specific line endings
|
|
252
256
|
const hasCRLF = data.includes('\r\n');
|
|
253
257
|
const lineEnding = hasCRLF ? '\r\n' : '\n';
|
|
254
258
|
const originalLines = data.split(/\r?\n/);
|
|
@@ -258,6 +262,7 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
258
262
|
const annotated = originalLines.map((text, index) => ({ text, originalIndex: index }));
|
|
259
263
|
let analysis = null;
|
|
260
264
|
|
|
265
|
+
// 'clean' mode removes all existing comments/documentation
|
|
261
266
|
if (mode === 'clean') {
|
|
262
267
|
analysis = analyzeComments(originalLines, ext);
|
|
263
268
|
const finalDeletions = new Set();
|
|
@@ -306,6 +311,7 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
306
311
|
annotated.splice(lineNum - 1, 1);
|
|
307
312
|
}
|
|
308
313
|
} else {
|
|
314
|
+
// 'default'/'light'/'full' mode: Inject AI-generated comments
|
|
309
315
|
for (const c of validComments) {
|
|
310
316
|
if (isLineInsideString(originalLines, c.line - 1, ext)) {
|
|
311
317
|
console.warn(`[devsplain] Skipping comment insertion at line ${c.line} to avoid string literal corruption.`);
|
|
@@ -368,7 +374,7 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
368
374
|
return annotated.map(line => line.text).join(lineEnding);
|
|
369
375
|
}
|
|
370
376
|
|
|
371
|
-
/** Main
|
|
377
|
+
/** Main entry point for the CLI tool logic */
|
|
372
378
|
async function runCLI() {
|
|
373
379
|
rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
374
380
|
askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
@@ -416,7 +422,8 @@ Options:
|
|
|
416
422
|
|
|
417
423
|
if (args.includes('--setup-hook')) {
|
|
418
424
|
rl.close();
|
|
419
|
-
require('./setup-hook.js');
|
|
425
|
+
const installHooks = require('./setup-hook.js');
|
|
426
|
+
await installHooks();
|
|
420
427
|
return;
|
|
421
428
|
}
|
|
422
429
|
|
|
@@ -484,7 +491,7 @@ Options:
|
|
|
484
491
|
let successCount = 0;
|
|
485
492
|
let failCount = 0;
|
|
486
493
|
|
|
487
|
-
/** Recursively traverses
|
|
494
|
+
/** Recursively traverses the file system to identify and process source files */
|
|
488
495
|
async function processPath(targetPath) {
|
|
489
496
|
const stats = fs.statSync(targetPath);
|
|
490
497
|
|
|
@@ -529,6 +536,7 @@ Options:
|
|
|
529
536
|
|
|
530
537
|
console.log(` Analyzing ${filename} in ${mode} mode...`);
|
|
531
538
|
try {
|
|
539
|
+
// Logic to either clean existing comments or replace/insert new ones
|
|
532
540
|
let comments = [];
|
|
533
541
|
let commentedCode;
|
|
534
542
|
if (mode !== 'clean') {
|
|
@@ -544,6 +552,7 @@ Options:
|
|
|
544
552
|
console.log(`---------------------------------------\n`);
|
|
545
553
|
const answer = await askQuestion("Type 'write' to save to file, or press any key to discard: ");
|
|
546
554
|
if (answer.toLowerCase() === 'write') {
|
|
555
|
+
// Use temporary file for atomic write operations
|
|
547
556
|
const tempPath = targetPath + '.tmp';
|
|
548
557
|
fs.writeFileSync(tempPath, commentedCode, 'utf8');
|
|
549
558
|
fs.renameSync(tempPath, targetPath);
|
|
@@ -581,7 +590,6 @@ Options:
|
|
|
581
590
|
rl.close();
|
|
582
591
|
}
|
|
583
592
|
|
|
584
|
-
// If running as a standalone script, start the CLI; otherwise export helpers
|
|
585
593
|
if (require.main === module) {
|
|
586
594
|
runCLI().catch(err => {
|
|
587
595
|
console.error(err);
|
package/bin/setup-hook.js
CHANGED
|
@@ -4,12 +4,12 @@ const { execSync } = require('child_process');
|
|
|
4
4
|
const readline = require('readline');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Initializes and installs Git pre-commit and post-commit hooks.
|
|
8
|
+
* Prompts the user for documentation mode configuration.
|
|
9
9
|
*/
|
|
10
10
|
async function installHooks() {
|
|
11
11
|
try {
|
|
12
|
-
// Resolve the
|
|
12
|
+
// Resolve the root .git directory path
|
|
13
13
|
const gitDir = execSync('git rev-parse --git-dir', { encoding: 'utf8' }).trim();
|
|
14
14
|
const hooksDir = path.join(gitDir, 'hooks');
|
|
15
15
|
if (!fs.existsSync(hooksDir)) {
|
|
@@ -17,13 +17,13 @@ async function installHooks() {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
let modeChoice = '1';
|
|
20
|
-
//
|
|
20
|
+
// Interactive mode requires a TTY terminal
|
|
21
21
|
if (process.stdout.isTTY) {
|
|
22
22
|
const rl = readline.createInterface({
|
|
23
23
|
input: process.stdin,
|
|
24
24
|
output: process.stdout
|
|
25
25
|
});
|
|
26
|
-
//
|
|
26
|
+
// Promisify the readline interface for async/await flow
|
|
27
27
|
const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
28
28
|
|
|
29
29
|
console.log('\nSelect default commenting mode for Git commits:');
|
|
@@ -35,7 +35,7 @@ async function installHooks() {
|
|
|
35
35
|
rl.close();
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// Determine documentation style flags based on user input
|
|
39
39
|
let modeArgs = '';
|
|
40
40
|
if (modeChoice === '2') {
|
|
41
41
|
modeArgs = ' --light';
|
|
@@ -43,7 +43,7 @@ async function installHooks() {
|
|
|
43
43
|
modeArgs = ' --full';
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
//
|
|
46
|
+
// Write the pre-commit script to the git hooks directory
|
|
47
47
|
const preCommitHookPath = path.join(hooksDir, 'pre-commit');
|
|
48
48
|
const preCommitContent = `#!/bin/sh
|
|
49
49
|
# devsplain native pre-commit hook
|
|
@@ -51,12 +51,12 @@ echo "Running pre-commit tests..."
|
|
|
51
51
|
npm test || exit 1
|
|
52
52
|
`;
|
|
53
53
|
fs.writeFileSync(preCommitHookPath, preCommitContent);
|
|
54
|
-
// Ensure the shell script is executable by the system
|
|
55
54
|
try {
|
|
55
|
+
// Ensure the hook file is executable
|
|
56
56
|
fs.chmodSync(preCommitHookPath, 0o755);
|
|
57
57
|
} catch (err) {}
|
|
58
58
|
|
|
59
|
-
//
|
|
59
|
+
// Write the post-commit script that triggers documentation generation
|
|
60
60
|
const postCommitHookPath = path.join(hooksDir, 'post-commit');
|
|
61
61
|
const postCommitContent = `#!/bin/sh
|
|
62
62
|
# devsplain native post-commit hook
|
|
@@ -64,7 +64,6 @@ echo "Auto-generating comments for files in the last commit..."
|
|
|
64
64
|
node bin/post-commit.js${modeArgs} || exit 1
|
|
65
65
|
`;
|
|
66
66
|
fs.writeFileSync(postCommitHookPath, postCommitContent);
|
|
67
|
-
// Ensure the post-commit shell script is executable
|
|
68
67
|
try {
|
|
69
68
|
fs.chmodSync(postCommitHookPath, 0o755);
|
|
70
69
|
} catch (err) {}
|
|
@@ -75,5 +74,8 @@ node bin/post-commit.js${modeArgs} || exit 1
|
|
|
75
74
|
}
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
// Execute the
|
|
79
|
-
|
|
77
|
+
// Execute the function automatically if the file is run directly
|
|
78
|
+
if (require.main === module) {
|
|
79
|
+
installHooks();
|
|
80
|
+
}
|
|
81
|
+
module.exports = installHooks;
|
package/package.json
CHANGED