pi-tool-discipline 0.1.9 → 0.1.11
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/extensions/search.ts +25 -13
- package/package.json +1 -1
package/extensions/search.ts
CHANGED
|
@@ -53,6 +53,7 @@ async function walk(dir: string, out: string[], state: WalkState, signal?: Abort
|
|
|
53
53
|
try {
|
|
54
54
|
entries = await readdir(dir);
|
|
55
55
|
} catch (error: any) {
|
|
56
|
+
throwIfAborted(signal); // cancellation wins over the fs error
|
|
56
57
|
if (depth === 0) state.rootError = error?.message ?? String(error);
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
@@ -69,13 +70,16 @@ async function walk(dir: string, out: string[], state: WalkState, signal?: Abort
|
|
|
69
70
|
const lst = await lstat(p);
|
|
70
71
|
throwIfAborted(signal);
|
|
71
72
|
if (lst.isSymbolicLink()) continue; // never follow symlinks
|
|
72
|
-
if (lst.isDirectory())
|
|
73
|
-
|
|
73
|
+
if (lst.isDirectory()) {
|
|
74
|
+
await walk(p, out, state, signal, depth + 1);
|
|
75
|
+
throwIfAborted(signal);
|
|
76
|
+
} else if (lst.isFile()) out.push(p); // regular files only
|
|
74
77
|
} catch (error: any) {
|
|
75
|
-
|
|
78
|
+
throwIfAborted(signal); // cancellation wins over the fs error
|
|
76
79
|
// unreadable entries are skipped
|
|
77
80
|
}
|
|
78
81
|
}
|
|
82
|
+
throwIfAborted(signal); // post-recursion check
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
/**
|
|
@@ -84,7 +88,7 @@ async function walk(dir: string, out: string[], state: WalkState, signal?: Abort
|
|
|
84
88
|
*/
|
|
85
89
|
function truncate(text: string, maxBytes = MAX_OUTPUT_BYTES, extraMarkerBytes = 0): string {
|
|
86
90
|
const buf = Buffer.from(text, "utf8");
|
|
87
|
-
if (buf.length <= maxBytes) return text;
|
|
91
|
+
if (buf.length + extraMarkerBytes <= maxBytes) return text;
|
|
88
92
|
const budget = maxBytes - Buffer.byteLength(TRUNCATE_MARKER) - extraMarkerBytes;
|
|
89
93
|
if (budget <= 0) return TRUNCATE_MARKER.trim();
|
|
90
94
|
const cut = buf.subarray(0, budget).toString("utf8");
|
|
@@ -93,12 +97,14 @@ function truncate(text: string, maxBytes = MAX_OUTPUT_BYTES, extraMarkerBytes =
|
|
|
93
97
|
return `${cut.slice(0, lastNewline)}\n${TRUNCATE_MARKER}`;
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
async function resolveRoot(cwd: string, sub?: string): Promise<string | null> {
|
|
100
|
+
async function resolveRoot(cwd: string, sub?: string, signal?: AbortSignal): Promise<string | null> {
|
|
97
101
|
const root = resolve(cwd, sub || ".");
|
|
98
102
|
try {
|
|
99
103
|
const st = await stat(root);
|
|
104
|
+
throwIfAborted(signal);
|
|
100
105
|
return st.isDirectory() ? root : null;
|
|
101
|
-
} catch {
|
|
106
|
+
} catch (error: any) {
|
|
107
|
+
throwIfAborted(signal);
|
|
102
108
|
return null;
|
|
103
109
|
}
|
|
104
110
|
}
|
|
@@ -118,26 +124,31 @@ export async function grepFiles(opts: {
|
|
|
118
124
|
signal?: AbortSignal;
|
|
119
125
|
}): Promise<string> {
|
|
120
126
|
throwIfAborted(opts.signal);
|
|
121
|
-
const root = await resolveRoot(opts.cwd, opts.path);
|
|
127
|
+
const root = await resolveRoot(opts.cwd, opts.path, opts.signal);
|
|
122
128
|
if (!root) return `Error: search path not found: ${resolve(opts.cwd, opts.path || ".")}`;
|
|
123
129
|
const pattern = opts.caseSensitive ? opts.pattern : opts.pattern.toLowerCase();
|
|
124
130
|
const limit = opts.maxResults ?? 100;
|
|
125
131
|
const { files, state } = await walkFiles(root, opts.signal);
|
|
132
|
+
throwIfAborted(opts.signal);
|
|
126
133
|
if (state.rootError) return `Error: cannot read search root ${root}: ${state.rootError}`;
|
|
127
134
|
const matches: FileMatch[] = [];
|
|
128
135
|
for (const file of files) {
|
|
129
136
|
throwIfAborted(opts.signal);
|
|
130
137
|
if (matches.length >= limit) break;
|
|
131
138
|
try {
|
|
132
|
-
|
|
133
|
-
|
|
139
|
+
const size = (await stat(file)).size;
|
|
140
|
+
throwIfAborted(opts.signal);
|
|
141
|
+
if (size > MAX_FILE_BYTES) continue; // cap only before reading content
|
|
142
|
+
} catch (error: any) {
|
|
143
|
+
throwIfAborted(opts.signal);
|
|
134
144
|
continue;
|
|
135
145
|
}
|
|
136
146
|
throwIfAborted(opts.signal);
|
|
137
147
|
let content: string;
|
|
138
148
|
try {
|
|
139
149
|
content = await readFile(file, "utf8");
|
|
140
|
-
} catch {
|
|
150
|
+
} catch (error: any) {
|
|
151
|
+
throwIfAborted(opts.signal);
|
|
141
152
|
continue;
|
|
142
153
|
}
|
|
143
154
|
throwIfAborted(opts.signal);
|
|
@@ -167,9 +178,10 @@ export async function findFiles(opts: {
|
|
|
167
178
|
signal?: AbortSignal;
|
|
168
179
|
}): Promise<string> {
|
|
169
180
|
throwIfAborted(opts.signal);
|
|
170
|
-
const root = await resolveRoot(opts.cwd, opts.path);
|
|
181
|
+
const root = await resolveRoot(opts.cwd, opts.path, opts.signal);
|
|
171
182
|
if (!root) return `Error: search path not found: ${resolve(opts.cwd, opts.path || ".")}`;
|
|
172
183
|
const { files, state } = await walkFiles(root, opts.signal);
|
|
184
|
+
throwIfAborted(opts.signal);
|
|
173
185
|
if (state.rootError) return `Error: cannot read search root ${root}: ${state.rootError}`;
|
|
174
186
|
const needle = opts.pattern?.toLowerCase();
|
|
175
187
|
// Match against the RELATIVE path so a pattern matching an ancestor
|
|
@@ -193,7 +205,7 @@ export async function findFiles(opts: {
|
|
|
193
205
|
|
|
194
206
|
export async function listDir(opts: { path?: string; cwd: string; signal?: AbortSignal }): Promise<string> {
|
|
195
207
|
throwIfAborted(opts.signal);
|
|
196
|
-
const root = await resolveRoot(opts.cwd, opts.path);
|
|
208
|
+
const root = await resolveRoot(opts.cwd, opts.path, opts.signal);
|
|
197
209
|
if (!root) return `Error: directory not found: ${resolve(opts.cwd, opts.path || ".")}`;
|
|
198
210
|
try {
|
|
199
211
|
const entries = await readdir(root);
|
|
@@ -201,7 +213,7 @@ export async function listDir(opts: { path?: string; cwd: string; signal?: Abort
|
|
|
201
213
|
if (entries.length === 0) return "(empty directory)";
|
|
202
214
|
return truncate(entries.join("\n"));
|
|
203
215
|
} catch (error: any) {
|
|
204
|
-
|
|
216
|
+
throwIfAborted(opts.signal);
|
|
205
217
|
return `Error listing ${root}: ${error.message}`;
|
|
206
218
|
}
|
|
207
219
|
}
|
package/package.json
CHANGED