@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
JavaScript
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,
},
],
},
];
}