halo-dev/halo · error · Error

Explicit ESM output requires a simple stable spec.requires t

Error message

Explicit ESM output requires a simple stable spec.requires target or targetHaloVersion.

What it means

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.

Source

Thrown at ui/packages/ui-plugin-bundler-kit/src/utils/halo-plugin.ts:108

        reason: "automatic",
        targetHaloVersion: derivedTarget,
        warnings: [],
      };
    }
    return {
      format: "esm",
      reason: "automatic",
      targetHaloVersion: derivedTarget,
      warnings: [],
    };
  }

  const explicitTarget = options.targetHaloVersion
    ? parse(options.targetHaloVersion)
    : undefined;
  const target = derivedTarget || explicitTarget?.version;
  if (!target) {
    throw new Error(
      "Explicit ESM output requires a simple stable spec.requires target or targetHaloVersion."
    );
  }

  const warnings: string[] = [];
  if (!derivedTarget) {
    warnings.push(
      `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.`
    );
  } else if (!gte(derivedTarget, ESM_PROVIDER_MIN_HALO_VERSION)) {
    warnings.push(
      `Explicit ESM output targets Halo ${derivedTarget}, which predates ESM UI provider support in Halo ${ESM_PROVIDER_MIN_HALO_VERSION}.`
    );
  }
  return {
    format: "esm",
    reason: "explicit",
    targetHaloVersion: target,

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Set spec.requires in plugin.yaml to a simple stable target like '>=2.26.0'.
  2. Pass targetHaloVersion: '2.26.0' (no 'v' prefix, no prerelease) to selectProviderFormat.
  3. Drop the explicit esm override and let format default to 'auto', which falls back to IIFE when no target is derivable.

Example fix

// before
selectProviderFormat({ format: "esm", requires: "^2.0.0" });

// after
selectProviderFormat({ format: "esm", requires: ">=2.26.0" });
Defensive patterns

Strategy: validation

Validate before calling

import { parse } from "semver";
function assertEsmTarget(opts: { requires?: string; targetHaloVersion?: string }) {
  const m = opts.requires?.trim().match(/^(?:>=)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/);
  const target = m ? `${m[1]}.${m[2]}.${m[3]}` : (opts.targetHaloVersion && parse(opts.targetHaloVersion)?.version);
  if (!target) {
    throw new Error("Provide a simple stable spec.requires (>=MAJOR.MINOR.PATCH) or targetHaloVersion for ESM output.");
  }
}

Type guard

import { parse } from "semver";
function isSimpleStableTarget(value: unknown): value is string {
  return typeof value === "string" &&
    /^(?:>=)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value.trim());
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/1e3ab1c83c1cdc1c. Report an issue: GitHub.