devsplain 2.2.0 → 2.2.2
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 +64 -11
- package/bin/setup-hook.js +97 -34
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ const { execSync } = require('child_process');
|
|
|
10
10
|
let rl;
|
|
11
11
|
let askQuestion;
|
|
12
12
|
|
|
13
|
-
// Checks if the Git
|
|
13
|
+
// Checks if the Git repository is dirty [ds]
|
|
14
14
|
function isGitDirty() {
|
|
15
15
|
try {
|
|
16
16
|
const gitDir = execSync('git rev-parse --is-inside-work-tree', { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }).trim();
|
|
@@ -23,7 +23,7 @@ function isGitDirty() {
|
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
//
|
|
26
|
+
// Determines if a line is inside a string literal [ds]
|
|
27
27
|
function isLineInsideString(lines, targetLineIndex, ext = '') {
|
|
28
28
|
const isPython = ext.toLowerCase() === '.py';
|
|
29
29
|
const isHTML = ['.html', '.vue', '.svelte'].includes(ext.toLowerCase());
|
|
@@ -236,7 +236,7 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
|
|
|
236
236
|
return inBacktick || inTripleDouble || inTripleSingle || inSingle || inDouble || inCppRawString || inRegex;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
// Analyzes comments in
|
|
239
|
+
// Analyzes comments in the given lines of code [ds]
|
|
240
240
|
function analyzeComments(lines, ext = '') {
|
|
241
241
|
const isPython = ext.toLowerCase() === '.py';
|
|
242
242
|
const isHTML = ['.html', '.vue', '.svelte'].includes(ext.toLowerCase());
|
|
@@ -477,7 +477,7 @@ function analyzeComments(lines, ext = '') {
|
|
|
477
477
|
return analysis;
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
-
// Splices comments into the
|
|
480
|
+
// Splices comments into the provided data [ds]
|
|
481
481
|
function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
482
482
|
const hasCRLF = data.includes('\r\n');
|
|
483
483
|
const lineEnding = hasCRLF ? '\r\n' : '\n';
|
|
@@ -530,7 +530,23 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
530
530
|
if (lineAnalysis.isPureComment) {
|
|
531
531
|
finalDeletions.add(lineNum);
|
|
532
532
|
} else if (lineAnalysis.commentStartIndex !== -1) {
|
|
533
|
-
|
|
533
|
+
const idx = lineAnalysis.commentStartIndex;
|
|
534
|
+
const remainder = lineStr.slice(idx);
|
|
535
|
+
let newText = lineStr.slice(0, idx).trimEnd();
|
|
536
|
+
|
|
537
|
+
if (remainder.startsWith('/*')) {
|
|
538
|
+
const endIdx = remainder.indexOf('*/');
|
|
539
|
+
if (endIdx !== -1) {
|
|
540
|
+
newText = lineStr.slice(0, idx) + remainder.slice(endIdx + 2);
|
|
541
|
+
}
|
|
542
|
+
} else if (remainder.startsWith('<!--')) {
|
|
543
|
+
const endIdx = remainder.indexOf('-->');
|
|
544
|
+
if (endIdx !== -1) {
|
|
545
|
+
newText = lineStr.slice(0, idx) + remainder.slice(endIdx + 3);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
annotated[i].text = newText.trimEnd();
|
|
534
550
|
}
|
|
535
551
|
} else if (mode === 'clean') {
|
|
536
552
|
const isDsBlockLine = dsBlocks.has(lineNum);
|
|
@@ -542,7 +558,23 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
542
558
|
}
|
|
543
559
|
} else if (lineAnalysis.commentStartIndex !== -1) {
|
|
544
560
|
if (isDsBlockLine || hasDsInline) {
|
|
545
|
-
|
|
561
|
+
const idx = lineAnalysis.commentStartIndex;
|
|
562
|
+
const remainder = lineStr.slice(idx);
|
|
563
|
+
let newText = lineStr.slice(0, idx).trimEnd();
|
|
564
|
+
|
|
565
|
+
if (remainder.startsWith('/*')) {
|
|
566
|
+
const endIdx = remainder.indexOf('*/');
|
|
567
|
+
if (endIdx !== -1) {
|
|
568
|
+
newText = lineStr.slice(0, idx) + remainder.slice(endIdx + 2);
|
|
569
|
+
}
|
|
570
|
+
} else if (remainder.startsWith('<!--')) {
|
|
571
|
+
const endIdx = remainder.indexOf('-->');
|
|
572
|
+
if (endIdx !== -1) {
|
|
573
|
+
newText = lineStr.slice(0, idx) + remainder.slice(endIdx + 3);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
annotated[i].text = newText.trimEnd();
|
|
546
578
|
}
|
|
547
579
|
}
|
|
548
580
|
}
|
|
@@ -643,7 +675,23 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
|
|
|
643
675
|
const isDsBlockLine = dsBlocks.has(origIdx + 1);
|
|
644
676
|
const hasDsInline = originalLine.includes('[ds]');
|
|
645
677
|
if (mode === 'prune' || (mode === 'clean' && (hasDsInline || isDsBlockLine))) {
|
|
646
|
-
const
|
|
678
|
+
const idx = lineAnalysis.commentStartIndex;
|
|
679
|
+
const remainder = originalLine.slice(idx);
|
|
680
|
+
let expectedStripped = originalLine.slice(0, idx).trimEnd();
|
|
681
|
+
|
|
682
|
+
if (remainder.startsWith('/*')) {
|
|
683
|
+
const endIdx = remainder.indexOf('*/');
|
|
684
|
+
if (endIdx !== -1) {
|
|
685
|
+
expectedStripped = originalLine.slice(0, idx) + remainder.slice(endIdx + 2);
|
|
686
|
+
}
|
|
687
|
+
} else if (remainder.startsWith('<!--')) {
|
|
688
|
+
const endIdx = remainder.indexOf('-->');
|
|
689
|
+
if (endIdx !== -1) {
|
|
690
|
+
expectedStripped = originalLine.slice(0, idx) + remainder.slice(endIdx + 3);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
expectedStripped = expectedStripped.trimEnd();
|
|
694
|
+
|
|
647
695
|
if (text === expectedStripped) {
|
|
648
696
|
return true;
|
|
649
697
|
}
|
|
@@ -696,6 +744,7 @@ Options:
|
|
|
696
744
|
--base-url <url> Override base URL for custom APIs
|
|
697
745
|
--config Force run the configuration setup wizard
|
|
698
746
|
--setup-hook Install Git pre-commit and post-commit hooks in repository
|
|
747
|
+
--remove-hook Remove devsplain-installed Git hooks from repository
|
|
699
748
|
--help, -h Show this help message
|
|
700
749
|
--version, -v Show version information
|
|
701
750
|
`);
|
|
@@ -719,11 +768,18 @@ Options:
|
|
|
719
768
|
|
|
720
769
|
if (args.includes('--setup-hook')) {
|
|
721
770
|
rl.close();
|
|
722
|
-
const installHooks = require('./setup-hook.js');
|
|
771
|
+
const { installHooks } = require('./setup-hook.js');
|
|
723
772
|
await installHooks();
|
|
724
773
|
return;
|
|
725
774
|
}
|
|
726
775
|
|
|
776
|
+
if (args.includes('--remove-hook')) {
|
|
777
|
+
rl.close();
|
|
778
|
+
const { removeHooks } = require('./setup-hook.js');
|
|
779
|
+
await removeHooks();
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
|
|
727
783
|
const getArgValue = (flag) => {
|
|
728
784
|
const index = args.indexOf(flag);
|
|
729
785
|
if (index !== -1 && index + 1 < args.length) {
|
|
@@ -823,7 +879,6 @@ Options:
|
|
|
823
879
|
return false;
|
|
824
880
|
}
|
|
825
881
|
|
|
826
|
-
// Processes a given file path, either a file or directory [ds]
|
|
827
882
|
async function processPath(targetPath) {
|
|
828
883
|
const stats = fs.statSync(targetPath);
|
|
829
884
|
|
|
@@ -881,7 +936,6 @@ Options:
|
|
|
881
936
|
fs.renameSync(tempPath, targetPath);
|
|
882
937
|
console.log(` Successfully saved ${targetPath}`);
|
|
883
938
|
} else {
|
|
884
|
-
// Asks the user if they want to save the commented code to a file [ds]
|
|
885
939
|
console.log(` Skipped ${targetPath}`);
|
|
886
940
|
}
|
|
887
941
|
} else {
|
|
@@ -914,7 +968,6 @@ Options:
|
|
|
914
968
|
rl.close();
|
|
915
969
|
}
|
|
916
970
|
|
|
917
|
-
// Starts the CLI application if this script is run directly [ds]
|
|
918
971
|
if (require.main === module) {
|
|
919
972
|
runCLI().catch(err => {
|
|
920
973
|
console.error(err);
|
package/bin/setup-hook.js
CHANGED
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
4
|
const readline = require('readline');
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// Installs Git hooks for automatic comment generation [ds]
|
|
7
7
|
async function installHooks() {
|
|
8
8
|
try {
|
|
9
9
|
const gitDir = execSync('git rev-parse --git-dir', { encoding: 'utf8' }).trim();
|
|
@@ -14,7 +14,7 @@ async function installHooks() {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
let modeChoice = '1';
|
|
17
|
-
//
|
|
17
|
+
// Prompt user for commenting mode if running in a TTY [ds]
|
|
18
18
|
if (process.stdout.isTTY) {
|
|
19
19
|
const rl = readline.createInterface({
|
|
20
20
|
input: process.stdin,
|
|
@@ -22,13 +22,13 @@ async function installHooks() {
|
|
|
22
22
|
});
|
|
23
23
|
const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// Display commenting mode options to the user [ds]
|
|
26
26
|
console.log('\nSelect default commenting mode for Git commits:');
|
|
27
27
|
console.log('1. Balanced (mix of JSDoc and sparse inline comments)');
|
|
28
28
|
console.log('2. Light (JSDoc block comments above functions only)');
|
|
29
29
|
console.log('3. Full (aggressive inline commenting)');
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// Loop until a valid mode choice is selected [ds]
|
|
32
32
|
while (true) {
|
|
33
33
|
const answer = (await askQuestion('Select (1-3, default: 1): ')).trim();
|
|
34
34
|
if (answer === '' || ['1', '2', '3'].includes(answer)) {
|
|
@@ -40,7 +40,7 @@ async function installHooks() {
|
|
|
40
40
|
rl.close();
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// Determine mode arguments based on user
|
|
43
|
+
// Determine mode arguments based on user selection [ds]
|
|
44
44
|
let modeArgs = '';
|
|
45
45
|
if (modeChoice === '2') {
|
|
46
46
|
modeArgs = ' --light';
|
|
@@ -48,7 +48,7 @@ async function installHooks() {
|
|
|
48
48
|
modeArgs = ' --full';
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
//
|
|
51
|
+
// Create pre-commit hook script [ds]
|
|
52
52
|
const preCommitHookPath = path.join(hooksDir, 'pre-commit');
|
|
53
53
|
const preCommitContent = `#!/bin/sh
|
|
54
54
|
# devsplain native pre-commit hook
|
|
@@ -57,63 +57,126 @@ 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
|
|
60
|
+
// Write pre-commit hook to file [ds]
|
|
61
61
|
fs.writeFileSync(preCommitHookPath, preCommitContent);
|
|
62
62
|
try {
|
|
63
63
|
fs.chmodSync(preCommitHookPath, 0o755);
|
|
64
64
|
} catch (err) {}
|
|
65
65
|
|
|
66
|
-
//
|
|
66
|
+
// Path to post-commit script [ds]
|
|
67
67
|
const postCommitScript = path.join(__dirname, 'post-commit.js').replace(/\\/g, '/');
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// Create post-commit hook script [ds]
|
|
70
70
|
const postCommitHookPath = path.join(hooksDir, 'post-commit');
|
|
71
71
|
const postCommitContent = `#!/bin/sh
|
|
72
72
|
# devsplain native post-commit hook
|
|
73
73
|
echo "Auto-generating comments for files in the last commit..."
|
|
74
74
|
node "${postCommitScript}"${modeArgs} || exit 1
|
|
75
75
|
`;
|
|
76
|
+
// Write post-commit hook to file [ds]
|
|
76
77
|
fs.writeFileSync(postCommitHookPath, postCommitContent);
|
|
77
78
|
try {
|
|
78
79
|
fs.chmodSync(postCommitHookPath, 0o755);
|
|
79
80
|
} catch (err) {}
|
|
80
81
|
|
|
81
|
-
//
|
|
82
|
+
// Log successful installation of post-commit hook [ds]
|
|
82
83
|
console.log(`[devsplain] Git post-commit hook successfully installed at: ${postCommitHookPath}`);
|
|
83
84
|
|
|
84
|
-
//
|
|
85
|
+
// Path to devsplain ignore file [ds]
|
|
85
86
|
const ignorePath = path.join(gitRoot, '.devsplainignore');
|
|
87
|
+
const defaultIgnoreLines = [
|
|
88
|
+
'node_modules/', '.git/', 'dist/', 'build/', 'out/',
|
|
89
|
+
'.next/', '.nuxt/', '.svelte-kit/',
|
|
90
|
+
'venv/', 'env/', '.venv/',
|
|
91
|
+
'.vscode/', '.idea/', 'coverage/',
|
|
92
|
+
'tests/', '__tests__/', 'fixtures/'
|
|
93
|
+
];
|
|
94
|
+
// List of default patterns to ignore [ds]
|
|
95
|
+
|
|
96
|
+
const gitignorePath = path.join(gitRoot, '.gitignore');
|
|
97
|
+
let gitignoreLines = [];
|
|
98
|
+
if (fs.existsSync(gitignorePath)) {
|
|
99
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
100
|
+
gitignoreLines = gitignoreContent.split(/\r?\n/)
|
|
101
|
+
.map(l => l.trim())
|
|
102
|
+
.filter(l => l && !l.startsWith('#'));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check if .gitignore file exists [ds]
|
|
86
106
|
if (!fs.existsSync(ignorePath)) {
|
|
87
|
-
const
|
|
88
|
-
.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.
|
|
93
|
-
.
|
|
94
|
-
.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
const gitignoreOnly = gitignoreLines.filter(p => !defaultIgnoreLines.includes(p));
|
|
108
|
+
let content = defaultIgnoreLines.join('\n') + '\n';
|
|
109
|
+
if (gitignoreOnly.length > 0) {
|
|
110
|
+
content += '\n# From .gitignore\n' + gitignoreOnly.join('\n') + '\n';
|
|
111
|
+
}
|
|
112
|
+
fs.writeFileSync(ignorePath, content);
|
|
113
|
+
console.log(`[devsplain] Created .devsplainignore at: ${ignorePath}`);
|
|
114
|
+
if (gitignoreOnly.length > 0) {
|
|
115
|
+
console.log(`[devsplain] Merged ${gitignoreOnly.length} pattern(s) from .gitignore into .devsplainignore.`);
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
const existingContent = fs.readFileSync(ignorePath, 'utf8');
|
|
119
|
+
const existingLines = existingContent.split(/\r?\n/).map(l => l.trim()).filter(l => l && !l.startsWith('#'));
|
|
120
|
+
const newPatterns = gitignoreLines.filter(p => !existingLines.includes(p));
|
|
121
|
+
if (newPatterns.length > 0) {
|
|
122
|
+
const appendContent = '\n# From .gitignore\n' + newPatterns.join('\n') + '\n';
|
|
123
|
+
fs.appendFileSync(ignorePath, appendContent);
|
|
124
|
+
console.log(`[devsplain] Merged ${newPatterns.length} new pattern(s) from .gitignore into .devsplainignore.`);
|
|
125
|
+
} else if (gitignoreLines.length > 0) {
|
|
126
|
+
console.log('[devsplain] .devsplainignore is already up-to-date with .gitignore patterns.');
|
|
127
|
+
}
|
|
128
|
+
// Handle any errors during hook installation [ds]
|
|
107
129
|
}
|
|
130
|
+
|
|
108
131
|
} catch (e) {
|
|
109
132
|
console.warn('Warning: Could not set up Git hooks (not inside a git repository or git command missing).');
|
|
110
133
|
console.warn(e.message);
|
|
111
134
|
}
|
|
112
135
|
}
|
|
113
136
|
|
|
114
|
-
//
|
|
137
|
+
// Removes Git hooks installed by devsplain [ds]
|
|
138
|
+
async function removeHooks() {
|
|
139
|
+
try {
|
|
140
|
+
const gitDir = execSync('git rev-parse --git-dir', { encoding: 'utf8' }).trim();
|
|
141
|
+
const hooksDir = path.join(gitDir, 'hooks');
|
|
142
|
+
const hookSignatures = {
|
|
143
|
+
'pre-commit': '# devsplain native pre-commit hook',
|
|
144
|
+
'post-commit': '# devsplain native post-commit hook'
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
let removed = 0;
|
|
148
|
+
// Iterate through hook signatures to remove [ds]
|
|
149
|
+
for (const [hookName, signature] of Object.entries(hookSignatures)) {
|
|
150
|
+
const hookPath = path.join(hooksDir, hookName);
|
|
151
|
+
if (fs.existsSync(hookPath)) {
|
|
152
|
+
const content = fs.readFileSync(hookPath, 'utf8');
|
|
153
|
+
if (content.includes(signature)) {
|
|
154
|
+
fs.unlinkSync(hookPath);
|
|
155
|
+
console.log(`[devsplain] Removed ${hookName} hook at: ${hookPath}`);
|
|
156
|
+
removed++;
|
|
157
|
+
} else {
|
|
158
|
+
console.log(`[devsplain] Skipping ${hookName}: not installed by devsplain.`);
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
console.log(`[devsplain] No ${hookName} hook found at: ${hookPath}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Log removal of hooks [ds]
|
|
166
|
+
if (removed > 0) {
|
|
167
|
+
console.log(`[devsplain] Successfully removed ${removed} hook(s).`);
|
|
168
|
+
} else {
|
|
169
|
+
console.log('[devsplain] No devsplain hooks were found to remove.');
|
|
170
|
+
}
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.warn('Warning: Could not remove Git hooks (not inside a git repository or git command missing).');
|
|
173
|
+
console.warn(e.message);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Run installHooks if this script is executed directly [ds]
|
|
115
178
|
if (require.main === module) {
|
|
116
179
|
installHooks();
|
|
117
180
|
}
|
|
118
|
-
// Export
|
|
119
|
-
module.exports = installHooks;
|
|
181
|
+
// Export installHooks and removeHooks functions [ds]
|
|
182
|
+
module.exports = { installHooks, removeHooks };
|
package/package.json
CHANGED