vg-coder-cli 1.0.6 → 1.0.7
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 +1 -0
- package/package.json +1 -1
- package/src/exporter/html-exporter.js +125 -1
- package/src/index.js +53 -6
- package/src/scanner/file-scanner.js +48 -0
- package/src/utils/clipboard.js +170 -0
- package/vg-coder-cli-1.0.7.tgz +0 -0
- package/vg-coder-cli-1.0.2.tgz +0 -0
- package/vg-coder-cli-1.0.3.tgz +0 -0
- package/vg-coder-cli-1.0.4.tgz +0 -0
- package/vg-coder-cli-1.0.5.tgz +0 -0
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- 🔍 **Phát hiện loại dự án**: Tự động nhận diện Angular, Spring Boot, React, Vue, Node.js, Python, Java, .NET
|
|
8
8
|
- 📁 **Xử lý .gitignore**: Tuân thủ chuẩn Git với multi-level ignore rules
|
|
9
|
+
- **.vgignore support**: Priority cao hơn .gitignore, với syntax giống hệt
|
|
9
10
|
- 📄 **Scan và nối file**: Quét toàn bộ dự án và nối file mã nguồn
|
|
10
11
|
- 🧮 **Đếm token**: Sử dụng tiktoken để đếm token chính xác cho AI models
|
|
11
12
|
- ✂️ **Chia nhỏ nội dung**: Smart chunking với preserve structure
|
package/package.json
CHANGED
|
@@ -39,11 +39,15 @@ class HtmlExporter {
|
|
|
39
39
|
|
|
40
40
|
// Tạo combined view
|
|
41
41
|
await this.createCombinedPage(chunks, metadata);
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
// Tạo combined.txt cho AI tools
|
|
44
|
+
await this.createCombinedTxtFile(chunks, metadata);
|
|
45
|
+
|
|
43
46
|
return {
|
|
44
47
|
indexPath: path.join(this.outputPath, 'index.html'),
|
|
45
48
|
chunksPath: path.join(this.outputPath, 'chunks'),
|
|
46
49
|
combinedPath: path.join(this.outputPath, 'combined.html'),
|
|
50
|
+
combinedTxtPath: path.join(this.outputPath, 'combined.txt'),
|
|
47
51
|
totalFiles: chunks.length
|
|
48
52
|
};
|
|
49
53
|
}
|
|
@@ -250,6 +254,126 @@ Nếu file chưa tồn tại, script sẽ tự tạo file và thư mục cha.</c
|
|
|
250
254
|
await fs.writeFile(path.join(this.outputPath, 'combined.html'), html);
|
|
251
255
|
}
|
|
252
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Tạo combined.txt file cho AI tools với formatting tối ưu
|
|
259
|
+
*/
|
|
260
|
+
async createCombinedTxtFile(chunks, metadata) {
|
|
261
|
+
// Sử dụng files gốc nếu có, nếu không thì dùng chunks
|
|
262
|
+
const files = metadata.files;
|
|
263
|
+
|
|
264
|
+
if (files && files.length > 0) {
|
|
265
|
+
// Tạo content từ files gốc với formatting AI-friendly
|
|
266
|
+
let content = '';
|
|
267
|
+
|
|
268
|
+
// Minimal header cho AI context
|
|
269
|
+
content += `// VG Coder Analysis - ${metadata.projectInfo?.primary || 'Unknown'} Project\n`;
|
|
270
|
+
content += `// Files: ${files.length} | Generated: ${new Date().toISOString()}\n\n`;
|
|
271
|
+
|
|
272
|
+
// Nội dung từng file với boundaries rõ ràng
|
|
273
|
+
for (let i = 0; i < files.length; i++) {
|
|
274
|
+
const file = files[i];
|
|
275
|
+
|
|
276
|
+
// File boundary marker
|
|
277
|
+
content += `// ===== FILE: ${file.relativePath} =====\n`;
|
|
278
|
+
|
|
279
|
+
// Nội dung file nguyên bản
|
|
280
|
+
content += file.content;
|
|
281
|
+
|
|
282
|
+
// Đảm bảo file kết thúc bằng newline
|
|
283
|
+
if (!file.content.endsWith('\n')) {
|
|
284
|
+
content += '\n';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Separator giữa các files
|
|
288
|
+
if (i < files.length - 1) {
|
|
289
|
+
content += '\n';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await fs.writeFile(path.join(this.outputPath, 'combined.txt'), content, 'utf8');
|
|
294
|
+
} else {
|
|
295
|
+
// Fallback: sử dụng chunks (legacy)
|
|
296
|
+
let content = '';
|
|
297
|
+
|
|
298
|
+
// Minimal header cho AI context
|
|
299
|
+
content += `// VG Coder Analysis - ${metadata.projectInfo?.primary || 'Unknown'} Project\n`;
|
|
300
|
+
content += `// Files: ${chunks.length} | Generated: ${new Date().toISOString()}\n\n`;
|
|
301
|
+
|
|
302
|
+
// Parse chunks để extract file content với boundaries rõ ràng
|
|
303
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
304
|
+
const chunk = chunks[i];
|
|
305
|
+
|
|
306
|
+
// Extract file content từ chunk, bỏ qua headers và separators
|
|
307
|
+
const cleanContent = this.extractCleanContent(chunk.content);
|
|
308
|
+
|
|
309
|
+
if (cleanContent.trim()) {
|
|
310
|
+
content += cleanContent;
|
|
311
|
+
|
|
312
|
+
// Đảm bảo kết thúc bằng newline
|
|
313
|
+
if (!cleanContent.endsWith('\n')) {
|
|
314
|
+
content += '\n';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Separator giữa các chunks (minimal)
|
|
318
|
+
if (i < chunks.length - 1) {
|
|
319
|
+
content += '\n';
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
await fs.writeFile(path.join(this.outputPath, 'combined.txt'), content, 'utf8');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Extract clean content từ chunk, loại bỏ headers và formatting
|
|
330
|
+
*/
|
|
331
|
+
extractCleanContent(chunkContent) {
|
|
332
|
+
const lines = chunkContent.split('\n');
|
|
333
|
+
let cleanLines = [];
|
|
334
|
+
let inFileContent = false;
|
|
335
|
+
let currentFilePath = '';
|
|
336
|
+
|
|
337
|
+
for (let i = 0; i < lines.length; i++) {
|
|
338
|
+
const line = lines[i];
|
|
339
|
+
|
|
340
|
+
// Detect file header
|
|
341
|
+
if (line.includes('================================================================================')) {
|
|
342
|
+
if (i + 1 < lines.length && lines[i + 1].startsWith('File: ')) {
|
|
343
|
+
// Start of new file
|
|
344
|
+
currentFilePath = lines[i + 1].replace('File: ', '').trim();
|
|
345
|
+
cleanLines.push(`// ===== FILE: ${currentFilePath} =====`);
|
|
346
|
+
inFileContent = false;
|
|
347
|
+
i += 2; // Skip header lines
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Skip project headers and structure
|
|
353
|
+
if (line.startsWith('# Project Analysis Report') ||
|
|
354
|
+
line.startsWith('Generated:') ||
|
|
355
|
+
line.startsWith('Project Path:') ||
|
|
356
|
+
line.startsWith('## Statistics') ||
|
|
357
|
+
line.startsWith('## Project Structure') ||
|
|
358
|
+
line.startsWith('- Total') ||
|
|
359
|
+
line.startsWith('- Extensions:') ||
|
|
360
|
+
line.startsWith('```') ||
|
|
361
|
+
line.startsWith('├──') ||
|
|
362
|
+
line.startsWith('└──') ||
|
|
363
|
+
line.trim() === '') {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Add actual file content
|
|
368
|
+
if (currentFilePath && !line.includes('================================================================================')) {
|
|
369
|
+
cleanLines.push(line);
|
|
370
|
+
inFileContent = true;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return cleanLines.join('\n');
|
|
375
|
+
}
|
|
376
|
+
|
|
253
377
|
/**
|
|
254
378
|
* Copy static assets
|
|
255
379
|
*/
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const ProjectDetector = require('./detectors/project-detector');
|
|
|
9
9
|
const FileScanner = require('./scanner/file-scanner');
|
|
10
10
|
const TokenManager = require('./tokenizer/token-manager');
|
|
11
11
|
const HtmlExporter = require('./exporter/html-exporter');
|
|
12
|
+
const ClipboardManager = require('./utils/clipboard');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Main CLI Application
|
|
@@ -39,6 +40,8 @@ class VGCoderCLI {
|
|
|
39
40
|
.option('--include-hidden', 'Bao gồm file ẩn')
|
|
40
41
|
.option('--no-structure', 'Không ưu tiên giữ cấu trúc file')
|
|
41
42
|
.option('--theme <theme>', 'Theme cho syntax highlighting', 'github')
|
|
43
|
+
.option('--clipboard-only', 'Copy content to clipboard without creating files')
|
|
44
|
+
.option('--clipboard', 'Alias for --clipboard-only')
|
|
42
45
|
.action(this.handleAnalyze.bind(this));
|
|
43
46
|
|
|
44
47
|
// Info command
|
|
@@ -60,17 +63,25 @@ class VGCoderCLI {
|
|
|
60
63
|
*/
|
|
61
64
|
async handleAnalyze(projectPath, options) {
|
|
62
65
|
const spinner = ora('Initializing analysis...').start();
|
|
63
|
-
|
|
66
|
+
|
|
64
67
|
try {
|
|
65
68
|
// Resolve project path
|
|
66
69
|
projectPath = path.resolve(projectPath || process.cwd());
|
|
67
|
-
|
|
70
|
+
|
|
71
|
+
// Check if clipboard-only mode
|
|
72
|
+
const clipboardMode = options.clipboardOnly || options.clipboard;
|
|
73
|
+
const outputPath = clipboardMode ? null : path.resolve(options.output || './vg-output');
|
|
68
74
|
|
|
69
75
|
// Validate project path
|
|
70
76
|
if (!await fs.pathExists(projectPath)) {
|
|
71
77
|
throw new Error(`Project path does not exist: ${projectPath}`);
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
// Validate output path for non-clipboard mode
|
|
81
|
+
if (!clipboardMode && !outputPath) {
|
|
82
|
+
throw new Error('Output path is required for non-clipboard mode');
|
|
83
|
+
}
|
|
84
|
+
|
|
74
85
|
spinner.text = 'Detecting project type...';
|
|
75
86
|
|
|
76
87
|
// Detect project type
|
|
@@ -123,10 +134,42 @@ class VGCoderCLI {
|
|
|
123
134
|
console.log(`Estimated Chunks: ${tokenAnalysis.summary.estimatedChunks}`);
|
|
124
135
|
|
|
125
136
|
spinner.text = 'Creating content chunks...';
|
|
126
|
-
|
|
127
|
-
|
|
137
|
+
|
|
138
|
+
if (clipboardMode) {
|
|
139
|
+
// Clipboard mode: create AI-friendly content and copy to clipboard
|
|
140
|
+
spinner.text = 'Creating AI-friendly content...';
|
|
141
|
+
|
|
142
|
+
const aiContent = await scanner.createCombinedContentForAI(scanResult.files, {
|
|
143
|
+
includeStats: false,
|
|
144
|
+
includeTree: false,
|
|
145
|
+
preserveLineNumbers: true
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
spinner.text = 'Copying to clipboard...';
|
|
149
|
+
|
|
150
|
+
await ClipboardManager.copyToClipboard(aiContent);
|
|
151
|
+
const contentInfo = ClipboardManager.getContentInfo(aiContent);
|
|
152
|
+
|
|
153
|
+
// Cleanup
|
|
154
|
+
tokenManager.cleanup();
|
|
155
|
+
|
|
156
|
+
spinner.succeed('Content copied to clipboard successfully!');
|
|
157
|
+
|
|
158
|
+
console.log(chalk.green('\n📋 Clipboard Content:'));
|
|
159
|
+
console.log(`Files: ${chalk.cyan(scanResult.files.length)}`);
|
|
160
|
+
console.log(`Lines: ${chalk.cyan(contentInfo.lines.toLocaleString())}`);
|
|
161
|
+
console.log(`Characters: ${chalk.cyan(contentInfo.characters.toLocaleString())}`);
|
|
162
|
+
console.log(`Size: ${chalk.cyan(contentInfo.size)}`);
|
|
163
|
+
|
|
164
|
+
console.log(chalk.blue('\n💡 Ready for AI analysis!'));
|
|
165
|
+
console.log('Content is now in your clipboard and ready to paste into AI tools.');
|
|
166
|
+
|
|
167
|
+
return; // Exit early for clipboard mode
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Normal mode: create HTML output
|
|
128
171
|
const combinedContent = await scanner.createCombinedContent(scanResult.files);
|
|
129
|
-
|
|
172
|
+
|
|
130
173
|
// Chunk content
|
|
131
174
|
const chunks = await tokenManager.chunkContent(combinedContent, {
|
|
132
175
|
projectType: projectInfo.primary,
|
|
@@ -153,7 +196,8 @@ class VGCoderCLI {
|
|
|
153
196
|
projectInfo: projectInfo,
|
|
154
197
|
scanStats: scanResult.stats,
|
|
155
198
|
tokenStats: tokenAnalysis.summary,
|
|
156
|
-
directoryStructure: treeStructure
|
|
199
|
+
directoryStructure: treeStructure,
|
|
200
|
+
files: scanResult.files // Thêm files gốc để tạo combined.txt
|
|
157
201
|
});
|
|
158
202
|
|
|
159
203
|
// Cleanup
|
|
@@ -164,6 +208,9 @@ class VGCoderCLI {
|
|
|
164
208
|
console.log(chalk.green('\n✅ Output Generated:'));
|
|
165
209
|
console.log(`Index: ${chalk.cyan(exportResult.indexPath)}`);
|
|
166
210
|
console.log(`Combined: ${chalk.cyan(exportResult.combinedPath)}`);
|
|
211
|
+
if (exportResult.combinedTxtPath) {
|
|
212
|
+
console.log(`Combined.txt: ${chalk.cyan(exportResult.combinedTxtPath)}`);
|
|
213
|
+
}
|
|
167
214
|
console.log(`Chunks: ${chalk.cyan(exportResult.chunksPath)}`);
|
|
168
215
|
console.log(`Total Files: ${exportResult.totalFiles}`);
|
|
169
216
|
|
|
@@ -446,6 +446,54 @@ Size: {size} bytes | Lines: {lines}
|
|
|
446
446
|
return '\n\n';
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Tạo nội dung kết hợp cho AI tools với formatting chính xác
|
|
451
|
+
*/
|
|
452
|
+
async createCombinedContentForAI(files, options = {}) {
|
|
453
|
+
const {
|
|
454
|
+
includeStats = false,
|
|
455
|
+
includeTree = false,
|
|
456
|
+
preserveLineNumbers = true
|
|
457
|
+
} = options;
|
|
458
|
+
|
|
459
|
+
let content = '';
|
|
460
|
+
|
|
461
|
+
// Header với thông tin project (tùy chọn)
|
|
462
|
+
if (includeStats) {
|
|
463
|
+
content += this.generateProjectHeader(files);
|
|
464
|
+
content += '\n\n';
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Cấu trúc thư mục (tùy chọn)
|
|
468
|
+
if (includeTree) {
|
|
469
|
+
content += this.generateTreeStructure(files);
|
|
470
|
+
content += '\n\n';
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Nội dung từng file với formatting chính xác
|
|
474
|
+
for (let i = 0; i < files.length; i++) {
|
|
475
|
+
const file = files[i];
|
|
476
|
+
|
|
477
|
+
// File boundary marker - không ảnh hưởng line numbering
|
|
478
|
+
content += `// ===== FILE: ${file.relativePath} =====\n`;
|
|
479
|
+
|
|
480
|
+
// Nội dung file nguyên bản
|
|
481
|
+
content += file.content;
|
|
482
|
+
|
|
483
|
+
// Đảm bảo file kết thúc bằng newline
|
|
484
|
+
if (!file.content.endsWith('\n')) {
|
|
485
|
+
content += '\n';
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Separator giữa các files
|
|
489
|
+
if (i < files.length - 1) {
|
|
490
|
+
content += '\n';
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return content;
|
|
495
|
+
}
|
|
496
|
+
|
|
449
497
|
/**
|
|
450
498
|
* Tạo header thông tin project
|
|
451
499
|
*/
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cross-platform clipboard utility
|
|
6
|
+
*/
|
|
7
|
+
class ClipboardManager {
|
|
8
|
+
/**
|
|
9
|
+
* Copy text to clipboard
|
|
10
|
+
*/
|
|
11
|
+
static async copyToClipboard(text) {
|
|
12
|
+
const platform = os.platform();
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
switch (platform) {
|
|
16
|
+
case 'darwin': // macOS
|
|
17
|
+
return await this.copyMacOS(text);
|
|
18
|
+
case 'win32': // Windows
|
|
19
|
+
return await this.copyWindows(text);
|
|
20
|
+
case 'linux': // Linux
|
|
21
|
+
return await this.copyLinux(text);
|
|
22
|
+
default:
|
|
23
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`Failed to copy to clipboard: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Copy to clipboard on macOS
|
|
32
|
+
*/
|
|
33
|
+
static async copyMacOS(text) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const pbcopy = spawn('pbcopy');
|
|
36
|
+
|
|
37
|
+
pbcopy.stdin.write(text);
|
|
38
|
+
pbcopy.stdin.end();
|
|
39
|
+
|
|
40
|
+
pbcopy.on('close', (code) => {
|
|
41
|
+
if (code === 0) {
|
|
42
|
+
resolve();
|
|
43
|
+
} else {
|
|
44
|
+
reject(new Error(`pbcopy exited with code ${code}`));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
pbcopy.on('error', (error) => {
|
|
49
|
+
reject(error);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Copy to clipboard on Windows
|
|
56
|
+
*/
|
|
57
|
+
static async copyWindows(text) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const clip = spawn('clip');
|
|
60
|
+
|
|
61
|
+
clip.stdin.write(text);
|
|
62
|
+
clip.stdin.end();
|
|
63
|
+
|
|
64
|
+
clip.on('close', (code) => {
|
|
65
|
+
if (code === 0) {
|
|
66
|
+
resolve();
|
|
67
|
+
} else {
|
|
68
|
+
reject(new Error(`clip exited with code ${code}`));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
clip.on('error', (error) => {
|
|
73
|
+
reject(error);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Copy to clipboard on Linux
|
|
80
|
+
*/
|
|
81
|
+
static async copyLinux(text) {
|
|
82
|
+
// Try xclip first, then xsel as fallback
|
|
83
|
+
try {
|
|
84
|
+
return await this.copyLinuxXclip(text);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
try {
|
|
87
|
+
return await this.copyLinuxXsel(text);
|
|
88
|
+
} catch (xselError) {
|
|
89
|
+
throw new Error('Neither xclip nor xsel is available. Please install one of them.');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Copy using xclip
|
|
96
|
+
*/
|
|
97
|
+
static async copyLinuxXclip(text) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const xclip = spawn('xclip', ['-selection', 'clipboard']);
|
|
100
|
+
|
|
101
|
+
xclip.stdin.write(text);
|
|
102
|
+
xclip.stdin.end();
|
|
103
|
+
|
|
104
|
+
xclip.on('close', (code) => {
|
|
105
|
+
if (code === 0) {
|
|
106
|
+
resolve();
|
|
107
|
+
} else {
|
|
108
|
+
reject(new Error(`xclip exited with code ${code}`));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
xclip.on('error', (error) => {
|
|
113
|
+
reject(error);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Copy using xsel
|
|
120
|
+
*/
|
|
121
|
+
static async copyLinuxXsel(text) {
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
const xsel = spawn('xsel', ['--clipboard', '--input']);
|
|
124
|
+
|
|
125
|
+
xsel.stdin.write(text);
|
|
126
|
+
xsel.stdin.end();
|
|
127
|
+
|
|
128
|
+
xsel.on('close', (code) => {
|
|
129
|
+
if (code === 0) {
|
|
130
|
+
resolve();
|
|
131
|
+
} else {
|
|
132
|
+
reject(new Error(`xsel exited with code ${code}`));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
xsel.on('error', (error) => {
|
|
137
|
+
reject(error);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get clipboard content size info
|
|
144
|
+
*/
|
|
145
|
+
static getContentInfo(text) {
|
|
146
|
+
const lines = text.split('\n').length;
|
|
147
|
+
const chars = text.length;
|
|
148
|
+
const bytes = Buffer.byteLength(text, 'utf8');
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
lines,
|
|
152
|
+
characters: chars,
|
|
153
|
+
bytes,
|
|
154
|
+
size: this.formatBytes(bytes)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Format bytes to human readable
|
|
160
|
+
*/
|
|
161
|
+
static formatBytes(bytes) {
|
|
162
|
+
if (bytes === 0) return '0 Bytes';
|
|
163
|
+
const k = 1024;
|
|
164
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
165
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
166
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = ClipboardManager;
|
|
Binary file
|
package/vg-coder-cli-1.0.2.tgz
DELETED
|
Binary file
|
package/vg-coder-cli-1.0.3.tgz
DELETED
|
Binary file
|
package/vg-coder-cli-1.0.4.tgz
DELETED
|
Binary file
|
package/vg-coder-cli-1.0.5.tgz
DELETED
|
Binary file
|