gent-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/README.md +316 -0
- package/commands-reference.js +116 -0
- package/package.json +43 -0
- package/src/commands/add.js +88 -0
- package/src/commands/branch.js +115 -0
- package/src/commands/checkout.js +74 -0
- package/src/commands/commit.js +108 -0
- package/src/commands/init.js +140 -0
- package/src/commands/log.js +92 -0
- package/src/commands/status.js +167 -0
- package/src/index.js +104 -0
- package/src/utils/constants.js +41 -0
- package/src/utils/fileSystem.js +188 -0
- package/src/utils/helpers.js +130 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants - Application-wide constants
|
|
3
|
+
* Defines paths, patterns, and configuration values
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
// Directory and file names
|
|
8
|
+
GENT_DIR: '.gent',
|
|
9
|
+
CONFIG_FILE: 'config.json',
|
|
10
|
+
STAGING_FILE: 'staging.json',
|
|
11
|
+
COMMITS_FILE: 'commits.json',
|
|
12
|
+
HEAD_FILE: 'HEAD',
|
|
13
|
+
|
|
14
|
+
// Default ignore patterns
|
|
15
|
+
DEFAULT_IGNORE_PATTERNS: [
|
|
16
|
+
'.gent',
|
|
17
|
+
'node_modules',
|
|
18
|
+
'.git',
|
|
19
|
+
'.DS_Store',
|
|
20
|
+
'*.log',
|
|
21
|
+
'.env',
|
|
22
|
+
'.env.local',
|
|
23
|
+
'dist',
|
|
24
|
+
'build',
|
|
25
|
+
'coverage',
|
|
26
|
+
'.vscode',
|
|
27
|
+
'.idea'
|
|
28
|
+
],
|
|
29
|
+
|
|
30
|
+
// Regex patterns
|
|
31
|
+
IGNORE_FILE: '.gentignore',
|
|
32
|
+
|
|
33
|
+
// Colors (for consistency)
|
|
34
|
+
COLORS: {
|
|
35
|
+
SUCCESS: 'green',
|
|
36
|
+
ERROR: 'red',
|
|
37
|
+
WARNING: 'yellow',
|
|
38
|
+
INFO: 'cyan',
|
|
39
|
+
MUTED: 'gray'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File System Utilities
|
|
3
|
+
* Helper functions for file system operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs').promises;
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { GENT_DIR, DEFAULT_IGNORE_PATTERNS, IGNORE_FILE } = require('./constants');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check if a path exists
|
|
12
|
+
* @param {String} path - Path to check
|
|
13
|
+
* @returns {Promise<Boolean>}
|
|
14
|
+
*/
|
|
15
|
+
async function pathExists(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
await fs.access(filePath);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Ensure directory exists, create if not
|
|
26
|
+
* @param {String} dir - Directory path
|
|
27
|
+
*/
|
|
28
|
+
async function ensureDir(dir) {
|
|
29
|
+
try {
|
|
30
|
+
await fs.mkdir(dir, { recursive: true });
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error.code !== 'EEXIST') {
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read JSON file
|
|
40
|
+
* @param {String} filePath - Path to JSON file
|
|
41
|
+
* @returns {Promise<Object>}
|
|
42
|
+
*/
|
|
43
|
+
async function readJSON(filePath) {
|
|
44
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
45
|
+
return JSON.parse(content);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Write JSON file
|
|
50
|
+
* @param {String} filePath - Path to JSON file
|
|
51
|
+
* @param {Object} data - Data to write
|
|
52
|
+
*/
|
|
53
|
+
async function writeJSON(filePath, data) {
|
|
54
|
+
const content = JSON.stringify(data, null, 2);
|
|
55
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get .gent directory path
|
|
60
|
+
* @returns {Promise<String>}
|
|
61
|
+
*/
|
|
62
|
+
async function getGentPath() {
|
|
63
|
+
const cwd = process.cwd();
|
|
64
|
+
const gentPath = path.join(cwd, GENT_DIR);
|
|
65
|
+
|
|
66
|
+
if (!await pathExists(gentPath)) {
|
|
67
|
+
const error = new Error('Not a gent repository (or any of the parent directories): .gent not found');
|
|
68
|
+
error.code = 'ENOENT';
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return gentPath;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get all files in directory recursively
|
|
77
|
+
* @param {String} dir - Directory to scan
|
|
78
|
+
* @param {Array} ignorePatterns - Patterns to ignore
|
|
79
|
+
* @returns {Promise<Array>}
|
|
80
|
+
*/
|
|
81
|
+
async function getAllFiles(dir, ignorePatterns = []) {
|
|
82
|
+
const files = [];
|
|
83
|
+
|
|
84
|
+
async function scan(currentDir) {
|
|
85
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
86
|
+
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
89
|
+
const relativePath = path.relative(dir, fullPath);
|
|
90
|
+
|
|
91
|
+
// Check if should be ignored
|
|
92
|
+
if (shouldIgnore(relativePath, ignorePatterns)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (entry.isDirectory()) {
|
|
97
|
+
await scan(fullPath);
|
|
98
|
+
} else {
|
|
99
|
+
files.push(fullPath);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await scan(dir);
|
|
105
|
+
return files;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if path should be ignored
|
|
110
|
+
* @param {String} filePath - File path to check
|
|
111
|
+
* @param {Array} patterns - Ignore patterns
|
|
112
|
+
* @returns {Boolean}
|
|
113
|
+
*/
|
|
114
|
+
function shouldIgnore(filePath, patterns) {
|
|
115
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
116
|
+
|
|
117
|
+
for (const pattern of patterns) {
|
|
118
|
+
// Exact match
|
|
119
|
+
if (normalizedPath === pattern || normalizedPath.startsWith(pattern + '/')) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Wildcard match
|
|
124
|
+
if (pattern.includes('*')) {
|
|
125
|
+
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
|
|
126
|
+
if (regex.test(normalizedPath)) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Extension match
|
|
132
|
+
if (pattern.startsWith('*.') && normalizedPath.endsWith(pattern.substring(1))) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get ignore patterns from .gentignore file
|
|
142
|
+
* @param {String} dir - Directory to check
|
|
143
|
+
* @returns {Promise<Array>}
|
|
144
|
+
*/
|
|
145
|
+
async function getIgnorePatterns(dir) {
|
|
146
|
+
const patterns = [...DEFAULT_IGNORE_PATTERNS];
|
|
147
|
+
const ignorePath = path.join(dir, IGNORE_FILE);
|
|
148
|
+
|
|
149
|
+
if (await pathExists(ignorePath)) {
|
|
150
|
+
const content = await fs.readFile(ignorePath, 'utf-8');
|
|
151
|
+
const lines = content.split('\n')
|
|
152
|
+
.map(line => line.trim())
|
|
153
|
+
.filter(line => line && !line.startsWith('#'));
|
|
154
|
+
|
|
155
|
+
patterns.push(...lines);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return patterns;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get tracked files from a commit
|
|
163
|
+
* @param {String} gentPath - Path to .gent directory
|
|
164
|
+
* @param {String} commitHash - Commit hash
|
|
165
|
+
* @returns {Promise<Array>}
|
|
166
|
+
*/
|
|
167
|
+
async function getTrackedFiles(gentPath, commitHash) {
|
|
168
|
+
if (!commitHash) {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const repository = await readJSON(path.join(gentPath, 'commits.json'));
|
|
173
|
+
const commit = repository.commits.find(c => c.hash === commitHash);
|
|
174
|
+
|
|
175
|
+
return commit ? commit.files : [];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = {
|
|
179
|
+
pathExists,
|
|
180
|
+
ensureDir,
|
|
181
|
+
readJSON,
|
|
182
|
+
writeJSON,
|
|
183
|
+
getGentPath,
|
|
184
|
+
getAllFiles,
|
|
185
|
+
shouldIgnore,
|
|
186
|
+
getIgnorePatterns,
|
|
187
|
+
getTrackedFiles
|
|
188
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper Utilities
|
|
3
|
+
* General helper functions for the application
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const fs = require('fs').promises;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate a unique commit hash
|
|
11
|
+
* @returns {String} - SHA-256 hash
|
|
12
|
+
*/
|
|
13
|
+
function generateCommitHash() {
|
|
14
|
+
const timestamp = Date.now();
|
|
15
|
+
const random = Math.random().toString(36);
|
|
16
|
+
const data = `${timestamp}-${random}`;
|
|
17
|
+
|
|
18
|
+
return crypto
|
|
19
|
+
.createHash('sha256')
|
|
20
|
+
.update(data)
|
|
21
|
+
.digest('hex');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate hash for a file
|
|
26
|
+
* @param {String} filePath - Path to file
|
|
27
|
+
* @returns {Promise<String>} - SHA-256 hash of file content
|
|
28
|
+
*/
|
|
29
|
+
async function getFileHash(filePath) {
|
|
30
|
+
try {
|
|
31
|
+
const content = await fs.readFile(filePath);
|
|
32
|
+
return crypto
|
|
33
|
+
.createHash('sha256')
|
|
34
|
+
.update(content)
|
|
35
|
+
.digest('hex');
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new Error(`Failed to hash file ${filePath}: ${error.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Format bytes to human-readable size
|
|
43
|
+
* @param {Number} bytes - Size in bytes
|
|
44
|
+
* @returns {String}
|
|
45
|
+
*/
|
|
46
|
+
function formatBytes(bytes) {
|
|
47
|
+
if (bytes === 0) return '0 Bytes';
|
|
48
|
+
|
|
49
|
+
const k = 1024;
|
|
50
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
51
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
52
|
+
|
|
53
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Truncate string to specified length
|
|
58
|
+
* @param {String} str - String to truncate
|
|
59
|
+
* @param {Number} length - Maximum length
|
|
60
|
+
* @returns {String}
|
|
61
|
+
*/
|
|
62
|
+
function truncate(str, length = 50) {
|
|
63
|
+
if (str.length <= length) return str;
|
|
64
|
+
return str.substring(0, length - 3) + '...';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get short commit hash (7 characters)
|
|
69
|
+
* @param {String} hash - Full commit hash
|
|
70
|
+
* @returns {String}
|
|
71
|
+
*/
|
|
72
|
+
function shortHash(hash) {
|
|
73
|
+
return hash ? hash.substring(0, 7) : '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate email format
|
|
78
|
+
* @param {String} email - Email to validate
|
|
79
|
+
* @returns {Boolean}
|
|
80
|
+
*/
|
|
81
|
+
function isValidEmail(email) {
|
|
82
|
+
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
83
|
+
return regex.test(email);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Parse command line arguments
|
|
88
|
+
* @param {Array} args - Arguments array
|
|
89
|
+
* @returns {Object}
|
|
90
|
+
*/
|
|
91
|
+
function parseArgs(args) {
|
|
92
|
+
const parsed = {
|
|
93
|
+
flags: {},
|
|
94
|
+
args: []
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < args.length; i++) {
|
|
98
|
+
const arg = args[i];
|
|
99
|
+
|
|
100
|
+
if (arg.startsWith('--')) {
|
|
101
|
+
const key = arg.substring(2);
|
|
102
|
+
const nextArg = args[i + 1];
|
|
103
|
+
|
|
104
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
105
|
+
parsed.flags[key] = nextArg;
|
|
106
|
+
i++;
|
|
107
|
+
} else {
|
|
108
|
+
parsed.flags[key] = true;
|
|
109
|
+
}
|
|
110
|
+
} else if (arg.startsWith('-')) {
|
|
111
|
+
const key = arg.substring(1);
|
|
112
|
+
parsed.flags[key] = true;
|
|
113
|
+
} else {
|
|
114
|
+
parsed.args.push(arg);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return parsed;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
generateCommitHash,
|
|
123
|
+
getFileHash,
|
|
124
|
+
formatBytes,
|
|
125
|
+
truncate,
|
|
126
|
+
shortHash,
|
|
127
|
+
isValidEmail,
|
|
128
|
+
parseArgs,
|
|
129
|
+
getAllFiles: require('./fileSystem').getAllFiles
|
|
130
|
+
};
|