{"record":{"id":"be898f6064962bd0","repo":"heygen-com/hyperframes","slug":"project-archive-must-include-index-html-at-the-roo","errorCode":null,"errorMessage":"Project archive must include index.html at the root. Check that .hyperframesignore does not exclude it.","messagePattern":"Project archive must include index\\.html at the root\\. Check that \\.hyperframesignore does not exclude it\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/cli/src/utils/publishProject.ts","lineNumber":491,"sourceCode":"    }\n  }\n\n  return ctx.externalMap.size;\n}\n\n/**\n * Walk the project dir, read every non-ignored file, and localize external\n * (out-of-project) asset references. Returns the in-memory archive file map —\n * the seam `publish.ts` hooks a proxy-baking transform into (U6) between this\n * and `zipPublishFileMap` below. `cloud render` never sees this seam: it\n * keeps calling `createPublishArchive` directly.\n */\nexport function buildPublishFileMap(projectDir: string): Map<string, Buffer> {\n  const absProjectDir = resolve(projectDir);\n  const filePaths: string[] = [];\n  collectProjectFiles(absProjectDir, absProjectDir, filePaths, createProjectIgnore(absProjectDir));\n  if (!filePaths.includes(\"index.html\")) {\n    throw new Error(\n      \"Project archive must include index.html at the root. Check that .hyperframesignore does not exclude it.\",\n    );\n  }\n\n  const fileContents = new Map<string, Buffer>();\n  for (const filePath of filePaths) {\n    fileContents.set(filePath, readFileSync(join(absProjectDir, filePath)));\n  }\n\n  localizeExternalAssets(absProjectDir, fileContents);\n  return fileContents;\n}\n\n/** Zip an in-memory archive file map (from `buildPublishFileMap`, optionally\n * transformed in between, e.g. by proxy baking) into the final archive buffer. */\nexport function zipPublishFileMap(fileContents: Map<string, Buffer>): PublishArchiveResult {\n  const archive = new AdmZip();\n  for (const [filePath, content] of fileContents) {","sourceCodeStart":473,"sourceCodeEnd":509,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/utils/publishProject.ts#L473-L509","documentation":"buildPublishFileMap walks the project dir (respecting .hyperframesignore, IGNORED_DIRS like node_modules/.git/dist, and DEFAULT_PROJECT_IGNORE like /renders/ /snapshots/) and collects relative file paths. If 'index.html' is not among the collected relative paths, publish cannot proceed — a published project must have an entry composition. The message points at .hyperframesignore because that is the usual culprit: an overly broad ignore pattern excluded index.html.","triggerScenarios":"index.html is missing from the project root entirely; .hyperframesignore contains a pattern that matches index.html (e.g. `/*.html`, `/index.*`, `index.html`); index.html exists only in a subdirectory (the walker records relative paths, so a nested index.html becomes 'subdir/index.html', not 'index.html'); the project root passed to publish is the wrong directory.","commonSituations":"User added `*.html` or `/index.html` to .hyperframesignore to exclude build artifacts, accidentally excluding the entry; a monorepo where the publish root was set to the workspace, not the package; an index.html that lives in src/ but publish is run from the parent; a generated project where index.html was gitignored and not checked out.","solutions":["Confirm index.html exists at the ROOT of the directory passed to publish: ls <project-dir>/index.html.","Inspect .hyperframesignore for patterns matching index.html (e.g. `*.html`, `/index.*`) and remove/narrow them.","Run publish from the directory that actually contains index.html.","If index.html is generated, ensure the generation step runs before publish."],"exampleFix":"# .hyperframesignore before (excludes index.html)\n*.html\n# after\n/renders/*.html\n/snapshots/*.html","handlingStrategy":"validation","validationCode":"import { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport ignore from 'ignore';\n\nfunction ensureIndexHtmlIncluded(projectDir: string): void {\n  const abs = resolve(projectDir, 'index.html');\n  if (!existsSync(abs)) throw new Error('index.html missing from project root');\n  const ig = ignore().add(readFileSync(resolve(projectDir, '.hyperframesignore'), 'utf-8').catch?.() ?? '');\n  if (ig.ignores('index.html')) throw new Error('.hyperframesignore excludes index.html');\n}","typeGuard":null,"tryCatchPattern":"try {\n  return buildPublishFileMap(projectDir);\n} catch (err) {\n  if (err instanceof Error && /must include index.html at the root/.test(err.message)) {\n    // surface the .hyperframesignore contents so the user can find the offending pattern\n    console.error(err.message, '\\nCheck .hyperframesignore patterns.');\n  } else throw err;\n}","preventionTips":["Never put a bare `*.html` in .hyperframesignore — scope excludes to subfolders like /renders/.","Confirm `ls <project>/index.html` succeeds before publishing.","Run `npx hyperframes lint` before publish — it flags a missing entry file early."],"tags":["publish","archive","ignore-file","configuration","filesystem"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}