Skip to content

Commit d70cef4

Browse files
authored
feat: add automatic version bump detection to bit ci merge (teambit#9838)
Enhances `bit ci merge` command with automatic version bump detection from commit messages, eliminating the need to manually specify version flags in many cases. **Auto-detection priority:** 1. **Explicit keywords**: `BIT-BUMP-MAJOR`, `BIT-BUMP-MINOR` in commit message 2. **Conventional commits**: `feat\!:`/`BREAKING CHANGE` → major, `feat:` → minor, `fix:` → patch 3. **Default**: patch version bump **Key features:** - Only triggers when no explicit version flags (`--patch`, `--minor`, `--major`) are provided - Configurable via `useConventionalCommitsForVersionBump` and `useExplicitBumpKeywords` in workspace.jsonc - Includes CircleCI wrapper script for environment variable support (`VERSION_BUMP_TYPE`) - Comprehensive documentation with examples
1 parent 7cabcd2 commit d70cef4

File tree

5 files changed

+221
-15
lines changed

5 files changed

+221
-15
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ jobs:
667667
- run: bit config set user.token ${BIT_CLOUD_PROD_TOKEN}
668668
- run:
669669
name: 'bit ci merge'
670-
command: 'cd bit && bit ci merge --build'
670+
command: 'cd bit && bit ci merge --build ${BIT_CI_MERGE_EXTRA_FLAGS}'
671671
no_output_timeout: '50m'
672672
environment:
673673
NODE_OPTIONS: --max-old-space-size=32000

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,6 @@ package-lock.json
7272
!.yarn/plugins
7373
!.yarn/sdks
7474
!.yarn/versions
75+
76+
# Claude Code files
77+
.claude/

scopes/git/ci/ci.docs.mdx

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,25 @@ bit ci merge [--message <string>] [--build] [--increment <level>] [--patch|--min
9696
| `--prerelease-id` | | Prerelease identifier (e.g. "dev" to get "1.0.0-dev.1"). |
9797
| `--increment-by` | | Increment by more than 1 (e.g. `--increment-by 2` with patch: 0.0.1 → 0.0.3). |
9898

99+
### Automatic Version Bump Detection
100+
101+
When **no explicit version flags** are provided, `bit ci merge` can automatically determine the version bump level from the commit message:
102+
103+
1. **Explicit Keywords** (highest priority):
104+
105+
- `BIT-BUMP-MAJOR` anywhere in commit message → major version bump
106+
- `BIT-BUMP-MINOR` anywhere in commit message → minor version bump
107+
108+
2. **Conventional Commits** (when enabled):
109+
110+
- `feat!:` or `BREAKING CHANGE` → major version bump
111+
- `feat:` → minor version bump
112+
- `fix:` → patch version bump
113+
114+
3. **Default**: patch version bump
115+
116+
**Note**: Auto-detection only occurs when no version flags (`--patch`, `--minor`, `--major`, etc.) are provided. Explicit flags always take precedence.
117+
99118
### Internal flow
100119

101120
1. **Ensure main lane**
@@ -123,20 +142,34 @@ bit ci merge [--message <string>] [--build] [--increment <level>] [--patch|--min
123142
### Version bump examples
124143

125144
```bash
126-
# Default patch increment (1.0.0 → 1.0.1)
127-
bit ci merge --message "fix: resolve memory leak"
128-
129-
# Minor version bump (1.0.0 → 1.1.0)
145+
# Explicit version bump (takes precedence over auto-detection)
130146
bit ci merge --minor --message "feat: add new API endpoint"
131-
132-
# Major version bump (1.0.0 → 2.0.0)
133147
bit ci merge --major --message "feat!: breaking API changes"
148+
bit ci merge --patch --increment-by 3 --message "fix: critical patches"
134149

135-
# Prerelease increment (1.0.0 → 1.0.1-dev.1)
136-
bit ci merge --pre-release dev --message "feat: experimental feature"
150+
# Automatic detection from commit message (no flags needed)
151+
git commit -m "feat: add new API endpoint"
152+
bit ci merge --build # → auto-detects minor bump
137153

138-
# Custom increment amount (1.0.0 → 1.0.3)
139-
bit ci merge --patch --increment-by 3 --message "fix: critical patches"
154+
git commit -m "feat!: breaking API changes"
155+
bit ci merge --build # → auto-detects major bump
156+
157+
git commit -m "fix: resolve memory leak"
158+
bit ci merge --build # → auto-detects patch bump (if conventional commits enabled)
159+
160+
# Using explicit keywords for auto-detection
161+
git commit -m "feat: add new feature BIT-BUMP-MINOR"
162+
bit ci merge --build # → auto-detects minor bump
163+
164+
git commit -m "refactor: major code restructure BIT-BUMP-MAJOR"
165+
bit ci merge --build # → auto-detects major bump
166+
167+
# Default patch increment (when no detection rules match)
168+
git commit -m "chore: update dependencies"
169+
bit ci merge --build # → defaults to patch bump
170+
171+
# Prerelease increment (explicit flag required)
172+
bit ci merge --pre-release dev --message "feat: experimental feature"
140173
```
141174

142175
### CI hint
@@ -152,7 +185,9 @@ The CI aspect supports configuration in `workspace.jsonc`:
152185
```json
153186
{
154187
"teambit.git/ci": {
155-
"commitMessageScript": "node scripts/generate-commit-message.js"
188+
"commitMessageScript": "node scripts/generate-commit-message.js",
189+
"useConventionalCommitsForVersionBump": true,
190+
"useExplicitBumpKeywords": true
156191
}
157192
}
158193
```
@@ -179,3 +214,45 @@ try {
179214
console.log('chore: update .bitmap and lockfiles as needed [skip ci]');
180215
}
181216
```
217+
218+
### `useConventionalCommitsForVersionBump`
219+
220+
**Optional.** Enable automatic version bump detection based on conventional commit patterns.
221+
222+
- **Default**: `false` (disabled)
223+
- **When enabled**: Analyzes commit messages for conventional commit patterns:
224+
- `feat!:` or `BREAKING CHANGE` → major version bump
225+
- `feat:` → minor version bump
226+
- `fix:` → patch version bump
227+
228+
```json
229+
{
230+
"teambit.git/ci": {
231+
"useConventionalCommitsForVersionBump": true
232+
}
233+
}
234+
```
235+
236+
### `useExplicitBumpKeywords`
237+
238+
**Optional.** Enable automatic version bump detection using explicit keywords.
239+
240+
- **Default**: `true` (enabled)
241+
- **Keywords**:
242+
- `BIT-BUMP-MAJOR` anywhere in commit message → major version bump
243+
- `BIT-BUMP-MINOR` anywhere in commit message → minor version bump
244+
245+
```json
246+
{
247+
"teambit.git/ci": {
248+
"useExplicitBumpKeywords": false // disable explicit keywords
249+
}
250+
}
251+
```
252+
253+
**Example usage:**
254+
255+
```bash
256+
git commit -m "feat: add new feature BIT-BUMP-MINOR"
257+
bit ci merge --build # → automatically uses minor version bump
258+
```

scopes/git/ci/ci.main.runtime.ts

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,64 @@ import { CiMergeCmd } from './commands/merge.cmd';
2121
import { git } from './git';
2222

2323
export interface CiWorkspaceConfig {
24+
/**
25+
* Path to a custom script that generates commit messages for `bit ci merge` operations.
26+
* The script will be executed when components are tagged and committed to the repository.
27+
* If not specified, falls back to the default commit message:
28+
* "chore: update .bitmap and lockfiles as needed [skip ci]"
29+
*
30+
* @example
31+
* ```json
32+
* {
33+
* "teambit.git/ci": {
34+
* "commitMessageScript": "node scripts/generate-commit-message.js"
35+
* }
36+
* }
37+
* ```
38+
*/
2439
commitMessageScript?: string;
40+
41+
/**
42+
* Enables automatic version bump detection from conventional commit messages.
43+
* When enabled, the system analyzes commit messages to determine the appropriate version bump:
44+
* - `feat!:` or `BREAKING CHANGE` → major version bump
45+
* - `feat:` → minor version bump
46+
* - `fix:` → patch version bump
47+
*
48+
* Only applies when no explicit version flags (--patch, --minor, --major) are provided.
49+
*
50+
* @default false
51+
* @example
52+
* ```json
53+
* {
54+
* "teambit.git/ci": {
55+
* "useConventionalCommitsForVersionBump": true
56+
* }
57+
* }
58+
* ```
59+
*/
60+
useConventionalCommitsForVersionBump?: boolean;
61+
62+
/**
63+
* Enables detection of explicit version bump keywords in commit messages.
64+
* When enabled, the system looks for these keywords in commit messages:
65+
* - `BIT-BUMP-MAJOR` → major version bump
66+
* - `BIT-BUMP-MINOR` → minor version bump
67+
*
68+
* These keywords have higher priority than conventional commits parsing.
69+
* Only applies when no explicit version flags are provided.
70+
*
71+
* @default true
72+
* @example
73+
* ```json
74+
* {
75+
* "teambit.git/ci": {
76+
* "useExplicitBumpKeywords": true
77+
* }
78+
* }
79+
* ```
80+
*/
81+
useExplicitBumpKeywords?: boolean;
2582
}
2683

2784
export class CiMain {
@@ -139,6 +196,44 @@ export class CiMain {
139196
}
140197
}
141198

199+
private parseVersionBumpFromCommit(commitMessage: string): ReleaseType | null {
200+
// Check explicit bump keywords (highest priority after env vars)
201+
if (this.config.useExplicitBumpKeywords !== false) {
202+
// default to true
203+
if (commitMessage.includes('BIT-BUMP-MAJOR')) {
204+
this.logger.console(chalk.blue('Found BIT-BUMP-MAJOR keyword in commit message'));
205+
return 'major';
206+
}
207+
if (commitMessage.includes('BIT-BUMP-MINOR')) {
208+
this.logger.console(chalk.blue('Found BIT-BUMP-MINOR keyword in commit message'));
209+
return 'minor';
210+
}
211+
}
212+
213+
// Check conventional commits if enabled
214+
if (this.config.useConventionalCommitsForVersionBump) {
215+
// Check for breaking changes (major version bump)
216+
if (/^feat!(\(.+\))?:|^fix!(\(.+\))?:|BREAKING CHANGE/m.test(commitMessage)) {
217+
this.logger.console(chalk.blue('Found breaking changes in commit message (conventional commits)'));
218+
return 'major';
219+
}
220+
221+
// Check for features (minor version bump)
222+
if (/^feat(\(.+\))?:/m.test(commitMessage)) {
223+
this.logger.console(chalk.blue('Found feature commits (conventional commits)'));
224+
return 'minor';
225+
}
226+
227+
// Check for fixes (patch version bump) - explicit patch not needed as it's default
228+
if (/^fix(\(.+\))?:/m.test(commitMessage)) {
229+
this.logger.console(chalk.blue('Found fix commits (conventional commits) - using default patch'));
230+
return 'patch';
231+
}
232+
}
233+
234+
return null; // No specific version bump detected
235+
}
236+
142237
private async getCustomCommitMessage() {
143238
try {
144239
const commitMessageScript = this.config.commitMessageScript;
@@ -359,21 +454,22 @@ export class CiMain {
359454
releaseType,
360455
preReleaseId,
361456
incrementBy,
457+
explicitVersionBump,
362458
}: {
363459
message?: string;
364460
build?: boolean;
365461
strict?: boolean;
366-
releaseType?: ReleaseType;
462+
releaseType: ReleaseType;
367463
preReleaseId?: string;
368464
incrementBy?: number;
465+
explicitVersionBump?: boolean;
369466
}) {
370467
const message = argMessage || (await this.getGitCommitMessage());
371468
if (!message) {
372469
throw new Error('Failed to get commit message from git. Please provide a message using --message option.');
373470
}
374471

375472
const currentLane = await this.lanes.getCurrentLane();
376-
377473
if (currentLane) {
378474
// this doesn't normally happen. we expect this mergePr to be called from the default branch, which normally checks
379475
// out to main lane.
@@ -425,13 +521,14 @@ export class CiMain {
425521

426522
this.logger.console('📦 Component Operations');
427523
this.logger.console(chalk.blue('Tagging components'));
524+
const finalReleaseType = await this.determineReleaseType(releaseType, explicitVersionBump);
428525
const tagResults = await this.snapping.tag({
429526
all: true,
430527
message,
431528
build,
432529
failFast: true,
433530
persist: hasSoftTaggedComponents,
434-
releaseType,
531+
releaseType: finalReleaseType,
435532
preReleaseId,
436533
incrementBy,
437534
});
@@ -490,6 +587,29 @@ export class CiMain {
490587

491588
return { code: 0, data: '' };
492589
}
590+
591+
/**
592+
* Auto-detect version bump from commit messages if no explicit version bump was provided
593+
*/
594+
private async determineReleaseType(releaseType: ReleaseType, explicitVersionBump?: boolean): Promise<ReleaseType> {
595+
if (explicitVersionBump) {
596+
this.logger.console(chalk.blue(`Using explicit version bump: ${releaseType}`));
597+
return releaseType;
598+
}
599+
// Only auto-detect if user didn't specify any version flags
600+
const lastCommit = await this.getGitCommitMessage();
601+
if (!lastCommit) {
602+
this.logger.console(chalk.blue('No commit message found, using default patch'));
603+
return releaseType;
604+
}
605+
const detectedReleaseType = this.parseVersionBumpFromCommit(lastCommit);
606+
if (detectedReleaseType) {
607+
this.logger.console(chalk.green(`Auto-detected version bump: ${detectedReleaseType}`));
608+
return detectedReleaseType;
609+
}
610+
this.logger.console(chalk.blue('No specific version bump detected, using default patch'));
611+
return releaseType;
612+
}
493613
}
494614

495615
// @ts-ignore

scopes/git/ci/commands/merge.cmd.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,19 @@ export class CiMergeCmd implements Command {
5757

5858
const { releaseType, preReleaseId } = validateOptions(options);
5959

60+
// Check if user explicitly provided any version bump flags
61+
const explicitVersionBump = Boolean(
62+
options.increment || options.patch || options.minor || options.major || options.preRelease
63+
);
64+
6065
return this.ci.mergePr({
6166
message: options.message,
6267
build: options.build,
6368
strict: options.strict,
6469
releaseType,
6570
preReleaseId,
6671
incrementBy: options.incrementBy,
72+
explicitVersionBump,
6773
});
6874
}
6975
}

0 commit comments

Comments
 (0)