fork-version 4.1.10 → 5.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.js +54 -193
  4. package/dist/commands/inspect.d.ts +9 -0
  5. package/dist/commands/inspect.js +41 -0
  6. package/dist/commands/main.d.ts +16 -0
  7. package/dist/commands/main.js +30 -0
  8. package/dist/commands/validate-config.d.ts +6 -0
  9. package/dist/commands/validate-config.js +11 -0
  10. package/dist/commit-parser/commit-parser.d.ts +114 -0
  11. package/dist/commit-parser/commit-parser.js +327 -0
  12. package/dist/commit-parser/filter-reverted-commits.d.ts +17 -0
  13. package/dist/commit-parser/filter-reverted-commits.js +34 -0
  14. package/dist/commit-parser/options.d.ts +74 -0
  15. package/dist/commit-parser/options.js +70 -0
  16. package/dist/commit-parser/parser-error.js +14 -0
  17. package/dist/commit-parser/types.d.ts +53 -0
  18. package/dist/config/changelog-preset-config.js +41 -0
  19. package/dist/config/cli-arguments.d.ts +109 -0
  20. package/dist/config/cli-arguments.js +141 -0
  21. package/dist/config/defaults.js +38 -0
  22. package/dist/config/define-config.d.ts +9 -0
  23. package/dist/config/define-config.js +9 -0
  24. package/dist/config/load-config.js +45 -0
  25. package/dist/config/merge-files.js +12 -0
  26. package/dist/config/schema.d.ts +50 -0
  27. package/dist/config/schema.js +61 -0
  28. package/dist/config/types.d.ts +279 -0
  29. package/dist/config/user-config.d.ts +6 -0
  30. package/dist/config/user-config.js +50 -0
  31. package/dist/detect-git-host/detect-git-host.js +35 -0
  32. package/dist/detect-git-host/host-azure-devops.js +28 -0
  33. package/dist/detect-git-host/host-bitbucket.js +28 -0
  34. package/dist/detect-git-host/host-github.js +32 -0
  35. package/dist/detect-git-host/host-gitlab.js +48 -0
  36. package/dist/files/arm-bicep.js +44 -0
  37. package/dist/files/file-manager.d.ts +47 -0
  38. package/dist/files/file-manager.js +65 -0
  39. package/dist/files/install-shield-ism.js +59 -0
  40. package/dist/files/json-package.js +68 -0
  41. package/dist/files/ms-build-project.js +59 -0
  42. package/dist/files/plain-text.js +35 -0
  43. package/dist/files/yaml-package.js +61 -0
  44. package/dist/index.d.ts +21 -655
  45. package/dist/index.js +19 -10
  46. package/dist/process/changelog.d.ts +7 -0
  47. package/dist/process/changelog.js +69 -0
  48. package/dist/process/commit.d.ts +9 -0
  49. package/dist/process/commit.js +22 -0
  50. package/dist/process/get-commits.d.ts +14 -0
  51. package/dist/process/get-commits.js +25 -0
  52. package/dist/process/get-current-version.d.ts +13 -0
  53. package/dist/process/get-current-version.js +35 -0
  54. package/dist/process/get-next-version.d.ts +21 -0
  55. package/dist/process/get-next-version.js +72 -0
  56. package/dist/process/tag.d.ts +8 -0
  57. package/dist/process/tag.js +15 -0
  58. package/dist/services/git.d.ts +141 -0
  59. package/dist/services/git.js +236 -0
  60. package/dist/services/logger.d.ts +18 -0
  61. package/dist/services/logger.js +35 -0
  62. package/dist/utils/clean-tag.js +21 -0
  63. package/dist/utils/escape-regex.js +17 -0
  64. package/dist/utils/file-state.js +19 -0
  65. package/dist/utils/format-commit-message.js +13 -0
  66. package/dist/utils/parse-regexp-string.js +31 -0
  67. package/dist/utils/release-type.js +47 -0
  68. package/dist/utils/trim-string-array.js +20 -0
  69. package/package.json +11 -29
  70. package/dist/chunk-33WIJWQZ.cjs +0 -2306
  71. package/dist/chunk-33WIJWQZ.cjs.map +0 -1
  72. package/dist/chunk-L5UDUEHE.js +0 -2262
  73. package/dist/chunk-L5UDUEHE.js.map +0 -1
  74. package/dist/cli.cjs +0 -205
  75. package/dist/cli.cjs.map +0 -1
  76. package/dist/cli.d.cts +0 -1
  77. package/dist/cli.js.map +0 -1
  78. package/dist/index.cjs +0 -80
  79. package/dist/index.cjs.map +0 -1
  80. package/dist/index.d.cts +0 -655
  81. package/dist/index.js.map +0 -1
@@ -1,2262 +0,0 @@
1
- import { z } from 'zod';
2
- import { execFile } from 'child_process';
3
- import semver5 from 'semver';
4
- import { resolve, join, isAbsolute, parse, basename } from 'path';
5
- import { glob } from 'fs/promises';
6
- import conventionalChangelogConfigSpec from 'conventional-changelog-config-spec';
7
- import { writeFileSync, readFileSync, lstatSync } from 'fs';
8
- import JoyCon from 'joycon';
9
- import { bundleRequire } from 'bundle-require';
10
- import { styleText } from 'util';
11
- import { modify, applyEdits, parse as parse$1 } from 'jsonc-parser';
12
- import { parse as parse$2, parseDocument } from 'yaml';
13
- import * as cheerio from 'cheerio/slim';
14
- import conventionalChangelog from 'conventional-changelog';
15
-
16
- // src/config/schema.js
17
- var ChangelogPresetConfigTypeSchema = z.object({
18
- /**
19
- * The type of commit message.
20
- * @example "feat", "fix", "chore", etc..
21
- */
22
- type: z.string().describe('The type of commit message, such as "feat", "fix", "chore".'),
23
- /**
24
- * The scope of the commit message.
25
- */
26
- scope: z.string().optional().describe("The scope of the commit message."),
27
- /**
28
- * The section of the `CHANGELOG` the commit should show up in.
29
- */
30
- section: z.string().optional().describe("The section of the `CHANGELOG` the commit should show up in."),
31
- /**
32
- * Should show in the generated changelog message?
33
- */
34
- hidden: z.boolean().optional().describe("Should show in the generated changelog message?")
35
- });
36
- var ChangelogPresetConfigSchema = z.object({
37
- /**
38
- * List of explicitly supported commit message types.
39
- */
40
- types: z.array(ChangelogPresetConfigTypeSchema).describe("List of explicitly supported commit message types."),
41
- /**
42
- * A URL representing a specific commit at a hash.
43
- * @default "{{host}}/{{owner}}/{{repository}}/commit/{{hash}}"
44
- */
45
- commitUrlFormat: z.string().describe("A URL representing a specific commit at a hash."),
46
- /**
47
- * A URL representing the comparison between two git SHAs.
48
- * @default "{{host}}/{{owner}}/{{repository}}/compare/{{previousTag}}...{{currentTag}}"
49
- */
50
- compareUrlFormat: z.string().describe("A URL representing the comparison between two git SHAs."),
51
- /**
52
- * A URL representing the issue format (allowing a different URL format to be swapped in
53
- * for Gitlab, Bitbucket, etc).
54
- * @default "{{host}}/{{owner}}/{{repository}}/issues/{{id}}"
55
- */
56
- issueUrlFormat: z.string().describe("A URL representing the issue format."),
57
- /**
58
- * A URL representing a user's profile on GitHub, Gitlab, etc. This URL is used
59
- * for substituting @eglavin with https://github.com/eglavin in commit messages.
60
- * @default "{{host}}/{{user}}"
61
- */
62
- userUrlFormat: z.string().describe("A URL representing a user's profile on GitHub, Gitlab, etc."),
63
- /**
64
- * A string to be used to format the auto-generated release commit message.
65
- * @default "chore(release): {{currentTag}}"
66
- */
67
- releaseCommitMessageFormat: z.string().describe("A string to be used to format the auto-generated release commit message."),
68
- /**
69
- * List of prefixes used to detect references to issues.
70
- * @default ["#"]
71
- */
72
- issuePrefixes: z.array(z.string()).describe("List of prefixes used to detect references to issues.")
73
- });
74
- var ForkConfigSchema = z.object({
75
- // Commands
76
- //
77
- /**
78
- * The command to run, can be one of the following:
79
- *
80
- * - `main` - Bumps the version, update files, generate changelog, commit, and tag.
81
- * - `inspect-version` - Prints the current version and exits.
82
- * - `inspect-tag` - Prints the current git tag and exits.
83
- * - `inspect` - Prints the current version and git tag and exits.
84
- * - `validate-config` - Validates the configuration and exits.
85
- *
86
- * @default "main"
87
- */
88
- command: z.literal(["main", "inspect", "inspect-version", "inspect-tag", "validate-config"]).describe(
89
- "The command to run. Can be one of: main, inspect, inspect-version, inspect-tag, validate-config. Defaults to main."
90
- ),
91
- /**
92
- * If set, Fork-Version will print the current version and exit.
93
- * @default false
94
- *
95
- * @deprecated Set the `inspect-version` command instead.
96
- */
97
- inspectVersion: z.boolean().optional().describe("If set, Fork-Version will print the current version and exit."),
98
- // Options
99
- //
100
- /**
101
- * List of the files to be updated.
102
- * @default
103
- * ```js
104
- * ["bower.json", "deno.json", "deno.jsonc", "jsr.json", "jsr.jsonc", "manifest.json", "npm-shrinkwrap.json", "package-lock.json", "package.json"]
105
- * ```
106
- */
107
- files: z.array(z.string()).describe("List of the files to be updated."),
108
- /**
109
- * Glob pattern to match files to be updated.
110
- *
111
- * Internally we're using [glob](https://github.com/isaacs/node-glob) to match files.
112
- *
113
- * Read more about the pattern syntax [here](https://github.com/isaacs/node-glob/tree/v10.3.12?tab=readme-ov-file#glob-primer).
114
- *
115
- * @default undefined
116
- * @example "*.json"
117
- */
118
- glob: z.string().optional().describe("Glob pattern to match files to be updated."),
119
- /**
120
- * The path Fork-Version will run from.
121
- * @default
122
- * ```js
123
- * process.cwd()
124
- * ```
125
- */
126
- path: z.string().describe('The path Fork-Version will run from. Defaults to "process.cwd()".'),
127
- /**
128
- * Name of the changelog file.
129
- * @default "CHANGELOG.md"
130
- */
131
- changelog: z.string().describe('Name of the changelog file. Defaults to "CHANGELOG.md".'),
132
- /**
133
- * The header text for the changelog.
134
- * @default
135
- * ```markdown
136
- * # Changelog
137
- *
138
- * All notable changes to this project will be documented in this file. See [fork-version](https://github.com/eglavin/fork-version) for commit guidelines.
139
- * ```
140
- */
141
- header: z.string().describe("The header text for the changelog."),
142
- /**
143
- * Specify a prefix for the created tag.
144
- *
145
- * For instance if your version tag is prefixed by "version/" instead of "v" you have to specify
146
- * `tagPrefix: "version/"`.
147
- *
148
- * `tagPrefix` can also be used for a monorepo environment where you might want to deploy
149
- * multiple package from the same repository. In this case you can specify a prefix for
150
- * each package:
151
- *
152
- * | Example Value | Tag Created |
153
- * |:-------------------------|:------------------------------|
154
- * | "" | `1.2.3` |
155
- * | "version/" | `version/1.2.3` |
156
- * | "@eglavin/fork-version-" | `@eglavin/fork-version-1.2.3` |
157
- *
158
- * @example "", "version/", "@eglavin/fork-version-"
159
- * @default "v"
160
- */
161
- tagPrefix: z.string().describe('Specify a prefix for the created tag. Defaults to "v".'),
162
- /**
163
- * Make a pre-release with optional label if given value is a string.
164
- *
165
- * | Example Value | Produced Version |
166
- * |:--------------|:-----------------|
167
- * | true | `1.2.3-0` |
168
- * | "alpha" | `1.2.3-alpha-0` |
169
- * | "beta" | `1.2.3-beta-0` |
170
- *
171
- * @example true, "alpha", "beta", "rc"
172
- * @default undefined
173
- */
174
- preRelease: z.string().or(z.boolean()).optional().describe("Make a pre-release with optional label if given value is a string."),
175
- /**
176
- * If set, Fork-Version will use this version instead of trying to determine one.
177
- * @example "1.0.0"
178
- * @default undefined
179
- */
180
- currentVersion: z.string().optional().describe("If set, Fork-Version will use this version instead of trying to determine one."),
181
- /**
182
- * If set, Fork-Version will attempt to update to this version, instead of incrementing using "conventional-commit".
183
- * @example "2.0.0"
184
- * @default undefined
185
- */
186
- nextVersion: z.string().optional().describe(
187
- 'If set, Fork-Version will attempt to update to this version, instead of incrementing using "conventional-commit".'
188
- ),
189
- /**
190
- * Release as increments the version by the specified level. Overrides the default behaviour of "conventional-commit".
191
- * @example "major", "minor", "patch"
192
- * @default undefined
193
- */
194
- releaseAs: z.union([z.literal("major"), z.literal("minor"), z.literal("patch")]).optional().describe(
195
- 'Release as increments the version by the specified level. Overrides the default behaviour of "conventional-commit".'
196
- ),
197
- // Flags
198
- //
199
- /**
200
- * Don't throw an error if multiple versions are found in the given files.
201
- * @default true
202
- */
203
- allowMultipleVersions: z.boolean().describe("Don't throw an error if multiple versions are found in the given files."),
204
- /**
205
- * Commit all changes, not just files updated by Fork-Version.
206
- * @default false
207
- */
208
- commitAll: z.boolean().describe("Commit all changes, not just files updated by Fork-Version."),
209
- /**
210
- * By default the conventional-changelog spec will only add commit types of `feat` and `fix` to the generated changelog.
211
- * If this flag is set, all [default commit types](https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/238093090c14bd7d5151eb5316e635623ce633f9/versions/2.2.0/schema.json#L18)
212
- * will be added to the changelog.
213
- * @default false
214
- */
215
- changelogAll: z.boolean().describe(
216
- "If this flag is set, all default commit types will be added to the changelog, not just `feat` and `fix`."
217
- ),
218
- /**
219
- * Output debug information.
220
- * @default false
221
- */
222
- debug: z.boolean().describe("Output debug information."),
223
- /**
224
- * No output will be written to disk or committed.
225
- * @default false
226
- */
227
- dryRun: z.boolean().describe("No output will be written to disk or committed."),
228
- /**
229
- * Run without logging to the terminal.
230
- * @default false
231
- */
232
- silent: z.boolean().describe("Run without logging to the terminal."),
233
- /**
234
- * If unable to find a version in the given files, fallback and attempt to use the latest git tag.
235
- * @default true
236
- */
237
- gitTagFallback: z.boolean().describe(
238
- "If unable to find a version in the given files, fallback and attempt to use the latest git tag. Defaults to true."
239
- ),
240
- /**
241
- * If true, git will sign the commit with the systems GPG key.
242
- * @see {@link https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--Sltkeyidgt Git - GPG Sign Commits}
243
- * @default false
244
- */
245
- sign: z.boolean().describe("If true, git will sign the commit with the systems GPG key."),
246
- /**
247
- * If true, git will run user defined git hooks before committing.
248
- * @see {@link https://git-scm.com/docs/githooks Git - Git Hooks}
249
- * @default false
250
- */
251
- verify: z.boolean().describe("If true, git will run user defined git hooks before committing."),
252
- /**
253
- * Print inspected output as a parsable json string.
254
- * @default false
255
- */
256
- asJson: z.boolean().describe("Print inspected output as a parsable json string."),
257
- // Skip Steps
258
- //
259
- /**
260
- * Skip the bump step.
261
- * @default false
262
- */
263
- skipBump: z.boolean().describe("Skip the bump step."),
264
- /**
265
- * Skip the changelog step.
266
- * @default false
267
- */
268
- skipChangelog: z.boolean().describe("Skip the changelog step."),
269
- /**
270
- * Skip the commit step.
271
- * @default false
272
- */
273
- skipCommit: z.boolean().describe("Skip the commit step."),
274
- /**
275
- * Skip the tag step.
276
- * @default false
277
- */
278
- skipTag: z.boolean().describe("Skip the tag step."),
279
- // Parser Options
280
- //
281
- /**
282
- * The detected git host:
283
- * - `GitHub`
284
- * - `GitLab`
285
- * - `Bitbucket`
286
- * - `Azure Devops`
287
- * - Or undefined if unknown or not detected.
288
- */
289
- detectedGitHost: z.string().optional().describe(
290
- "The detected git host, such as GitHub, GitLab, Bitbucket, Azure Devops, or undefined if unknown or not detected."
291
- ),
292
- /**
293
- * Override the default "conventional-changelog-conventionalcommits" preset configuration.
294
- */
295
- changelogPresetConfig: ChangelogPresetConfigSchema.partial().optional().describe(
296
- 'Override the default "conventional-changelog-conventionalcommits" preset configuration.'
297
- ),
298
- /**
299
- * Add a suffix to the release commit message.
300
- * @example "[skip ci]"
301
- */
302
- releaseMessageSuffix: z.string().optional().describe("Add a suffix to the release commit message."),
303
- /**
304
- * Options to pass to commits parser.
305
- */
306
- commitParserOptions: z.looseObject().optional().describe("Options to pass to commits parser.")
307
- });
308
-
309
- // src/utils/escape-regex.ts
310
- function escapeRegex(input) {
311
- return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
312
- }
313
-
314
- // src/services/git.ts
315
- var Git = class {
316
- #path;
317
- #dryRun;
318
- constructor(config) {
319
- this.#path = config.path;
320
- this.#dryRun = config.dryRun ?? false;
321
- this.add = this.add.bind(this);
322
- this.commit = this.commit.bind(this);
323
- this.tag = this.tag.bind(this);
324
- this.log = this.log.bind(this);
325
- this.isIgnored = this.isIgnored.bind(this);
326
- this.getBranchName = this.getBranchName.bind(this);
327
- this.getRemoteUrl = this.getRemoteUrl.bind(this);
328
- this.getTags = this.getTags.bind(this);
329
- this.getMostRecentTag = this.getMostRecentTag.bind(this);
330
- this.getCommits = this.getCommits.bind(this);
331
- }
332
- async #execGit(command, args) {
333
- return new Promise((onResolve, onReject) => {
334
- execFile(
335
- "git",
336
- [command, ...args],
337
- {
338
- cwd: this.#path,
339
- maxBuffer: Infinity
340
- },
341
- (error, stdout, stderr) => {
342
- if (error) {
343
- onReject(error);
344
- } else {
345
- onResolve(stdout ? stdout : stderr);
346
- }
347
- }
348
- );
349
- });
350
- }
351
- /**
352
- * Add file contents to the index
353
- *
354
- * [git-add Documentation](https://git-scm.com/docs/git-add)
355
- *
356
- * @example
357
- * ```ts
358
- * await git.add("CHANGELOG.md");
359
- * ```
360
- */
361
- async add(...args) {
362
- if (this.#dryRun) {
363
- return "";
364
- }
365
- return this.#execGit("add", args.filter(Boolean));
366
- }
367
- /**
368
- * Record changes to the repository
369
- *
370
- * [git-commit Documentation](https://git-scm.com/docs/git-commit)
371
- *
372
- * @example
373
- * ```ts
374
- * await git.commit("--message", "chore(release): 1.2.3");
375
- * ```
376
- */
377
- async commit(...args) {
378
- if (this.#dryRun) {
379
- return "";
380
- }
381
- return this.#execGit("commit", args.filter(Boolean));
382
- }
383
- /**
384
- * Create, list, delete or verify a tag object
385
- *
386
- * [git-tag Documentation](https://git-scm.com/docs/git-tag)
387
- *
388
- * @example
389
- * ```ts
390
- * await git.tag("--annotate", "v1.2.3", "--message", "chore(release): 1.2.3");
391
- * ```
392
- */
393
- async tag(...args) {
394
- if (this.#dryRun) {
395
- return "";
396
- }
397
- return this.#execGit("tag", args.filter(Boolean));
398
- }
399
- /**
400
- * Show commit logs
401
- *
402
- * - [git-log Documentation](https://git-scm.com/docs/git-log)
403
- * - [pretty-formats Documentation](https://git-scm.com/docs/pretty-formats)
404
- *
405
- * @example
406
- * ```ts
407
- * await git.log("--oneline");
408
- * ```
409
- */
410
- async log(...args) {
411
- try {
412
- return await this.#execGit("log", args.filter(Boolean));
413
- } catch {
414
- return "";
415
- }
416
- }
417
- /**
418
- * Check if a file is ignored by git
419
- *
420
- * [git-check-ignore Documentation](https://git-scm.com/docs/git-check-ignore)
421
- *
422
- * @example
423
- * ```ts
424
- * await git.isIgnored("src/my-file.txt");
425
- * ```
426
- */
427
- async isIgnored(file) {
428
- try {
429
- await this.#execGit("check-ignore", ["--no-index", file]);
430
- return true;
431
- } catch (_error) {
432
- return false;
433
- }
434
- }
435
- /**
436
- * Get the name of the current branch
437
- *
438
- * [git-rev-parse Documentation](https://git-scm.com/docs/git-rev-parse)
439
- *
440
- * @example
441
- * ```ts
442
- * await git.getBranchName(); // "main"
443
- * ```
444
- */
445
- async getBranchName() {
446
- try {
447
- const branchName = await this.#execGit("rev-parse", ["--abbrev-ref", "HEAD"]);
448
- return branchName.trim();
449
- } catch {
450
- return "";
451
- }
452
- }
453
- /**
454
- * Get the URL of the remote repository
455
- *
456
- * [git-config Documentation](https://git-scm.com/docs/git-config)
457
- *
458
- * @example
459
- * ```ts
460
- * await git.getRemoteUrl(); // "https://github.com/eglavin/fork-version"
461
- * ```
462
- */
463
- async getRemoteUrl() {
464
- try {
465
- const remoteUrl = await this.#execGit("config", ["--get", "remote.origin.url"]);
466
- return remoteUrl.trim();
467
- } catch (_error) {
468
- return "";
469
- }
470
- }
471
- /**
472
- * `getTags` returns valid semver version tags in order of the commit history
473
- *
474
- * Using `git log` to get the commit history, we then parse the tags from the
475
- * commit details which is expected to be in the following format:
476
- * ```txt
477
- * commit 3841b1d05750d42197fe958e3d8e06df378a842d (HEAD -> main, tag: v1.0.2, tag: v1.0.1, tag: v1.0.0)
478
- * Author: Username <username@example.com>
479
- * Date: Sat Nov 9 15:00:00 2024 +0000
480
- *
481
- * chore(release): v1.0.0
482
- * ```
483
- *
484
- * - [Functionality extracted from the conventional-changelog - git-semver-tags project](https://github.com/conventional-changelog/conventional-changelog/blob/fac8045242099c016f5f3905e54e02b7d466bd7b/packages/git-semver-tags/index.js)
485
- * - [conventional-changelog git-semver-tags MIT Licence](https://github.com/conventional-changelog/conventional-changelog/blob/fac8045242099c016f5f3905e54e02b7d466bd7b/packages/git-semver-tags/LICENSE.md)
486
- *
487
- * @example
488
- * ```ts
489
- * await git.getTags("v"); // ["v1.0.2", "v1.0.1", "v1.0.0"]
490
- * ```
491
- */
492
- async getTags(tagPrefix) {
493
- const logOutput = await this.log("--decorate", "--no-color", "--date-order");
494
- const TAG_REGEX = /tag:\s*(?<tag>.+?)[,)]/gi;
495
- const tags = [];
496
- const escapedTagPrefix = tagPrefix ? escapeRegex(tagPrefix) : void 0;
497
- let tagMatch = null;
498
- while (tagMatch = TAG_REGEX.exec(logOutput)) {
499
- const { tag = "" } = tagMatch.groups ?? {};
500
- if (tagPrefix) {
501
- if (tag.startsWith(tagPrefix)) {
502
- const tagWithoutPrefix = tag.replace(new RegExp(`^${escapedTagPrefix}`), "");
503
- if (semver5.valid(tagWithoutPrefix)) {
504
- tags.push(tag);
505
- }
506
- }
507
- } else if (/^\d/.test(tag) && semver5.valid(tag)) {
508
- tags.push(tag);
509
- }
510
- }
511
- return tags;
512
- }
513
- /**
514
- * Returns the most recent tag from the commit history, or `undefined` if no valid semver tags are found
515
- *
516
- * @example
517
- * ```ts
518
- * await git.getMostRecentTag("v"); // "1.2.3"
519
- * ```
520
- */
521
- async getMostRecentTag(tagPrefix) {
522
- const tags = await this.getTags(tagPrefix);
523
- return tags[0] || void 0;
524
- }
525
- /**
526
- * Get commit history in a parsable format
527
- *
528
- * An array of strings with commit details is returned in the following format:
529
- * ```txt
530
- * subject
531
- * body
532
- * hash
533
- * committer date
534
- * committer name
535
- * committer email
536
- * ```
537
- *
538
- * @example
539
- * ```ts
540
- * await git.getCommits("v1.0.0", "HEAD", "src/utils");
541
- * ```
542
- */
543
- async getCommits(from = "", to = "HEAD", ...paths) {
544
- const SCISSOR = "^----------- FORK VERSION -----------^";
545
- const LOG_FORMAT = [
546
- "%s",
547
- // subject
548
- "%b",
549
- // body
550
- "%H",
551
- // hash
552
- "%d",
553
- // ref names
554
- "%cI",
555
- // committer date
556
- "%cN",
557
- // committer name
558
- "%cE",
559
- // committer email
560
- SCISSOR
561
- ].join("%n");
562
- const commits = await this.log(
563
- `--format=${LOG_FORMAT}`,
564
- [from, to].filter(Boolean).join(".."),
565
- paths.length ? "--" : "",
566
- ...paths
567
- );
568
- const splitCommits = commits.split(`
569
- ${SCISSOR}
570
- `);
571
- if (splitCommits.length === 0) {
572
- return splitCommits;
573
- }
574
- if (splitCommits[0] === SCISSOR) {
575
- splitCommits.shift();
576
- }
577
- if (splitCommits[splitCommits.length - 1] === "") {
578
- splitCommits.pop();
579
- }
580
- return splitCommits;
581
- }
582
- };
583
- function getChangelogPresetConfig(mergedConfig, cliArguments, detectedChangelogOptions) {
584
- const preset = {
585
- name: "conventionalcommits"
586
- };
587
- if (typeof conventionalChangelogConfigSpec.properties === "object") {
588
- Object.entries(conventionalChangelogConfigSpec.properties).forEach(([key, value]) => {
589
- if ("default" in value && value.default !== void 0) {
590
- if (mergedConfig?.changelogAll && key === "types") {
591
- const parsedTypes = z.array(ChangelogPresetConfigTypeSchema).safeParse(value.default);
592
- if (parsedTypes.success) {
593
- parsedTypes.data.forEach((type) => {
594
- if (!type.section) {
595
- delete type.hidden;
596
- type.section = "Other Changes";
597
- }
598
- });
599
- preset[key] = parsedTypes.data;
600
- return;
601
- }
602
- }
603
- preset[key] = value.default;
604
- }
605
- });
606
- }
607
- if (detectedChangelogOptions) {
608
- Object.entries(detectedChangelogOptions).forEach(([key, value]) => {
609
- if (value !== void 0) {
610
- preset[key] = value;
611
- }
612
- });
613
- }
614
- if (mergedConfig?.changelogPresetConfig && typeof mergedConfig.changelogPresetConfig === "object") {
615
- Object.entries(mergedConfig.changelogPresetConfig).forEach(([key, value]) => {
616
- if (value !== void 0) {
617
- preset[key] = value;
618
- }
619
- });
620
- }
621
- if (mergedConfig?.releaseMessageSuffix && !cliArguments?.releaseMessageSuffix) {
622
- preset.releaseCommitMessageFormat = `${preset.releaseCommitMessageFormat} ${mergedConfig.releaseMessageSuffix}`;
623
- }
624
- if (cliArguments?.commitUrlFormat) {
625
- preset.commitUrlFormat = cliArguments.commitUrlFormat;
626
- }
627
- if (cliArguments?.compareUrlFormat) {
628
- preset.compareUrlFormat = cliArguments.compareUrlFormat;
629
- }
630
- if (cliArguments?.issueUrlFormat) {
631
- preset.issueUrlFormat = cliArguments.issueUrlFormat;
632
- }
633
- if (cliArguments?.userUrlFormat) {
634
- preset.userUrlFormat = cliArguments.userUrlFormat;
635
- }
636
- if (cliArguments?.releaseCommitMessageFormat) {
637
- preset.releaseCommitMessageFormat = cliArguments.releaseCommitMessageFormat;
638
- }
639
- if (cliArguments?.releaseMessageSuffix) {
640
- preset.releaseCommitMessageFormat = `${preset.releaseCommitMessageFormat} ${cliArguments.releaseMessageSuffix}`;
641
- }
642
- return ChangelogPresetConfigSchema.passthrough().parse(preset);
643
- }
644
-
645
- // src/config/defaults.ts
646
- var DEFAULT_CONFIG = {
647
- // Commands
648
- command: "main",
649
- // Options
650
- files: [
651
- "package.json",
652
- "package-lock.json",
653
- "npm-shrinkwrap.json",
654
- "jsr.json",
655
- "jsr.jsonc",
656
- "deno.json",
657
- "deno.jsonc",
658
- "manifest.json",
659
- // Chrome extensions
660
- "bower.json"
661
- ],
662
- path: process.cwd(),
663
- changelog: "CHANGELOG.md",
664
- header: `# Changelog
665
-
666
- All notable changes to this project will be documented in this file. See [fork-version](https://github.com/eglavin/fork-version) for commit guidelines.
667
- `,
668
- tagPrefix: "v",
669
- // Flags
670
- allowMultipleVersions: true,
671
- commitAll: false,
672
- changelogAll: false,
673
- debug: false,
674
- dryRun: false,
675
- silent: false,
676
- gitTagFallback: true,
677
- sign: false,
678
- verify: false,
679
- asJson: false,
680
- // Skip Steps
681
- skipBump: false,
682
- skipChangelog: false,
683
- skipCommit: false,
684
- skipTag: false
685
- };
686
-
687
- // src/detect-git-host/host-github.ts
688
- function detectGitHubOptions(remoteUrl) {
689
- let matches = null;
690
- if (/^https:\/\/(.*)?github\.com/.test(remoteUrl)) {
691
- matches = /^https:\/\/(.*)?github\.com\/(?<organisation>.*?)\/(?<repository>.*?)(?:\.git)?$/.exec(
692
- remoteUrl
693
- );
694
- } else if (remoteUrl.startsWith("git@github.com:")) {
695
- matches = /^git@github\.com:(?<organisation>.*?)\/(?<repository>.*?)(?:\.git)?$/.exec(
696
- remoteUrl
697
- );
698
- }
699
- if (matches?.groups) {
700
- const { organisation = "", repository = "" } = matches.groups;
701
- return {
702
- hostName: "GitHub",
703
- changelogOptions: {
704
- commitUrlFormat: `https://github.com/${organisation}/${repository}/commit/{{hash}}`,
705
- compareUrlFormat: `https://github.com/${organisation}/${repository}/compare/{{previousTag}}...{{currentTag}}`,
706
- issueUrlFormat: `https://github.com/${organisation}/${repository}/issues/{{id}}`,
707
- issuePrefixes: ["#", "gh-"]
708
- },
709
- commitParserOptions: {
710
- mergePattern: /^Merge pull request #(?<id>\d*) from (?<source>.*)/i,
711
- issuePrefixes: ["#", "gh-"]
712
- }
713
- };
714
- }
715
- return void 0;
716
- }
717
-
718
- // src/detect-git-host/host-gitlab.ts
719
- function detectGitlabOptions(remoteUrl) {
720
- let matches = null;
721
- if (/^https:\/\/(.*)?gitlab\.com/.test(remoteUrl)) {
722
- matches = /^https:\/\/(.*)?gitlab\.com\/(?<organisation>.*?)\/(?<repository>.*?)(?:\.git)?$/.exec(
723
- remoteUrl
724
- );
725
- } else if (remoteUrl.startsWith("git@gitlab.com:")) {
726
- matches = /^git@gitlab\.com:(?<organisation>.*?)\/(?<repository>.*?)(?:\.git)?$/.exec(
727
- remoteUrl
728
- );
729
- }
730
- if (matches?.groups) {
731
- const { organisation = "", repository = "" } = matches.groups;
732
- return {
733
- hostName: "GitLab",
734
- changelogOptions: {
735
- commitUrlFormat: `https://gitlab.com/${organisation}/${repository}/-/commit/{{hash}}`,
736
- compareUrlFormat: `https://gitlab.com/${organisation}/${repository}/-/compare/{{previousTag}}...{{currentTag}}`,
737
- issueUrlFormat: `https://gitlab.com/${organisation}/${repository}/-/issues/{{id}}`
738
- },
739
- commitParserOptions: {
740
- mergePattern: /^Merge branch '(?<source>.*)' into '(.*)'/i,
741
- // https://docs.gitlab.com/user/project/issues/managing_issues/#default-closing-pattern
742
- referenceActions: [
743
- "close",
744
- "closes",
745
- "closed",
746
- "closing",
747
- "fix",
748
- "fixes",
749
- "fixed",
750
- "fixing",
751
- "resolve",
752
- "resolves",
753
- "resolved",
754
- "resolving",
755
- "implement",
756
- "implements",
757
- "implemented",
758
- "implementing"
759
- ]
760
- }
761
- };
762
- }
763
- return void 0;
764
- }
765
-
766
- // src/detect-git-host/host-bitbucket.ts
767
- function detectBitbucketOptions(remoteUrl) {
768
- let matches = null;
769
- if (/^https:\/\/(.*)?bitbucket\.(org|com)/.test(remoteUrl)) {
770
- matches = /^https:\/\/(.*)?bitbucket\.(?<domain>org|com)\/(?<organisation>.*?)\/(?<repository>.*?)(?:\.git)?$/.exec(
771
- remoteUrl
772
- );
773
- } else if (remoteUrl.startsWith("git@bitbucket.org:")) {
774
- matches = /^git@bitbucket\.(?<domain>org|com):(?<organisation>.*?)\/(?<repository>.*?)(?:\.git)?$/.exec(
775
- remoteUrl
776
- );
777
- }
778
- if (matches?.groups) {
779
- const { domain = "", organisation = "", repository = "" } = matches.groups;
780
- return {
781
- hostName: "Bitbucket",
782
- changelogOptions: {
783
- commitUrlFormat: `https://bitbucket.${domain}/${organisation}/${repository}/commits/{{hash}}`,
784
- compareUrlFormat: `https://bitbucket.${domain}/${organisation}/${repository}/branches/compare/{{currentTag}}..{{previousTag}}`,
785
- // Bitbucket doesn't have a builtin issue tracker like GitHub or GitLab, this should be overridden by the user if they want to link to issues in their changelog.
786
- issueUrlFormat: `https://bitbucket.${domain}/${organisation}/${repository}/issues/{{id}}`
787
- },
788
- commitParserOptions: {
789
- mergePattern: /^Merged in (?<source>.*) \(pull request #(?<id>\d*)\)/i
790
- }
791
- };
792
- }
793
- return void 0;
794
- }
795
-
796
- // src/detect-git-host/host-azure-devops.ts
797
- function detectAzureDevopsOptions(remoteUrl) {
798
- let matches = null;
799
- if (/^https:\/\/(.*)?dev\.azure\.com/.test(remoteUrl)) {
800
- matches = /^https:\/\/(.*)?dev\.azure\.com\/(?<organisation>.*?)\/(?<project>.*?)\/_git\/(?<repository>.*?)(?:\.git)?$/.exec(
801
- remoteUrl
802
- );
803
- } else if (remoteUrl.startsWith("git@ssh.dev.azure.com:")) {
804
- matches = /^git@ssh\.dev\.azure\.com:v\d\/(?<organisation>.*?)\/(?<project>.*?)\/(?<repository>.*?)(?:\.git)?$/.exec(
805
- remoteUrl
806
- );
807
- }
808
- if (matches?.groups) {
809
- const { organisation = "", project = "", repository = "" } = matches.groups;
810
- return {
811
- hostName: "Azure Devops",
812
- changelogOptions: {
813
- commitUrlFormat: `https://dev.azure.com/${organisation}/${project}/_git/${repository}/commit/{{hash}}`,
814
- compareUrlFormat: `https://dev.azure.com/${organisation}/${project}/_git/${repository}/branchCompare?baseVersion=GT{{previousTag}}&targetVersion=GT{{currentTag}}`,
815
- issueUrlFormat: `https://dev.azure.com/${organisation}/${project}/_workitems/edit/{{id}}`
816
- },
817
- commitParserOptions: {
818
- mergePattern: /^Merged PR (?<id>\d*): (?<source>.*)/i
819
- }
820
- };
821
- }
822
- return void 0;
823
- }
824
-
825
- // src/detect-git-host/detect-git-host.ts
826
- async function detectGitHost(path) {
827
- const remoteUrl = await new Git({ path }).getRemoteUrl();
828
- if (remoteUrl.includes("github.com")) {
829
- const githubOptions = detectGitHubOptions(remoteUrl);
830
- if (githubOptions) {
831
- return githubOptions;
832
- }
833
- }
834
- if (remoteUrl.includes("gitlab.com")) {
835
- const gitlabOptions = detectGitlabOptions(remoteUrl);
836
- if (gitlabOptions) {
837
- return gitlabOptions;
838
- }
839
- }
840
- if (/bitbucket\.(org|com)/.test(remoteUrl)) {
841
- const bitbucketOptions = detectBitbucketOptions(remoteUrl);
842
- if (bitbucketOptions) {
843
- return bitbucketOptions;
844
- }
845
- }
846
- if (remoteUrl.includes("dev.azure.com")) {
847
- const azureDevopsOptions = detectAzureDevopsOptions(remoteUrl);
848
- if (azureDevopsOptions) {
849
- return azureDevopsOptions;
850
- }
851
- }
852
- return void 0;
853
- }
854
- var PACKAGE_JSON_CONFIG_KEY = "fork-version";
855
- async function loadConfigFile(cwd) {
856
- const joycon = new JoyCon({
857
- cwd,
858
- packageKey: PACKAGE_JSON_CONFIG_KEY,
859
- stopDir: parse(cwd).root
860
- });
861
- const configFilePath = await joycon.resolve([
862
- "fork.config.ts",
863
- "fork.config.js",
864
- "fork.config.cjs",
865
- "fork.config.mjs",
866
- "fork.config.json",
867
- "package.json"
868
- ]);
869
- if (!configFilePath) {
870
- return {};
871
- }
872
- if (configFilePath.endsWith("json")) {
873
- const fileContent2 = JSON.parse(readFileSync(configFilePath).toString());
874
- if (configFilePath.endsWith("package.json")) {
875
- if (fileContent2[PACKAGE_JSON_CONFIG_KEY] && typeof fileContent2[PACKAGE_JSON_CONFIG_KEY] === "object") {
876
- const parsed3 = ForkConfigSchema.partial().safeParse(fileContent2[PACKAGE_JSON_CONFIG_KEY]);
877
- if (!parsed3.success) {
878
- throw new Error(`Validation error in: ${configFilePath}`, { cause: parsed3.error });
879
- }
880
- return parsed3.data;
881
- }
882
- return {};
883
- }
884
- const parsed2 = ForkConfigSchema.partial().safeParse(fileContent2);
885
- if (!parsed2.success) {
886
- throw new Error(`Validation error in: ${configFilePath}`, { cause: parsed2.error });
887
- }
888
- return parsed2.data;
889
- }
890
- const fileContent = await bundleRequire({ filepath: configFilePath });
891
- const parsed = ForkConfigSchema.partial().safeParse(fileContent.mod.default || fileContent.mod);
892
- if (!parsed.success) {
893
- throw new Error(`Validation error in: ${configFilePath}`, { cause: parsed.error });
894
- }
895
- return parsed.data;
896
- }
897
-
898
- // src/config/merge-files.ts
899
- function mergeFiles(configFiles, cliFiles, globResults) {
900
- const listOfFiles = /* @__PURE__ */ new Set();
901
- if (Array.isArray(configFiles)) {
902
- configFiles.forEach((file) => listOfFiles.add(file));
903
- }
904
- if (Array.isArray(cliFiles)) {
905
- cliFiles.forEach((file) => listOfFiles.add(file));
906
- }
907
- globResults.forEach((file) => listOfFiles.add(file));
908
- if (listOfFiles.size) {
909
- return Array.from(listOfFiles);
910
- }
911
- return DEFAULT_CONFIG.files;
912
- }
913
-
914
- // src/config/user-config.ts
915
- async function getUserConfig(cliArguments) {
916
- const cwd = cliArguments.flags.path ? resolve(cliArguments.flags.path) : process.cwd();
917
- const configFile = await loadConfigFile(cwd);
918
- const mergedConfig = {
919
- ...DEFAULT_CONFIG,
920
- ...configFile,
921
- ...cliArguments.flags
922
- };
923
- const globResults = [];
924
- if (mergedConfig.glob) {
925
- const IGNORE_LIST = /* @__PURE__ */ new Set(["node_modules", ".git"]);
926
- const entries = glob(mergedConfig.glob, {
927
- cwd,
928
- withFileTypes: true,
929
- exclude: (entry) => IGNORE_LIST.has(entry.name)
930
- });
931
- for await (const entry of entries) {
932
- if (entry.isFile()) {
933
- globResults.push(join(entry.parentPath, entry.name));
934
- }
935
- }
936
- }
937
- const files = mergeFiles(configFile?.files, cliArguments.flags.files, globResults);
938
- const detectedGitHost = await detectGitHost(cwd);
939
- let command = DEFAULT_CONFIG.command;
940
- if (cliArguments.input.length > 0 && cliArguments.input[0].trim()) {
941
- command = cliArguments.input[0].trim().toLowerCase();
942
- } else if (mergedConfig.command.trim()) {
943
- command = mergedConfig.command.trim().toLowerCase();
944
- }
945
- if (mergedConfig.inspectVersion) {
946
- command = "inspect-version";
947
- }
948
- const shouldBeSilent = ![DEFAULT_CONFIG.command].includes(command);
949
- return {
950
- ...mergedConfig,
951
- command,
952
- files,
953
- path: cwd,
954
- preRelease: (
955
- // Meow doesn't support multiple flags with the same name, so we need to check both.
956
- cliArguments.flags.preReleaseTag ?? cliArguments.flags.preRelease ?? configFile.preRelease
957
- ),
958
- silent: shouldBeSilent || mergedConfig.silent,
959
- detectedGitHost: detectedGitHost?.hostName,
960
- changelogPresetConfig: getChangelogPresetConfig(
961
- mergedConfig,
962
- cliArguments.flags,
963
- detectedGitHost?.changelogOptions
964
- ),
965
- commitParserOptions: {
966
- ...detectedGitHost?.commitParserOptions,
967
- ...mergedConfig.commitParserOptions
968
- }
969
- };
970
- }
971
- var Logger = class {
972
- #silent;
973
- #debug;
974
- constructor(config) {
975
- this.#silent = config.silent ?? false;
976
- this.#debug = config.debug ?? false;
977
- this.log = this.log.bind(this);
978
- this.warn = this.warn.bind(this);
979
- this.error = this.error.bind(this);
980
- this.debug = this.debug.bind(this);
981
- this.skipping = this.skipping.bind(this);
982
- }
983
- log(message) {
984
- if (!this.#silent) {
985
- console.log(message);
986
- }
987
- }
988
- warn(message) {
989
- if (!this.#silent) {
990
- console.warn(styleText("yellowBright", message));
991
- }
992
- }
993
- error(message) {
994
- if (!this.#silent) {
995
- console.error(styleText("redBright", message));
996
- }
997
- }
998
- debug(message, ...optionalParams) {
999
- if (!this.#silent && this.#debug) {
1000
- console.debug(styleText("cyanBright", message));
1001
- if (optionalParams.length > 0) {
1002
- console.debug(...optionalParams);
1003
- }
1004
- }
1005
- }
1006
- skipping(message) {
1007
- if (!this.#silent) {
1008
- console.log(styleText("magenta", message));
1009
- }
1010
- }
1011
- };
1012
- function fileExists(filePath) {
1013
- try {
1014
- return lstatSync(filePath).isFile();
1015
- } catch (_error) {
1016
- return false;
1017
- }
1018
- }
1019
- var JSONPackage = class {
1020
- #logger;
1021
- constructor(logger) {
1022
- this.#logger = logger;
1023
- }
1024
- /** Options for parsing JSON and JSONC files. */
1025
- #jsoncOptions = {
1026
- allowEmptyContent: false,
1027
- allowTrailingComma: true,
1028
- disallowComments: false
1029
- };
1030
- /**
1031
- * Sets a new string value at the given path in a JSON or JSONC string.
1032
- * @param jsonc the JSON or JSONC string (the contents of a file)
1033
- * @param jsonPath path to the value to set, as an array of segments
1034
- * @param newString string to set the value to
1035
- * @returns the JSON or JSONC string with the value set
1036
- */
1037
- #setStringInJsonc(jsonc, jsonPath, newString) {
1038
- const edits = modify(jsonc, jsonPath, newString, {});
1039
- return applyEdits(jsonc, edits);
1040
- }
1041
- read(filePath) {
1042
- const fileName = basename(filePath);
1043
- const fileContents = readFileSync(filePath, "utf8");
1044
- const parseErrors = [];
1045
- const parsedJson = parse$1(fileContents, parseErrors, this.#jsoncOptions);
1046
- if (parsedJson?.version && parseErrors.length === 0) {
1047
- return {
1048
- name: fileName,
1049
- path: filePath,
1050
- version: parsedJson.version,
1051
- isPrivate: typeof parsedJson?.private === "boolean" ? parsedJson.private : true
1052
- };
1053
- }
1054
- this.#logger.warn(`[File Manager] Unable to determine json version: ${fileName}`);
1055
- }
1056
- write(fileState, newVersion) {
1057
- let fileContents = readFileSync(fileState.path, "utf8");
1058
- const parseErrors = [];
1059
- const parsedJson = parse$1(fileContents, parseErrors, this.#jsoncOptions);
1060
- fileContents = this.#setStringInJsonc(fileContents, ["version"], newVersion);
1061
- if (parsedJson?.packages?.[""]) {
1062
- fileContents = this.#setStringInJsonc(fileContents, ["packages", "", "version"], newVersion);
1063
- }
1064
- writeFileSync(fileState.path, fileContents, "utf8");
1065
- }
1066
- isSupportedFile(fileName) {
1067
- return fileName.endsWith(".json") || fileName.endsWith(".jsonc");
1068
- }
1069
- };
1070
- var YAMLPackage = class {
1071
- #logger;
1072
- constructor(logger) {
1073
- this.#logger = logger;
1074
- }
1075
- /**
1076
- * If the version is returned with a "+" symbol in the value then the version might be from a
1077
- * flutter `pubspec.yaml` file, if so we want to retain the input builderNumber by splitting it
1078
- * and joining it again later.
1079
- */
1080
- #handleBuildNumber(fileVersion) {
1081
- const [version, builderNumber] = fileVersion.split("+");
1082
- if (/^\d+$/.test(builderNumber)) {
1083
- return {
1084
- version,
1085
- builderNumber
1086
- };
1087
- }
1088
- return {
1089
- version: fileVersion
1090
- };
1091
- }
1092
- read(filePath) {
1093
- const fileName = basename(filePath);
1094
- const fileContents = readFileSync(filePath, "utf-8");
1095
- const fileVersion = parse$2(fileContents)?.version;
1096
- if (fileVersion) {
1097
- const parsedVersion = this.#handleBuildNumber(fileVersion);
1098
- return {
1099
- name: fileName,
1100
- path: filePath,
1101
- version: parsedVersion.version || "",
1102
- builderNumber: parsedVersion.builderNumber ?? void 0
1103
- };
1104
- }
1105
- this.#logger.warn(`[File Manager] Unable to determine yaml version: ${fileName}`);
1106
- }
1107
- write(fileState, newVersion) {
1108
- const fileContents = readFileSync(fileState.path, "utf8");
1109
- const yamlDocument = parseDocument(fileContents);
1110
- let newFileVersion = newVersion;
1111
- if (fileState.builderNumber !== void 0) {
1112
- newFileVersion += `+${fileState.builderNumber}`;
1113
- }
1114
- yamlDocument.set("version", newFileVersion);
1115
- writeFileSync(fileState.path, yamlDocument.toString(), "utf8");
1116
- }
1117
- isSupportedFile(fileName) {
1118
- return fileName.endsWith(".yaml") || fileName.endsWith(".yml");
1119
- }
1120
- };
1121
- var PlainText = class {
1122
- #logger;
1123
- constructor(logger) {
1124
- this.#logger = logger;
1125
- }
1126
- read(filePath) {
1127
- const fileName = basename(filePath);
1128
- const fileContents = readFileSync(filePath, "utf8").trim();
1129
- if (fileContents) {
1130
- return {
1131
- name: fileName,
1132
- path: filePath,
1133
- version: fileContents
1134
- };
1135
- }
1136
- this.#logger.warn(`[File Manager] Unable to determine plain text version: ${fileName}`);
1137
- }
1138
- write(fileState, newVersion) {
1139
- writeFileSync(fileState.path, newVersion, "utf8");
1140
- }
1141
- isSupportedFile(fileName) {
1142
- return fileName.endsWith("version.txt");
1143
- }
1144
- };
1145
- var MSBuildProject = class {
1146
- #logger;
1147
- constructor(logger) {
1148
- this.#logger = logger;
1149
- }
1150
- #cheerioOptions = {
1151
- xmlMode: true,
1152
- xml: { decodeEntities: false }
1153
- };
1154
- read(filePath) {
1155
- const fileName = basename(filePath);
1156
- const fileContents = readFileSync(filePath, "utf8");
1157
- const $ = cheerio.load(fileContents, this.#cheerioOptions);
1158
- const version = $("Project > PropertyGroup > Version").text();
1159
- if (version) {
1160
- return {
1161
- name: fileName,
1162
- path: filePath,
1163
- version
1164
- };
1165
- }
1166
- this.#logger.warn(`[File Manager] Unable to determine ms-build version: ${fileName}`);
1167
- }
1168
- write(fileState, newVersion) {
1169
- const fileContents = readFileSync(fileState.path, "utf8");
1170
- const $ = cheerio.load(fileContents, this.#cheerioOptions);
1171
- $("Project > PropertyGroup > Version").text(newVersion);
1172
- const updatedContent = $.xml().replaceAll('"/>', '" />');
1173
- writeFileSync(fileState.path, updatedContent, "utf8");
1174
- }
1175
- isSupportedFile(fileName) {
1176
- return [".csproj", ".dbproj", ".esproj", ".fsproj", ".props", ".vbproj", ".vcxproj"].findIndex(
1177
- (ext) => fileName.endsWith(ext)
1178
- ) !== -1;
1179
- }
1180
- };
1181
- var ARMBicep = class {
1182
- #logger;
1183
- constructor(logger) {
1184
- this.#logger = logger;
1185
- }
1186
- /** https://regex101.com/r/Lriphb/2 */
1187
- #metadataRegex = /(metadata contentVersion *= *['"])(?<version>[^'"]+)(['"])/;
1188
- /** https://regex101.com/r/iKCTF9/1 */
1189
- #varRegex = /(var contentVersion(?: string)? *= *['"])(?<version>[^'"]+)(['"])/;
1190
- read(filePath) {
1191
- const fileName = basename(filePath);
1192
- const fileContents = readFileSync(filePath, "utf8");
1193
- const metadataMatch = this.#metadataRegex.exec(fileContents);
1194
- const varMatch = this.#varRegex.exec(fileContents);
1195
- if (metadataMatch?.groups?.version && varMatch?.groups?.version) {
1196
- return {
1197
- name: fileName,
1198
- path: filePath,
1199
- version: metadataMatch.groups.version
1200
- };
1201
- }
1202
- if (!metadataMatch) {
1203
- this.#logger.warn(
1204
- `[File Manager] Missing 'metadata contentVersion' in bicep file: ${fileName}`
1205
- );
1206
- }
1207
- if (!varMatch) {
1208
- this.#logger.warn(`[File Manager] Missing 'var contentVersion' in bicep file: ${fileName}`);
1209
- }
1210
- }
1211
- write(fileState, newVersion) {
1212
- const fileContents = readFileSync(fileState.path, "utf8");
1213
- const updatedContent = fileContents.replace(this.#metadataRegex, `$1${newVersion}$3`).replace(this.#varRegex, `$1${newVersion}$3`);
1214
- writeFileSync(fileState.path, updatedContent, "utf8");
1215
- }
1216
- isSupportedFile(fileName) {
1217
- return fileName.endsWith(".bicep");
1218
- }
1219
- };
1220
- var InstallShieldISM = class {
1221
- #logger;
1222
- constructor(logger) {
1223
- this.#logger = logger;
1224
- }
1225
- #cheerioOptions = {
1226
- xmlMode: true,
1227
- xml: { decodeEntities: false }
1228
- };
1229
- read(filePath) {
1230
- const fileName = basename(filePath);
1231
- const fileContents = readFileSync(filePath, "utf8");
1232
- const $ = cheerio.load(fileContents, this.#cheerioOptions);
1233
- const version = $('msi > table[name="Property"] > row > td:contains("ProductVersion")').next().text().trim();
1234
- if (version) {
1235
- return {
1236
- name: fileName,
1237
- path: filePath,
1238
- version
1239
- };
1240
- }
1241
- this.#logger.warn(`[File Manager] Unable to determine InstallShield ISM version: ${fileName}`);
1242
- }
1243
- write(fileState, newVersion) {
1244
- const fileContents = readFileSync(fileState.path, "utf8");
1245
- const $ = cheerio.load(fileContents, this.#cheerioOptions);
1246
- const versionCell = $(
1247
- 'msi > table[name="Property"] > row > td:contains("ProductVersion")'
1248
- ).next();
1249
- if (versionCell.length > 0) {
1250
- versionCell.text(newVersion);
1251
- }
1252
- const updatedContent = $.xml().replaceAll('"/>', '" />');
1253
- writeFileSync(fileState.path, updatedContent, "utf8");
1254
- }
1255
- isSupportedFile(fileName) {
1256
- return fileName.endsWith(".ism");
1257
- }
1258
- };
1259
-
1260
- // src/files/file-manager.ts
1261
- var FileManager = class {
1262
- #config;
1263
- #logger;
1264
- #fileManagers = [];
1265
- constructor(config, logger) {
1266
- this.#config = config;
1267
- this.#logger = logger;
1268
- this.#fileManagers = [
1269
- new JSONPackage(logger),
1270
- new YAMLPackage(logger),
1271
- new PlainText(logger),
1272
- new MSBuildProject(logger),
1273
- new ARMBicep(logger),
1274
- new InstallShieldISM(logger)
1275
- ];
1276
- }
1277
- /**
1278
- * Get the state from the given file name.
1279
- *
1280
- * @example
1281
- * ```ts
1282
- * fileManager.read("package.json");
1283
- * ```
1284
- *
1285
- * @returns
1286
- * ```json
1287
- * { "name": "package.json", "path": "/path/to/package.json", "version": "1.2.3", "isPrivate": true }
1288
- * ```
1289
- */
1290
- read(pathOrName) {
1291
- const _fileName = pathOrName.toLowerCase();
1292
- const filePath = isAbsolute(pathOrName) ? pathOrName : resolve(this.#config.path, pathOrName);
1293
- if (!fileExists(filePath)) return;
1294
- for (const fileManager of this.#fileManagers) {
1295
- if (fileManager.isSupportedFile(_fileName)) {
1296
- return fileManager.read(filePath);
1297
- }
1298
- }
1299
- this.#logger.error(`[File Manager] Unsupported file: ${pathOrName}`);
1300
- }
1301
- /**
1302
- * Write the new version to the given file.
1303
- *
1304
- * @example
1305
- * ```ts
1306
- * fileManager.write(
1307
- * { name: "package.json", path: "/path/to/package.json", version: "1.2.2" },
1308
- * "1.2.3"
1309
- * );
1310
- * ```
1311
- */
1312
- write(fileState, newVersion) {
1313
- if (this.#config.dryRun) {
1314
- return;
1315
- }
1316
- const _fileName = fileState.name.toLowerCase();
1317
- for (const fileManager of this.#fileManagers) {
1318
- if (fileManager.isSupportedFile(_fileName)) {
1319
- return fileManager.write(fileState, newVersion);
1320
- }
1321
- }
1322
- this.#logger.error(`[File Manager] Unsupported file: ${fileState.path}`);
1323
- }
1324
- };
1325
-
1326
- // src/commands/validate-config.ts
1327
- function validateConfig(config) {
1328
- console.log(`
1329
- \u2699\uFE0F Configuration:
1330
- ${JSON.stringify(config, null, 2)}
1331
-
1332
- \u2705 Configuration is valid.
1333
- `);
1334
- }
1335
-
1336
- // src/utils/parse-regexp-string.ts
1337
- function parseRegExpString(input) {
1338
- const literal = /^\/(.+)\/([a-z]*)$/i.exec(input);
1339
- if (literal) {
1340
- try {
1341
- return new RegExp(literal[1], literal[2]);
1342
- } catch {
1343
- return null;
1344
- }
1345
- }
1346
- try {
1347
- return new RegExp(input);
1348
- } catch {
1349
- return null;
1350
- }
1351
- }
1352
-
1353
- // src/utils/trim-string-array.ts
1354
- function trimStringArray(array, transformFn) {
1355
- const items = [];
1356
- if (Array.isArray(array)) {
1357
- for (const item of array) {
1358
- const _item = item.trim();
1359
- if (_item) {
1360
- items.push(transformFn ? transformFn(_item) : _item);
1361
- }
1362
- }
1363
- }
1364
- if (items.length === 0) {
1365
- return void 0;
1366
- }
1367
- return items;
1368
- }
1369
-
1370
- // src/commit-parser/options.ts
1371
- function createDefaultParserOptions(userOptions) {
1372
- const referenceActions = trimStringArray(userOptions?.referenceActions, escapeRegex) ?? [
1373
- "close",
1374
- "closes",
1375
- "closed",
1376
- "fix",
1377
- "fixes",
1378
- "fixed",
1379
- "resolve",
1380
- "resolves",
1381
- "resolved"
1382
- ];
1383
- const joinedReferenceActions = referenceActions.join("|");
1384
- const issuePrefixes = trimStringArray(userOptions?.issuePrefixes, escapeRegex) ?? ["#"];
1385
- const joinedIssuePrefixes = issuePrefixes.join("|");
1386
- const noteKeywords = trimStringArray(userOptions?.noteKeywords, escapeRegex) ?? [
1387
- "BREAKING CHANGE",
1388
- "BREAKING-CHANGE"
1389
- ];
1390
- const joinedNoteKeywords = noteKeywords.join("|");
1391
- return {
1392
- subjectPattern: /^(?<type>\w+)(?:\((?<scope>.*)\))?(?<breakingChange>!)?:\s+(?<title>.*)/i,
1393
- mergePattern: /^Merge pull request #(?<id>\d*) from (?<source>.*)/i,
1394
- revertPattern: /^[Rr]evert "(?<subject>.*)"(\s*This reverts commit (?<hash>[a-zA-Z0-9]*)\.)?/,
1395
- commentPattern: /^#(?!\d+\s)/,
1396
- mentionPattern: /(?<!\w)@(?<username>[\w-]+)/,
1397
- referenceActions,
1398
- referenceActionPattern: joinedReferenceActions ? new RegExp(
1399
- `(?<action>${joinedReferenceActions})(?:\\s+(?<reference>.*?))(?=(?:${joinedReferenceActions})|$)`
1400
- ) : void 0,
1401
- issuePrefixes,
1402
- issuePattern: joinedIssuePrefixes ? new RegExp(
1403
- `(?:.*?)??\\s*(?<repository>[\\w-\\.\\/]*?)??(?<prefix>${joinedIssuePrefixes})(?<issue>[\\w-]*\\d+)`
1404
- ) : void 0,
1405
- noteKeywords,
1406
- notePattern: joinedNoteKeywords ? new RegExp(`^(?<title>${joinedNoteKeywords}):(\\s*(?<text>.*))`) : void 0
1407
- };
1408
- }
1409
- function createParserOptions(userOptions) {
1410
- const initialOptions = createDefaultParserOptions(userOptions);
1411
- if (userOptions) {
1412
- for (const key of Object.keys(userOptions)) {
1413
- const userValue = userOptions[key];
1414
- if (!(key in initialOptions)) {
1415
- continue;
1416
- }
1417
- if (Array.isArray(initialOptions[key])) {
1418
- continue;
1419
- }
1420
- if (initialOptions[key] instanceof RegExp) {
1421
- if (userValue instanceof RegExp) {
1422
- initialOptions[key] = userValue;
1423
- } else if (typeof userValue === "string") {
1424
- const parsed = parseRegExpString(userValue);
1425
- if (parsed) {
1426
- initialOptions[key] = parsed;
1427
- }
1428
- } else if (userValue == void 0) {
1429
- initialOptions[key] = void 0;
1430
- }
1431
- }
1432
- }
1433
- }
1434
- return initialOptions;
1435
- }
1436
-
1437
- // src/commit-parser/parser-error.ts
1438
- var ParserError = class extends Error {
1439
- detail;
1440
- constructor(message, detail) {
1441
- super(message);
1442
- this.name = "ParserError";
1443
- this.detail = detail;
1444
- }
1445
- };
1446
-
1447
- // src/commit-parser/commit-parser.ts
1448
- var CommitParser = class {
1449
- #options;
1450
- #logger;
1451
- constructor(userOptions) {
1452
- this.#options = createParserOptions(userOptions);
1453
- this.setLogger = this.setLogger.bind(this);
1454
- this.createCommit = this.createCommit.bind(this);
1455
- this.parseRawCommit = this.parseRawCommit.bind(this);
1456
- this.parseSubject = this.parseSubject.bind(this);
1457
- this.parseMerge = this.parseMerge.bind(this);
1458
- this.parseRevert = this.parseRevert.bind(this);
1459
- this.parseMentions = this.parseMentions.bind(this);
1460
- this.parseReferenceParts = this.parseReferenceParts.bind(this);
1461
- this.parseReferences = this.parseReferences.bind(this);
1462
- this.parseNotes = this.parseNotes.bind(this);
1463
- this.parseRawLines = this.parseRawLines.bind(this);
1464
- this.parse = this.parse.bind(this);
1465
- }
1466
- setLogger(logger) {
1467
- this.#logger = logger;
1468
- return this;
1469
- }
1470
- createCommit() {
1471
- return {
1472
- raw: "",
1473
- subject: "",
1474
- body: "",
1475
- hash: "",
1476
- refNames: "",
1477
- date: "",
1478
- name: "",
1479
- email: "",
1480
- type: "",
1481
- scope: "",
1482
- breakingChange: "",
1483
- title: "",
1484
- merge: null,
1485
- revert: null,
1486
- notes: [],
1487
- mentions: [],
1488
- references: [],
1489
- tags: []
1490
- };
1491
- }
1492
- /**
1493
- * Parse the raw commit message into its expected parts
1494
- * - subject
1495
- * - body
1496
- * - hash
1497
- * - date
1498
- * - name
1499
- * - email
1500
- *
1501
- * @throws {ParserError}
1502
- */
1503
- parseRawCommit(rawCommit) {
1504
- const parsedCommit = this.createCommit();
1505
- const parts = rawCommit.split(/\r?\n/);
1506
- if (parts.length < 6) {
1507
- throw new ParserError("Commit doesn't contain enough parts", rawCommit);
1508
- }
1509
- const email = parts.pop();
1510
- const name = parts.pop();
1511
- const date = parts.pop();
1512
- const refNames = parts.pop();
1513
- const hash = parts.pop();
1514
- if (email) parsedCommit.email = email.trim();
1515
- if (name) parsedCommit.name = name.trim();
1516
- if (date) {
1517
- parsedCommit.date = date.trim();
1518
- if (Number.isNaN(Date.parse(parsedCommit.date))) {
1519
- throw new ParserError("Unable to parse commit date", rawCommit);
1520
- }
1521
- }
1522
- if (refNames) {
1523
- parsedCommit.refNames = refNames.trim();
1524
- const TAG_REGEX = /tag:\s*(?<tag>.+?)[,)]/gi;
1525
- let tagMatch = null;
1526
- while (tagMatch = TAG_REGEX.exec(refNames)) {
1527
- const { tag = "" } = tagMatch.groups ?? {};
1528
- if (tag) {
1529
- parsedCommit.tags.push(tag);
1530
- }
1531
- }
1532
- }
1533
- if (hash) parsedCommit.hash = hash.trim();
1534
- const subject = parts.shift()?.trimStart();
1535
- if (subject) {
1536
- parsedCommit.subject = subject;
1537
- parsedCommit.raw = subject;
1538
- }
1539
- parsedCommit.body = parts.filter((line) => {
1540
- if (this.#options.commentPattern) {
1541
- return !this.#options.commentPattern.test(line.trim());
1542
- }
1543
- return true;
1544
- }).join("\n").trim();
1545
- const raw = parts.join("\n").trim();
1546
- if (raw) parsedCommit.raw += "\n" + raw;
1547
- return parsedCommit;
1548
- }
1549
- /**
1550
- * Parse the commit subject into its expected parts
1551
- * - type
1552
- * - scope (optional)
1553
- * - breaking change (optional)
1554
- * - title
1555
- *
1556
- * @throws {ParserError}
1557
- */
1558
- parseSubject(commit) {
1559
- if (!this.#options.subjectPattern) return false;
1560
- const subjectMatch = this.#options.subjectPattern.exec(commit.subject);
1561
- if (subjectMatch?.groups) {
1562
- const { type = "", scope = "", breakingChange = "", title = "" } = subjectMatch.groups;
1563
- if (!type || !title) {
1564
- throw new ParserError("Unable to parse commit subject", commit);
1565
- }
1566
- commit.type = type;
1567
- commit.scope = scope;
1568
- if (breakingChange) commit.breakingChange = breakingChange;
1569
- commit.title = title;
1570
- return true;
1571
- }
1572
- return false;
1573
- }
1574
- /**
1575
- * Parse merge information from the commit subject
1576
- * @example
1577
- * ```txt
1578
- * "Merge pull request #123 from fork-version/feature"
1579
- * ```
1580
- */
1581
- parseMerge(commit) {
1582
- if (!this.#options.mergePattern) return false;
1583
- const mergeMatch = this.#options.mergePattern.exec(commit.subject);
1584
- if (mergeMatch?.groups) {
1585
- const { id = "", source = "" } = mergeMatch.groups;
1586
- commit.merge = {
1587
- id,
1588
- source
1589
- };
1590
- return true;
1591
- }
1592
- return false;
1593
- }
1594
- /**
1595
- * Parse revert information from the commit body
1596
- * @example
1597
- * ```txt
1598
- * "Revert "feat: initial commit"
1599
- *
1600
- * This reverts commit 4a79e9e546b4020d2882b7810dc549fa71960f4f."
1601
- * ```
1602
- */
1603
- parseRevert(commit) {
1604
- if (!this.#options.revertPattern) return false;
1605
- const revertMatch = this.#options.revertPattern.exec(commit.raw);
1606
- if (revertMatch?.groups) {
1607
- const { hash = "", subject = "" } = revertMatch.groups;
1608
- commit.revert = {
1609
- hash,
1610
- subject
1611
- };
1612
- return true;
1613
- }
1614
- return false;
1615
- }
1616
- /**
1617
- * Search for mentions from the commit line
1618
- * @example
1619
- * ```txt
1620
- * "@fork-version"
1621
- * ```
1622
- */
1623
- parseMentions(line, outMentions) {
1624
- if (!this.#options.mentionPattern) return false;
1625
- const mentionRegex = new RegExp(this.#options.mentionPattern, "g");
1626
- let foundMention = false;
1627
- let mentionMatch;
1628
- while (mentionMatch = mentionRegex.exec(line)) {
1629
- if (!mentionMatch) {
1630
- break;
1631
- }
1632
- const { username = "" } = mentionMatch.groups ?? {};
1633
- outMentions.add(username);
1634
- foundMention = true;
1635
- }
1636
- return foundMention;
1637
- }
1638
- /**
1639
- * Search for references from the commit line
1640
- * @example
1641
- * ```txt
1642
- * "#1234"
1643
- * "owner/repo#1234"
1644
- * ```
1645
- */
1646
- parseReferenceParts(referenceText, action) {
1647
- if (!this.#options.issuePattern) return void 0;
1648
- const references = [];
1649
- const issueRegex = new RegExp(this.#options.issuePattern, "gi");
1650
- let issueMatch;
1651
- while (issueMatch = issueRegex.exec(referenceText)) {
1652
- if (!issueMatch) {
1653
- break;
1654
- }
1655
- const { repository = "", prefix = "", issue = "" } = issueMatch.groups ?? {};
1656
- const reference = {
1657
- prefix,
1658
- issue,
1659
- action,
1660
- owner: null,
1661
- repository: null
1662
- };
1663
- if (repository) {
1664
- const slashIndex = repository.indexOf("/");
1665
- if (slashIndex !== -1) {
1666
- reference.owner = repository.slice(0, slashIndex);
1667
- reference.repository = repository.slice(slashIndex + 1);
1668
- } else {
1669
- reference.repository = repository;
1670
- }
1671
- }
1672
- references.push(reference);
1673
- }
1674
- if (references.length > 0) {
1675
- return references;
1676
- }
1677
- return void 0;
1678
- }
1679
- /**
1680
- * Search for actions and references from the commit line
1681
- * @example
1682
- * ```txt
1683
- * "Closes #1234"
1684
- * "fixes owner/repo#1234"
1685
- * ```
1686
- */
1687
- parseReferences(line, outReferences) {
1688
- if (!this.#options.referenceActionPattern || !this.#options.issuePattern) return false;
1689
- const referenceActionRegex = new RegExp(this.#options.referenceActionPattern, "gi").test(line) ? new RegExp(this.#options.referenceActionPattern, "gi") : /(?<reference>.*)/g;
1690
- let foundReference = false;
1691
- let referenceActionMatch;
1692
- while (referenceActionMatch = referenceActionRegex.exec(line)) {
1693
- if (!referenceActionMatch) {
1694
- break;
1695
- }
1696
- const { action = "", reference = "" } = referenceActionMatch.groups ?? {};
1697
- const parsedReferences = this.parseReferenceParts(reference, action || null);
1698
- if (!parsedReferences) {
1699
- break;
1700
- }
1701
- for (const ref of parsedReferences) {
1702
- if (!outReferences.some((r) => r.prefix === ref.prefix && r.issue === ref.issue)) {
1703
- outReferences.push(ref);
1704
- }
1705
- }
1706
- foundReference = true;
1707
- }
1708
- return foundReference;
1709
- }
1710
- /**
1711
- * Search for notes from the commit line
1712
- * @example
1713
- * ```txt
1714
- * "BREAKING CHANGE: this is a breaking change"
1715
- * ```
1716
- */
1717
- parseNotes(line, outNotes) {
1718
- if (!this.#options.notePattern) return false;
1719
- const noteMatch = new RegExp(this.#options.notePattern, "ig").exec(line);
1720
- if (noteMatch?.groups) {
1721
- const { title = "", text = "" } = noteMatch.groups;
1722
- outNotes.push({
1723
- title,
1724
- text
1725
- });
1726
- return true;
1727
- }
1728
- return false;
1729
- }
1730
- /**
1731
- * Parse the raw commit for mentions, references and notes
1732
- */
1733
- parseRawLines(commit) {
1734
- const mentions = /* @__PURE__ */ new Set();
1735
- const references = [];
1736
- const notes = [];
1737
- let lastNoteLine = -1;
1738
- const splitMessage = commit.raw.split("\n");
1739
- for (let index = 0; index < splitMessage.length; index++) {
1740
- const line = splitMessage[index];
1741
- const trimmedLine = line.trim();
1742
- if (this.#options.commentPattern?.test(trimmedLine)) {
1743
- continue;
1744
- }
1745
- this.parseMentions(trimmedLine, mentions);
1746
- const foundReference = this.parseReferences(trimmedLine, references);
1747
- if (foundReference) {
1748
- lastNoteLine = -1;
1749
- continue;
1750
- }
1751
- if (this.parseNotes(trimmedLine, notes)) {
1752
- lastNoteLine = index;
1753
- } else if (lastNoteLine !== -1) {
1754
- notes[notes.length - 1].text += `
1755
- ${line}`;
1756
- lastNoteLine = index;
1757
- }
1758
- }
1759
- if (mentions.size > 0) {
1760
- commit.mentions = Array.from(mentions);
1761
- }
1762
- if (references.length > 0) {
1763
- commit.references = references;
1764
- }
1765
- if (notes.length > 0) {
1766
- commit.notes = notes.map((note) => ({
1767
- ...note,
1768
- text: note.text.trim()
1769
- }));
1770
- }
1771
- }
1772
- /**
1773
- * Parse a commit log with the following format separated by new line characters:
1774
- * ```txt
1775
- * refactor: add test file
1776
- * Add a test file to the project
1777
- * 4ef2c86d393a9660aa9f753144256b1f200c16bd
1778
- * 2024-12-22T17:36:50Z
1779
- * Fork Version
1780
- * fork-version@example.com
1781
- * ```
1782
- *
1783
- * @example
1784
- * ```ts
1785
- * parse("refactor: add test file\nAdd a test file to the project\n4ef2c86d393a9660aa9f753144256b1f200c16bd\n2024-12-22T17:36:50Z\nFork Version\nfork-version@example.com");
1786
- * ```
1787
- *
1788
- * The expected input value can be generated by running the following command:
1789
- * ```sh
1790
- * git log --format="%s%n%b%n%H%n%cI%n%cN%n%cE%n"
1791
- * ```
1792
- * @see {@link https://git-scm.com/docs/pretty-formats|Git Pretty Format Documentation}
1793
- */
1794
- parse(rawCommit) {
1795
- try {
1796
- const commit = this.parseRawCommit(rawCommit);
1797
- this.parseSubject(commit);
1798
- this.parseMerge(commit);
1799
- this.parseRevert(commit);
1800
- this.parseRawLines(commit);
1801
- return commit;
1802
- } catch (error) {
1803
- if (this.#logger) {
1804
- this.#logger.debug("[Commit Parser] Failed to parse commit", { error });
1805
- }
1806
- return void 0;
1807
- }
1808
- }
1809
- };
1810
-
1811
- // src/commit-parser/filter-reverted-commits.ts
1812
- function filterRevertedCommits(parsedCommits) {
1813
- const revertedCommits = [];
1814
- for (const commit of parsedCommits) {
1815
- if (!commit.revert) continue;
1816
- if (revertedCommits.some(
1817
- (r) => r.revert?.hash === commit.hash || r.revert?.subject === commit.subject
1818
- )) {
1819
- continue;
1820
- }
1821
- revertedCommits.push(commit);
1822
- }
1823
- if (revertedCommits.length === 0) {
1824
- return parsedCommits;
1825
- }
1826
- const commitsWithoutReverts = [];
1827
- for (const commit of parsedCommits) {
1828
- if (commit.revert) continue;
1829
- const revertedIndex = revertedCommits.findIndex(
1830
- (r) => r.revert?.hash === commit.hash || r.revert?.subject === commit.subject
1831
- );
1832
- if (revertedIndex !== -1) {
1833
- revertedCommits.splice(revertedIndex, 1);
1834
- continue;
1835
- }
1836
- commitsWithoutReverts.push(commit);
1837
- }
1838
- return commitsWithoutReverts;
1839
- }
1840
- function cleanTag(tag, tagPrefix) {
1841
- if (!tag) return void 0;
1842
- const escapedTagPrefix = tagPrefix ? escapeRegex(tagPrefix) : void 0;
1843
- const tagWithoutPrefix = escapedTagPrefix ? tag.replace(new RegExp(`^${escapedTagPrefix}`), "") : tag;
1844
- return semver5.clean(tagWithoutPrefix) ?? void 0;
1845
- }
1846
-
1847
- // src/process/get-commits.ts
1848
- async function getCommitsSinceTag(config, logger, git) {
1849
- const commitParser = new CommitParser(config.commitParserOptions);
1850
- if (config.debug) commitParser.setLogger(logger);
1851
- const latestTag = await git.getMostRecentTag(config.tagPrefix);
1852
- if (!latestTag) {
1853
- logger.warn("No previous tag found, using all commits");
1854
- }
1855
- const foundCommits = await git.getCommits(latestTag, "HEAD");
1856
- const commits = foundCommits.reduce((acc, commit) => {
1857
- const parsed = commitParser.parse(commit);
1858
- if (parsed) {
1859
- acc.push(parsed);
1860
- }
1861
- return acc;
1862
- }, []);
1863
- const filteredCommits = filterRevertedCommits(commits);
1864
- logger.debug(
1865
- `Found ${foundCommits.length} commits since tag: ${latestTag ?? "none"} (${commits.length} parsed, ${filteredCommits.length} after filtering reverts)`
1866
- );
1867
- return {
1868
- latestTag,
1869
- latestTagVersion: cleanTag(latestTag, config.tagPrefix),
1870
- commits: filteredCommits
1871
- };
1872
- }
1873
- async function getCurrentVersion(config, logger, git, fileManager, filesToUpdate, latestTagVersion) {
1874
- const files = [];
1875
- const versions = /* @__PURE__ */ new Set();
1876
- for (const file of filesToUpdate) {
1877
- if (await git.isIgnored(file)) {
1878
- logger.debug(`[Git Ignored] ${file}`);
1879
- continue;
1880
- }
1881
- const fileState = fileManager.read(file);
1882
- if (fileState) {
1883
- files.push(fileState);
1884
- if (!config.currentVersion) {
1885
- versions.add(fileState.version);
1886
- }
1887
- }
1888
- }
1889
- if (config.currentVersion) {
1890
- versions.add(config.currentVersion);
1891
- }
1892
- if (versions.size === 0 && config.gitTagFallback && latestTagVersion) {
1893
- logger.warn(`Using latest git tag as fallback`);
1894
- versions.add(latestTagVersion);
1895
- }
1896
- if (versions.size === 0) {
1897
- throw new Error("Unable to find current version");
1898
- } else if (versions.size > 1) {
1899
- if (!config.allowMultipleVersions) {
1900
- throw new Error("Found multiple versions");
1901
- }
1902
- logger.warn(
1903
- `Found multiple versions (${Array.from(versions).join(", ")}), using the higher semver version`
1904
- );
1905
- }
1906
- const currentVersion = semver5.rsort(Array.from(versions))[0];
1907
- logger.log(`Current version: ${currentVersion}`);
1908
- return {
1909
- files,
1910
- version: currentVersion
1911
- };
1912
- }
1913
- async function inspect(config, logger, fileManager, git) {
1914
- let latestTag = "";
1915
- let latestVersion = "";
1916
- try {
1917
- const commits = await getCommitsSinceTag(config, logger, git);
1918
- if (commits.latestTag) {
1919
- latestTag = commits.latestTag;
1920
- latestVersion = commits.latestTagVersion ?? "";
1921
- }
1922
- const currentVersion = await getCurrentVersion(
1923
- config,
1924
- logger,
1925
- git,
1926
- fileManager,
1927
- config.files,
1928
- latestVersion
1929
- );
1930
- if (currentVersion.version) {
1931
- latestVersion = currentVersion.version;
1932
- }
1933
- } catch {
1934
- }
1935
- if (!latestVersion && !latestTag) {
1936
- console.error(
1937
- styleText(
1938
- "yellowBright",
1939
- "No version found. Make sure you have at least one tag in your repository."
1940
- )
1941
- );
1942
- process.exit(1);
1943
- return;
1944
- }
1945
- switch (config.command) {
1946
- case "inspect-version": {
1947
- console.log(
1948
- config.asJson ? JSON.stringify(
1949
- {
1950
- version: latestVersion
1951
- },
1952
- null,
1953
- 2
1954
- ) : latestVersion
1955
- );
1956
- return;
1957
- }
1958
- case "inspect-tag": {
1959
- console.log(
1960
- config.asJson ? JSON.stringify(
1961
- {
1962
- tag: latestTag
1963
- },
1964
- null,
1965
- 2
1966
- ) : latestTag
1967
- );
1968
- return;
1969
- }
1970
- default: {
1971
- console.log(
1972
- config.asJson ? JSON.stringify(
1973
- {
1974
- version: latestVersion,
1975
- tag: latestTag
1976
- },
1977
- null,
1978
- 2
1979
- ) : `
1980
- Version: ${latestVersion}
1981
- Tag: ${latestTag}
1982
- `.trim()
1983
- );
1984
- return;
1985
- }
1986
- }
1987
- }
1988
- function getPriority(type) {
1989
- return ["patch", "minor", "major"].indexOf(type ?? "");
1990
- }
1991
- function getVersionType(version) {
1992
- const parseVersion = semver5.parse(version);
1993
- if (parseVersion?.major) {
1994
- return "major";
1995
- } else if (parseVersion?.minor) {
1996
- return "minor";
1997
- } else if (parseVersion?.patch) {
1998
- return "patch";
1999
- }
2000
- return void 0;
2001
- }
2002
- function getReleaseType(releaseType, currentVersion, preReleaseTag) {
2003
- if (!preReleaseTag) {
2004
- return releaseType;
2005
- }
2006
- const currentVersionsIsPreRelease = Array.isArray(semver5.prerelease(currentVersion));
2007
- if (currentVersionsIsPreRelease) {
2008
- const currentReleaseType = getVersionType(currentVersion);
2009
- if (currentReleaseType === releaseType || getPriority(currentReleaseType) > getPriority(releaseType)) {
2010
- return "prerelease";
2011
- }
2012
- }
2013
- return `pre${releaseType}`;
2014
- }
2015
-
2016
- // src/process/get-next-version.ts
2017
- async function getNextVersion(config, logger, commits, currentVersion) {
2018
- if (config.skipBump) {
2019
- logger.skipping(`Skipping bump, using ${currentVersion} as the next version`);
2020
- return {
2021
- version: currentVersion
2022
- };
2023
- }
2024
- if (config.nextVersion) {
2025
- if (!semver5.valid(config.nextVersion)) {
2026
- throw new Error(`Invalid Version: ${config.nextVersion}`);
2027
- }
2028
- logger.log(`Next version: ${config.nextVersion}`);
2029
- return {
2030
- version: config.nextVersion
2031
- };
2032
- }
2033
- const isPreMajor = semver5.lt(currentVersion, "1.0.0");
2034
- let releaseType = "patch";
2035
- const changes = {
2036
- major: 0,
2037
- minor: 0,
2038
- patch: 0,
2039
- merges: 0,
2040
- reverts: 0
2041
- };
2042
- if (config.releaseAs) {
2043
- releaseType = config.releaseAs;
2044
- } else {
2045
- let level = 2;
2046
- const MINOR_TYPES = ["feat", "feature"];
2047
- for (const commit of commits) {
2048
- if (commit.merge) {
2049
- changes.merges += 1;
2050
- continue;
2051
- }
2052
- if (commit.revert) {
2053
- changes.reverts += 1;
2054
- continue;
2055
- }
2056
- if (commit.notes.length > 0 || commit.breakingChange) {
2057
- changes.major += commit.notes.length + (commit.breakingChange ? 1 : 0);
2058
- level = 0;
2059
- } else if (MINOR_TYPES.includes(commit.type.toLowerCase())) {
2060
- changes.minor += 1;
2061
- if (level === 2) {
2062
- level = 1;
2063
- }
2064
- } else {
2065
- changes.patch += 1;
2066
- }
2067
- }
2068
- if (isPreMajor && level < 2) {
2069
- level++;
2070
- changes.patch += changes.minor;
2071
- changes.minor = changes.major;
2072
- changes.major = 0;
2073
- }
2074
- if (level === 0) {
2075
- releaseType = "major";
2076
- } else if (level === 1) {
2077
- releaseType = "minor";
2078
- } else {
2079
- releaseType = "patch";
2080
- }
2081
- }
2082
- const releaseTypeOrPreRelease = getReleaseType(releaseType, currentVersion, config.preRelease);
2083
- const nextVersion = semver5.inc(
2084
- currentVersion,
2085
- releaseTypeOrPreRelease,
2086
- typeof config.preRelease === "string" ? config.preRelease : ""
2087
- ) ?? "";
2088
- logger.log(`Next version: ${nextVersion} (${releaseTypeOrPreRelease})`);
2089
- if (commits.length > 0) {
2090
- logger.log(
2091
- ` - Commits: ${commits.length}` + (changes.major > 0 ? `, Majors: ${changes.major}` : "") + (changes.minor > 0 ? `, Minors: ${changes.minor}` : "") + (changes.patch > 0 ? `, Patches: ${changes.patch}` : "") + (changes.reverts > 0 ? `, Reverts: ${changes.reverts}` : "") + (changes.merges > 0 ? `, Merges: ${changes.merges}` : "")
2092
- );
2093
- } else {
2094
- logger.log(" - No commits found.");
2095
- }
2096
- return {
2097
- version: nextVersion,
2098
- releaseType: releaseTypeOrPreRelease,
2099
- preMajor: isPreMajor,
2100
- changes
2101
- };
2102
- }
2103
- var RELEASE_PATTERN = /(^#+ \[?[0-9]+\.[0-9]+\.[0-9]+|<a name=)/m;
2104
- function getOldReleaseContent(filePath, exists) {
2105
- if (exists) {
2106
- const fileContents = readFileSync(filePath, "utf-8");
2107
- const oldContentStart = fileContents.search(RELEASE_PATTERN);
2108
- if (oldContentStart !== -1) {
2109
- return fileContents.substring(oldContentStart);
2110
- }
2111
- }
2112
- return "";
2113
- }
2114
- function getNewReleaseContent(config, logger, nextVersion) {
2115
- return new Promise((onResolve) => {
2116
- let newContent = "";
2117
- conventionalChangelog(
2118
- {
2119
- preset: {
2120
- name: "conventionalcommits",
2121
- ...config.changelogPresetConfig
2122
- },
2123
- tagPrefix: config.tagPrefix,
2124
- warn: (...message) => logger.debug("[conventional-changelog] ", ...message),
2125
- cwd: config.path
2126
- },
2127
- {
2128
- version: nextVersion
2129
- },
2130
- {
2131
- merges: null,
2132
- path: config.path
2133
- }
2134
- ).on("error", (cause) => {
2135
- throw new Error("[conventional-changelog] Unable to parse changes", { cause });
2136
- }).on("data", (chunk) => {
2137
- newContent += chunk.toString();
2138
- }).on("end", () => {
2139
- onResolve(newContent);
2140
- });
2141
- });
2142
- }
2143
- async function updateChangelog(config, logger, nextVersion) {
2144
- if (config.skipChangelog) {
2145
- logger.skipping("Skipping changelog update");
2146
- return;
2147
- }
2148
- if (config.header.search(RELEASE_PATTERN) !== -1) {
2149
- throw new Error("Header cannot contain release pattern");
2150
- }
2151
- const changelogPath = resolve(config.path, config.changelog);
2152
- if (!config.dryRun && !fileExists(changelogPath)) {
2153
- logger.log(`Creating changelog: ${changelogPath}`);
2154
- writeFileSync(changelogPath, "\n", "utf8");
2155
- } else {
2156
- logger.log(`Updating changelog: ${changelogPath}`);
2157
- }
2158
- const oldContent = getOldReleaseContent(changelogPath, fileExists(changelogPath));
2159
- const newContent = await getNewReleaseContent(config, logger, nextVersion);
2160
- if (!config.dryRun && newContent) {
2161
- writeFileSync(
2162
- changelogPath,
2163
- `${config.header}
2164
- ${newContent}
2165
- ${oldContent}
2166
- `.trim(),
2167
- "utf8"
2168
- );
2169
- }
2170
- }
2171
-
2172
- // src/utils/format-commit-message.ts
2173
- function formatCommitMessage(message, version) {
2174
- if (!message) {
2175
- message = "chore(release): {{currentTag}}";
2176
- }
2177
- return message.replace(new RegExp("{{currentTag}}", "g"), version);
2178
- }
2179
-
2180
- // src/process/commit.ts
2181
- async function commitChanges(config, logger, git, files, nextVersion) {
2182
- if (config.skipCommit) {
2183
- logger.skipping("Skipping commit");
2184
- return;
2185
- }
2186
- logger.log("Committing changes");
2187
- const filesToCommit = [];
2188
- if (fileExists(resolve(config.path, config.changelog))) {
2189
- filesToCommit.push(resolve(config.path, config.changelog));
2190
- }
2191
- for (const file of files) {
2192
- filesToCommit.push(file.path);
2193
- }
2194
- if (filesToCommit.length === 0) {
2195
- return;
2196
- }
2197
- if (config.commitAll) {
2198
- await git.add("--all");
2199
- } else {
2200
- await git.add(...filesToCommit);
2201
- }
2202
- const shouldVerify = config.verify ? void 0 : "--no-verify";
2203
- const shouldSign = config.sign ? "--gpg-sign" : "--no-gpg-sign";
2204
- await git.commit(
2205
- shouldVerify,
2206
- shouldSign,
2207
- "--message",
2208
- formatCommitMessage(config.changelogPresetConfig?.releaseCommitMessageFormat, nextVersion)
2209
- );
2210
- }
2211
-
2212
- // src/process/tag.ts
2213
- async function tagChanges(config, logger, git, nextVersion) {
2214
- if (config.skipTag) {
2215
- logger.skipping("Skipping tag creation");
2216
- return;
2217
- }
2218
- const tag = `${config.tagPrefix}${nextVersion}`;
2219
- logger.log(`Creating tag: ${tag}`);
2220
- const shouldSign = config.sign ? "--sign" : "--no-sign";
2221
- await git.tag(
2222
- shouldSign,
2223
- "--annotate",
2224
- tag,
2225
- "--message",
2226
- formatCommitMessage(config.changelogPresetConfig?.releaseCommitMessageFormat, nextVersion)
2227
- );
2228
- }
2229
-
2230
- // src/commands/main.ts
2231
- async function main(config, logger, fileManager, git) {
2232
- logger.log(`Running fork-version - ${(/* @__PURE__ */ new Date()).toUTCString()}`);
2233
- logger.warn(config.dryRun ? "[Dry Run] No changes will be written to disk.\n" : "");
2234
- const commits = await getCommitsSinceTag(config, logger, git);
2235
- const current = await getCurrentVersion(
2236
- config,
2237
- logger,
2238
- git,
2239
- fileManager,
2240
- config.files,
2241
- commits.latestTagVersion
2242
- );
2243
- const next = await getNextVersion(config, logger, commits.commits, current.version);
2244
- logger.log("Updating files: ");
2245
- for (const outFile of current.files) {
2246
- logger.log(` - ${outFile.path}`);
2247
- fileManager.write(outFile, next.version);
2248
- }
2249
- await updateChangelog(config, logger, next.version);
2250
- await commitChanges(config, logger, git, current.files, next.version);
2251
- await tagChanges(config, logger, git, next.version);
2252
- return {
2253
- config,
2254
- commits,
2255
- current,
2256
- next
2257
- };
2258
- }
2259
-
2260
- export { CommitParser, FileManager, ForkConfigSchema, Git, Logger, commitChanges, createParserOptions, filterRevertedCommits, getCommitsSinceTag, getCurrentVersion, getNextVersion, getUserConfig, inspect, main, tagChanges, updateChangelog, validateConfig };
2261
- //# sourceMappingURL=chunk-L5UDUEHE.js.map
2262
- //# sourceMappingURL=chunk-L5UDUEHE.js.map