@bfra.me/semantic-release
Version:
Semantic Release shareable configuration and plugins for bfra.me.
792 lines (785 loc) • 25.2 kB
JavaScript
import {
createConfigBuilder
} from "./chunk-Y2EWY7XM.js";
import "./chunk-XM5DVH32.js";
import {
ValidationError,
applyEnvironmentTransformations,
branchSpecSchema,
changelogConfigSchema,
commitAnalyzerConfigSchema,
defineConfig,
detectEnvironment,
environmentPresets,
getEnvironmentTransformations,
gitConfigSchema,
githubConfigSchema,
globalConfigSchema,
npmConfigSchema,
pluginSpecSchema,
releaseNotesGeneratorConfigSchema,
validateCompleteConfig,
validateConfig,
validatePluginConfig
} from "./chunk-2II77SNF.js";
// src/composition/index.ts
function mergeConfigs(config1, config2, ...restConfigs) {
return mergeConfigsWithOptions([config1, config2, ...restConfigs], {});
}
function mergeConfigsWithOptions(configs, options = {}) {
const { pluginStrategy = "merge", branchStrategy = "merge", validate = true } = options;
const result = {};
for (const config of configs) {
if (config.repositoryUrl != null) {
result.repositoryUrl = config.repositoryUrl;
}
if (config.tagFormat != null) {
result.tagFormat = config.tagFormat;
}
if (config.dryRun != null) {
result.dryRun = config.dryRun;
}
if (config.ci != null) {
result.ci = config.ci;
}
}
const allExtends = configs.map((config) => config.extends).filter((extend) => extend != null).flat();
if (allExtends.length > 0) {
result.extends = allExtends.length === 1 ? allExtends[0] : allExtends;
}
result.branches = mergeBranches(
configs.map((config) => config.branches).filter((branches) => branches != null),
branchStrategy
);
result.plugins = mergePlugins(
configs.map((config) => config.plugins).filter((plugins) => plugins != null),
pluginStrategy
);
for (const config of configs) {
for (const [key, value] of Object.entries(config)) {
if (key !== "extends" && key !== "branches" && key !== "repositoryUrl" && key !== "tagFormat" && key !== "plugins" && key !== "dryRun" && key !== "ci" && value != null) {
result[key] = value;
}
}
}
Object.keys(result).forEach((key) => {
if (result[key] === void 0) {
delete result[key];
}
});
if (validate) {
}
return result;
}
function extendConfig(base, extension, options = {}) {
return mergeConfigsWithOptions([base, extension], options);
}
function overrideConfig(base, override, options = {}) {
return mergeConfigsWithOptions([base, override], {
...options,
pluginStrategy: "replace",
branchStrategy: "replace"
});
}
function mergeBranches(branchConfigs, strategy) {
if (branchConfigs.length === 0) {
return void 0;
}
if (strategy === "replace") {
return branchConfigs.at(-1);
}
const allBranches = branchConfigs.flat();
const uniqueBranches = /* @__PURE__ */ new Map();
for (const branch of allBranches) {
const key = typeof branch === "string" ? branch : branch.name;
uniqueBranches.set(key, branch);
}
const result = Array.from(uniqueBranches.values());
return result;
}
function mergePlugins(pluginConfigs, strategy) {
if (pluginConfigs.length === 0) {
return void 0;
}
if (strategy === "replace") {
return pluginConfigs.at(-1);
}
if (strategy === "append") {
return pluginConfigs.flat();
}
if (strategy === "prepend") {
return pluginConfigs.reverse().flat();
}
const mergedPlugins = /* @__PURE__ */ new Map();
for (const plugins of pluginConfigs) {
for (const plugin of plugins) {
const pluginName = typeof plugin === "string" ? plugin : plugin[0];
const existing = mergedPlugins.get(pluginName);
if (existing === void 0) {
mergedPlugins.set(pluginName, plugin);
} else {
const merged = mergePluginConfigurations(existing, plugin);
mergedPlugins.set(pluginName, merged);
}
}
}
return Array.from(mergedPlugins.values());
}
function mergePluginConfigurations(existing, newPlugin) {
if (typeof existing === "string" && typeof newPlugin === "string") {
return newPlugin;
}
if (typeof existing === "string") {
return newPlugin;
}
if (typeof newPlugin === "string") {
return newPlugin;
}
const [, existingConfig] = existing;
const [newName, newConfig] = newPlugin;
const mergedConfig = {
...existingConfig,
...newConfig
};
return [newName, mergedConfig];
}
// src/config.ts
function defineConfig2(config, options) {
return defineConfig(config, options);
}
function defineConfigLegacy(config) {
return defineConfig(config, { validate: true });
}
// src/config/helpers.ts
function commitAnalyzer(config) {
if (config === void 0) {
return "@semantic-release/commit-analyzer";
}
return ["@semantic-release/commit-analyzer", config];
}
function releaseNotesGenerator(config) {
if (config === void 0) {
return "@semantic-release/release-notes-generator";
}
return ["@semantic-release/release-notes-generator", config];
}
function changelog(config) {
if (config === void 0) {
return "@semantic-release/changelog";
}
return ["@semantic-release/changelog", config];
}
function npm(config) {
if (config === void 0) {
return "@semantic-release/npm";
}
return ["@semantic-release/npm", config];
}
function github(config) {
if (config === void 0) {
return "@semantic-release/github";
}
return ["@semantic-release/github", config];
}
function git(config) {
if (config === void 0) {
return "@semantic-release/git";
}
return ["@semantic-release/git", config];
}
var pluginPresets = {
/**
* Standard commit analyzer with conventional commits preset.
*/
standardCommitAnalyzer: commitAnalyzer({
preset: "conventionalcommits",
releaseRules: [
{ type: "docs", scope: "README", release: "patch" },
{ type: "refactor", release: "patch" },
{ scope: "no-release", release: false }
]
}),
/**
* Standard release notes generator with conventional commits preset.
*/
standardReleaseNotes: releaseNotesGenerator({
preset: "conventionalcommits",
presetConfig: {
types: [
{ type: "feat", section: "Features" },
{ type: "fix", section: "Bug Fixes" },
{ type: "perf", section: "Performance Improvements" },
{ type: "revert", section: "Reverts" },
{ type: "docs", section: "Documentation", hidden: true },
{ type: "style", section: "Styles", hidden: true },
{ type: "chore", section: "Miscellaneous Chores", hidden: true },
{ type: "refactor", section: "Code Refactoring", hidden: true },
{ type: "test", section: "Tests", hidden: true },
{ type: "build", section: "Build System", hidden: true },
{ type: "ci", section: "Continuous Integration", hidden: true }
]
}
}),
/**
* Standard changelog configuration.
*/
standardChangelog: changelog({
changelogFile: "CHANGELOG.md",
changelogTitle: "# Changelog"
}),
/**
* NPM plugin configured for public package publishing.
*/
npmPublicPackage: npm({
npmPublish: true,
access: "public"
}),
/**
* NPM plugin configured for private package publishing.
*/
npmPrivatePackage: npm({
npmPublish: true,
access: "restricted"
}),
/**
* GitHub plugin with standard configuration.
*/
githubStandard: github({
successComment: (
// eslint-disable-next-line no-template-curly-in-string
'This ${issue.pull_request ? "PR is included" : "issue has been resolved"} in version ${nextRelease.version} :tada:'
),
failComment: (
// eslint-disable-next-line no-template-curly-in-string
'This release from branch ${branch.name} had failed due to the following errors:\n- ${errors.map(err => err.message).join("\\n- ")}'
),
// eslint-disable-next-line no-template-curly-in-string
releasedLabels: ['released<%= nextRelease.channel ? `-${nextRelease.channel}` : "" %>']
}),
/**
* Git plugin with standard commit configuration.
*/
gitStandard: git({
assets: ["CHANGELOG.md", "package.json", "package-lock.json"],
// eslint-disable-next-line no-template-curly-in-string
message: "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
})
};
// src/config/presets.ts
function npmPreset(options = {}) {
const { branches = ["main"], repositoryUrl, dryRun = false, defineOptions = {} } = options;
const builder = createConfigBuilder().branches(branches).dryRun(dryRun);
if (repositoryUrl != null && repositoryUrl.length > 0) {
builder.repositoryUrl(repositoryUrl);
}
builder.plugins().commitAnalyzer().releaseNotesGenerator().changelog().npm().github().git({
assets: ["CHANGELOG.md", "package.json"],
// Note: This is not a template string, it's the literal string that semantic-release uses
// eslint-disable-next-line no-template-curly-in-string
message: "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
});
return builder.build(defineOptions);
}
function githubPreset(options = {}) {
const { branches = ["main"], repositoryUrl, dryRun = false, defineOptions = {} } = options;
const builder = createConfigBuilder().branches(branches).dryRun(dryRun);
if (repositoryUrl != null && repositoryUrl.length > 0) {
builder.repositoryUrl(repositoryUrl);
}
builder.plugins().commitAnalyzer().releaseNotesGenerator().github();
return builder.build(defineOptions);
}
function monorepoPreset(options = {}) {
const {
branches = ["main"],
repositoryUrl,
dryRun = false,
defineOptions = {},
pkgRoot,
packageName,
changesetsIntegration = false,
publishOnlyIfChanged = changesetsIntegration,
releaseNotesTemplate
} = options;
const builder = createConfigBuilder().branches(branches).dryRun(dryRun);
if (repositoryUrl != null && repositoryUrl.length > 0) {
builder.repositoryUrl(repositoryUrl);
}
if (packageName != null && packageName.length > 0) {
builder.tagFormat(`${packageName}@\${version}`);
}
const pluginBuilder = builder.plugins();
pluginBuilder.commitAnalyzer({
preset: "conventionalcommits",
releaseRules: [
{ type: "docs", scope: "README", release: "patch" },
{ type: "refactor", release: "patch" },
{ scope: "no-release", release: false },
// Changesets integration: respect changeset files
...changesetsIntegration ? [
{ type: "chore", scope: "changeset", release: false },
{ type: "docs", scope: "changeset", release: false }
] : []
]
});
pluginBuilder.releaseNotesGenerator({
preset: "conventionalcommits",
...releaseNotesTemplate != null && releaseNotesTemplate.length > 0 ? {
writerOpts: {
mainTemplate: releaseNotesTemplate
}
} : {},
// Add package context to release notes
presetConfig: {
types: [
{ type: "feat", section: "Features" },
{ type: "fix", section: "Bug Fixes" },
{ type: "perf", section: "Performance Improvements" },
{ type: "revert", section: "Reverts" },
{ type: "docs", section: "Documentation", hidden: true },
{ type: "style", section: "Styles", hidden: true },
{ type: "chore", section: "Miscellaneous Chores", hidden: true },
{ type: "refactor", section: "Code Refactoring", hidden: true },
{ type: "test", section: "Tests", hidden: true },
{ type: "build", section: "Build System", hidden: true },
{ type: "ci", section: "Continuous Integration", hidden: true }
]
}
});
if (pkgRoot != null && pkgRoot.length > 0) {
const npmConfig = { pkgRoot };
if (changesetsIntegration) {
npmConfig.publishOnly = publishOnlyIfChanged;
npmConfig.tarballDir = `${pkgRoot}/dist`;
}
pluginBuilder.npm(npmConfig);
} else {
pluginBuilder.npm();
}
pluginBuilder.github({
// Use package-specific release titles
...packageName != null && packageName.length > 0 ? {
releaseNameTemplate: `${packageName}@\${nextRelease.version}`,
releaseBodyTemplate: `Release notes for ${packageName}@\${nextRelease.version}
\${nextRelease.notes}`
} : {},
// Optimize for monorepo workflow
successComment: changesetsIntegration ? (
// Changesets-aware success comment
// eslint-disable-next-line no-template-curly-in-string
'This ${issue.pull_request ? "PR is included" : "issue has been resolved"} in ${packageName ?? "package"}@${nextRelease.version} :tada:'
) : (
// Standard success comment
// eslint-disable-next-line no-template-curly-in-string
'This ${issue.pull_request ? "PR is included" : "issue has been resolved"} in version ${nextRelease.version} :tada:'
)
});
const gitAssets = ["package.json"];
if (!changesetsIntegration) {
gitAssets.push("CHANGELOG.md");
}
if (pkgRoot != null && pkgRoot.length > 0) {
gitAssets.forEach((asset, index) => {
gitAssets[index] = `${pkgRoot}/${asset}`;
});
}
pluginBuilder.git({
assets: gitAssets,
// Use package-aware commit message
message: packageName != null && packageName.length > 0 ? `chore(release): ${packageName}@\${nextRelease.version} [skip ci]
\${nextRelease.notes}` : (
// eslint-disable-next-line no-template-curly-in-string
"chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
)
});
return builder.build(defineOptions);
}
function developmentPreset(options = {}) {
const { branches = ["develop"], repositoryUrl, defineOptions = {} } = options;
const builder = createConfigBuilder().branches(branches).dryRun(true).ci(false).debug(true);
if (repositoryUrl != null && repositoryUrl.length > 0) {
builder.repositoryUrl(repositoryUrl);
}
builder.plugins().commitAnalyzer().releaseNotesGenerator();
return builder.build({
...defineOptions,
environment: "development",
validate: true
});
}
var presets = {
npm: npmPreset,
github: githubPreset,
monorepo: monorepoPreset,
development: developmentPreset
};
function getPreset(name) {
return presets[name];
}
// src/config/versioning.ts
var DEFAULT_PRESET_REGISTRY = {
presets: {
npm: [
{
version: "1.0.0",
releaseDate: /* @__PURE__ */ new Date("2024-01-01"),
breaking: false,
description: "Initial npm preset with conventional commits and GitHub integration",
compatibility: {
semanticRelease: ">=21.0.0",
node: ">=18.0.0"
}
},
{
version: "2.0.0",
releaseDate: /* @__PURE__ */ new Date("2024-06-01"),
breaking: true,
description: "Enhanced npm preset with improved changelog generation and validation",
migrations: [
{
from: "^1.0.0",
to: "2.0.0",
breaking: true,
description: "Migrate from v1 changelog format to v2 with improved templates",
transform: (config) => {
return {
...config,
plugins: config.plugins?.map((plugin) => {
if (typeof plugin === "string" && plugin === "@semantic-release/changelog") {
return ["@semantic-release/changelog", { changelogTitle: "# Changelog\n\n" }];
}
if (Array.isArray(plugin) && plugin[0] === "@semantic-release/changelog") {
return [
plugin[0],
{
...plugin[1],
changelogTitle: plugin[1]?.changelogTitle ?? "# Changelog\n\n"
}
];
}
return plugin;
})
};
}
}
],
compatibility: {
semanticRelease: ">=23.0.0",
node: ">=18.0.0"
}
}
],
github: [
{
version: "1.0.0",
releaseDate: /* @__PURE__ */ new Date("2024-01-01"),
breaking: false,
description: "Initial GitHub-only preset for projects without npm publishing",
compatibility: {
semanticRelease: ">=21.0.0",
node: ">=18.0.0"
}
}
],
monorepo: [
{
version: "1.0.0",
releaseDate: /* @__PURE__ */ new Date("2024-01-01"),
breaking: false,
description: "Initial monorepo preset with basic package-aware configuration",
compatibility: {
semanticRelease: ">=21.0.0",
node: ">=18.0.0"
}
},
{
version: "2.0.0",
releaseDate: /* @__PURE__ */ new Date("2024-12-01"),
breaking: true,
description: "Enhanced monorepo preset with changesets integration and improved package handling",
migrations: [
{
from: "^1.0.0",
to: "2.0.0",
breaking: true,
description: "Add changesets integration and update package handling",
transform: (config) => {
return config;
}
}
],
compatibility: {
semanticRelease: ">=23.0.0",
node: ">=18.0.0"
}
}
]
},
current: {
npm: "2.0.0",
github: "1.0.0",
monorepo: "2.0.0"
},
migrations: {}
};
var PresetVersionManager = class {
registry;
constructor(registry = DEFAULT_PRESET_REGISTRY) {
this.registry = registry;
}
/**
* Get available versions for a preset.
*/
getAvailableVersions(presetName) {
return this.registry.presets[presetName] ?? [];
}
/**
* Get current version for a preset.
*/
getCurrentVersion(presetName) {
return this.registry.current[presetName];
}
/**
* Get version information for a specific preset version.
*/
getVersionInfo(presetName, version) {
const versions = this.getAvailableVersions(presetName);
return versions.find((v) => v.version === version);
}
/**
* Check compatibility between preset versions.
*/
checkCompatibility(presetName, fromVersion, toVersion) {
const fromInfo = this.getVersionInfo(presetName, fromVersion);
const toInfo = this.getVersionInfo(presetName, toVersion);
if (fromInfo === void 0) {
return {
compatible: false,
issues: [
{
type: "version-mismatch",
component: presetName,
description: `Source version ${fromVersion} not found`,
severity: "error"
}
]
};
}
if (toInfo === void 0) {
return {
compatible: false,
issues: [
{
type: "version-mismatch",
component: presetName,
description: `Target version ${toVersion} not found`,
severity: "error"
}
]
};
}
const issues = [];
const suggestions = [];
if (toInfo.breaking && this.compareVersions(fromVersion, toVersion) < 0) {
issues.push({
type: "breaking-change",
component: presetName,
description: `Version ${toVersion} contains breaking changes`,
severity: "warning",
resolution: "Review migration guide and test thoroughly"
});
suggestions.push(`Review breaking changes in ${presetName} v${toVersion}`);
}
if (!this.isVersionCompatible(
fromInfo.compatibility.semanticRelease,
toInfo.compatibility.semanticRelease
)) {
issues.push({
type: "dependency-conflict",
component: "semantic-release",
description: "semantic-release version requirement changed",
severity: "error",
resolution: `Update semantic-release to ${toInfo.compatibility.semanticRelease}`
});
}
return {
compatible: issues.filter((i) => i.severity === "error").length === 0,
issues: issues.length > 0 ? issues : void 0,
suggestions: suggestions.length > 0 ? suggestions : void 0
};
}
/**
* Migrate a configuration to a newer preset version.
*/
migrateConfig(config, presetName, fromVersion, toVersion) {
const compatibility = this.checkCompatibility(presetName, fromVersion, toVersion);
if (!compatibility.compatible) {
return {
success: false,
errors: compatibility.issues?.map((i) => i.description) ?? ["Unknown compatibility error"]
};
}
const migrations = this.findMigrationPath(presetName, fromVersion, toVersion);
if (migrations.length === 0) {
return {
success: true,
config,
version: toVersion,
appliedMigrations: []
};
}
const warnings = [];
const appliedMigrations = [];
let currentConfig = config;
try {
for (const migration of migrations) {
if (migration.isApplicable?.(currentConfig) === false) {
continue;
}
currentConfig = migration.transform(currentConfig);
appliedMigrations.push(`${migration.from} -> ${migration.to}`);
if (migration.breaking) {
warnings.push(`Applied breaking migration: ${migration.description}`);
}
if (migration.validate != null) {
const validation = migration.validate(currentConfig);
if (!validation.valid) {
return {
success: false,
errors: validation.errors ?? ["Migration validation failed"],
appliedMigrations
};
}
}
}
return {
success: true,
config: currentConfig,
version: toVersion,
appliedMigrations,
warnings: warnings.length > 0 ? warnings : void 0
};
} catch (error) {
return {
success: false,
errors: [`Migration failed: ${String(error)}`],
appliedMigrations
};
}
}
/**
* Find migration path between two versions.
*/
findMigrationPath(presetName, fromVersion, toVersion) {
const versions = this.getAvailableVersions(presetName);
const fromInfo = versions.find((v) => v.version === fromVersion);
const toInfo = versions.find((v) => v.version === toVersion);
if (fromInfo === void 0 || toInfo === void 0) {
return [];
}
const allMigrations = versions.flatMap((v) => v.migrations ?? []);
return allMigrations.filter(
(m) => this.isVersionInRange(fromVersion, m.from) && this.compareVersions(m.to, toVersion) <= 0
);
}
/**
* Compare two version strings.
*/
compareVersions(a, b) {
const aParts = a.split(".").map(Number);
const bParts = b.split(".").map(Number);
const maxLength = Math.max(aParts.length, bParts.length);
for (let i = 0; i < maxLength; i++) {
const aPart = aParts[i] ?? 0;
const bPart = bParts[i] ?? 0;
if (aPart !== bPart) {
return aPart - bPart;
}
}
return 0;
}
/**
* Check if a version is compatible with a requirement range.
*/
isVersionCompatible(current, required) {
if (required.startsWith(">=")) {
const minVersion = required.slice(2);
return this.compareVersions(current, minVersion) >= 0;
}
return current === required;
}
/**
* Check if a version is in a given range.
*/
isVersionInRange(version, range) {
if (range.startsWith("^")) {
const baseVersion = range.slice(1);
const versionParts = version.split(".").map(Number);
const baseParts = baseVersion.split(".").map(Number);
return versionParts[0] === baseParts[0] && this.compareVersions(version, baseVersion) >= 0;
}
return version === range;
}
};
function createVersionManager(registry) {
return new PresetVersionManager(registry);
}
var defaultVersionManager = new PresetVersionManager();
function checkPresetCompatibility(presetName, fromVersion, toVersion) {
return defaultVersionManager.checkCompatibility(presetName, fromVersion, toVersion);
}
function migratePresetConfig(config, presetName, fromVersion, toVersion) {
return defaultVersionManager.migrateConfig(config, presetName, fromVersion, toVersion);
}
function getPresetVersions(presetName) {
return defaultVersionManager.getAvailableVersions(presetName);
}
function getCurrentPresetVersion(presetName) {
return defaultVersionManager.getCurrentVersion(presetName);
}
export {
PresetVersionManager,
ValidationError,
applyEnvironmentTransformations,
branchSpecSchema,
changelog,
changelogConfigSchema,
checkPresetCompatibility,
commitAnalyzer,
commitAnalyzerConfigSchema,
createVersionManager,
defaultVersionManager,
defineConfig2 as defineConfig,
defineConfigLegacy,
detectEnvironment,
developmentPreset,
environmentPresets,
extendConfig,
getCurrentPresetVersion,
getEnvironmentTransformations,
getPreset,
getPresetVersions,
git,
gitConfigSchema,
github,
githubConfigSchema,
githubPreset,
globalConfigSchema,
mergeConfigs,
mergeConfigsWithOptions,
migratePresetConfig,
monorepoPreset,
npm,
npmConfigSchema,
npmPreset,
overrideConfig,
pluginPresets,
pluginSpecSchema,
presets,
releaseNotesGenerator,
releaseNotesGeneratorConfigSchema,
validateCompleteConfig,
validateConfig,
validatePluginConfig as validatePluginConfigRuntime
};
//# sourceMappingURL=index.js.map