mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
refactor(parsers): share local-asset resolution across lint and studio-server
Moves assetResolution.ts from packages/lint into @hyperframes/parsers behind a ./asset-resolution subpath export, mirroring the ./ff-binaries shape, and points lint's two importers at it. Pure move: no behavior change. Unblocks studio-server consuming the same helpers without a dependency cycle (the CLI already depends on lint, so importing lint from studio-server is not an option).
This commit is contained in:
@@ -1,75 +0,0 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { isAbsolute, posix, relative, resolve } from "node:path";
|
||||
import { decodeUrlPathVariants } from "@hyperframes/parsers/composition";
|
||||
|
||||
/**
|
||||
* Shared local-asset resolution helpers used by both the project-level lint
|
||||
* rules (`project.ts`) and the HEVC preview-codec check
|
||||
* (`hevcPreviewLint.ts`). Split out so the latter doesn't need to import from
|
||||
* `project.ts` (which imports it back to run the rule) — that would be a
|
||||
* circular import within the package.
|
||||
*/
|
||||
|
||||
export function isRemoteOrInlineUrl(url: string): boolean {
|
||||
return /^(https?:|data:|blob:|\/\/|#)/i.test(url);
|
||||
}
|
||||
|
||||
export function cleanAssetUrl(url: string): string {
|
||||
return url.trim().split(/[?#]/, 1)[0] ?? "";
|
||||
}
|
||||
|
||||
export function isWithinProjectRoot(projectDir: string, candidate: string): boolean {
|
||||
const projectRoot = resolve(projectDir);
|
||||
const relativePath = relative(projectRoot, candidate);
|
||||
return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
|
||||
}
|
||||
|
||||
function addCandidate(candidates: string[], candidate: string): void {
|
||||
if (!candidates.includes(candidate)) candidates.push(candidate);
|
||||
}
|
||||
|
||||
export function resolveLocalAssetCandidates(projectDir: string, url: string): string[] {
|
||||
const cleanUrl = cleanAssetUrl(url);
|
||||
const projectRoot = resolve(projectDir);
|
||||
const candidates: string[] = [];
|
||||
|
||||
for (const variant of decodeUrlPathVariants(cleanUrl)) {
|
||||
const projectRelative = variant.startsWith("/") ? variant.slice(1) : variant;
|
||||
const resolved = resolve(projectRoot, projectRelative);
|
||||
if (isWithinProjectRoot(projectRoot, resolved)) {
|
||||
addCandidate(candidates, resolved);
|
||||
continue;
|
||||
}
|
||||
|
||||
const normalized = posix.normalize(projectRelative.replace(/\\/g, "/"));
|
||||
const clamped = normalized.replace(/^(\.\.\/)+/, "");
|
||||
if (clamped && !clamped.startsWith("..")) {
|
||||
addCandidate(candidates, resolve(projectRoot, clamped));
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
export function resolveExistingLocalAsset(
|
||||
projectDir: string,
|
||||
url: string,
|
||||
): { resolved: string; rootRelativePath: string } | null {
|
||||
const projectRoot = resolve(projectDir);
|
||||
const resolved = resolveLocalAssetCandidates(projectRoot, url).find(existsSync);
|
||||
if (!resolved) return null;
|
||||
return { resolved, rootRelativePath: relative(projectRoot, resolved) };
|
||||
}
|
||||
|
||||
function maskRange(src: string, pattern: RegExp): string {
|
||||
return src.replace(pattern, (m) => " ".repeat(m.length));
|
||||
}
|
||||
|
||||
/** Blanks out comments, `<style>`, and `<script>` bodies so tag-scanning
|
||||
* regexes don't false-positive on commented-out or scripted markup. */
|
||||
export function maskNonScannableRanges(html: string): string {
|
||||
let out = maskRange(html, /<!--[\s\S]*?-->/g);
|
||||
out = maskRange(out, /<style\b[^>]*>[\s\S]*?<\/style\b[^>]*>/gi);
|
||||
out = maskRange(out, /<script\b[^>]*>[\s\S]*?<\/script\b[^>]*>/gi);
|
||||
return out;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
isRemoteOrInlineUrl,
|
||||
maskNonScannableRanges,
|
||||
resolveExistingLocalAsset,
|
||||
} from "./assetResolution.js";
|
||||
} from "@hyperframes/parsers/asset-resolution";
|
||||
import type { HyperframeLintFinding } from "./types.js";
|
||||
|
||||
/** Structurally compatible with `project.ts`'s (unexported) `HtmlSource` —
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
maskNonScannableRanges,
|
||||
resolveExistingLocalAsset,
|
||||
resolveLocalAssetCandidates,
|
||||
} from "./assetResolution.js";
|
||||
} from "@hyperframes/parsers/asset-resolution";
|
||||
import { collectLocalVideoCandidates, lintHevcPreviewCodec } from "./hevcPreviewLint.js";
|
||||
import { lintHyperframeHtml } from "./hyperframeLinter.js";
|
||||
import type { HyperframeLintFinding, HyperframeLintResult } from "./types.js";
|
||||
|
||||
Reference in New Issue
Block a user