@hyperframes/lint 0.9.12 → 0.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +67 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +68 -1
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -1824,6 +1824,71 @@ function findLinkedClipFindings(ctx) {
|
|
|
1824
1824
|
return findings;
|
|
1825
1825
|
}
|
|
1826
1826
|
|
|
1827
|
+
// src/rules/audioAttributes.ts
|
|
1828
|
+
var AUDIO_ATTRIBUTES = {
|
|
1829
|
+
"data-automation": { list: "lanes", noun: "automation", item: "Lane" },
|
|
1830
|
+
"data-fx-chain": { list: "nodes", noun: "chain", item: "Node" }
|
|
1831
|
+
};
|
|
1832
|
+
var AUDIO_ATTRIBUTE_VERSION = 1;
|
|
1833
|
+
function audioAttributeShapeError(attr, json) {
|
|
1834
|
+
const { list, noun, item } = AUDIO_ATTRIBUTES[attr];
|
|
1835
|
+
const parsed = parseEnvelope(json);
|
|
1836
|
+
if (typeof parsed === "string") return parsed;
|
|
1837
|
+
if (parsed.version !== AUDIO_ATTRIBUTE_VERSION) {
|
|
1838
|
+
return `unsupported ${noun} version: ${String(parsed.version)}`;
|
|
1839
|
+
}
|
|
1840
|
+
const entries = parsed[list];
|
|
1841
|
+
if (!Array.isArray(entries)) return `it is missing a \`${list}\` array`;
|
|
1842
|
+
for (const [i, entry] of entries.entries()) {
|
|
1843
|
+
const reason = entryShapeError(attr, item, i, entry);
|
|
1844
|
+
if (reason) return reason;
|
|
1845
|
+
}
|
|
1846
|
+
return null;
|
|
1847
|
+
}
|
|
1848
|
+
function parseEnvelope(json) {
|
|
1849
|
+
let raw;
|
|
1850
|
+
try {
|
|
1851
|
+
raw = JSON.parse(json);
|
|
1852
|
+
} catch (err) {
|
|
1853
|
+
return `it is not valid JSON (${err.message})`;
|
|
1854
|
+
}
|
|
1855
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
1856
|
+
return "it is not a JSON object";
|
|
1857
|
+
return raw;
|
|
1858
|
+
}
|
|
1859
|
+
function entryShapeError(attr, item, i, entry) {
|
|
1860
|
+
if (typeof entry !== "object" || entry === null) return `${item} ${i} is not an object`;
|
|
1861
|
+
const fields = entry;
|
|
1862
|
+
if (attr === "data-fx-chain") {
|
|
1863
|
+
return typeof fields.type === "string" ? null : `Node ${i} has no effect \`type\``;
|
|
1864
|
+
}
|
|
1865
|
+
if (typeof fields.target !== "string") return `Lane ${i} has no \`target\``;
|
|
1866
|
+
if (!Array.isArray(fields.points)) return `Lane ${i} is missing a \`points\` array`;
|
|
1867
|
+
return null;
|
|
1868
|
+
}
|
|
1869
|
+
function findUnreadableAudioAttributeFindings(ctx) {
|
|
1870
|
+
const findings = [];
|
|
1871
|
+
for (const tag of ctx.tags) {
|
|
1872
|
+
for (const attr of Object.keys(AUDIO_ATTRIBUTES)) {
|
|
1873
|
+
const value = readDecodedAttr(tag.raw, attr);
|
|
1874
|
+
if (value === null || value === "") continue;
|
|
1875
|
+
const reason = audioAttributeShapeError(attr, value);
|
|
1876
|
+
if (!reason) continue;
|
|
1877
|
+
const id = readAttr(tag.raw, "id");
|
|
1878
|
+
const { list } = AUDIO_ATTRIBUTES[attr];
|
|
1879
|
+
findings.push({
|
|
1880
|
+
code: "audio_attribute_unreadable",
|
|
1881
|
+
severity: "error",
|
|
1882
|
+
message: `${id ? `#${id}` : `<${tag.name}>`} has a \`${attr}\` the render cannot read: ${reason}. The render fails on it, locally and in the cloud.`,
|
|
1883
|
+
elementId: id || void 0,
|
|
1884
|
+
fixHint: `Write it as {"version": ${AUDIO_ATTRIBUTE_VERSION}, "${list}": [...]}, or remove the attribute.`,
|
|
1885
|
+
snippet: truncateSnippet(tag.raw)
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
return findings;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1827
1892
|
// src/rules/media.ts
|
|
1828
1893
|
function tweensVolumeInSameCall(script, id) {
|
|
1829
1894
|
const selector = new RegExp(`#${escapeRegExp(id)}(?![\\w-])`, "g");
|
|
@@ -2556,7 +2621,8 @@ var mediaRules = [
|
|
|
2556
2621
|
findAudioGroupCarveAttrFindings,
|
|
2557
2622
|
// audio_group_holds_elements
|
|
2558
2623
|
findAudioGroupHoldsElementsFindings,
|
|
2559
|
-
findLinkedClipFindings
|
|
2624
|
+
findLinkedClipFindings,
|
|
2625
|
+
findUnreadableAudioAttributeFindings
|
|
2560
2626
|
];
|
|
2561
2627
|
function findVolumeTweenOverridesGainFindings(ctx) {
|
|
2562
2628
|
const boosted = ctx.tags.filter((tag) => isMediaTag(tag.name)).map((tag) => ({ tag, volume: Number(readAttr(tag.raw, "data-volume") ?? "1") })).filter((entry) => Number.isFinite(entry.volume) && entry.volume !== 1).filter((entry) => !readDecodedAttr(entry.tag.raw, "data-automation")).map((entry) => ({ ...entry, id: readAttr(entry.tag.raw, "id") })).filter((entry) => Boolean(entry.id));
|
package/dist/index.d.ts
CHANGED
|
@@ -109,4 +109,11 @@ interface ProjectLintResult {
|
|
|
109
109
|
declare function collectExternalStyles(projectDir: string, html: string, compSrcPath?: string): NonNullable<HyperframeLinterOptions["externalStyles"]>;
|
|
110
110
|
declare function lintProject(projectDir: string, entryFile?: string, hostOptions?: Pick<HyperframeLinterOptions, "host" | "isSubComposition">): Promise<ProjectLintResult>;
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
type AudioAttribute = "data-automation" | "data-fx-chain";
|
|
113
|
+
/**
|
|
114
|
+
* Why the renderer would refuse an audio attribute's value before reading any lane or effect, or null when the
|
|
115
|
+
* envelope reads. Lane targets and effect types are left to the render: they need core's effect registry.
|
|
116
|
+
*/
|
|
117
|
+
declare function audioAttributeShapeError(attr: AudioAttribute, json: string): string | null;
|
|
118
|
+
|
|
119
|
+
export { type AudioAttribute, type HyperframeLintFinding, type HyperframeLintResult, type HyperframeLintSeverity, type HyperframeLinterOptions, LINT_RULE_COUNT, LINT_RULE_GROUP_COUNTS, type LintTimings, type ProjectLintResult, audioAttributeShapeError, collectExternalStyles, lintHyperframeHtml, lintMediaUrls, lintProject, shouldBlockRender };
|
package/dist/index.js
CHANGED
|
@@ -1830,6 +1830,71 @@ function findLinkedClipFindings(ctx) {
|
|
|
1830
1830
|
return findings;
|
|
1831
1831
|
}
|
|
1832
1832
|
|
|
1833
|
+
// src/rules/audioAttributes.ts
|
|
1834
|
+
var AUDIO_ATTRIBUTES = {
|
|
1835
|
+
"data-automation": { list: "lanes", noun: "automation", item: "Lane" },
|
|
1836
|
+
"data-fx-chain": { list: "nodes", noun: "chain", item: "Node" }
|
|
1837
|
+
};
|
|
1838
|
+
var AUDIO_ATTRIBUTE_VERSION = 1;
|
|
1839
|
+
function audioAttributeShapeError(attr, json) {
|
|
1840
|
+
const { list, noun, item } = AUDIO_ATTRIBUTES[attr];
|
|
1841
|
+
const parsed = parseEnvelope(json);
|
|
1842
|
+
if (typeof parsed === "string") return parsed;
|
|
1843
|
+
if (parsed.version !== AUDIO_ATTRIBUTE_VERSION) {
|
|
1844
|
+
return `unsupported ${noun} version: ${String(parsed.version)}`;
|
|
1845
|
+
}
|
|
1846
|
+
const entries = parsed[list];
|
|
1847
|
+
if (!Array.isArray(entries)) return `it is missing a \`${list}\` array`;
|
|
1848
|
+
for (const [i, entry] of entries.entries()) {
|
|
1849
|
+
const reason = entryShapeError(attr, item, i, entry);
|
|
1850
|
+
if (reason) return reason;
|
|
1851
|
+
}
|
|
1852
|
+
return null;
|
|
1853
|
+
}
|
|
1854
|
+
function parseEnvelope(json) {
|
|
1855
|
+
let raw;
|
|
1856
|
+
try {
|
|
1857
|
+
raw = JSON.parse(json);
|
|
1858
|
+
} catch (err) {
|
|
1859
|
+
return `it is not valid JSON (${err.message})`;
|
|
1860
|
+
}
|
|
1861
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
1862
|
+
return "it is not a JSON object";
|
|
1863
|
+
return raw;
|
|
1864
|
+
}
|
|
1865
|
+
function entryShapeError(attr, item, i, entry) {
|
|
1866
|
+
if (typeof entry !== "object" || entry === null) return `${item} ${i} is not an object`;
|
|
1867
|
+
const fields = entry;
|
|
1868
|
+
if (attr === "data-fx-chain") {
|
|
1869
|
+
return typeof fields.type === "string" ? null : `Node ${i} has no effect \`type\``;
|
|
1870
|
+
}
|
|
1871
|
+
if (typeof fields.target !== "string") return `Lane ${i} has no \`target\``;
|
|
1872
|
+
if (!Array.isArray(fields.points)) return `Lane ${i} is missing a \`points\` array`;
|
|
1873
|
+
return null;
|
|
1874
|
+
}
|
|
1875
|
+
function findUnreadableAudioAttributeFindings(ctx) {
|
|
1876
|
+
const findings = [];
|
|
1877
|
+
for (const tag of ctx.tags) {
|
|
1878
|
+
for (const attr of Object.keys(AUDIO_ATTRIBUTES)) {
|
|
1879
|
+
const value = readDecodedAttr(tag.raw, attr);
|
|
1880
|
+
if (value === null || value === "") continue;
|
|
1881
|
+
const reason = audioAttributeShapeError(attr, value);
|
|
1882
|
+
if (!reason) continue;
|
|
1883
|
+
const id = readAttr(tag.raw, "id");
|
|
1884
|
+
const { list } = AUDIO_ATTRIBUTES[attr];
|
|
1885
|
+
findings.push({
|
|
1886
|
+
code: "audio_attribute_unreadable",
|
|
1887
|
+
severity: "error",
|
|
1888
|
+
message: `${id ? `#${id}` : `<${tag.name}>`} has a \`${attr}\` the render cannot read: ${reason}. The render fails on it, locally and in the cloud.`,
|
|
1889
|
+
elementId: id || void 0,
|
|
1890
|
+
fixHint: `Write it as {"version": ${AUDIO_ATTRIBUTE_VERSION}, "${list}": [...]}, or remove the attribute.`,
|
|
1891
|
+
snippet: truncateSnippet(tag.raw)
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return findings;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1833
1898
|
// src/rules/media.ts
|
|
1834
1899
|
function tweensVolumeInSameCall(script, id) {
|
|
1835
1900
|
const selector = new RegExp(`#${escapeRegExp(id)}(?![\\w-])`, "g");
|
|
@@ -2562,7 +2627,8 @@ var mediaRules = [
|
|
|
2562
2627
|
findAudioGroupCarveAttrFindings,
|
|
2563
2628
|
// audio_group_holds_elements
|
|
2564
2629
|
findAudioGroupHoldsElementsFindings,
|
|
2565
|
-
findLinkedClipFindings
|
|
2630
|
+
findLinkedClipFindings,
|
|
2631
|
+
findUnreadableAudioAttributeFindings
|
|
2566
2632
|
];
|
|
2567
2633
|
function findVolumeTweenOverridesGainFindings(ctx) {
|
|
2568
2634
|
const boosted = ctx.tags.filter((tag) => isMediaTag(tag.name)).map((tag) => ({ tag, volume: Number(readAttr(tag.raw, "data-volume") ?? "1") })).filter((entry) => Number.isFinite(entry.volume) && entry.volume !== 1).filter((entry) => !readDecodedAttr(entry.tag.raw, "data-automation")).map((entry) => ({ ...entry, id: readAttr(entry.tag.raw, "id") })).filter((entry) => Boolean(entry.id));
|
|
@@ -8134,6 +8200,7 @@ function isSnippetFragment(html) {
|
|
|
8134
8200
|
export {
|
|
8135
8201
|
LINT_RULE_COUNT,
|
|
8136
8202
|
LINT_RULE_GROUP_COUNTS,
|
|
8203
|
+
audioAttributeShapeError,
|
|
8137
8204
|
collectExternalStyles,
|
|
8138
8205
|
lintHyperframeHtml,
|
|
8139
8206
|
lintMediaUrls,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/lint",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.13",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/heygen-com/hyperframes",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"linkedom": "^0.18.12",
|
|
36
36
|
"postcss": "^8.5.8",
|
|
37
37
|
"postcss-selector-parser": "^7.1.4",
|
|
38
|
-
"@hyperframes/parsers": "0.9.
|
|
38
|
+
"@hyperframes/parsers": "0.9.13"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.0.10",
|