@ethima/semantic-release-configuration 3.3.4 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ configuration supporting a range of languages and platforms supported by the
13
13
  Commits specification][conventionalcommits-url].
14
14
  - Generates release notes using
15
15
  [`@semantic-release/release-notes-generator`][semantic-release-notes-generator-plugin-url].
16
- - Updates templated content in a project's root-level `README.md` file
16
+ - Updates templated content in files, e.g. a project's root-level `README.md`,
17
17
  with updated version information using
18
18
  [`@google/semantic-release-replace-plugin`][semantic-release-replace-plugin-url]
19
19
  and [`@semantic-release/git`][semantic-release-git-plugin-url].
@@ -106,39 +106,50 @@ The configuration is not able to automatically register releases with the
106
106
  General registry. Newly created releases should be registered using [JuliaHub's
107
107
  _Register Packages_ interface][juliahub-register-package-url].
108
108
 
109
- ### Templated Content in `README.md` Files
109
+ ### Templated Content in Files
110
110
 
111
111
  The configuration will look for `__NEXT_SEMANTIC_RELEASE_VERSION__` tokens in
112
- templates in a project's root-level `README.md` and replace them with the
113
- version that is being released. This is, for instance, useful for automatically
114
- keeping installation instructions up-to-date.
112
+ templates in files specified in the `files_with_versioned_templates`
113
+ configuration and replace them with the version that is being released. This
114
+ is, for instance, useful for automatically keeping installation instructions
115
+ up-to-date. The configuration defaults to a project's root-level `README.md`.
115
116
 
116
- Templated content is created using HTML comments and has the following
117
- structure:
117
+ Templated content has the following token-based structure:
118
118
 
119
- - A `<!-- START_VERSIONED_TEMPLATE` token,
119
+ - A `BEGIN_VERSIONED_TEMPLATE` token,
120
120
  - the template itself with one or more
121
121
  `__NEXT_SEMANTIC_RELEASE_VERSION__` tokens,
122
- - a comment closing tag `-->`,
123
- - (optionally) content that was previously templated,
124
- - a comment closing the templated content `<!-- END_VERSIONED_TEMPLATE -->`.
125
-
126
- More concretely a section in a `README` that looks like
122
+ - an `END_VERSIONED_TEMPLATE` token,
123
+ - (optionally) content that was previously templated and which will be
124
+ discarded,
125
+ - an `END_VERSIONED_TEMPLATE_REPLACEMENT` token.
126
+
127
+ The `BEGIN_VERSIONED_TEMPLATE`, `END_VERSIONED_TEMPLATE` and
128
+ `END_VERSIONED_TEMPLATE_REPLACEMENT` tokens must each be on their own line.
129
+ These lines may be indented and contain other content that is exempt from
130
+ replacements, e.g. comment markers to ensure the templates do not affect
131
+ surrounding code or documentation. The exact tokens may differ per file-type,
132
+ [see the source code for available
133
+ configurations](./src/versioned-templates.js). For instance, Markdown files can
134
+ use HTML block comments and may replace the literal `END_VERSIONED_TEMPLATE`
135
+ token with an "end comment" token.
136
+
137
+ More concretely a section in a `README.md` that looks like
127
138
 
128
139
  ```markdown
129
- <!-- START_VERSIONED_TEMPLATE
140
+ <!-- BEGIN_VERSIONED_TEMPLATE
130
141
 
131
142
  The next semantically released version will be v__NEXT_SEMANTIC_RELEASE_VERSION__!
132
143
 
133
144
  -->
134
- <!-- END_VERSIONED_TEMPLATE -->
145
+ <!-- END_VERSIONED_TEMPLATE_REPLACEMENT -->
135
146
  ```
136
147
 
137
148
  would, after a v1.2.3 release using the configuration has been triggered,
138
149
  become
139
150
 
140
151
  ```markdown
141
- <!-- START_VERSIONED_TEMPLATE
152
+ <!-- BEGIN_VERSIONED_TEMPLATE
142
153
 
143
154
  The next semantically released version will be v__NEXT_SEMANTIC_RELEASE_VERSION__!
144
155
 
@@ -146,7 +157,7 @@ The next semantically released version will be v__NEXT_SEMANTIC_RELEASE_VERSION_
146
157
 
147
158
  The next semantically released version will be v1.2.3!
148
159
 
149
- <!-- END_VERSIONED_TEMPLATE -->
160
+ <!-- END_VERSIONED_TEMPLATE_REPLACEMENT -->
150
161
  ```
151
162
 
152
163
  Note that the `v` is in the template! The version as derived by the semantic
@@ -182,7 +193,7 @@ including a non-visible whitespace character, e.g. [a _Zero Width Non-Joiner
182
193
  (ZWNJ)_ character](https://unicode-table.com/en/200C/). A sufficient approach
183
194
  is to modify the starting token, so that the template is no longer recognized
184
195
  as one. Using the suggested ZWNJ character the starting token becomes
185
- `<!--‌ START_VERSIONED_TEMPLATE`.
196
+ `<!--‌ BEGIN_VERSIONED_TEMPLATE`.
186
197
 
187
198
  Note that when using this approach, although the templates/tokens _visually_
188
199
  look like versions that should be replaced, they cannot be copied and pasted as
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethima/semantic-release-configuration",
3
- "version": "3.3.4",
3
+ "version": "4.0.0",
4
4
  "description": "A shareable semantic release configuration supporting a range of languages and platforms supported by the Ethima organization.",
5
5
  "main": "./src/index.js",
6
6
  "repository": {
@@ -24,6 +24,6 @@
24
24
  "@semantic-release/github": "8.0.7",
25
25
  "@semantic-release/gitlab": "12.0.1",
26
26
  "conventional-changelog-conventionalcommits": "5.0.0",
27
- "cosmiconfig": "8.1.0"
27
+ "cosmiconfig": "8.1.3"
28
28
  }
29
29
  }
@@ -1,42 +1,229 @@
1
- const BLOCK_COMMENT_END_PATTERN = "-->";
2
- const BLOCK_COMMENT_START_PATTERN = "<!--";
3
- const TEMPLATE_VERSION_PLACEHOLDER = "__NEXT_SEMANTIC_RELEASE_VERSION__";
4
-
5
- const templateMatcher = new RegExp(
6
- [
7
- `(${BLOCK_COMMENT_START_PATTERN}\\s*START_VERSIONED_TEMPLATE\\s*)`,
8
- "(.*?)", // The template to insert the version into
9
- `(\\s*${BLOCK_COMMENT_END_PATTERN})`,
10
- ".*?", // The templated content, will be discarded
11
- `(${BLOCK_COMMENT_START_PATTERN}\\s*END_VERSIONED_TEMPLATE\\s*${BLOCK_COMMENT_END_PATTERN})`,
12
- ].join(""),
13
- "gms"
14
- );
15
-
16
- function templateReplacer(
17
- _,
18
- templateStartMarker,
1
+ const { extname } = require("node:path");
2
+
3
+ /**
4
+ * The default configuration for tokens used by the {@link buildReplacement}
5
+ * and {@link buildTemplateMatcher} functions to parse templates and perform
6
+ * replacements.
7
+ *
8
+ * The tokens serve the following purposes:
9
+ * - `next_version_placeholder_token` represents locations in a template that
10
+ * need to be replaced by the version of the next semantic release.
11
+ * - `replacement_end_token` represents the end of content that was previously
12
+ * filled in. Should be on a separate line. This line may contain additional
13
+ * content which will be maintained along with the token in the replaced
14
+ * content, e.g. indentation, comment markers, etc..
15
+ * - `template_begin_token` indicates the start of the template in which
16
+ * `next_version_placeholder_token`s should be replaced. Should be on a
17
+ * separate line. This line may contain additional content which will be
18
+ * maintained along with the token in the replaced content, e.g. indentation,
19
+ * comment markers, etc..
20
+ * - `template_continuation_token` represents indented markers that are
21
+ * necessary to indicate continuation of the template, but which should not
22
+ * be present in the replaced content, e.g. comment indicators for line-based
23
+ * comments, etc. Indented whitespace does not have to be configured in the
24
+ * token.
25
+ * - `template_end_token` indicates the end of the template. Should be on a
26
+ * separate line. This line may contain additional content which will be
27
+ * maintained along with the token in the replaced content, e.g. indentation,
28
+ * comment markers, etc..
29
+ */
30
+ const DEFAULT_TEMPLATE_TOKEN_CONFIGURATION = {
31
+ next_version_placeholder_token: "__NEXT_SEMANTIC_RELEASE_VERSION__",
32
+ replacement_end_token: "END_VERSIONED_TEMPLATE_REPLACEMENT",
33
+ template_begin_token: "BEGIN_VERSIONED_TEMPLATE",
34
+ template_continuation_token: "",
35
+ template_end_token: "END_VERSIONED_TEMPLATE",
36
+ };
37
+
38
+ /**
39
+ * Template token configuration for files that can be commented using "C-style
40
+ * comments", e.g. C, CSS, JavaScript, TypeScript, etc.
41
+ */
42
+ const C_BLOCK_TEMPLATE_TOKEN_CONFIGURATION = {
43
+ template_continuation_token: "\\s\\*+",
44
+ template_end_token: "\\*+/",
45
+ };
46
+
47
+ /**
48
+ * Template token configuration for files that can be commented using "hash
49
+ * signs", e.g. Python, TOML, YAML, etc.
50
+ */
51
+ const HASH_TEMPLATE_TOKEN_CONFIGURATION = {
52
+ template_continuation_token: "#",
53
+ };
54
+
55
+ /**
56
+ * Template token configuration for files that can be commented using XML
57
+ * comments, e.g. HTML, Markdown, etc.
58
+ */
59
+ const XML_TEMPLATE_TOKEN_CONFIGURATION = {
60
+ template_end_token: "-->",
61
+ };
62
+
63
+ /**
64
+ * Maps file extensions to token configurations for templates that can be
65
+ * included in the specified file formats.
66
+ */
67
+ const FILE_EXTENSION_TO_TEMPLATE_CONFIGURATION = {
68
+ c: C_BLOCK_TEMPLATE_TOKEN_CONFIGURATION,
69
+ css: C_BLOCK_TEMPLATE_TOKEN_CONFIGURATION,
70
+ html: XML_TEMPLATE_TOKEN_CONFIGURATION,
71
+ jl: HASH_TEMPLATE_TOKEN_CONFIGURATION,
72
+ js: C_BLOCK_TEMPLATE_TOKEN_CONFIGURATION,
73
+ md: XML_TEMPLATE_TOKEN_CONFIGURATION,
74
+ py: HASH_TEMPLATE_TOKEN_CONFIGURATION,
75
+ toml: HASH_TEMPLATE_TOKEN_CONFIGURATION,
76
+ ts: C_BLOCK_TEMPLATE_TOKEN_CONFIGURATION,
77
+ xml: XML_TEMPLATE_TOKEN_CONFIGURATION,
78
+ yaml: HASH_TEMPLATE_TOKEN_CONFIGURATION,
79
+ yml: HASH_TEMPLATE_TOKEN_CONFIGURATION,
80
+ };
81
+
82
+ /**
83
+ * Builds a replacement string from the components matched by a matcher created
84
+ * by {@link buildTemplateMatcher}. Any `next_version_placeholder_token`s, as
85
+ * identified by the {@link DEFAULT_TEMPLATE_TOKEN_CONFIGURATION}, are replaced
86
+ * by the next semantic release version. Any "template continuation tokens" are
87
+ * removed from the template using {@link stripTemplateContinuationTokens}.
88
+ */
89
+ function buildReplacement(
90
+ // Only grab the tokens of interest that should be included in the
91
+ // replacement string. These are obtained from the match instead of the
92
+ // configuration to ensure additional information in the templated area is
93
+ // preserved, e.g. whitespace, etc.
94
+ _, // Full match
95
+ template_begin,
19
96
  template,
20
- templateEndMarker,
21
- templateCloseMarker,
22
- ...args
97
+ template_end,
98
+ replacement_end,
99
+ _, // Index of match start
100
+ _, // Searched text
101
+ filename,
102
+ context
23
103
  ) {
24
- const context = args.pop();
104
+ const { next_version_placeholder_token, template_continuation_token } =
105
+ getTemplateTokenConfiguration(filename);
106
+
107
+ const normalized_template = stripTemplateContinuationTokens(
108
+ template_continuation_token,
109
+ template
110
+ );
25
111
 
26
112
  return [
27
- templateStartMarker,
113
+ template_begin,
28
114
  template,
29
- templateEndMarker,
30
- `\n\n`,
31
- template.replaceAll(
32
- TEMPLATE_VERSION_PLACEHOLDER,
115
+ template_end,
116
+ normalized_template.replaceAll(
117
+ next_version_placeholder_token,
33
118
  context.nextRelease.version
34
119
  ),
35
- `\n\n`,
36
- templateCloseMarker,
120
+ replacement_end,
37
121
  ].join("");
38
122
  }
39
123
 
124
+ /**
125
+ * Builds a regular expression to find templated regions in a file. Templated
126
+ * regions are defined by a token configuration based on the {@param filename}'s
127
+ * extension.
128
+ *
129
+ * The resulting regular expression is set up to capture:
130
+ * - The line starting the template,
131
+ * - One or more lines defining the template in which
132
+ * `next_version_placeholder_token` should be replaced by the next semantic
133
+ * release version,
134
+ * - The line indicating the end of the template.
135
+ * - The line indicating the end of the replacement.
136
+ *
137
+ * Any content between the end of the template and the end of the replacement
138
+ * is dropped.
139
+ */
140
+ function buildTemplateMatcher(filename) {
141
+ const { replacement_end_token, template_begin_token, template_end_token } =
142
+ getTemplateTokenConfiguration(filename);
143
+
144
+ return new RegExp(
145
+ [
146
+ `(\n[^\n]*${template_begin_token}[^\n]*)`,
147
+ // The template to insert the version into. Needs to lazily match to
148
+ // ensure only content up until the first `template_end_token` is matched
149
+ // to prevent issues when multiple templates are present in a single file
150
+ "((?:\n[^\n]*?)+?)",
151
+ `(\n[^\n]*${template_end_token}[^\n]*)`,
152
+ // The templated content, will be discarded. Needs to lazily match to
153
+ // ensure only content up until the first `replacement_end_token` is
154
+ // grabbed
155
+ ".*?",
156
+ `(\n[^\n]*${replacement_end_token}[^\n]*?)`,
157
+ ].join(""),
158
+ "gs"
159
+ );
160
+ }
161
+
162
+ /**
163
+ * Returns the template token configuration corresponding to the specified
164
+ * {@param filename} based on its extension. Extension specific configuration
165
+ * is merged with the {@link DEFAULT_TEMPLATE_TOKEN_CONFIGURATION}
166
+ */
167
+ function getTemplateTokenConfiguration(filename) {
168
+ // `extname` returns the extension with a dot-prefix
169
+ return {
170
+ ...DEFAULT_TEMPLATE_TOKEN_CONFIGURATION,
171
+ ...FILE_EXTENSION_TO_TEMPLATE_CONFIGURATION[extname(filename).slice(1)],
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Strips the {@param template_continuation_token} and the largest common
177
+ * length of whitespace after those tokens from the {@param template}.
178
+ *
179
+ * For instance, assuming the {@param template_continuation_token} is `#`, this
180
+ * will turn
181
+ * ```
182
+ * # Foo bar
183
+ * # Quuz Quux
184
+ * ```
185
+ * into
186
+ * ```
187
+ * Foo bar
188
+ * Quuz Quux
189
+ * ```
190
+ */
191
+ function stripTemplateContinuationTokens(
192
+ template_continuation_token,
193
+ template
194
+ ) {
195
+ // The capture group is only necessary when using this matcher in the context
196
+ // of replacement, i.e. where all the matched content should be replaced by
197
+ // the indentation. It is used in that context and for determining the
198
+ // largest common whitespace prefix for each line in the template
199
+ const indented_continuation_token_matcher = `^(\\s*)${template_continuation_token}`;
200
+ const continuation_prefix_matcher = new RegExp(
201
+ `${indented_continuation_token_matcher}(?<prefix>\\s*)`
202
+ );
203
+
204
+ const prefix_lengths = template
205
+ .split("\n")
206
+ // The first line in the template will be empty when splitting on newlines,
207
+ // due to the way the matchers are set up to match on starting newlines
208
+ .slice(1)
209
+ .map((template_line) => {
210
+ const match = template_line.match(continuation_prefix_matcher);
211
+ return match?.groups?.prefix?.length ?? 0;
212
+ });
213
+
214
+ const largest_common_prefix_matcher = new RegExp(
215
+ `${indented_continuation_token_matcher}\\s{${Math.min(...prefix_lengths)}}`,
216
+ "gm"
217
+ );
218
+
219
+ return template.replaceAll(largest_common_prefix_matcher, "$1");
220
+ }
221
+
222
+ /**
223
+ * Returns the semantic release configuration for the
224
+ * `@google/semantic-release-replace-plugin` set up to replace specific tokens
225
+ * with the next semantic release version in the provided {@param files}.
226
+ */
40
227
  function VersionedTemplatesConfiguration(files) {
41
228
  return [
42
229
  "@google/semantic-release-replace-plugin",
@@ -45,8 +232,8 @@ function VersionedTemplatesConfiguration(files) {
45
232
  {
46
233
  countMatches: true,
47
234
  files,
48
- from: templateMatcher,
49
- to: templateReplacer,
235
+ from: buildTemplateMatcher,
236
+ to: buildReplacement,
50
237
  },
51
238
  ],
52
239
  },