| 176 | } |
| 177 | |
| 178 | export async function lintProject(projectDir: string): Promise<ProjectLintResult> { |
| 179 | const indexPath = resolve(projectDir, "index.html"); |
| 180 | const results: Array<{ file: string; result: HyperframeLintResult }> = []; |
| 181 | let totalErrors = 0; |
| 182 | let totalWarnings = 0; |
| 183 | let totalInfos = 0; |
| 184 | |
| 185 | const rootHtml = readFileSync(indexPath, "utf-8"); |
| 186 | const rootResult = await lintHyperframeHtml(rootHtml, { |
| 187 | filePath: indexPath, |
| 188 | externalStyles: collectExternalStyles(projectDir, rootHtml), |
| 189 | }); |
| 190 | results.push({ file: "index.html", result: rootResult }); |
| 191 | totalErrors += rootResult.errorCount; |
| 192 | totalWarnings += rootResult.warningCount; |
| 193 | totalInfos += rootResult.infoCount; |
| 194 | |
| 195 | const allHtmlSources: HtmlSource[] = [{ html: rootHtml }]; |
| 196 | const compositionsDir = resolve(projectDir, "compositions"); |
| 197 | if (existsSync(compositionsDir)) { |
| 198 | const collectHtmlFiles = (dir: string, rel: string): string[] => { |
| 199 | const out: string[] = []; |
| 200 | for (const entry of readdirSync(dir, { withFileTypes: true })) { |
| 201 | const relPath = rel ? `${rel}/${entry.name}` : entry.name; |
| 202 | if (entry.isDirectory()) out.push(...collectHtmlFiles(join(dir, entry.name), relPath)); |
| 203 | else if (entry.isFile() && entry.name.endsWith(".html")) out.push(relPath); |
| 204 | } |
| 205 | return out; |
| 206 | }; |
| 207 | const files = collectHtmlFiles(compositionsDir, "").sort(); |
| 208 | for (const file of files) { |
| 209 | const filePath = join(compositionsDir, file); |
| 210 | const html = readFileSync(filePath, "utf-8"); |
| 211 | const compSrcPath = `compositions/${file}`; |
| 212 | allHtmlSources.push({ html, compSrcPath }); |
| 213 | const result = await lintHyperframeHtml(html, { |
| 214 | filePath, |
| 215 | isSubComposition: true, |
| 216 | externalStyles: collectExternalStyles(projectDir, html, compSrcPath), |
| 217 | }); |
| 218 | results.push({ file: `compositions/${file}`, result }); |
| 219 | totalErrors += result.errorCount; |
| 220 | totalWarnings += result.warningCount; |
| 221 | totalInfos += result.infoCount; |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | const projectFindings = [ |
| 226 | ...lintProjectAudioFiles(projectDir, allHtmlSources), |
| 227 | ...lintAudioSrcNotFound(projectDir, allHtmlSources), |
| 228 | ...lintMissingLocalAsset(projectDir, allHtmlSources), |
| 229 | ...lintTextureMaskAssetNotFound(projectDir, allHtmlSources), |
| 230 | ...lintMultipleRootCompositions(projectDir), |
| 231 | ...lintDuplicateAudioTracks(allHtmlSources), |
| 232 | ...lintMissingOrEmptySubComposition(projectDir, rootHtml), |
| 233 | ]; |
| 234 | if (projectFindings.length > 0) { |
| 235 | for (const finding of projectFindings) { |