diff --git a/src/files.ts b/src/files.ts index f87874d66..40bd131bc 100644 --- a/src/files.ts +++ b/src/files.ts @@ -3,7 +3,21 @@ import {readdir, stat} from "node:fs/promises"; import {extname, join, normalize, relative} from "node:path"; import {isNodeError} from "./error.js"; -export function canReadSync(path: string): boolean { +// A file is local if it exists in the root folder or a subfolder. +export function isLocalFile(ref: string | null, root: string): boolean { + return ( + typeof ref === "string" && + !/^(\w+:)\/\//.test(ref) && + !normalize(ref).startsWith("../") && + canReadSync(join(root, ref)) + ); +} + +export function pathFromRoot(ref: string | null, root: string): string | null { + return isLocalFile(ref, root) ? join(root, ref!) : null; +} + +function canReadSync(path: string): boolean { try { accessSync(path, constants.R_OK); return statSync(path).isFile(); diff --git a/src/javascript.ts b/src/javascript.ts index aaa17f5fa..9b3893012 100644 --- a/src/javascript.ts +++ b/src/javascript.ts @@ -1,7 +1,6 @@ import {Parser, tokTypes, type Options} from "acorn"; import mime from "mime"; -import {join} from "node:path"; -import {canReadSync} from "./files.js"; +import {isLocalFile} from "./files.js"; import {findAwaits} from "./javascript/awaits.js"; import {findDeclarations} from "./javascript/declarations.js"; import {findFeatures} from "./javascript/features.js"; @@ -50,7 +49,7 @@ export function transpileJavaScript(input: string, options: ParseOptions): Trans const databases = node.features.filter((f) => f.type === "DatabaseClient").map((f) => ({name: f.name})); const files = node.features .filter((f) => f.type === "FileAttachment") - .filter((f) => canReadSync(join(root, f.name))) + .filter((f) => isLocalFile(f.name, root)) .map((f) => ({name: f.name, mimeType: mime.getType(f.name)})); const inputs = Array.from(new Set(node.references.map((r) => r.name))); const output = new Sourcemap(input); diff --git a/src/markdown.ts b/src/markdown.ts index c622601a2..656e22ac6 100644 --- a/src/markdown.ts +++ b/src/markdown.ts @@ -10,8 +10,7 @@ import {type RuleInline} from "markdown-it/lib/parser_inline.js"; import {type default as Renderer, type RenderRule} from "markdown-it/lib/renderer.js"; import mime from "mime"; import {readFile} from "node:fs/promises"; -import {join} from "node:path"; -import {canReadSync} from "./files.js"; +import {pathFromRoot} from "./files.js"; import {computeHash} from "./hash.js"; import {transpileJavaScript, type FileReference, type ImportReference, type Transpile} from "./javascript.js"; @@ -301,9 +300,8 @@ function renderIntoPieces(renderer: Renderer, root: string): Renderer["render"] function normalizePieceHtml(html: string, root: string, context: ParseContext): string { const {document} = parseHTML(html); for (const element of document.querySelectorAll("link[href]") as any as Iterable) { - const href = element.getAttribute("href")!; - if (/^(\w+:)\/\//.test(href)) continue; // absolute url - if (canReadSync(join(root, href))) { + const href = pathFromRoot(element.getAttribute("href"), root); + if (href) { context.files.push({name: href, mimeType: mime.getType(href)}); element.setAttribute("href", `/_file/${href}`); }