semantic-release-linear-app 0.3.0-next.2 โ 0.3.0-next.4
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 +24 -225
- package/dist/lib/linear-client.d.ts +1 -1
- package/dist/lib/linear-client.js +5 -5
- package/dist/lib/parse-issues.d.ts +0 -7
- package/dist/lib/parse-issues.js +2 -25
- package/dist/lib/parse-issues.test.js +13 -26
- package/dist/lib/success.d.ts +2 -2
- package/dist/lib/success.js +52 -47
- package/dist/lib/verify.d.ts +2 -2
- package/dist/lib/verify.js +7 -7
- package/dist/lib/verify.test.js +11 -11
- package/dist/types.d.ts +1 -10
- package/package.json +12 -13
- package/src/lib/linear-client.ts +10 -21
- package/src/lib/parse-issues.test.ts +14 -35
- package/src/lib/parse-issues.ts +2 -29
- package/src/lib/success.ts +63 -72
- package/src/lib/verify.test.ts +14 -16
- package/src/lib/verify.ts +16 -16
- package/src/types/semantic-release-error.d.ts +1 -1
- package/src/types.ts +7 -19
package/README.md
CHANGED
|
@@ -1,262 +1,61 @@
|
|
|
1
|
-
# semantic-release-linear
|
|
1
|
+
# semantic-release-linear-app
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
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
|
-
- โก Batch operations for efficiency
|
|
13
|
-
- ๐ Full TypeScript support
|
|
3
|
+
A [semantic-release](https://github.com/semantic-release/semantic-release) plugin that adds version labels to Linear issues based on branch names.
|
|
14
4
|
|
|
15
5
|
## Install
|
|
16
6
|
|
|
17
7
|
```bash
|
|
18
|
-
npm install --save-dev semantic-release-linear
|
|
8
|
+
npm install --save-dev semantic-release-linear-app
|
|
19
9
|
```
|
|
20
10
|
|
|
21
|
-
##
|
|
11
|
+
## Setup
|
|
22
12
|
|
|
23
|
-
1.
|
|
13
|
+
1. Add to your `.releaserc.json`:
|
|
24
14
|
|
|
25
15
|
```json
|
|
26
16
|
{
|
|
27
17
|
"plugins": [
|
|
28
18
|
"@semantic-release/commit-analyzer",
|
|
29
19
|
"@semantic-release/release-notes-generator",
|
|
30
|
-
["semantic-release-linear", {
|
|
31
|
-
"teamKeys": ["ENG", "FEAT"
|
|
20
|
+
["semantic-release-linear-app", {
|
|
21
|
+
"teamKeys": ["ENG", "FEAT"]
|
|
32
22
|
}],
|
|
33
23
|
"@semantic-release/github"
|
|
34
24
|
]
|
|
35
25
|
}
|
|
36
26
|
```
|
|
37
27
|
|
|
38
|
-
2.
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
export LINEAR_API_KEY=lin_api_xxx
|
|
42
|
-
```
|
|
28
|
+
2. Set `LINEAR_API_KEY` environment variable (create at Linear Settings > API)
|
|
43
29
|
|
|
44
|
-
3.
|
|
30
|
+
3. Use branch names with Linear issue IDs:
|
|
45
31
|
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
32
|
+
```
|
|
33
|
+
feature/ENG-123-add-auth
|
|
34
|
+
bugfix/FEAT-456-fix-bug
|
|
35
|
+
ENG-789
|
|
50
36
|
```
|
|
51
37
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
## Configuration
|
|
55
|
-
|
|
56
|
-
### Options
|
|
38
|
+
## Options
|
|
57
39
|
|
|
58
40
|
| Option | Default | Description |
|
|
59
41
|
|--------|---------|-------------|
|
|
60
|
-
| `
|
|
61
|
-
| `teamKeys` | `[]` | Team keys to filter (e.g., `["ENG", "FEAT"]`) |
|
|
42
|
+
| `teamKeys` | `[]` | Team keys to filter (e.g., `["ENG"]`) |
|
|
62
43
|
| `labelPrefix` | `"v"` | Prefix for version labels |
|
|
63
44
|
| `removeOldLabels` | `true` | Remove previous version labels |
|
|
64
|
-
| `addComment` | `false` | Add
|
|
65
|
-
| `skipBranches` | `["main", "master", "develop", "staging", "production"]` | Branches to skip unless they contain issues |
|
|
66
|
-
| `requireIssueInBranch` | `true` | Only process branches with Linear issues |
|
|
45
|
+
| `addComment` | `false` | Add release comment to issues |
|
|
67
46
|
| `dryRun` | `false` | Preview without making changes |
|
|
68
47
|
|
|
69
|
-
|
|
70
|
-
|
|
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 {
|
|
83
|
-
plugins: [
|
|
84
|
-
'@semantic-release/commit-analyzer',
|
|
85
|
-
'@semantic-release/release-notes-generator',
|
|
86
|
-
['semantic-release-linear', config],
|
|
87
|
-
'@semantic-release/github'
|
|
88
|
-
]
|
|
89
|
-
};
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Branch Naming Convention
|
|
93
|
-
|
|
94
|
-
### Recommended Patterns
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
# Feature branches
|
|
98
|
-
feature/ENG-123-user-authentication
|
|
99
|
-
feature/FEAT-456-payment-integration
|
|
100
|
-
|
|
101
|
-
# Bug fixes
|
|
102
|
-
bugfix/BUG-789-login-error
|
|
103
|
-
fix/ENG-101-memory-leak
|
|
104
|
-
|
|
105
|
-
# Hotfixes
|
|
106
|
-
hotfix/ENG-102-critical-security-patch
|
|
107
|
-
|
|
108
|
-
# Simple format (still works)
|
|
109
|
-
ENG-103-quick-update
|
|
110
|
-
FEAT-104
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Multiple Issues in One Branch
|
|
114
|
-
|
|
115
|
-
If you need to update multiple issues, include them all in the branch name:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
feature/ENG-123-FEAT-456-combined-update
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Enforce Branch Naming
|
|
122
|
-
|
|
123
|
-
Use Git hooks or CI checks to enforce the naming convention:
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
#!/bin/bash
|
|
127
|
-
# .git/hooks/pre-push or CI script
|
|
128
|
-
|
|
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
|
|
137
|
-
```
|
|
138
|
-
|
|
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
|
|
48
|
+
## Labels
|
|
148
49
|
|
|
149
|
-
Labels are
|
|
50
|
+
Labels are created based on version and channel:
|
|
150
51
|
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
- ๐ข **Patch** (bug fixes) - Green
|
|
154
|
-
- ๐ฃ **Prerelease** - Purple
|
|
52
|
+
- Default channel: `v1.2.3`
|
|
53
|
+
- Beta/next channels: `v1.2.3-beta`, `v1.2.3-next`
|
|
155
54
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
Test what will happen without making changes:
|
|
159
|
-
|
|
160
|
-
```javascript
|
|
161
|
-
{
|
|
162
|
-
"plugins": [
|
|
163
|
-
["semantic-release-linear", {
|
|
164
|
-
"dryRun": true
|
|
165
|
-
}]
|
|
166
|
-
]
|
|
167
|
-
}
|
|
168
|
-
```
|
|
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
|
-
|
|
177
|
-
## Linear API Setup
|
|
178
|
-
|
|
179
|
-
1. Go to **Linear Settings** โ **API** โ **Personal API keys**
|
|
180
|
-
2. Create a new key with **write** access
|
|
181
|
-
3. Add to your CI environment as `LINEAR_API_KEY`
|
|
182
|
-
|
|
183
|
-
## CI Examples
|
|
184
|
-
|
|
185
|
-
### GitHub Actions
|
|
186
|
-
|
|
187
|
-
```yaml
|
|
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
|
|
214
|
-
```
|
|
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
|
-
|
|
245
|
-
## Troubleshooting
|
|
246
|
-
|
|
247
|
-
### Issues not being detected
|
|
248
|
-
|
|
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
|
|
253
|
-
|
|
254
|
-
### API errors
|
|
255
|
-
|
|
256
|
-
- Verify API key has write access
|
|
257
|
-
- Check Linear workspace permissions
|
|
258
|
-
- Ensure network connectivity in CI
|
|
55
|
+
Colors indicate release type: red (major), orange (minor), green (patch), purple (prerelease).
|
|
259
56
|
|
|
260
57
|
## License
|
|
261
58
|
|
|
262
|
-
MIT
|
|
59
|
+
MIT
|
|
60
|
+
|
|
61
|
+
<!-- SD-1135 e2e test v2 -->
|
|
@@ -10,7 +10,7 @@ const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
|
10
10
|
*/
|
|
11
11
|
class LinearClient {
|
|
12
12
|
apiKey;
|
|
13
|
-
apiUrl =
|
|
13
|
+
apiUrl = 'https://api.linear.app/graphql';
|
|
14
14
|
constructor(apiKey) {
|
|
15
15
|
this.apiKey = apiKey;
|
|
16
16
|
}
|
|
@@ -19,10 +19,10 @@ class LinearClient {
|
|
|
19
19
|
*/
|
|
20
20
|
async query(query, variables = {}) {
|
|
21
21
|
const response = await (0, node_fetch_1.default)(this.apiUrl, {
|
|
22
|
-
method:
|
|
22
|
+
method: 'POST',
|
|
23
23
|
headers: {
|
|
24
24
|
Authorization: this.apiKey,
|
|
25
|
-
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
26
|
},
|
|
27
27
|
body: JSON.stringify({ query, variables }),
|
|
28
28
|
});
|
|
@@ -31,7 +31,7 @@ class LinearClient {
|
|
|
31
31
|
throw new Error(`Linear API error: ${data.errors[0].message}`);
|
|
32
32
|
}
|
|
33
33
|
if (!data.data) {
|
|
34
|
-
throw new Error(
|
|
34
|
+
throw new Error('No data returned from Linear API');
|
|
35
35
|
}
|
|
36
36
|
return data.data;
|
|
37
37
|
}
|
|
@@ -53,7 +53,7 @@ class LinearClient {
|
|
|
53
53
|
/**
|
|
54
54
|
* Find or create a label
|
|
55
55
|
*/
|
|
56
|
-
async ensureLabel(name, color =
|
|
56
|
+
async ensureLabel(name, color = '#4752C4') {
|
|
57
57
|
// First, try to find existing label
|
|
58
58
|
const searchQuery = `
|
|
59
59
|
query FindLabel($name: String!) {
|
|
@@ -9,10 +9,3 @@
|
|
|
9
9
|
* @returns Array of unique issue identifiers
|
|
10
10
|
*/
|
|
11
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
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.parseIssuesFromBranch = parseIssuesFromBranch;
|
|
8
|
-
exports.shouldProcessBranch = shouldProcessBranch;
|
|
9
8
|
/**
|
|
10
9
|
* Extract Linear issue IDs from a branch name
|
|
11
10
|
* @param branchName - The branch name to parse
|
|
@@ -15,34 +14,12 @@ exports.shouldProcessBranch = shouldProcessBranch;
|
|
|
15
14
|
function parseIssuesFromBranch(branchName, teamKeys = null) {
|
|
16
15
|
const issues = new Set();
|
|
17
16
|
// Build regex pattern based on team keys
|
|
18
|
-
const teamPattern = teamKeys ? `(?:${teamKeys.join(
|
|
17
|
+
const teamPattern = teamKeys ? `(?:${teamKeys.join('|')})` : '[A-Z]+';
|
|
19
18
|
// Pattern matches: feature/ENG-123-description, ENG-123, bugfix/FEAT-45, etc.
|
|
20
|
-
const issuePattern = new RegExp(`\\b(${teamPattern}-\\d+)\\b`,
|
|
19
|
+
const issuePattern = new RegExp(`\\b(${teamPattern}-\\d+)\\b`, 'gi');
|
|
21
20
|
const matches = Array.from(branchName.matchAll(issuePattern));
|
|
22
21
|
for (const match of matches) {
|
|
23
22
|
issues.add(match[1].toUpperCase());
|
|
24
23
|
}
|
|
25
24
|
return Array.from(issues);
|
|
26
25
|
}
|
|
27
|
-
/**
|
|
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
|
|
32
|
-
*/
|
|
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);
|
|
48
|
-
}
|
|
@@ -1,39 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const parse_issues_1 = require("./parse-issues");
|
|
4
|
-
describe(
|
|
5
|
-
test(
|
|
6
|
-
const branchName =
|
|
4
|
+
describe('parse-issues', () => {
|
|
5
|
+
test('extracts Linear issue IDs from branch name', () => {
|
|
6
|
+
const branchName = 'feature/ENG-123-add-new-feature';
|
|
7
7
|
const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName);
|
|
8
|
-
expect(result).toEqual([
|
|
8
|
+
expect(result).toEqual(['ENG-123']);
|
|
9
9
|
});
|
|
10
|
-
test(
|
|
11
|
-
const branchName =
|
|
10
|
+
test('extracts multiple issue IDs from branch name', () => {
|
|
11
|
+
const branchName = 'fix/ENG-123-FEAT-456-bug-fix';
|
|
12
12
|
const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName);
|
|
13
|
-
expect(result).toEqual(expect.arrayContaining([
|
|
13
|
+
expect(result).toEqual(expect.arrayContaining(['ENG-123', 'FEAT-456']));
|
|
14
14
|
expect(result).toHaveLength(2);
|
|
15
15
|
});
|
|
16
|
-
test(
|
|
17
|
-
const branchName =
|
|
18
|
-
const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName, [
|
|
19
|
-
expect(result).toEqual([
|
|
16
|
+
test('filters by team keys when provided', () => {
|
|
17
|
+
const branchName = 'feature/ENG-123-OTHER-456';
|
|
18
|
+
const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName, ['ENG']);
|
|
19
|
+
expect(result).toEqual(['ENG-123']);
|
|
20
20
|
});
|
|
21
|
-
test(
|
|
22
|
-
const branchName =
|
|
21
|
+
test('returns empty array for branch without issues', () => {
|
|
22
|
+
const branchName = 'feature/no-issues-here';
|
|
23
23
|
const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName);
|
|
24
24
|
expect(result).toEqual([]);
|
|
25
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
|
-
});
|
|
39
26
|
});
|
package/dist/lib/success.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SuccessContext } from
|
|
2
|
-
import { PluginConfig, LinearContext } from
|
|
1
|
+
import { SuccessContext } from 'semantic-release';
|
|
2
|
+
import { PluginConfig, LinearContext } from '../types';
|
|
3
3
|
interface ExtendedContext extends SuccessContext {
|
|
4
4
|
linear?: LinearContext;
|
|
5
5
|
}
|
package/dist/lib/success.js
CHANGED
|
@@ -9,31 +9,33 @@ const parse_issues_1 = require("./parse-issues");
|
|
|
9
9
|
*/
|
|
10
10
|
async function findSourceBranches(commits, logger) {
|
|
11
11
|
const branches = new Set();
|
|
12
|
+
const skipBranches = ['main', 'master', 'develop', 'stable', 'HEAD'];
|
|
12
13
|
if (commits.length === 0)
|
|
13
14
|
return branches;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
branches.add(match[1]);
|
|
31
|
-
logger.log(`Found source branch: ${match[1]}`);
|
|
15
|
+
// Check all commits to find all source branches
|
|
16
|
+
for (const commit of commits) {
|
|
17
|
+
try {
|
|
18
|
+
const { stdout } = await (0, execa_1.execa)('git', [
|
|
19
|
+
'branch',
|
|
20
|
+
'-r',
|
|
21
|
+
'--contains',
|
|
22
|
+
commit.hash,
|
|
23
|
+
'--merged',
|
|
24
|
+
]);
|
|
25
|
+
const branchLines = stdout.split('\n').map((b) => b.trim());
|
|
26
|
+
for (const line of branchLines) {
|
|
27
|
+
const match = line.match(/origin\/(.+)/);
|
|
28
|
+
if (match && !skipBranches.includes(match[1])) {
|
|
29
|
+
branches.add(match[1]);
|
|
30
|
+
}
|
|
32
31
|
}
|
|
33
32
|
}
|
|
33
|
+
catch {
|
|
34
|
+
// Commit might not exist or other git error, continue with next commit
|
|
35
|
+
}
|
|
34
36
|
}
|
|
35
|
-
|
|
36
|
-
logger.
|
|
37
|
+
if (branches.size > 0) {
|
|
38
|
+
logger.log(`Found source branches: ${Array.from(branches).join(', ')}`);
|
|
37
39
|
}
|
|
38
40
|
return branches;
|
|
39
41
|
}
|
|
@@ -43,18 +45,18 @@ async function findSourceBranches(commits, logger) {
|
|
|
43
45
|
async function success(pluginConfig, context) {
|
|
44
46
|
const { logger, nextRelease, linear, commits } = context;
|
|
45
47
|
if (!linear) {
|
|
46
|
-
logger.log(
|
|
48
|
+
logger.log('Linear context not found, skipping issue updates');
|
|
47
49
|
return;
|
|
48
50
|
}
|
|
49
51
|
if (!commits || commits.length === 0) {
|
|
50
|
-
logger.log(
|
|
52
|
+
logger.log('No commits found in release, skipping Linear updates');
|
|
51
53
|
return;
|
|
52
54
|
}
|
|
53
|
-
const { removeOldLabels = true, addComment = false, dryRun = false
|
|
55
|
+
const { removeOldLabels = true, addComment = false, dryRun = false } = pluginConfig;
|
|
54
56
|
// Find all branches that contributed to this release
|
|
55
57
|
const sourceBranches = await findSourceBranches(commits, logger);
|
|
56
58
|
if (sourceBranches.size === 0) {
|
|
57
|
-
logger.log(
|
|
59
|
+
logger.log('No source branches found, skipping Linear updates');
|
|
58
60
|
return;
|
|
59
61
|
}
|
|
60
62
|
// Extract Linear issue IDs from all found branches
|
|
@@ -64,21 +66,24 @@ async function success(pluginConfig, context) {
|
|
|
64
66
|
branchIssues.forEach((id) => issueIds.add(id));
|
|
65
67
|
}
|
|
66
68
|
if (issueIds.size === 0) {
|
|
67
|
-
logger.log(`No Linear issues found in branches: ${Array.from(sourceBranches).join(
|
|
69
|
+
logger.log(`No Linear issues found in branches: ${Array.from(sourceBranches).join(', ')}`);
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
70
|
-
logger.log(`Found ${issueIds.size} Linear issue(s): ${Array.from(issueIds).join(
|
|
72
|
+
logger.log(`Found ${issueIds.size} Linear issue(s): ${Array.from(issueIds).join(', ')} ` +
|
|
71
73
|
`from ${sourceBranches.size} branch(es)`);
|
|
74
|
+
// Build label name with optional channel suffix
|
|
75
|
+
const version = nextRelease.version;
|
|
76
|
+
const channel = nextRelease.channel;
|
|
77
|
+
const labelName = channel
|
|
78
|
+
? `${linear.labelPrefix}${version}-${channel}`
|
|
79
|
+
: `${linear.labelPrefix}${version}`;
|
|
72
80
|
if (dryRun) {
|
|
73
|
-
logger.log(
|
|
74
|
-
logger.log(`[Dry run] Would apply label: ${
|
|
81
|
+
logger.log('[Dry run] Would update issues:', Array.from(issueIds));
|
|
82
|
+
logger.log(`[Dry run] Would apply label: ${labelName}`);
|
|
75
83
|
return;
|
|
76
84
|
}
|
|
77
85
|
// Initialize Linear client and prepare label
|
|
78
86
|
const client = new linear_client_1.LinearClient(linear.apiKey);
|
|
79
|
-
const version = nextRelease.version;
|
|
80
|
-
const channel = nextRelease.channel || "latest";
|
|
81
|
-
const labelName = `${linear.labelPrefix}${version}`;
|
|
82
87
|
const labelColor = getLabelColor(nextRelease.type);
|
|
83
88
|
// Ensure the version label exists
|
|
84
89
|
const label = await client.ensureLabel(labelName, labelColor);
|
|
@@ -89,7 +94,7 @@ async function success(pluginConfig, context) {
|
|
|
89
94
|
const issue = await client.getIssue(issueId);
|
|
90
95
|
if (!issue) {
|
|
91
96
|
logger.warn(`Issue ${issueId} not found in Linear`);
|
|
92
|
-
return { issueId, status:
|
|
97
|
+
return { issueId, status: 'not_found' };
|
|
93
98
|
}
|
|
94
99
|
// Remove old version labels if configured
|
|
95
100
|
if (removeOldLabels) {
|
|
@@ -99,24 +104,24 @@ async function success(pluginConfig, context) {
|
|
|
99
104
|
await client.addLabelToIssue(issue.id, label.id);
|
|
100
105
|
// Add comment if configured
|
|
101
106
|
if (addComment) {
|
|
102
|
-
const emoji = channel
|
|
103
|
-
const channelText = channel
|
|
107
|
+
const emoji = channel ? '๐ฌ' : '๐';
|
|
108
|
+
const channelText = channel ? ` (${channel} channel)` : '';
|
|
104
109
|
const comment = `${emoji} Released in version ${version}${channelText}`;
|
|
105
110
|
await client.addComment(issue.id, comment);
|
|
106
111
|
}
|
|
107
112
|
logger.log(`โ Updated issue ${issueId}`);
|
|
108
|
-
return { issueId, status:
|
|
113
|
+
return { issueId, status: 'updated' };
|
|
109
114
|
}
|
|
110
115
|
catch (error) {
|
|
111
116
|
const message = error instanceof Error ? error.message : String(error);
|
|
112
117
|
logger.error(`Failed to update issue ${issueId}: ${message}`);
|
|
113
|
-
return { issueId, status:
|
|
118
|
+
return { issueId, status: 'failed', error: message };
|
|
114
119
|
}
|
|
115
120
|
}));
|
|
116
121
|
// Log summary
|
|
117
|
-
const updated = results.filter((r) => r.status ===
|
|
118
|
-
const failed = results.filter((r) => r.status ===
|
|
119
|
-
const notFound = results.filter((r) => r.status ===
|
|
122
|
+
const updated = results.filter((r) => r.status === 'fulfilled' && r.value?.status === 'updated').length;
|
|
123
|
+
const failed = results.filter((r) => r.status === 'rejected' || r.value?.status === 'failed').length;
|
|
124
|
+
const notFound = results.filter((r) => r.status === 'fulfilled' && r.value?.status === 'not_found').length;
|
|
120
125
|
logger.log(`Linear update complete: ${updated} updated, ${failed} failed, ${notFound} not found`);
|
|
121
126
|
}
|
|
122
127
|
/**
|
|
@@ -124,13 +129,13 @@ async function success(pluginConfig, context) {
|
|
|
124
129
|
*/
|
|
125
130
|
function getLabelColor(releaseType) {
|
|
126
131
|
const colors = {
|
|
127
|
-
major:
|
|
128
|
-
premajor:
|
|
129
|
-
minor:
|
|
130
|
-
preminor:
|
|
131
|
-
patch:
|
|
132
|
-
prepatch:
|
|
133
|
-
prerelease:
|
|
132
|
+
major: '#F44336', // Red for breaking changes
|
|
133
|
+
premajor: '#E91E63', // Pink for pre-major
|
|
134
|
+
minor: '#FF9800', // Orange for new features
|
|
135
|
+
preminor: '#FFC107', // Amber for pre-minor
|
|
136
|
+
patch: '#4CAF50', // Green for fixes
|
|
137
|
+
prepatch: '#8BC34A', // Light green for pre-patch
|
|
138
|
+
prerelease: '#9C27B0', // Purple for prereleases
|
|
134
139
|
};
|
|
135
|
-
return colors[releaseType] ||
|
|
140
|
+
return colors[releaseType] || '#4752C4'; // Default blue
|
|
136
141
|
}
|
package/dist/lib/verify.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { VerifyConditionsContext } from
|
|
2
|
-
import { PluginConfig, LinearContext } from
|
|
1
|
+
import { VerifyConditionsContext } from 'semantic-release';
|
|
2
|
+
import { PluginConfig, LinearContext } from '../types';
|
|
3
3
|
/**
|
|
4
4
|
* Verify the plugin configuration and Linear API access
|
|
5
5
|
*/
|