gent-cli 10.0.0 → 11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gent-cli",
3
- "version": "10.0.0",
3
+ "version": "11.0.0",
4
4
  "description": "A modern, Git-like version control CLI with cloud sync, AI-powered superpowers (ask/review/docs/changelog), and zero-friction setup (gent setup/doctor/config).",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -8,8 +8,8 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node src/index.js",
11
- "test": "node --check src/index.js && node --test tests/diff.test.js tests/merge.test.js tests/hash.test.js tests/merge-base.test.js && node tests/offline-e2e.js",
12
- "test:unit": "node --test tests/diff.test.js tests/merge.test.js tests/hash.test.js tests/merge-base.test.js",
11
+ "test": "node --check src/index.js && node --test tests/diff.test.js tests/merge.test.js tests/hash.test.js tests/merge-base.test.js tests/file-system.test.js && node tests/offline-e2e.js",
12
+ "test:unit": "node --test tests/diff.test.js tests/merge.test.js tests/hash.test.js tests/merge-base.test.js tests/file-system.test.js",
13
13
  "test:e2e": "node tests/offline-e2e.js",
14
14
  "test:remote:e2e": "node tests/remote-e2e.js",
15
15
  "demo": "bash demo.sh",
@@ -92,6 +92,8 @@ module.exports = {
92
92
  '.gent',
93
93
  'node_modules',
94
94
  '.git',
95
+ '.gitignore',
96
+ '.gentignore',
95
97
  '.DS_Store',
96
98
  '*.log',
97
99
  '.env',
@@ -7,6 +7,8 @@ const fs = require('fs').promises;
7
7
  const path = require('path');
8
8
  const { GENT_DIR, DEFAULT_IGNORE_PATTERNS, IGNORE_FILE } = require('./constants');
9
9
 
10
+ const GIT_IGNORE_FILE = '.gitignore';
11
+
10
12
  /**
11
13
  * Check if a path exists
12
14
  * @param {String} path - Path to check
@@ -115,21 +117,35 @@ function shouldIgnore(filePath, patterns) {
115
117
  const normalizedPath = filePath.replace(/\\/g, '/');
116
118
 
117
119
  for (const pattern of patterns) {
120
+ const normalizedPattern = normalizeIgnorePattern(pattern);
121
+ if (!normalizedPattern) {
122
+ continue;
123
+ }
124
+
118
125
  // Exact match
119
- if (normalizedPath === pattern || normalizedPath.startsWith(pattern + '/')) {
126
+ if (normalizedPath === normalizedPattern || normalizedPath.startsWith(normalizedPattern + '/')) {
120
127
  return true;
121
128
  }
122
129
 
130
+ // Basename match
131
+ if (!normalizedPattern.includes('/') && !normalizedPattern.includes('*')) {
132
+ const parts = normalizedPath.split('/');
133
+ if (parts.includes(normalizedPattern)) {
134
+ return true;
135
+ }
136
+ }
137
+
123
138
  // Wildcard match
124
- if (pattern.includes('*')) {
125
- const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
126
- if (regex.test(normalizedPath)) {
139
+ if (normalizedPattern.includes('*')) {
140
+ const wildcardRegex = normalizedPattern.split('*').map(escapeRegex).join('.*');
141
+ const regex = new RegExp('^' + wildcardRegex + '$');
142
+ if (regex.test(normalizedPath) || regex.test(path.posix.basename(normalizedPath))) {
127
143
  return true;
128
144
  }
129
145
  }
130
146
 
131
147
  // Extension match
132
- if (pattern.startsWith('*.') && normalizedPath.endsWith(pattern.substring(1))) {
148
+ if (normalizedPattern.startsWith('*.') && normalizedPath.endsWith(normalizedPattern.substring(1))) {
133
149
  return true;
134
150
  }
135
151
  }
@@ -137,6 +153,56 @@ function shouldIgnore(filePath, patterns) {
137
153
  return false;
138
154
  }
139
155
 
156
+ function normalizeIgnorePattern(pattern) {
157
+ return pattern
158
+ .replace(/\\/g, '/')
159
+ .replace(/^\.\//, '')
160
+ .replace(/^\/+/, '')
161
+ .replace(/\/+$/, '');
162
+ }
163
+
164
+ function escapeRegex(pattern) {
165
+ return pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
166
+ }
167
+
168
+ function parseIgnoreContent(content, basePath = '') {
169
+ return content.split('\n')
170
+ .map(line => line.trim())
171
+ .filter(line => line && !line.startsWith('#') && !line.startsWith('!'))
172
+ .map(line => normalizeIgnorePattern(path.posix.join(basePath, line)))
173
+ .filter(Boolean);
174
+ }
175
+
176
+ async function collectGitIgnorePatterns(rootDir, patterns) {
177
+ async function scan(currentDir) {
178
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
179
+ const relativeDir = path.relative(rootDir, currentDir).replace(/\\/g, '/');
180
+ const gitIgnore = entries.find(entry => entry.isFile() && entry.name === GIT_IGNORE_FILE);
181
+
182
+ if (gitIgnore) {
183
+ const ignorePath = path.join(currentDir, GIT_IGNORE_FILE);
184
+ const content = await fs.readFile(ignorePath, 'utf-8');
185
+ patterns.push(...parseIgnoreContent(content, relativeDir));
186
+ }
187
+
188
+ for (const entry of entries) {
189
+ if (!entry.isDirectory()) {
190
+ continue;
191
+ }
192
+
193
+ const fullPath = path.join(currentDir, entry.name);
194
+ const relativePath = path.relative(rootDir, fullPath);
195
+ if (shouldIgnore(relativePath, patterns)) {
196
+ continue;
197
+ }
198
+
199
+ await scan(fullPath);
200
+ }
201
+ }
202
+
203
+ await scan(rootDir);
204
+ }
205
+
140
206
  /**
141
207
  * Get ignore patterns from .gentignore file
142
208
  * @param {String} dir - Directory to check
@@ -148,13 +214,11 @@ async function getIgnorePatterns(dir) {
148
214
 
149
215
  if (await pathExists(ignorePath)) {
150
216
  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);
217
+ patterns.push(...parseIgnoreContent(content));
156
218
  }
157
219
 
220
+ await collectGitIgnorePatterns(dir, patterns);
221
+
158
222
  return patterns;
159
223
  }
160
224