koala73/worldmonitor · error · Error

cannot read ${dashboardPath} — run: ${BUILD_COMMANDS.dashboa

Error message

cannot read ${dashboardPath} — run: ${BUILD_COMMANDS.dashboard}: ${error.message}

What it means

bundle-budgets.mjs measures the built dashboard bundle sizes by parsing dist/dashboard.html. readDashboardHtml wraps readFileSync of that file; if it cannot be read (missing or unreadable), it throws a remediation-aware error telling the developer to run the dashboard build command first. It exists so budget checks fail with an actionable message instead of a bare ENOENT.

Solutions

  1. Run the dashboard build command (BUILD_COMMANDS.dashboard, e.g. `npm run build:dashboard`) so dist/dashboard.html exists
  2. Verify the distDir argument passed to the script points at the real Vite output directory
  3. Check file permissions on dist/dashboard.html if the build output exists but is unreadable
  4. Re-run a clean full build if dashboard.html exists but is stale/corrupt

Example fix

// before: checking budgets on a clean tree
node scripts/bundle-budgets.mjs --dist dist
// after: build first, then check
npm run build:dashboard && node scripts/bundle-budgets.mjs --dist dist
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync } from 'node:fs';
if (!existsSync(join(distDir, 'dashboard.html'))) throw new Error(`run the dashboard build before budget checks: ${distDir}/dashboard.html missing`);

Type guard

const dashboardBuilt = (distDir) => existsSync(join(distDir, 'dashboard.html'));

Try / catch

try {
  measureBudgets(distDir);
} catch (e) {
  if (e.message.includes('cannot read') && e.message.includes('dashboard.html')) {
    execSync('npm run build:dashboard', { stdio: 'inherit' });
    measureBudgets(distDir);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling initialDashboardAssetNames or deferredDashboardAppDependencies with a distDir where dist/dashboard.html does not exist (build not run, wrong distDir passed, build output cleaned) or is not readable (permissions).

Common situations: CI runs bundle-budget checks before `npm run build:dashboard`; developer checks out a fresh clone and runs the budget script directly; dist/ was wiped by `clean` but not rebuilt; an incorrect --dist argument points at an empty directory.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/d3626c33b80c23c0. Report an issue: GitHub.

Appendix: source

Thrown at scripts/bundle-budgets.mjs:167

/** '/assets/main-x.js', 'assets/main-x.js?v=1' -> 'main-x.js'; null outside assets/. */
function assetFileNameFromUrl(url) {
  const path = url.split(/[?#]/, 1)[0];
  const marker = path.lastIndexOf('/assets/');
  const fileName = marker >= 0
    ? path.slice(marker + '/assets/'.length)
    : path.startsWith('assets/')
      ? path.slice('assets/'.length)
      : null;
  return fileName && !fileName.includes('/') ? fileName : null;
}

function readDashboardHtml(distDir) {
  const dashboardPath = join(distDir, 'dashboard.html');
  try {
    return { dashboardPath, html: readFileSync(dashboardPath, 'utf8') };
  } catch (error) {
    throw new Error(`cannot read ${dashboardPath} — run: ${BUILD_COMMANDS.dashboard}: ${error.message}`);
  }
}

export function initialDashboardAssetNames(distDir) {
  const { dashboardPath, html } = readDashboardHtml(distDir);
  const assets = new Set();
  for (const match of html.matchAll(/(?:src|href)=["']([^"']+\.js(?:[?#][^"']*)?)["']/gi)) {
    const fileName = assetFileNameFromUrl(match[1]);
    if (fileName) assets.add(fileName);
  }
  if (assets.size === 0) {
    throw new Error(`no initial JS assets referenced by ${dashboardPath} — run: ${BUILD_COMMANDS.dashboard}`);
  }
  return [...assets].sort();
}

/**
 * The asset files Vite preloads for the dashboard entry's deferred

View on GitHub (pinned to 7d06c8633d)