Repository navigation
Implement fs-extra as a built-in package; hoist lazily defined error classes - #1504
Merged
Merged
Conversation
fs-extra builds its exports at load time (computed keys over graceful-fs, universalify wrappers), which the static CommonJS model cannot follow, so programs reading its members through the default import failed. The package-shim registry now maps fs-extra 8.x-11.x to a TypeScript implementation over node:fs: fs's members re-exported, the wrapped methods following universalify's callback-or-promise rule over fs.promises, and the extras (copy, move, remove, emptyDir, mkdirs, ensureFile, pathExists, outputFile, the JSON helpers and their aliases) with fs-extra's messages. Its declarations stand in for @types/fs-extra, including the default import, and a namespace import resolves to the shim. What it does not implement is not declared, so using it is a type error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bundled package's error class defined inside an __esm initializer
(`NowBuildError = class extends Error { … }`) now hoists to a declaration
like the classes over builtin module members, so a program class can
extend it and `new` and `instanceof` work against both. Reading a standard
global constructor the bundle never rebinds runs no code. References in
esbuild's `0 && (module.exports = { … })` annotation for Node's CommonJS
lexer never run and no longer block the move.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Date-only utimes, literal mkdir recursion and promise-backed readFile callbacks: the forms that compile natively with either type world. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build-utils declares its error module's bindings and initializer in one statement (`var NowBuildError, mapTypoToSuggestion, init_errors = __esm(…)`). The split pieces follow the statement, so the initializer only needs to be its last declarator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/npm-fs-extra-shim
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
In minified output the class closes right where the initializer body
does (`…Counter=class{…}}})`), so the empty insertion that opens the next
run-once piece shared its position with the body's closing edit and was
dropped: the initializer then called a piece that did not exist. The
opener now rides on the class's own closing brace.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bundle that embeds fs-extra (build-utils does) compiled the package's own JavaScript, whose computed export table and graceful-fs internals the static model cannot follow. A __commonJS chunk whose key names a shimmed package's main file at a supported version now requires the shim instead, through a forwarder declaration beside the bundle: the checker follows it to the shim (a package's JavaScript cannot load TypeScript sources directly), and module resolution answers the shim for its path. The package's other chunks are no longer reached. Calls through such a require of a TypeScript module lower when the export never reads `this` (require() answers the module's namespace). package-shim-guard.test.ts compiles every registered shim module under both type worlds and fails on any lowering diagnostic or fence inside the shims, reached or not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # packages/compiler/src/frontend/npm-static-bundled-esm.test.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fs-extra shim - writeFile and outputFile with byte data and a mode open the file with that mode, and writeJsonSync and outputJsonSync pass the mode. - copySync and moveSync compare source and destination inodes and walk the destination's parents, as copy and move do. - The resolver parity sweep skips the project-import comparison for shimmed packages. - The JSON reader writes the byte order mark as an escape.
…/npm-fs-extra-shim
…/npm-fs-extra-shim
…ource copy and copySync now follow fs-extra's onLink: only a destination that is itself a symlink is replaced; over a regular file or directory the symlink call fails with EEXIST and the destination is kept, whatever overwrite says. The fs-extra corpus program covers both forms.
This branch was successfully deployed
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.
Stacked on #1458 (
opt/npm-polyfill-shims, which now includes #1487). Review the stack below it first.Summary
fs-extra 8.x–11.x is built in. fs-extra builds its export object at load time. It copies
Object.keys(graceful-fs)ontoexports, wraps the asynchronous methods with universalify, and spreads its submodules intomodule.exports. The static CommonJS model can't follow that, so programs that read its members through the default import failed with "a binding form with no lowering". Readingmkdtemp,readFile,writeFile,stat,existsSync,promisesorcreateReadStreamfromimport fs from 'fs-extra'hit this.Like undici in #1425, the package-shim registry now maps fs-extra to a TypeScript implementation (
packages/compiler/shims/fs-extra) built onnode:fs:fs.promises, using only the call shapes that compile natively. The methods areaccess,appendFile,writeFile,readFile,chmod,chown,lchown,copyFile,link,lstat,stat,mkdir,mkdtemp,readdir,readlink,realpath,rename,rm,rmdir,symlink,truncate,unlinkandutimes.promises, the streams, the*Syncfunctions andconstants.copy/copySync(with fs-extra's checks and messages),move/moveSync,remove/removeSync,emptyDir,ensureDir/mkdirs/mkdirpand their sync forms,ensureFile,pathExists,outputFile,readJson/readJsonSync,writeJson/writeJsonSync,outputJson/outputJsonSync, and thereadJSON-style aliases. JSON follows jsonfile's formatting, BOM stripping,throws: falseand file-prefixed error messages.@types/fs-extra. The DefinitelyTyped twin'sindex.d.tsreads as a re-export of the shim, including its default export. A namespace import (import * as fs from 'fs-extra') resolves to the shim as well.ensureLink,ensureSymlinkand their aliases,gracefulify, the descriptor-based promise forms, the callback forms of the methods that answer stats, listings or paths, Buffer and URL paths, and option shapesfs.promisesdoes not compile natively.Error classes defined in a lazy initializer. The hoisting from #1487 now also accepts a class whose base is a standard global constructor the bundle never rebinds, such as
class extends Error; reading that base runs no code. This is the shape of a bundled package's error class, and a program class can now extend it, withnewandinstanceofworking against both classes. The hoisting also changed in two other ways:varstatement (var A, B, init_x = __esm(…)).0 && (module.exports = { … })annotation, which never runs, no longer block it.Bundled copies of shimmed packages. A bundle that embeds fs-extra (build-utils does) compiled fs-extra's own JavaScript, which the static model can't follow. Now a
__commonJSchunk whose key names a shimmed package's main file at a supported version (…/.pnpm/fs-extra@9.0.0/node_modules/fs-extra/lib/index.js) requires the shim instead:this, becauserequire()answers the module's namespace.The registry gained
mainFilefor each shim to support this.A guard over every shim.
packages/compiler/test/package-shim-guard.test.tscompiles each registered shim module (fs-extra, undici v5, undici v7) under both type worlds: scriptc's bundled Node declarations and @types/node. It fails on any lowering diagnostic or runtime fence located in the shim files, whether a program reaches it or not. Shim internals only suppress TypeScript type errors. A lowering refusal in shim code a program reaches is a build error located in the shim; one in unreached shim code would fail a later program, and the guard catches it now. I checked that the guard fails on a broken shim version.A fix to the class hoisting from #1487. In minified output, an initializer that ends with its class lost the opener of its next run-once piece. Its empty insertion shared a position with the body's closing edit, so the initializer then called a piece that did not exist. The fix is on #1458 (
Keep a hoisted class's next piece when the initializer ends with it), with a unit test, and is merged here. Here it also covers the shared-varform that the whitespace-minified lazy-barrel case uses.Evidence
fs-extra-package/runs the real fs-extra 9.0.0 under Node; the package, its dependencies and@types/fs-extraare vendored and unmodified (MIT). It exercises:fs.promisesand a read stream;The binary's output is byte-identical to Node's.
package-shims.test.tshas a new test: an installed CommonJS package'srequire('fs-extra')compiles the shim and matches Node running the real package.2589-bundled-error-subclassuses real esbuild output. A program class extends a bundled error class defined in a lazy initializer, andnewandinstanceofare checked against both classes. Before this change it failed with "extending classes not declared in the program".varstatement, and in minified text that ends with the class.2590-bundled-fs-extrauses real esbuild output that embeds fs-extra 9.0.0, with its user module lazily required as in build-utils. A unit test checks the forwarder, and that an unsupported fs-extra version keeps its chunk.package-shim-guard.test.tscovers all three shim modules in both type worlds.self-hosting-native-driver.test.tspasses on the final candidate (d118fcb), in 1022 s.pnpm lintand the formatting of the touched files pass, as dotest:ts7 --baselines-only, the frontend unit tests,package-shims.test.ts, the npm-static suite (127) and the npm suite. The npm suite's one failure,node24-url-authority, comes from the sandbox's Node 24.15 versus the pinned 24.22.🤖 Generated with Claude Code