stdin-glob 1.3.0 → 1.8.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.
- package/README.md +166 -9
- package/dist/apply.d.ts +26 -0
- package/dist/apply.js +103 -0
- package/dist/apply.test.d.ts +2 -0
- package/dist/apply.test.js +317 -0
- package/dist/index.js +2 -126
- package/dist/main.d.ts +18 -0
- package/dist/main.js +249 -0
- package/dist/main.test.d.ts +2 -0
- package/dist/main.test.js +133 -0
- package/dist/utils/binary.test.d.ts +2 -0
- package/dist/utils/binary.test.js +33 -0
- package/dist/utils/gitignore.d.ts +10 -0
- package/dist/utils/gitignore.js +66 -0
- package/dist/utils/gitignore.test.d.ts +2 -0
- package/dist/utils/gitignore.test.js +62 -0
- package/dist/utils/pipes.test.d.ts +2 -0
- package/dist/utils/pipes.test.js +40 -0
- package/package.json +13 -5
package/dist/index.js
CHANGED
|
@@ -1,129 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const promises_1 = require("fs/promises");
|
|
9
|
-
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
10
|
-
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const clipboardy_1 = __importDefault(require("clipboardy"));
|
|
12
|
-
const binary_1 = require("./utils/binary");
|
|
13
|
-
const pipes_1 = require("./utils/pipes");
|
|
14
|
-
const program = new commander_1.Command();
|
|
15
|
-
program
|
|
16
|
-
.name('stdin-glob')
|
|
17
|
-
.description('Expand glob patterns and output file contents and paths')
|
|
18
|
-
.version(package_json_1.version)
|
|
19
|
-
.option('--no-content', 'Do not show file contents, only list matching paths')
|
|
20
|
-
.option('--absolute', 'Show the absolute path for entries', false)
|
|
21
|
-
.option('-c, --copy', 'Copy the output to clipboard instead of printing to console', false)
|
|
22
|
-
.option('-m, --max-lines <int>', 'Show a limited number of lines in the file. If you not provide a number of lines it will show the full file content.', (value) => {
|
|
23
|
-
if (isNaN(parseInt(value)))
|
|
24
|
-
throw new Error('Lines must be a number');
|
|
25
|
-
return parseInt(value);
|
|
26
|
-
})
|
|
27
|
-
.option('-n, --line-numbers', 'Show line numbers next to each line, like in IDE sidebars', false)
|
|
28
|
-
.argument('[patterns...]', 'Glob patterns to match files')
|
|
29
|
-
.action(async (patterns, options) => {
|
|
30
|
-
if (patterns.length === 0) {
|
|
31
|
-
console.error('Error: No patterns provided.');
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
//expand glob
|
|
35
|
-
const files = await (0, fast_glob_1.default)(patterns, {
|
|
36
|
-
onlyFiles: true,
|
|
37
|
-
absolute: options.absolute ?? false,
|
|
38
|
-
});
|
|
39
|
-
if (files.length === 0) {
|
|
40
|
-
console.error('No files matched the given patterns.');
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
let output = '';
|
|
44
|
-
for (const file of files) {
|
|
45
|
-
if (options.content) {
|
|
46
|
-
const fileOutput = await getFileContent(file, options.maxLines ?? undefined, options.lineNumbers ?? false);
|
|
47
|
-
output += fileOutput;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
output += file + '\n';
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (options.copy) {
|
|
54
|
-
try {
|
|
55
|
-
await clipboardy_1.default.write(output.trim());
|
|
56
|
-
console.log('-> Output copied to clipboard successfully!');
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
console.error('-X Error copying to clipboard:', error);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
console.log(output.trim());
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
program.parse(process.argv);
|
|
68
|
-
/**
|
|
69
|
-
* Find the maximum number of consecutive backticks in a string
|
|
70
|
-
*/
|
|
71
|
-
const findMaxConsecutiveBackticks = (str) => {
|
|
72
|
-
const matches = str.match(/`+/g);
|
|
73
|
-
if (!matches)
|
|
74
|
-
return 0;
|
|
75
|
-
return Math.max(...matches.map((m) => m.length));
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Get file content with markdown format
|
|
79
|
-
* @param filePath - The path to the file
|
|
80
|
-
* @param maxLines - The number of lines to show. If you not provide a number of lines it will show the full file content.
|
|
81
|
-
* @param showLineNumbers - Whether to show line numbers
|
|
82
|
-
* @returns The file content with markdown format
|
|
83
|
-
*/
|
|
84
|
-
const getFileContent = async (filePath, maxLines, showLineNumbers) => {
|
|
85
|
-
try {
|
|
86
|
-
const fileBuffer = await (0, promises_1.readFile)(filePath);
|
|
87
|
-
if ((0, binary_1.isBinaryFile)(fileBuffer)) {
|
|
88
|
-
// is binary!! not show content
|
|
89
|
-
const stats = await (0, promises_1.stat)(filePath);
|
|
90
|
-
const fileSize = stats.size;
|
|
91
|
-
const extension = path_1.default.extname(filePath).replace('.', '');
|
|
92
|
-
const sizeFormatted = (0, pipes_1.formatSizeInMB)(fileSize);
|
|
93
|
-
return ('```' +
|
|
94
|
-
extension +
|
|
95
|
-
'\n' +
|
|
96
|
-
`// ${filePath}\n` +
|
|
97
|
-
`// [BINARY FILE] - Size: ${sizeFormatted}\n` +
|
|
98
|
-
'```\n\n');
|
|
99
|
-
}
|
|
100
|
-
// is text file!! proceed with normal processing
|
|
101
|
-
const content = fileBuffer.toString('utf-8');
|
|
102
|
-
const lines = content.split('\n');
|
|
103
|
-
// maxLines if exists
|
|
104
|
-
const linesToShow = maxLines ? lines.slice(0, maxLines) : lines;
|
|
105
|
-
let contentToShow = linesToShow.join('\n');
|
|
106
|
-
if (showLineNumbers)
|
|
107
|
-
contentToShow = (0, pipes_1.addLineNumbers)(linesToShow.join('\n'), 1);
|
|
108
|
-
const extension = path_1.default.extname(filePath).replace('.', '');
|
|
109
|
-
const maxBackticks = findMaxConsecutiveBackticks(content);
|
|
110
|
-
const wrapper = '`'.repeat(Math.max(3, maxBackticks + 1));
|
|
111
|
-
const truncation = maxLines && lines.length > maxLines
|
|
112
|
-
? `\n// ... (${lines.length - maxLines} more lines truncated)`
|
|
113
|
-
: '';
|
|
114
|
-
return (wrapper +
|
|
115
|
-
extension +
|
|
116
|
-
'\n' +
|
|
117
|
-
`// ${filePath}\n\n` +
|
|
118
|
-
contentToShow +
|
|
119
|
-
truncation +
|
|
120
|
-
'\n' +
|
|
121
|
-
wrapper +
|
|
122
|
-
'\n\n');
|
|
123
|
-
}
|
|
124
|
-
catch (e) {
|
|
125
|
-
console.error(`Error reading file ${filePath}:`, e);
|
|
126
|
-
return '';
|
|
127
|
-
}
|
|
128
|
-
};
|
|
3
|
+
const main_1 = require("./main");
|
|
4
|
+
(0, main_1.main)();
|
|
129
5
|
//# sourceMappingURL=index.js.map
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const main: () => Promise<void>;
|
|
2
|
+
/**
|
|
3
|
+
* Filter files by .gitignore rules recursively
|
|
4
|
+
*/
|
|
5
|
+
export declare const filterByGitignore: (files: string[]) => Promise<string[]>;
|
|
6
|
+
/**
|
|
7
|
+
* Find the maximum number of consecutive backticks in a string
|
|
8
|
+
*/
|
|
9
|
+
export declare const findMaxConsecutiveBackticks: (str: string) => number;
|
|
10
|
+
/**
|
|
11
|
+
* Get file content with markdown format
|
|
12
|
+
* @param filePath - The path to the file
|
|
13
|
+
* @param maxLines - The number of lines to show. If you not provide a number of lines it will show the full file content.
|
|
14
|
+
* @param showLineNumbers - Whether to show line numbers
|
|
15
|
+
* @returns The file content with markdown format
|
|
16
|
+
*/
|
|
17
|
+
export declare const getFileContent: (filePath: string, maxLines?: number, showLineNumbers?: boolean) => Promise<string>;
|
|
18
|
+
//# sourceMappingURL=main.d.ts.map
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.getFileContent = exports.findMaxConsecutiveBackticks = exports.filterByGitignore = exports.main = void 0;
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const package_json_1 = require("../package.json");
|
|
42
|
+
const promises_1 = require("fs/promises");
|
|
43
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
44
|
+
const path_1 = __importDefault(require("path"));
|
|
45
|
+
const clipboardy_1 = __importDefault(require("clipboardy"));
|
|
46
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
47
|
+
const binary_1 = require("./utils/binary");
|
|
48
|
+
const pipes_1 = require("./utils/pipes");
|
|
49
|
+
const gitignore_1 = require("./utils/gitignore");
|
|
50
|
+
const apply_1 = require("./apply");
|
|
51
|
+
const main = async () => {
|
|
52
|
+
const program = new commander_1.Command();
|
|
53
|
+
program
|
|
54
|
+
.name('stdin-glob')
|
|
55
|
+
.description('Expand glob patterns and output file contents and paths')
|
|
56
|
+
.version(package_json_1.version)
|
|
57
|
+
.option('--no-content', 'Do not show file contents, only list matching paths')
|
|
58
|
+
.option('--absolute', 'Show the absolute path for entries', false)
|
|
59
|
+
.option('-c, --copy', 'Copy the output to clipboard instead of printing to console', false)
|
|
60
|
+
.option('-m, --max-lines <int>', 'Show a limited number of lines in the file. If you not provide a number of lines it will show the full file content.', (value) => {
|
|
61
|
+
if (isNaN(parseInt(value)))
|
|
62
|
+
throw new Error('Lines must be a number');
|
|
63
|
+
return parseInt(value);
|
|
64
|
+
})
|
|
65
|
+
.option('-n, --line-numbers', 'Show line numbers next to each line, like in IDE sidebars', false)
|
|
66
|
+
.option('--no-gitignore', 'Disable .gitignore filtering (include files that would normally be ignored)')
|
|
67
|
+
.argument('[patterns...]', 'Glob patterns to match files')
|
|
68
|
+
.action(async (patterns, options) => {
|
|
69
|
+
if (patterns.length === 0) {
|
|
70
|
+
console.error('Error: No patterns provided.');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
// Expand glob
|
|
74
|
+
let files = await (0, fast_glob_1.default)(patterns, {
|
|
75
|
+
onlyFiles: true,
|
|
76
|
+
dot: true,
|
|
77
|
+
absolute: false,
|
|
78
|
+
});
|
|
79
|
+
if (files.length === 0) {
|
|
80
|
+
console.error('No files matched the given patterns.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
// Apply gitignore filtering if not disabled
|
|
84
|
+
if (options.gitignore) {
|
|
85
|
+
files = await (0, exports.filterByGitignore)(files);
|
|
86
|
+
if (files.length === 0) {
|
|
87
|
+
console.error('No files remained after applying .gitignore rules.');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
let output = '';
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
if (options.content) {
|
|
94
|
+
const fileOutput = await (0, exports.getFileContent)(options.absolute ? path_1.default.join(process.cwd(), file) : file, options.maxLines ?? undefined, options.lineNumbers ?? false);
|
|
95
|
+
output += fileOutput;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
output += file + '\n';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (options.copy) {
|
|
102
|
+
try {
|
|
103
|
+
await clipboardy_1.default.write(output.trim());
|
|
104
|
+
console.log('-> Output copied to clipboard successfully!');
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.error('-X Error copying to clipboard:', error);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.log(output.trim());
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
// Apply subcommand
|
|
116
|
+
program
|
|
117
|
+
.command('apply')
|
|
118
|
+
.description('Apply code blocks from a file to create/update files (inverse operation)')
|
|
119
|
+
.argument('<input-file>', 'File containing code blocks in markdown format')
|
|
120
|
+
.option('-d, --dir <path>', 'Base directory to apply files (default: current directory)')
|
|
121
|
+
.action(async (inputFile, options) => {
|
|
122
|
+
try {
|
|
123
|
+
const { parseCodeBlocks } = await Promise.resolve().then(() => __importStar(require('./apply')));
|
|
124
|
+
const content = await (0, promises_1.readFile)(inputFile, 'utf-8');
|
|
125
|
+
const parsed = parseCodeBlocks(content);
|
|
126
|
+
if (parsed.length === 0) {
|
|
127
|
+
console.error('No valid code blocks found in the input file.');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
console.log(`Found ${parsed.length} file(s) to process:\n`);
|
|
131
|
+
for (const file of parsed) {
|
|
132
|
+
const status = file.isBinary ? '[SKIP - binary]' : '[OK]';
|
|
133
|
+
console.log(` ${status} ${file.filePath}`);
|
|
134
|
+
}
|
|
135
|
+
if (options.dryRun) {
|
|
136
|
+
console.log('\n[Dry run] No files were modified.');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const result = await (0, apply_1.applyFromFile)(inputFile, options.dir);
|
|
140
|
+
console.log('\nResults:');
|
|
141
|
+
if (result.created.length > 0) {
|
|
142
|
+
console.log(` Created: ${result.created.length}`);
|
|
143
|
+
result.created.forEach((f) => console.log(` + ${f}`));
|
|
144
|
+
}
|
|
145
|
+
if (result.updated.length > 0) {
|
|
146
|
+
console.log(` Updated: ${result.updated.length}`);
|
|
147
|
+
result.updated.forEach((f) => console.log(` ~ ${f}`));
|
|
148
|
+
}
|
|
149
|
+
if (result.skipped.length > 0) {
|
|
150
|
+
console.log(` Skipped (binary): ${result.skipped.length}`);
|
|
151
|
+
result.skipped.forEach((f) => console.log(` - ${f}`));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.error('Error:', error);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
await program.parseAsync(process.argv);
|
|
160
|
+
};
|
|
161
|
+
exports.main = main;
|
|
162
|
+
/**
|
|
163
|
+
* Filter files by .gitignore rules recursively
|
|
164
|
+
*/
|
|
165
|
+
const filterByGitignore = async (files) => {
|
|
166
|
+
try {
|
|
167
|
+
// Find all .gitignore files in the project
|
|
168
|
+
const gitignoreFiles = await (0, gitignore_1.findGitignoreFiles)();
|
|
169
|
+
if (gitignoreFiles.length === 0) {
|
|
170
|
+
return files;
|
|
171
|
+
}
|
|
172
|
+
// Create ignore instance with all rules
|
|
173
|
+
const ig = (0, ignore_1.default)().add(await (0, gitignore_1.loadGitignoreRules)(gitignoreFiles));
|
|
174
|
+
return files.filter((file) => {
|
|
175
|
+
// Get relative path from the root where gitignore rules apply
|
|
176
|
+
const relativePath = path_1.default.relative(process.cwd(), file);
|
|
177
|
+
return !ig.ignores(relativePath);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
console.warn('Warning: Error processing .gitignore files, proceeding without filtering:', error);
|
|
182
|
+
return files;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
exports.filterByGitignore = filterByGitignore;
|
|
186
|
+
/**
|
|
187
|
+
* Find the maximum number of consecutive backticks in a string
|
|
188
|
+
*/
|
|
189
|
+
const findMaxConsecutiveBackticks = (str) => {
|
|
190
|
+
const matches = str.match(/`+/g);
|
|
191
|
+
if (!matches)
|
|
192
|
+
return 0;
|
|
193
|
+
return Math.max(...matches.map((m) => m.length));
|
|
194
|
+
};
|
|
195
|
+
exports.findMaxConsecutiveBackticks = findMaxConsecutiveBackticks;
|
|
196
|
+
/**
|
|
197
|
+
* Get file content with markdown format
|
|
198
|
+
* @param filePath - The path to the file
|
|
199
|
+
* @param maxLines - The number of lines to show. If you not provide a number of lines it will show the full file content.
|
|
200
|
+
* @param showLineNumbers - Whether to show line numbers
|
|
201
|
+
* @returns The file content with markdown format
|
|
202
|
+
*/
|
|
203
|
+
const getFileContent = async (filePath, maxLines, showLineNumbers) => {
|
|
204
|
+
try {
|
|
205
|
+
const fileBuffer = await (0, promises_1.readFile)(filePath);
|
|
206
|
+
if ((0, binary_1.isBinaryFile)(fileBuffer)) {
|
|
207
|
+
// is binary!! not show content
|
|
208
|
+
const stats = await (0, promises_1.stat)(filePath);
|
|
209
|
+
const fileSize = stats.size;
|
|
210
|
+
const extension = path_1.default.extname(filePath).replace('.', '');
|
|
211
|
+
const sizeFormatted = (0, pipes_1.formatSizeInMB)(fileSize);
|
|
212
|
+
return ('```' +
|
|
213
|
+
extension +
|
|
214
|
+
'\n' +
|
|
215
|
+
`// ${filePath}\n` +
|
|
216
|
+
`// [BINARY FILE] - Size: ${sizeFormatted}\n` +
|
|
217
|
+
'```\n\n');
|
|
218
|
+
}
|
|
219
|
+
// is text file!! proceed with normal processing
|
|
220
|
+
const content = fileBuffer.toString('utf-8');
|
|
221
|
+
const lines = content.split('\n');
|
|
222
|
+
// maxLines if exists
|
|
223
|
+
const linesToShow = maxLines ? lines.slice(0, maxLines) : lines;
|
|
224
|
+
let contentToShow = linesToShow.join('\n');
|
|
225
|
+
if (showLineNumbers)
|
|
226
|
+
contentToShow = (0, pipes_1.addLineNumbers)(linesToShow.join('\n'), 1);
|
|
227
|
+
const extension = path_1.default.extname(filePath).replace('.', '');
|
|
228
|
+
const maxBackticks = (0, exports.findMaxConsecutiveBackticks)(content);
|
|
229
|
+
const wrapper = '`'.repeat(Math.max(3, maxBackticks + 1));
|
|
230
|
+
const truncation = maxLines && lines.length > maxLines
|
|
231
|
+
? `\n// ... (${lines.length - maxLines} more lines truncated)`
|
|
232
|
+
: '';
|
|
233
|
+
return (wrapper +
|
|
234
|
+
extension +
|
|
235
|
+
'\n' +
|
|
236
|
+
`// ${filePath}\n\n` +
|
|
237
|
+
contentToShow +
|
|
238
|
+
truncation +
|
|
239
|
+
'\n' +
|
|
240
|
+
wrapper +
|
|
241
|
+
'\n\n');
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
console.error(`Error reading file ${filePath}:`, e);
|
|
245
|
+
return '';
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
exports.getFileContent = getFileContent;
|
|
249
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const promises_1 = require("fs/promises");
|
|
5
|
+
const binary_1 = require("./utils/binary");
|
|
6
|
+
const pipes_1 = require("./utils/pipes");
|
|
7
|
+
const gitignore_1 = require("./utils/gitignore");
|
|
8
|
+
const main_1 = require("./main");
|
|
9
|
+
vitest_1.vi.mock('fs/promises');
|
|
10
|
+
vitest_1.vi.mock('fast-glob', () => ({ default: async () => [] }));
|
|
11
|
+
vitest_1.vi.mock('clipboardy');
|
|
12
|
+
vitest_1.vi.mock('./utils/binary');
|
|
13
|
+
vitest_1.vi.mock('./utils/pipes');
|
|
14
|
+
vitest_1.vi.mock('./utils/gitignore');
|
|
15
|
+
(0, vitest_1.describe)('filterByGitignore', () => {
|
|
16
|
+
(0, vitest_1.beforeEach)(() => {
|
|
17
|
+
vitest_1.vi.resetAllMocks();
|
|
18
|
+
});
|
|
19
|
+
//
|
|
20
|
+
(0, vitest_1.it)('returns all files when no gitignore files found', async () => {
|
|
21
|
+
vitest_1.vi.mocked(gitignore_1.findGitignoreFiles).mockResolvedValue([]);
|
|
22
|
+
const files = ['file1.ts', 'file2.ts'];
|
|
23
|
+
const result = await (0, main_1.filterByGitignore)(files);
|
|
24
|
+
(0, vitest_1.expect)(result).toEqual(files);
|
|
25
|
+
});
|
|
26
|
+
//
|
|
27
|
+
(0, vitest_1.it)('filters files based on gitignore rules', async () => {
|
|
28
|
+
vitest_1.vi.mocked(gitignore_1.findGitignoreFiles).mockResolvedValue(['.gitignore']);
|
|
29
|
+
vitest_1.vi.mocked(gitignore_1.loadGitignoreRules).mockResolvedValue(['node_modules']);
|
|
30
|
+
const mockIg = { ignores: vitest_1.vi.fn().mockReturnValue(true) };
|
|
31
|
+
vitest_1.vi.doMock('ignore', () => () => mockIg);
|
|
32
|
+
const files = ['src/index.ts', 'node_modules/test.js'];
|
|
33
|
+
const result = await (0, main_1.filterByGitignore)(files);
|
|
34
|
+
(0, vitest_1.expect)(result).toHaveLength(1);
|
|
35
|
+
});
|
|
36
|
+
//
|
|
37
|
+
(0, vitest_1.it)('returns all files when gitignore processing fails', async () => {
|
|
38
|
+
vitest_1.vi.mocked(gitignore_1.findGitignoreFiles).mockRejectedValue(new Error('Failed'));
|
|
39
|
+
const files = ['file1.ts'];
|
|
40
|
+
const result = await (0, main_1.filterByGitignore)(files);
|
|
41
|
+
(0, vitest_1.expect)(result).toEqual(files);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
(0, vitest_1.describe)('findMaxConsecutiveBackticks', () => {
|
|
45
|
+
(0, vitest_1.it)('returns 0 for string without backticks', () => {
|
|
46
|
+
(0, vitest_1.expect)((0, main_1.findMaxConsecutiveBackticks)('hello world')).toBe(0);
|
|
47
|
+
});
|
|
48
|
+
(0, vitest_1.it)('finds max consecutive backticks', () => {
|
|
49
|
+
(0, vitest_1.expect)((0, main_1.findMaxConsecutiveBackticks)('` ``` ````')).toBe(4);
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.it)('handles multiple occurrences', () => {
|
|
52
|
+
(0, vitest_1.expect)((0, main_1.findMaxConsecutiveBackticks)('` ``` ` ``')).toBe(3);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
(0, vitest_1.describe)('getFileContent', () => {
|
|
56
|
+
(0, vitest_1.beforeEach)(() => {
|
|
57
|
+
vitest_1.vi.resetAllMocks();
|
|
58
|
+
});
|
|
59
|
+
(0, vitest_1.it)('returns empty string on file read error', async () => {
|
|
60
|
+
vitest_1.vi.mocked(promises_1.readFile).mockRejectedValue(new Error('Read error'));
|
|
61
|
+
const result = await (0, main_1.getFileContent)('test.txt');
|
|
62
|
+
(0, vitest_1.expect)(result).toBe('');
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.it)('handles binary files', async () => {
|
|
65
|
+
vitest_1.vi.mocked(promises_1.readFile).mockResolvedValue(Buffer.from([0x00, 0x01]));
|
|
66
|
+
vitest_1.vi.mocked(binary_1.isBinaryFile).mockReturnValue(true);
|
|
67
|
+
vitest_1.vi.mocked(promises_1.stat).mockResolvedValue({ size: 1024 });
|
|
68
|
+
vitest_1.vi.mocked(pipes_1.formatSizeInMB).mockReturnValue('0.001 MB');
|
|
69
|
+
const result = await (0, main_1.getFileContent)('test.png');
|
|
70
|
+
(0, vitest_1.expect)(result).toContain('[BINARY FILE]');
|
|
71
|
+
(0, vitest_1.expect)(result).toContain('test.png');
|
|
72
|
+
});
|
|
73
|
+
(0, vitest_1.it)('processes text files correctly', async () => {
|
|
74
|
+
const content = 'line1\nline2\nline3';
|
|
75
|
+
vitest_1.vi.mocked(promises_1.readFile).mockResolvedValue(Buffer.from(content));
|
|
76
|
+
vitest_1.vi.mocked(binary_1.isBinaryFile).mockReturnValue(false);
|
|
77
|
+
const result = await (0, main_1.getFileContent)('test.txt');
|
|
78
|
+
(0, vitest_1.expect)(result).toContain('test.txt');
|
|
79
|
+
(0, vitest_1.expect)(result).toContain('line1');
|
|
80
|
+
(0, vitest_1.expect)(result).toContain('line2');
|
|
81
|
+
});
|
|
82
|
+
(0, vitest_1.it)('respects maxLines parameter', async () => {
|
|
83
|
+
const content = 'line1\nline2\nline3';
|
|
84
|
+
vitest_1.vi.mocked(promises_1.readFile).mockResolvedValue(Buffer.from(content));
|
|
85
|
+
vitest_1.vi.mocked(binary_1.isBinaryFile).mockReturnValue(false);
|
|
86
|
+
const result = await (0, main_1.getFileContent)('test.txt', 2);
|
|
87
|
+
(0, vitest_1.expect)(result).toContain('line1');
|
|
88
|
+
(0, vitest_1.expect)(result).toContain('line2');
|
|
89
|
+
(0, vitest_1.expect)(result).not.toContain('line3');
|
|
90
|
+
(0, vitest_1.expect)(result).toContain('1 more lines truncated');
|
|
91
|
+
});
|
|
92
|
+
(0, vitest_1.it)('adds line numbers when requested', async () => {
|
|
93
|
+
const content = 'line1\nline2';
|
|
94
|
+
vitest_1.vi.mocked(promises_1.readFile).mockResolvedValue(Buffer.from(content));
|
|
95
|
+
vitest_1.vi.mocked(binary_1.isBinaryFile).mockReturnValue(false);
|
|
96
|
+
vitest_1.vi.mocked(pipes_1.addLineNumbers).mockReturnValue('1 | line1\n2 | line2');
|
|
97
|
+
const result = await (0, main_1.getFileContent)('test.txt', undefined, true);
|
|
98
|
+
(0, vitest_1.expect)(result).toContain('1 | line1');
|
|
99
|
+
(0, vitest_1.expect)(result).toContain('2 | line2');
|
|
100
|
+
});
|
|
101
|
+
(0, vitest_1.it)('handles code blocks with backticks', async () => {
|
|
102
|
+
const content = '```\ncode\n```';
|
|
103
|
+
vitest_1.vi.mocked(promises_1.readFile).mockResolvedValue(Buffer.from(content));
|
|
104
|
+
vitest_1.vi.mocked(binary_1.isBinaryFile).mockReturnValue(false);
|
|
105
|
+
const result = await (0, main_1.getFileContent)('test.js');
|
|
106
|
+
(0, vitest_1.expect)(result).toContain('````js');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
(0, vitest_1.describe)('CLI Integration', () => {
|
|
110
|
+
(0, vitest_1.it)('exits with error when no patterns provided', async () => {
|
|
111
|
+
vitest_1.vi.mocked(gitignore_1.findGitignoreFiles).mockResolvedValue([]);
|
|
112
|
+
const mockExit = vitest_1.vi
|
|
113
|
+
.spyOn(process, 'exit')
|
|
114
|
+
.mockImplementation(() => undefined);
|
|
115
|
+
const mockError = vitest_1.vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
116
|
+
await (0, main_1.main)();
|
|
117
|
+
(0, vitest_1.expect)(mockExit).toHaveBeenCalledWith(1);
|
|
118
|
+
mockExit.mockRestore();
|
|
119
|
+
mockError.mockRestore();
|
|
120
|
+
});
|
|
121
|
+
(0, vitest_1.it)('exits with error when no files matched', async () => {
|
|
122
|
+
vitest_1.vi.mocked(gitignore_1.findGitignoreFiles).mockResolvedValue([]);
|
|
123
|
+
const mockExit = vitest_1.vi
|
|
124
|
+
.spyOn(process, 'exit')
|
|
125
|
+
.mockImplementation(() => undefined);
|
|
126
|
+
const mockError = vitest_1.vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
127
|
+
await (0, main_1.main)();
|
|
128
|
+
(0, vitest_1.expect)(mockExit).toHaveBeenCalledWith(1);
|
|
129
|
+
mockExit.mockRestore();
|
|
130
|
+
mockError.mockRestore();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
//# sourceMappingURL=main.test.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const binary_1 = require("./binary");
|
|
5
|
+
(0, vitest_1.describe)('isBinaryFile', () => {
|
|
6
|
+
(0, vitest_1.it)('returns false for text content', () => {
|
|
7
|
+
const buffer = Buffer.from('Hello world\nThis is text');
|
|
8
|
+
(0, vitest_1.expect)((0, binary_1.isBinaryFile)(buffer)).toBe(false);
|
|
9
|
+
});
|
|
10
|
+
(0, vitest_1.it)('returns true for null bytes', () => {
|
|
11
|
+
const buffer = Buffer.from([0x48, 0x00, 0x65, 0x6c, 0x6c, 0x6f]);
|
|
12
|
+
(0, vitest_1.expect)((0, binary_1.isBinaryFile)(buffer)).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
(0, vitest_1.it)('returns true for control characters', () => {
|
|
15
|
+
const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04]);
|
|
16
|
+
(0, vitest_1.expect)((0, binary_1.isBinaryFile)(buffer)).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
(0, vitest_1.it)('allows whitespace characters', () => {
|
|
19
|
+
const buffer = Buffer.from('Hello\tworld\nnew line\rreturn');
|
|
20
|
+
(0, vitest_1.expect)((0, binary_1.isBinaryFile)(buffer)).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)('only checks first 512 bytes', () => {
|
|
23
|
+
const buffer = Buffer.alloc(1000);
|
|
24
|
+
buffer.fill(0x41); // 'A'
|
|
25
|
+
buffer[500] = 0x00; // null byte within first 512
|
|
26
|
+
(0, vitest_1.expect)((0, binary_1.isBinaryFile)(buffer)).toBe(true);
|
|
27
|
+
const buffer2 = Buffer.alloc(1000);
|
|
28
|
+
buffer2.fill(0x41);
|
|
29
|
+
buffer2[600] = 0x00; // null byte after first 512
|
|
30
|
+
(0, vitest_1.expect)((0, binary_1.isBinaryFile)(buffer2)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=binary.test.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find all .gitignore files in the project
|
|
3
|
+
*/
|
|
4
|
+
export declare const findGitignoreFiles: () => Promise<string[]>;
|
|
5
|
+
/**
|
|
6
|
+
* Load and combine rules from recursive/multiple .gitignore files
|
|
7
|
+
* @see https://git-scm.com/docs/gitignore#_pattern_format
|
|
8
|
+
*/
|
|
9
|
+
export declare const loadGitignoreRules: (gitignoreFiles: string[]) => Promise<string[]>;
|
|
10
|
+
//# sourceMappingURL=gitignore.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadGitignoreRules = exports.findGitignoreFiles = void 0;
|
|
7
|
+
const promises_1 = require("fs/promises");
|
|
8
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
/**
|
|
11
|
+
* Find all .gitignore files in the project
|
|
12
|
+
*/
|
|
13
|
+
const findGitignoreFiles = async () => {
|
|
14
|
+
try {
|
|
15
|
+
const files = await (0, fast_glob_1.default)('**/.gitignore', {
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
ignore: ['**/node_modules/**'],
|
|
18
|
+
});
|
|
19
|
+
return files;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.warn('Warning: Error finding .gitignore files:', error);
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
exports.findGitignoreFiles = findGitignoreFiles;
|
|
27
|
+
/**
|
|
28
|
+
* Parse a .gitignore file and return its rules
|
|
29
|
+
*/
|
|
30
|
+
const parseGitignore = (content) => {
|
|
31
|
+
return content
|
|
32
|
+
.split('\n')
|
|
33
|
+
.map((line) => line.trim())
|
|
34
|
+
.filter((line) => line && // Not empty
|
|
35
|
+
!line.startsWith('#'));
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Load and combine rules from recursive/multiple .gitignore files
|
|
39
|
+
* @see https://git-scm.com/docs/gitignore#_pattern_format
|
|
40
|
+
*/
|
|
41
|
+
const loadGitignoreRules = async (gitignoreFiles) => {
|
|
42
|
+
const allRules = [];
|
|
43
|
+
for (const file of gitignoreFiles) {
|
|
44
|
+
try {
|
|
45
|
+
const content = await (0, promises_1.readFile)(file, 'utf-8');
|
|
46
|
+
const rules = parseGitignore(content);
|
|
47
|
+
// directory of this .gitignore
|
|
48
|
+
const gitignoreDir = path_1.default.dirname(file);
|
|
49
|
+
// to be relative to the gitignore location
|
|
50
|
+
const dirRelativeRules = rules.map((rule) => {
|
|
51
|
+
//handle negations
|
|
52
|
+
if (rule.startsWith('!')) {
|
|
53
|
+
return `!${path_1.default.join(gitignoreDir, rule.slice(1))}`;
|
|
54
|
+
}
|
|
55
|
+
return path_1.default.join(gitignoreDir, rule);
|
|
56
|
+
});
|
|
57
|
+
allRules.push(...dirRelativeRules);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.warn(`Warning: Error reading .gitignore file ${file}:`, error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return allRules;
|
|
64
|
+
};
|
|
65
|
+
exports.loadGitignoreRules = loadGitignoreRules;
|
|
66
|
+
//# sourceMappingURL=gitignore.js.map
|