import { isDeno } from "@commontools/utils/env"; import { ProgramResolver, Source } from "./interface.ts"; import { dirname, join, normalize } from "@std/path/posix"; export class InMemoryProgram implements ProgramResolver { private modules: Record; private _main: string; constructor(main: string, modules: Record) { this.modules = modules; this._main = main; } main(): Promise { const main = this.modules[this._main]; if (!main) { throw new Error(`${this._main} not in modules.`); } return Promise.resolve({ name: this._main, contents: main }); } resolveSource(identifier: string): Promise { const contents = this.modules[identifier]; if (!contents) return Promise.resolve(undefined); return Promise.resolve({ contents, name: identifier }); } } // Resolve a program using the file system. // Deno-only. export class FileSystemProgramResolver implements ProgramResolver { private fsRoot: string; private _main: Source; constructor(mainPath: string, rootPath?: string) { this.fsRoot = rootPath ? normalize(rootPath) : dirname(mainPath); if (rootPath && !mainPath.startsWith(this.fsRoot)) { throw new Error( `Main file "${mainPath}" must be within root directory "${this.fsRoot}".`, ); } this._main = { name: mainPath.substring(this.fsRoot.length), contents: this.#readFile(mainPath), }; } main(): Promise { return Promise.resolve(this._main); } resolveSource(specifier: string): Promise { if (!specifier || specifier[0] !== "/") { return Promise.resolve(undefined); } const absPath = normalize( join(this.fsRoot, specifier.substring(1, specifier.length)), ); // Ensure the resolved path stays within fsRoot if (!absPath.startsWith(this.fsRoot + "/") && absPath !== this.fsRoot) { throw new Error( `Import "${specifier}" resolves outside of root directory "${this.fsRoot}".`, ); } return Promise.resolve({ name: specifier, contents: this.#readFile(absPath), }); } #readFile(path: string): string { if (!isDeno()) { throw new Error( "FileSystemProgramResolver is not supported in this environment.", ); } return Deno.readTextFileSync(path); } } // Resolve a program from HTTP. export class HttpProgramResolver implements ProgramResolver { #mainUrl: URL; #main?: Promise; constructor(main: string | URL) { this.#mainUrl = !(main instanceof URL) ? new URL(main) : main; } main(): Promise { if (!this.#main) { this.#main = this.#fetch(this.#mainUrl); } return this.#main; } resolveSource(specifier: string): Promise { if (!specifier || specifier[0] !== "/") { return Promise.resolve(undefined); } const url = new URL(this.#mainUrl); url.pathname = normalize(specifier); return this.#fetch(url); } async #fetch(url: URL): Promise { const res = await fetch(url); if (!res.ok) { throw new Error( `Failed to fetch ${url}: ${res.status} ${res.statusText}`, ); } const contents = await res.text(); return { name: url.pathname, contents, }; } }