Conversation
added 4 commits
August 25, 2026 08:12
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.
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.
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.
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.
🦋 Changeset detectedLatest commit: 2201c59 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This closes four reports: #118, #119, #120 and #121. Three of them change what
Workspace.fsoffers callers and a bug.For #121,
mkdirwalked the path with its own helper, which read each directory entry directly and treated anything that wasn't a directory as an error. So if you pointed a symbolic link at a directory,mkdirrefused to create anything underneath it.Every other write path follows links,
writeFileincluded.mkdirnow does the same. It also tracks the resolved path as it walks, so the new directory lands under the directory the link points at instead of next to the link.A parent that turns out to be a file still reports
ENOTDIR, a dangling parent link reportsENOENTinstead of creating whatever the link pointed at, and a loop reportsELOOP.findchecked its glob before returning an entry, but it walked into every directory either way. Search a workspace withnode_modulesor.gitin it.You can now pass globs to exclude matched against the same path relative to where the search started. Excluding beats including, so an excluded entry never comes back.
The check runs before the walker asks a directory for its children, so an excluded directory takes its whole subtree with it and nothing under it is ever read. The order is unchanged, and
limitandoffsetcount what's left.The store has been able to move files, directories and symbolic links in one transaction for a while now, and it handles:
None of that reached
Workspace.fs. Callers had to copy the source and then delete it, and the Worker shell did exactly that formv. If it failed in between, you were left with the bytes at both paths, or half a directory.renameis on the public surface now, and on the stub as well, so a caller over remote procedure calls sees the same surface as one running in the same process. The shell uses it, and only falls back to copy-then-delete whenrenamewon't replace the destination, which happens when the shell is merging one tree into another.Nothing new crosses the connection to the container, because sync already carries the entries and tombstones a move produces.
It replaces the destination when both ends are the same sort of thing, so a file or symbolic link can replace a file or symbolic link, and a directory can replace an empty one. Anything else gets the error you'd expect:
ENOENT,ENOTEMPTY,EISDIR,ENOTDIR,EINVALorEROFS.The documentation now covers the full implementation. Each method gets its return value and its errors, and the comparison against
node:fs/promises. The note on symbolic links now gives the two rules that cover everything. A link in the middle of a path is always followed. A link at the end is followed by everything exceptlstatandreadlink, which exist to describe the link itself.To check the work, run the suites in both environments. The Node run uses an in-process SQLite database, the Workers run drives a real durable object, and that difference is why both are worth running.
There are four changesets, one per report. One thing I left alone:
grepshares the walk withfindand would take the same option almost for free, but I kept its signature as it is so this change closes the four reports and nothing else.