@maxgfr/codeindex 2.13.0 → 2.15.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/src/walk.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { readdirSync, statSync, lstatSync, readFileSync, realpathSync } from "node:fs";
1
+ import { readdirSync, statSync, lstatSync, readFileSync, realpathSync, type Dirent } from "node:fs";
2
2
  import { join, relative, sep, extname } from "node:path";
3
3
  import { parseGitignore, isIgnored, type IgnoreRule } from "./ignore.js";
4
4
 
@@ -113,27 +113,44 @@ export function walk(root: string, opts: WalkOptions = {}): WalkResult {
113
113
  if (seenDirs.has(real)) continue;
114
114
  seenDirs.add(real);
115
115
  if (!contained(real)) continue; // dir symlink escaping the repo
116
- let entries: string[];
116
+ let entries: Dirent[];
117
117
  try {
118
118
  // Sorted so the walk order — and therefore WHICH files survive a
119
- // maxFiles cap — is identical across filesystems and machines.
120
- entries = readdirSync(frame.dir).sort();
119
+ // maxFiles cap — is identical across filesystems and machines. Dirents
120
+ // sort by .name under the same code-unit comparison the bare-string
121
+ // sort used, so the order is byte-identical to the previous readdir.
122
+ entries = readdirSync(frame.dir, { withFileTypes: true }).sort((a, b) =>
123
+ a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
124
+ );
121
125
  } catch {
122
126
  continue;
123
127
  }
124
128
  let rules = frame.rules;
125
- if (useGitignore && entries.includes(".gitignore")) {
129
+ if (useGitignore && entries.some((e) => e.name === ".gitignore")) {
126
130
  const parsed = parseGitignore(readText(join(frame.dir, ".gitignore")), frame.rel);
127
131
  if (parsed.length) rules = [...rules, ...parsed];
128
132
  }
129
- for (const name of entries) {
133
+ for (const entry of entries) {
134
+ const name = entry.name;
130
135
  const abs = join(frame.dir, name);
131
136
  const rel = frame.rel ? `${frame.rel}/${name}` : name;
137
+ // The dirent type IS the lstat type (Node lstats internally when the
138
+ // filesystem can't supply it), so no lstat is needed to detect links.
139
+ const isLink = entry.isSymbolicLink();
140
+ // Ignored-directory boundary (node_modules, .git…): skip on the dirent
141
+ // type alone — ZERO stat syscalls. A symlink reports isDirectory()
142
+ // false on its dirent and falls through to the stat-based
143
+ // classification below, so a link named node_modules still classifies
144
+ // by its target exactly as before.
145
+ if (entry.isDirectory() && ignoreDirs.has(name)) continue;
132
146
  let st;
133
- let isLink: boolean;
134
147
  try {
135
- st = statSync(abs);
136
- isLink = lstatSync(abs).isSymbolicLink();
148
+ // Non-links: a single lstatSync supplies isDirectory/isFile/size/
149
+ // mtimeMs — field-for-field what the previous statSync returned,
150
+ // since with no link to follow the two calls are identical. Links:
151
+ // keep the following statSync so the entry classifies by its TARGET;
152
+ // a broken link throws here and is skipped, same as before.
153
+ st = isLink ? statSync(abs) : lstatSync(abs);
137
154
  } catch {
138
155
  continue;
139
156
  }