UNPKG

@ethima/semantic-release-configuration

Version:

A shareable semantic release configuration supporting a range of languages and platforms supported by the Ethima organization.

242 lines (225 loc) 8.57 kB
import { extname } from "node:path"; /** * The default configuration for tokens used by the {@link buildReplacement} * and {@link buildTemplateMatcher} functions to parse templates and perform * replacements. * * The tokens serve the following purposes: * - `next_version_placeholder_token` represents locations in a template that * need to be replaced by the version of the next semantic release. * - `replacement_end_token` represents the end of content that was previously * filled in. Should be on a separate line. This line may contain additional * content which will be maintained along with the token in the replaced * content, e.g. indentation, comment markers, etc.. * - `template_begin_token` indicates the start of the template in which * `next_version_placeholder_token`s should be replaced. Should be on a * separate line. This line may contain additional content which will be * maintained along with the token in the replaced content, e.g. indentation, * comment markers, etc.. * - `template_continuation_token` represents indented markers that are * necessary to indicate continuation of the template, but which should not * be present in the replaced content, e.g. comment indicators for line-based * comments, etc. Indented whitespace does not have to be configured in the * token. * - `template_end_token` indicates the end of the template. Should be on a * separate line. This line may contain additional content which will be * maintained along with the token in the replaced content, e.g. indentation, * comment markers, etc.. */ const DEFAULT_TEMPLATE_TOKEN_CONFIGURATION = { next_version_placeholder_token: "__NEXT_SEMANTIC_RELEASE_VERSION__", replacement_end_token: "END_VERSIONED_TEMPLATE_REPLACEMENT", template_begin_token: "BEGIN_VERSIONED_TEMPLATE", template_continuation_token: "", template_end_token: "END_VERSIONED_TEMPLATE", }; /** * Template token configuration for files that can be commented using "C-style * comments", e.g. C, CSS, JavaScript, TypeScript, etc. */ const C_BLOCK_TEMPLATE_TOKEN_CONFIGURATION = { template_continuation_token: "\\s\\*+", template_end_token: "\\*+/", }; /** * Template token configuration for files that can be commented using "hash * signs", e.g. Python, TOML, YAML, etc. */ const HASH_TEMPLATE_TOKEN_CONFIGURATION = { template_continuation_token: "#", }; /** * Template token configuration for files that can be commented using XML * comments, e.g. HTML, Markdown, etc. */ const XML_TEMPLATE_TOKEN_CONFIGURATION = { template_end_token: "-->", }; /** * Maps file extensions to token configurations for templates that can be * included in the specified file formats. */ const FILE_EXTENSION_TO_TEMPLATE_CONFIGURATION = { c: C_BLOCK_TEMPLATE_TOKEN_CONFIGURATION, css: C_BLOCK_TEMPLATE_TOKEN_CONFIGURATION, html: XML_TEMPLATE_TOKEN_CONFIGURATION, jl: HASH_TEMPLATE_TOKEN_CONFIGURATION, js: C_BLOCK_TEMPLATE_TOKEN_CONFIGURATION, md: XML_TEMPLATE_TOKEN_CONFIGURATION, py: HASH_TEMPLATE_TOKEN_CONFIGURATION, toml: HASH_TEMPLATE_TOKEN_CONFIGURATION, ts: C_BLOCK_TEMPLATE_TOKEN_CONFIGURATION, xml: XML_TEMPLATE_TOKEN_CONFIGURATION, yaml: HASH_TEMPLATE_TOKEN_CONFIGURATION, yml: HASH_TEMPLATE_TOKEN_CONFIGURATION, }; /** * Builds a replacement string from the components matched by a matcher created * by {@link buildTemplateMatcher}. Any `next_version_placeholder_token`s, as * identified by the {@link DEFAULT_TEMPLATE_TOKEN_CONFIGURATION}, are replaced * by the next semantic release version. Any "template continuation tokens" are * removed from the template using {@link stripTemplateContinuationTokens}. */ function buildReplacement( // Only grab the tokens of interest that should be included in the // replacement string. These are obtained from the match instead of the // configuration to ensure additional information in the templated area is // preserved, e.g. whitespace, etc. _, // Full match template_begin, template, template_end, replacement_end, __, // Index of match start ___, // Searched text filename, context, ) { const { next_version_placeholder_token, template_continuation_token } = getTemplateTokenConfiguration(filename); const normalized_template = stripTemplateContinuationTokens( template_continuation_token, template, ); return [ template_begin, template, template_end, normalized_template.replaceAll( next_version_placeholder_token, context.nextRelease.version, ), replacement_end, ].join(""); } /** * Builds a regular expression to find templated regions in a file. Templated * regions are defined by a token configuration based on the {@param filename}'s * extension. * * The resulting regular expression is set up to capture: * - The line starting the template, * - One or more lines defining the template in which * `next_version_placeholder_token` should be replaced by the next semantic * release version, * - The line indicating the end of the template. * - The line indicating the end of the replacement. * * Any content between the end of the template and the end of the replacement * is dropped. */ function buildTemplateMatcher(filename) { const { replacement_end_token, template_begin_token, template_end_token } = getTemplateTokenConfiguration(filename); return new RegExp( [ `(\n[^\n]*${template_begin_token}[^\n]*)`, // The template to insert the version into. Needs to lazily match to // ensure only content up until the first `template_end_token` is matched // to prevent issues when multiple templates are present in a single file "((?:\n[^\n]*?)+?)", `(\n[^\n]*${template_end_token}[^\n]*)`, // The templated content, will be discarded. Needs to lazily match to // ensure only content up until the first `replacement_end_token` is // grabbed ".*?", `(\n[^\n]*${replacement_end_token}[^\n]*?)`, ].join(""), "gs", ); } /** * Returns the template token configuration corresponding to the specified * {@param filename} based on its extension. Extension specific configuration * is merged with the {@link DEFAULT_TEMPLATE_TOKEN_CONFIGURATION} */ function getTemplateTokenConfiguration(filename) { // `extname` returns the extension with a dot-prefix return { ...DEFAULT_TEMPLATE_TOKEN_CONFIGURATION, ...FILE_EXTENSION_TO_TEMPLATE_CONFIGURATION[extname(filename).slice(1)], }; } /** * Strips the {@param template_continuation_token} and the largest common * length of whitespace after those tokens from the {@param template}. * * For instance, assuming the {@param template_continuation_token} is `#`, this * will turn * ``` * # Foo bar * # Quuz Quux * ``` * into * ``` * Foo bar * Quuz Quux * ``` */ function stripTemplateContinuationTokens( template_continuation_token, template, ) { // The capture group is only necessary when using this matcher in the context // of replacement, i.e. where all the matched content should be replaced by // the indentation. It is used in that context and for determining the // largest common whitespace prefix for each line in the template const indented_continuation_token_matcher = `^(\\s*)${template_continuation_token}`; const continuation_prefix_matcher = new RegExp( `${indented_continuation_token_matcher}(?<prefix>\\s*)`, ); const prefix_lengths = template .split("\n") // The first line in the template will be empty when splitting on newlines, // due to the way the matchers are set up to match on starting newlines .slice(1) .map((template_line) => { const match = template_line.match(continuation_prefix_matcher); return match?.groups?.prefix?.length ?? 0; }); const largest_common_prefix_matcher = new RegExp( `${indented_continuation_token_matcher}\\s{${Math.min(...prefix_lengths)}}`, "gm", ); return template.replaceAll(largest_common_prefix_matcher, "$1"); } /** * Returns the semantic release configuration for the * `semantic-release-replace-plugin` set up to replace specific tokens with the * next semantic release version in the provided {@param files}. */ export function VersionedTemplatesConfiguration(files) { return [ "semantic-release-replace-plugin", { replacements: [ { countMatches: true, files, from: buildTemplateMatcher, to: buildReplacement, }, ], }, ]; }