semantic-release-linear-app 0.1.0 โ 0.2.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 +169 -112
- package/dist/lib/parse-issues.d.ts +12 -9
- package/dist/lib/parse-issues.js +34 -32
- package/dist/lib/parse-issues.test.js +30 -14
- package/dist/lib/success.d.ts +5 -0
- package/dist/lib/success.js +25 -10
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
- package/src/lib/parse-issues.test.ts +41 -20
- package/src/lib/parse-issues.ts +37 -38
- package/src/lib/success.ts +44 -9
- package/src/types.ts +6 -0
package/README.md
CHANGED
|
@@ -1,204 +1,261 @@
|
|
|
1
|
-
# semantic-release-linear
|
|
1
|
+
# semantic-release-linear
|
|
2
2
|
|
|
3
|
-
> A [semantic-release](https://github.com/semantic-release/semantic-release) plugin
|
|
3
|
+
> A [semantic-release](https://github.com/semantic-release/semantic-release) plugin that updates Linear issues with version labels based on branch names.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
- ๐ฟ Extracts Linear issue IDs from branch names
|
|
8
|
+
- ๐ท๏ธ Automatically adds version labels to Linear issues
|
|
9
|
+
- ๐จ Color-coded labels based on release type
|
|
10
|
+
- ๐งน Removes old version labels (configurable)
|
|
11
|
+
- ๐ฌ Adds release comments to issues (optional)
|
|
12
12
|
- โก Batch operations for efficiency
|
|
13
|
-
- ๐ Full TypeScript support
|
|
13
|
+
- ๐ Full TypeScript support
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install --save-dev semantic-release-linear
|
|
18
|
+
npm install --save-dev semantic-release-linear
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Quick Start
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
1. **Configure semantic-release** (`.releaserc.json`):
|
|
24
24
|
|
|
25
25
|
```json
|
|
26
26
|
{
|
|
27
27
|
"plugins": [
|
|
28
28
|
"@semantic-release/commit-analyzer",
|
|
29
29
|
"@semantic-release/release-notes-generator",
|
|
30
|
-
["semantic-release-linear
|
|
31
|
-
"
|
|
30
|
+
["semantic-release-linear", {
|
|
31
|
+
"teamKeys": ["ENG", "FEAT", "BUG"]
|
|
32
32
|
}],
|
|
33
33
|
"@semantic-release/github"
|
|
34
34
|
]
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
If you're using TypeScript for your configuration, the plugin exports types:
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
import type { PluginConfig } from 'semantic-release-linear-app';
|
|
38
|
+
2. **Set your Linear API key**:
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
teamKeys: ['ENG', 'FEAT'],
|
|
48
|
-
labelPrefix: 'v',
|
|
49
|
-
removeOldLabels: true,
|
|
50
|
-
addComment: false
|
|
51
|
-
};
|
|
40
|
+
```bash
|
|
41
|
+
export LINEAR_API_KEY=lin_api_xxx
|
|
52
42
|
```
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
### Authentication
|
|
57
|
-
|
|
58
|
-
Set your Linear API key via environment variable:
|
|
44
|
+
3. **Use proper branch names**:
|
|
59
45
|
|
|
60
46
|
```bash
|
|
61
|
-
|
|
47
|
+
git checkout -b feature/ENG-123-add-authentication
|
|
48
|
+
git checkout -b bugfix/FEAT-456-fix-validation
|
|
49
|
+
git checkout -b ENG-789-quick-fix
|
|
62
50
|
```
|
|
63
51
|
|
|
64
|
-
|
|
52
|
+
That's it! When semantic-release creates a release from these branches, the corresponding Linear issues will be labeled with the version number.
|
|
65
53
|
|
|
66
|
-
|
|
67
|
-
{
|
|
68
|
-
"apiKey": "lin_api_xxx"
|
|
69
|
-
}
|
|
70
|
-
```
|
|
54
|
+
## Configuration
|
|
71
55
|
|
|
72
56
|
### Options
|
|
73
57
|
|
|
74
58
|
| Option | Default | Description |
|
|
75
59
|
|--------|---------|-------------|
|
|
76
60
|
| `apiKey` | - | Linear API key (or use `LINEAR_API_KEY` env var) |
|
|
77
|
-
| `teamKeys` | `[]` |
|
|
61
|
+
| `teamKeys` | `[]` | Team keys to filter (e.g., `["ENG", "FEAT"]`) |
|
|
78
62
|
| `labelPrefix` | `"v"` | Prefix for version labels |
|
|
79
|
-
| `removeOldLabels` | `true` | Remove previous version labels
|
|
80
|
-
| `addComment` | `false` | Add a comment to issues
|
|
81
|
-
| `
|
|
63
|
+
| `removeOldLabels` | `true` | Remove previous version labels |
|
|
64
|
+
| `addComment` | `false` | Add a release comment to issues |
|
|
65
|
+
| `skipBranches` | `["main", "master", "develop", "staging", "production"]` | Branches to skip unless they contain issues |
|
|
66
|
+
| `requireIssueInBranch` | `true` | Only process branches with Linear issues |
|
|
67
|
+
| `dryRun` | `false` | Preview without making changes |
|
|
82
68
|
|
|
83
|
-
###
|
|
69
|
+
### TypeScript Configuration
|
|
84
70
|
|
|
85
|
-
```
|
|
86
|
-
// .releaserc.
|
|
87
|
-
|
|
88
|
-
|
|
71
|
+
```typescript
|
|
72
|
+
// .releaserc.ts
|
|
73
|
+
import type { PluginConfig } from 'semantic-release-linear';
|
|
74
|
+
|
|
75
|
+
const config: PluginConfig = {
|
|
76
|
+
teamKeys: ['ENG', 'FEAT'],
|
|
77
|
+
labelPrefix: 'release-',
|
|
78
|
+
addComment: true,
|
|
79
|
+
skipBranches: ['main', 'develop']
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default {
|
|
89
83
|
plugins: [
|
|
90
84
|
'@semantic-release/commit-analyzer',
|
|
91
85
|
'@semantic-release/release-notes-generator',
|
|
92
|
-
['semantic-release-linear
|
|
93
|
-
teamKeys: ['ENG', 'FEAT', 'BUG'],
|
|
94
|
-
labelPrefix: 'version:',
|
|
95
|
-
removeOldLabels: true,
|
|
96
|
-
addComment: true
|
|
97
|
-
}],
|
|
98
|
-
'@semantic-release/npm',
|
|
86
|
+
['semantic-release-linear', config],
|
|
99
87
|
'@semantic-release/github'
|
|
100
88
|
]
|
|
101
89
|
};
|
|
102
90
|
```
|
|
103
91
|
|
|
104
|
-
##
|
|
92
|
+
## Branch Naming Convention
|
|
105
93
|
|
|
106
|
-
|
|
107
|
-
2. **Label Creation**: Creates a version label if it doesn't exist (color-coded by release type)
|
|
108
|
-
3. **Issue Updates**: Applies the label to all detected issues
|
|
109
|
-
4. **Cleanup**: Optionally removes old version labels to avoid clutter
|
|
110
|
-
|
|
111
|
-
### Commit Message Examples
|
|
112
|
-
|
|
113
|
-
The plugin will detect Linear issues in various formats:
|
|
94
|
+
### Recommended Patterns
|
|
114
95
|
|
|
115
96
|
```bash
|
|
116
|
-
#
|
|
117
|
-
|
|
97
|
+
# Feature branches
|
|
98
|
+
feature/ENG-123-user-authentication
|
|
99
|
+
feature/FEAT-456-payment-integration
|
|
118
100
|
|
|
119
|
-
#
|
|
120
|
-
|
|
101
|
+
# Bug fixes
|
|
102
|
+
bugfix/BUG-789-login-error
|
|
103
|
+
fix/ENG-101-memory-leak
|
|
121
104
|
|
|
122
|
-
#
|
|
123
|
-
|
|
105
|
+
# Hotfixes
|
|
106
|
+
hotfix/ENG-102-critical-security-patch
|
|
124
107
|
|
|
125
|
-
#
|
|
126
|
-
|
|
108
|
+
# Simple format (still works)
|
|
109
|
+
ENG-103-quick-update
|
|
110
|
+
FEAT-104
|
|
127
111
|
```
|
|
128
112
|
|
|
129
|
-
###
|
|
113
|
+
### Multiple Issues in One Branch
|
|
130
114
|
|
|
131
|
-
|
|
115
|
+
If you need to update multiple issues, include them all in the branch name:
|
|
132
116
|
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
|
|
136
|
-
- ๐ฃ **Prerelease** versions - Purple
|
|
117
|
+
```bash
|
|
118
|
+
feature/ENG-123-FEAT-456-combined-update
|
|
119
|
+
```
|
|
137
120
|
|
|
138
|
-
|
|
121
|
+
### Enforce Branch Naming
|
|
139
122
|
|
|
140
|
-
|
|
123
|
+
Use Git hooks or CI checks to enforce the naming convention:
|
|
141
124
|
|
|
142
|
-
|
|
125
|
+
```bash
|
|
126
|
+
#!/bin/bash
|
|
127
|
+
# .git/hooks/pre-push or CI script
|
|
143
128
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
latest: {
|
|
153
|
-
labelPrefix: 'stable:',
|
|
154
|
-
addComment: true
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
129
|
+
branch=$(git rev-parse --abbrev-ref HEAD)
|
|
130
|
+
pattern="^(feature|bugfix|fix|hotfix)/[A-Z]+-[0-9]+|^[A-Z]+-[0-9]+"
|
|
131
|
+
|
|
132
|
+
if ! [[ "$branch" =~ $pattern ]]; then
|
|
133
|
+
echo "โ Branch name must include a Linear issue ID"
|
|
134
|
+
echo " Example: feature/ENG-123-description"
|
|
135
|
+
exit 1
|
|
136
|
+
fi
|
|
158
137
|
```
|
|
159
138
|
|
|
160
|
-
|
|
139
|
+
## How It Works
|
|
140
|
+
|
|
141
|
+
1. **Branch Detection**: When semantic-release runs, it reads the current branch name
|
|
142
|
+
2. **Issue Extraction**: Extracts Linear issue IDs (e.g., `ENG-123`) from the branch
|
|
143
|
+
3. **Label Creation**: Creates a version label if needed (e.g., `v1.2.3`)
|
|
144
|
+
4. **Issue Update**: Applies the label to the Linear issue(s)
|
|
145
|
+
5. **Cleanup**: Optionally removes old version labels
|
|
146
|
+
|
|
147
|
+
### Label Colors
|
|
148
|
+
|
|
149
|
+
Labels are color-coded by release type:
|
|
161
150
|
|
|
162
|
-
|
|
151
|
+
- ๐ด **Major** (breaking changes) - Red
|
|
152
|
+
- ๐ **Minor** (new features) - Orange
|
|
153
|
+
- ๐ข **Patch** (bug fixes) - Green
|
|
154
|
+
- ๐ฃ **Prerelease** - Purple
|
|
155
|
+
|
|
156
|
+
## Dry Run Mode
|
|
157
|
+
|
|
158
|
+
Test what will happen without making changes:
|
|
163
159
|
|
|
164
160
|
```javascript
|
|
165
161
|
{
|
|
166
|
-
|
|
162
|
+
"plugins": [
|
|
163
|
+
["semantic-release-linear", {
|
|
164
|
+
"dryRun": true
|
|
165
|
+
}]
|
|
166
|
+
]
|
|
167
167
|
}
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
Output:
|
|
171
|
+
```
|
|
172
|
+
[semantic-release] [semantic-release-linear] Found 1 Linear issue(s) in branch "feature/ENG-123-auth": ENG-123
|
|
173
|
+
[semantic-release] [semantic-release-linear] [Dry run] Would update issues: ["ENG-123"]
|
|
174
|
+
[semantic-release] [semantic-release-linear] [Dry run] Would apply label: v1.2.3
|
|
175
|
+
```
|
|
176
|
+
|
|
170
177
|
## Linear API Setup
|
|
171
178
|
|
|
172
|
-
1. Go to Linear Settings โ API โ Personal API keys
|
|
173
|
-
2. Create a new key with
|
|
179
|
+
1. Go to **Linear Settings** โ **API** โ **Personal API keys**
|
|
180
|
+
2. Create a new key with **write** access
|
|
174
181
|
3. Add to your CI environment as `LINEAR_API_KEY`
|
|
175
182
|
|
|
176
|
-
## CI
|
|
183
|
+
## CI Examples
|
|
177
184
|
|
|
178
185
|
### GitHub Actions
|
|
179
186
|
|
|
180
187
|
```yaml
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
188
|
+
name: Release
|
|
189
|
+
|
|
190
|
+
on:
|
|
191
|
+
push:
|
|
192
|
+
branches: [main, next]
|
|
193
|
+
|
|
194
|
+
jobs:
|
|
195
|
+
release:
|
|
196
|
+
runs-on: ubuntu-latest
|
|
197
|
+
steps:
|
|
198
|
+
- uses: actions/checkout@v3
|
|
199
|
+
|
|
200
|
+
- name: Setup Node.js
|
|
201
|
+
uses: actions/setup-node@v3
|
|
202
|
+
with:
|
|
203
|
+
node-version: 18
|
|
204
|
+
|
|
205
|
+
- name: Install dependencies
|
|
206
|
+
run: npm ci
|
|
207
|
+
|
|
208
|
+
- name: Release
|
|
209
|
+
env:
|
|
210
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
211
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
212
|
+
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
|
213
|
+
run: npx semantic-release
|
|
187
214
|
```
|
|
188
215
|
|
|
216
|
+
## FAQ
|
|
217
|
+
|
|
218
|
+
### Why only branch names?
|
|
219
|
+
|
|
220
|
+
Extracting from commits is unreliable - developers forget, commits get squashed, and there's no enforcement. Branch names are:
|
|
221
|
+
- Required for every PR
|
|
222
|
+
- Easily enforced via Git hooks
|
|
223
|
+
- Visible in PR lists
|
|
224
|
+
- A single source of truth
|
|
225
|
+
|
|
226
|
+
### What about releases from main/master?
|
|
227
|
+
|
|
228
|
+
You have three options:
|
|
229
|
+
|
|
230
|
+
1. **Skip them** (default): Set `requireIssueInBranch: true`
|
|
231
|
+
2. **Tag main with an issue**: `git checkout main-ENG-123`
|
|
232
|
+
3. **Allow all branches**: Set `requireIssueInBranch: false`
|
|
233
|
+
|
|
234
|
+
### Can I update multiple issues?
|
|
235
|
+
|
|
236
|
+
Yes, include multiple issue IDs in your branch name:
|
|
237
|
+
```bash
|
|
238
|
+
feature/ENG-123-FEAT-456-big-feature
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### What if my branch doesn't have an issue?
|
|
242
|
+
|
|
243
|
+
The plugin will skip it (unless `requireIssueInBranch: false`). This is intentional - not every release needs to update Linear.
|
|
244
|
+
|
|
189
245
|
## Troubleshooting
|
|
190
246
|
|
|
191
247
|
### Issues not being detected
|
|
192
248
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
249
|
+
1. Check branch name: `git rev-parse --abbrev-ref HEAD`
|
|
250
|
+
2. Verify format matches: `TEAM-NUMBER`
|
|
251
|
+
3. Check team keys filter if configured
|
|
252
|
+
4. Run with `dryRun: true` to debug
|
|
196
253
|
|
|
197
254
|
### API errors
|
|
198
255
|
|
|
199
|
-
-
|
|
200
|
-
- Check
|
|
201
|
-
- Ensure network connectivity
|
|
256
|
+
- Verify API key has write access
|
|
257
|
+
- Check Linear workspace permissions
|
|
258
|
+
- Ensure network connectivity in CI
|
|
202
259
|
|
|
203
260
|
## License
|
|
204
261
|
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { Commit } from "semantic-release";
|
|
2
1
|
/**
|
|
3
|
-
* Extract Linear issue IDs from
|
|
4
|
-
*
|
|
5
|
-
* @param teamKeys - Optional list of team keys to filter by
|
|
6
|
-
* @returns Set of unique issue identifiers
|
|
2
|
+
* Extract Linear issue IDs from branch name ONLY
|
|
3
|
+
* This enforces a single source of truth for issue tracking
|
|
7
4
|
*/
|
|
8
|
-
export declare function parseCommit(commit: Commit, teamKeys?: string[] | null): Set<string>;
|
|
9
5
|
/**
|
|
10
|
-
* Extract
|
|
11
|
-
* @param
|
|
6
|
+
* Extract Linear issue IDs from a branch name
|
|
7
|
+
* @param branchName - The branch name to parse
|
|
12
8
|
* @param teamKeys - Optional list of team keys to filter by
|
|
13
9
|
* @returns Array of unique issue identifiers
|
|
14
10
|
*/
|
|
15
|
-
export declare function
|
|
11
|
+
export declare function parseIssuesFromBranch(branchName: string, teamKeys?: string[] | null): string[];
|
|
12
|
+
/**
|
|
13
|
+
* Check if branch should be processed for Linear updates
|
|
14
|
+
* @param branchName - The branch name to check
|
|
15
|
+
* @param skipBranches - Branches to skip (default: main, master, develop without issue IDs)
|
|
16
|
+
* @returns true if branch should be processed
|
|
17
|
+
*/
|
|
18
|
+
export declare function shouldProcessBranch(branchName: string, skipBranches?: string[]): boolean;
|
package/dist/lib/parse-issues.js
CHANGED
|
@@ -1,46 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Extract Linear issue IDs from branch name ONLY
|
|
4
|
+
* This enforces a single source of truth for issue tracking
|
|
5
|
+
*/
|
|
2
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
7
|
+
exports.parseIssuesFromBranch = parseIssuesFromBranch;
|
|
8
|
+
exports.shouldProcessBranch = shouldProcessBranch;
|
|
5
9
|
/**
|
|
6
|
-
* Extract Linear issue IDs from a
|
|
7
|
-
* @param
|
|
10
|
+
* Extract Linear issue IDs from a branch name
|
|
11
|
+
* @param branchName - The branch name to parse
|
|
8
12
|
* @param teamKeys - Optional list of team keys to filter by
|
|
9
|
-
* @returns
|
|
13
|
+
* @returns Array of unique issue identifiers
|
|
10
14
|
*/
|
|
11
|
-
function
|
|
15
|
+
function parseIssuesFromBranch(branchName, teamKeys = null) {
|
|
12
16
|
const issues = new Set();
|
|
13
17
|
// Build regex pattern based on team keys
|
|
14
18
|
const teamPattern = teamKeys ? `(?:${teamKeys.join("|")})` : "[A-Z]+";
|
|
15
|
-
// Pattern matches: ENG-123, FEAT-45, etc.
|
|
19
|
+
// Pattern matches: feature/ENG-123-description, ENG-123, bugfix/FEAT-45, etc.
|
|
16
20
|
const issuePattern = new RegExp(`\\b(${teamPattern}-\\d+)\\b`, "gi");
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
for (const match of messageMatches) {
|
|
21
|
-
issues.add(match[1].toUpperCase());
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
// Search in commit body
|
|
25
|
-
if (commit.body) {
|
|
26
|
-
const bodyMatches = Array.from(commit.body.matchAll(issuePattern));
|
|
27
|
-
for (const match of bodyMatches) {
|
|
28
|
-
issues.add(match[1].toUpperCase());
|
|
29
|
-
}
|
|
21
|
+
const matches = Array.from(branchName.matchAll(issuePattern));
|
|
22
|
+
for (const match of matches) {
|
|
23
|
+
issues.add(match[1].toUpperCase());
|
|
30
24
|
}
|
|
31
|
-
return issues;
|
|
25
|
+
return Array.from(issues);
|
|
32
26
|
}
|
|
33
27
|
/**
|
|
34
|
-
*
|
|
35
|
-
* @param
|
|
36
|
-
* @param
|
|
37
|
-
* @returns
|
|
28
|
+
* Check if branch should be processed for Linear updates
|
|
29
|
+
* @param branchName - The branch name to check
|
|
30
|
+
* @param skipBranches - Branches to skip (default: main, master, develop without issue IDs)
|
|
31
|
+
* @returns true if branch should be processed
|
|
38
32
|
*/
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
33
|
+
function shouldProcessBranch(branchName, skipBranches = [
|
|
34
|
+
"main",
|
|
35
|
+
"master",
|
|
36
|
+
"develop",
|
|
37
|
+
"staging",
|
|
38
|
+
"production",
|
|
39
|
+
]) {
|
|
40
|
+
// Skip certain branches UNLESS they contain an issue ID
|
|
41
|
+
if (skipBranches.includes(branchName)) {
|
|
42
|
+
// These branches are OK if they contain an issue ID
|
|
43
|
+
const hasIssue = /[A-Z]+-\d+/.test(branchName);
|
|
44
|
+
return hasIssue;
|
|
45
|
+
}
|
|
46
|
+
// Process any other branch that contains an issue ID
|
|
47
|
+
return /[A-Z]+-\d+/.test(branchName);
|
|
46
48
|
}
|
|
@@ -2,22 +2,38 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const parse_issues_1 = require("./parse-issues");
|
|
4
4
|
describe("parse-issues", () => {
|
|
5
|
-
test("extracts Linear issue IDs from
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
expect(result).toEqual(expect.arrayContaining(["ENG-123", "FEAT-456", "BUG-789"]));
|
|
16
|
-
expect(result).toHaveLength(3);
|
|
5
|
+
test("extracts Linear issue IDs from branch name", () => {
|
|
6
|
+
const branchName = "feature/ENG-123-add-new-feature";
|
|
7
|
+
const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName);
|
|
8
|
+
expect(result).toEqual(["ENG-123"]);
|
|
9
|
+
});
|
|
10
|
+
test("extracts multiple issue IDs from branch name", () => {
|
|
11
|
+
const branchName = "fix/ENG-123-FEAT-456-bug-fix";
|
|
12
|
+
const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName);
|
|
13
|
+
expect(result).toEqual(expect.arrayContaining(["ENG-123", "FEAT-456"]));
|
|
14
|
+
expect(result).toHaveLength(2);
|
|
17
15
|
});
|
|
18
16
|
test("filters by team keys when provided", () => {
|
|
19
|
-
const
|
|
20
|
-
const result = (0, parse_issues_1.
|
|
17
|
+
const branchName = "feature/ENG-123-OTHER-456";
|
|
18
|
+
const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName, ["ENG"]);
|
|
21
19
|
expect(result).toEqual(["ENG-123"]);
|
|
22
20
|
});
|
|
21
|
+
test("returns empty array for branch without issues", () => {
|
|
22
|
+
const branchName = "feature/no-issues-here";
|
|
23
|
+
const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName);
|
|
24
|
+
expect(result).toEqual([]);
|
|
25
|
+
});
|
|
26
|
+
test("shouldProcessBranch returns false for skip branches without issues", () => {
|
|
27
|
+
expect((0, parse_issues_1.shouldProcessBranch)("main", ["main", "master"])).toBe(false);
|
|
28
|
+
expect((0, parse_issues_1.shouldProcessBranch)("master", ["main", "master"])).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
test("shouldProcessBranch returns true for skip branches with issues", () => {
|
|
31
|
+
expect((0, parse_issues_1.shouldProcessBranch)("main-ENG-123", ["main", "master"])).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
test("shouldProcessBranch returns true for feature branches with issues", () => {
|
|
34
|
+
expect((0, parse_issues_1.shouldProcessBranch)("feature/ENG-123", ["main", "master"])).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
test("shouldProcessBranch returns false for feature branches without issues", () => {
|
|
37
|
+
expect((0, parse_issues_1.shouldProcessBranch)("feature/no-issues", ["main", "master"])).toBe(false);
|
|
38
|
+
});
|
|
23
39
|
});
|
package/dist/lib/success.d.ts
CHANGED
|
@@ -2,9 +2,14 @@ import { SuccessContext } from "semantic-release";
|
|
|
2
2
|
import { PluginConfig, LinearContext } from "../types";
|
|
3
3
|
interface ExtendedContext extends SuccessContext {
|
|
4
4
|
linear?: LinearContext;
|
|
5
|
+
branch: {
|
|
6
|
+
name: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
5
9
|
}
|
|
6
10
|
/**
|
|
7
11
|
* Update Linear issues after a successful release
|
|
12
|
+
* Only uses branch name for issue detection - single source of truth
|
|
8
13
|
*/
|
|
9
14
|
export declare function success(pluginConfig: PluginConfig, context: ExtendedContext): Promise<void>;
|
|
10
15
|
export {};
|
package/dist/lib/success.js
CHANGED
|
@@ -5,31 +5,45 @@ const linear_client_1 = require("./linear-client");
|
|
|
5
5
|
const parse_issues_1 = require("./parse-issues");
|
|
6
6
|
/**
|
|
7
7
|
* Update Linear issues after a successful release
|
|
8
|
+
* Only uses branch name for issue detection - single source of truth
|
|
8
9
|
*/
|
|
9
10
|
async function success(pluginConfig, context) {
|
|
10
|
-
const { logger, nextRelease,
|
|
11
|
+
const { logger, nextRelease, linear, branch } = context;
|
|
11
12
|
if (!linear) {
|
|
12
13
|
logger.log("Linear context not found, skipping issue updates");
|
|
13
14
|
return;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
if (!branch.name) {
|
|
17
|
+
logger.log("No branch name available, skipping Linear updates");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const { removeOldLabels = true, addComment = false, dryRun = false, skipBranches = ["main", "master", "develop", "staging", "production"], requireIssueInBranch = true, } = pluginConfig;
|
|
21
|
+
// Check if this branch should be processed
|
|
22
|
+
if (requireIssueInBranch && !(0, parse_issues_1.shouldProcessBranch)(branch.name, skipBranches)) {
|
|
23
|
+
logger.log(`Branch "${branch.name}" doesn't contain Linear issues or is in skip list, skipping updates`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
16
26
|
const client = new linear_client_1.LinearClient(linear.apiKey);
|
|
17
27
|
const version = nextRelease.version;
|
|
18
28
|
const channel = nextRelease.channel || "latest";
|
|
19
29
|
// Format the label based on configuration
|
|
20
30
|
const labelName = `${linear.labelPrefix}${version}`;
|
|
21
31
|
const labelColor = getLabelColor(nextRelease.type);
|
|
22
|
-
logger.log(`Updating Linear issues for release ${version} (${channel})`);
|
|
23
|
-
// Extract issue IDs from
|
|
24
|
-
const issueIds = (0, parse_issues_1.
|
|
32
|
+
logger.log(`Updating Linear issues for release ${version} (${channel}) from branch "${branch.name}"`);
|
|
33
|
+
// Extract issue IDs from branch name ONLY
|
|
34
|
+
const issueIds = (0, parse_issues_1.parseIssuesFromBranch)(branch.name, linear.teamKeys);
|
|
25
35
|
if (issueIds.length === 0) {
|
|
26
|
-
logger.log(
|
|
36
|
+
logger.log(`No Linear issues found in branch name "${branch.name}"`);
|
|
37
|
+
if (requireIssueInBranch) {
|
|
38
|
+
logger.warn("โ ๏ธ Consider using branch names like: feature/ENG-123-description");
|
|
39
|
+
}
|
|
27
40
|
return;
|
|
28
41
|
}
|
|
29
|
-
logger.log(`Found ${issueIds.length} Linear issue(s): ${issueIds.join(", ")}`);
|
|
42
|
+
logger.log(`Found ${issueIds.length} Linear issue(s) in branch "${branch.name}": ${issueIds.join(", ")}`);
|
|
30
43
|
if (dryRun) {
|
|
31
44
|
logger.log("[Dry run] Would update issues:", issueIds);
|
|
32
45
|
logger.log(`[Dry run] Would apply label: ${labelName}`);
|
|
46
|
+
logger.log(`[Dry run] Branch: ${branch.name}`);
|
|
33
47
|
return;
|
|
34
48
|
}
|
|
35
49
|
// Ensure the version label exists
|
|
@@ -41,7 +55,7 @@ async function success(pluginConfig, context) {
|
|
|
41
55
|
// Get the issue first
|
|
42
56
|
const issue = await client.getIssue(issueId);
|
|
43
57
|
if (!issue) {
|
|
44
|
-
logger.warn(`Issue ${issueId} not found, skipping`);
|
|
58
|
+
logger.warn(`Issue ${issueId} not found in Linear, skipping`);
|
|
45
59
|
return { issueId, status: "not_found" };
|
|
46
60
|
}
|
|
47
61
|
// Remove old version labels if configured
|
|
@@ -52,7 +66,7 @@ async function success(pluginConfig, context) {
|
|
|
52
66
|
await client.addLabelToIssue(issue.id, label.id);
|
|
53
67
|
// Add comment if configured
|
|
54
68
|
if (addComment) {
|
|
55
|
-
const comment = formatComment(version, channel, nextRelease);
|
|
69
|
+
const comment = formatComment(version, channel, nextRelease, branch.name);
|
|
56
70
|
await client.addComment(issue.id, comment);
|
|
57
71
|
}
|
|
58
72
|
logger.log(`โ Updated issue ${issueId}`);
|
|
@@ -89,10 +103,11 @@ function getLabelColor(releaseType) {
|
|
|
89
103
|
/**
|
|
90
104
|
* Format comment for Linear issue
|
|
91
105
|
*/
|
|
92
|
-
function formatComment(version, channel, release) {
|
|
106
|
+
function formatComment(version, channel, release, branchName) {
|
|
93
107
|
const emoji = channel === "latest" ? "๐" : "๐ฌ";
|
|
94
108
|
const channelText = channel === "latest" ? "stable" : channel;
|
|
95
109
|
let comment = `${emoji} **Released in \`v${version}\`** (${channelText})\n\n`;
|
|
110
|
+
comment += `๐ Released from branch: \`${branchName}\`\n\n`;
|
|
96
111
|
const githubRepo = process.env.GITHUB_REPOSITORY;
|
|
97
112
|
if (release.gitTag && githubRepo) {
|
|
98
113
|
comment += `[View release โ](https://github.com/${githubRepo}/releases/tag/${release.gitTag})`;
|
package/dist/types.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ export interface PluginConfig {
|
|
|
11
11
|
addComment?: boolean;
|
|
12
12
|
/** Preview changes without updating Linear (default: false) */
|
|
13
13
|
dryRun?: boolean;
|
|
14
|
+
/** Branches to skip unless they contain issue IDs (default: ["main", "master", "develop", "staging", "production"]) */
|
|
15
|
+
skipBranches?: string[];
|
|
16
|
+
/** Require issues in branch name to process (default: true) */
|
|
17
|
+
requireIssueInBranch?: boolean;
|
|
14
18
|
}
|
|
15
19
|
export interface LinearContext {
|
|
16
20
|
apiKey: string;
|
package/package.json
CHANGED
|
@@ -1,28 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Commit } from "semantic-release";
|
|
1
|
+
import { parseIssuesFromBranch, shouldProcessBranch } from "./parse-issues";
|
|
3
2
|
|
|
4
3
|
describe("parse-issues", () => {
|
|
5
|
-
test("extracts Linear issue IDs from
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
message: "feat: new feature",
|
|
10
|
-
body: "Closes BUG-789",
|
|
11
|
-
},
|
|
12
|
-
{ message: "chore: no issues here" },
|
|
13
|
-
] as Commit[];
|
|
14
|
-
|
|
15
|
-
const result = parseIssues(commits);
|
|
16
|
-
expect(result).toEqual(
|
|
17
|
-
expect.arrayContaining(["ENG-123", "FEAT-456", "BUG-789"]),
|
|
18
|
-
);
|
|
19
|
-
expect(result).toHaveLength(3);
|
|
4
|
+
test("extracts Linear issue IDs from branch name", () => {
|
|
5
|
+
const branchName = "feature/ENG-123-add-new-feature";
|
|
6
|
+
const result = parseIssuesFromBranch(branchName);
|
|
7
|
+
expect(result).toEqual(["ENG-123"]);
|
|
20
8
|
});
|
|
21
9
|
|
|
22
|
-
test("
|
|
23
|
-
const
|
|
10
|
+
test("extracts multiple issue IDs from branch name", () => {
|
|
11
|
+
const branchName = "fix/ENG-123-FEAT-456-bug-fix";
|
|
12
|
+
const result = parseIssuesFromBranch(branchName);
|
|
13
|
+
expect(result).toEqual(expect.arrayContaining(["ENG-123", "FEAT-456"]));
|
|
14
|
+
expect(result).toHaveLength(2);
|
|
15
|
+
});
|
|
24
16
|
|
|
25
|
-
|
|
17
|
+
test("filters by team keys when provided", () => {
|
|
18
|
+
const branchName = "feature/ENG-123-OTHER-456";
|
|
19
|
+
const result = parseIssuesFromBranch(branchName, ["ENG"]);
|
|
26
20
|
expect(result).toEqual(["ENG-123"]);
|
|
27
21
|
});
|
|
22
|
+
|
|
23
|
+
test("returns empty array for branch without issues", () => {
|
|
24
|
+
const branchName = "feature/no-issues-here";
|
|
25
|
+
const result = parseIssuesFromBranch(branchName);
|
|
26
|
+
expect(result).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("shouldProcessBranch returns false for skip branches without issues", () => {
|
|
30
|
+
expect(shouldProcessBranch("main", ["main", "master"])).toBe(false);
|
|
31
|
+
expect(shouldProcessBranch("master", ["main", "master"])).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("shouldProcessBranch returns true for skip branches with issues", () => {
|
|
35
|
+
expect(shouldProcessBranch("main-ENG-123", ["main", "master"])).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("shouldProcessBranch returns true for feature branches with issues", () => {
|
|
39
|
+
expect(shouldProcessBranch("feature/ENG-123", ["main", "master"])).toBe(
|
|
40
|
+
true,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("shouldProcessBranch returns false for feature branches without issues", () => {
|
|
45
|
+
expect(shouldProcessBranch("feature/no-issues", ["main", "master"])).toBe(
|
|
46
|
+
false,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
28
49
|
});
|
package/src/lib/parse-issues.ts
CHANGED
|
@@ -1,58 +1,57 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Extract Linear issue IDs from branch name ONLY
|
|
3
|
+
* This enforces a single source of truth for issue tracking
|
|
4
|
+
*/
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
|
-
* Extract Linear issue IDs from a
|
|
5
|
-
* @param
|
|
7
|
+
* Extract Linear issue IDs from a branch name
|
|
8
|
+
* @param branchName - The branch name to parse
|
|
6
9
|
* @param teamKeys - Optional list of team keys to filter by
|
|
7
|
-
* @returns
|
|
10
|
+
* @returns Array of unique issue identifiers
|
|
8
11
|
*/
|
|
9
|
-
export function
|
|
10
|
-
|
|
12
|
+
export function parseIssuesFromBranch(
|
|
13
|
+
branchName: string,
|
|
11
14
|
teamKeys: string[] | null = null,
|
|
12
|
-
):
|
|
15
|
+
): string[] {
|
|
13
16
|
const issues = new Set<string>();
|
|
14
17
|
|
|
15
18
|
// Build regex pattern based on team keys
|
|
16
19
|
const teamPattern = teamKeys ? `(?:${teamKeys.join("|")})` : "[A-Z]+";
|
|
17
20
|
|
|
18
|
-
// Pattern matches: ENG-123, FEAT-45, etc.
|
|
21
|
+
// Pattern matches: feature/ENG-123-description, ENG-123, bugfix/FEAT-45, etc.
|
|
19
22
|
const issuePattern = new RegExp(`\\b(${teamPattern}-\\d+)\\b`, "gi");
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
for (const match of messageMatches) {
|
|
25
|
-
issues.add(match[1].toUpperCase());
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Search in commit body
|
|
30
|
-
if (commit.body) {
|
|
31
|
-
const bodyMatches = Array.from(commit.body.matchAll(issuePattern));
|
|
32
|
-
for (const match of bodyMatches) {
|
|
33
|
-
issues.add(match[1].toUpperCase());
|
|
34
|
-
}
|
|
24
|
+
const matches = Array.from(branchName.matchAll(issuePattern));
|
|
25
|
+
for (const match of matches) {
|
|
26
|
+
issues.add(match[1].toUpperCase());
|
|
35
27
|
}
|
|
36
28
|
|
|
37
|
-
return issues;
|
|
29
|
+
return Array.from(issues);
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
/**
|
|
41
|
-
*
|
|
42
|
-
* @param
|
|
43
|
-
* @param
|
|
44
|
-
* @returns
|
|
33
|
+
* Check if branch should be processed for Linear updates
|
|
34
|
+
* @param branchName - The branch name to check
|
|
35
|
+
* @param skipBranches - Branches to skip (default: main, master, develop without issue IDs)
|
|
36
|
+
* @returns true if branch should be processed
|
|
45
37
|
*/
|
|
46
|
-
export function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
38
|
+
export function shouldProcessBranch(
|
|
39
|
+
branchName: string,
|
|
40
|
+
skipBranches: string[] = [
|
|
41
|
+
"main",
|
|
42
|
+
"master",
|
|
43
|
+
"develop",
|
|
44
|
+
"staging",
|
|
45
|
+
"production",
|
|
46
|
+
],
|
|
47
|
+
): boolean {
|
|
48
|
+
// Skip certain branches UNLESS they contain an issue ID
|
|
49
|
+
if (skipBranches.includes(branchName)) {
|
|
50
|
+
// These branches are OK if they contain an issue ID
|
|
51
|
+
const hasIssue = /[A-Z]+-\d+/.test(branchName);
|
|
52
|
+
return hasIssue;
|
|
53
|
+
}
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
// Process any other branch that contains an issue ID
|
|
56
|
+
return /[A-Z]+-\d+/.test(branchName);
|
|
58
57
|
}
|
package/src/lib/success.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SuccessContext } from "semantic-release";
|
|
2
2
|
import { LinearClient } from "./linear-client";
|
|
3
|
-
import {
|
|
3
|
+
import { parseIssuesFromBranch, shouldProcessBranch } from "./parse-issues";
|
|
4
4
|
import {
|
|
5
5
|
PluginConfig,
|
|
6
6
|
LinearContext,
|
|
@@ -10,28 +10,48 @@ import {
|
|
|
10
10
|
|
|
11
11
|
interface ExtendedContext extends SuccessContext {
|
|
12
12
|
linear?: LinearContext;
|
|
13
|
+
branch: {
|
|
14
|
+
name: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
};
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* Update Linear issues after a successful release
|
|
21
|
+
* Only uses branch name for issue detection - single source of truth
|
|
17
22
|
*/
|
|
18
23
|
export async function success(
|
|
19
24
|
pluginConfig: PluginConfig,
|
|
20
25
|
context: ExtendedContext,
|
|
21
26
|
): Promise<void> {
|
|
22
|
-
const { logger, nextRelease,
|
|
27
|
+
const { logger, nextRelease, linear, branch } = context;
|
|
23
28
|
|
|
24
29
|
if (!linear) {
|
|
25
30
|
logger.log("Linear context not found, skipping issue updates");
|
|
26
31
|
return;
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
if (!branch.name) {
|
|
35
|
+
logger.log("No branch name available, skipping Linear updates");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
const {
|
|
30
40
|
removeOldLabels = true,
|
|
31
41
|
addComment = false,
|
|
32
42
|
dryRun = false,
|
|
43
|
+
skipBranches = ["main", "master", "develop", "staging", "production"],
|
|
44
|
+
requireIssueInBranch = true,
|
|
33
45
|
} = pluginConfig;
|
|
34
46
|
|
|
47
|
+
// Check if this branch should be processed
|
|
48
|
+
if (requireIssueInBranch && !shouldProcessBranch(branch.name, skipBranches)) {
|
|
49
|
+
logger.log(
|
|
50
|
+
`Branch "${branch.name}" doesn't contain Linear issues or is in skip list, skipping updates`,
|
|
51
|
+
);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
35
55
|
const client = new LinearClient(linear.apiKey);
|
|
36
56
|
const version = nextRelease.version;
|
|
37
57
|
const channel = nextRelease.channel || "latest";
|
|
@@ -40,23 +60,31 @@ export async function success(
|
|
|
40
60
|
const labelName = `${linear.labelPrefix}${version}`;
|
|
41
61
|
const labelColor = getLabelColor(nextRelease.type as ReleaseType);
|
|
42
62
|
|
|
43
|
-
logger.log(
|
|
63
|
+
logger.log(
|
|
64
|
+
`Updating Linear issues for release ${version} (${channel}) from branch "${branch.name}"`,
|
|
65
|
+
);
|
|
44
66
|
|
|
45
|
-
// Extract issue IDs from
|
|
46
|
-
const issueIds =
|
|
67
|
+
// Extract issue IDs from branch name ONLY
|
|
68
|
+
const issueIds = parseIssuesFromBranch(branch.name, linear.teamKeys);
|
|
47
69
|
|
|
48
70
|
if (issueIds.length === 0) {
|
|
49
|
-
logger.log(
|
|
71
|
+
logger.log(`No Linear issues found in branch name "${branch.name}"`);
|
|
72
|
+
if (requireIssueInBranch) {
|
|
73
|
+
logger.warn(
|
|
74
|
+
"โ ๏ธ Consider using branch names like: feature/ENG-123-description",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
50
77
|
return;
|
|
51
78
|
}
|
|
52
79
|
|
|
53
80
|
logger.log(
|
|
54
|
-
`Found ${issueIds.length} Linear issue(s): ${issueIds.join(", ")}`,
|
|
81
|
+
`Found ${issueIds.length} Linear issue(s) in branch "${branch.name}": ${issueIds.join(", ")}`,
|
|
55
82
|
);
|
|
56
83
|
|
|
57
84
|
if (dryRun) {
|
|
58
85
|
logger.log("[Dry run] Would update issues:", issueIds);
|
|
59
86
|
logger.log(`[Dry run] Would apply label: ${labelName}`);
|
|
87
|
+
logger.log(`[Dry run] Branch: ${branch.name}`);
|
|
60
88
|
return;
|
|
61
89
|
}
|
|
62
90
|
|
|
@@ -72,7 +100,7 @@ export async function success(
|
|
|
72
100
|
const issue = await client.getIssue(issueId);
|
|
73
101
|
|
|
74
102
|
if (!issue) {
|
|
75
|
-
logger.warn(`Issue ${issueId} not found, skipping`);
|
|
103
|
+
logger.warn(`Issue ${issueId} not found in Linear, skipping`);
|
|
76
104
|
return { issueId, status: "not_found" };
|
|
77
105
|
}
|
|
78
106
|
|
|
@@ -86,7 +114,12 @@ export async function success(
|
|
|
86
114
|
|
|
87
115
|
// Add comment if configured
|
|
88
116
|
if (addComment) {
|
|
89
|
-
const comment = formatComment(
|
|
117
|
+
const comment = formatComment(
|
|
118
|
+
version,
|
|
119
|
+
channel,
|
|
120
|
+
nextRelease,
|
|
121
|
+
branch.name,
|
|
122
|
+
);
|
|
90
123
|
await client.addComment(issue.id, comment);
|
|
91
124
|
}
|
|
92
125
|
|
|
@@ -144,11 +177,13 @@ function formatComment(
|
|
|
144
177
|
version: string,
|
|
145
178
|
channel: string,
|
|
146
179
|
release: SuccessContext["nextRelease"],
|
|
180
|
+
branchName: string,
|
|
147
181
|
): string {
|
|
148
182
|
const emoji = channel === "latest" ? "๐" : "๐ฌ";
|
|
149
183
|
const channelText = channel === "latest" ? "stable" : channel;
|
|
150
184
|
|
|
151
185
|
let comment = `${emoji} **Released in \`v${version}\`** (${channelText})\n\n`;
|
|
186
|
+
comment += `๐ Released from branch: \`${branchName}\`\n\n`;
|
|
152
187
|
|
|
153
188
|
const githubRepo = process.env.GITHUB_REPOSITORY;
|
|
154
189
|
if (release.gitTag && githubRepo) {
|
package/src/types.ts
CHANGED
|
@@ -16,6 +16,12 @@ export interface PluginConfig {
|
|
|
16
16
|
|
|
17
17
|
/** Preview changes without updating Linear (default: false) */
|
|
18
18
|
dryRun?: boolean;
|
|
19
|
+
|
|
20
|
+
/** Branches to skip unless they contain issue IDs (default: ["main", "master", "develop", "staging", "production"]) */
|
|
21
|
+
skipBranches?: string[];
|
|
22
|
+
|
|
23
|
+
/** Require issues in branch name to process (default: true) */
|
|
24
|
+
requireIssueInBranch?: boolean;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export interface LinearContext {
|