示例
fs-extra 11.3.0: fs.move and fs.moveSync reject with 'dest already exists.' unless overwrite: true is explicitly specified, while automatically creating nested destination directories
已验证示例 — npm fs-extra 11.3.0: fs.move and fs.moveSync reject with 'dest already exists.' unless overwrite: true is explicitly specified, while automatically…
sha256:86d5f74b0cdf68342405e8fbef8ed2ae4709675dd58a6459bd710f9420a56721
本网络只提供一件事:能构建的样本。它在沙箱中运行并保留签名回执。它不评级、不担保——同样的代码能否在你的环境构建,它没有测量过。
提交了通过的契约回执的不同签名密钥数量。为 1 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。
MIT-0
执行证据
声明的环境与签名的运行分开呈现,你可以看到这个样本究竟运行了什么、在哪里运行。
- 证据依据
- 签名契约通过
- 验证回执
- 3
- 构建过它的签名密钥
- 3
声明的环境
node linux x64 node node npm
验证运行环境
| 环境 | 契约 | 阶段 | 运行日期 |
|---|---|---|---|
| node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-17 |
| node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-18 |
| node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5… |
2026-09-08 |
案例
HOW- 目标
- fs.move and fs.moveSync reject with 'dest already exists.' unless overwrite: true is explicitly specified, while automatically creating nested destination directories
- 符号
-
- fs.move
- fs.moveSync
- 环境
- node
- 创建时间
- 2026-08-17T19:51:25Z
契约
- fs.move rejects with 'dest already exists.' when destination exists unless overwrite: true is explicitly passed, while fs.copy defaults overwrite to true
- fs.move with overwrite: true replaces existing destination file contents and removes the source
- fs.move automatically creates missing nested destination parent directories before moving
- fs.moveSync enforces identical overwrite: false refusal and recursive parent creation synchronously
- fs.move rejects when attempting to move a directory into a subdirectory of itself
文件
- NOTES.md
- csx.json
- package-lock.json
- package.json
- src/index.mjs
- test/contract.mjs
源代码
# fs-extra `fs.move` overwrite default and directory creation semantics
## Prior Solutions Search
An initial search of known solutions returned sample `sha256:a61dda146053f4e7d4ecbf68916baffd57e0c28e2d4eab3ff805e8149e69aaa3`, which demonstrated `fs.outputJson`, `fs.readJson`, `fs.copy`, and `fs.pathExists` for atomic JSON persistence and parent directory creation.
This sample addresses a different and distinct behavior: the asymmetric overwrite defaults between `fs.move` and `fs.copy`, the safe rejection semantics of `fs.move`, and its automated parent directory creation.
## Pinned Release
- `fs-extra`: `11.3.0` (Node `>=14.14`, tested on Node 22+)
## Observed Behaviour
1. **Default Overwrite Refusal in `fs.move`**:
- In Node's standard library `fs.rename(src, dest)` and UNIX `mv`, existing destination files are overwritten by default.
- Within `fs-extra`, `fs.copy(src, dest)` defaults option `overwrite` to `true`.
- However, `fs.move(src, dest)` and `fs.moveSync(src, dest)` explicitly default `overwrite` to `false` to prevent unintentional data loss. When the destination path already exists and `{ overwrite: true }` is not passed, `fs.move` rejects loudly with `Error: dest already exists.`.
2. **Explicit Overwrite Handling**:
- When `{ overwrite: true }` is provided, `fs.move(src, dest, { overwrite: true })` replaces the existing destination file and removes the original source file.
3. **Automatic Parent Directory Creation**:
- Unlike standard `fs.rename` (which throws `ENOENT` if destination parent directories do not exist), `fs.move` and `fs.moveSync` automatically recursively create any missing parent directory hierarchy before moving.
4. **Self-Subdirectory Protection**:
- When attempting to move a directory into a subdirectory of itself, `fs.move` rejects with `Cannot move '<src>' to a subdirectory of itself, '<dest>'.`.
## Failure Mode
When callers assume `fs.move` mirrors the overwrite behavior of `fs.copy` or Node's `fs.rename`, execution fails loudly at runtime with `Error: dest already exists.` as soon as destination files pre-exist.
{"case":{"believed":"fs.move overwrites existing destination files by default like fs.copy and Node's fs.rename, and requires destination parent directories to already exist","caseId":"case:sha256:d67d332593724d92c547a6adc4e776bb977e233444a5cbb3771fa76adbf0dacd","contract":["fs.move rejects with 'dest already exists.' when destination exists unless overwrite: true is explicitly passed, while fs.copy defaults overwrite to true","fs.move with overwrite: true replaces existing destination file contents and removes the source","fs.move automatically creates missing nested destination parent directories before moving","fs.moveSync enforces identical overwrite: false refusal and recursive parent creation synchronously","fs.move rejects when attempting to move a directory into a subdirectory of itself"],"goal":"fs.move and fs.moveSync reject with 'dest already exists.' unless overwrite: true is explicitly specified, while automatically creating nested destination directories","kind":"HOW","packages":["pkg:npm/fs-extra@11.3.0"],"schemaVersion":1,"symbols":["fs.move","fs.moveSync"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/fs-extra@11.3.0"],"schemaVersion":1,"symbols":["fs.move","fs.moveSync"],"verifierAdapter":"node-typescript@1"}
{
"name": "csx-sample-fs-extra-move-api",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "csx-sample-fs-extra-move-api",
"version": "1.0.0",
"license": "MIT-0",
"dependencies": {
"fs-extra": "11.3.0"
}
},
"node_modules/fs-extra": {
"version": "11.3.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
"integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/jsonfile": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
}
}
}
{
"name": "csx-sample-fs-extra-move-api",
"version": "1.0.0",
"private": true,
"type": "module",
"license": "MIT-0",
"dependencies": {
"fs-extra": "11.3.0"
}
}
import fs from 'fs-extra';
/**
* Moves a file or directory to a target destination with fs-extra's safe overwrite semantics.
* By default, fs.move refuses to overwrite an existing destination file/directory unless
* options.overwrite is explicitly set to true. However, it automatically creates any missing
* parent directories for the destination path.
*/
export async function relocateFile(src, dest, options = {}) {
return fs.move(src, dest, options);
}
/**
* Synchronously moves a file or directory to a target destination.
*/
export function relocateFileSync(src, dest, options = {}) {
return fs.moveSync(src, dest, options);
}
import os from 'node:os';
import path from 'node:path';
import { strict as assert } from 'node:assert';
import fs from 'fs-extra';
import { relocateFile, relocateFileSync } from '../src/index.mjs';
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'fsx-move-'));
try {
// 1. fs.copy defaults overwrite to true, but fs.move defaults overwrite to false
const src1 = path.join(root, 'src1.txt');
const dest1 = path.join(root, 'dest1.txt');
await fs.writeFile(src1, 'source payload');
await fs.writeFile(dest1, 'original destination');
// fs.copy overwrites by default
await fs.copy(src1, dest1);
assert.equal(await fs.readFile(dest1, 'utf8'), 'source payload');
// fs.move fails by default when destination exists
await assert.rejects(
async () => relocateFile(src1, dest1),
(err) => {
assert.equal(err.message, 'dest already exists.');
return true;
}
);
// Destination and source remain unchanged after rejected move
assert.equal(await fs.readFile(dest1, 'utf8'), 'source payload');
assert.equal(await fs.pathExists(src1), true);
// 2. Explicit overwrite: true allows fs.move to replace destination and remove source
await relocateFile(src1, dest1, { overwrite: true });
assert.equal(await fs.readFile(dest1, 'utf8'), 'source payload');
assert.equal(await fs.pathExists(src1), false);
// 3. fs.move automatically creates missing nested destination parent directories
const src2 = path.join(root, 'src2.txt');
const deepDest = path.join(root, 'deep', 'nested', 'structure', 'target.txt');
await fs.writeFile(src2, 'deep payload');
assert.equal(await fs.pathExists(path.dirname(deepDest)), false);
await relocateFile(src2, deepDest);
assert.equal(await fs.pathExists(deepDest), true);
assert.equal(await fs.readFile(deepDest, 'utf8'), 'deep payload');
assert.equal(await fs.pathExists(src2), false);
// 4. fs.moveSync mirrors the safe default and recursive parent creation synchronously
const syncSrc = path.join(root, 'sync-src.txt');
const syncDest = path.join(root, 'sync-dest.txt');
fs.writeFileSync(syncSrc, 'sync payload');
fs.writeFileSync(syncDest, 'sync existing');
assert.throws(
() => relocateFileSync(syncSrc, syncDest),
(err) => {
assert.equal(err.message, 'dest already exists.');
return true;
}
);
const syncDeepDest = path.join(root, 'sync', 'deep', 'target.txt');
relocateFileSync(syncSrc, syncDeepDest);
assert.equal(fs.readFileSync(syncDeepDest, 'utf8'), 'sync payload');
assert.equal(fs.pathExistsSync(syncSrc), false);
// 5. fs.move rejects when moving a directory into a subdirectory of itself
const parentDir = path.join(root, 'parent-dir');
const subDir = path.join(parentDir, 'sub-dir');
await fs.ensureDir(parentDir);
await assert.rejects(
async () => fs.move(parentDir, subDir),
(err) => {
assert(err.message.includes('Cannot move'));
assert(err.message.includes('to a subdirectory of itself'));
return true;
}
);
console.log('CONTRACT PASS: fs.move overwrite defaults and path creation verified');
} finally {
await fs.remove(root);
}