Skip to content
Warlock.js v4.7.0

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.

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:

src/paths.ts
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 /.

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:

src/template-loader.ts
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();

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:

src/branch.ts
const storage = fs.dir(process.cwd()).dir("storage");
await storage.dir("uploads").ensure();
const manifest = storage.file("manifest.json");
await manifest.putJson({ version: 1 });

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:

src/app.ts
import { fs } from "@warlock.js/fs";
import { storagePath } from "@warlock.js/core";
await fs.file(storagePath("manifest.json")).putJson(manifest);