mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
17a901a74f
### What? Adds debug-only OS realpath validation to successful `DiskFileSystem` file and directory reads. When a successfully canonicalized path differs from the supplied path, the read returns a normal task error naming both paths. Fixes pattern/glob traversal and NFT tracing so physical filesystem access uses resolved paths while logical paths remain available for user-visible specifiers and complete symlink-chain recovery. ### Why? Reading through an unresolved symlink parent gives the same filesystem object multiple path identities. That can make Turbo Tasks dependency tracking and invalidation inconsistent and can produce invalid deployment ZIPs when NFT output contains files below unresolved links. The checks return errors rather than asserting because paths can disagree temporarily under eventual consistency. Propagating a task error avoids panicking a worker thread while still exposing invalid callers during development. ### How? The validation lives directly in `DiskFileSystem::read` and `DiskFileSystem::raw_read_dir`. It calls the OS canonicalization API inline instead of the Turbo Tasks realpath task, keeping the diagnostic out of the task dependency graph. The guard runs only after the OS read succeeds, so missing/non-directory probes preserve their existing behavior. `read_matches` resolves each physical directory immediately before enumeration while retaining logical `PatternMatch` paths. `read_glob` and `track_glob` now resolve their initial directory before enumeration. Symlinks discovered later through wildcard segments are also traversed through resolved targets. `ReadGlobResult` deliberately retains logical paths rooted at the supplied base, allowing consumers to call `realpath_with_links` and recover the complete symlink chain. Consumers follow that contract explicitly: - NFT includes expand each logical match with `realpath_with_links`, emit resolved files and every traversed symlink, skip resolved directory targets, and deterministically deduplicate/sort output. - `import.meta.glob` uses recursive logical keys as the source of user-visible requests, while module resolution follows and tracks symlinks. - the hash-glob example resolves returned logical paths before reading. Webpack-loader context dependencies are covered for both `path/to/symlink/inner/path/*` and `path/to/*/inner/path/*`. The loader fixture performs its directory read with Node `fs`, reports the directory using `addContextDependency`, and Turbopack tracks the resolved target. ### Verification - `cargo fmt -p turbo-tasks-fs -p turbopack-ecmascript -p next-api -- --check` - `cargo clippy -p turbo-tasks-fs -p turbopack-ecmascript -p next-api --all-targets` - `cargo test -p turbo-tasks-fs` (128 passed) - `cargo test -p next-api` (7 passed) - `cargo check -p turbo-tasks-fs --examples` - `import.meta.glob` symlink execution fixture (1 passed) - Nine targeted node-file-trace CI cases with `release-with-assertions` (9 passed) - `pnpm build-all` - `webpack-loader-fs` Turbopack dev e2e (1 passed) - `build-trace-extra-entries-turbo` Turbopack production e2e (1 passed) - twoslash Turbopack production, normal mode (4 passed) - twoslash Turbopack production, cache-components mode (4 passed) - `bench/heavy-npm-deps` Turbopack development smoke test (HTTP 200) ### Notes The disk guard is cross-platform, while its symlink-parent regression test is Unix-only, matching neighbouring symlink tests. On Windows, OS canonicalization can also normalize casing and 8.3 short names; a debug read using a non-canonical spelling will therefore return the same diagnostic error. <!-- NEXT_JS_LLM --> <!-- fleet 1d32e12c-f4ec-4f22-862a-c85f0005805c --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
35 lines
1.1 KiB
JavaScript
35 lines
1.1 KiB
JavaScript
const fs = require('fs')
|
|
const path = require('path')
|
|
|
|
module.exports = async function (content) {
|
|
let dir = path.dirname(this.resourcePath)
|
|
|
|
let read1 = await new Promise((res, rej) =>
|
|
this.fs.readFile(path.join(dir, 'test.txt'), (err, data) => {
|
|
if (err) return rej(err)
|
|
res(data)
|
|
})
|
|
)
|
|
let read2 = await new Promise((res, rej) =>
|
|
this.fs.readFile(path.join(dir, 'test.txt'), 'utf8', (err, data) => {
|
|
if (err) return rej(err)
|
|
res(data)
|
|
})
|
|
)
|
|
let read3 = await new Promise((res, rej) =>
|
|
this.fs.readFile(path.join(dir, 'test.mp4'), (err, data) => {
|
|
if (err) return rej(err)
|
|
res(data)
|
|
})
|
|
)
|
|
const globDir = path.join(dir, 'path/to/symlink/inner/path')
|
|
const globEntries = await new Promise((res, rej) =>
|
|
fs.readdir(globDir, (err, entries) => {
|
|
if (err) return rej(err)
|
|
res(entries)
|
|
})
|
|
)
|
|
this.addContextDependency(globDir)
|
|
return `module.exports = "Buffer read: ${read1 instanceof Buffer ? read1.length : 0}, string read: '${read2.trim()}', binary read: ${read3.length}, glob read: '${globEntries.sort().join(',')}'"`
|
|
}
|