Proposal: Extract `@semantic-release/core` package to enable composable release pipelines

Author: babblebeyCreated Mar 5, 2026Updated Sep 8, 2026
Labelscore

Extract the core engine (orchestration, plugin system, git operations, config loading) into a standalone @semantic-release/core package. The existing semantic-release package becomes a thin wrapper that provides the CLI and bundles the four default plugins.

This lets users compose custom release pipelines without being forced to depend on @semantic-release/commit-analyzer, @semantic-release/release-notes-generator, @semantic-release/npm, and @semantic-release/github.

Motivation

Today, semantic-release bundles three distinct concerns into one package:

Concern Source Dependencies
Core engine index.js, lib env-ci, semver, cosmiconfig, hook-std, git-log-parser, etc.
CLI cli.js, semantic-release.js yargs, marked-terminal
Default plugins (bundled as deps) @semantic-release/commit-analyzer, release-notes-generator, npm, github

Users who want to build a custom release workflow.... e.g., a framework-specific CLI, or a pipeline that publishes to a private registry instead of npm/GitHub; still have to install all four default plugins as transitive dependencies, even if they never use them.

Use cases this unblocks

  • Custom CLIs: Teams building internal release tooling on top of the semantic-release engine without inheriting the default CLI or plugins.
  • Alternative plugin sets: Using a different commit analyzer (I don't know, you never can tell) or publishing to GitLab/Bitbucket without pulling in the GitHub plugin.
  • Lighter installs: CI environments that only need the engine + 1-2 plugins instead of the full default stack.
  • Framework wrappers: Tools or platform-specific release managers that embed semantic-release as a library.

Proposed Design

Package split

@semantic-release/core     ← NEW: engine + plugin system (no CLI, no default plugins)
semantic-release           ← existing: CLI + re-exports core + default plugins as deps

@semantic-release/core

Will Contain:

  • index.js - the run() orchestration function and the public API (export default)
  • lib - all modules (git operations, plugin loading/pipeline, branch handling, config, verification, etc.)
  • index.d.ts - TypeScript types

Will NOT Contain:

Key change: The default plugins array in get-config.js#L75-L80 becomes [] instead of the current hardcoded list:

javascript
// Current (in semantic-release)
plugins: [
  "@semantic-release/commit-analyzer",
  "@semantic-release/release-notes-generator",
  "@semantic-release/npm",
  "@semantic-release/github",
],

// In @semantic-release/core
plugins: [],

The core would throw a clear error if no plugins are configured and no analyzeCommits step is provided (since that step is required: true in plugins.js).

semantic-release (existing package)

Becomes a thin wrapper:

  • Depends on @semantic-release/core
  • Re-exports the core API for full backward compatibility
  • Provides the CLI (yargs, marked-terminal)
  • Bundles the 4 default plugins as dependencies
  • Injects the default plugin list so existing users see zero behavior change

Usage of @semantic-release/core

javascript
import semanticRelease from "@semantic-release/core";

const result = await semanticRelease({
  plugins: [
    "@my-org/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@my-org/deploy-plugin",
  ],
});

Feasibility

The architecture already supports this cleanly:

  1. Core has zero imports from CLI code. index.js and lib never import from cli.js or bin.
  2. CLI only imports core via dynamic import("./index.js") - a clean boundary already exists.
  3. Default plugins are referenced only as strings in get-config.js, not imported directly. The plugin system resolves them by name at runtime via import-from-esm.
  4. The plugin pipeline is fully generic. index.js and plugins.js don't know or care which specific plugins are loaded.

The only coupling point that needs attention is terminalOutput() in index.js, which imports marked-terminal - a CLI-oriented dependency that should either move to the CLI layer or become injectable.

Migration & Backward Compatibility

  • semantic-release users: Zero breaking changes. The package continues to work identically, it just delegates to @semantic-release/core internally.
  • Programmatic API users: No change - semantic-release re-exports the same default function.

Open Questions

  • Should we just still pack a default commit-analyzer with @semantic-release/core, like we already do anyways in considering how imperative commit analysis is to software release at all? but does it not beat the point on no plugins in core? https://github.com/semantic-release/semantic-release/blob/e9f4aba2a23800b5c3c769b2ca150962fd199fc2/lib/definitions/plugins.js#L15-L18
  • Should @semantic-release/core ship its own minimal CLI? Or should it be library-only, leaving all CLI concerns to consumers?
  • Should the marked-terminal rendering be part of core? It's used for error output formatting in index.js, not just the CLI. We could make it a soft/optional dependency or inject a formatter function.

Source: semantic-release/semantic-release