{"record":{"id":"1e3ab1c83c1cdc1c","repo":"halo-dev/halo","slug":"explicit-esm-output-requires-a-simple-stable-spec","errorCode":null,"errorMessage":"Explicit ESM output requires a simple stable spec.requires target or targetHaloVersion.","messagePattern":"Explicit ESM output requires a simple stable spec\\.requires target or targetHaloVersion\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ui/packages/ui-plugin-bundler-kit/src/utils/halo-plugin.ts","lineNumber":108,"sourceCode":"        reason: \"automatic\",\n        targetHaloVersion: derivedTarget,\n        warnings: [],\n      };\n    }\n    return {\n      format: \"esm\",\n      reason: \"automatic\",\n      targetHaloVersion: derivedTarget,\n      warnings: [],\n    };\n  }\n\n  const explicitTarget = options.targetHaloVersion\n    ? parse(options.targetHaloVersion)\n    : undefined;\n  const target = derivedTarget || explicitTarget?.version;\n  if (!target) {\n    throw new Error(\n      \"Explicit ESM output requires a simple stable spec.requires target or targetHaloVersion.\"\n    );\n  }\n\n  const warnings: string[] = [];\n  if (!derivedTarget) {\n    warnings.push(\n      `Explicit ESM output uses target Halo ${target}, but spec.requires ${JSON.stringify(options.requires)} does not prove a minimum Halo version of ${ESM_PROVIDER_MIN_HALO_VERSION} or newer. Update spec.requires so older Halo releases do not install this ESM-only artifact.`\n    );\n  } else if (!gte(derivedTarget, ESM_PROVIDER_MIN_HALO_VERSION)) {\n    warnings.push(\n      `Explicit ESM output targets Halo ${derivedTarget}, which predates ESM UI provider support in Halo ${ESM_PROVIDER_MIN_HALO_VERSION}.`\n    );\n  }\n  return {\n    format: \"esm\",\n    reason: \"explicit\",\n    targetHaloVersion: target,","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/ui/packages/ui-plugin-bundler-kit/src/utils/halo-plugin.ts#L90-L126","documentation":"Thrown by selectProviderFormat when the caller explicitly requests ESM output (format: 'esm') but neither spec.requires nor targetHaloVersion can be parsed by parseSimpleStableTarget/parse into a simple stable MAJOR.MINOR.PATCH. ESM output needs a concrete Halo target to select the right host runtime snapshot, so without a derivable target the kit cannot safely emit ESM.","triggerScenarios":"Called with { format: 'esm' } (or a config resolving to explicit esm) while options.requires is undefined/complex (e.g. '>=2.20 <2.26', '~2.25', '^2.0') AND options.targetHaloVersion is omitted or non-semver. parseSimpleStableTarget only accepts an optional '>=' prefix followed by exactly MAJOR.MINOR.PATCH.","commonSituations":"Setting format:'esm' in vite config but leaving plugin.yaml spec.requires as a range or empty; passing a targetHaloVersion with a 'v' prefix or prerelease; CI that forces esm without setting a target.","solutions":["Set spec.requires in plugin.yaml to a simple stable target like '>=2.26.0'.","Pass targetHaloVersion: '2.26.0' (no 'v' prefix, no prerelease) to selectProviderFormat.","Drop the explicit esm override and let format default to 'auto', which falls back to IIFE when no target is derivable."],"exampleFix":"// before\nselectProviderFormat({ format: \"esm\", requires: \"^2.0.0\" });\n\n// after\nselectProviderFormat({ format: \"esm\", requires: \">=2.26.0\" });","handlingStrategy":"validation","validationCode":"import { parse } from \"semver\";\nfunction assertEsmTarget(opts: { requires?: string; targetHaloVersion?: string }) {\n  const m = opts.requires?.trim().match(/^(?:>=)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$/);\n  const target = m ? `${m[1]}.${m[2]}.${m[3]}` : (opts.targetHaloVersion && parse(opts.targetHaloVersion)?.version);\n  if (!target) {\n    throw new Error(\"Provide a simple stable spec.requires (>=MAJOR.MINOR.PATCH) or targetHaloVersion for ESM output.\");\n  }\n}","typeGuard":"import { parse } from \"semver\";\nfunction isSimpleStableTarget(value: unknown): value is string {\n  return typeof value === \"string\" &&\n    /^(?:>=)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$/.test(value.trim());\n}","tryCatchPattern":null,"preventionTips":["Always set spec.requires to a simple '>=X.Y.Z' in plugin.yaml when opting into ESM.","Pass targetHaloVersion without a 'v' prefix and without prerelease tags.","Prefer format: 'auto' so the kit falls back to IIFE instead of throwing."],"tags":["build","esm","config","semver","bundler-kit"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}