mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Support fs.promises.rm options
fs.promises.rm(path, options) accepts the { recursive, force, maxRetries,
retryDelay } literal options that rmSync accepts. It runs the same removal
and rejects with its errors.
Removing a directory without recursive now throws Node's ERR_FS_EISDIR
SystemError from rmSync and fs.promises.rm. A retryDelay outside int32 >= 0
or a maxRetries outside uint32 throws ERR_OUT_OF_RANGE. rmSync checks these
options before the path, and fs.promises.rm checks the path first, as Node
does.
Fixes #1615
Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
This commit is contained in:
@@ -2128,6 +2128,10 @@ declare module "fs/promises" {
|
||||
options: import("node:fs").GlobOptionsWithFileTypes,
|
||||
): AsyncIterableIterator<import("node:fs").Dirent>;
|
||||
export function rm(path: string): Promise<void>;
|
||||
export function rm(
|
||||
path: string,
|
||||
options: { recursive?: boolean; force?: boolean; maxRetries?: number; retryDelay?: number },
|
||||
): Promise<void>;
|
||||
export function stat(path: string): Promise<import("node:fs").Stats>;
|
||||
export function statfs(
|
||||
path: string | Uint8Array | URL,
|
||||
|
||||
@@ -793,6 +793,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"fsp.rename": "scr_fsp_rename",
|
||||
"fsp.readdir": "scr_fsp_readdir",
|
||||
"fsp.rm": "scr_fsp_rm",
|
||||
"fsp.rmOpts": "scr_fsp_rm_opts",
|
||||
"fsp.rmRetry": "scr_fsp_rm_opts_retry",
|
||||
"fsp.stat": "scr_fsp_stat",
|
||||
"fsp.realpath": "scr_fsp_realpath",
|
||||
"fsp.lstat": "scr_fsp_lstat",
|
||||
|
||||
@@ -863,15 +863,22 @@ export function lowerFsPromisesMkdirCall(
|
||||
: { kind: "libCall", fn: "fsp.mkdirRecursive", args: [path], type, loc };
|
||||
}
|
||||
|
||||
// rmSync(p, options): literal { recursive?, force? } booleans — the
|
||||
// cleanup shape rmSync(dir, { recursive: true, force: true }) —
|
||||
// plus the maxRetries/retryDelay numbers (the tmpdir-harness shape
|
||||
// rmSync(p, options) and fs.promises.rm(p, options): literal
|
||||
// { recursive?, force? } booleans — the cleanup shape
|
||||
// rmSync(dir, { recursive: true, force: true }) — plus the
|
||||
// maxRetries/retryDelay numbers (the tmpdir-harness shape
|
||||
// rmSync(p, { maxRetries: 3, recursive: true, force: true })): those
|
||||
// lower as ordinary number expressions into the retry libCall, whose
|
||||
// runtime implements Node's linear-backoff retry on
|
||||
// EBUSY/EMFILE/ENFILE/ENOTEMPTY/EPERM. The retry-free shape keeps the
|
||||
// historical rmOptsSync lowering unchanged.
|
||||
export function lowerFsRemoveCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
|
||||
// historical rmOptsSync lowering unchanged. The promise form runs the same
|
||||
// removal and rejects with its error.
|
||||
export function lowerFsRemoveCall(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.CallExpression,
|
||||
loc: SrcLoc,
|
||||
promise: boolean,
|
||||
): IrExpr {
|
||||
const opts = literalBoolOptions(
|
||||
expr.arguments.nodes[1]!,
|
||||
["recursive", "force"],
|
||||
@@ -879,7 +886,7 @@ export function lowerFsRemoveCall(lowerer: Lowerer, expr: ts.CallExpression, loc
|
||||
);
|
||||
if (opts === null) {
|
||||
lowerer.noLowering(
|
||||
"rmSync with an options argument beyond { recursive, force, maxRetries, retryDelay }",
|
||||
`${promise ? "fs.promises.rm" : "rmSync"} with an options argument beyond { recursive, force, maxRetries, retryDelay }`,
|
||||
expr.arguments.nodes[1]!,
|
||||
"the recursive/force flags must be boolean literals; maxRetries/retryDelay are numbers; other options have no lowering",
|
||||
);
|
||||
@@ -888,12 +895,13 @@ export function lowerFsRemoveCall(lowerer: Lowerer, expr: ts.CallExpression, loc
|
||||
|
||||
const recursive = boolLit(opts.bools["recursive"] === true, loc);
|
||||
const force = boolLit(opts.bools["force"] === true, loc);
|
||||
const type: IrType = promise ? { kind: "promise", inner: VOID } : VOID;
|
||||
if (opts.exprs["maxRetries"] === undefined && opts.exprs["retryDelay"] === undefined) {
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "fs.rmOptsSync",
|
||||
fn: promise ? "fsp.rmOpts" : "fs.rmOptsSync",
|
||||
args: [path, recursive, force],
|
||||
type: VOID,
|
||||
type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
@@ -904,7 +912,7 @@ export function lowerFsRemoveCall(lowerer: Lowerer, expr: ts.CallExpression, loc
|
||||
node ? lowerer.lowerExprExpecting(node, F64) : { kind: "numLit", value: dflt, type: F64, loc };
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "fs.rmRetrySync",
|
||||
fn: promise ? "fsp.rmRetry" : "fs.rmRetrySync",
|
||||
args: [
|
||||
path,
|
||||
recursive,
|
||||
@@ -912,7 +920,7 @@ export function lowerFsRemoveCall(lowerer: Lowerer, expr: ts.CallExpression, loc
|
||||
num(opts.exprs["maxRetries"], 0),
|
||||
num(opts.exprs["retryDelay"], 100),
|
||||
],
|
||||
type: VOID,
|
||||
type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -292,7 +292,10 @@ export function lowerBuiltinModuleCall(
|
||||
return lowerFsPromisesMkdirCall(lowerer, expr, loc);
|
||||
}
|
||||
if (bi.module === "fs" && bi.member === "rmSync" && expr.arguments.nodes.length === 2) {
|
||||
return lowerFsRemoveCall(lowerer, expr, loc);
|
||||
return lowerFsRemoveCall(lowerer, expr, loc, false);
|
||||
}
|
||||
if (bi.module === "fs/promises" && bi.member === "rm" && expr.arguments.nodes.length === 2) {
|
||||
return lowerFsRemoveCall(lowerer, expr, loc, true);
|
||||
}
|
||||
// accessSync(p, mode?) and truncateSync(p, len?), with their promise
|
||||
// spellings: an omitted mode is Node's F_OK (0) and an omitted len is 0.
|
||||
|
||||
@@ -2104,6 +2104,8 @@ export const BUILTIN_MODULE_FN_ALIASES: Record<
|
||||
glob: ["fsp.globTypes"],
|
||||
// The Uint8Array/Buffer-data form.
|
||||
appendFile: ["fsp.appendFileBytes"],
|
||||
// The options forms.
|
||||
rm: ["fsp.rmOpts", "fsp.rmRetry"],
|
||||
},
|
||||
crypto: {
|
||||
// The composed randomBytes(n).toString(enc) chain keeps its one-libCall
|
||||
|
||||
@@ -3139,6 +3139,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
* scandir failure becomes its rejection. */
|
||||
"fsp.readdirTypes": { argTypes: [STRING], result: { kind: "promise", inner: VOID } },
|
||||
"fsp.rm": { argTypes: [STRING], result: { kind: "promise", inner: VOID } },
|
||||
/** `fs.promises.rm(path, options)`: fs.rmOptsSync and fs.rmRetrySync
|
||||
* behind a settled promise; their errors become its rejection. */
|
||||
"fsp.rmOpts": { argTypes: [STRING, BOOL, BOOL], result: { kind: "promise", inner: VOID } },
|
||||
"fsp.rmRetry": {
|
||||
argTypes: [STRING, BOOL, BOOL, F64, F64],
|
||||
result: { kind: "promise", inner: VOID },
|
||||
},
|
||||
"fsp.stat": { argTypes: [STRING], result: { kind: "promise", inner: STATS_T } },
|
||||
"fsp.realpath": { argTypes: [STRING], result: { kind: "promise", inner: STRING } },
|
||||
"fsp.lstat": { argTypes: [STRING], result: { kind: "promise", inner: STATS_T } },
|
||||
@@ -4622,8 +4629,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
* return value has no lowering (statement position only, frontend-
|
||||
* fenced). rmOptsSync is rmSync with (recursive, force) bools: force
|
||||
* swallows ENOENT, recursive removes trees post-order, a directory
|
||||
* without recursive throws the EISDIR-worded error (divergence 13's
|
||||
* wording note). mkdtempSync appends the six X's and returns the
|
||||
* without recursive throws Node's ERR_FS_EISDIR SystemError. mkdtempSync appends the six X's and returns the
|
||||
* created path (+1). accessSync takes the F_OK/R_OK/W_OK/X_OK bits as
|
||||
* one f64 (the frontend bakes fs.constants.* as literals and completes
|
||||
* an omitted mode to 0). readFdSync/readFdSyncBytes are the
|
||||
@@ -4634,7 +4640,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
/** rmOptsSync's maxRetries/retryDelay form (path, recursive, force,
|
||||
* maxRetries, retryDelay): Node's linear-backoff retry on
|
||||
* EBUSY/EMFILE/ENFILE/ENOTEMPTY/EPERM — the tmpdir-harness shape
|
||||
* rmSync(p, { maxRetries: 3, recursive: true, force: true }). */
|
||||
* rmSync(p, { maxRetries: 3, recursive: true, force: true }). Throws
|
||||
* ERR_OUT_OF_RANGE for a retryDelay outside int32 >= 0 or a maxRetries
|
||||
* outside uint32, before checking the path. */
|
||||
"fs.rmRetrySync": { argTypes: [STRING, BOOL, BOOL, F64, F64], result: VOID },
|
||||
"fs.mkdtempSync": { argTypes: [STRING], result: STRING },
|
||||
"fs.accessSync": { argTypes: [STRING, F64], result: VOID },
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6930-fs-promises-rm-options.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -2900,6 +2900,19 @@ ScrPromise *scr_fsp_rm(ScrStr *path) {
|
||||
return scr_promise_settled_void();
|
||||
}
|
||||
|
||||
ScrPromise *scr_fsp_rm_opts(ScrStr *path, bool recursive, bool force) {
|
||||
scr_fs_rm_opts(path, recursive, force);
|
||||
return scr_promise_settled_void();
|
||||
}
|
||||
|
||||
/* fs.promises.rm validates the path before its options, unlike rmSync. */
|
||||
ScrPromise *scr_fsp_rm_opts_retry(ScrStr *path, bool recursive, bool force, double max_retries,
|
||||
double retry_delay) {
|
||||
if (scr_fs_path_ok(path, "path") && scr_fs_rm_retry_options_ok(max_retries, retry_delay))
|
||||
scr_fs_rm_opts_retry(path, recursive, force, max_retries, retry_delay);
|
||||
return scr_promise_settled_void();
|
||||
}
|
||||
|
||||
ScrPromise *scr_fsp_stat(ScrStr *path) {
|
||||
ScrStats *st = scr_fs_stat(path);
|
||||
return scr_promise_settled_ref(st, &scr_stats_retain_v, &scr_stats_release_v, NULL);
|
||||
|
||||
@@ -4596,6 +4596,13 @@ void scr_fs_rename(ScrStr *oldpath, ScrStr *newpath) {
|
||||
|
||||
static int scr_rm_unlink(const char *path, size_t len);
|
||||
|
||||
/* Node's ERR_FS_EISDIR for rm on a directory without `recursive`. */
|
||||
static void scr_fs_rm_throw_eisdir(const ScrStr *path) {
|
||||
ScrStr *message = scr_str_new("is a directory", 14);
|
||||
scr_fs_throw_system_error("ERR_FS_EISDIR", "Path is a directory", EISDIR, "rm", message, path);
|
||||
scr_str_release(message);
|
||||
}
|
||||
|
||||
void scr_fs_rm(ScrStr *path) {
|
||||
if (!scr_fs_path_ok(path, "path")) return;
|
||||
#ifdef _WIN32
|
||||
@@ -4603,16 +4610,15 @@ void scr_fs_rm(ScrStr *path) {
|
||||
if (link != 0) { if (link < 0) scr_fs_throw(errno, "unlink", path); return; }
|
||||
#endif
|
||||
/* Node's rmSync: lstat first (a missing path reports the lstat syscall),
|
||||
* refuse directories (Node requires `recursive`, which the scriptc
|
||||
* surface doesn't declare — the message wording diverges from Node's
|
||||
* ERR_FS_EISDIR, see SEMANTICS.md), then unlink. */
|
||||
* refuse directories (ERR_FS_EISDIR: removing one requires `recursive`),
|
||||
* then unlink. */
|
||||
struct stat st;
|
||||
if (lstat(path->data, &st) != 0) {
|
||||
scr_fs_throw(errno, "lstat", path);
|
||||
return;
|
||||
}
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
scr_fs_throw(EISDIR, "rm", path);
|
||||
scr_fs_rm_throw_eisdir(path);
|
||||
return;
|
||||
}
|
||||
if (scr_rm_unlink(path->data, path->len) != 0) scr_fs_throw(errno, "unlink", path);
|
||||
@@ -4766,6 +4772,13 @@ typedef struct {
|
||||
ScrStr *path;
|
||||
} ScrRmFail;
|
||||
|
||||
/* Throws the recorded failure and releases its path. */
|
||||
static void scr_rm_fail_throw(ScrRmFail *f) {
|
||||
if (f->err == EISDIR && strcmp(f->op, "rm") == 0) scr_fs_rm_throw_eisdir(f->path);
|
||||
else scr_fs_throw(f->err, f->op, f->path);
|
||||
scr_str_release(f->path);
|
||||
}
|
||||
|
||||
static void scr_rm_fail_set(ScrRmFail *f, int err, const char *op, const char *path, size_t len) {
|
||||
if (f->err != 0) return; /* first failure wins, exactly like the old unwind */
|
||||
f->err = err;
|
||||
@@ -4861,8 +4874,6 @@ static void scr_fs_rm_attempt(ScrStr *path, bool recursive, bool force, ScrRmFai
|
||||
}
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (!recursive) {
|
||||
/* Node throws ERR_FS_EISDIR here; the EISDIR-prefixed wording is
|
||||
* divergence 13's documented difference. */
|
||||
scr_rm_fail_set(f, EISDIR, "rm", path->data, path->len);
|
||||
return;
|
||||
}
|
||||
@@ -4876,18 +4887,24 @@ void scr_fs_rm_opts(ScrStr *path, bool recursive, bool force) {
|
||||
if (!scr_fs_path_ok(path, "path")) return;
|
||||
ScrRmFail f = {0, NULL, NULL};
|
||||
scr_fs_rm_attempt(path, recursive, force, &f);
|
||||
if (f.err != 0) {
|
||||
scr_fs_throw(f.err, f.op, f.path);
|
||||
scr_str_release(f.path);
|
||||
}
|
||||
if (f.err != 0) scr_rm_fail_throw(&f);
|
||||
}
|
||||
|
||||
/* Node's validateRmdirOptions range checks: retryDelay is an int32 >= 0,
|
||||
* then maxRetries is a uint32 (ERR_OUT_OF_RANGE). */
|
||||
bool scr_fs_rm_retry_options_ok(double max_retries, double retry_delay) {
|
||||
return scr_bytes_validate_off("options.retryDelay", retry_delay, 2147483647.0) &&
|
||||
scr_bytes_validate_off("options.maxRetries", max_retries, 4294967295.0);
|
||||
}
|
||||
|
||||
/* rmSync(p, { recursive, force, maxRetries, retryDelay }): Node retries
|
||||
* the operation on EBUSY/EMFILE/ENFILE/ENOTEMPTY/EPERM up to maxRetries
|
||||
* times, waiting retryDelay ms LONGER on each try (linear backoff — the
|
||||
* fs.rmSync documented semantics). Everything else throws immediately
|
||||
* with the failing path, exactly like the plain options form. */
|
||||
* with the failing path, exactly like the plain options form. rmSync
|
||||
* validates the options before the path. */
|
||||
void scr_fs_rm_opts_retry(ScrStr *path, bool recursive, bool force, double max_retries, double retry_delay) {
|
||||
if (!scr_fs_rm_retry_options_ok(max_retries, retry_delay)) return;
|
||||
if (!scr_fs_path_ok(path, "path")) return;
|
||||
long tries = max_retries > 0 ? (long)max_retries : 0;
|
||||
double delay_ms = retry_delay > 0 ? retry_delay : 0;
|
||||
@@ -4912,8 +4929,7 @@ void scr_fs_rm_opts_retry(ScrStr *path, bool recursive, bool force, double max_r
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
}
|
||||
scr_fs_throw(f.err, f.op, f.path);
|
||||
scr_str_release(f.path);
|
||||
scr_rm_fail_throw(&f);
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(__wasi__)
|
||||
|
||||
@@ -306,6 +306,7 @@ void scr_fs_rm_opts(ScrStr *path, bool recursive, bool force);
|
||||
* EPERM failures up to maxRetries times with Node's linear backoff
|
||||
* (retryDelay ms longer per try); everything else throws immediately. */
|
||||
void scr_fs_rm_opts_retry(ScrStr *path, bool recursive, bool force, double max_retries, double retry_delay);
|
||||
bool scr_fs_rm_retry_options_ok(double max_retries, double retry_delay);
|
||||
ScrStr *scr_fs_mkdtemp(ScrStr *prefix);
|
||||
ScrStr *scr_fs_mkdtemp_async(ScrStr *prefix); /* the callback and promise error path */
|
||||
void scr_fs_access(ScrStr *path, double mode);
|
||||
@@ -493,6 +494,9 @@ ScrPromise *scr_fsp_utimes(const ScrDyn *path, const ScrDyn *atime, const ScrDyn
|
||||
ScrPromise *scr_fsp_lutimes(const ScrDyn *path, const ScrDyn *atime, const ScrDyn *mtime);
|
||||
ScrPromise *scr_fsp_readdir(ScrStr *path);
|
||||
ScrPromise *scr_fsp_rm(ScrStr *path);
|
||||
ScrPromise *scr_fsp_rm_opts(ScrStr *path, bool recursive, bool force);
|
||||
ScrPromise *scr_fsp_rm_opts_retry(ScrStr *path, bool recursive, bool force, double max_retries,
|
||||
double retry_delay);
|
||||
ScrPromise *scr_fsp_stat(ScrStr *path);
|
||||
ScrPromise *scr_fsp_realpath(ScrStr *path);
|
||||
ScrPromise *scr_fsp_lstat(ScrStr *path);
|
||||
|
||||
@@ -62,7 +62,8 @@ afterAll(async () => {
|
||||
// fs failure through scr_exc_print_uncaught. fs failures are Error
|
||||
// INSTANCES whose messages carry Node's exact shape ("<ERRNO>: <text>,
|
||||
// <syscall> '<path>'"), so the uncaught line renders "Error: <message>" —
|
||||
// the same first line Node prints for an uncaught fs error. Typed catches
|
||||
// the same first line Node prints for an uncaught fs error (rm on a
|
||||
// directory is Node's ERR_FS_EISDIR SystemError instead). Typed catches
|
||||
// observe the message itself (`e.message`, corpus-covered differentially);
|
||||
// these lines pin OUR uncaught rendering.
|
||||
test("process + fs library: checks pass, error messages match Node's shape", async () => {
|
||||
@@ -76,7 +77,7 @@ test("process + fs library: checks pass, error messages match Node's shape", asy
|
||||
`Uncaught Error: ENOENT: no such file or directory, scandir '${scratch}/missing.txt'`,
|
||||
`Uncaught Error: EEXIST: file already exists, mkdir '${scratch}/sub'`,
|
||||
`Uncaught Error: ENOENT: no such file or directory, mkdir '${scratch}/nope/deep'`,
|
||||
`Uncaught Error: EISDIR: illegal operation on a directory, rm '${scratch}/sub'`,
|
||||
`Uncaught SystemError [ERR_FS_EISDIR]: Path is a directory: rm returned EISDIR (is a directory) ${scratch}/sub`,
|
||||
``,
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// fs.promises.rm with an options object: { recursive, force } and the
|
||||
// maxRetries/retryDelay pair, as rmSync accepts them. Failures reject with
|
||||
// Node's errors: a missing path without force, a directory without
|
||||
// recursive (ERR_FS_EISDIR), and an out-of-range maxRetries or retryDelay.
|
||||
// rmSync throws the same errors, checking its options before the path,
|
||||
// while fs.promises.rm checks the path first.
|
||||
import * as fs from "node:fs";
|
||||
import * as fsp from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fsp-rm-"));
|
||||
|
||||
function report(label: string, error: unknown): void {
|
||||
if (error instanceof Error) {
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
console.log(label, error.name, code, error.message.split(dir).join("<dir>"));
|
||||
} else {
|
||||
console.log(label, "non-error");
|
||||
}
|
||||
}
|
||||
|
||||
async function settle(label: string, run: () => Promise<void>): Promise<void> {
|
||||
try {
|
||||
await run();
|
||||
console.log(label, "fulfilled");
|
||||
} catch (error) {
|
||||
report(label, error);
|
||||
}
|
||||
}
|
||||
|
||||
function attempt(label: string, run: () => void): void {
|
||||
try {
|
||||
run();
|
||||
console.log(label, "returned");
|
||||
} catch (error) {
|
||||
report(label, error);
|
||||
}
|
||||
}
|
||||
|
||||
function tree(root: string): void {
|
||||
fs.mkdirSync(path.join(root, "a", "b"), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, "a", "b", "leaf.txt"), "leaf");
|
||||
fs.writeFileSync(path.join(root, "top.txt"), "top");
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const missing = path.join(dir, "missing");
|
||||
await settle("missing force", () => fsp.rm(missing, { recursive: true, force: true }));
|
||||
await settle("missing force only", () => fsp.rm(missing, { force: true }));
|
||||
await settle("missing", () => fsp.rm(missing, { recursive: true }));
|
||||
await settle("missing empty options", () => fsp.rm(missing, {}));
|
||||
|
||||
const file = path.join(dir, "file.txt");
|
||||
fs.writeFileSync(file, "x");
|
||||
await settle("file", () => fsp.rm(file, { force: false }));
|
||||
console.log("file exists", fs.existsSync(file));
|
||||
|
||||
const nested = path.join(dir, "nested");
|
||||
tree(nested);
|
||||
await settle("dir without recursive", () => fsp.rm(nested, { force: true }));
|
||||
console.log("dir exists", fs.existsSync(nested));
|
||||
await settle("dir recursive", () => fsp.rm(nested, { recursive: true }));
|
||||
console.log("dir exists", fs.existsSync(nested));
|
||||
tree(nested);
|
||||
await settle("fs.promises.rm", () => fs.promises.rm(nested, { recursive: true, force: true }));
|
||||
console.log("dir exists", fs.existsSync(nested));
|
||||
|
||||
const retried = path.join(dir, "retried");
|
||||
tree(retried);
|
||||
await settle("retry", () => fsp.rm(retried, { recursive: true, force: true, maxRetries: 2, retryDelay: 10 }));
|
||||
console.log("retried exists", fs.existsSync(retried));
|
||||
await settle("retry missing", () => fsp.rm(retried, { maxRetries: 1 }));
|
||||
await settle("negative retries", () => fsp.rm(retried, { force: true, maxRetries: -1 }));
|
||||
await settle("fractional delay", () => fsp.rm(retried, { force: true, retryDelay: 1.5 }));
|
||||
|
||||
await settle("null byte and range", () => fsp.rm("a\0b", { maxRetries: -1 }));
|
||||
await settle("too many retries", () => fsp.rm(retried, { force: true, maxRetries: 2 ** 32 }));
|
||||
await settle("long delay", () => fsp.rm(retried, { force: true, retryDelay: 2 ** 31 }));
|
||||
await settle("delay before retries", () => fsp.rm(retried, { maxRetries: -1, retryDelay: 0.5 }));
|
||||
|
||||
tree(nested);
|
||||
attempt("rmSync dir", () => fs.rmSync(nested));
|
||||
attempt("rmSync dir force", () => fs.rmSync(nested, { force: true }));
|
||||
attempt("rmSync null byte and range", () => fs.rmSync("a\0b", { maxRetries: -1 }));
|
||||
attempt("rmSync negative delay", () => fs.rmSync(nested, { recursive: true, retryDelay: -1 }));
|
||||
attempt("rmSync retry", () => fs.rmSync(nested, { recursive: true, maxRetries: 1 }));
|
||||
console.log("dir exists", fs.existsSync(nested));
|
||||
|
||||
// The promise is created before the await, and a rejection is caught
|
||||
// through .catch like any other promise.
|
||||
const pending = fsp.rm(missing, { recursive: false });
|
||||
await pending.catch((error: unknown) => report("caught", error));
|
||||
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
console.log("done", fs.existsSync(dir));
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 1965, "san": 46298 }
|
||||
Reference in New Issue
Block a user