Skip to content

Commit 328bba0

Browse files
fix: make registry blocks pass lint errors (#564)
1 parent c0a74c1 commit 328bba0

23 files changed

Lines changed: 121 additions & 29 deletions

File tree

packages/cli/src/commands/add.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,9 @@ describe("runAdd (integration, mocked registry)", () => {
181181
expect(result.type).toBe("hyperframes:block");
182182
expect(result.written).toHaveLength(1);
183183
expect(existsSync(join(dir, "compositions/my-block.html"))).toBe(true);
184-
expect(readFileSync(join(dir, "compositions/my-block.html"), "utf-8")).toContain(
185-
"my-block.html",
186-
);
184+
const installed = readFileSync(join(dir, "compositions/my-block.html"), "utf-8");
185+
expect(installed).toContain("<!-- hyperframes-registry-item: my-block -->");
186+
expect(installed).toContain("my-block.html");
187187
expect(result.snippet).toContain("compositions/my-block.html");
188188
} finally {
189189
rmSync(dir, { recursive: true, force: true });

packages/cli/src/registry/installer.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* runtime to reject traversal even if the registry JSON schema was bypassed.
77
*/
88

9+
import { readFileSync, writeFileSync } from "node:fs";
910
import { resolve, relative, isAbsolute } from "node:path";
1011
import type { FileTarget, RegistryItem } from "@hyperframes/core";
1112
import { fetchItemFile, DEFAULT_REGISTRY_URL } from "./remote.js";
@@ -45,6 +46,22 @@ export function assertSafeTarget(destDir: string, target: string): void {
4546
}
4647
}
4748

49+
function isInstalledRegistryBlockComposition(item: RegistryItem, file: FileTarget): boolean {
50+
return (
51+
item.type === "hyperframes:block" &&
52+
file.type === "hyperframes:composition" &&
53+
file.target.toLowerCase().endsWith(".html")
54+
);
55+
}
56+
57+
function addRegistryItemMarker(source: string, item: RegistryItem): string {
58+
if (/^\s*<!--\s*hyperframes-registry-item:[^>]*-->/i.test(source.slice(0, 512))) {
59+
return source;
60+
}
61+
62+
return `<!-- hyperframes-registry-item: ${item.name} -->\n${source}`;
63+
}
64+
4865
/**
4966
* Install a resolved `RegistryItem` into `destDir` by fetching each file in
5067
* parallel and writing it to its validated target path.
@@ -65,6 +82,10 @@ export async function installItem(
6582
item.files.map(async (file: FileTarget) => {
6683
const destPath = resolve(destDir, file.target);
6784
await fetchItemFile(item, file, destPath, baseUrl);
85+
if (isInstalledRegistryBlockComposition(item, file)) {
86+
const source = readFileSync(destPath, "utf-8");
87+
writeFileSync(destPath, addRegistryItemMarker(source, item), "utf-8");
88+
}
6889
return destPath;
6990
}),
7091
);

packages/core/src/lint/rules/composition.test.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,45 @@ describe("composition rules", () => {
3535
expect(finding).toBeUndefined();
3636
});
3737

38-
it("warns on large HTML files regardless of path", () => {
38+
it("does not warn for large registry source block files", () => {
3939
const html = Array.from({ length: 301 }, (_, i) =>
4040
i === 0 ? "<html><body>" : `<!-- filler ${i} -->`,
4141
).join("\n");
4242

4343
const result = lintHyperframeHtml(html, {
44-
filePath: "/project/registry/blocks/data-chart.html",
44+
filePath: "/project/registry/blocks/data-chart/data-chart.html",
45+
});
46+
const finding = result.findings.find((f) => f.code === "composition_file_too_large");
47+
expect(finding).toBeUndefined();
48+
});
49+
50+
it("warns for large installed block composition files", () => {
51+
const html = Array.from({ length: 301 }, (_, i) =>
52+
i === 0 ? "<html><body>" : `<!-- filler ${i} -->`,
53+
).join("\n");
54+
55+
const result = lintHyperframeHtml(html, {
56+
filePath: "/project/compositions/data-chart.html",
4557
});
4658
const finding = result.findings.find((f) => f.code === "composition_file_too_large");
4759
expect(finding).toBeDefined();
4860
expect(finding?.severity).toBe("warning");
4961
});
5062

63+
it("does not warn for large registry-installed block composition files", () => {
64+
const html =
65+
"<!-- hyperframes-registry-item: data-chart -->\n" +
66+
Array.from({ length: 300 }, (_, i) =>
67+
i === 0 ? "<html><body>" : `<!-- filler ${i} -->`,
68+
).join("\n");
69+
70+
const result = lintHyperframeHtml(html, {
71+
filePath: "/project/compositions/data-chart.html",
72+
});
73+
const finding = result.findings.find((f) => f.code === "composition_file_too_large");
74+
expect(finding).toBeUndefined();
75+
});
76+
5177
it("uses nested split copy for large sub-composition files", () => {
5278
const html = Array.from({ length: 301 }, (_, i) =>
5379
i === 0 ? "<html><body>" : `<!-- filler ${i} -->`,

packages/core/src/lint/rules/composition.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ function countPhysicalLines(source: string): number {
1515
return withoutFinalNewline.split("\n").length;
1616
}
1717

18+
function isRegistrySourceFile(filePath?: string): boolean {
19+
if (!filePath) return false;
20+
21+
const normalized = filePath.replace(/\\/g, "/");
22+
return /(?:^|\/)registry\/blocks\/([^/]+)\/\1\.html$/i.test(normalized);
23+
}
24+
25+
function isRegistryInstalledFile(rawSource: string): boolean {
26+
return /^\s*<!--\s*hyperframes-registry-item:[^>]*-->/i.test(rawSource.slice(0, 512));
27+
}
28+
1829
function isCompositionRootOrMount(rawTag: string): boolean {
1930
return Boolean(
2031
readAttr(rawTag, "data-composition-id") || readAttr(rawTag, "data-composition-src"),
@@ -24,6 +35,8 @@ function isCompositionRootOrMount(rawTag: string): boolean {
2435
export const compositionRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> = [
2536
// composition_file_too_large
2637
({ rawSource, options }) => {
38+
if (isRegistrySourceFile(options.filePath) || isRegistryInstalledFile(rawSource)) return [];
39+
2740
const lineCount = countPhysicalLines(rawSource);
2841
if (lineCount <= MAX_COMPOSITION_LINES) return [];
2942

registry/blocks/app-showcase/app-showcase.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
data-composition-id="app-showcase"
2626
data-width="1920"
2727
data-height="1080"
28+
data-start="0"
2829
data-duration="5.5"
2930
>
3031
<!-- Background -->

registry/blocks/chromatic-radial-split/chromatic-radial-split.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@
190190

191191
<div
192192
id="driver"
193+
class="clip"
193194
data-start="0"
194195
data-duration="4"
195196
data-track-index="0"

registry/blocks/cinematic-zoom/cinematic-zoom.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@
190190

191191
<div
192192
id="driver"
193+
class="clip"
193194
data-start="0"
194195
data-duration="4"
195196
data-track-index="0"

registry/blocks/cross-warp-morph/cross-warp-morph.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@
190190

191191
<div
192192
id="driver"
193+
class="clip"
193194
data-start="0"
194195
data-duration="4"
195196
data-track-index="0"

registry/blocks/data-chart/data-chart.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
data-composition-id="data-chart"
3434
data-width="1920"
3535
data-height="1080"
36+
data-start="0"
3637
data-duration="15"
3738
>
3839
<div class="chart-container">

registry/blocks/domain-warp-dissolve/domain-warp-dissolve.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@
190190

191191
<div
192192
id="driver"
193+
class="clip"
193194
data-start="0"
194195
data-duration="4"
195196
data-track-index="0"

0 commit comments

Comments
 (0)