Skip to content

Commit b7c76d5

Browse files
authored
feat(fs/unstable): add makeTempDir and makeTempDirSync (#6391)
1 parent e52dc6b commit b7c76d5

File tree

6 files changed

+273
-0
lines changed

6 files changed

+273
-0
lines changed

_tools/node_test_runner/run_test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import "../../collections/unzip_test.ts";
5050
import "../../collections/without_all_test.ts";
5151
import "../../collections/zip_test.ts";
5252
import "../../fs/unstable_link_test.ts";
53+
import "../../fs/unstable_make_temp_dir_test.ts";
5354
import "../../fs/unstable_read_dir_test.ts";
5455
import "../../fs/unstable_read_link_test.ts";
5556
import "../../fs/unstable_real_path_test.ts";

fs/_utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,17 @@ function checkWindows(): boolean {
2727
export function getNodeFs() {
2828
return (globalThis as any).process.getBuiltinModule("node:fs");
2929
}
30+
31+
/**
32+
* @returns The Node.js `os` module.
33+
*/
34+
export function getNodeOs() {
35+
return (globalThis as any).process.getBuiltinModule("node:os");
36+
}
37+
38+
/**
39+
* @returns The Node.js `path` module.
40+
*/
41+
export function getNodePath() {
42+
return (globalThis as any).process.getBuiltinModule("node:path");
43+
}

fs/deno.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"./unstable-chmod": "./unstable_chmod.ts",
1717
"./unstable-link": "./unstable_link.ts",
1818
"./unstable-lstat": "./unstable_lstat.ts",
19+
"./unstable-make-temp-dir": "./unstable_make_temp_dir.ts",
1920
"./unstable-read-dir": "./unstable_read_dir.ts",
2021
"./unstable-read-link": "./unstable_read_link.ts",
2122
"./unstable-real-path": "./unstable_real_path.ts",

fs/unstable_make_temp_dir.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright 2018-2025 the Deno authors. MIT license.
2+
3+
import { getNodeFs, getNodeOs, getNodePath, isDeno } from "./_utils.ts";
4+
import type { MakeTempOptions } from "./unstable_types.ts";
5+
import { mapError } from "./_map_error.ts";
6+
7+
/**
8+
* Creates a new temporary directory in the default directory for temporary
9+
* files, unless `dir` is specified. Other optional options include
10+
* prefixing and suffixing the directory name with `prefix` and `suffix`
11+
* respectively.
12+
*
13+
* This call resolves to the full path to the newly created directory.
14+
*
15+
* Multiple programs calling this function simultaneously will create different
16+
* directories. It is the caller's responsibility to remove the directory when
17+
* no longer needed.
18+
*
19+
* Requires `allow-write` permission.
20+
*
21+
* @example Usage
22+
* ```ts ignore
23+
* import { makeTempDir } from "@std/unstable-make-temp-dir";
24+
* const tempDirName0 = await makeTempDir(); // e.g. /tmp/2894ea76
25+
* const tempDirName1 = await makeTempDir({ prefix: 'my_temp' }); // e.g. /tmp/my_temp339c944d
26+
* ```
27+
*
28+
* @tags allow-write
29+
*
30+
* @param options The options specified when creating a temporary directory.
31+
* @returns A promise that resolves to a path to the temporary directory.
32+
*/
33+
export async function makeTempDir(options?: MakeTempOptions): Promise<string> {
34+
if (isDeno) {
35+
return Deno.makeTempDir({ ...options });
36+
} else {
37+
const {
38+
dir = undefined,
39+
prefix = undefined,
40+
suffix = undefined,
41+
} = options ?? {};
42+
43+
try {
44+
const { mkdtemp, rename } = getNodeFs().promises;
45+
const { tmpdir } = getNodeOs();
46+
const { join, sep } = getNodePath();
47+
48+
if (!options) {
49+
return await mkdtemp(join(tmpdir(), sep));
50+
}
51+
52+
let prependPath = tmpdir();
53+
if (dir != null) {
54+
prependPath = typeof dir === "string" ? dir : ".";
55+
if (prependPath === "") {
56+
prependPath = ".";
57+
}
58+
}
59+
60+
if (prefix != null && typeof prefix === "string") {
61+
prependPath = join(prependPath, prefix || sep);
62+
} else {
63+
prependPath = join(prependPath, sep);
64+
}
65+
66+
if (suffix != null && typeof suffix === "string") {
67+
const tempPath = await mkdtemp(prependPath);
68+
const combinedTempPath = "".concat(tempPath, suffix);
69+
await rename(tempPath, combinedTempPath);
70+
return combinedTempPath;
71+
}
72+
73+
return await mkdtemp(prependPath);
74+
} catch (error) {
75+
throw mapError(error);
76+
}
77+
}
78+
}
79+
80+
/**
81+
* Synchronously creates a new temporary directory in the default directory
82+
* for temporary files, unless `dir` is specified. Other optional options
83+
* include prefixing and suffixing the directory name with `prefix` and
84+
* `suffix` respectively.
85+
*
86+
* The full path to the newly created directory is returned.
87+
*
88+
* Multiple programs calling this function simultaneously will create different
89+
* directories. It is the caller's responsibility to remove the directory when
90+
* no longer needed.
91+
*
92+
* Requires `allow-write` permission.
93+
*
94+
* @example Usage
95+
* ```ts ignore
96+
* import { makeTempDirSync } from "@std/fs/unstable-make-temp-dir";
97+
* const tempDirName0 = makeTempDirSync(); // e.g. /tmp/2894ea76
98+
* const tempDirName1 = makeTempDirSync({ prefix: 'my_temp' }); // e.g. /tmp/my_temp339c944d
99+
* ```
100+
*
101+
* @tags allow-write
102+
*
103+
* @param options The options specified when creating a temporary directory.
104+
* @returns The path of the temporary directory.
105+
*/
106+
export function makeTempDirSync(options?: MakeTempOptions): string {
107+
if (isDeno) {
108+
return Deno.makeTempDirSync({ ...options });
109+
} else {
110+
const {
111+
dir = undefined,
112+
prefix = undefined,
113+
suffix = undefined,
114+
} = options ?? {};
115+
116+
try {
117+
const { mkdtempSync, renameSync } = getNodeFs();
118+
const { tmpdir } = getNodeOs();
119+
const { join, sep } = getNodePath();
120+
121+
if (!options) {
122+
return mkdtempSync(join(tmpdir(), sep));
123+
}
124+
125+
let prependPath = tmpdir();
126+
if (dir != null) {
127+
prependPath = typeof dir === "string" ? dir : ".";
128+
if (prependPath === "") {
129+
prependPath = ".";
130+
}
131+
}
132+
133+
if (prefix != null && typeof prefix === "string") {
134+
prependPath = join(prependPath, prefix || sep);
135+
} else {
136+
prependPath = join(prependPath, sep);
137+
}
138+
139+
if (suffix != null && typeof prefix === "string") {
140+
const tempPath = mkdtempSync(prependPath);
141+
const combinedTempPath = "".concat(tempPath, suffix);
142+
renameSync(tempPath, combinedTempPath);
143+
return combinedTempPath;
144+
}
145+
146+
return mkdtempSync(prependPath);
147+
} catch (error) {
148+
throw mapError(error);
149+
}
150+
}
151+
}

fs/unstable_make_temp_dir_test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2018-2025 the Deno authors. MIT license.
2+
3+
import { assert, assertRejects, assertThrows } from "@std/assert";
4+
import { makeTempDir, makeTempDirSync } from "./unstable_make_temp_dir.ts";
5+
import { NotFound } from "./unstable_errors.js";
6+
import { rmSync } from "node:fs";
7+
import { rm } from "node:fs/promises";
8+
9+
Deno.test("makeTempDir() creates temporary directories in the default temp directory path", async () => {
10+
const dir1 = await makeTempDir({ prefix: "standard", suffix: "library" });
11+
const dir2 = await makeTempDir({ prefix: "standard", suffix: "library" });
12+
13+
try {
14+
assert(dir1 !== dir2);
15+
16+
for (const dir of [dir1, dir2]) {
17+
const tempDirName = dir.replace(/^.*[\\\/]/, "");
18+
assert(tempDirName.startsWith("standard"));
19+
assert(tempDirName.endsWith("library"));
20+
}
21+
} finally {
22+
await rm(dir1, { recursive: true, force: true });
23+
await rm(dir2, { recursive: true, force: true });
24+
}
25+
});
26+
27+
Deno.test("makeTempDir() creates temporary directories with the 'dir' option", async () => {
28+
const tempParent = await makeTempDir({ prefix: "first", suffix: "last" });
29+
const dir = await makeTempDir({ dir: tempParent });
30+
31+
try {
32+
assert(dir.startsWith(tempParent));
33+
assert(/^[\\\/]/.test(dir.slice(tempParent.length)));
34+
} finally {
35+
await rm(tempParent, { recursive: true, force: true });
36+
}
37+
});
38+
39+
Deno.test("makeTempDir() rejects with NotFound when passing a 'dir' path that does not exist", async () => {
40+
await assertRejects(async () => {
41+
await makeTempDir({ dir: "/non-existent-dir" });
42+
}, NotFound);
43+
});
44+
45+
Deno.test("makeTempDirSync() creates temporary directories in the default temp directory path", () => {
46+
const dir1 = makeTempDirSync({ prefix: "standard", suffix: "library" });
47+
const dir2 = makeTempDirSync({ prefix: "standard", suffix: "library" });
48+
49+
try {
50+
assert(dir1 !== dir2);
51+
52+
for (const dir of [dir1, dir2]) {
53+
const tempDirName = dir.replace(/^.*[\\\/]/, "");
54+
assert(tempDirName.startsWith("standard"));
55+
assert(tempDirName.endsWith("library"));
56+
}
57+
} finally {
58+
rmSync(dir1, { recursive: true, force: true });
59+
rmSync(dir2, { recursive: true, force: true });
60+
}
61+
});
62+
63+
Deno.test("makeTempDirSync() creates temporary directories with the 'dir' option", () => {
64+
const tempParent = makeTempDirSync({ prefix: "first", suffix: "last" });
65+
const dir = makeTempDirSync({ dir: tempParent });
66+
67+
try {
68+
assert(dir.startsWith(tempParent));
69+
assert(/^[\\\/]/.test(dir.slice(tempParent.length)));
70+
} finally {
71+
rmSync(tempParent, { recursive: true, force: true });
72+
}
73+
});
74+
75+
Deno.test("makeTempDirSync() throws with NotFound when passing a 'dir' path that does not exist", () => {
76+
assertThrows(() => {
77+
makeTempDirSync({ dir: "/non-existent-dir" });
78+
}, NotFound);
79+
});

fs/unstable_types.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,30 @@ export interface SymlinkOptions {
117117
* option only applies to Windows and is ignored on other operating systems. */
118118
type: "file" | "dir" | "junction";
119119
}
120+
121+
/**
122+
* Options which can be set when using {@linkcode makeTempDir},
123+
* {@linkcode makeTempDirSync}, {@linkcode makeTempFile}, and
124+
* {@linkcode makeTempFileSync}.
125+
*/
126+
export interface MakeTempOptions {
127+
/**
128+
* Directory where the temporary directory should be created (defaults to the
129+
* env variable `TMPDIR`, or the system's default, usually `/tmp`).
130+
*
131+
* Note that if the passed `dir` is relative, the path returned by
132+
* `makeTempFile()` and `makeTempDir()` will also be relative. Be mindful of
133+
* this when changing working directory.
134+
*/
135+
dir?: string;
136+
/**
137+
* String that should precede the random portion of the temporary directory's
138+
* name.
139+
*/
140+
prefix?: string;
141+
/**
142+
* String that should follow the random portion of the temporary directory's
143+
* name.
144+
*/
145+
suffix?: string;
146+
}

0 commit comments

Comments
 (0)