From 01a8dfdb0dc881cb079f3a9f86f58c6727c85608 Mon Sep 17 00:00:00 2001 From: Tim Reichen Date: Wed, 12 Feb 2025 02:59:36 +0100 Subject: [PATCH 1/3] refactor(semver): clean up `parseRange`, add missing tests (#6362) --- semver/compare_test.ts | 49 ++++++++++++++++++++++- semver/greater_than_range_test.ts | 42 +++++++++++++++++++- semver/is_range_test.ts | 66 ++++++++++++++++++++++++------- semver/is_semver_test.ts | 3 +- semver/less_than_range_test.ts | 22 ++++++++++- semver/parse_range.ts | 16 ++++---- semver/parse_range_test.ts | 21 ++++++++++ semver/try_parse_range_test.ts | 34 ++++++++++++++++ semver/types.ts | 1 + 9 files changed, 227 insertions(+), 27 deletions(-) create mode 100644 semver/try_parse_range_test.ts diff --git a/semver/compare_test.ts b/semver/compare_test.ts index 18f22c3b0b43..8d11ca84eddb 100644 --- a/semver/compare_test.ts +++ b/semver/compare_test.ts @@ -1,5 +1,5 @@ // Copyright 2018-2025 the Deno authors. MIT license. -import { assertEquals } from "@std/assert"; +import { assertEquals, assertThrows } from "@std/assert"; import { parse } from "./parse.ts"; import { compare } from "./compare.ts"; @@ -54,3 +54,50 @@ Deno.test({ } }, }); + +Deno.test({ + name: "compare() throws on NaN", + fn: () => { + assertThrows( + () => + compare({ major: NaN, minor: 0, patch: 0 }, { + major: 1, + minor: 0, + patch: 0, + }), + Error, + "Cannot compare against non-numbers", + ); + + assertThrows( + () => + compare({ major: 1, minor: 0, patch: 0 }, { + major: 1, + minor: NaN, + patch: 0, + }), + Error, + "Cannot compare against non-numbers", + ); + }, +}); + +Deno.test({ + name: "compare() handles undefined in prerelease", + fn: () => { + assertEquals( + compare({ + major: 1, + minor: 0, + patch: 0, + prerelease: [undefined as unknown as string], + }, { + major: 1, + minor: 0, + patch: 0, + prerelease: [undefined as unknown as string], + }), + 0, + ); + }, +}); diff --git a/semver/greater_than_range_test.ts b/semver/greater_than_range_test.ts index d24a88d656ba..3299f2fced0f 100644 --- a/semver/greater_than_range_test.ts +++ b/semver/greater_than_range_test.ts @@ -1,7 +1,7 @@ // Copyright 2018-2025 the Deno authors. MIT license. // Copyright Isaac Z. Schlueter and npm contributors. All rights reserved. ISC license. -import { assert, assertFalse } from "@std/assert"; +import { assert, assertEquals, assertFalse } from "@std/assert"; import { format, formatRange, @@ -9,6 +9,7 @@ import { parse, parseRange, } from "./mod.ts"; +import type { Operator } from "./types.ts"; Deno.test("greaterThanRange() checks if the semver is greater than the range", async (t) => { // from https://github.com/npm/node-semver/blob/692451bd6f75b38a71a99f39da405c94a5954a22/test/fixtures/version-gt-range.js @@ -167,3 +168,42 @@ Deno.test("greaterThanRange() checks if the semver is greater than the range", a }); } }); + +Deno.test("greaterThanRange() handles equals operator", () => { + const version = { + major: 1, + minor: 0, + patch: 0, + prerelease: [], + build: [], + }; + const range = [[{ + operator: "=" as unknown as Operator, + major: 1, + minor: 0, + patch: 0, + prerelease: [], + build: [], + }]]; + assertEquals(greaterThanRange(version, range), false); +}); + +Deno.test("greaterThanRange() handles not equals operator", () => { + const version = { + major: 1, + minor: 0, + patch: 0, + prerelease: [], + build: [], + }; + const range = [[{ + operator: "!=" as const, + major: 1, + minor: 0, + patch: 0, + prerelease: [], + build: [], + }]]; + // FIXME(kt3k): This demonstrates a bug. This should be false + assertEquals(greaterThanRange(version, range), true); +}); diff --git a/semver/is_range_test.ts b/semver/is_range_test.ts index 488274af9ab1..b55452a7d297 100644 --- a/semver/is_range_test.ts +++ b/semver/is_range_test.ts @@ -1,16 +1,13 @@ // Copyright 2018-2025 the Deno authors. MIT license. -import { assert } from "@std/assert"; +import { assertEquals } from "@std/assert"; import { ALL, MIN } from "./_constants.ts"; -import { formatRange } from "./format_range.ts"; import { isRange } from "./is_range.ts"; import type { Range } from "./types.ts"; Deno.test({ - name: "isRange()", - fn: async (t) => { - const ranges: Range[] = [[ - [ALL], - ], [ + name: "isRange() handles simple range", + fn: () => { + const range: Range = [ [{ operator: ">=", major: 0, @@ -22,12 +19,53 @@ Deno.test({ operator: "<", ...MIN, }], - ]]; - for (const r of ranges) { - await t.step(`${formatRange(r)}`, () => { - const actual = isRange(r); - assert(actual); - }); - } + ]; + const actual = isRange(range); + assertEquals(actual, true); + }, +}); + +Deno.test({ + name: "isRange() handles ALL constant", + fn: () => { + const range: Range = [[ALL]]; + const actual = isRange(range); + assertEquals(actual, true); + }, +}); + +Deno.test({ + name: "isRange() handles null", + fn: () => { + const range: Range = [[null]] as unknown as Range; + const actual = isRange(range); + assertEquals(actual, false); + }, +}); + +Deno.test({ + name: "isRange() handles undefined", + fn: () => { + const range: Range = [[undefined]] as unknown as Range; + const actual = isRange(range); + assertEquals(actual, false); + }, +}); + +Deno.test({ + name: "isRange() handles array type", + fn: () => { + const range: Range = [[[1, 2, 3]]] as unknown as Range; + const actual = isRange(range); + assertEquals(actual, false); + }, +}); + +Deno.test({ + name: "isRange() handles not object type", + fn: () => { + const range: Range = [[[true]]] as unknown as Range; + const actual = isRange(range); + assertEquals(actual, false); }, }); diff --git a/semver/is_semver_test.ts b/semver/is_semver_test.ts index cb96f1fec674..96c76af36963 100644 --- a/semver/is_semver_test.ts +++ b/semver/is_semver_test.ts @@ -1,6 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. import { assert } from "@std/assert"; -import { MIN } from "./_constants.ts"; +import { ANY, MIN } from "./_constants.ts"; import { isSemVer } from "./is_semver.ts"; Deno.test({ @@ -61,6 +61,7 @@ Deno.test({ [{ major: 0, minor: 0, patch: 0, build: [], prerelease: ["abc"] }], [{ major: 0, minor: 0, patch: 0, build: [], prerelease: ["abc", 0] }], [MIN], + [ANY], ]; for (const [v] of versions) { await t.step( diff --git a/semver/less_than_range_test.ts b/semver/less_than_range_test.ts index bddd0739ee04..8b889a769dbc 100644 --- a/semver/less_than_range_test.ts +++ b/semver/less_than_range_test.ts @@ -1,7 +1,7 @@ // Copyright 2018-2025 the Deno authors. MIT license. // Copyright Isaac Z. Schlueter and npm contributors. All rights reserved. ISC license. -import { assert, assertFalse } from "@std/assert"; +import { assert, assertEquals, assertFalse } from "@std/assert"; import { format, formatRange, @@ -169,3 +169,23 @@ Deno.test("lessThanRange() checks if the SemVer is less than the range", async ( }); } }); + +Deno.test("lessThanRange() handles not equals operator", () => { + const version = { + major: 1, + minor: 0, + patch: 0, + prerelease: [], + build: [], + }; + const range = [[{ + operator: "!=" as const, + major: 1, + minor: 0, + patch: 0, + prerelease: [], + build: [], + }]]; + // FIXME(kt3k): This demonstrates a bug. This should be false + assertEquals(lessThanRange(version, range), true); +}); diff --git a/semver/parse_range.ts b/semver/parse_range.ts index 262989eff3c8..e2ba5211a07d 100644 --- a/semver/parse_range.ts +++ b/semver/parse_range.ts @@ -141,27 +141,25 @@ function handleRightHyphenRangeGroups( major: +rightGroups.major, minor: +rightGroups.minor, patch: +rightGroups.patch, - prerelease: rightGroups.prerelease - ? parsePrerelease(rightGroups.prerelease) - : [], + prerelease: [], build: [], }; } -function parseHyphenRange(range: string): Comparator[] | undefined { +function parseHyphenRange(range: string): Comparator[] | null { const leftMatch = range.match(new RegExp(`^${XRANGE}`)); const leftGroup = leftMatch?.groups; - if (!leftGroup) return; + if (!leftGroup) return null; const leftLength = leftMatch[0].length; const hyphenMatch = range.slice(leftLength).match(/^\s+-\s+/); - if (!hyphenMatch) return; + if (!hyphenMatch) return null; const hyphenLength = hyphenMatch[0].length; const rightMatch = range.slice(leftLength + hyphenLength).match( new RegExp(`^${XRANGE}\\s*$`), ); const rightGroups = rightMatch?.groups; - if (!rightGroups) return; + if (!rightGroups) return null; const from = handleLeftHyphenRangeGroups(leftGroup as RangeRegExpGroups); const to = handleRightHyphenRangeGroups(rightGroups as RangeRegExpGroups); @@ -255,7 +253,7 @@ function handleLessThanOperator(groups: RangeRegExpGroups): Comparator[] { if (majorIsWildcard) return [{ operator: "<", major: 0, minor: 0, patch: 0 }]; if (minorIsWildcard) { if (patchIsWildcard) return [{ operator: "<", major, minor: 0, patch: 0 }]; - return [{ operator: "<", major, minor, patch: 0 }]; + return [{ operator: "<", major, minor: 0, patch: 0 }]; } if (patchIsWildcard) return [{ operator: "<", major, minor, patch: 0 }]; const prerelease = parsePrerelease(groups.prerelease ?? ""); @@ -318,7 +316,7 @@ function handleGreaterOrEqualOperator(groups: RangeRegExpGroups): Comparator[] { if (majorIsWildcard) return [ALL]; if (minorIsWildcard) { if (patchIsWildcard) return [{ operator: ">=", major, minor: 0, patch: 0 }]; - return [{ operator: ">=", major, minor, patch: 0 }]; + return [{ operator: ">=", major, minor: 0, patch: 0 }]; } if (patchIsWildcard) return [{ operator: ">=", major, minor, patch: 0 }]; const prerelease = parsePrerelease(groups.prerelease ?? ""); diff --git a/semver/parse_range_test.ts b/semver/parse_range_test.ts index 636f838cf10c..a0d91ab52838 100644 --- a/semver/parse_range_test.ts +++ b/semver/parse_range_test.ts @@ -666,3 +666,24 @@ Deno.test("parseRange() throws on invalid range", () => { 'Cannot parse version range: range "blerg" is invalid', ); }); + +Deno.test("parseRange() handles wildcards", () => { + assertEquals(parseRange("<1.*"), [ + [{ operator: "<", major: 1, minor: 0, patch: 0 }], + ]); + assertEquals(parseRange("<1.*.0"), [ + [{ operator: "<", major: 1, minor: 0, patch: 0 }], + ]); + assertEquals(parseRange("<1.*.*"), [ + [{ operator: "<", major: 1, minor: 0, patch: 0 }], + ]); + assertEquals(parseRange(">=1.*.0"), [ + [{ operator: ">=", major: 1, minor: 0, patch: 0 }], + ]); + assertEquals(parseRange(">=1.*.*"), [ + [{ operator: ">=", major: 1, minor: 0, patch: 0 }], + ]); + assertEquals(parseRange(">=1.0.*"), [ + [{ operator: ">=", major: 1, minor: 0, patch: 0 }], + ]); +}); diff --git a/semver/try_parse_range_test.ts b/semver/try_parse_range_test.ts new file mode 100644 index 000000000000..062b663a0ae4 --- /dev/null +++ b/semver/try_parse_range_test.ts @@ -0,0 +1,34 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +import { assertEquals } from "@std/assert"; +import { tryParseRange } from "./try_parse_range.ts"; +import type { Range } from "./types.ts"; + +Deno.test("tryParseRange()", () => { + const actual = tryParseRange(">=1.2.3 <1.2.4"); + const expected: Range = [ + [ + { + operator: ">=", + major: 1, + minor: 2, + patch: 3, + prerelease: [], + build: [], + }, + { + operator: "<", + major: 1, + minor: 2, + patch: 4, + prerelease: [], + build: [], + }, + ], + ]; + assertEquals(actual, expected); +}); + +Deno.test("tryParseRange() handles invalid range", () => { + assertEquals(tryParseRange("blerg"), undefined); +}); diff --git a/semver/types.ts b/semver/types.ts index 7e9b6102fbbf..6ad5cf6e305c 100644 --- a/semver/types.ts +++ b/semver/types.ts @@ -21,6 +21,7 @@ export type ReleaseType = export type Operator = | undefined | "=" + // Note: `!=` operator type does not exist in npm:semver | "!=" | ">" | ">=" From e52dc6b4276d03b23d835015ce1c5c5ee9f8dc19 Mon Sep 17 00:00:00 2001 From: Tim Reichen Date: Wed, 12 Feb 2025 04:15:59 +0100 Subject: [PATCH 2/3] refactor(front-matter): call `extract()` functions in `any.ts` (#6390) --- front_matter/any.ts | 29 ++++++++++++----------------- front_matter/toml.ts | 2 +- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/front_matter/any.ts b/front_matter/any.ts index 039742194427..d16ebf8ad775 100644 --- a/front_matter/any.ts +++ b/front_matter/any.ts @@ -1,25 +1,15 @@ // Copyright 2018-2025 the Deno authors. MIT license. -import { extractAndParse, type Parser, recognize } from "./_shared.ts"; -import { parse as parseYaml } from "@std/yaml/parse"; -import { parse as parseToml } from "@std/toml/parse"; +import { recognize } from "./_shared.ts"; +import { extract as extractToml } from "@std/front-matter/toml"; +import { extract as extractYaml } from "@std/front-matter/yaml"; +import { extract as extractJson } from "@std/front-matter/json"; import type { Extract } from "./types.ts"; import type { Format } from "./test.ts"; import { EXTRACT_REGEXP_MAP } from "./_formats.ts"; export type { Extract }; -function getParserForFormat(format: Format): Parser { - switch (format) { - case "yaml": - return parseYaml as Parser; - case "toml": - return parseToml as Parser; - case "json": - return JSON.parse; - } -} - /** * Extracts and parses {@link https://yaml.org | YAML}, {@link https://toml.io | * TOML}, or {@link https://www.json.org/ | JSON} from the metadata of front @@ -49,7 +39,12 @@ function getParserForFormat(format: Format): Parser { export function extract(text: string): Extract { const formats = [...EXTRACT_REGEXP_MAP.keys()] as Format[]; const format = recognize(text, formats); - const regexp = EXTRACT_REGEXP_MAP.get(format) as RegExp; - const parser = getParserForFormat(format); - return extractAndParse(text, regexp, parser); + switch (format) { + case "yaml": + return extractYaml(text); + case "toml": + return extractToml(text); + case "json": + return extractJson(text); + } } diff --git a/front_matter/toml.ts b/front_matter/toml.ts index 0bfc21c7fd9f..5002f6690a9b 100644 --- a/front_matter/toml.ts +++ b/front_matter/toml.ts @@ -34,5 +34,5 @@ export type { Extract }; * @returns The extracted TOML front matter and body content. */ export function extract(text: string): Extract { - return extractAndParse(text, EXTRACT_TOML_REGEXP, parse as Parser); + return extractAndParse(text, EXTRACT_TOML_REGEXP, parse as Parser); } From b7c76d5d904dcdd307296d5ee5f5dd9a22771523 Mon Sep 17 00:00:00 2001 From: James Bronder <36022278+jbronder@users.noreply.github.com> Date: Tue, 11 Feb 2025 20:28:51 -0800 Subject: [PATCH 3/3] feat(fs/unstable): add makeTempDir and makeTempDirSync (#6391) --- _tools/node_test_runner/run_test.mjs | 1 + fs/_utils.ts | 14 +++ fs/deno.json | 1 + fs/unstable_make_temp_dir.ts | 151 +++++++++++++++++++++++++++ fs/unstable_make_temp_dir_test.ts | 79 ++++++++++++++ fs/unstable_types.ts | 27 +++++ 6 files changed, 273 insertions(+) create mode 100644 fs/unstable_make_temp_dir.ts create mode 100644 fs/unstable_make_temp_dir_test.ts diff --git a/_tools/node_test_runner/run_test.mjs b/_tools/node_test_runner/run_test.mjs index b15cdaeddde8..cebb60fc67c8 100644 --- a/_tools/node_test_runner/run_test.mjs +++ b/_tools/node_test_runner/run_test.mjs @@ -50,6 +50,7 @@ import "../../collections/unzip_test.ts"; import "../../collections/without_all_test.ts"; import "../../collections/zip_test.ts"; import "../../fs/unstable_link_test.ts"; +import "../../fs/unstable_make_temp_dir_test.ts"; import "../../fs/unstable_read_dir_test.ts"; import "../../fs/unstable_read_link_test.ts"; import "../../fs/unstable_real_path_test.ts"; diff --git a/fs/_utils.ts b/fs/_utils.ts index 76478802f5d2..d8c4593e02dd 100644 --- a/fs/_utils.ts +++ b/fs/_utils.ts @@ -27,3 +27,17 @@ function checkWindows(): boolean { export function getNodeFs() { return (globalThis as any).process.getBuiltinModule("node:fs"); } + +/** + * @returns The Node.js `os` module. + */ +export function getNodeOs() { + return (globalThis as any).process.getBuiltinModule("node:os"); +} + +/** + * @returns The Node.js `path` module. + */ +export function getNodePath() { + return (globalThis as any).process.getBuiltinModule("node:path"); +} diff --git a/fs/deno.json b/fs/deno.json index bf24e9531e2e..1cb8a4618672 100644 --- a/fs/deno.json +++ b/fs/deno.json @@ -16,6 +16,7 @@ "./unstable-chmod": "./unstable_chmod.ts", "./unstable-link": "./unstable_link.ts", "./unstable-lstat": "./unstable_lstat.ts", + "./unstable-make-temp-dir": "./unstable_make_temp_dir.ts", "./unstable-read-dir": "./unstable_read_dir.ts", "./unstable-read-link": "./unstable_read_link.ts", "./unstable-real-path": "./unstable_real_path.ts", diff --git a/fs/unstable_make_temp_dir.ts b/fs/unstable_make_temp_dir.ts new file mode 100644 index 000000000000..a8a09a243af6 --- /dev/null +++ b/fs/unstable_make_temp_dir.ts @@ -0,0 +1,151 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +import { getNodeFs, getNodeOs, getNodePath, isDeno } from "./_utils.ts"; +import type { MakeTempOptions } from "./unstable_types.ts"; +import { mapError } from "./_map_error.ts"; + +/** + * Creates a new temporary directory in the default directory for temporary + * files, unless `dir` is specified. Other optional options include + * prefixing and suffixing the directory name with `prefix` and `suffix` + * respectively. + * + * This call resolves to the full path to the newly created directory. + * + * Multiple programs calling this function simultaneously will create different + * directories. It is the caller's responsibility to remove the directory when + * no longer needed. + * + * Requires `allow-write` permission. + * + * @example Usage + * ```ts ignore + * import { makeTempDir } from "@std/unstable-make-temp-dir"; + * const tempDirName0 = await makeTempDir(); // e.g. /tmp/2894ea76 + * const tempDirName1 = await makeTempDir({ prefix: 'my_temp' }); // e.g. /tmp/my_temp339c944d + * ``` + * + * @tags allow-write + * + * @param options The options specified when creating a temporary directory. + * @returns A promise that resolves to a path to the temporary directory. + */ +export async function makeTempDir(options?: MakeTempOptions): Promise { + if (isDeno) { + return Deno.makeTempDir({ ...options }); + } else { + const { + dir = undefined, + prefix = undefined, + suffix = undefined, + } = options ?? {}; + + try { + const { mkdtemp, rename } = getNodeFs().promises; + const { tmpdir } = getNodeOs(); + const { join, sep } = getNodePath(); + + if (!options) { + return await mkdtemp(join(tmpdir(), sep)); + } + + let prependPath = tmpdir(); + if (dir != null) { + prependPath = typeof dir === "string" ? dir : "."; + if (prependPath === "") { + prependPath = "."; + } + } + + if (prefix != null && typeof prefix === "string") { + prependPath = join(prependPath, prefix || sep); + } else { + prependPath = join(prependPath, sep); + } + + if (suffix != null && typeof suffix === "string") { + const tempPath = await mkdtemp(prependPath); + const combinedTempPath = "".concat(tempPath, suffix); + await rename(tempPath, combinedTempPath); + return combinedTempPath; + } + + return await mkdtemp(prependPath); + } catch (error) { + throw mapError(error); + } + } +} + +/** + * Synchronously creates a new temporary directory in the default directory + * for temporary files, unless `dir` is specified. Other optional options + * include prefixing and suffixing the directory name with `prefix` and + * `suffix` respectively. + * + * The full path to the newly created directory is returned. + * + * Multiple programs calling this function simultaneously will create different + * directories. It is the caller's responsibility to remove the directory when + * no longer needed. + * + * Requires `allow-write` permission. + * + * @example Usage + * ```ts ignore + * import { makeTempDirSync } from "@std/fs/unstable-make-temp-dir"; + * const tempDirName0 = makeTempDirSync(); // e.g. /tmp/2894ea76 + * const tempDirName1 = makeTempDirSync({ prefix: 'my_temp' }); // e.g. /tmp/my_temp339c944d + * ``` + * + * @tags allow-write + * + * @param options The options specified when creating a temporary directory. + * @returns The path of the temporary directory. + */ +export function makeTempDirSync(options?: MakeTempOptions): string { + if (isDeno) { + return Deno.makeTempDirSync({ ...options }); + } else { + const { + dir = undefined, + prefix = undefined, + suffix = undefined, + } = options ?? {}; + + try { + const { mkdtempSync, renameSync } = getNodeFs(); + const { tmpdir } = getNodeOs(); + const { join, sep } = getNodePath(); + + if (!options) { + return mkdtempSync(join(tmpdir(), sep)); + } + + let prependPath = tmpdir(); + if (dir != null) { + prependPath = typeof dir === "string" ? dir : "."; + if (prependPath === "") { + prependPath = "."; + } + } + + if (prefix != null && typeof prefix === "string") { + prependPath = join(prependPath, prefix || sep); + } else { + prependPath = join(prependPath, sep); + } + + if (suffix != null && typeof prefix === "string") { + const tempPath = mkdtempSync(prependPath); + const combinedTempPath = "".concat(tempPath, suffix); + renameSync(tempPath, combinedTempPath); + return combinedTempPath; + } + + return mkdtempSync(prependPath); + } catch (error) { + throw mapError(error); + } + } +} diff --git a/fs/unstable_make_temp_dir_test.ts b/fs/unstable_make_temp_dir_test.ts new file mode 100644 index 000000000000..8c319ed98a56 --- /dev/null +++ b/fs/unstable_make_temp_dir_test.ts @@ -0,0 +1,79 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +import { assert, assertRejects, assertThrows } from "@std/assert"; +import { makeTempDir, makeTempDirSync } from "./unstable_make_temp_dir.ts"; +import { NotFound } from "./unstable_errors.js"; +import { rmSync } from "node:fs"; +import { rm } from "node:fs/promises"; + +Deno.test("makeTempDir() creates temporary directories in the default temp directory path", async () => { + const dir1 = await makeTempDir({ prefix: "standard", suffix: "library" }); + const dir2 = await makeTempDir({ prefix: "standard", suffix: "library" }); + + try { + assert(dir1 !== dir2); + + for (const dir of [dir1, dir2]) { + const tempDirName = dir.replace(/^.*[\\\/]/, ""); + assert(tempDirName.startsWith("standard")); + assert(tempDirName.endsWith("library")); + } + } finally { + await rm(dir1, { recursive: true, force: true }); + await rm(dir2, { recursive: true, force: true }); + } +}); + +Deno.test("makeTempDir() creates temporary directories with the 'dir' option", async () => { + const tempParent = await makeTempDir({ prefix: "first", suffix: "last" }); + const dir = await makeTempDir({ dir: tempParent }); + + try { + assert(dir.startsWith(tempParent)); + assert(/^[\\\/]/.test(dir.slice(tempParent.length))); + } finally { + await rm(tempParent, { recursive: true, force: true }); + } +}); + +Deno.test("makeTempDir() rejects with NotFound when passing a 'dir' path that does not exist", async () => { + await assertRejects(async () => { + await makeTempDir({ dir: "/non-existent-dir" }); + }, NotFound); +}); + +Deno.test("makeTempDirSync() creates temporary directories in the default temp directory path", () => { + const dir1 = makeTempDirSync({ prefix: "standard", suffix: "library" }); + const dir2 = makeTempDirSync({ prefix: "standard", suffix: "library" }); + + try { + assert(dir1 !== dir2); + + for (const dir of [dir1, dir2]) { + const tempDirName = dir.replace(/^.*[\\\/]/, ""); + assert(tempDirName.startsWith("standard")); + assert(tempDirName.endsWith("library")); + } + } finally { + rmSync(dir1, { recursive: true, force: true }); + rmSync(dir2, { recursive: true, force: true }); + } +}); + +Deno.test("makeTempDirSync() creates temporary directories with the 'dir' option", () => { + const tempParent = makeTempDirSync({ prefix: "first", suffix: "last" }); + const dir = makeTempDirSync({ dir: tempParent }); + + try { + assert(dir.startsWith(tempParent)); + assert(/^[\\\/]/.test(dir.slice(tempParent.length))); + } finally { + rmSync(tempParent, { recursive: true, force: true }); + } +}); + +Deno.test("makeTempDirSync() throws with NotFound when passing a 'dir' path that does not exist", () => { + assertThrows(() => { + makeTempDirSync({ dir: "/non-existent-dir" }); + }, NotFound); +}); diff --git a/fs/unstable_types.ts b/fs/unstable_types.ts index 60d61e8a8a3e..8ad466ce961c 100644 --- a/fs/unstable_types.ts +++ b/fs/unstable_types.ts @@ -117,3 +117,30 @@ export interface SymlinkOptions { * option only applies to Windows and is ignored on other operating systems. */ type: "file" | "dir" | "junction"; } + +/** + * Options which can be set when using {@linkcode makeTempDir}, + * {@linkcode makeTempDirSync}, {@linkcode makeTempFile}, and + * {@linkcode makeTempFileSync}. + */ +export interface MakeTempOptions { + /** + * Directory where the temporary directory should be created (defaults to the + * env variable `TMPDIR`, or the system's default, usually `/tmp`). + * + * Note that if the passed `dir` is relative, the path returned by + * `makeTempFile()` and `makeTempDir()` will also be relative. Be mindful of + * this when changing working directory. + */ + dir?: string; + /** + * String that should precede the random portion of the temporary directory's + * name. + */ + prefix?: string; + /** + * String that should follow the random portion of the temporary directory's + * name. + */ + suffix?: string; +}