@youtyan/code-viewer 0.1.14 → 0.1.16

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.
@@ -18,10 +18,39 @@ export type GitTreeEntry = {
18
18
  path: string;
19
19
  type: 'tree' | 'blob' | 'commit';
20
20
  children_omitted?: true;
21
- children_omitted_reason?: 'ignored' | 'internal';
21
+ children_omitted_reason?: 'heavy' | 'internal' | 'truncated';
22
22
  };
23
23
 
24
24
  const WORKTREE_RECURSIVE_DEPTH_LIMIT = 32;
25
+ export const WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000;
26
+ export const DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
27
+ 'node_modules',
28
+ '.venv',
29
+ 'venv',
30
+ '.next',
31
+ '.nuxt',
32
+ '.svelte-kit',
33
+ '.astro',
34
+ '.vercel',
35
+ 'dist',
36
+ 'build',
37
+ 'out',
38
+ 'target',
39
+ '.gradle',
40
+ '__pycache__',
41
+ '.pytest_cache',
42
+ '.tox',
43
+ '.terraform',
44
+ '.idea',
45
+ '.vscode',
46
+ 'vendor',
47
+ '.cache',
48
+ 'coverage',
49
+ 'DerivedData',
50
+ 'Pods',
51
+ 'bin',
52
+ 'obj',
53
+ ];
25
54
 
26
55
  function run(args: string[], cwd: string): { code: number; stdout: string; stderr: string } {
27
56
  const proc = Bun.spawnSync(args, { cwd, stdout: 'pipe', stderr: 'pipe' });
@@ -59,11 +88,34 @@ export function showBytes(ref: string, path: string, cwd: string): { code: numbe
59
88
  return runBytes(['git', 'show', `${ref}:${path}`], cwd);
60
89
  }
61
90
 
91
+ export function catFileBlobStream(oid: string, cwd: string): { stream: ReadableStream<Uint8Array>; exited: Promise<number>; kill(signal?: string): void } {
92
+ const proc = Bun.spawn(['git', 'cat-file', 'blob', oid], { cwd, stdout: 'pipe', stderr: 'ignore', stdin: 'ignore' });
93
+ return {
94
+ stream: proc.stdout as ReadableStream<Uint8Array>,
95
+ exited: proc.exited,
96
+ kill: (signal?: string) => proc.kill(signal),
97
+ };
98
+ }
99
+
62
100
  export function objectSize(ref: string, path: string, cwd: string): { code: number; size: number; stderr: string } {
63
101
  const res = run(['git', 'cat-file', '-s', `${ref}:${path}`], cwd);
64
102
  return { code: res.code, size: Number(res.stdout.trim()) || 0, stderr: res.stderr };
65
103
  }
66
104
 
105
+ export function objectByteSize(oid: string, cwd: string): { code: number; size: number; stderr: string } {
106
+ const res = run(['git', 'cat-file', '-s', oid], cwd);
107
+ return { code: res.code, size: Number(res.stdout.trim()) || 0, stderr: res.stderr };
108
+ }
109
+
110
+ export function objectId(ref: string, path: string, cwd: string): { code: number; oid: string; stderr: string } {
111
+ const res = run(['git', 'rev-parse', '--verify', `${ref}:${path}`], cwd);
112
+ const oid = res.stdout.trim();
113
+ if (res.code !== 0 || !oid) return { code: res.code || 1, oid: '', stderr: res.stderr };
114
+ const type = run(['git', 'cat-file', '-t', oid], cwd);
115
+ if (type.code !== 0 || type.stdout.trim() !== 'blob') return { code: 1, oid: '', stderr: type.stderr };
116
+ return { code: 0, oid, stderr: '' };
117
+ }
118
+
67
119
  export function verifyTreeRef(ref: string, cwd: string): boolean {
68
120
  if (!ref || ref === 'worktree') return false;
69
121
  if (ref.startsWith('-')) return false;
@@ -159,55 +211,63 @@ function sortTreeEntries(entries: GitTreeEntry[]): GitTreeEntry[] {
159
211
  });
160
212
  }
161
213
 
162
- function ignoredWorktreeDirectories(cwd: string, path: string): Set<string> {
163
- const base = normalizeTreePath(path);
164
- const args = ['git', '-c', 'core.quotepath=false', 'ls-files', '--others', '--ignored', '--exclude-standard', '--directory', '-z'];
165
- if (base) args.push('--', `${base}/`);
166
- const proc = Bun.spawnSync(args, {
167
- cwd,
168
- stdout: 'pipe',
169
- stderr: 'ignore',
170
- });
171
- if (proc.exitCode !== 0) return new Set();
172
- return new Set(new TextDecoder().decode(proc.stdout)
173
- .split('\0')
174
- .filter(entry => entry.endsWith('/'))
175
- .map(entry => entry.replace(/\/+$/g, ''))
176
- .filter(entry => entry && entry !== base));
214
+ function omittedWorktreeDirectoryReason(name: string, omitDirNames: Set<string>): GitTreeEntry['children_omitted_reason'] | undefined {
215
+ if (name === '.git') return 'internal';
216
+ return omitDirNames.has(name) ? 'heavy' : undefined;
177
217
  }
178
218
 
179
- function worktreeEntryFromDirent(base: string, dir: string, name: string, isDirectory: boolean, ignoredDirectories: Set<string>): GitTreeEntry {
219
+ function worktreeEntryFromDirent(base: string, dir: string, name: string, isDirectory: boolean, omitDirNames: Set<string>): GitTreeEntry {
180
220
  const entryPath = base ? `${base}/${name}` : name;
181
221
  const type = isDirectory
182
222
  ? hasDotGitEntry(join(dir, name)) ? 'commit' as const : 'tree' as const
183
223
  : 'blob' as const;
184
- return type === 'tree' && (name === '.git' || ignoredDirectories.has(entryPath))
224
+ const omittedReason = type === 'tree' ? omittedWorktreeDirectoryReason(name, omitDirNames) : undefined;
225
+ return omittedReason
185
226
  ? {
186
227
  name,
187
228
  path: entryPath,
188
229
  type,
189
230
  children_omitted: true,
190
- children_omitted_reason: name === '.git' ? 'internal' : 'ignored',
231
+ children_omitted_reason: omittedReason,
191
232
  }
192
233
  : { name, path: entryPath, type };
193
234
  }
194
235
 
195
- function worktreeFilesystemEntries(cwd: string, path: string, recursive: boolean): GitTreeEntry[] {
236
+ function worktreeFilesystemEntries(cwd: string, path: string, recursive: boolean, omitDirNames: string[] = DEFAULT_WORKTREE_OMIT_DIR_NAMES): GitTreeEntry[] {
196
237
  const base = normalizeTreePath(path);
197
238
  const root = join(cwd, base);
198
- const ignoredDirectories = ignoredWorktreeDirectories(cwd, base);
239
+ const omitDirNameSet = new Set(omitDirNames);
199
240
  let directEntries: GitTreeEntry[];
200
241
  try {
201
242
  const dirents = readdirSync(root, { withFileTypes: true });
202
243
  directEntries = sortTreeEntries(dirents
203
- .map(entry => worktreeEntryFromDirent(base, root, entry.name, entry.isDirectory(), ignoredDirectories)));
244
+ .map(entry => worktreeEntryFromDirent(base, root, entry.name, entry.isDirectory(), omitDirNameSet)));
204
245
  } catch {
205
246
  return [];
206
247
  }
207
248
  if (!recursive) return directEntries;
208
249
 
209
250
  const fileEntries: GitTreeEntry[] = [];
251
+ let truncated = false;
252
+ const pushRecursiveEntry = (entry: GitTreeEntry): boolean => {
253
+ if (fileEntries.length >= WORKTREE_RECURSIVE_ENTRY_LIMIT) {
254
+ if (!truncated) {
255
+ fileEntries.push({
256
+ name: 'more...',
257
+ path: '__code_viewer_truncated__',
258
+ type: 'tree',
259
+ children_omitted: true,
260
+ children_omitted_reason: 'truncated',
261
+ });
262
+ truncated = true;
263
+ }
264
+ return false;
265
+ }
266
+ fileEntries.push(entry);
267
+ return true;
268
+ };
210
269
  const walk = (dir: string, prefix: string, depth: number) => {
270
+ if (truncated) return;
211
271
  if (depth >= WORKTREE_RECURSIVE_DEPTH_LIMIT) return;
212
272
  let entries;
213
273
  try {
@@ -219,20 +279,21 @@ function worktreeFilesystemEntries(cwd: string, path: string, recursive: boolean
219
279
  const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
220
280
  const full = join(dir, entry.name);
221
281
  if (entry.isDirectory()) {
222
- if (entry.name === '.git' || ignoredDirectories.has(entryPath)) {
223
- fileEntries.push({
282
+ const omittedReason = omittedWorktreeDirectoryReason(entry.name, omitDirNameSet);
283
+ if (omittedReason) {
284
+ if (!pushRecursiveEntry({
224
285
  name: entry.name,
225
286
  path: entryPath,
226
287
  type: 'tree',
227
288
  children_omitted: true,
228
- children_omitted_reason: entry.name === '.git' ? 'internal' : 'ignored',
229
- });
289
+ children_omitted_reason: omittedReason,
290
+ })) return;
230
291
  continue;
231
292
  }
232
293
  if (hasDotGitEntry(full)) continue;
233
294
  walk(full, entryPath, depth + 1);
234
295
  } else if (entry.isFile() || entry.isSymbolicLink()) {
235
- fileEntries.push({ name: entry.name, path: entryPath, type: 'blob' });
296
+ if (!pushRecursiveEntry({ name: entry.name, path: entryPath, type: 'blob' })) return;
236
297
  }
237
298
  }
238
299
  };
@@ -297,11 +358,11 @@ export function listTree(
297
358
  ref: string,
298
359
  path: string,
299
360
  cwd: string,
300
- options: { recursive?: boolean } = {},
361
+ options: { recursive?: boolean; omitDirNames?: string[] } = {},
301
362
  ): { code: number; entries: GitTreeEntry[]; stderr: string } {
302
363
  const base = normalizeTreePath(path);
303
364
  if (ref === 'worktree') {
304
- return { code: 0, entries: worktreeFilesystemEntries(cwd, base, !!options.recursive), stderr: '' };
365
+ return { code: 0, entries: worktreeFilesystemEntries(cwd, base, !!options.recursive, options.omitDirNames), stderr: '' };
305
366
  }
306
367
 
307
368
  const direct = gitTreeEntries(ref, base, cwd, false);