mirror of https://github.com/openclaw/openclaw.git
refactor: share temp dir test helper
This commit is contained in:
parent
b5349f7563
commit
88b87d893d
|
|
@ -1,22 +1,13 @@
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { withTempDir } from "../../test-helpers/temp-dir.js";
|
||||||
import {
|
import {
|
||||||
buildPinnedWritePlan,
|
buildPinnedWritePlan,
|
||||||
SANDBOX_PINNED_MUTATION_PYTHON,
|
SANDBOX_PINNED_MUTATION_PYTHON,
|
||||||
} from "./fs-bridge-mutation-helper.js";
|
} from "./fs-bridge-mutation-helper.js";
|
||||||
|
|
||||||
async function withTempRoot<T>(prefix: string, run: (root: string) => Promise<T>): Promise<T> {
|
|
||||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
||||||
try {
|
|
||||||
return await run(root);
|
|
||||||
} finally {
|
|
||||||
await fs.rm(root, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runMutation(args: string[], input?: string) {
|
function runMutation(args: string[], input?: string) {
|
||||||
return spawnSync("python3", ["-c", SANDBOX_PINNED_MUTATION_PYTHON, ...args], {
|
return spawnSync("python3", ["-c", SANDBOX_PINNED_MUTATION_PYTHON, ...args], {
|
||||||
input,
|
input,
|
||||||
|
|
@ -56,7 +47,7 @@ function runWritePlan(args: string[], input?: string) {
|
||||||
|
|
||||||
describe("sandbox pinned mutation helper", () => {
|
describe("sandbox pinned mutation helper", () => {
|
||||||
it("writes through a pinned directory fd", async () => {
|
it("writes through a pinned directory fd", async () => {
|
||||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||||
const workspace = path.join(root, "workspace");
|
const workspace = path.join(root, "workspace");
|
||||||
await fs.mkdir(workspace, { recursive: true });
|
await fs.mkdir(workspace, { recursive: true });
|
||||||
|
|
||||||
|
|
@ -72,7 +63,7 @@ describe("sandbox pinned mutation helper", () => {
|
||||||
it.runIf(process.platform !== "win32")(
|
it.runIf(process.platform !== "win32")(
|
||||||
"preserves stdin payload bytes when the pinned write plan runs through sh",
|
"preserves stdin payload bytes when the pinned write plan runs through sh",
|
||||||
async () => {
|
async () => {
|
||||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||||
const workspace = path.join(root, "workspace");
|
const workspace = path.join(root, "workspace");
|
||||||
await fs.mkdir(workspace, { recursive: true });
|
await fs.mkdir(workspace, { recursive: true });
|
||||||
|
|
||||||
|
|
@ -92,7 +83,7 @@ describe("sandbox pinned mutation helper", () => {
|
||||||
it.runIf(process.platform !== "win32")(
|
it.runIf(process.platform !== "win32")(
|
||||||
"rejects symlink-parent writes instead of materializing a temp file outside the mount",
|
"rejects symlink-parent writes instead of materializing a temp file outside the mount",
|
||||||
async () => {
|
async () => {
|
||||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||||
const workspace = path.join(root, "workspace");
|
const workspace = path.join(root, "workspace");
|
||||||
const outside = path.join(root, "outside");
|
const outside = path.join(root, "outside");
|
||||||
await fs.mkdir(workspace, { recursive: true });
|
await fs.mkdir(workspace, { recursive: true });
|
||||||
|
|
@ -108,7 +99,7 @@ describe("sandbox pinned mutation helper", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
it.runIf(process.platform !== "win32")("rejects symlink segments during mkdirp", async () => {
|
it.runIf(process.platform !== "win32")("rejects symlink segments during mkdirp", async () => {
|
||||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||||
const workspace = path.join(root, "workspace");
|
const workspace = path.join(root, "workspace");
|
||||||
const outside = path.join(root, "outside");
|
const outside = path.join(root, "outside");
|
||||||
await fs.mkdir(workspace, { recursive: true });
|
await fs.mkdir(workspace, { recursive: true });
|
||||||
|
|
@ -123,7 +114,7 @@ describe("sandbox pinned mutation helper", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it.runIf(process.platform !== "win32")("remove unlinks the symlink itself", async () => {
|
it.runIf(process.platform !== "win32")("remove unlinks the symlink itself", async () => {
|
||||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||||
const workspace = path.join(root, "workspace");
|
const workspace = path.join(root, "workspace");
|
||||||
const outside = path.join(root, "outside");
|
const outside = path.join(root, "outside");
|
||||||
await fs.mkdir(workspace, { recursive: true });
|
await fs.mkdir(workspace, { recursive: true });
|
||||||
|
|
@ -144,7 +135,7 @@ describe("sandbox pinned mutation helper", () => {
|
||||||
it.runIf(process.platform !== "win32")(
|
it.runIf(process.platform !== "win32")(
|
||||||
"rejects symlink destination parents during rename",
|
"rejects symlink destination parents during rename",
|
||||||
async () => {
|
async () => {
|
||||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||||
const workspace = path.join(root, "workspace");
|
const workspace = path.join(root, "workspace");
|
||||||
const outside = path.join(root, "outside");
|
const outside = path.join(root, "outside");
|
||||||
await fs.mkdir(workspace, { recursive: true });
|
await fs.mkdir(workspace, { recursive: true });
|
||||||
|
|
@ -175,7 +166,7 @@ describe("sandbox pinned mutation helper", () => {
|
||||||
it.runIf(process.platform !== "win32")(
|
it.runIf(process.platform !== "win32")(
|
||||||
"copies directories across different mount roots during rename fallback",
|
"copies directories across different mount roots during rename fallback",
|
||||||
async () => {
|
async () => {
|
||||||
await withTempRoot("openclaw-mutation-helper-", async (root) => {
|
await withTempDir({ prefix: "openclaw-mutation-helper-" }, async (root) => {
|
||||||
const sourceRoot = path.join(root, "source");
|
const sourceRoot = path.join(root, "source");
|
||||||
const destRoot = path.join(root, "dest");
|
const destRoot = path.join(root, "dest");
|
||||||
await fs.mkdir(path.join(sourceRoot, "dir", "nested"), { recursive: true });
|
await fs.mkdir(path.join(sourceRoot, "dir", "nested"), { recursive: true });
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||||
import {
|
import {
|
||||||
resolveDefaultConfigCandidates,
|
resolveDefaultConfigCandidates,
|
||||||
resolveConfigPathCandidate,
|
resolveConfigPathCandidate,
|
||||||
|
|
@ -37,15 +37,6 @@ describe("oauth paths", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("state + config path candidates", () => {
|
describe("state + config path candidates", () => {
|
||||||
async function withTempRoot(prefix: string, run: (root: string) => Promise<void>): Promise<void> {
|
|
||||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
||||||
try {
|
|
||||||
await run(root);
|
|
||||||
} finally {
|
|
||||||
await fs.rm(root, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function expectOpenClawHomeDefaults(env: NodeJS.ProcessEnv): void {
|
function expectOpenClawHomeDefaults(env: NodeJS.ProcessEnv): void {
|
||||||
const configuredHome = env.OPENCLAW_HOME;
|
const configuredHome = env.OPENCLAW_HOME;
|
||||||
if (!configuredHome) {
|
if (!configuredHome) {
|
||||||
|
|
@ -107,7 +98,7 @@ describe("state + config path candidates", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prefers ~/.openclaw when it exists and legacy dir is missing", async () => {
|
it("prefers ~/.openclaw when it exists and legacy dir is missing", async () => {
|
||||||
await withTempRoot("openclaw-state-", async (root) => {
|
await withTempDir({ prefix: "openclaw-state-" }, async (root) => {
|
||||||
const newDir = path.join(root, ".openclaw");
|
const newDir = path.join(root, ".openclaw");
|
||||||
await fs.mkdir(newDir, { recursive: true });
|
await fs.mkdir(newDir, { recursive: true });
|
||||||
const resolved = resolveStateDir({} as NodeJS.ProcessEnv, () => root);
|
const resolved = resolveStateDir({} as NodeJS.ProcessEnv, () => root);
|
||||||
|
|
@ -116,7 +107,7 @@ describe("state + config path candidates", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to existing legacy state dir when ~/.openclaw is missing", async () => {
|
it("falls back to existing legacy state dir when ~/.openclaw is missing", async () => {
|
||||||
await withTempRoot("openclaw-state-legacy-", async (root) => {
|
await withTempDir({ prefix: "openclaw-state-legacy-" }, async (root) => {
|
||||||
const legacyDir = path.join(root, ".clawdbot");
|
const legacyDir = path.join(root, ".clawdbot");
|
||||||
await fs.mkdir(legacyDir, { recursive: true });
|
await fs.mkdir(legacyDir, { recursive: true });
|
||||||
const resolved = resolveStateDir({} as NodeJS.ProcessEnv, () => root);
|
const resolved = resolveStateDir({} as NodeJS.ProcessEnv, () => root);
|
||||||
|
|
@ -125,7 +116,7 @@ describe("state + config path candidates", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("CONFIG_PATH prefers existing config when present", async () => {
|
it("CONFIG_PATH prefers existing config when present", async () => {
|
||||||
await withTempRoot("openclaw-config-", async (root) => {
|
await withTempDir({ prefix: "openclaw-config-" }, async (root) => {
|
||||||
const legacyDir = path.join(root, ".openclaw");
|
const legacyDir = path.join(root, ".openclaw");
|
||||||
await fs.mkdir(legacyDir, { recursive: true });
|
await fs.mkdir(legacyDir, { recursive: true });
|
||||||
const legacyPath = path.join(legacyDir, "openclaw.json");
|
const legacyPath = path.join(legacyDir, "openclaw.json");
|
||||||
|
|
@ -137,7 +128,7 @@ describe("state + config path candidates", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("respects state dir overrides when config is missing", async () => {
|
it("respects state dir overrides when config is missing", async () => {
|
||||||
await withTempRoot("openclaw-config-override-", async (root) => {
|
await withTempDir({ prefix: "openclaw-config-override-" }, async (root) => {
|
||||||
const legacyDir = path.join(root, ".openclaw");
|
const legacyDir = path.join(root, ".openclaw");
|
||||||
await fs.mkdir(legacyDir, { recursive: true });
|
await fs.mkdir(legacyDir, { recursive: true });
|
||||||
const legacyConfig = path.join(legacyDir, "openclaw.json");
|
const legacyConfig = path.join(legacyDir, "openclaw.json");
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,10 @@
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||||
import { resolveBoundaryPath, resolveBoundaryPathSync } from "./boundary-path.js";
|
import { resolveBoundaryPath, resolveBoundaryPathSync } from "./boundary-path.js";
|
||||||
import { isPathInside } from "./path-guards.js";
|
import { isPathInside } from "./path-guards.js";
|
||||||
|
|
||||||
async function withTempRoot<T>(prefix: string, run: (root: string) => Promise<T>): Promise<T> {
|
|
||||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
||||||
try {
|
|
||||||
return await run(root);
|
|
||||||
} finally {
|
|
||||||
await fs.rm(root, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSeededRandom(seed: number): () => number {
|
function createSeededRandom(seed: number): () => number {
|
||||||
let state = seed >>> 0;
|
let state = seed >>> 0;
|
||||||
return () => {
|
return () => {
|
||||||
|
|
@ -28,7 +19,7 @@ describe("resolveBoundaryPath", () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await withTempRoot("openclaw-boundary-path-", async (base) => {
|
await withTempDir({ prefix: "openclaw-boundary-path-" }, async (base) => {
|
||||||
const root = path.join(base, "workspace");
|
const root = path.join(base, "workspace");
|
||||||
const targetDir = path.join(root, "target-dir");
|
const targetDir = path.join(root, "target-dir");
|
||||||
const linkPath = path.join(root, "alias");
|
const linkPath = path.join(root, "alias");
|
||||||
|
|
@ -55,7 +46,7 @@ describe("resolveBoundaryPath", () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await withTempRoot("openclaw-boundary-path-", async (base) => {
|
await withTempDir({ prefix: "openclaw-boundary-path-" }, async (base) => {
|
||||||
const root = path.join(base, "workspace");
|
const root = path.join(base, "workspace");
|
||||||
const outside = path.join(base, "outside");
|
const outside = path.join(base, "outside");
|
||||||
const linkPath = path.join(root, "alias-out");
|
const linkPath = path.join(root, "alias-out");
|
||||||
|
|
@ -86,7 +77,7 @@ describe("resolveBoundaryPath", () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await withTempRoot("openclaw-boundary-path-", async (base) => {
|
await withTempDir({ prefix: "openclaw-boundary-path-" }, async (base) => {
|
||||||
const root = path.join(base, "workspace");
|
const root = path.join(base, "workspace");
|
||||||
const outside = path.join(base, "outside");
|
const outside = path.join(base, "outside");
|
||||||
const outsideFile = path.join(outside, "target.txt");
|
const outsideFile = path.join(outside, "target.txt");
|
||||||
|
|
@ -122,7 +113,7 @@ describe("resolveBoundaryPath", () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await withTempRoot("openclaw-boundary-path-", async (base) => {
|
await withTempDir({ prefix: "openclaw-boundary-path-" }, async (base) => {
|
||||||
const root = path.join(base, "workspace");
|
const root = path.join(base, "workspace");
|
||||||
const aliasRoot = path.join(base, "workspace-alias");
|
const aliasRoot = path.join(base, "workspace-alias");
|
||||||
const fileName = "plugin.js";
|
const fileName = "plugin.js";
|
||||||
|
|
@ -153,7 +144,7 @@ describe("resolveBoundaryPath", () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await withTempRoot("openclaw-boundary-path-fuzz-", async (base) => {
|
await withTempDir({ prefix: "openclaw-boundary-path-fuzz-" }, async (base) => {
|
||||||
const root = path.join(base, "workspace");
|
const root = path.join(base, "workspace");
|
||||||
const outside = path.join(base, "outside");
|
const outside = path.join(base, "outside");
|
||||||
const safeTarget = path.join(root, "safe-target");
|
const safeTarget = path.join(root, "safe-target");
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,75 @@
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||||
import { assertNoPathAliasEscape } from "./path-alias-guards.js";
|
import { assertNoPathAliasEscape } from "./path-alias-guards.js";
|
||||||
|
|
||||||
async function withTempRoot<T>(run: (root: string) => Promise<T>): Promise<T> {
|
|
||||||
const base = await fs.mkdtemp(path.join(process.cwd(), "openclaw-path-alias-"));
|
|
||||||
const root = path.join(base, "root");
|
|
||||||
await fs.mkdir(root, { recursive: true });
|
|
||||||
try {
|
|
||||||
return await run(root);
|
|
||||||
} finally {
|
|
||||||
await fs.rm(base, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("assertNoPathAliasEscape", () => {
|
describe("assertNoPathAliasEscape", () => {
|
||||||
it.runIf(process.platform !== "win32")(
|
it.runIf(process.platform !== "win32")(
|
||||||
"rejects broken final symlink targets outside root",
|
"rejects broken final symlink targets outside root",
|
||||||
async () => {
|
async () => {
|
||||||
await withTempRoot(async (root) => {
|
await withTempDir(
|
||||||
const outside = path.join(path.dirname(root), "outside");
|
{ prefix: "openclaw-path-alias-", parentDir: process.cwd(), subdir: "root" },
|
||||||
await fs.mkdir(outside, { recursive: true });
|
async (root) => {
|
||||||
const linkPath = path.join(root, "jump");
|
const outside = path.join(path.dirname(root), "outside");
|
||||||
await fs.symlink(path.join(outside, "owned.txt"), linkPath);
|
await fs.mkdir(outside, { recursive: true });
|
||||||
|
const linkPath = path.join(root, "jump");
|
||||||
|
await fs.symlink(path.join(outside, "owned.txt"), linkPath);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
assertNoPathAliasEscape({
|
assertNoPathAliasEscape({
|
||||||
absolutePath: linkPath,
|
absolutePath: linkPath,
|
||||||
rootPath: root,
|
rootPath: root,
|
||||||
boundaryLabel: "sandbox root",
|
boundaryLabel: "sandbox root",
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(/Symlink escapes sandbox root/);
|
).rejects.toThrow(/Symlink escapes sandbox root/);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it.runIf(process.platform !== "win32")(
|
it.runIf(process.platform !== "win32")(
|
||||||
"allows broken final symlink targets that remain inside root",
|
"allows broken final symlink targets that remain inside root",
|
||||||
async () => {
|
async () => {
|
||||||
await withTempRoot(async (root) => {
|
await withTempDir(
|
||||||
const linkPath = path.join(root, "jump");
|
{ prefix: "openclaw-path-alias-", parentDir: process.cwd(), subdir: "root" },
|
||||||
await fs.symlink(path.join(root, "missing", "owned.txt"), linkPath);
|
async (root) => {
|
||||||
|
const linkPath = path.join(root, "jump");
|
||||||
|
await fs.symlink(path.join(root, "missing", "owned.txt"), linkPath);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
assertNoPathAliasEscape({
|
assertNoPathAliasEscape({
|
||||||
absolutePath: linkPath,
|
absolutePath: linkPath,
|
||||||
rootPath: root,
|
rootPath: root,
|
||||||
boundaryLabel: "sandbox root",
|
boundaryLabel: "sandbox root",
|
||||||
}),
|
}),
|
||||||
).resolves.toBeUndefined();
|
).resolves.toBeUndefined();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it.runIf(process.platform !== "win32")(
|
it.runIf(process.platform !== "win32")(
|
||||||
"rejects broken targets that traverse via an in-root symlink alias",
|
"rejects broken targets that traverse via an in-root symlink alias",
|
||||||
async () => {
|
async () => {
|
||||||
await withTempRoot(async (root) => {
|
await withTempDir(
|
||||||
const outside = path.join(path.dirname(root), "outside");
|
{ prefix: "openclaw-path-alias-", parentDir: process.cwd(), subdir: "root" },
|
||||||
await fs.mkdir(outside, { recursive: true });
|
async (root) => {
|
||||||
await fs.symlink(outside, path.join(root, "hop"));
|
const outside = path.join(path.dirname(root), "outside");
|
||||||
const linkPath = path.join(root, "jump");
|
await fs.mkdir(outside, { recursive: true });
|
||||||
await fs.symlink(path.join("hop", "missing", "owned.txt"), linkPath);
|
await fs.symlink(outside, path.join(root, "hop"));
|
||||||
|
const linkPath = path.join(root, "jump");
|
||||||
|
await fs.symlink(path.join("hop", "missing", "owned.txt"), linkPath);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
assertNoPathAliasEscape({
|
assertNoPathAliasEscape({
|
||||||
absolutePath: linkPath,
|
absolutePath: linkPath,
|
||||||
rootPath: root,
|
rootPath: root,
|
||||||
boundaryLabel: "sandbox root",
|
boundaryLabel: "sandbox root",
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(/Symlink escapes sandbox root/);
|
).rejects.toThrow(/Symlink escapes sandbox root/);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
export async function withTempDir<T>(
|
||||||
|
options: {
|
||||||
|
prefix: string;
|
||||||
|
parentDir?: string;
|
||||||
|
subdir?: string;
|
||||||
|
},
|
||||||
|
run: (dir: string) => Promise<T>,
|
||||||
|
): Promise<T> {
|
||||||
|
const base = await fs.mkdtemp(path.join(options.parentDir ?? os.tmpdir(), options.prefix));
|
||||||
|
const dir = options.subdir ? path.join(base, options.subdir) : base;
|
||||||
|
if (options.subdir) {
|
||||||
|
await fs.mkdir(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await run(dir);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(base, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue