Resolve project-relative paths
The bug everyone hits once: fs.files.get("./data/seed.json") works from the
project root, then breaks the moment a script runs from a different directory
or a cron job runs from /. Relative strings resolve against process.cwd() —
the working directory, not the file the code lives in.
The facade’s fix: anchor a Directory handle once, then let it build child
paths for you.
Anchor once, derive everything
Section titled “Anchor once, derive everything”fs.dir(root) gives you a handle. Its .file(...segments) and
.dir(...segments) build child handles that carry the fully-resolved path —
and the IO methods with them:
import { fs } from "@warlock.js/fs";
const root = fs.dir(process.cwd());
const seed = await root.file("data", "seed.json").getJson();await root.dir("storage").file("state.json").putJson({ ok: true });Segments are joined with node:path, so the same code normalizes separators on
Windows (\) and POSIX (/) — no hand-concatenating with + and /.
Anchor to the current module (ESM)
Section titled “Anchor to the current module (ESM)”When a path must resolve next to the module that references it — a seeder, a template loader — anchor the handle to the file’s own directory instead of the cwd:
import path from "node:path";import { fileURLToPath } from "node:url";import { fs } from "@warlock.js/fs";
const here = fs.dir(path.dirname(fileURLToPath(import.meta.url)));
// Resolves next to THIS file regardless of where the process started.const html = await here.file("templates", "email.html").get();Handles compose
Section titled “Handles compose”Because a handle is just a stable reference to a path (zero IO until you call a method), you can pass the anchored directory around and branch off it wherever you need:
const storage = fs.dir(process.cwd()).dir("storage");
await storage.dir("uploads").ensure();const manifest = storage.file("manifest.json");await manifest.putJson({ version: 1 });Inside a Warlock.js app
Section titled “Inside a Warlock.js app”In a @warlock.js/core project you don’t hand-roll the root at all — core
ships path helpers (rootPath, storagePath, publicPath, uploadsPath, …)
that already know your layout. Feed one straight into a handle:
import { fs } from "@warlock.js/fs";import { storagePath } from "@warlock.js/core";
await fs.file(storagePath("manifest.json")).putJson(manifest);Related
Section titled “Related”- Work with handles — the full
File/Directoryhandle API. - Copy files and folders —
copyTo/moveTotake these directory handles.