You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* dofs: follow symbolic links in mkdir
mkdir walked the target path with a helper that read each dirent
directly and treated every node other than a directory as ENOTDIR, so
a symbolic link to a directory blocked directory creation rather than
resolving through it. Every other write path, writeFile included,
follows links and shares one forty-hop budget.
The parent walk now expands intermediate links the way writeFile's
does, tracking the resolved path so the new directory lands under the
directory the link points at and the read-only mount guard sees the
location actually written. Recursive creation places its missing
ancestors under that resolved parent. A resolved parent that is a file
still reports ENOTDIR, a dangling parent link reports ENOENT in both
modes rather than being materialised, and a chain beyond forty hops
reports ELOOP.
Closes#119.
* dofs, computer: add exclusion globs to find
The walker tested the inclusion glob before yielding an entry but
descended into every directory regardless, so a search in a workspace
holding node_modules, .git, or generated build output paid for those
trees even when the caller wanted nothing from them.
FindOptions gains exclude, a list of globs of the same shape as the
inclusion pattern and matched against the same directory-relative
path. An exclusion is decided before inclusion, so it always wins, and
before any child query, so an excluded directory takes its whole
subtree with it rather than being filtered out afterwards. Traversal
stays deterministic and limit and offset apply to what survives. The
option reaches the public find tool, whose schema now advertises it.
Closes#121.
* dofs, computer: expose rename on the public filesystem
The store has implemented transactional file, directory, and symbolic
link moves for some time, covering destination replacement, non-empty
directories, read-only mounts, tombstones, revision stamping, and
subtree tracking. None of that reached Workspace.fs, so a caller had
to copy the source and then delete it, and the Worker shell used that
fallback for mv. A failure between the two steps left the entry at
both paths or a directory half copied.
WorkspaceFilesystem now forwards rename, and WorkspaceFilesystemStub
mirrors it with the usual filesystem observation span, so the Workers
RPC surface matches the in-process one. The shell adapter calls it and
keeps copy-then-delete only for a destination rename refuses to
replace, which is what the shell expects when it merges a tree. No new
method crosses the Cap'n Web boundary: the existing synchronisation
protocol already carries the resulting live entries and tombstones.
Closes#120.
* docs: describe the shipped symbolic-link surface
The filesystem specification said symbolic links were an internal
primitive, that Workspace.fs exposed neither symlink nor readlink,
that there was no lstat, and that an existing file's mode could not be
changed. The shipped API contradicts all four: WorkspaceFilesystem
exposes symlink, readlink, lstat, and chmod, the stub mirrors them
across the Workers RPC boundary, and the Dynamic Worker filesystem
adapters rely on them for the node:fs behaviour a shell expects.
Removing the methods would be a breaking change and would leave those
adapters without a way to serve ln -s, readlink, or test -L, so the
document follows the code.
Each of the four methods gains a section with its return value and its
errors, the comparison with node:fs/promises maps them rather than
striking them out, and the note on symbolic links now states the two
rules that cover the surface: intermediate segments are always
followed, and a trailing link is followed by everything except lstat
and readlink. The rename and find entries added alongside are
documented in the same pass.
Closes#118.
---------
Co-authored-by: agent <agent@users.noreply.github.com>
Document the symbolic-link filesystem surface. `docs/04_filesystem_interface.md` claimed that symbolic links were internal and that `Workspace.fs` had no `symlink`, `readlink`, `lstat`, or `chmod`, none of which matched the shipped API. Those four methods now have sections of their own covering return values and the `ENOENT`, `EINVAL`, and `ELOOP` cases, the comparison with `node:fs/promises` maps them, and the specification explains that `stat` follows a trailing link while `lstat` reports the link itself.
`Workspace.fs` gains `rename(oldPath, newPath)`, exposing the store's existing transactional move through the public surface and through `WorkspaceFilesystemStub`. An existing destination is replaced when the two ends agree on kind — a file or symbolic link for a file or symbolic link, an empty directory for a directory — and the operation reports `ENOENT`, `ENOTEMPTY`, `EISDIR`, `ENOTDIR`, `EINVAL`, and `EROFS` as documented in `docs/04_filesystem_interface.md`. The Worker shell's `mv` now calls it, so an interrupted move no longer leaves the entry at both paths or a directory half copied.
`find` accepts `exclude`, a list of glob patterns matched against the same directory-relative path as the inclusion glob. Exclusion is decided first, so it always wins, and an excluded directory is pruned during traversal: neither it nor anything below it is read. `limit` and `offset` apply to the matches that survive. The option travels through `WorkspaceFilesystem`, `WorkspaceFilesystemStub`, and the public find tool.
`mkdir` now follows symbolic links in intermediate path segments, so a link to a directory resolves transparently instead of failing with `ENOTDIR`. Creating `/alias/new-directory` where `/alias` points at `/real` creates `/real/new-directory`, and recursive creation places its missing ancestors under the resolved parent. A resolved parent that is a file still reports `ENOTDIR`, a dangling parent link reports `ENOENT`, and a chain longer than the shared forty-hop budget reports `ELOOP`.
@@ -321,12 +447,12 @@ so handlers from Node code port over directly.
321
447
| Code | When |
322
448
| --- | --- |
323
449
|`ENOENT`| Path does not exist and `force` is not true. Also raised by `stat` when a parent segment turns out to be a file. |
324
-
|`ENOTEMPTY`| Path is a non-empty directory and `recursive` is not true. |
325
-
|`ENOTDIR`| A parent path segment is a file (raised explicitly by `mkdir` and `writeFile`; `find` raises it when its `directory` argument is a file). |
326
-
|`EISDIR`| Expected a file, got a directory (e.g. `readFile` on a dir, `writeFile` on `/`). |
327
-
|`EEXIST`|`mkdir` without `recursive: true` on an existing path. |
328
-
|`EINVAL`| Invalid path or unsupported options. |
329
-
|`ELOOP`|Symlink traversal exceeded 40 hops. Thrown by the internal resolver when the `node:vfs` adapter wires up a cycle. |
450
+
|`ENOTEMPTY`| Path is a non-empty directory and `recursive` is not true. Also raised by `rename` when the destination directory has children. |
451
+
|`ENOTDIR`| A parent path segment is a file (raised explicitly by `mkdir` and `writeFile`; `find` raises it when its `directory` argument is a file; `rename` raises it when a directory would replace a non-directory). |
452
+
|`EISDIR`| Expected a file, got a directory (e.g. `readFile` on a dir, `writeFile` on `/`, `rename` of a file onto a directory). |
453
+
|`EEXIST`|`mkdir` without `recursive: true` on an existing path, or `symlink` onto an existing path. |
454
+
|`EINVAL`| Invalid path or unsupported options: `readlink` on something that is not a symbolic link, `rename` of the root or of a directory into itself. |
455
+
|`ELOOP`|Symbolic-link traversal exceeded 40 hops. Every path-walking method shares that budget, so a cycle surfaces from `stat`, `readFile`, `writeFile`, `mkdir`, and the rest alike. |
330
456
|`EPERM`| Operation is forbidden, e.g. deleting the workspace root. |
331
457
|`EIO`| Backing storage failed unexpectedly. |
332
458
|`EACCES`|*Reserved for future mount layer (see [06. Mount Interface](./06_mount_interface.md)).* No code path in `workspace-fs` currently throws it. |
@@ -379,28 +505,35 @@ maps to `Workspace.fs`:
379
505
|`rm`|`rm`|`{ recursive: true }` for non-empty dirs. |
|`stat` / `lstat`|`stat`| No `lstat`; `stat` follows symlinks. See note below. |
508
+
|`stat` / `lstat`|`stat`/ `lstat`|`stat` follows a trailing symbolic link; `lstat` reports the link. |
383
509
|`truncate`| — | Read, slice, write. |
384
-
|`chmod`|—|Pass `mode` to `writeFile` / `mkdir` at create time. There is no way to chmod an existing file without rewriting its bytes. |
510
+
|`chmod`|`chmod`|Mode masked to twelve bits; follows a trailing symbolic link. `mode` can also be passed to `writeFile` / `mkdir` at create time. |
385
511
|`chown`| — | No ownership model. |
386
512
|`utimes`| — |`mtime` is managed by the VFS. |
387
513
|`cp` / `copyFile`| — | Read + write. |
388
-
|`rename`|—|Read + write + delete. |
514
+
|`rename`|`rename`|One transaction; replaces a destination of the same kind. |
389
515
|`realpath`| — | Paths are already canonical. |
390
-
|`symlink` / `readlink`|— | Not on the public surface; see note below. |
516
+
|`symlink` / `readlink`|`symlink` / `readlink`| Same argument order as Node. Targets are stored verbatim and may dangle. |
391
517
|`watch`| — | Low-level primitive in `fs/watch.ts` (`createWatcher`, `createWatchAsyncIterable`, `WatchHandle`, `WatchOptions`); not exposed on the `WorkspaceFilesystem` class. |
392
518
|`open` / `FileHandle`| — | Use streams instead. |
393
-
|`glob`|`find`| Limited glob support (`*`, `**`, `**/`, and `?`). |
519
+
|`glob`|`find`| Limited glob support (`*`, `**`, `**/`, and `?`), plus `exclude` for pruning subtrees. |
394
520
| — |`grep`| Not in `node:fs`; literal by default, with optional regular expressions. |
395
521
| — |`find`| Recursive directory walk with an optional glob, relative-rooted. |
396
522
| — |`ls`| Flat list of file paths under a directory (segment-aware). |
397
523
398
-
### Note: symlinks
399
-
400
-
Symlinks exist as an **internal primitive** used by the `node:vfs`
401
-
adapter — the schema supports a `'symlink'` node type with a
402
-
`link_target`, and the resolver in `fs/resolve.ts` follows them with a
403
-
40-hop cap (throws `ELOOP` on overflow). They are **not** part of the
404
-
public `WorkspaceFilesystem` surface: there are no `fs.symlink` or
405
-
`fs.readlink` methods on `Workspace.fs`, and callers should treat all
406
-
visible paths as if they pointed straight at real files.
524
+
### Note: symbolic links
525
+
526
+
Symbolic links are part of the public surface. The schema carries a
527
+
`'symlink'` node type with a `link_target`, the resolver in
528
+
`fs/resolve.ts` follows them with a 40-hop cap (throws `ELOOP` on
529
+
overflow), and `Workspace.fs` exposes `symlink`, `readlink`, and
530
+
`lstat` on top of that. `WorkspaceFilesystemStub` mirrors all three
531
+
across the Workers RPC boundary, which is how the Dynamic Worker
532
+
filesystem adapters provide the `node:fs` behavior a shell expects from
533
+
`ln -s`, `readlink`, and `test -L`.
534
+
535
+
Two rules cover the whole surface. Intermediate segments are always
536
+
followed, so a link to a directory behaves like the directory for every
537
+
method, `mkdir` included. A trailing link is followed by everything
538
+
except `lstat` and `readlink`, which are the two methods whose purpose
Copy file name to clipboardExpand all lines: docs/09_tool_interface.md
+5-2Lines changed: 5 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -166,15 +166,18 @@ Entries are in name order. A non-final page includes `nextOffset`; pass it as th
166
166
167
167
```ts
168
168
{
169
-
path?:string; // default /workspace
169
+
path?:string; // default /workspace
170
170
pattern: string;
171
-
limit?:number; // default 200, maximum 1000
171
+
exclude?:string[];
172
+
limit?:number; // default 200, maximum 1000
172
173
offset?:number;
173
174
}
174
175
```
175
176
176
177
The pattern is relative to `path`. `*` stays within one path segment, `**` crosses directories, and `?` matches one non-separator character. Results contain `path` and `type`; a non-final page includes `nextOffset`. Pagination reaches `workspace.fs.find`, which walks directory children in fixed-size pages and stops after collecting the requested page instead of materializing every match.
177
178
179
+
`exclude` takes globs of the same shape, matched against the same relative path, and beats the inclusion pattern. An excluded directory is pruned rather than filtered, so `exclude: ["node_modules", "node_modules/**"]` keeps the walk out of a package tree instead of walking it and discarding the results.
0 commit comments