@bomb.sh/tools
Version:
The internal dev, build, and lint CLI for Bombshell projects
119 lines (117 loc) • 3.89 kB
JavaScript
import { fileURLToPath, pathToFileURL } from "node:url";
import { mkdtemp, symlink } from "node:fs/promises";
import { tmpdir } from "node:os";
import { sep } from "node:path";
import { NodeHfs } from "@humanfs/node";
import { expect, onTestFinished } from "vitest";
//#region src/test-utils/fixture.ts
const SYMLINK = Symbol("symlink");
function isSymlinkMarker(value) {
return typeof value === "object" && value !== null && SYMLINK in value;
}
function isFileTree(value) {
return typeof value === "object" && value !== null && !Buffer.isBuffer(value) && !Array.isArray(value) && !isSymlinkMarker(value);
}
function scopeHfs(inner, base) {
const r = (p) => new URL(`./${p}`, base);
const r2 = (a, b) => [r(a), r(b)];
return {
text: (p) => inner.text(r(p)),
json: (p) => inner.json(r(p)),
bytes: (p) => inner.bytes(r(p)),
write: (p, c) => inner.write(r(p), c),
append: (p, c) => inner.append(r(p), c),
isFile: (p) => inner.isFile(r(p)),
isDirectory: (p) => inner.isDirectory(r(p)),
createDirectory: (p) => inner.createDirectory(r(p)),
delete: (p) => inner.delete(r(p)),
deleteAll: (p) => inner.deleteAll(r(p)),
list: (p) => inner.list(r(p)),
size: (p) => inner.size(r(p)),
lastModified: (p) => inner.lastModified(r(p)),
copy: (s, d) => inner.copy(...r2(s, d)),
copyAll: (s, d) => inner.copyAll(...r2(s, d)),
move: (s, d) => inner.move(...r2(s, d)),
moveAll: (s, d) => inner.moveAll(...r2(s, d))
};
}
/**
* Create a temporary fixture directory from an inline file tree.
*
* Returns a {@link Fixture} with all `hfs` methods scoped to the fixture root.
*
* @example
* ```ts
* const fixture = await createFixture({
* 'hello.txt': 'hello world',
* 'package.json': { name: 'test', version: '1.0.0' },
* 'icon.png': Buffer.from([0x89, 0x50]),
* src: {
* 'index.ts': 'export default 1',
* },
* 'link.txt': ({ symlink }) => symlink('./hello.txt'),
* 'info.txt': ({ importMeta }) => `Root: ${importMeta.url}`,
* })
*
* const text = await fixture.text('hello.txt')
* const json = await fixture.json('package.json')
* ```
*/
async function createFixture(files) {
const prefix = (expect.getState().currentTestName ?? "bsh").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
const path = await mkdtemp(fileURLToPath(new URL(`${prefix}-`, `file://${tmpdir()}/`)));
const base = pathToFileURL(path + sep);
const inner = new NodeHfs();
const scoped = scopeHfs(inner, base);
const resolve = (...segments) => new URL(`./${segments.join("/")}`, base);
const ctx = {
importMeta: {
url: base.toString(),
filename: fileURLToPath(base),
dirname: fileURLToPath(base),
resolve: (p) => new URL(`./${p}`, base).toString()
},
symlink: (target) => ({
[SYMLINK]: true,
target
})
};
async function writeTree(tree, dir) {
for (const [name, raw] of Object.entries(tree)) {
const url = new URL(name, dir);
if (typeof raw !== "function" && !Buffer.isBuffer(raw) && !Array.isArray(raw) && isFileTree(raw) && !name.includes(".")) {
await inner.createDirectory(url);
await writeTree(raw, new URL(`${url}/`));
continue;
}
const parent = new URL("./", url);
await inner.createDirectory(parent);
const content = typeof raw === "function" ? raw(ctx) : raw;
if (isSymlinkMarker(content)) {
await symlink(content.target, url);
continue;
}
if (Buffer.isBuffer(content)) {
await inner.write(url, content);
continue;
}
if (name.endsWith(".json") && typeof content !== "string") {
await inner.write(url, JSON.stringify(content, null, 2));
continue;
}
await inner.write(url, content);
}
}
await writeTree(files, base);
const cleanup = () => inner.deleteAll(path).then(() => void 0);
onTestFinished(cleanup);
return {
root: base,
resolve,
cleanup,
...scoped
};
}
//#endregion
export { createFixture };
//# sourceMappingURL=fixture.mjs.map