/** * The platform capabilities the editor's routes need, stated in the routes' own * terms rather than as a subset of `node:fs`. * * Everything here is injected, so the same Hono app runs against a developer's * real project directory (the Zuplo CLI) and against an in-memory project (the * per-PR preview Worker). Keeping the seam this narrow is what stops the two * from drifting: a route can't reach for `node:fs` without adding a method * here, and the preview build fails when something does. */ /** * A path that escaped the project root, e.g. `?path=../../../etc/passwd`. * * Rejecting traversal is the port's job rather than each route's, because a * route that forgets is a directory-traversal hole and there is no other * chokepoint every path passes through. */ export declare class UnsafePathError extends Error { readonly path: string; constructor(path: string); } /** * A path that doesn't exist. * * Routes branch on this instead of on `ENOENT`, so a miss reports identically * however the filesystem is implemented — the case an errno-shaped port gets * subtly wrong. */ export declare class PathNotFoundError extends Error { readonly path: string; constructor(path: string); } export type PathType = "file" | "directory"; export interface DirEntry { /** The entry's own name, not a path. */ name: string; type: PathType; } export interface PathStat { type: PathType; mtime: Date; } export interface FileWithStat { content: string; /** The mtime of the content returned alongside it — see `readFileWithStat`. */ mtime: Date; } /** * A project directory the editor can read and write. * * Every path is relative to the project root and slash-separated * (`config/routes.oas.json`), with or without a leading slash. Implementations * reject paths that climb out of the root with `UnsafePathError`. */ export interface FileSystemPort { /** * Reads a file and the mtime describing *that* content, together. * * The pair has to be consistent: the portal saves the returned mtime as * `Last-Modified` and sends it back as `If-Unmodified-Since` to detect * clobbering, so a stat taken separately from the read can describe a * different version of the file and produce phantom conflicts — or miss real * ones. How that's achieved is the adapter's problem (Node holds a single * file handle open across both). * * @throws PathNotFoundError when the file doesn't exist. */ readFileWithStat(path: string): Promise; /** * Lists a directory's immediate children. Order is not significant. * * @throws PathNotFoundError when the directory doesn't exist. */ listDir(path: string): Promise; /** Returns `undefined` when nothing exists at `path`. */ statPath(path: string): Promise; /** * Writes a file, creating any missing parent directories, and returns the * stat it now has — the caller hands that mtime straight back to the client. */ writeFile(path: string, content: string | Uint8Array): Promise; /** @throws PathNotFoundError when `from` doesn't exist. */ rename(from: string, to: string): Promise; /** @throws PathNotFoundError when `path` doesn't exist. */ remove(path: string): Promise; /** Creates a directory and any missing parents. Succeeds if it exists. */ mkdirp(path: string): Promise; } /** * Which external editor the user picked for "open in editor". * * Separate from `FileSystemPort` because this lives outside the project — an * XDG config file under the user's home directory on Node, and nowhere at all * in a Worker, which has no home directory to write to. */ export interface EditorPreferencesPort { /** The saved editor id, or `null` if the user hasn't chosen one. */ get(): Promise; set(editor: string): Promise; } /** * Tells the editor a file changed underneath it, so the SPA can reload it. * * On Node this is chokidar watching the project directory. In the preview it's * the in-memory filesystem reporting its own writes, which is what keeps the * SPA's live-reload path exercised there rather than dormant. */ export interface ChangeNotifier { /** * Registers `listener`, called with a project-relative path on each change. * Returns a function that unsubscribes. */ onChange(listener: (path: string) => void): () => void; } /** * Splits a project-relative path into its segments, rejecting anything that * climbs out of the project or isn't relative to begin with. * * Both adapters route every path through this, so traversal is refused once, at * the boundary, instead of relying on each route to remember. */ export declare function toSafeSegments(path: string): string[];