@releasekit/version 0.3.1 → 0.4.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
@@ -133,10 +133,19 @@ Configure via `releasekit.config.json`:
133
133
  | `cargo.enabled` | Update Cargo.toml files | `true` |
134
134
  | `cargo.paths` | Directories containing Cargo.toml | auto-detect |
135
135
 
136
+ ## Using in CI
137
+
138
+ A few things to keep in mind when running `releasekit-version` in a pipeline:
139
+
140
+ - **Always pass `fetch-depth: 0`** on checkout — the tool reads git history to determine the version bump and will produce incorrect results on a shallow clone.
141
+ - **Use `--json`** for reliable downstream parsing. Text output format can change; the JSON schema is stable.
142
+ - **`NO_COLOR=1`** disables ANSI colour codes in log output. Most CI environments set `CI=true` automatically, which the tool detects and adjusts for.
143
+
144
+ If you are running the full release pipeline (version + changelog + publish), use `@releasekit/release` instead of invoking `releasekit-version` directly. See the [CI setup guide](../release/docs/ci-setup.md).
145
+
136
146
  ## Documentation
137
147
 
138
148
  - [Versioning Strategies and Concepts](./docs/versioning.md)
139
- - [CI/CD Integration](./docs/CI_CD_INTEGRATION.md)
140
149
 
141
150
  ## Acknowledgements
142
151
 
@@ -122,14 +122,14 @@ var GitHubReleaseConfigSchema = z.object({
122
122
  perPackage: z.boolean().default(true),
123
123
  prerelease: z.union([z.literal("auto"), z.boolean()]).default("auto"),
124
124
  /**
125
- * Controls how release notes are sourced for GitHub releases.
126
- * - 'auto': Use RELEASE_NOTES.md if it exists, then per-package changelog
127
- * data from the version output, then GitHub's auto-generated notes.
128
- * - 'github': Always use GitHub's auto-generated notes.
129
- * - 'none': No notes body.
130
- * - Any other string: Treated as a file path to read notes from.
125
+ * Controls the source for the GitHub release body.
126
+ * - 'auto': Use release notes if enabled, else changelog, else GitHub auto-generated.
127
+ * - 'releaseNotes': Use LLM-generated release notes (requires notes.releaseNotes.enabled: true).
128
+ * - 'changelog': Use formatted changelog entries.
129
+ * - 'generated': Use GitHub's auto-generated notes.
130
+ * - 'none': No body.
131
131
  */
132
- releaseNotes: z.union([z.literal("auto"), z.literal("github"), z.literal("none"), z.string()]).default("auto")
132
+ body: z.enum(["auto", "releaseNotes", "changelog", "generated", "none"]).default("auto")
133
133
  });
134
134
  var VerifyRegistryConfigSchema = z.object({
135
135
  enabled: z.boolean().default(true),
@@ -173,7 +173,7 @@ var PublishConfigSchema = z.object({
173
173
  draft: true,
174
174
  perPackage: true,
175
175
  prerelease: "auto",
176
- releaseNotes: "auto"
176
+ body: "auto"
177
177
  }),
178
178
  verify: VerifyConfigSchema.default({
179
179
  npm: {
@@ -194,10 +194,10 @@ var TemplateConfigSchema = z.object({
194
194
  path: z.string().optional(),
195
195
  engine: z.enum(["handlebars", "liquid", "ejs"]).optional()
196
196
  });
197
- var OutputConfigSchema = z.object({
198
- format: z.enum(["markdown", "github-release", "json"]),
197
+ var LocationModeSchema = z.enum(["root", "packages", "both"]);
198
+ var ChangelogConfigSchema = z.object({
199
+ mode: LocationModeSchema.optional(),
199
200
  file: z.string().optional(),
200
- options: z.record(z.string(), z.unknown()).optional(),
201
201
  templates: TemplateConfigSchema.optional()
202
202
  });
203
203
  var LLMOptionsSchema = z.object({
@@ -257,17 +257,20 @@ var LLMConfigSchema = z.object({
257
257
  scopes: ScopeConfigSchema.optional(),
258
258
  prompts: LLMPromptsConfigSchema.optional()
259
259
  });
260
+ var ReleaseNotesConfigSchema = z.object({
261
+ mode: LocationModeSchema.optional(),
262
+ file: z.string().optional(),
263
+ templates: TemplateConfigSchema.optional(),
264
+ llm: LLMConfigSchema.optional()
265
+ });
260
266
  var NotesInputConfigSchema = z.object({
261
267
  source: z.string().optional(),
262
268
  file: z.string().optional()
263
269
  });
264
270
  var NotesConfigSchema = z.object({
265
- input: NotesInputConfigSchema.optional(),
266
- output: z.array(OutputConfigSchema).default([{ format: "markdown", file: "CHANGELOG.md" }]),
267
- monorepo: MonorepoConfigSchema.optional(),
268
- templates: TemplateConfigSchema.optional(),
269
- llm: LLMConfigSchema.optional(),
270
- updateStrategy: z.enum(["prepend", "regenerate"]).default("prepend")
271
+ changelog: z.union([z.literal(false), ChangelogConfigSchema]).optional(),
272
+ releaseNotes: z.union([z.literal(false), ReleaseNotesConfigSchema]).optional(),
273
+ updateStrategy: z.enum(["prepend", "regenerate"]).optional()
271
274
  });
272
275
  var CILabelsConfigSchema = z.object({
273
276
  stable: z.string().default("release:stable"),
@@ -629,6 +632,7 @@ function formatVersionPrefix(prefix) {
629
632
  return prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
630
633
  }
631
634
  function formatTag(version, prefix, packageName, template, packageSpecificTags) {
635
+ const sanitizedPackageName = packageName?.startsWith("@") ? packageName.slice(1).replace(/\//g, "-") : packageName;
632
636
  if (template?.includes("${packageName}") && !packageName) {
633
637
  log(
634
638
  `Warning: Your tagTemplate contains \${packageName} but no package name is available.
@@ -642,10 +646,10 @@ To fix this:
642
646
  );
643
647
  }
644
648
  if (template) {
645
- return template.replace(/\$\{version\}/g, version).replace(/\$\{prefix\}/g, prefix).replace(/\$\{packageName\}/g, packageName || "");
649
+ return template.replace(/\$\{version\}/g, version).replace(/\$\{prefix\}/g, prefix).replace(/\$\{packageName\}/g, sanitizedPackageName || "");
646
650
  }
647
- if (packageSpecificTags && packageName) {
648
- return `${packageName}@${prefix}${version}`;
651
+ if (packageSpecificTags && sanitizedPackageName) {
652
+ return `${sanitizedPackageName}@${prefix}${version}`;
649
653
  }
650
654
  return `${prefix}${version}`;
651
655
  }
@@ -742,9 +746,10 @@ async function lastMergeBranchName(branches, baseBranch) {
742
746
  }
743
747
  async function getLatestTagForPackage(packageName, versionPrefix, options) {
744
748
  try {
745
- const tagTemplate = options?.tagTemplate || `\${prefix}\${version}`;
746
749
  const packageSpecificTags = options?.packageSpecificTags ?? false;
747
- const escapedPackageName = escapeRegExp(packageName);
750
+ const tagTemplate = options?.tagTemplate || (packageSpecificTags ? `\${packageName}@\${prefix}\${version}` : `\${prefix}\${version}`);
751
+ const sanitizedPackageName = packageName.startsWith("@") ? packageName.slice(1).replace(/\//g, "-") : packageName;
752
+ const escapedPackageName = escapeRegExp(sanitizedPackageName);
748
753
  const escapedPrefix = versionPrefix ? escapeRegExp(versionPrefix) : "";
749
754
  log(
750
755
  `Looking for tags for package ${packageName} with prefix ${versionPrefix || "none"}, packageSpecificTags: ${packageSpecificTags}`,
@@ -763,45 +768,20 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
763
768
  const packageTagPattern = escapeRegExp(tagTemplate).replace(/\\\$\\\{packageName\\\}/g, `(?:${escapedPackageName})`).replace(/\\\$\\\{prefix\\\}/g, `(?:${escapedPrefix})`).replace(/\\\$\\\{version\\\}/g, "(?:[0-9]+\\.[0-9]+\\.[0-9]+(?:-[a-zA-Z0-9.-]+)?)");
764
769
  log(`Using package tag pattern: ${packageTagPattern}`, "debug");
765
770
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
766
- let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
771
+ const packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
767
772
  log(`Found ${packageTags.length} matching tags for ${packageName}`, "debug");
768
773
  if (packageTags.length > 0) {
769
774
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
770
775
  log(`Using most recently created tag: ${packageTags[0]}`, "debug");
771
776
  return packageTags[0];
772
777
  }
773
- if (versionPrefix) {
774
- const pattern1 = new RegExp(`^${escapedPackageName}@${escapeRegExp(versionPrefix)}`);
775
- packageTags = allTags.filter((tag) => pattern1.test(tag));
776
- if (packageTags.length > 0) {
777
- log(`Found ${packageTags.length} package tags using pattern: packageName@${versionPrefix}...`, "debug");
778
- log(`Using most recently created tag: ${packageTags[0]}`, "debug");
779
- return packageTags[0];
780
- }
781
- }
782
- if (versionPrefix) {
783
- const pattern2 = new RegExp(`^${escapeRegExp(versionPrefix)}${escapedPackageName}@`);
784
- packageTags = allTags.filter((tag) => pattern2.test(tag));
785
- if (packageTags.length > 0) {
786
- log(`Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`, "debug");
787
- log(`Using most recently created tag: ${packageTags[0]}`, "debug");
788
- return packageTags[0];
789
- }
790
- }
791
- const pattern3 = new RegExp(`^${escapedPackageName}@`);
792
- packageTags = allTags.filter((tag) => pattern3.test(tag));
793
- if (packageTags.length === 0) {
794
- log("No matching tags found for pattern: packageName@version", "debug");
795
- if (allTags.length > 0) {
796
- log(`Available tags: ${allTags.join(", ")}`, "debug");
797
- } else {
798
- log("No tags available in the repository", "debug");
799
- }
800
- return "";
778
+ log("No matching tags found for configured tag pattern", "debug");
779
+ if (allTags.length > 0) {
780
+ log(`Available tags: ${allTags.join(", ")}`, "debug");
781
+ } else {
782
+ log("No tags available in the repository", "debug");
801
783
  }
802
- log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
803
- log(`Using most recently created tag: ${packageTags[0]}`, "debug");
804
- return packageTags[0];
784
+ return "";
805
785
  }
806
786
  log(`Package-specific tags disabled for ${packageName}, falling back to global tags`, "debug");
807
787
  return "";
@@ -1863,30 +1843,38 @@ function createSyncStrategy(config) {
1863
1843
  let latestTag = await getLatestTag();
1864
1844
  let mainPkgPath = packages.root;
1865
1845
  let mainPkgName;
1846
+ let versionSourcePath = mainPkgPath;
1847
+ let versionSourceName;
1866
1848
  if (mainPackage) {
1867
1849
  const mainPkg = packages.packages.find((p) => p.packageJson.name === mainPackage);
1868
1850
  if (mainPkg) {
1869
1851
  mainPkgPath = mainPkg.dir;
1870
1852
  mainPkgName = mainPkg.packageJson.name;
1853
+ versionSourcePath = mainPkgPath;
1854
+ versionSourceName = mainPkgName;
1871
1855
  log(`Using ${mainPkgName} as primary package for version determination`, "info");
1872
1856
  } else {
1873
1857
  log(`Main package '${mainPackage}' not found. Using root package for version determination.`, "warning");
1874
1858
  }
1859
+ } else if (packages.packages.length > 0) {
1860
+ versionSourcePath = packages.packages[0].dir;
1861
+ versionSourceName = packages.packages[0].packageJson.name;
1862
+ log(`No mainPackage specified; using ${versionSourceName} as sync version source`, "info");
1875
1863
  }
1876
1864
  if (!mainPkgPath) {
1877
1865
  mainPkgPath = process.cwd();
1878
1866
  log(`No valid package path found, using current working directory: ${mainPkgPath}`, "warning");
1879
1867
  }
1880
- if (mainPkgName) {
1881
- const packageSpecificTag = await getLatestTagForPackage(mainPkgName, formattedPrefix, {
1868
+ if (versionSourceName) {
1869
+ const packageSpecificTag = await getLatestTagForPackage(versionSourceName, formattedPrefix, {
1882
1870
  tagTemplate,
1883
1871
  packageSpecificTags: config.packageSpecificTags
1884
1872
  });
1885
1873
  if (packageSpecificTag) {
1886
1874
  latestTag = packageSpecificTag;
1887
- log(`Using package-specific tag for ${mainPkgName}: ${latestTag}`, "debug");
1875
+ log(`Using package-specific tag for ${versionSourceName}: ${latestTag}`, "debug");
1888
1876
  } else {
1889
- log(`No package-specific tag found for ${mainPkgName}, using global tag: ${latestTag}`, "debug");
1877
+ log(`No package-specific tag found for ${versionSourceName}, using global tag: ${latestTag}`, "debug");
1890
1878
  }
1891
1879
  }
1892
1880
  const nextVersion = await calculateVersion(config, {
@@ -1895,8 +1883,8 @@ function createSyncStrategy(config) {
1895
1883
  branchPattern,
1896
1884
  baseBranch,
1897
1885
  prereleaseIdentifier,
1898
- path: mainPkgPath,
1899
- name: mainPkgName,
1886
+ path: versionSourcePath,
1887
+ name: versionSourceName,
1900
1888
  type: config.type
1901
1889
  });
1902
1890
  if (!nextVersion) {
@@ -2002,7 +1990,10 @@ function createSyncStrategy(config) {
2002
1990
  nextVersion,
2003
1991
  formattedPrefix,
2004
1992
  tagPackageName,
2005
- tagTemplate,
1993
+ // Only pass tagTemplate when we have a package name to substitute into it.
1994
+ // In multi-package sync mode tagPackageName is null, so omit the template to
1995
+ // avoid a spurious ${packageName} warning and a malformed tag like "-v1.0.0".
1996
+ tagPackageName ? tagTemplate : void 0,
2006
1997
  config.packageSpecificTags || false
2007
1998
  );
2008
1999
  let formattedCommitMessage;
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  loadConfig,
6
6
  log,
7
7
  printJsonOutput
8
- } from "./chunk-V6S7BEBD.js";
8
+ } from "./chunk-ZTFI7TXV.js";
9
9
  import {
10
10
  readPackageVersion
11
11
  } from "./chunk-2MN2VLZF.js";
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  flushPendingWrites,
12
12
  getJsonData,
13
13
  loadConfig
14
- } from "./chunk-V6S7BEBD.js";
14
+ } from "./chunk-ZTFI7TXV.js";
15
15
  import {
16
16
  BaseVersionError
17
17
  } from "./chunk-2MN2VLZF.js";
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## How the Next Version is Calculated
6
6
 
7
- There are two primary methods the tool uses to decide the version bump (e.g., patch, minor, major), configured via the `versionStrategy` option in `version.config.json`:
7
+ There are two primary methods the tool uses to decide the version bump (e.g., patch, minor, major), configured via the `versionStrategy` option in `releasekit.config.json`:
8
8
 
9
9
  ### 1. Conventional Commits (`versionStrategy: "conventional"`)
10
10
 
@@ -14,7 +14,7 @@ This is the default strategy. `releasekit-version` analyzes Git commit messages
14
14
  - **Minor Bump (e.g., 1.2.3 -> 1.3.0):** Triggered by `feat:` commit types.
15
15
  - **Major Bump (e.g., 1.2.3 -> 2.0.0):** Triggered by commits with `BREAKING CHANGE:` in the footer or `feat!:`, `fix!:` etc. in the header.
16
16
 
17
- The specific preset used for analysis (e.g., "angular", "conventional") can be set using the `preset` option in `version.config.json`.
17
+ The specific preset used for analysis (e.g., "angular", "conventional") can be set using the `preset` option in `releasekit.config.json`.
18
18
 
19
19
  **Format:** `<type>(<scope>): <subject>`
20
20
 
@@ -39,9 +39,9 @@ The specific preset used for analysis (e.g., "angular", "conventional") can be s
39
39
 
40
40
  This strategy uses the name of the current Git branch (or the most recently merged branch matching a pattern, if applicable) to determine the version bump.
41
41
 
42
- You define patterns in the `branchPattern` array in `version.config.json`. Each pattern is a string like `"prefix:bumptype"`.
42
+ You define patterns in the `branchPattern` array in `releasekit.config.json`. Each pattern is a string like `"prefix:bumptype"`.
43
43
 
44
- **Example `version.config.json`:**
44
+ **Example `releasekit.config.json`:**
45
45
 
46
46
  ```json
47
47
  {
@@ -156,7 +156,7 @@ Mismatches are detected in the following cases:
156
156
  - Git tag is ahead by a major or minor version (e.g., tag `2.0.0` vs package `1.0.0`)
157
157
  - Git tag is a prerelease but package.json is a stable release (e.g., tag `1.0.0-beta.1` vs package `1.0.0`)
158
158
 
159
- Configure it in `version.config.json`:
159
+ Configure it in `releasekit.config.json`:
160
160
  ```json
161
161
  {
162
162
  "mismatchStrategy": "prefer-package"
@@ -277,7 +277,7 @@ This configuration will process all packages in the `@mycompany` scope except fo
277
277
 
278
278
  ### Tag Template Configuration
279
279
 
280
- You can customize how tags are formatted using the following configuration options in `version.config.json`:
280
+ You can customize how tags are formatted using the following configuration options in `releasekit.config.json`:
281
281
 
282
282
  ```json
283
283
  {
@@ -430,7 +430,7 @@ For global commit messages, use templates without `${packageName}`:
430
430
 
431
431
  ## Monorepo Versioning Modes
432
432
 
433
- While primarily used for single packages now, `releasekit-version` retains options for monorepo workflows, controlled mainly by the `sync` flag in `version.config.json`.
433
+ While primarily used for single packages now, `releasekit-version` retains options for monorepo workflows, controlled mainly by the `sync` flag in `releasekit.config.json`.
434
434
 
435
435
  ### Sync Mode (`sync: true`)
436
436
 
@@ -475,7 +475,7 @@ npx releasekit-version --bump minor --prerelease beta
475
475
  # Result: 1.0.0 -> 1.1.0-beta.0
476
476
  ```
477
477
 
478
- You can also set a default prerelease identifier in your `version.config.json`:
478
+ You can also set a default prerelease identifier in your `releasekit.config.json`:
479
479
 
480
480
  ```json
481
481
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@releasekit/version",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Semantic versioning based on Git history and conventional commits",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -72,8 +72,8 @@
72
72
  "tsup": "^8.5.1",
73
73
  "typescript": "^5.9.3",
74
74
  "vitest": "^4.1.0",
75
- "@releasekit/config": "0.0.0",
76
- "@releasekit/core": "0.0.0"
75
+ "@releasekit/core": "0.0.0",
76
+ "@releasekit/config": "0.0.0"
77
77
  },
78
78
  "engines": {
79
79
  "node": ">=20"
@@ -1,197 +0,0 @@
1
- # CI/CD Integration
2
-
3
- `releasekit-version` is designed to work seamlessly in CI/CD pipelines, making it easy to automate versioning as part of your release workflow.
4
-
5
- ## JSON Output Mode
6
-
7
- For programmatic consumption in CI/CD scripts, `releasekit-version` provides a structured JSON output option:
8
-
9
- ```bash
10
- # Output results in JSON format
11
- npx releasekit-version --json
12
-
13
- # Combine with dry-run for planning
14
- npx releasekit-version --dry-run --json
15
- ```
16
-
17
- This will suppress all normal console output and instead output a single JSON object containing:
18
-
19
- ```json
20
- {
21
- "dryRun": false, // Whether this was a dry run
22
- "updates": [ // Array of packages that were updated
23
- {
24
- "packageName": "@scope/package-a", // Package name
25
- "newVersion": "1.2.3", // New version number
26
- "filePath": "/path/to/package.json" // Path to the updated package.json
27
- }
28
- ],
29
- "changelogs": [ // Structured changelog data per package
30
- {
31
- "packageName": "@scope/package-a", // Package name
32
- "version": "1.2.3", // New version
33
- "previousVersion": "v1.2.2", // Previous tag (null if none)
34
- "revisionRange": "v1.2.2..HEAD", // Git revision range used
35
- "repoUrl": "https://github.com/org/repo", // Repository URL (null if unknown)
36
- "entries": [ // Parsed changelog entries
37
- { "type": "added", "description": "New feature", "scope": "core" },
38
- { "type": "fixed", "description": "Bug fix" }
39
- ]
40
- }
41
- ],
42
- "commitMessage": "chore: release my-package v1.2.3", // The commit message that was used
43
- "tags": [ // Array of tags that were created
44
- "v1.2.3" // or package-specific tags in targeted mode
45
- ]
46
- }
47
- ```
48
-
49
- ### Benefits of JSON Output
50
-
51
- The structured JSON output provides several advantages for CI/CD integration:
52
-
53
- - **Reliable Parsing**: Unlike text logs that might change format or include ANSI color codes, the JSON structure remains consistent
54
- - **Programmatic Access**: Easily extract specific values like version numbers for subsequent steps
55
- - **Conditional Workflows**: Trigger different CI actions based on the presence of updates or specific version changes
56
- - **Audit Trail**: Store the JSON output as artifacts for version change tracking
57
- - **Error Handling**: Better detect and respond to versioning issues in your pipeline
58
-
59
- ## Sample CI/CD Integration Patterns
60
-
61
- Here are some common ways to incorporate `releasekit-version` into your CI/CD pipeline:
62
-
63
- ### GitHub Actions Workflow Example
64
-
65
- ```yaml
66
- name: Release
67
-
68
- on:
69
- push:
70
- branches: [main]
71
-
72
- jobs:
73
- version:
74
- runs-on: ubuntu-latest
75
- outputs:
76
- changes_detected: ${{ steps.version.outputs.changes_detected }}
77
- new_version: ${{ steps.version.outputs.new_version }}
78
-
79
- steps:
80
- - uses: actions/checkout@v6
81
- with:
82
- fetch-depth: 0 # Important for git history
83
-
84
- - name: Setup Node.js
85
- uses: actions/setup-node@v6
86
- with:
87
- node-version: '18'
88
-
89
- - name: Install dependencies
90
- run: npm ci
91
-
92
- - name: Determine version
93
- id: version
94
- run: |
95
- # Run in JSON mode for parsing
96
- VERSION_OUTPUT=$(npx releasekit-version --json)
97
- echo "Version output: $VERSION_OUTPUT"
98
-
99
- # Use jq to parse the JSON output
100
- CHANGES_DETECTED=$(echo "$VERSION_OUTPUT" | jq -r '.updates | length > 0')
101
- echo "changes_detected=$CHANGES_DETECTED" >> $GITHUB_OUTPUT
102
-
103
- if [ "$CHANGES_DETECTED" = "true" ]; then
104
- # Extract the first package's new version as representative version
105
- NEW_VERSION=$(echo "$VERSION_OUTPUT" | jq -r '.updates[0].newVersion')
106
- echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
107
- fi
108
-
109
- publish:
110
- needs: version
111
- if: needs.version.outputs.changes_detected == 'true'
112
- runs-on: ubuntu-latest
113
- steps:
114
- # Publishing steps using the detected version
115
- - run: echo "Would publish version ${{ needs.version.outputs.new_version }}"
116
- ```
117
-
118
- ### GitLab CI Pipeline Example
119
-
120
- ```yaml
121
- stages:
122
- - version
123
- - publish
124
-
125
- determine_version:
126
- stage: version
127
- script:
128
- - npm ci
129
- - |
130
- VERSION_OUTPUT=$(npx releasekit-version --json)
131
- echo "VERSION_OUTPUT=$VERSION_OUTPUT" >> version.env
132
-
133
- # Parse values for use in later stages
134
- CHANGES_DETECTED=$(echo "$VERSION_OUTPUT" | jq -r '.updates | length > 0')
135
- echo "CHANGES_DETECTED=$CHANGES_DETECTED" >> version.env
136
-
137
- if [ "$CHANGES_DETECTED" = "true" ]; then
138
- NEW_VERSION=$(echo "$VERSION_OUTPUT" | jq -r '.updates[0].newVersion')
139
- echo "NEW_VERSION=$NEW_VERSION" >> version.env
140
- fi
141
- artifacts:
142
- reports:
143
- dotenv: version.env
144
-
145
- publish:
146
- stage: publish
147
- needs: determine_version
148
- script:
149
- - echo "Publishing version $NEW_VERSION"
150
- rules:
151
- - if: $CHANGES_DETECTED == "true"
152
- ```
153
-
154
- ## Working with Tags in CI
155
-
156
- When using the targeted mode with `-t` flag, `releasekit-version` creates package-specific tags (e.g., `@scope/package-a@1.2.0`) but not a global tag. If your release process needs a global tag, you can add a step to your CI/CD pipeline:
157
-
158
- ```bash
159
- # Create a global tag based on the representative version
160
- NEW_VERSION=$(echo "$VERSION_OUTPUT" | jq -r '.updates[0].newVersion')
161
- git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION"
162
- git push origin "v$NEW_VERSION"
163
- ```
164
-
165
- ## Environment Variables
166
-
167
- `releasekit-version` respects the following environment variables:
168
-
169
- - `NO_COLOR=1`: Disables colored output in logs (automatically detected in CI environments)
170
- - `CI=true`: Most CI environments set this automatically, which helps the tool adjust its output behaviour
171
-
172
- ## Skipping CI for Version Commits
173
-
174
- If you want to prevent additional CI runs when version commits are made, you can include CI skip flags in your commit message template in `version.config.json`:
175
-
176
- ```json
177
- {
178
- "commitMessage": "chore: release ${packageName} v${version} [skip ci]",
179
- // other configuration options...
180
- }
181
- ```
182
-
183
- Common CI skip patterns include:
184
- - `[skip ci]` or `[ci skip]` - Works in GitHub Actions, GitLab CI, CircleCI
185
- - `[skip-ci]` - Alternative format supported by some CI systems
186
- - `[no ci]` - Another variant
187
-
188
- Each CI system might have slightly different syntax, so check your CI provider's documentation for the exact skip token to use.
189
-
190
- ## Tips for Reliable CI/CD Integration
191
-
192
- 1. **Always use `--json`** in CI/CD pipelines for consistent output parsing
193
- 2. **Use the `fetch-depth: 0`** option in GitHub Actions (or equivalent in other CIs) to ensure access to the full Git history
194
- 3. **Store the JSON output** as a build artifact for debugging and auditing
195
- 4. **Consider dry runs** in your preview/staging branches to validate version changes before they're applied
196
- 5. **Use `--project-dir`** when running from a different directory than your project root
197
- 6. **Be mindful of Git credentials** - ensure your CI has proper permissions for creating commits and tags