@semantic-release/core is the composable release engine behind semantic-release. It owns orchestration, config resolution, branch validation, plugin execution, and git interactions, while leaving CLI parsing and wrapper defaults to higher-level packages.
Use this package when you want to build a custom release workflow, compose your own CLI, or run semantic-release with an explicit plugin stack.
npm install @semantic-release/coreThe package exports four public entry points:
default: run a release with an explicit executioncontext,pluginsinput, optionalonInitcallback, and optional terminalformatOutputformatter.resolveConfig(context, runtimeOptions?, configOptions?): resolve configuration and, when requested, build the plugin pipeline.getLogger({ stdout, stderr }): create the logger used by semantic-release.resolveEnvCi({ env?, cwd? }): resolve CI metadata for the current environment.
Execute one full release run: verify context, normalize plugins, analyze commits, prepare, tag, publish, and call success/fail hooks.
Signature:
default(input: {
context: Context;
plugins: PluginsInput;
onInit?: (context: Context) => Promise<void> | void;
formatOutput?: (text: string) => string | Promise<string>;
}): Promise<false | { lastRelease; commits; nextRelease; releases }>Inputs:
context(required): execution context. Must includecontext.options.plugins(required): explicit plugin source for the run.onInit(optional): hook called before plugin normalization.formatOutput(optional): formatter used before writing markdown release notes or semantic-release error details to terminal streams.
Behavior notes:
- Throws a
TypeErrorwhencontext.optionsorpluginsis missing. - Returns
falsewhen no release should be performed. - Returns a result object when a release is added or published.
- Calls
plugins.failwith extracted semantic-release errors when possible. - Does not implement wrapper-level CI policy guards such as pull-request skip or auto dry-run.
Result shape:
lastRelease: last matching release on the active branch.commits: commits used for analysis.nextRelease: computed next release metadata (when applicable).releases: publish/addChannel outputs from plugins.
Resolve semantic-release config into normalized options, and optionally build a normalized plugin pipeline.
Signature:
resolveConfig(
context: Context,
runtimeOptions?: Options,
configOptions?: {
buildPlugins?: boolean;
baseConfig?: string | object;
}
): Promise<{ options } | { options; plugins }>Inputs:
context(required): runtime context used during resolution (typically includescwd,env,envCi,stdout,stderr, andlogger).runtimeOptions(optional): caller-provided semantic-release options that override values from config files and shareable configs.configOptions(optional): controls how config resolution is performed.configOptionsfields:buildPlugins(optional, defaultfalse):true: resolve config and build/normalize plugin pipeline.false: resolve config only; plugin loading is deferred to the caller.
baseConfig(optional): seed config source (for example a shareable-config name or object) used as a base during resolution.
Return value:
- Always returns normalized
options. - Returns
pluginsonly whenconfigOptions.buildPluginsistrue.
Typical usage:
- Use
configOptions.buildPlugins: trueto return bothoptionsandpluginsfor config-driven composition. - Use
configOptions.buildPlugins: falseto resolveoptionsonly and providepluginsexplicitly to the default export for direct composition. - Use
configOptions.buildPlugins: falsewhen wrapper code needs to readoptions.plugins, enforce required plugins, and then pass the final plugin list explicitly to the default export for direct composition.
Create a logger instance compatible with core/plugin execution.
Inputs:
stdoutandstderrwritable streams (required).
Behavior notes:
getLoggerdoes not inject fallback process streams. Passing missing streams (for examplegetLogger({})) produces an unusable logger.- Use this output to populate
context.loggerbefore callingresolveConfigordefault. - The logger supports semantic-release style levels (
log,success,warn,error).
Detect CI runtime metadata (for example branch/pr context) using the current process environment and working directory.
Inputs:
env(optional): environment variable object. Defaults toprocess.env.cwd(optional): working directory. Defaults toprocess.cwd().
Behavior notes:
- Use this output to populate
context.envCibefore callingresolveConfigordefault. - Wrapper packages can apply additional policy on top of the detected CI state.
There are two supported ways to compose core.
Use resolveConfig(..., { buildPlugins: true }) when you want core to resolve configuration and build the plugin pipeline for you.
import semanticRelease, { getLogger, resolveConfig, resolveEnvCi } from "@semantic-release/core";
const cwd = process.cwd();
const env = process.env;
const envCi = resolveEnvCi({ cwd, env });
const logger = getLogger({ stdout: process.stdout, stderr: process.stderr });
const context = {
cwd,
env,
envCi,
logger,
stdout: process.stdout,
stderr: process.stderr,
};
const runtimeOptions = { dryRun: true };
const { options, plugins } = await resolveConfig(context, runtimeOptions, {
buildPlugins: true,
baseConfig: "@my-org/release-config",
});
const result = await semanticRelease({
context: { ...context, options },
plugins,
});Pass an explicit plugin list or plugin pipeline directly to core when you want the plugin source to be authoritative in your code.
If you only want configuration resolution, use resolveConfig(..., { buildPlugins: false }) and provide plugins separately.
import semanticRelease, { getLogger, resolveConfig, resolveEnvCi } from "@semantic-release/core";
const cwd = process.cwd();
const env = process.env;
const envCi = resolveEnvCi({ cwd, env });
const logger = getLogger({ stdout: process.stdout, stderr: process.stderr });
const context = {
cwd,
env,
envCi,
logger,
stdout: process.stdout,
stderr: process.stderr,
};
const runtimeOptions = {
ci: true,
dryRun: true,
};
const { options } = await resolveConfig(context, runtimeOptions, {
buildPlugins: false,
});
const result = await semanticRelease({
context: { ...context, options },
plugins: ["@semantic-release/commit-analyzer"],
});Wrappers can also use resolveConfig(..., { buildPlugins: false }) to read configured plugin specs, apply wrapper policy, and then pass the final plugin list explicitly to core. This remains direct composition because the explicit plugins argument is still authoritative.
import semanticRelease, { getLogger, resolveConfig, resolveEnvCi } from "@semantic-release/core";
const BASE_CONFIG = {
plugins: ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator"],
};
function ensureRequiredPlugins(configuredPlugins) {
return configuredPlugins.includes("@semantic-release/git")
? configuredPlugins
: [...configuredPlugins, "@semantic-release/git"];
}
const cwd = process.cwd();
const env = process.env;
const envCi = resolveEnvCi({ cwd, env });
const logger = getLogger({ stdout: process.stdout, stderr: process.stderr });
const context = {
cwd,
env,
envCi,
logger,
stdout: process.stdout,
stderr: process.stderr,
};
const { options } = await resolveConfig(
context,
{},
{
buildPlugins: false,
baseConfig: BASE_CONFIG,
}
);
const configuredPlugins = Array.isArray(options.plugins) ? options.plugins : [];
const plugins = ensureRequiredPlugins(configuredPlugins);
await semanticRelease({
context: { ...context, options },
plugins,
});In the direct-composition path, any plugin list passed to core is the plugin source, whether it comes directly from caller code or from wrapper code that first reads options.plugins. options.plugins from config or shareable-config extends is not used as a fallback source for plugin loading once the caller passes plugins explicitly.
The default export expects an object with:
context: a release context that must includecontext.options.plugins: the explicit plugin input for the run.onInit?: an optional hook that runs before plugin normalization.formatOutput?: optional formatter for markdown output written tostdout/stderr.
context.options is required. plugins is required.
The release context should include the standard semantic-release runtime fields such as cwd, env, envCi, stdout, stderr, and logger.
Core injects @semantic-release/commit-analyzer as a fallback for analyzeCommits only when all of the following are true:
- No plugin in the plugin list already provides
analyzeCommits. options.analyzeCommitsis not already an explicit plugin spec.
An explicit plugin spec means one of these forms:
- a string
- a function
- an array plugin spec
- a plain object with a
path
When options.analyzeCommits is a plain object without path, core treats it as step configuration and merges it into the injected fallback plugin. If you want the highest-release-type-wins behavior from multiple analyzers, include @semantic-release/commit-analyzer explicitly in your plugin list.
Examples:
- Plugin list covers
analyzeCommits-> no fallback injection. options.analyzeCommitsis a plugin spec -> no fallback injection.options.analyzeCommitsis an options-only plain object or is absent -> commit-analyzer is injected and the options are merged into it.
The Git process environment is what core and git use to make automated releases non-interactive and to attribute commits and tags correctly in CI.
Set these values before building the release context so context.env already includes them.
The example below shows the Git process environment values a caller or wrapper can set for CI.
Typical caller-provided CI setup:
Object.assign(env, {
GIT_AUTHOR_NAME: "semantic-release-bot",
GIT_AUTHOR_EMAIL: "semantic-release-bot@semantic-release.org",
GIT_COMMITTER_NAME: "semantic-release-bot",
GIT_COMMITTER_EMAIL: "semantic-release-bot@semantic-release.org",
...env,
GIT_ASKPASS: "echo",
GIT_TERMINAL_PROMPT: 0,
});For each of these value that you do not set, core applies its own defaults.
@semantic-release/core does not own CLI parsing or wrapper defaults.
- Core owns the release engine and config-resolution API.
- Core expects callers to provide plugins explicitly.
- Wrapper packages can layer default plugins, CLI flags, or output formatting on top of core.
- Wrapper/caller code owns CI policy decisions (for example PR skip and auto dry-run behavior).
- Core applies default git process environment hardening when values are missing; see Git process environment defaults.
- Wrapper/caller code can override any of those defaults by providing explicit values on
context.env.
Plugin resolution is a trusted-code execution boundary. Plugin specs and shareable configuration sources can load code dynamically, so only use sources you trust.
Recommended hardening practices:
- protect release branches
- use least-privilege CI tokens
- enforce lockfile integrity with
npm ci - prefer trusted shareable-config sources
- make plugin-load failures visible in CI logs
The package ships TypeScript declarations. A minimal typed setup looks like this:
import semanticRelease, { getLogger, resolveConfig, resolveEnvCi } from "@semantic-release/core";
const cwd = process.cwd();
const env = process.env;
const envCi = resolveEnvCi({ cwd, env });
const logger = getLogger({ stdout: process.stdout, stderr: process.stderr });
// Optional: override defaults applied by core.
Object.assign(env, {
GIT_AUTHOR_NAME: "semantic-release-bot",
GIT_AUTHOR_EMAIL: "semantic-release-bot@example.com",
GIT_COMMITTER_NAME: "semantic-release-bot",
GIT_COMMITTER_EMAIL: "semantic-release-bot@example.com",
...env,
GIT_ASKPASS: "echo",
GIT_TERMINAL_PROMPT: 0,
});
const context = {
cwd,
env,
envCi,
logger,
stdout: process.stdout,
stderr: process.stderr,
};
const runtimeOptions = { dryRun: true };
const { options, plugins } = await resolveConfig(context, runtimeOptions, {
buildPlugins: true,
baseConfig: { branches: ["main"] },
});
await semanticRelease({
context: { ...context, options },
plugins,
});