{"record":{"id":"ed7a3d1345d9ab59","repo":"JuliusBrussee/caveman","slug":"caveman-build-computed-source-dependency-is-not-l","errorCode":null,"errorMessage":"caveman build: computed source dependency is not lockable in ${JSON.stringify(path)}","messagePattern":"caveman build: computed source dependency is not lockable in (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/source-graph.ts","lineNumber":285,"sourceCode":"  return child === \"\" || (!isAbsolute(child) && child !== \"..\" &&\n    !child.startsWith(\"../\") && !child.startsWith(\"..\\\\\"));\n}\n\nfunction esmSourceSpecifiers(source: string, path: string): string[] {\n  let imports: ReturnType<typeof parse>[0];\n  try {\n    [imports] = parse(source, path);\n  } catch (error) {\n    throw new Error(\n      `caveman build: source module syntax is not lexable in ${JSON.stringify(path)}`,\n      { cause: error },\n    );\n  }\n  const specifiers: string[] = [];\n  for (const item of imports) {\n    if (item.d === -2) continue; // import.meta is metadata, not a dependency.\n    if (item.n === undefined) {\n      throw new Error(`caveman build: computed source dependency is not lockable in ${JSON.stringify(path)}`);\n    }\n    specifiers.push(item.n);\n  }\n  return specifiers;\n}\n\nfunction typescriptSourceSyntax(\n  source: string,\n  path: string,\n  code: Uint8Array,\n): { lexableSource: string; specifiers: string[] } {\n  if (!TYPESCRIPT_SOURCE_EXTENSIONS.has(extname(path))) {\n    return { lexableSource: source, specifiers: [] };\n  }\n  const masks: Array<{ start: number; end: number }> = [];\n  const specifiers: string[] = [];\n  let precedingExportEnd: number | undefined;\n  for (const match of source.matchAll(TYPESCRIPT_IMPORT_EXPORT_PATTERN)) {","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/source-graph.ts#L267-L303","documentation":"esmSourceSpecifiers() throws when an import/export declaration has no statically known specifier string (es-module-lexer reports n === undefined for it). The build lock must enumerate every dependency edge as a literal; a computed dependency (e.g. import(someVariable) or export * from template) cannot be locked, so the file path is reported and the build aborts.","triggerScenarios":"Dynamic import with a non-literal argument: const mod = await import(locale); re-export from a computed string; import.meta.dynamic-ish patterns that defeat static analysis.","commonSituations":"Plugin/lazy-loading systems that build module names at runtime; i18n loaders importing locale files by variable; refactors that replace static imports with data-driven ones.","solutions":["Replace computed imports with a static map of literal specifiers and select from it at runtime.","If only some paths are possible, enumerate them all as literal imports (optionally via a generated file).","Move truly dynamic loading behind an API boundary that stays outside the locked source graph."],"exampleFix":"// before\nconst mod = await import(`./locales/${locale}.ts`);\n\n// after\nconst locales = { en: () => import(\"./locales/en.ts\"), de: () => import(\"./locales/de.ts\") };\nconst mod = await locales[locale]();","handlingStrategy":"validation","validationCode":"import { parse } from \"es-module-lexer\";\nfunction hasComputedImports(source: string, path: string): boolean {\n  const [imports] = parse(source, path);\n  return imports.some((item) => item.d !== -2 && item.n === undefined);\n}\nif (hasComputedImports(src, file)) throw new Error(`computed import in ${file}`);","typeGuard":"const isStaticSpecifier = (item: { d: number; n: string | undefined }): boolean =>\n  item.d === -2 || item.n !== undefined;","tryCatchPattern":"try {\n  await buildSourceGraph(root);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"computed source dependency is not lockable\")) {\n    // replace import(variable) with a literal specifier map\n  } else throw error;\n}","preventionTips":["Never build import specifiers from variables inside locked sources.","Use a static map of literal dynamic imports for finite path sets.","Add a lint rule banning `import(` with non-literal arguments (e.g. no-dynamic-require equivalents)."],"tags":["build","dynamic-import","static-analysis","lockability"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}