repo-cloak-cli 1.0.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/.github/workflows/publish.yml +36 -0
- package/.github/workflows/version-bump.yml +49 -0
- package/LICENSE +21 -0
- package/README.md +118 -0
- package/bin/repo-cloak.js +10 -0
- package/package.json +48 -0
- package/src/cli.js +84 -0
- package/src/commands/pull.js +128 -0
- package/src/commands/push.js +145 -0
- package/src/core/anonymizer.js +128 -0
- package/src/core/copier.js +96 -0
- package/src/core/mapper.js +120 -0
- package/src/core/scanner.js +137 -0
- package/src/index.js +8 -0
- package/src/ui/banner.js +70 -0
- package/src/ui/fileSelector.js +182 -0
- package/src/ui/prompts.js +165 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymizer
|
|
3
|
+
* Handles keyword replacement for anonymizing content
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a replacement function for a list of replacements
|
|
8
|
+
* @param {Array<{original: string, replacement: string}>} replacements
|
|
9
|
+
* @param {Object} options
|
|
10
|
+
* @returns {Function} Transform function
|
|
11
|
+
*/
|
|
12
|
+
export function createAnonymizer(replacements, options = {}) {
|
|
13
|
+
const { caseSensitive = false } = options;
|
|
14
|
+
|
|
15
|
+
if (!replacements || replacements.length === 0) {
|
|
16
|
+
return (content) => content;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (content) => {
|
|
20
|
+
let result = content;
|
|
21
|
+
|
|
22
|
+
for (const { original, replacement } of replacements) {
|
|
23
|
+
if (caseSensitive) {
|
|
24
|
+
// Case-sensitive replacement
|
|
25
|
+
result = result.split(original).join(replacement);
|
|
26
|
+
} else {
|
|
27
|
+
// Case-insensitive replacement using regex
|
|
28
|
+
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
29
|
+
result = result.replace(regex, (match) => {
|
|
30
|
+
// Preserve case pattern
|
|
31
|
+
return matchCase(match, replacement);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create reverse anonymizer (for push operation)
|
|
42
|
+
* @param {Array<{original: string, replacement: string}>} replacements
|
|
43
|
+
* @returns {Function} Transform function
|
|
44
|
+
*/
|
|
45
|
+
export function createDeanonymizer(replacements, options = {}) {
|
|
46
|
+
const { caseSensitive = false } = options;
|
|
47
|
+
|
|
48
|
+
if (!replacements || replacements.length === 0) {
|
|
49
|
+
return (content) => content;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Reverse the replacements
|
|
53
|
+
const reversed = replacements.map(r => ({
|
|
54
|
+
original: r.replacement,
|
|
55
|
+
replacement: r.original
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
return createAnonymizer(reversed, options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Escape special regex characters
|
|
63
|
+
*/
|
|
64
|
+
function escapeRegex(string) {
|
|
65
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Match the case pattern of the original to the replacement
|
|
70
|
+
*/
|
|
71
|
+
function matchCase(original, replacement) {
|
|
72
|
+
// If original is all uppercase, make replacement uppercase
|
|
73
|
+
if (original === original.toUpperCase()) {
|
|
74
|
+
return replacement.toUpperCase();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If original is all lowercase, make replacement lowercase
|
|
78
|
+
if (original === original.toLowerCase()) {
|
|
79
|
+
return replacement.toLowerCase();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// If original is Title Case, make replacement Title Case
|
|
83
|
+
if (original[0] === original[0].toUpperCase()) {
|
|
84
|
+
return replacement.charAt(0).toUpperCase() + replacement.slice(1).toLowerCase();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Default: return replacement as-is
|
|
88
|
+
return replacement;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Apply replacements to a file path (for renaming files/folders)
|
|
93
|
+
*/
|
|
94
|
+
export function anonymizePath(filePath, replacements) {
|
|
95
|
+
if (!replacements || replacements.length === 0) {
|
|
96
|
+
return filePath;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let result = filePath;
|
|
100
|
+
|
|
101
|
+
for (const { original, replacement } of replacements) {
|
|
102
|
+
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
103
|
+
result = result.replace(regex, replacement);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Count replacements in content
|
|
111
|
+
*/
|
|
112
|
+
export function countReplacements(content, replacements) {
|
|
113
|
+
if (!replacements || replacements.length === 0) {
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let count = 0;
|
|
118
|
+
|
|
119
|
+
for (const { original } of replacements) {
|
|
120
|
+
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
121
|
+
const matches = content.match(regex);
|
|
122
|
+
if (matches) {
|
|
123
|
+
count += matches.length;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return count;
|
|
128
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Copier
|
|
3
|
+
* Copies files while preserving directory structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
7
|
+
import { dirname, join, relative } from 'path';
|
|
8
|
+
import { isBinaryFile } from './scanner.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Copy a single file, creating directories as needed
|
|
12
|
+
*/
|
|
13
|
+
export function copyFile(sourcePath, destPath) {
|
|
14
|
+
// Ensure destination directory exists
|
|
15
|
+
const destDir = dirname(destPath);
|
|
16
|
+
mkdirSync(destDir, { recursive: true });
|
|
17
|
+
|
|
18
|
+
// Copy the file
|
|
19
|
+
copyFileSync(sourcePath, destPath);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Copy a file with content transformation
|
|
24
|
+
*/
|
|
25
|
+
export function copyFileWithTransform(sourcePath, destPath, transformFn) {
|
|
26
|
+
// Ensure destination directory exists
|
|
27
|
+
const destDir = dirname(destPath);
|
|
28
|
+
mkdirSync(destDir, { recursive: true });
|
|
29
|
+
|
|
30
|
+
// Check if binary - copy as-is
|
|
31
|
+
if (isBinaryFile(sourcePath)) {
|
|
32
|
+
copyFileSync(sourcePath, destPath);
|
|
33
|
+
return { transformed: false };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Read, transform, write
|
|
37
|
+
try {
|
|
38
|
+
let content = readFileSync(sourcePath, 'utf-8');
|
|
39
|
+
const originalContent = content;
|
|
40
|
+
content = transformFn(content);
|
|
41
|
+
writeFileSync(destPath, content, 'utf-8');
|
|
42
|
+
return {
|
|
43
|
+
transformed: content !== originalContent,
|
|
44
|
+
originalLength: originalContent.length,
|
|
45
|
+
newLength: content.length
|
|
46
|
+
};
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// If reading as text fails, copy as binary
|
|
49
|
+
copyFileSync(sourcePath, destPath);
|
|
50
|
+
return { transformed: false, error: error.message };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Copy multiple files with progress callback
|
|
56
|
+
*/
|
|
57
|
+
export async function copyFiles(files, sourceBase, destBase, transformFn, onProgress) {
|
|
58
|
+
const results = {
|
|
59
|
+
total: files.length,
|
|
60
|
+
copied: 0,
|
|
61
|
+
transformed: 0,
|
|
62
|
+
errors: []
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < files.length; i++) {
|
|
66
|
+
const file = files[i];
|
|
67
|
+
const relativePath = typeof file === 'string'
|
|
68
|
+
? relative(sourceBase, file)
|
|
69
|
+
: file.relativePath;
|
|
70
|
+
const sourcePath = typeof file === 'string' ? file : file.absolutePath;
|
|
71
|
+
const destPath = join(destBase, relativePath);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
if (transformFn) {
|
|
75
|
+
const result = copyFileWithTransform(sourcePath, destPath, transformFn);
|
|
76
|
+
if (result.transformed) {
|
|
77
|
+
results.transformed++;
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
copyFile(sourcePath, destPath);
|
|
81
|
+
}
|
|
82
|
+
results.copied++;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
results.errors.push({
|
|
85
|
+
file: relativePath,
|
|
86
|
+
error: error.message
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (onProgress) {
|
|
91
|
+
onProgress(i + 1, files.length, relativePath);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapping File Manager
|
|
3
|
+
* Tracks replacements and file mappings for push/pull operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
const MAP_FILENAME = '.repo-cloak-map.json';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a mapping object
|
|
13
|
+
*/
|
|
14
|
+
export function createMapping(options) {
|
|
15
|
+
const {
|
|
16
|
+
sourceDir,
|
|
17
|
+
destDir,
|
|
18
|
+
replacements,
|
|
19
|
+
files,
|
|
20
|
+
timestamp = new Date().toISOString()
|
|
21
|
+
} = options;
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
version: '1.0.0',
|
|
25
|
+
tool: 'repo-cloak',
|
|
26
|
+
timestamp,
|
|
27
|
+
source: {
|
|
28
|
+
path: sourceDir,
|
|
29
|
+
platform: process.platform
|
|
30
|
+
},
|
|
31
|
+
destination: {
|
|
32
|
+
path: destDir
|
|
33
|
+
},
|
|
34
|
+
replacements: replacements.map(r => ({
|
|
35
|
+
original: r.original,
|
|
36
|
+
replacement: r.replacement
|
|
37
|
+
})),
|
|
38
|
+
files: files.map(f => ({
|
|
39
|
+
original: typeof f === 'string' ? f : f.relativePath,
|
|
40
|
+
cloaked: typeof f === 'string' ? f : f.relativePath
|
|
41
|
+
})),
|
|
42
|
+
stats: {
|
|
43
|
+
totalFiles: files.length,
|
|
44
|
+
replacementsCount: replacements.length
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Save mapping to destination directory
|
|
51
|
+
*/
|
|
52
|
+
export function saveMapping(destDir, mapping) {
|
|
53
|
+
const mapPath = join(destDir, MAP_FILENAME);
|
|
54
|
+
writeFileSync(mapPath, JSON.stringify(mapping, null, 2), 'utf-8');
|
|
55
|
+
return mapPath;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Load mapping from a cloaked directory
|
|
60
|
+
*/
|
|
61
|
+
export function loadMapping(cloakedDir) {
|
|
62
|
+
const mapPath = join(cloakedDir, MAP_FILENAME);
|
|
63
|
+
|
|
64
|
+
if (!existsSync(mapPath)) {
|
|
65
|
+
throw new Error(`No mapping file found in ${cloakedDir}. Is this a repo-cloak backup?`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const content = readFileSync(mapPath, 'utf-8');
|
|
70
|
+
return JSON.parse(content);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new Error(`Failed to read mapping file: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if a directory has a mapping file
|
|
78
|
+
*/
|
|
79
|
+
export function hasMapping(dir) {
|
|
80
|
+
const mapPath = join(dir, MAP_FILENAME);
|
|
81
|
+
return existsSync(mapPath);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get the original source path from mapping
|
|
86
|
+
*/
|
|
87
|
+
export function getOriginalSource(mapping) {
|
|
88
|
+
return mapping.source?.path || null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get replacements from mapping
|
|
93
|
+
*/
|
|
94
|
+
export function getReplacements(mapping) {
|
|
95
|
+
return mapping.replacements || [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get file list from mapping
|
|
100
|
+
*/
|
|
101
|
+
export function getFiles(mapping) {
|
|
102
|
+
return mapping.files || [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Update mapping with additional info
|
|
107
|
+
*/
|
|
108
|
+
export function updateMapping(destDir, updates) {
|
|
109
|
+
const mapPath = join(destDir, MAP_FILENAME);
|
|
110
|
+
|
|
111
|
+
if (!existsSync(mapPath)) {
|
|
112
|
+
throw new Error('No mapping file found');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const mapping = loadMapping(destDir);
|
|
116
|
+
const updated = { ...mapping, ...updates, updatedAt: new Date().toISOString() };
|
|
117
|
+
|
|
118
|
+
writeFileSync(mapPath, JSON.stringify(updated, null, 2), 'utf-8');
|
|
119
|
+
return updated;
|
|
120
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory Scanner
|
|
3
|
+
* Scans and builds file tree structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readdirSync, statSync } from 'fs';
|
|
7
|
+
import { join, relative, sep, extname } from 'path';
|
|
8
|
+
|
|
9
|
+
// Binary file extensions to copy without modification
|
|
10
|
+
const BINARY_EXTENSIONS = new Set([
|
|
11
|
+
'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.webp', '.svg',
|
|
12
|
+
'.mp3', '.mp4', '.wav', '.avi', '.mov', '.mkv', '.webm',
|
|
13
|
+
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
14
|
+
'.zip', '.rar', '.7z', '.tar', '.gz', '.bz2',
|
|
15
|
+
'.exe', '.dll', '.so', '.dylib', '.bin',
|
|
16
|
+
'.ttf', '.otf', '.woff', '.woff2', '.eot',
|
|
17
|
+
'.sqlite', '.db', '.mdb'
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
// Directories to always ignore
|
|
21
|
+
const IGNORE_DIRS = new Set([
|
|
22
|
+
'node_modules',
|
|
23
|
+
'.git',
|
|
24
|
+
'.svn',
|
|
25
|
+
'.hg',
|
|
26
|
+
'.DS_Store',
|
|
27
|
+
'Thumbs.db',
|
|
28
|
+
'.idea',
|
|
29
|
+
'.vscode',
|
|
30
|
+
'__pycache__',
|
|
31
|
+
'.pytest_cache',
|
|
32
|
+
'dist',
|
|
33
|
+
'build',
|
|
34
|
+
'.next',
|
|
35
|
+
'.nuxt',
|
|
36
|
+
'coverage',
|
|
37
|
+
'.nyc_output',
|
|
38
|
+
'.repo-cloak-map.json',
|
|
39
|
+
'.env',
|
|
40
|
+
'.env.local'
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if a file is binary based on extension
|
|
45
|
+
*/
|
|
46
|
+
export function isBinaryFile(filePath) {
|
|
47
|
+
const ext = extname(filePath).toLowerCase();
|
|
48
|
+
return BINARY_EXTENSIONS.has(ext);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a file/folder should be ignored
|
|
53
|
+
*/
|
|
54
|
+
export function shouldIgnore(name) {
|
|
55
|
+
return IGNORE_DIRS.has(name) || name.startsWith('.');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Recursively get all files in a directory
|
|
60
|
+
*/
|
|
61
|
+
export function getAllFiles(dir, basePath = dir, files = []) {
|
|
62
|
+
try {
|
|
63
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
64
|
+
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const fullPath = join(dir, entry.name);
|
|
67
|
+
|
|
68
|
+
if (shouldIgnore(entry.name)) continue;
|
|
69
|
+
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
getAllFiles(fullPath, basePath, files);
|
|
72
|
+
} else {
|
|
73
|
+
files.push({
|
|
74
|
+
absolutePath: fullPath,
|
|
75
|
+
relativePath: relative(basePath, fullPath),
|
|
76
|
+
name: entry.name,
|
|
77
|
+
isBinary: isBinaryFile(fullPath)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// Permission denied or other errors - skip
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return files;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get directory structure for display
|
|
90
|
+
*/
|
|
91
|
+
export function getDirectoryTree(dir, basePath = dir, depth = 0, maxDepth = 5) {
|
|
92
|
+
const tree = [];
|
|
93
|
+
|
|
94
|
+
if (depth > maxDepth) return tree;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
98
|
+
|
|
99
|
+
// Sort: folders first, then files
|
|
100
|
+
entries.sort((a, b) => {
|
|
101
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
102
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
103
|
+
return a.name.localeCompare(b.name);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
if (shouldIgnore(entry.name)) continue;
|
|
108
|
+
|
|
109
|
+
const fullPath = join(dir, entry.name);
|
|
110
|
+
const node = {
|
|
111
|
+
name: entry.name,
|
|
112
|
+
path: fullPath,
|
|
113
|
+
relativePath: relative(basePath, fullPath),
|
|
114
|
+
isDirectory: entry.isDirectory(),
|
|
115
|
+
depth
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
tree.push(node);
|
|
119
|
+
|
|
120
|
+
if (entry.isDirectory()) {
|
|
121
|
+
const children = getDirectoryTree(fullPath, basePath, depth + 1, maxDepth);
|
|
122
|
+
tree.push(...children);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// Skip inaccessible directories
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return tree;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Count files in a directory (recursive)
|
|
134
|
+
*/
|
|
135
|
+
export function countFiles(dir) {
|
|
136
|
+
return getAllFiles(dir).length;
|
|
137
|
+
}
|
package/src/index.js
ADDED
package/src/ui/banner.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fancy ASCII Banner
|
|
3
|
+
* Shows a colorful intro when the CLI starts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import figlet from 'figlet';
|
|
8
|
+
|
|
9
|
+
export async function showBanner() {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
figlet.text('repo-cloak', {
|
|
12
|
+
font: 'Standard',
|
|
13
|
+
horizontalLayout: 'default',
|
|
14
|
+
verticalLayout: 'default'
|
|
15
|
+
}, (err, data) => {
|
|
16
|
+
if (err) {
|
|
17
|
+
console.log(chalk.magentaBright.bold('\n🎭 repo-cloak\n'));
|
|
18
|
+
resolve();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Create gradient effect
|
|
23
|
+
const lines = data.split('\n');
|
|
24
|
+
const colors = [
|
|
25
|
+
chalk.hex('#FF6B6B'), // Coral
|
|
26
|
+
chalk.hex('#FF8E53'), // Orange
|
|
27
|
+
chalk.hex('#FEC89A'), // Peach
|
|
28
|
+
chalk.hex('#A8E6CF'), // Mint
|
|
29
|
+
chalk.hex('#88D8B0'), // Seafoam
|
|
30
|
+
chalk.hex('#7B68EE'), // Medium Slate Blue
|
|
31
|
+
chalk.hex('#9D4EDD'), // Purple
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
console.log('\n');
|
|
35
|
+
lines.forEach((line, index) => {
|
|
36
|
+
const color = colors[index % colors.length];
|
|
37
|
+
console.log(color(line));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Tagline box
|
|
41
|
+
const tagline = '🎭 Selectively extract & anonymize repository files';
|
|
42
|
+
const version = 'v1.0.0';
|
|
43
|
+
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log(chalk.dim('─'.repeat(55)));
|
|
46
|
+
console.log(chalk.white.bold(` ${tagline}`));
|
|
47
|
+
console.log(chalk.dim(` Compatible with Windows, macOS, and Linux | ${version}`));
|
|
48
|
+
console.log(chalk.dim('─'.repeat(55)));
|
|
49
|
+
console.log('');
|
|
50
|
+
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function showSuccess(message) {
|
|
57
|
+
console.log(chalk.green.bold(`\n✅ ${message}\n`));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function showError(message) {
|
|
61
|
+
console.log(chalk.red.bold(`\n❌ ${message}\n`));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function showWarning(message) {
|
|
65
|
+
console.log(chalk.yellow.bold(`\n⚠️ ${message}\n`));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function showInfo(message) {
|
|
69
|
+
console.log(chalk.cyan(`\nℹ️ ${message}\n`));
|
|
70
|
+
}
|