slidevjs/slidev · critical · Error

[slidev] Cannot find roots without entry

Error message

[slidev] Cannot find roots without entry

What it means

Thrown by getRoots(entry?) when rootsInfo is still null and no entry argument was supplied. getRoots memoizes after the first successful call; the very first call must receive an entry path so it can derive userRoot, detect global install mode, and locate @slidev/client. Subsequent calls return the cache and need no entry.

Source

Thrown at packages/slidev/node/resolver.ts:434

    return current
  if (await hasWorkspacePackageJSON(current))
    return current

  const dir = dirname(current)
  // reach the fs root
  if (!dir || dir === current)
    return root

  return searchForWorkspaceRoot(dir, root)
}

let rootsInfo: RootsInfo | null = null

export async function getRoots(entry?: string): Promise<RootsInfo> {
  if (rootsInfo)
    return rootsInfo
  if (!entry)
    throw new Error('[slidev] Cannot find roots without entry')
  const userRoot = dirname(entry)
  isInstalledGlobally.value
    = slash(relative(userRoot, process.argv[1])).includes('/.pnpm/')
      // pnpm v11 isolated globals don't expose a `.pnpm/` segment in argv[1]
      // and aren't detected by `is-installed-globally` (which only knows npm
      // and yarn). The cli's bin is symlinked into an install-group
      // `node_modules/` that's outside the user's workspace, so use that as
      // the global-mode signal.
      || (invocationNodeModules != null
        && slash(relative(userRoot, invocationNodeModules)).startsWith('..'))
      || (await import('is-installed-globally')).default
  const clientRoot = await findPkgRoot('@slidev/client', cliRoot, true)
  const closestPkgRoot = dirname(await findClosestPkgJsonPath(userRoot) || userRoot)
  const userPkgJson = await getUserPkgJson(closestPkgRoot)
  const userWorkspaceRoot = await searchForWorkspaceRoot(closestPkgRoot)
  rootsInfo = {
    cliRoot,
    clientRoot,

View on GitHub (pinned to 0d798ace58)

Solutions

  1. Call getRoots(entryPath) once at startup with the absolute path to the entry markdown before any code that may call getRoots() with no argument.
  2. Ensure the CLI entry flow resolves the entry file (resolveEntry) and forwards it into getRoots.
  3. In tests/programmatic use, seed rootsInfo by calling getRoots with a fixture entry path first.

Example fix

// before: first call without entry
const roots = await getRoots() // throws

// after: seed with the entry on first call
const roots = await getRoots(resolve('/abs/path/to/slides.md'))
// later calls can omit entry:
const same = await getRoots()
Defensive patterns

Strategy: validation

Validate before calling

let rootsSeeded = false

async function ensureRoots(entry: string): Promise<RootsInfo> {
  if (!rootsSeeded) {
    await getRoots(entry) // seed the cache on first call
    rootsSeeded = true
  }
  return getRoots() // subsequent calls are safe
}

// at startup:
await ensureRoots(resolve('/abs/path/to/slides.md'))

Type guard

function entryProvided(entry: string | undefined): entry is string {
  return typeof entry === 'string' && entry.length > 0
}

Try / catch

try {
  return await getRoots()
} catch (e) {
  if (e instanceof Error && e.message === '[slidev] Cannot find roots without entry') {
    if (!entryPath) throw e
    return await getRoots(entryPath) // seed on demand
  }
  throw e
}

Prevention

When it happens

Trigger: Calling getRoots() (no argument) before any caller has called getRoots(entryPath) to seed the cache. Happens when an API consumer (e.g. importOptionalDependency) invokes getRoots lazily without the boot path forwarding an entry.

Common situations: Programmatic use of Slidev APIs that omits the entry bootstrap; a plugin that calls getRoots() during module init, before the entry file is known; tests invoking resolver helpers in isolation.

Related errors


AI-assisted analysis of slidevjs/slidev@0d798ace58 (2026-08-12). Data as JSON: /api/errors/b8976a38907a60df. Report an issue: GitHub.