semantic-release-linear-app 0.3.0-next.2 โ 0.3.0-next.3
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 +40 -40
- 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/types.d.ts +1 -10
- package/package.json +12 -13
- 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/types.ts +7 -19
package/README.md
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
# semantic-release-linear
|
|
1
|
+
# semantic-release-linear-app
|
|
2
2
|
|
|
3
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
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- ๐ Full TypeScript support
|
|
7
|
+
- Extracts Linear issue IDs from branch names
|
|
8
|
+
- Adds version labels to Linear issues (e.g., `v1.2.3` or `v1.2.3-beta`)
|
|
9
|
+
- Color-coded labels based on release type
|
|
10
|
+
- Supports multiple release channels (beta, next, stable, etc.)
|
|
11
|
+
- Removes old version labels (configurable)
|
|
12
|
+
- Adds release comments to issues (optional)
|
|
14
13
|
|
|
15
14
|
## Install
|
|
16
15
|
|
|
17
16
|
```bash
|
|
18
|
-
npm install --save-dev semantic-release-linear
|
|
17
|
+
npm install --save-dev semantic-release-linear-app
|
|
19
18
|
```
|
|
20
19
|
|
|
21
20
|
## Quick Start
|
|
@@ -27,7 +26,7 @@ npm install --save-dev semantic-release-linear
|
|
|
27
26
|
"plugins": [
|
|
28
27
|
"@semantic-release/commit-analyzer",
|
|
29
28
|
"@semantic-release/release-notes-generator",
|
|
30
|
-
["semantic-release-linear", {
|
|
29
|
+
["semantic-release-linear-app", {
|
|
31
30
|
"teamKeys": ["ENG", "FEAT", "BUG"]
|
|
32
31
|
}],
|
|
33
32
|
"@semantic-release/github"
|
|
@@ -62,28 +61,25 @@ That's it! When semantic-release creates a release from these branches, the corr
|
|
|
62
61
|
| `labelPrefix` | `"v"` | Prefix for version labels |
|
|
63
62
|
| `removeOldLabels` | `true` | Remove previous version labels |
|
|
64
63
|
| `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
64
|
| `dryRun` | `false` | Preview without making changes |
|
|
68
65
|
|
|
69
66
|
### TypeScript Configuration
|
|
70
67
|
|
|
71
68
|
```typescript
|
|
72
69
|
// .releaserc.ts
|
|
73
|
-
import type { PluginConfig } from 'semantic-release-linear';
|
|
70
|
+
import type { PluginConfig } from 'semantic-release-linear-app';
|
|
74
71
|
|
|
75
72
|
const config: PluginConfig = {
|
|
76
73
|
teamKeys: ['ENG', 'FEAT'],
|
|
77
|
-
labelPrefix: '
|
|
78
|
-
addComment: true
|
|
79
|
-
skipBranches: ['main', 'develop']
|
|
74
|
+
labelPrefix: 'v',
|
|
75
|
+
addComment: true
|
|
80
76
|
};
|
|
81
77
|
|
|
82
78
|
export default {
|
|
83
79
|
plugins: [
|
|
84
80
|
'@semantic-release/commit-analyzer',
|
|
85
81
|
'@semantic-release/release-notes-generator',
|
|
86
|
-
['semantic-release-linear', config],
|
|
82
|
+
['semantic-release-linear-app', config],
|
|
87
83
|
'@semantic-release/github'
|
|
88
84
|
]
|
|
89
85
|
};
|
|
@@ -138,29 +134,41 @@ fi
|
|
|
138
134
|
|
|
139
135
|
## How It Works
|
|
140
136
|
|
|
141
|
-
1. **
|
|
142
|
-
2. **
|
|
143
|
-
3. **
|
|
144
|
-
4. **
|
|
145
|
-
5. **
|
|
137
|
+
1. **Commit Analysis**: When semantic-release creates a release, the plugin scans all commits
|
|
138
|
+
2. **Branch Detection**: Finds all source branches that contributed commits to this release
|
|
139
|
+
3. **Issue Extraction**: Extracts Linear issue IDs (e.g., `ENG-123`) from branch names
|
|
140
|
+
4. **Label Creation**: Creates a version label (e.g., `v1.2.3` or `v1.2.3-beta`)
|
|
141
|
+
5. **Issue Update**: Applies the label to the Linear issue(s)
|
|
142
|
+
6. **Cleanup**: Optionally removes old version labels
|
|
143
|
+
|
|
144
|
+
### Channel Labels
|
|
145
|
+
|
|
146
|
+
When releasing to different channels, labels include the channel suffix:
|
|
147
|
+
|
|
148
|
+
| Channel | Label Example |
|
|
149
|
+
|---------|---------------|
|
|
150
|
+
| Default (latest) | `v1.2.3` |
|
|
151
|
+
| beta | `v1.2.3-beta` |
|
|
152
|
+
| next | `v1.2.3-next` |
|
|
153
|
+
| alpha | `v1.2.3-alpha` |
|
|
146
154
|
|
|
147
155
|
### Label Colors
|
|
148
156
|
|
|
149
157
|
Labels are color-coded by release type:
|
|
150
158
|
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
159
|
+
- **Major** (breaking changes) - Red
|
|
160
|
+
- **Minor** (new features) - Orange
|
|
161
|
+
- **Patch** (bug fixes) - Green
|
|
162
|
+
- **Prerelease** - Purple
|
|
155
163
|
|
|
156
164
|
## Dry Run Mode
|
|
157
165
|
|
|
158
166
|
Test what will happen without making changes:
|
|
159
167
|
|
|
160
|
-
```
|
|
168
|
+
```json
|
|
161
169
|
{
|
|
162
170
|
"plugins": [
|
|
163
|
-
["semantic-release-linear", {
|
|
171
|
+
["semantic-release-linear-app", {
|
|
164
172
|
"dryRun": true
|
|
165
173
|
}]
|
|
166
174
|
]
|
|
@@ -169,9 +177,9 @@ Test what will happen without making changes:
|
|
|
169
177
|
|
|
170
178
|
Output:
|
|
171
179
|
```
|
|
172
|
-
[semantic-release
|
|
173
|
-
[semantic-release
|
|
174
|
-
[semantic-release
|
|
180
|
+
[semantic-release-linear-app] Found 1 Linear issue(s): ENG-123 from 1 branch(es)
|
|
181
|
+
[semantic-release-linear-app] [Dry run] Would update issues: ["ENG-123"]
|
|
182
|
+
[semantic-release-linear-app] [Dry run] Would apply label: v1.2.3
|
|
175
183
|
```
|
|
176
184
|
|
|
177
185
|
## Linear API Setup
|
|
@@ -223,14 +231,6 @@ Extracting from commits is unreliable - developers forget, commits get squashed,
|
|
|
223
231
|
- Visible in PR lists
|
|
224
232
|
- A single source of truth
|
|
225
233
|
|
|
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
234
|
### Can I update multiple issues?
|
|
235
235
|
|
|
236
236
|
Yes, include multiple issue IDs in your branch name:
|
|
@@ -240,7 +240,7 @@ feature/ENG-123-FEAT-456-big-feature
|
|
|
240
240
|
|
|
241
241
|
### What if my branch doesn't have an issue?
|
|
242
242
|
|
|
243
|
-
The plugin will skip
|
|
243
|
+
The plugin will skip branches without Linear issue IDs. This is intentional - not every release needs to update Linear.
|
|
244
244
|
|
|
245
245
|
## Troubleshooting
|
|
246
246
|
|
|
@@ -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/types.d.ts
CHANGED
|
@@ -11,10 +11,6 @@ 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;
|
|
18
14
|
}
|
|
19
15
|
export interface LinearContext {
|
|
20
16
|
apiKey: string;
|
|
@@ -34,13 +30,8 @@ export interface LinearLabel {
|
|
|
34
30
|
name: string;
|
|
35
31
|
color?: string;
|
|
36
32
|
}
|
|
37
|
-
export interface IssueUpdateResult {
|
|
38
|
-
issueId: string;
|
|
39
|
-
status: "updated" | "failed" | "not_found";
|
|
40
|
-
error?: string;
|
|
41
|
-
}
|
|
42
33
|
export interface LinearViewer {
|
|
43
34
|
id: string;
|
|
44
35
|
name: string;
|
|
45
36
|
}
|
|
46
|
-
export type ReleaseType =
|
|
37
|
+
export type ReleaseType = 'major' | 'premajor' | 'minor' | 'preminor' | 'patch' | 'prepatch' | 'prerelease';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "semantic-release-linear-app",
|
|
3
|
-
"version": "0.3.0-next.
|
|
3
|
+
"version": "0.3.0-next.3",
|
|
4
4
|
"description": "Semantic-release plugin to update Linear issues with version labels",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -46,29 +46,28 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@semantic-release/error": "^4.0.0",
|
|
49
|
-
"execa": "^9.6.
|
|
49
|
+
"execa": "^9.6.1",
|
|
50
50
|
"node-fetch": "^3.3.2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@semantic-release/changelog": "^6.0.3",
|
|
54
53
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
55
54
|
"@semantic-release/git": "^10.0.1",
|
|
56
|
-
"@semantic-release/github": "^
|
|
57
|
-
"@semantic-release/npm": "^
|
|
55
|
+
"@semantic-release/github": "^12.0.2",
|
|
56
|
+
"@semantic-release/npm": "^13.1.3",
|
|
58
57
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
59
58
|
"@types/jest": "^30.0.0",
|
|
60
|
-
"@types/node": "^
|
|
59
|
+
"@types/node": "^25.0.1",
|
|
61
60
|
"@types/node-fetch": "^2.6.13",
|
|
62
61
|
"@types/semantic-release": "^21.1.0",
|
|
63
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
64
|
-
"@typescript-eslint/parser": "^8.
|
|
65
|
-
"eslint": "^9.
|
|
62
|
+
"@typescript-eslint/eslint-plugin": "^8.49.0",
|
|
63
|
+
"@typescript-eslint/parser": "^8.49.0",
|
|
64
|
+
"eslint": "^9.39.2",
|
|
66
65
|
"husky": "^9.1.7",
|
|
67
66
|
"jest": "^30.2.0",
|
|
68
|
-
"lint-staged": "^16.2.
|
|
69
|
-
"prettier": "^3.
|
|
70
|
-
"semantic-release": "^
|
|
71
|
-
"ts-jest": "^29.4.
|
|
67
|
+
"lint-staged": "^16.2.7",
|
|
68
|
+
"prettier": "^3.7.4",
|
|
69
|
+
"semantic-release": "^25.0.2",
|
|
70
|
+
"ts-jest": "^29.4.6",
|
|
72
71
|
"typescript": "^5.9.3"
|
|
73
72
|
},
|
|
74
73
|
"files": [
|
|
@@ -1,49 +1,28 @@
|
|
|
1
|
-
import { parseIssuesFromBranch
|
|
1
|
+
import { parseIssuesFromBranch } from './parse-issues';
|
|
2
2
|
|
|
3
|
-
describe(
|
|
4
|
-
test(
|
|
5
|
-
const branchName =
|
|
3
|
+
describe('parse-issues', () => {
|
|
4
|
+
test('extracts Linear issue IDs from branch name', () => {
|
|
5
|
+
const branchName = 'feature/ENG-123-add-new-feature';
|
|
6
6
|
const result = parseIssuesFromBranch(branchName);
|
|
7
|
-
expect(result).toEqual([
|
|
7
|
+
expect(result).toEqual(['ENG-123']);
|
|
8
8
|
});
|
|
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 = 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
16
|
|
|
17
|
-
test(
|
|
18
|
-
const branchName =
|
|
19
|
-
const result = parseIssuesFromBranch(branchName, [
|
|
20
|
-
expect(result).toEqual([
|
|
17
|
+
test('filters by team keys when provided', () => {
|
|
18
|
+
const branchName = 'feature/ENG-123-OTHER-456';
|
|
19
|
+
const result = parseIssuesFromBranch(branchName, ['ENG']);
|
|
20
|
+
expect(result).toEqual(['ENG-123']);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
test(
|
|
24
|
-
const branchName =
|
|
23
|
+
test('returns empty array for branch without issues', () => {
|
|
24
|
+
const branchName = 'feature/no-issues-here';
|
|
25
25
|
const result = parseIssuesFromBranch(branchName);
|
|
26
26
|
expect(result).toEqual([]);
|
|
27
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
|
-
});
|
|
49
28
|
});
|
package/src/lib/parse-issues.ts
CHANGED
|
@@ -16,10 +16,10 @@ export function parseIssuesFromBranch(
|
|
|
16
16
|
const issues = new Set<string>();
|
|
17
17
|
|
|
18
18
|
// Build regex pattern based on team keys
|
|
19
|
-
const teamPattern = teamKeys ? `(?:${teamKeys.join(
|
|
19
|
+
const teamPattern = teamKeys ? `(?:${teamKeys.join('|')})` : '[A-Z]+';
|
|
20
20
|
|
|
21
21
|
// Pattern matches: feature/ENG-123-description, ENG-123, bugfix/FEAT-45, etc.
|
|
22
|
-
const issuePattern = new RegExp(`\\b(${teamPattern}-\\d+)\\b`,
|
|
22
|
+
const issuePattern = new RegExp(`\\b(${teamPattern}-\\d+)\\b`, 'gi');
|
|
23
23
|
|
|
24
24
|
const matches = Array.from(branchName.matchAll(issuePattern));
|
|
25
25
|
for (const match of matches) {
|
|
@@ -28,30 +28,3 @@ export function parseIssuesFromBranch(
|
|
|
28
28
|
|
|
29
29
|
return Array.from(issues);
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
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
|
|
37
|
-
*/
|
|
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
|
-
}
|
|
54
|
-
|
|
55
|
-
// Process any other branch that contains an issue ID
|
|
56
|
-
return /[A-Z]+-\d+/.test(branchName);
|
|
57
|
-
}
|
package/src/lib/success.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { SuccessContext } from
|
|
2
|
-
import { execa } from
|
|
3
|
-
import { LinearClient } from
|
|
4
|
-
import { parseIssuesFromBranch } from
|
|
5
|
-
import { PluginConfig, LinearContext, ReleaseType } from
|
|
1
|
+
import { SuccessContext } from 'semantic-release';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import { LinearClient } from './linear-client';
|
|
4
|
+
import { parseIssuesFromBranch } from './parse-issues';
|
|
5
|
+
import { PluginConfig, LinearContext, ReleaseType } from '../types';
|
|
6
6
|
|
|
7
7
|
interface ExtendedContext extends SuccessContext {
|
|
8
8
|
linear?: LinearContext;
|
|
@@ -13,37 +13,38 @@ interface ExtendedContext extends SuccessContext {
|
|
|
13
13
|
*/
|
|
14
14
|
async function findSourceBranches(
|
|
15
15
|
commits: readonly { hash: string }[],
|
|
16
|
-
logger: SuccessContext[
|
|
16
|
+
logger: SuccessContext['logger'],
|
|
17
17
|
): Promise<Set<string>> {
|
|
18
18
|
const branches = new Set<string>();
|
|
19
|
+
const skipBranches = ['main', 'master', 'develop', 'stable', 'HEAD'];
|
|
19
20
|
|
|
20
21
|
if (commits.length === 0) return branches;
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
!["main", "master", "develop", "stable", "HEAD"].includes(match[1])
|
|
40
|
-
) {
|
|
41
|
-
branches.add(match[1]);
|
|
42
|
-
logger.log(`Found source branch: ${match[1]}`);
|
|
23
|
+
// Check all commits to find all source branches
|
|
24
|
+
for (const commit of commits) {
|
|
25
|
+
try {
|
|
26
|
+
const { stdout } = await execa('git', [
|
|
27
|
+
'branch',
|
|
28
|
+
'-r',
|
|
29
|
+
'--contains',
|
|
30
|
+
commit.hash,
|
|
31
|
+
'--merged',
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const branchLines = stdout.split('\n').map((b: string) => b.trim());
|
|
35
|
+
for (const line of branchLines) {
|
|
36
|
+
const match = line.match(/origin\/(.+)/);
|
|
37
|
+
if (match && !skipBranches.includes(match[1])) {
|
|
38
|
+
branches.add(match[1]);
|
|
39
|
+
}
|
|
43
40
|
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Commit might not exist or other git error, continue with next commit
|
|
44
43
|
}
|
|
45
|
-
}
|
|
46
|
-
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (branches.size > 0) {
|
|
47
|
+
logger.log(`Found source branches: ${Array.from(branches).join(', ')}`);
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
return branches;
|
|
@@ -52,33 +53,26 @@ async function findSourceBranches(
|
|
|
52
53
|
/**
|
|
53
54
|
* Update Linear issues after a successful release
|
|
54
55
|
*/
|
|
55
|
-
export async function success(
|
|
56
|
-
pluginConfig: PluginConfig,
|
|
57
|
-
context: ExtendedContext,
|
|
58
|
-
): Promise<void> {
|
|
56
|
+
export async function success(pluginConfig: PluginConfig, context: ExtendedContext): Promise<void> {
|
|
59
57
|
const { logger, nextRelease, linear, commits } = context;
|
|
60
58
|
|
|
61
59
|
if (!linear) {
|
|
62
|
-
logger.log(
|
|
60
|
+
logger.log('Linear context not found, skipping issue updates');
|
|
63
61
|
return;
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
if (!commits || commits.length === 0) {
|
|
67
|
-
logger.log(
|
|
65
|
+
logger.log('No commits found in release, skipping Linear updates');
|
|
68
66
|
return;
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
const {
|
|
72
|
-
removeOldLabels = true,
|
|
73
|
-
addComment = false,
|
|
74
|
-
dryRun = false,
|
|
75
|
-
} = pluginConfig;
|
|
69
|
+
const { removeOldLabels = true, addComment = false, dryRun = false } = pluginConfig;
|
|
76
70
|
|
|
77
71
|
// Find all branches that contributed to this release
|
|
78
72
|
const sourceBranches = await findSourceBranches(commits, logger);
|
|
79
73
|
|
|
80
74
|
if (sourceBranches.size === 0) {
|
|
81
|
-
logger.log(
|
|
75
|
+
logger.log('No source branches found, skipping Linear updates');
|
|
82
76
|
return;
|
|
83
77
|
}
|
|
84
78
|
|
|
@@ -90,30 +84,30 @@ export async function success(
|
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
if (issueIds.size === 0) {
|
|
93
|
-
logger.log(
|
|
94
|
-
`No Linear issues found in branches: ${Array.from(sourceBranches).join(", ")}`,
|
|
95
|
-
);
|
|
87
|
+
logger.log(`No Linear issues found in branches: ${Array.from(sourceBranches).join(', ')}`);
|
|
96
88
|
return;
|
|
97
89
|
}
|
|
98
90
|
|
|
99
91
|
logger.log(
|
|
100
|
-
`Found ${issueIds.size} Linear issue(s): ${Array.from(issueIds).join(
|
|
92
|
+
`Found ${issueIds.size} Linear issue(s): ${Array.from(issueIds).join(', ')} ` +
|
|
101
93
|
`from ${sourceBranches.size} branch(es)`,
|
|
102
94
|
);
|
|
103
95
|
|
|
96
|
+
// Build label name with optional channel suffix
|
|
97
|
+
const version = nextRelease.version;
|
|
98
|
+
const channel = nextRelease.channel;
|
|
99
|
+
const labelName = channel
|
|
100
|
+
? `${linear.labelPrefix}${version}-${channel}`
|
|
101
|
+
: `${linear.labelPrefix}${version}`;
|
|
102
|
+
|
|
104
103
|
if (dryRun) {
|
|
105
|
-
logger.log(
|
|
106
|
-
logger.log(
|
|
107
|
-
`[Dry run] Would apply label: ${linear.labelPrefix}${nextRelease.version}`,
|
|
108
|
-
);
|
|
104
|
+
logger.log('[Dry run] Would update issues:', Array.from(issueIds));
|
|
105
|
+
logger.log(`[Dry run] Would apply label: ${labelName}`);
|
|
109
106
|
return;
|
|
110
107
|
}
|
|
111
108
|
|
|
112
109
|
// Initialize Linear client and prepare label
|
|
113
110
|
const client = new LinearClient(linear.apiKey);
|
|
114
|
-
const version = nextRelease.version;
|
|
115
|
-
const channel = nextRelease.channel || "latest";
|
|
116
|
-
const labelName = `${linear.labelPrefix}${version}`;
|
|
117
111
|
const labelColor = getLabelColor(nextRelease.type as ReleaseType);
|
|
118
112
|
|
|
119
113
|
// Ensure the version label exists
|
|
@@ -127,7 +121,7 @@ export async function success(
|
|
|
127
121
|
const issue = await client.getIssue(issueId);
|
|
128
122
|
if (!issue) {
|
|
129
123
|
logger.warn(`Issue ${issueId} not found in Linear`);
|
|
130
|
-
return { issueId, status:
|
|
124
|
+
return { issueId, status: 'not_found' };
|
|
131
125
|
}
|
|
132
126
|
|
|
133
127
|
// Remove old version labels if configured
|
|
@@ -140,37 +134,34 @@ export async function success(
|
|
|
140
134
|
|
|
141
135
|
// Add comment if configured
|
|
142
136
|
if (addComment) {
|
|
143
|
-
const emoji = channel
|
|
144
|
-
const channelText =
|
|
145
|
-
channel === "latest" ? "" : ` (${channel} channel)`;
|
|
137
|
+
const emoji = channel ? '๐ฌ' : '๐';
|
|
138
|
+
const channelText = channel ? ` (${channel} channel)` : '';
|
|
146
139
|
const comment = `${emoji} Released in version ${version}${channelText}`;
|
|
147
140
|
await client.addComment(issue.id, comment);
|
|
148
141
|
}
|
|
149
142
|
|
|
150
143
|
logger.log(`โ Updated issue ${issueId}`);
|
|
151
|
-
return { issueId, status:
|
|
144
|
+
return { issueId, status: 'updated' };
|
|
152
145
|
} catch (error) {
|
|
153
146
|
const message = error instanceof Error ? error.message : String(error);
|
|
154
147
|
logger.error(`Failed to update issue ${issueId}: ${message}`);
|
|
155
|
-
return { issueId, status:
|
|
148
|
+
return { issueId, status: 'failed', error: message };
|
|
156
149
|
}
|
|
157
150
|
}),
|
|
158
151
|
);
|
|
159
152
|
|
|
160
153
|
// Log summary
|
|
161
154
|
const updated = results.filter(
|
|
162
|
-
(r) => r.status ===
|
|
155
|
+
(r) => r.status === 'fulfilled' && r.value?.status === 'updated',
|
|
163
156
|
).length;
|
|
164
157
|
const failed = results.filter(
|
|
165
|
-
(r) => r.status ===
|
|
158
|
+
(r) => r.status === 'rejected' || r.value?.status === 'failed',
|
|
166
159
|
).length;
|
|
167
160
|
const notFound = results.filter(
|
|
168
|
-
(r) => r.status ===
|
|
161
|
+
(r) => r.status === 'fulfilled' && r.value?.status === 'not_found',
|
|
169
162
|
).length;
|
|
170
163
|
|
|
171
|
-
logger.log(
|
|
172
|
-
`Linear update complete: ${updated} updated, ${failed} failed, ${notFound} not found`,
|
|
173
|
-
);
|
|
164
|
+
logger.log(`Linear update complete: ${updated} updated, ${failed} failed, ${notFound} not found`);
|
|
174
165
|
}
|
|
175
166
|
|
|
176
167
|
/**
|
|
@@ -178,14 +169,14 @@ export async function success(
|
|
|
178
169
|
*/
|
|
179
170
|
function getLabelColor(releaseType: ReleaseType): string {
|
|
180
171
|
const colors: Record<ReleaseType, string> = {
|
|
181
|
-
major:
|
|
182
|
-
premajor:
|
|
183
|
-
minor:
|
|
184
|
-
preminor:
|
|
185
|
-
patch:
|
|
186
|
-
prepatch:
|
|
187
|
-
prerelease:
|
|
172
|
+
major: '#F44336', // Red for breaking changes
|
|
173
|
+
premajor: '#E91E63', // Pink for pre-major
|
|
174
|
+
minor: '#FF9800', // Orange for new features
|
|
175
|
+
preminor: '#FFC107', // Amber for pre-minor
|
|
176
|
+
patch: '#4CAF50', // Green for fixes
|
|
177
|
+
prepatch: '#8BC34A', // Light green for pre-patch
|
|
178
|
+
prerelease: '#9C27B0', // Purple for prereleases
|
|
188
179
|
};
|
|
189
180
|
|
|
190
|
-
return colors[releaseType] ||
|
|
181
|
+
return colors[releaseType] || '#4752C4'; // Default blue
|
|
191
182
|
}
|
package/src/types.ts
CHANGED
|
@@ -16,12 +16,6 @@ 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;
|
|
25
19
|
}
|
|
26
20
|
|
|
27
21
|
export interface LinearContext {
|
|
@@ -45,22 +39,16 @@ export interface LinearLabel {
|
|
|
45
39
|
color?: string;
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
export interface IssueUpdateResult {
|
|
49
|
-
issueId: string;
|
|
50
|
-
status: "updated" | "failed" | "not_found";
|
|
51
|
-
error?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
42
|
export interface LinearViewer {
|
|
55
43
|
id: string;
|
|
56
44
|
name: string;
|
|
57
45
|
}
|
|
58
46
|
|
|
59
47
|
export type ReleaseType =
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
48
|
+
| 'major'
|
|
49
|
+
| 'premajor'
|
|
50
|
+
| 'minor'
|
|
51
|
+
| 'preminor'
|
|
52
|
+
| 'patch'
|
|
53
|
+
| 'prepatch'
|
|
54
|
+
| 'prerelease';
|