halo-dev/halo · warning · Error

ESM UI provider output must contain exactly one entry JavaSc

Error message

ESM UI provider output must contain exactly one entry JavaScript file.

What it means

After path normalization, validateManifestResource looks up the entry/style resource inside the provider root (the console/ bundle location) and checks isReadable(); if it is missing or unreadable this IllegalArgumentException is thrown and converted to ClassifiedProvider.invalid. The literal path from the manifest is included in the message.

Source

Thrown at ui/packages/ui-plugin-bundler-kit/src/rsbuild-esm.ts:52

          test: /\.[cm]?[jt]sx?$/,
          enforce: "post",
        },
        async ({ code, resourcePath }) => {
          await validator.validateSource(code, resourcePath);
          return code;
        }
      );

      api.processAssets(
        { stage: "summarize" },
        async ({ assets, compilation, sources }) => {
          const entryFiles =
            compilation.entrypoints.get("main")?.getFiles() || [];
          const entryScripts = entryFiles.filter((fileName) =>
            fileName.endsWith(".js")
          );
          if (entryScripts.length !== 1) {
            throw new Error(
              "ESM UI provider output must contain exactly one entry JavaScript file."
            );
          }
          const entryFile = entryScripts[0];
          const entry = assets[entryFile];
          if (!entry) {
            throw new Error(
              `ESM UI provider output is missing its entry asset ${entryFile}.`
            );
          }
          const entryCode = entry.source().toString();
          await validator.validateSource(entryCode, entryFile);
          if (
            !/\bexport\s+default\b/.test(entryCode) &&
            !/\bexport\s*\{[^}]*\bdefault\b[^}]*\}/s.test(entryCode)
          ) {
            throw new Error(
              "ESM UI provider output must expose a default PluginModule export."

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Confirm the entry/style file is packaged under the plugin's console/ directory.
  2. Match the path spelling and case exactly to the file on disk.
  3. Rebuild the jar and verify with `jar tf` that the console asset is present.

Example fix

// before — entry references a file not in the jar
{
  "format": "esm",
  "entry": "dist/main.js"
}
// after — file actually shipped at console/index.js
{
  "format": "esm",
  "entry": "index.js"
}
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the entry/style file is packaged and readable inside console/.
String p = normalizeObjectPath(manifest.path("entry").asText());
Resource r = providerResource(candidate, p);
if (r == null || !r.isReadable()) {
    throw new IllegalStateException("Entry not packaged under console/: " + p);
}

Prevention

When it happens

Trigger: ui-plugin.json's entry or style points at a file that is not packaged inside the plugin's console/ directory, or the file exists but is unreadable.

Common situations: Path typo; file not included in the jar build; case mismatch on case-sensitive filesystems; entry pointing outside the bundled console assets.

Related errors


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