Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/content/docs/limitations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ title: "Limitations"

<include>../limitations/builtin-apis/filesystem-flags.mdx</include>

<include>../limitations/builtin-apis/copy.mdx</include>

<include>../limitations/builtin-apis/glob.mdx</include>

<include>../limitations/builtin-apis/filehandle-and-descriptor-i-o.mdx</include>
Expand Down
1 change: 1 addition & 0 deletions docs/content/limitations/builtin-apis/copy.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **Copy:** `fs.cpSync`, `fs.cp`, and `fs/promises` `cp` support every option and follow Node's two implementations: `cpSync` reports the native binding's errors, and the callback and promise forms report `SystemError`s. Files, directories, and symbolic links are copied in directory order, as in Node. The callback and promise forms complete the copy before the callback runs or the promise settles. An async `filter` must return a promise that is already settled; a pending one throws `SC2020`. On Windows, the three functions throw `SC2020`.
2 changes: 1 addition & 1 deletion docs/content/limitations/builtin-function-values.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ Other immutable aliases of table-backed Node builtins support direct calls, but

Synchronous and promise filesystem APIs with trailing arguments that affect behavior remain call-only. This includes writers, open, directory creation/removal, metadata readers, and directory readers. Passing them as values could discard modes, flags, encodings, or options during function-signature adaptation.

Native error-first filesystem callbacks can be stored and passed to platform adapters. Reads support Buffer and UTF-8 results; writes preserve string flags and modes; copy supports regular files and recursive directories. Reads and writes reject already-aborted signals, but native file operations complete synchronously before the deferred callback, so cancellation cannot interrupt I/O in progress. Symbolic-link copy, copy filters and clone modes, recursive directory reads, and Dirent results report a native limitation. `fs.glob` compiles only as a direct call; a stored `fs.glob` value throws `SC2020`.
Native error-first filesystem callbacks can be stored and passed to platform adapters. Reads support Buffer and UTF-8 results; writes preserve string flags and modes; copy follows `fs.cp`. Reads and writes reject already-aborted signals, but native file operations complete synchronously before the deferred callback, so cancellation cannot interrupt I/O in progress. Recursive directory reads and Dirent results report a native limitation. `fs.glob` compiles only as a direct call; a stored `fs.glob` value throws `SC2020`.
2 changes: 1 addition & 1 deletion docs/src/generated/node-v24-compatibility-meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"schemaVersion": 3,
"nodeVersion": "24.22.0",
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
"artifactVersion": "fba8a4c99de7c5424f50",
"artifactVersion": "ae3ec89c0c8935ca6c07",
"rowCount": 3880
}
22 changes: 11 additions & 11 deletions docs/src/generated/node-v24-compatibility.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,9 @@
"apiEntries": 280,
"static": {
"supported": 7,
"partial": 195,
"partial": 198,
"refused": 0,
"not-implemented": 78,
"not-implemented": 75,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
Expand Down Expand Up @@ -29667,9 +29667,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
Expand Down Expand Up @@ -30449,9 +30449,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
Expand Down Expand Up @@ -31714,9 +31714,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
Expand Down
55 changes: 35 additions & 20 deletions internal/compatibility/generated/node-v24-backlog.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"tierItemCount": 3472,
"tiers": {
"static": {
"audit-partial": 1058,
"verify-gap": 2287,
"audit-partial": 1061,
"verify-gap": 2284,
"replace-refusal": 40,
"classify": 2,
"implement": 85
Expand Down Expand Up @@ -26214,12 +26214,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:fs",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:fs.fsPromises.cp",
"tests": [
"tests/corpus/5567-fs-cpsync.ts",
"tests/corpus/5568-fs-cp-errors.js",
"tests/corpus/5569-fs-cp-symlinks.ts",
"tests/corpus/5570-fs-cp-async.ts"
]
}
}
},
Expand Down Expand Up @@ -27168,12 +27173,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:fs",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:fs.fs.cp",
"tests": [
"tests/corpus/5567-fs-cpsync.ts",
"tests/corpus/5568-fs-cp-errors.js",
"tests/corpus/5569-fs-cp-symlinks.ts",
"tests/corpus/5570-fs-cp-async.ts"
]
}
}
},
Expand Down Expand Up @@ -28700,12 +28710,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:fs",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:fs.fs.cpSync",
"tests": [
"tests/corpus/5567-fs-cpsync.ts",
"tests/corpus/5568-fs-cp-errors.js",
"tests/corpus/5569-fs-cp-symlinks.ts",
"tests/corpus/5570-fs-cp-async.ts"
]
}
}
},
Expand Down
38 changes: 30 additions & 8 deletions internal/compatibility/generated/node-v24-internal.json
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,9 @@
"apiEntries": 280,
"static": {
"supported": 7,
"partial": 195,
"partial": 198,
"refused": 0,
"not-implemented": 78,
"not-implemented": 75,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
Expand Down Expand Up @@ -44021,6 +44021,10 @@
"tests/corpus/5548-fs-callback-arguments.cjs",
"tests/corpus/5549-fs-callback-option-checks.cjs",
"tests/corpus/5554-fs-streams-typed.ts",
"tests/corpus/5567-fs-cpsync.ts",
"tests/corpus/5568-fs-cp-errors.js",
"tests/corpus/5569-fs-cp-symlinks.ts",
"tests/corpus/5570-fs-cp-async.ts",
"tests/corpus/fs-native-realpath.ts",
"tests/corpus/node24-fs-links-callbacks.cjs",
"tests/corpus/node24-fs-links-errors.cjs",
Expand Down Expand Up @@ -45067,8 +45071,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:fs"
"status": "partial",
"evidence": "compiler-feature:fs.fsPromises.cp",
"tests": [
"tests/corpus/5567-fs-cpsync.ts",
"tests/corpus/5568-fs-cp-errors.js",
"tests/corpus/5569-fs-cp-symlinks.ts",
"tests/corpus/5570-fs-cp-async.ts"
]
},
"anchorSource": "exact"
},
Expand Down Expand Up @@ -46027,8 +46037,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:fs"
"status": "partial",
"evidence": "compiler-feature:fs.fs.cp",
"tests": [
"tests/corpus/5567-fs-cpsync.ts",
"tests/corpus/5568-fs-cp-errors.js",
"tests/corpus/5569-fs-cp-symlinks.ts",
"tests/corpus/5570-fs-cp-async.ts"
]
},
"anchorSource": "exact"
},
Expand Down Expand Up @@ -47655,8 +47671,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:fs"
"status": "partial",
"evidence": "compiler-feature:fs.fs.cpSync",
"tests": [
"tests/corpus/5567-fs-cpsync.ts",
"tests/corpus/5568-fs-cp-errors.js",
"tests/corpus/5569-fs-cp-symlinks.ts",
"tests/corpus/5570-fs-cp-async.ts"
]
},
"anchorSource": "exact"
},
Expand Down
1 change: 1 addition & 0 deletions internal/compatibility/static-support.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@
{ "chapter": "child_process", "signatures": ["`stdin` Type: {stream.Writable|null|undefined}"], "status": "partial", "evidence": ["tests/corpus/2893-child-stdin-duplex.ts", "tests/corpus/2894-child-stdin-backpressure.ts", "tests/corpus/2895-child-stdin-write-after-end.ts", "tests/corpus/2896-child-stdin-write-after-finish.ts", "tests/corpus/2897-child-stdin-destroy.ts", "tests/corpus/2898-child-stdin-spawn-failure.ts"] },
{ "chapter": "fs", "signatures": ["`fsPromises.lstat(path[, options])`"], "status": "partial", "evidence": ["tests/corpus/2941-fsp-lstat-identity.ts"] },
{ "chapter": "fs", "signatures": ["`fs.globSync(pattern[, options])`", "`fs.glob(pattern[, options], callback)`", "`fsPromises.glob(pattern[, options])`"], "status": "partial", "evidence": ["tests/corpus/5448-fs-glob-patterns.ts", "tests/corpus/5449-fs-glob-options.ts", "tests/corpus/5450-fs-glob-validation.js", "tests/corpus/5451-fs-promises-glob.ts", "tests/corpus/5452-fs-glob-callback.ts", "tests/fixtures/node-types/fs-glob.ts"] },
{ "chapter": "fs", "signatures": ["`fs.cpSync(src, dest[, options])`", "`fs.cp(src, dest[, options], callback)`", "`fsPromises.cp(src, dest[, options])`"], "status": "partial", "evidence": ["tests/corpus/5567-fs-cpsync.ts", "tests/corpus/5568-fs-cp-errors.js", "tests/corpus/5569-fs-cp-symlinks.ts", "tests/corpus/5570-fs-cp-async.ts"] },
{ "chapter": "process", "signatures": ["`process.send(message[, sendHandle[, options]][, callback])`", "`process.disconnect()`", "`connected` Type: {boolean}", "Event: `'message'`", "Event: `'disconnect'`"], "status": "partial", "evidence": ["tests/corpus/2946-child-fork-ipc/main.ts"] },
{ "chapter": "fs", "symbols": ["fs.Stats", "stats.isBlockDevice", "stats.isCharacterDevice", "stats.isDirectory", "stats.isFIFO", "stats.isFile", "stats.isSocket", "stats.isSymbolicLink"], "status": "partial", "evidence": ["tests/corpus/5541-fs-stats-fields.cjs"] },
{ "chapter": "fs", "parent": "Class: `fs.Stats`", "signatures": ["`mode` Type: {number|bigint}", "`nlink` Type: {number|bigint}", "`uid` Type: {number|bigint}", "`gid` Type: {number|bigint}", "`rdev` Type: {number|bigint}", "`size` Type: {number|bigint}", "`blksize` Type: {number|bigint}", "`blocks` Type: {number|bigint}", "`atimeMs` Type: {number|bigint}", "`mtimeMs` Type: {number|bigint}", "`birthtimeMs` Type: {number|bigint}", "`atimeNs` Type: {bigint}", "`mtimeNs` Type: {bigint}", "`ctimeNs` Type: {bigint}", "`birthtimeNs` Type: {bigint}", "`atime` Type: {Date}", "`mtime` Type: {Date}", "`ctime` Type: {Date}", "`birthtime` Type: {Date}"], "status": "partial", "evidence": ["tests/corpus/5541-fs-stats-fields.cjs"] },
Expand Down
47 changes: 47 additions & 0 deletions packages/compiler/ambient/scriptc-node-fallback.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,45 @@ declare module "node:fs" {
path: string | Uint8Array | URL,
type?: string | null,
): void;
/* fs.cpSync / fs.cp: copies a file, symlink or (with recursive) a
* directory tree. filter decides per path; the callback and promise
* forms complete the copy before reporting. */
export interface CopySyncOptions {
dereference?: boolean;
errorOnExist?: boolean;
filter?: (source: string, destination: string) => boolean;
force?: boolean;
mode?: number;
preserveTimestamps?: boolean;
recursive?: boolean;
verbatimSymlinks?: boolean;
}
export interface CopyOptions {
dereference?: boolean;
errorOnExist?: boolean;
filter?: (source: string, destination: string) => boolean | Promise<boolean>;
force?: boolean;
mode?: number;
preserveTimestamps?: boolean;
recursive?: boolean;
verbatimSymlinks?: boolean;
}
export function cpSync(
source: string | URL,
destination: string | URL,
options?: CopySyncOptions,
): void;
export function cp(
source: string | URL,
destination: string | URL,
callback: (err: NodeJS.ErrnoException | null) => void,
): void;
export function cp(
source: string | URL,
destination: string | URL,
options: CopyOptions,
callback: (err: NodeJS.ErrnoException | null) => void,
): void;
export function symlink(
target: string | Uint8Array | URL,
path: string | Uint8Array | URL,
Expand Down Expand Up @@ -1441,6 +1480,9 @@ declare module "node:fs" {
readonly O_NONBLOCK: number;
readonly O_TRUNC: number;
readonly O_APPEND: number;
readonly COPYFILE_EXCL: number;
readonly COPYFILE_FICLONE: number;
readonly COPYFILE_FICLONE_FORCE: number;
};
/* A stat(2) snapshot (statSync follows symlinks, lstatSync does not —
* Node's split) — immutable; the supported surface is exactly these
Expand Down Expand Up @@ -1789,6 +1831,11 @@ declare module "fs/promises" {
path: string | Uint8Array | URL,
type?: string | null,
): Promise<void>;
export function cp(
source: string | URL,
destination: string | URL,
options?: import("node:fs").CopyOptions,
): Promise<void>;
export function readlink(
path: string | Uint8Array | URL,
options: "buffer" | { encoding: "buffer" },
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler/src/backend/llvm/lib-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"fs.writevSync": "scr_fs_writev_sync",
"fs.linkSync": "scr_fs_link_checked",
"fs.symlinkSync": "scr_fs_symlink_checked",
"fs.cpSync": "scr_fs_cp_sync",
"fs.readlinkSync": "scr_fs_readlink_dyn",
"fs.statfsSync": "scr_fs_statfs_checked",
"fs.readlinkSyncStr": "scr_fs_readlink_str",
Expand Down Expand Up @@ -712,6 +713,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"fsp.rmdir": "scr_fsp_rmdir",
"fsp.link": "scr_fsp_link",
"fsp.symlink": "scr_fsp_symlink",
"fsp.cp": "scr_fsp_cp",
"fsp.readlink": "scr_fsp_readlink_dyn",
"fsp.statfs": "scr_fsp_statfs",
"fsp.readlinkStr": "scr_fsp_readlink_str",
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/backend/native/runtime-inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const EXECUTABLE_RUNTIME_SOURCES = [
"scr_array.c",
"scr_bytes.c",
"scr_bytes_io.c",
"scr_fs_cp.c",
"scr_map.c",
"scr_closure.c",
"scr_ffi.c",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ export function lowerFsLinkOrStatfsCall(
return { kind: "libCall", fn: fn.fn, args, type: fn.result, loc };
}

/** fs.cpSync / fs/promises cp: src, dest and the optional options cross
* as dyn values (an omitted argument is undefined); scr_fs_cp_begin
* validates them in Node's order. */
export function lowerFsCpCall(
lowerer: Lowerer,
expr: ts.CallExpression,
fn: BuiltinModuleFn,
loc: SrcLoc,
name: string,
): IrExpr {
if (expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement))
lowerer.noLowering(`${name} with this argument shape`, expr);
const args = [0, 1, 2].map((index) =>
expr.arguments[index]
? lowerer.lowerExprExpecting(expr.arguments[index]!, DYN)
: dynUndefinedExpr(loc),
);
return { kind: "libCall", fn: fn.fn, args, type: fn.result, loc };
}

export function lowerFsTimestampCall(
lowerer: Lowerer,
expr: ts.CallExpression,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { lowerDiagnosticsChannelCall } from "./async-context.js";
import { lowerTimersPromisesCall } from "./timers.js";
import {
lowerFsCallbackCall,
lowerFsCpCall,
lowerFsLinkOrStatfsCall,
lowerFsTimestampCall,
lowerFsVectorOrTruncateCall,
Expand Down Expand Up @@ -72,6 +73,11 @@ export function lowerBuiltinModuleCall(
if (fn.fn === "fs.callbackCall" && bi.member !== "rename") {
return lowerFsCallbackCall(lowerer, expr, bi, loc);
}
if (
(bi.module === "fs" && bi.member === "cpSync") ||
(bi.module === "fs/promises" && bi.member === "cp")
)
return lowerFsCpCall(lowerer, expr, fn, loc, name);
if (
(bi.module === "fs" &&
["linkSync", "symlinkSync", "readlinkSync", "statfsSync"].includes(bi.member)) ||
Expand Down
4 changes: 4 additions & 0 deletions packages/compiler/src/frontend/lowering/lower-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,10 @@ export function registerBuiltinErrorClasses(lowerer: Lowerer): void {
{ name: "%systemCall", type: STRING },
{ name: "%systemPath", type: STRING },
{ name: "%systemDest", type: STRING },
{ name: "%systemMessage", type: STRING },
{ name: "%systemCode", type: STRING },
{ name: "%nativePlain", type: BOOL },
{ name: "%errnoOrder", type: BOOL },
],
loc,
},
Expand Down
Loading
Loading