@effected/walker 0.20.1 → 0.22.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/Confine.js +19 -10
- package/ConfineReal.js +123 -0
- package/Descend.js +60 -126
- package/DescendSync.js +79 -0
- package/Walker.js +193 -38
- package/index.d.ts +591 -19
- package/index.js +4 -2
- package/internal/absolute.js +19 -0
- package/internal/descendMachine.js +282 -0
- package/internal/trimTrailing.js +13 -0
- package/internal/upwardDrivers.js +71 -0
- package/internal/upwardMachine.js +239 -0
- package/node-sync.d.ts +102 -0
- package/node-sync.js +61 -0
- package/package.json +7 -2
package/Walker.js
CHANGED
|
@@ -1,21 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runUpward, runUpwardSync } from "./internal/upwardDrivers.js";
|
|
2
|
+
import { ascendChain, ascendToMarkerMachine, ascendWithinMachine, findUpwardAllMachine, findUpwardMachine } from "./internal/upwardMachine.js";
|
|
3
|
+
import { Effect, Option, Path } from "effect";
|
|
2
4
|
|
|
3
5
|
// src/Walker.ts
|
|
4
|
-
const ascendToPhysical = (start, ceiling, options) => Effect.gen(function* () {
|
|
5
|
-
const fs = yield* FileSystem.FileSystem;
|
|
6
|
-
const path = yield* Path.Path;
|
|
7
|
-
if (!path.isAbsolute(ceiling)) return yield* Effect.die(/* @__PURE__ */ new Error(`Walker.ascendWithin: ceiling must be an absolute path, received ${JSON.stringify(ceiling)} (ascending from ${JSON.stringify(start)})`));
|
|
8
|
-
const dirs = yield* Walker.ascend(start, {
|
|
9
|
-
...options,
|
|
10
|
-
stopAt: ceiling
|
|
11
|
-
});
|
|
12
|
-
const last = dirs.at(-1);
|
|
13
|
-
if (last !== void 0 && path.resolve(last) === path.resolve(ceiling)) return dirs;
|
|
14
|
-
const physical = yield* Effect.option(fs.realPath(ceiling));
|
|
15
|
-
if (Option.isNone(physical)) return dirs;
|
|
16
|
-
const hit = yield* Walker.findRoot(dirs, (dir) => Effect.map(fs.realPath(dir), (real) => real === physical.value));
|
|
17
|
-
return Option.isSome(hit) ? dirs.slice(0, dirs.indexOf(hit.value) + 1) : dirs;
|
|
18
|
-
});
|
|
19
6
|
/**
|
|
20
7
|
* Upward path traversal primitives: the directory chain from a start path to the
|
|
21
8
|
* root, and first-match scans over it.
|
|
@@ -61,20 +48,9 @@ var Walker = class Walker {
|
|
|
61
48
|
static ascend(start, options) {
|
|
62
49
|
return Effect.gen(function* () {
|
|
63
50
|
const path = yield* Path.Path;
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
const ceiling = options?.stopAt === void 0 ? void 0 : path.resolve(options.stopAt);
|
|
68
|
-
const dirs = [];
|
|
69
|
-
let current = start;
|
|
70
|
-
for (let depth = 0; depth < maxDepth; depth++) {
|
|
71
|
-
dirs.push(current);
|
|
72
|
-
if (ceiling !== void 0 && (current === ceiling || path.resolve(current) === ceiling)) break;
|
|
73
|
-
const parent = path.dirname(current);
|
|
74
|
-
if (parent === current) break;
|
|
75
|
-
current = parent;
|
|
76
|
-
}
|
|
77
|
-
return dirs;
|
|
51
|
+
const chain = ascendChain("Walker.ascend", start, options, path);
|
|
52
|
+
if (chain._tag === "Defect") return yield* Effect.die(new Error(chain.message));
|
|
53
|
+
return chain.value;
|
|
78
54
|
});
|
|
79
55
|
}
|
|
80
56
|
/**
|
|
@@ -106,8 +82,8 @@ var Walker = class Walker {
|
|
|
106
82
|
*/
|
|
107
83
|
static ascendWithin(start, ceiling, options) {
|
|
108
84
|
return Effect.gen(function* () {
|
|
109
|
-
|
|
110
|
-
return yield*
|
|
85
|
+
const path = yield* Path.Path;
|
|
86
|
+
return yield* runUpward(ascendWithinMachine("Walker.ascendWithin", start, Option.getOrUndefined(ceiling), options, path));
|
|
111
87
|
});
|
|
112
88
|
}
|
|
113
89
|
/**
|
|
@@ -139,12 +115,7 @@ var Walker = class Walker {
|
|
|
139
115
|
* output — a few hundred strings under the default `maxDepth`.
|
|
140
116
|
*/
|
|
141
117
|
static findUpward(dirs, candidatesFor) {
|
|
142
|
-
return Effect.
|
|
143
|
-
const fs = yield* FileSystem.FileSystem;
|
|
144
|
-
const candidates = [];
|
|
145
|
-
for (const dir of dirs) candidates.push(...candidatesFor(dir));
|
|
146
|
-
return yield* Walker.firstMatch(candidates, (candidate) => fs.exists(candidate));
|
|
147
|
-
});
|
|
118
|
+
return Effect.map(runUpward(findUpwardMachine(dirs, candidatesFor)), Option.fromUndefinedOr);
|
|
148
119
|
}
|
|
149
120
|
/**
|
|
150
121
|
* The first directory in `dirs` that `isRoot` accepts.
|
|
@@ -157,6 +128,190 @@ var Walker = class Walker {
|
|
|
157
128
|
static findRoot(dirs, isRoot) {
|
|
158
129
|
return Walker.firstMatch(dirs, isRoot);
|
|
159
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* {@link Walker.ascend} stopped at the nearest directory that holds any of
|
|
133
|
+
* `markers`, inclusive: the chain from `start` up to and including the
|
|
134
|
+
* first directory with a `.git` (or whatever the markers name) entry.
|
|
135
|
+
*
|
|
136
|
+
* @remarks
|
|
137
|
+
* A marker is an entry NAME probed with `FileSystem.exists` in each
|
|
138
|
+
* directory, nearest first, so any entry kind counts — a `.git` directory,
|
|
139
|
+
* or the `.git` FILE of a worktree or submodule — and a link counts when it
|
|
140
|
+
* resolves. The probes stop at the hit: nothing above the directory that
|
|
141
|
+
* holds a marker is ever touched. With no marker anywhere the chain runs to
|
|
142
|
+
* the filesystem root, exactly as `ascend(start)` gives it.
|
|
143
|
+
*
|
|
144
|
+
* A failed probe is absorbed as "no marker here", as
|
|
145
|
+
* {@link Walker.firstMatch} absorbs one, so one unreadable directory never
|
|
146
|
+
* hides a marker above it. An empty `markers` list, or an empty marker
|
|
147
|
+
* name, is a **defect**: the first would silently ask for the unbounded
|
|
148
|
+
* walk and the second would match every directory, and both are
|
|
149
|
+
* statically wrong at the call site. An invalid `maxDepth` dies exactly as
|
|
150
|
+
* `ascend`'s does.
|
|
151
|
+
*
|
|
152
|
+
* There is no `stopAt` here: a marker stop and a lexical ceiling answer the
|
|
153
|
+
* same question two ways. To bound a marker walk by a ceiling as well,
|
|
154
|
+
* take the shorter of the two chains.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* import { Walker } from "@effected/walker";
|
|
159
|
+
* import { Effect } from "effect";
|
|
160
|
+
*
|
|
161
|
+
* const program = Effect.gen(function* () {
|
|
162
|
+
* // With `.git` in /repo: ["/repo/packages/app", "/repo/packages", "/repo"]
|
|
163
|
+
* return yield* Walker.ascendToMarker("/repo/packages/app", [".git"]);
|
|
164
|
+
* });
|
|
165
|
+
* // Requires `FileSystem` and `Path` from the platform layer.
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
static ascendToMarker(start, markers, options) {
|
|
169
|
+
return Effect.gen(function* () {
|
|
170
|
+
const path = yield* Path.Path;
|
|
171
|
+
return yield* runUpward(ascendToMarkerMachine("Walker.ascendToMarker", start, markers, options, path));
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Every existing candidate of the NEAREST directory in `dirs` that has any,
|
|
176
|
+
* or `Option.none()` — {@link Walker.findUpward} for a caller that must see
|
|
177
|
+
* a second hit (an ambiguity) rather than have the first one chosen.
|
|
178
|
+
*
|
|
179
|
+
* @remarks
|
|
180
|
+
* Directories are scanned in order, and every candidate of a directory is
|
|
181
|
+
* probed before the scan ascends; the scan stops after the first directory
|
|
182
|
+
* with a hit, so nothing above it is probed. `found` keeps the order
|
|
183
|
+
* `candidatesFor` produced and is never empty. A failed probe is absorbed
|
|
184
|
+
* as "not there", as in `findUpward`, so not-found and cannot-look are
|
|
185
|
+
* indistinguishable here too.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* import { Walker } from "@effected/walker";
|
|
190
|
+
* import { Effect, Option } from "effect";
|
|
191
|
+
*
|
|
192
|
+
* const program = Effect.gen(function* () {
|
|
193
|
+
* const dirs = yield* Walker.ascend("/repo/packages/app");
|
|
194
|
+
* const hits = yield* Walker.findUpwardAll(dirs, (dir) => [`${dir}/tool.config.ts`, `${dir}/tool.config.js`]);
|
|
195
|
+
* // Two config files in one directory is the caller's ambiguity to report.
|
|
196
|
+
* return Option.map(hits, ({ found }) => found.length > 1);
|
|
197
|
+
* });
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
static findUpwardAll(dirs, candidatesFor) {
|
|
201
|
+
return Effect.map(runUpward(findUpwardAllMachine(dirs, candidatesFor)), Option.fromUndefinedOr);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* {@link Walker.ascend}, synchronously, over a caller-supplied path
|
|
205
|
+
* implementation.
|
|
206
|
+
*
|
|
207
|
+
* @remarks
|
|
208
|
+
* The same chain from the same code: lexical, nearest first, `stopAt`
|
|
209
|
+
* inclusive and compared in resolved form. A relative `stopAt` or an
|
|
210
|
+
* invalid `maxDepth` is a defect, which here THROWS, since there is no
|
|
211
|
+
* error channel to die through. For a caller with no Effect runtime (a CLI
|
|
212
|
+
* helper that runs before one, a bundler-plugin hook); inside Effect, use
|
|
213
|
+
* `ascend`.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```ts
|
|
217
|
+
* import { Walker } from "@effected/walker";
|
|
218
|
+
* import { nodeWalkerPath } from "@effected/walker/node-sync";
|
|
219
|
+
*
|
|
220
|
+
* const dirs = Walker.ascendSync("/repo/packages/app", { stopAt: "/repo", path: nodeWalkerPath });
|
|
221
|
+
* // => ["/repo/packages/app", "/repo/packages", "/repo"]
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
static ascendSync(start, options) {
|
|
225
|
+
const chain = ascendChain("Walker.ascendSync", start, options, options.path);
|
|
226
|
+
if (chain._tag === "Defect") throw new Error(chain.message);
|
|
227
|
+
return chain.value;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* {@link Walker.ascendWithin}, synchronously, over a caller-supplied port:
|
|
231
|
+
* the lexical chain stopped at the nearest directory whose `realPath` is
|
|
232
|
+
* the ceiling's.
|
|
233
|
+
*
|
|
234
|
+
* @remarks
|
|
235
|
+
* The same walk from the same code as `ascendWithin`: `Option.none()` is
|
|
236
|
+
* exactly `ascendSync(start)`, a ceiling the chain already spells costs no
|
|
237
|
+
* `realPath`, a `realPath` that throws is absorbed as "not the ceiling",
|
|
238
|
+
* and an unresolvable ceiling leaves the lexical chain standing. A
|
|
239
|
+
* relative ceiling or an invalid `maxDepth` throws.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* import { Walker } from "@effected/walker";
|
|
244
|
+
* import { nodeWalkerSyncOps } from "@effected/walker/node-sync";
|
|
245
|
+
* import { Option } from "effect";
|
|
246
|
+
*
|
|
247
|
+
* const dirs = Walker.ascendWithinSync("/repo/packages/app", Option.some("/repo"), nodeWalkerSyncOps);
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
static ascendWithinSync(start, ceiling, options) {
|
|
251
|
+
return runUpwardSync(ascendWithinMachine("Walker.ascendWithinSync", start, Option.getOrUndefined(ceiling), options, options.path), options.fileSystem);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* {@link Walker.ascendToMarker}, synchronously, over a caller-supplied port.
|
|
255
|
+
*
|
|
256
|
+
* @remarks
|
|
257
|
+
* The same walk from the same code. A marker exists when the port's `stat`
|
|
258
|
+
* (links followed) answers for it; a `stat` that throws, for any reason,
|
|
259
|
+
* is "no marker here", as the `Effect` form absorbs a failed probe. An
|
|
260
|
+
* empty `markers` list, an empty marker name or an invalid `maxDepth`
|
|
261
|
+
* throws.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* import { Walker } from "@effected/walker";
|
|
266
|
+
* import { nodeWalkerSyncOps } from "@effected/walker/node-sync";
|
|
267
|
+
*
|
|
268
|
+
* const dirs = Walker.ascendToMarkerSync("/repo/packages/app", [".git"], nodeWalkerSyncOps);
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
static ascendToMarkerSync(start, markers, options) {
|
|
272
|
+
return runUpwardSync(ascendToMarkerMachine("Walker.ascendToMarkerSync", start, markers, options, options.path), options.fileSystem);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* {@link Walker.findUpward}, synchronously, over a caller-supplied port: the
|
|
276
|
+
* first existing candidate, directory-major, or `Option.none()`.
|
|
277
|
+
*
|
|
278
|
+
* @remarks
|
|
279
|
+
* The same scan from the same code. A candidate exists when the port's
|
|
280
|
+
* `stat` (links followed) answers for it; a `stat` that throws is "not
|
|
281
|
+
* there". A throwing `candidatesFor` throws before any probe.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```ts
|
|
285
|
+
* import { Walker } from "@effected/walker";
|
|
286
|
+
* import { nodeWalkerSyncOps } from "@effected/walker/node-sync";
|
|
287
|
+
*
|
|
288
|
+
* const dirs = Walker.ascendSync("/repo/packages/app", { path: nodeWalkerSyncOps.path });
|
|
289
|
+
* const workspace = Walker.findUpwardSync(dirs, (dir) => [`${dir}/pnpm-workspace.yaml`], nodeWalkerSyncOps);
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
static findUpwardSync(dirs, candidatesFor, ops) {
|
|
293
|
+
return Option.fromUndefinedOr(runUpwardSync(findUpwardMachine(dirs, candidatesFor), ops.fileSystem));
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* {@link Walker.findUpwardAll}, synchronously, over a caller-supplied port.
|
|
297
|
+
*
|
|
298
|
+
* @remarks
|
|
299
|
+
* The same scan from the same code: every existing candidate of the
|
|
300
|
+
* nearest directory with any, in candidate order, and nothing above it
|
|
301
|
+
* probed. A candidate exists when the port's `stat` answers for it.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* import { Walker } from "@effected/walker";
|
|
306
|
+
* import { nodeWalkerSyncOps } from "@effected/walker/node-sync";
|
|
307
|
+
*
|
|
308
|
+
* const dirs = Walker.ascendSync("/repo/packages/app", { path: nodeWalkerSyncOps.path });
|
|
309
|
+
* const hits = Walker.findUpwardAllSync(dirs, (dir) => [`${dir}/a.config.ts`, `${dir}/a.config.js`], nodeWalkerSyncOps);
|
|
310
|
+
* ```
|
|
311
|
+
*/
|
|
312
|
+
static findUpwardAllSync(dirs, candidatesFor, ops) {
|
|
313
|
+
return Option.fromUndefinedOr(runUpwardSync(findUpwardAllMachine(dirs, candidatesFor), ops.fileSystem));
|
|
314
|
+
}
|
|
160
315
|
};
|
|
161
316
|
|
|
162
317
|
export { Walker };
|