@bfra.me/semantic-release
Version:
Semantic Release shareable configuration and plugins for bfra.me.
597 lines (591 loc) • 20.6 kB
JavaScript
// src/validation/schemas/core.ts
import { z } from "zod";
var branchSpecSchema = z.union([
z.string().min(1),
z.object({
name: z.string().min(1),
channel: z.string().optional(),
range: z.string().optional(),
prerelease: z.union([z.boolean(), z.string()]).optional()
})
]);
var pluginSpecSchema = z.union([
z.string().min(1),
z.tuple([z.string().min(1), z.record(z.string(), z.unknown())])
]);
var globalConfigSchema = z.object({
// Core configuration
extends: z.union([z.string(), z.array(z.string())]).optional(),
branches: z.array(branchSpecSchema).optional(),
repositoryUrl: z.string().url().optional(),
tagFormat: z.string().optional(),
plugins: z.array(pluginSpecSchema).optional(),
// CI environment
ci: z.boolean().optional(),
dryRun: z.boolean().optional(),
debug: z.boolean().optional(),
// Git configuration
preset: z.string().optional(),
presetConfig: z.record(z.string(), z.unknown()).optional(),
// Advanced options
noCi: z.boolean().optional()
// Environment variables will be validated separately
});
// src/validation/schemas/plugins.ts
import { z as z2 } from "zod";
var commitAnalyzerConfigSchema = z2.object({
preset: z2.string().optional(),
config: z2.record(z2.string(), z2.unknown()).optional(),
parserOpts: z2.record(z2.string(), z2.unknown()).optional(),
releaseRules: z2.array(
z2.object({
type: z2.string().optional(),
scope: z2.string().optional(),
subject: z2.string().optional(),
release: z2.union([z2.literal("major"), z2.literal("minor"), z2.literal("patch"), z2.literal(false)]).optional(),
breaking: z2.boolean().optional(),
revert: z2.boolean().optional()
})
).optional(),
presetConfig: z2.record(z2.string(), z2.unknown()).optional()
});
var releaseNotesGeneratorConfigSchema = z2.object({
preset: z2.string().optional(),
config: z2.record(z2.string(), z2.unknown()).optional(),
parserOpts: z2.record(z2.string(), z2.unknown()).optional(),
writerOpts: z2.object({
groupBy: z2.string().optional(),
commitGroupsSort: z2.union([z2.string(), z2.unknown()]).optional(),
commitsSort: z2.union([z2.string(), z2.unknown()]).optional(),
noteGroupsSort: z2.union([z2.string(), z2.unknown()]).optional(),
notesSort: z2.union([z2.string(), z2.unknown()]).optional(),
generateOn: z2.union([z2.string(), z2.unknown()]).optional(),
finalizeContext: z2.unknown().optional(),
debug: z2.unknown().optional(),
reverse: z2.boolean().optional(),
includeDetails: z2.boolean().optional(),
ignoreReverted: z2.boolean().optional(),
doFlush: z2.boolean().optional(),
mainTemplate: z2.string().optional(),
headerPartial: z2.string().optional(),
commitPartial: z2.string().optional(),
footerPartial: z2.string().optional(),
partials: z2.record(z2.string(), z2.string()).optional(),
transform: z2.record(z2.string(), z2.unknown()).optional()
}).optional(),
presetConfig: z2.record(z2.string(), z2.unknown()).optional(),
linkCompare: z2.boolean().optional(),
linkReferences: z2.boolean().optional(),
host: z2.string().optional(),
owner: z2.string().optional(),
repository: z2.string().optional(),
repoUrl: z2.string().optional(),
commit: z2.string().optional(),
issue: z2.string().optional(),
userUrlFormat: z2.string().optional()
});
var changelogConfigSchema = z2.object({
changelogFile: z2.string().optional(),
changelogTitle: z2.string().optional()
});
var npmConfigSchema = z2.object({
npmPublish: z2.boolean().optional(),
pkgRoot: z2.string().optional(),
tarballDir: z2.string().optional()
});
var githubConfigSchema = z2.object({
githubUrl: z2.string().optional(),
githubApiPathPrefix: z2.string().optional(),
proxy: z2.union([z2.string(), z2.record(z2.string(), z2.unknown())]).optional(),
assets: z2.array(
z2.union([
z2.string(),
z2.object({
path: z2.string(),
name: z2.string().optional(),
label: z2.string().optional()
})
])
).optional(),
successComment: z2.union([z2.string(), z2.literal(false)]).optional(),
failTitle: z2.string().optional(),
failComment: z2.union([z2.string(), z2.literal(false)]).optional(),
labels: z2.union([z2.array(z2.string()), z2.literal(false)]).optional(),
assignees: z2.array(z2.string()).optional(),
releasedLabels: z2.union([z2.array(z2.string()), z2.literal(false)]).optional(),
addReleases: z2.union([z2.literal("bottom"), z2.literal("top"), z2.literal(false)]).optional(),
draftRelease: z2.boolean().optional(),
releaseBodyTemplate: z2.string().optional(),
releaseNameTemplate: z2.string().optional(),
discussionCategoryName: z2.string().optional()
});
var gitConfigSchema = z2.object({
assets: z2.array(z2.string()).optional(),
message: z2.string().optional()
});
// src/validation/validator.ts
import { z as z3 } from "zod";
var ValidationError = class extends Error {
/**
* The original Zod validation error with detailed issue information.
*/
zodError;
/**
* Optional path identifier for the validation context (e.g., 'global', 'plugin:npm').
*/
path;
/**
* Create a new ValidationError instance.
*
* @param message - Human-readable error message
* @param zodError - The original Zod validation error
* @param path - Optional path identifier for context
*/
constructor(message, zodError, path) {
super(message);
this.name = "ValidationError";
this.zodError = zodError;
this.path = path;
}
/**
* Get formatted error messages for display to users.
*
* Transforms Zod validation issues into human-readable error messages
* with path information, making it easier to identify and fix configuration problems.
*
* @returns Array of formatted error message strings
*
* @example
* ```typescript
* const errors = validationError.getFormattedErrors()
* // ['branches: Expected array, got string', 'plugins.0: Expected string or array, got object']
* ```
*/
getFormattedErrors() {
return this.zodError.issues.map((issue) => {
const path = issue.path.length > 0 ? issue.path.join(".") : "root";
return `${path}: ${issue.message}`;
});
}
};
var pluginSchemas = {
"@semantic-release/commit-analyzer": commitAnalyzerConfigSchema,
"@semantic-release/release-notes-generator": releaseNotesGeneratorConfigSchema,
"@semantic-release/changelog": changelogConfigSchema,
"@semantic-release/npm": npmConfigSchema,
"@semantic-release/github": githubConfigSchema,
"@semantic-release/git": gitConfigSchema
};
function validateConfig(config) {
try {
const result = globalConfigSchema.parse(config);
return { success: true, data: result };
} catch (error) {
if (error instanceof z3.ZodError) {
return {
success: false,
error: new ValidationError("Invalid semantic-release configuration", error, "global")
};
}
throw error;
}
}
function validatePluginConfig(pluginName, config) {
const schema = pluginSchemas[pluginName];
if (schema === void 0) {
return { success: true, data: config };
}
try {
const result = schema.parse(config);
return { success: true, data: result };
} catch (error) {
if (error instanceof z3.ZodError) {
return {
success: false,
error: new ValidationError(
`Invalid configuration for plugin ${pluginName}`,
error,
pluginName
)
};
}
throw error;
}
}
function validateCompleteConfig(config) {
const errors = [];
const globalResult = validateConfig(config);
if (!globalResult.success) {
errors.push(globalResult.error);
return { success: false, errors };
}
if (globalResult.data.plugins) {
for (const [index, plugin] of globalResult.data.plugins.entries()) {
if (Array.isArray(plugin) && plugin.length === 2) {
const [pluginName, pluginConfig] = plugin;
const pluginResult = validatePluginConfig(pluginName, pluginConfig);
if (!pluginResult.success) {
const contextualError = new ValidationError(
`Plugin at index ${index}: ${pluginResult.error.message}`,
pluginResult.error.zodError,
`plugins.${index}`
);
errors.push(contextualError);
}
}
}
}
if (errors.length > 0) {
return { success: false, errors };
}
return { success: true, data: globalResult.data };
}
// src/config/environment.ts
import process from "process";
function detectEnvironment(explicitEnv) {
if (explicitEnv) {
return {
environment: explicitEnv,
autoDetected: false,
source: "explicit",
isCI: detectCI(),
metadata: {
nodeEnv: process.env.NODE_ENV,
ciVendor: detectCIVendor(),
branchName: detectBranchName()
}
};
}
const nodeEnv = process.env.NODE_ENV?.toLowerCase();
if (nodeEnv != null && ["development", "staging", "production"].includes(nodeEnv)) {
return {
environment: nodeEnv,
autoDetected: true,
source: "NODE_ENV",
isCI: detectCI(),
metadata: {
nodeEnv: process.env.NODE_ENV,
ciVendor: detectCIVendor(),
branchName: detectBranchName()
}
};
}
const ciVendor = detectCIVendor();
if (process.env.CI === "true" && ciVendor != null) {
const branchName = detectBranchName();
if (branchName?.includes("staging") || branchName?.includes("stage")) {
return {
environment: "staging",
autoDetected: true,
source: "CI_BRANCH",
isCI: true,
metadata: {
nodeEnv: process.env.NODE_ENV,
ciVendor: detectCIVendor(),
branchName
}
};
}
}
const hasExplicitTestIndicators = process.argv.includes("--test") || process.env.npm_lifecycle_event === "test" || process.env.JEST_WORKER_ID !== void 0 && process.env.JEST_WORKER_ID !== "";
const hasVitestIndicator = process.env.VITEST === "true";
if (nodeEnv === "test" || hasExplicitTestIndicators || hasVitestIndicator) {
return {
environment: "test",
autoDetected: true,
source: hasExplicitTestIndicators || hasVitestIndicator ? "TEST_INDICATORS" : "NODE_ENV",
isCI: detectCI(),
metadata: {
nodeEnv: process.env.NODE_ENV,
ciVendor: detectCIVendor(),
branchName: detectBranchName()
}
};
}
return {
environment: "production",
autoDetected: true,
source: "DEFAULT",
isCI: detectCI(),
metadata: {
nodeEnv: process.env.NODE_ENV,
ciVendor: detectCIVendor(),
branchName: detectBranchName()
}
};
}
function getEnvironmentTransformations(environment, context) {
switch (environment) {
case "development": {
return {
// Enable dry-run mode for safety in development
dryRun: true,
// Enable debug mode for better development experience
debug: true,
// Use local branches for development
branches: ["main", "master", "develop", "development"],
// Disable CI mode if not explicitly in CI
ci: context.isCI,
// Use development tag format for clarity
// eslint-disable-next-line no-template-curly-in-string
tagFormat: "v${version}-dev"
};
}
case "test": {
return {
// Always enable dry-run in test environments
dryRun: true,
// Enable CI mode for consistent test behavior
ci: true,
// Enable debug for test troubleshooting
debug: true,
// Use test-specific branches
branches: ["main", "master"],
// Use test tag format
// eslint-disable-next-line no-template-curly-in-string
tagFormat: "v${version}-test"
};
}
case "staging": {
return {
// Allow actual releases in staging but with staging tag
dryRun: false,
// Enable CI mode for staging
ci: true,
// Enable debug for staging troubleshooting
debug: true,
// Use staging-specific branches
branches: ["staging", "stage", "main"],
// Use staging tag format
// eslint-disable-next-line no-template-curly-in-string
tagFormat: "v${version}-staging"
};
}
case "production": {
return {
// Production releases are real
dryRun: false,
// Disable debug in production for clean output
debug: false,
// Enable CI mode in production
ci: context.isCI,
// Use production branches
branches: ["main", "master"],
// Use standard production tag format
// eslint-disable-next-line no-template-curly-in-string
tagFormat: "v${version}"
};
}
default: {
return {
dryRun: false,
debug: false,
ci: context.isCI,
branches: ["main", "master"],
// eslint-disable-next-line no-template-curly-in-string
tagFormat: "v${version}"
};
}
}
}
function applyEnvironmentTransformations(config, environment, context) {
const transformations = getEnvironmentTransformations(environment, context);
const processedConfig = { ...config };
if (transformations.branches != null && processedConfig.branches === void 0) {
processedConfig.branches = transformations.branches;
}
if (transformations.ci !== void 0 && processedConfig.ci === void 0) {
processedConfig.ci = transformations.ci;
}
if (transformations.dryRun !== void 0 && processedConfig.dryRun === void 0) {
processedConfig.dryRun = transformations.dryRun;
}
if (transformations.debug !== void 0 && processedConfig.debug === void 0) {
processedConfig.debug = transformations.debug;
}
if (transformations.tagFormat != null && processedConfig.tagFormat === void 0) {
processedConfig.tagFormat = transformations.tagFormat;
}
if (transformations.repositoryUrl != null && processedConfig.repositoryUrl === void 0) {
processedConfig.repositoryUrl = transformations.repositoryUrl;
}
return processedConfig;
}
function detectCI() {
return process.env.CI === "true" || process.env.CONTINUOUS_INTEGRATION === "true" || process.env.GITHUB_ACTIONS != null && process.env.GITHUB_ACTIONS !== "" || process.env.GITLAB_CI != null && process.env.GITLAB_CI !== "" || process.env.CIRCLECI != null && process.env.CIRCLECI !== "" || process.env.TRAVIS != null && process.env.TRAVIS !== "" || process.env.BUILDKITE != null && process.env.BUILDKITE !== "" || process.env.JENKINS_URL != null && process.env.JENKINS_URL !== "";
}
function detectCIVendor() {
if (process.env.GITHUB_ACTIONS != null && process.env.GITHUB_ACTIONS !== "")
return "github-actions";
if (process.env.GITLAB_CI != null && process.env.GITLAB_CI !== "") return "gitlab-ci";
if (process.env.CIRCLECI != null && process.env.CIRCLECI !== "") return "circleci";
if (process.env.TRAVIS != null && process.env.TRAVIS !== "") return "travis";
if (process.env.BUILDKITE != null && process.env.BUILDKITE !== "") return "buildkite";
if (process.env.JENKINS_URL != null && process.env.JENKINS_URL !== "") return "jenkins";
return void 0;
}
function detectBranchName() {
const envVars = [
process.env.GITHUB_REF_NAME,
process.env.CI_COMMIT_REF_NAME,
process.env.CIRCLE_BRANCH,
process.env.TRAVIS_BRANCH,
process.env.BUILDKITE_BRANCH,
process.env.GIT_BRANCH
];
for (const envVar of envVars) {
if (envVar != null && envVar !== "") {
return envVar;
}
}
return void 0;
}
var environmentPresets = {
/**
* Development environment preset with safe defaults.
*/
development: {
branches: ["main", "master", "develop", "development"],
dryRun: true,
debug: true,
ci: false,
// eslint-disable-next-line no-template-curly-in-string
tagFormat: "v${version}-dev"
},
/**
* Test environment preset optimized for testing.
*/
test: {
branches: ["main", "master"],
dryRun: true,
debug: true,
ci: true,
// eslint-disable-next-line no-template-curly-in-string
tagFormat: "v${version}-test"
},
/**
* Staging environment preset for pre-production testing.
*/
staging: {
branches: ["staging", "stage", "main"],
dryRun: false,
debug: true,
ci: true,
// eslint-disable-next-line no-template-curly-in-string
tagFormat: "v${version}-staging"
},
/**
* Production environment preset for production releases.
*/
production: {
branches: ["main", "master"],
dryRun: false,
debug: false,
ci: true,
// eslint-disable-next-line no-template-curly-in-string
tagFormat: "v${version}"
}
};
// src/config/define-config.ts
function generateValidationSuggestions(errors) {
const suggestions = [];
for (const error of errors) {
for (const issue of error.zodError.issues) {
const path = issue.path.join(".") || "root";
if (issue.code === "invalid_type") {
if (path === "branches" && issue.received === "string") {
suggestions.push('\u2022 `branches` should be an array. Try: branches: ["main"]');
} else if (path.includes("plugins")) {
suggestions.push(
"\u2022 Plugin configurations should be either strings or [name, config] tuples"
);
} else {
suggestions.push(`\u2022 ${path}: Expected ${issue.expected}, got ${issue.received}`);
}
} else if (issue.code === "invalid_string" && "validation" in issue && issue.validation === "url") {
suggestions.push(
'\u2022 `repositoryUrl` must be a valid URL. Example: "https://github.com/owner/repo.git"'
);
} else if (issue.code === "too_small" && "minimum" in issue) {
if (issue.type === "string") {
suggestions.push(`\u2022 ${path}: Cannot be empty`);
} else if (issue.type === "array") {
suggestions.push(`\u2022 ${path}: Must contain at least ${issue.minimum} item(s)`);
} else {
suggestions.push(`\u2022 ${path}: ${issue.message}`);
}
} else if (issue.code === "invalid_union") {
if (path.includes("branches")) {
suggestions.push("\u2022 Branch specifications must be strings or objects with name property");
} else if (path.includes("plugins")) {
suggestions.push("\u2022 Plugins must be strings (plugin names) or [name, config] tuples");
} else {
suggestions.push(`\u2022 ${path}: ${issue.message}`);
}
} else if (issue.code === "unrecognized_keys") {
suggestions.push(
`\u2022 ${path}: Unrecognized configuration options. Check the semantic-release documentation.`
);
} else {
suggestions.push(`\u2022 ${path}: ${issue.message}`);
}
}
}
if (suggestions.length > 0) {
suggestions.push(
"",
"\u{1F4D6} For more help: https://semantic-release.gitbook.io/semantic-release/usage/configuration"
);
}
return suggestions.join("\n");
}
function defineConfig(config, options = {}) {
const { validate: shouldValidate = true, environment = "production" } = options;
if (shouldValidate) {
const validationResult = validateCompleteConfig(config);
if (!validationResult.success) {
const errorMessages = validationResult.errors.map((error) => {
const formattedErrors = error.getFormattedErrors();
return formattedErrors.join(", ");
}).join("; ");
const suggestions = generateValidationSuggestions(validationResult.errors);
const fullMessage = `Configuration validation failed: ${errorMessages}${suggestions ? `
Suggestions:
${suggestions}` : ""}`;
const firstError = validationResult.errors[0];
if (firstError) {
throw new ValidationError(fullMessage, firstError.zodError, "defineConfig");
} else {
throw new Error(fullMessage);
}
}
}
const envContext = detectEnvironment(environment);
const processedConfig = applyEnvironmentTransformations(
config,
envContext.environment,
envContext
);
return processedConfig;
}
export {
branchSpecSchema,
pluginSpecSchema,
globalConfigSchema,
commitAnalyzerConfigSchema,
releaseNotesGeneratorConfigSchema,
changelogConfigSchema,
npmConfigSchema,
githubConfigSchema,
gitConfigSchema,
ValidationError,
validateConfig,
validatePluginConfig,
validateCompleteConfig,
detectEnvironment,
getEnvironmentTransformations,
applyEnvironmentTransformations,
environmentPresets,
defineConfig
};
//# sourceMappingURL=chunk-2II77SNF.js.map