mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(render): scope strict lint to composition (#2261)
* fix(render): scope strict lint to composition * fix(lint): validate explicit composition scope
This commit is contained in:
@@ -807,7 +807,8 @@ export default defineCommand({
|
|||||||
|
|
||||||
// ── Pre-render lint ──────────────────────────────────────────────────
|
// ── Pre-render lint ──────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
const lintResult = await lintProject(project.dir);
|
const explicitEntry = args.composition && args.composition !== "." ? entryFile : undefined;
|
||||||
|
const lintResult = await lintProject(project.dir, explicitEntry);
|
||||||
if (!quiet && (lintResult.totalErrors > 0 || lintResult.totalWarnings > 0)) {
|
if (!quiet && (lintResult.totalErrors > 0 || lintResult.totalWarnings > 0)) {
|
||||||
console.log("");
|
console.log("");
|
||||||
for (const line of formatLintFindings(lintResult, { errorsFirst: true })) console.log(line);
|
for (const line of formatLintFindings(lintResult, { errorsFirst: true })) console.log(line);
|
||||||
|
|||||||
@@ -876,6 +876,43 @@ describe("texture_mask_asset_not_found", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("multiple_root_compositions", () => {
|
describe("multiple_root_compositions", () => {
|
||||||
|
it("scopes lint to an explicit render composition entry", async () => {
|
||||||
|
const project = makeProject(validHtml());
|
||||||
|
const standalone = join(project, "standalone.html");
|
||||||
|
writeFileSync(standalone, validHtml("standalone"));
|
||||||
|
|
||||||
|
const { totalErrors, results } = await lintProject(project, standalone);
|
||||||
|
|
||||||
|
expect(totalErrors).toBe(0);
|
||||||
|
expect(results.map((result) => result.file)).toEqual(["standalone.html"]);
|
||||||
|
expect(
|
||||||
|
results[0]?.result.findings.find((finding) => finding.code === "multiple_root_compositions"),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports findings from an explicit render composition entry", async () => {
|
||||||
|
const project = makeProject(validHtml());
|
||||||
|
const standalone = join(project, "standalone.html");
|
||||||
|
writeFileSync(standalone, htmlWithMissingMediaId());
|
||||||
|
|
||||||
|
const { totalErrors, results } = await lintProject(project, standalone);
|
||||||
|
|
||||||
|
expect(totalErrors).toBeGreaterThan(0);
|
||||||
|
expect(results[0]?.result.findings.some((finding) => finding.code === "media_missing_id")).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects an explicit render composition entry outside the project", async () => {
|
||||||
|
const project = makeProject(validHtml());
|
||||||
|
const outsideDir = tmpProject("outside-entry");
|
||||||
|
dirs.push(outsideDir);
|
||||||
|
const outsideEntry = join(outsideDir, "standalone.html");
|
||||||
|
writeFileSync(outsideEntry, validHtml("standalone"));
|
||||||
|
|
||||||
|
await expect(lintProject(project, outsideEntry)).rejects.toThrow(/outside.*project/i);
|
||||||
|
});
|
||||||
|
|
||||||
it("fires when two HTML files have data-composition-id", async () => {
|
it("fires when two HTML files have data-composition-id", async () => {
|
||||||
const project = makeProject(validHtml());
|
const project = makeProject(validHtml());
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
|
|||||||
@@ -180,8 +180,16 @@ function resolveCssAssetCandidates(
|
|||||||
return resolveLocalAssetCandidates(projectDir, url);
|
return resolveLocalAssetCandidates(projectDir, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function lintProject(projectDir: string): Promise<ProjectLintResult> {
|
export async function lintProject(
|
||||||
const indexPath = resolve(projectDir, "index.html");
|
projectDir: string,
|
||||||
|
entryFile?: string,
|
||||||
|
): Promise<ProjectLintResult> {
|
||||||
|
const indexPath = entryFile ? resolve(entryFile) : resolve(projectDir, "index.html");
|
||||||
|
if (entryFile && !isWithinProjectRoot(projectDir, indexPath)) {
|
||||||
|
throw new Error(`Explicit lint entry is outside the project directory: ${entryFile}`);
|
||||||
|
}
|
||||||
|
const rootFile = relative(resolve(projectDir), indexPath).replace(/\\/g, "/") || "index.html";
|
||||||
|
const rootCompSrcPath = rootFile === "index.html" ? undefined : rootFile;
|
||||||
const results: Array<{ file: string; result: HyperframeLintResult }> = [];
|
const results: Array<{ file: string; result: HyperframeLintResult }> = [];
|
||||||
let totalErrors = 0;
|
let totalErrors = 0;
|
||||||
let totalWarnings = 0;
|
let totalWarnings = 0;
|
||||||
@@ -190,16 +198,16 @@ export async function lintProject(projectDir: string): Promise<ProjectLintResult
|
|||||||
const rootHtml = readFileSync(indexPath, "utf-8");
|
const rootHtml = readFileSync(indexPath, "utf-8");
|
||||||
const rootResult = await lintHyperframeHtml(rootHtml, {
|
const rootResult = await lintHyperframeHtml(rootHtml, {
|
||||||
filePath: indexPath,
|
filePath: indexPath,
|
||||||
externalStyles: collectExternalStyles(projectDir, rootHtml),
|
externalStyles: collectExternalStyles(projectDir, rootHtml, rootCompSrcPath),
|
||||||
});
|
});
|
||||||
results.push({ file: "index.html", result: rootResult });
|
results.push({ file: rootFile, result: rootResult });
|
||||||
totalErrors += rootResult.errorCount;
|
totalErrors += rootResult.errorCount;
|
||||||
totalWarnings += rootResult.warningCount;
|
totalWarnings += rootResult.warningCount;
|
||||||
totalInfos += rootResult.infoCount;
|
totalInfos += rootResult.infoCount;
|
||||||
|
|
||||||
const allHtmlSources: HtmlSource[] = [{ html: rootHtml }];
|
const allHtmlSources: HtmlSource[] = [{ html: rootHtml, compSrcPath: rootCompSrcPath }];
|
||||||
const compositionsDir = resolve(projectDir, "compositions");
|
const compositionsDir = resolve(projectDir, "compositions");
|
||||||
if (existsSync(compositionsDir)) {
|
if (!entryFile && existsSync(compositionsDir)) {
|
||||||
const collectHtmlFiles = (dir: string, rel: string): string[] => {
|
const collectHtmlFiles = (dir: string, rel: string): string[] => {
|
||||||
const out: string[] = [];
|
const out: string[] = [];
|
||||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||||
@@ -239,7 +247,7 @@ export async function lintProject(projectDir: string): Promise<ProjectLintResult
|
|||||||
...lintAudioSrcNotFound(projectDir, allHtmlSources),
|
...lintAudioSrcNotFound(projectDir, allHtmlSources),
|
||||||
...lintMissingLocalAsset(projectDir, allHtmlSources),
|
...lintMissingLocalAsset(projectDir, allHtmlSources),
|
||||||
...lintTextureMaskAssetNotFound(projectDir, allHtmlSources),
|
...lintTextureMaskAssetNotFound(projectDir, allHtmlSources),
|
||||||
...lintMultipleRootCompositions(projectDir),
|
...(!entryFile ? lintMultipleRootCompositions(projectDir) : []),
|
||||||
...lintDuplicateAudioTracks(allHtmlSources),
|
...lintDuplicateAudioTracks(allHtmlSources),
|
||||||
...lintMissingOrEmptySubComposition(projectDir, rootHtml),
|
...lintMissingOrEmptySubComposition(projectDir, rootHtml),
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user