Skip to content

Commit 3e5038d

Browse files
authored
feat(fs/unstable): add truncate and truncateSync (#6416)
1 parent b5b26d9 commit 3e5038d

File tree

5 files changed

+187
-0
lines changed

5 files changed

+187
-0
lines changed

_tools/check_docs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const ENTRY_POINTS = [
7474
"../fs/unstable_rename.ts",
7575
"../fs/unstable_stat.ts",
7676
"../fs/unstable_symlink.ts",
77+
"../fs/unstable_truncate.ts",
7778
"../fs/unstable_types.ts",
7879
"../html/mod.ts",
7980
"../html/unstable_is_valid_custom_element_name.ts",

_tools/node_test_runner/run_test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import "../../fs/unstable_real_path_test.ts";
5959
import "../../fs/unstable_rename_test.ts";
6060
import "../../fs/unstable_stat_test.ts";
6161
import "../../fs/unstable_symlink_test.ts";
62+
import "../../fs/unstable_truncate_test.ts";
6263
import "../../fs/unstable_lstat_test.ts";
6364
import "../../fs/unstable_chmod_test.ts";
6465

fs/deno.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"./unstable-rename": "./unstable_rename.ts",
2626
"./unstable-stat": "./unstable_stat.ts",
2727
"./unstable-symlink": "./unstable_symlink.ts",
28+
"./unstable-truncate": "./unstable_truncate.ts",
2829
"./unstable-types": "./unstable_types.ts",
2930
"./walk": "./walk.ts"
3031
}

fs/unstable_truncate.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2018-2025 the Deno authors. MIT license.
2+
3+
import { getNodeFs, isDeno } from "./_utils.ts";
4+
import { mapError } from "./_map_error.ts";
5+
6+
/**
7+
* Truncates (or extends) the specified file, to reach the specified `len`. If
8+
* `len` is not specified then the entire file contents are truncated.
9+
*
10+
* Requires `allow-write` permission.
11+
*
12+
* @example Truncate file to 0 bytes
13+
* ```ts ignore
14+
* import { truncate } from "@std/fs/unstable-truncate";
15+
* await truncate("my_file.txt");
16+
* ```
17+
*
18+
* @example Truncate part of a file
19+
* ```ts ignore
20+
* import { makeTempFile } from "@std/fs/unstable-make-temp-file";
21+
* import { readFile } from "@std/fs/unstable-read-file";
22+
* import { truncate } from "@std/fs/unstable-truncate";
23+
* import { writeTextFile } from "@std/fs/unstable-write-text-file";
24+
*
25+
* const file = await makeTempFile();
26+
* await writeTextFile(file, "Hello World");
27+
* await truncate(file, 7);
28+
* const data = await readFile(file);
29+
* console.log(new TextDecoder().decode(data)); // "Hello W"
30+
* ```
31+
*
32+
* @tags allow-write
33+
*
34+
* @param name The name/path to the file.
35+
* @param len An optional value that sets the new size of the file. Omitting this argument sets the file size to 0 bytes.
36+
*/
37+
export async function truncate(name: string, len?: number): Promise<void> {
38+
if (isDeno) {
39+
await Deno.truncate(name, len);
40+
} else {
41+
try {
42+
await getNodeFs().promises.truncate(name, len);
43+
} catch (error) {
44+
throw mapError(error);
45+
}
46+
}
47+
}
48+
49+
/**
50+
* Synchronously truncates (or extends) the specified file, to reach the
51+
* specified `len`. If `len` is not specified then the entire file contents are
52+
* truncated.
53+
*
54+
* Requires `allow-write` permission.
55+
*
56+
* @example Truncate file to 0 bytes
57+
* ```ts ignore
58+
* import { truncateSync } from "@std/fs/unstable-truncate";
59+
* truncateSync("my_file.txt");
60+
* ```
61+
*
62+
* @example Truncate part of a file
63+
* ```ts ignore
64+
* import { makeTempFileSync } from "@std/fs/unstable-make-temp-file";
65+
* import { readFileSync } from "@std/fs/unstable-read-file";
66+
* import { truncateSync } from "@std/fs/unstable-truncate";
67+
* import { writeFileSync } from "@std/fs/unstable-write-file";
68+
*
69+
* const file = makeTempFileSync();
70+
* writeFileSync(file, new TextEncoder().encode("Hello World"));
71+
* truncateSync(file, 7);
72+
* const data = readFileSync(file);
73+
* console.log(new TextDecoder().decode(data));
74+
* ```
75+
*
76+
* @tags allow-write
77+
*
78+
* @param name The name/path to the file.
79+
* @param len An optional value that sets the new size of the file. Omitting this argument sets the file size to 0 bytes.
80+
*/
81+
export function truncateSync(name: string, len?: number): void {
82+
if (isDeno) {
83+
Deno.truncateSync(name, len);
84+
} else {
85+
try {
86+
getNodeFs().truncateSync(name, len);
87+
} catch (error) {
88+
throw mapError(error);
89+
}
90+
}
91+
}

fs/unstable_truncate_test.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2018-2025 the Deno authors. MIT license.
2+
3+
import { assertEquals, assertRejects, assertThrows } from "@std/assert";
4+
import { truncate, truncateSync } from "./unstable_truncate.ts";
5+
import { NotFound } from "./unstable_errors.js";
6+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
7+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
8+
import { join, resolve } from "node:path";
9+
import { tmpdir } from "node:os";
10+
11+
Deno.test("truncate() succeeds in truncating file sizes", async () => {
12+
const tempDataDir = await mkdtemp(resolve(tmpdir(), "truncate_"));
13+
const testFile = join(tempDataDir, "truncFile.txt");
14+
await writeFile(testFile, "Hello, Standard Library");
15+
16+
await truncate(testFile, 30);
17+
assertEquals((await readFile(testFile)).length, 30);
18+
await truncate(testFile, 10);
19+
assertEquals((await readFile(testFile)).length, 10);
20+
await truncate(testFile, -5);
21+
assertEquals((await readFile(testFile)).length, 0);
22+
23+
await rm(tempDataDir, { recursive: true, force: true });
24+
});
25+
26+
Deno.test("truncate() truncates the file to zero when 'len' is not provided", async () => {
27+
const tempDataDir = await mkdtemp(resolve(tmpdir(), "truncate_"));
28+
const testFile = join(tempDataDir, "truncFile.txt");
29+
await writeFile(testFile, "Hello, Standard Library");
30+
31+
await truncate(testFile);
32+
assertEquals((await readFile(testFile)).length, 0);
33+
34+
await rm(tempDataDir, { recursive: true, force: true });
35+
});
36+
37+
Deno.test("truncate() rejects with Error when passing a non-regular file", async () => {
38+
const tempDataDir = await mkdtemp(resolve(tmpdir(), "truncate_"));
39+
40+
await assertRejects(async () => {
41+
await truncate(tempDataDir);
42+
}, Error);
43+
44+
await rm(tempDataDir, { recursive: true, force: true });
45+
});
46+
47+
Deno.test("truncate() rejects with NotFound with a non-existent file", async () => {
48+
await assertRejects(async () => {
49+
await truncate("non-existent-file.txt");
50+
}, NotFound);
51+
});
52+
53+
Deno.test("truncateSync() succeeds in truncating file sizes", () => {
54+
const tempDataDir = mkdtempSync(resolve(tmpdir(), "truncateSync_"));
55+
const testFile = join(tempDataDir, "truncFile.txt");
56+
writeFileSync(testFile, "Hello, Standard Library");
57+
58+
truncateSync(testFile, 30);
59+
assertEquals((readFileSync(testFile)).length, 30);
60+
truncateSync(testFile, 10);
61+
assertEquals((readFileSync(testFile)).length, 10);
62+
truncateSync(testFile, -5);
63+
assertEquals((readFileSync(testFile)).length, 0);
64+
65+
rmSync(tempDataDir, { recursive: true, force: true });
66+
});
67+
68+
Deno.test("truncateSync() truncates the file to zero when 'len' is not provided", () => {
69+
const tempDataDir = mkdtempSync(resolve(tmpdir(), "truncateSync_"));
70+
const testFile = join(tempDataDir, "truncFile.txt");
71+
writeFileSync(testFile, "Hello, Standard Library");
72+
73+
truncateSync(testFile);
74+
assertEquals((readFileSync(testFile)).length, 0);
75+
76+
rmSync(tempDataDir, { recursive: true, force: true });
77+
});
78+
79+
Deno.test("truncateSync() throws with Error with a non-regular file", () => {
80+
const tempDataDir = mkdtempSync(resolve(tmpdir(), "truncateSync_"));
81+
82+
assertThrows(() => {
83+
truncateSync(tempDataDir);
84+
}, Error);
85+
86+
rmSync(tempDataDir, { recursive: true, force: true });
87+
});
88+
89+
Deno.test("truncateSync() throws with NotFound with a non-existent file", () => {
90+
assertThrows(() => {
91+
truncateSync("non-existent-file.txt");
92+
}, NotFound);
93+
});

0 commit comments

Comments
 (0)