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.
@@ -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
+ };