semantic-release-linear-app 0.1.0-next.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Caio Pizzol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # semantic-release-linear-app
2
+
3
+ > A [semantic-release](https://github.com/semantic-release/semantic-release) plugin to automatically update Linear issues with version labels when they're included in releases.
4
+
5
+ ## Features
6
+
7
+ - ๐Ÿท๏ธ Automatically adds version labels to Linear issues mentioned in commits
8
+ - ๐ŸŽจ Color-coded labels based on release type (major/minor/patch)
9
+ - ๐Ÿงน Optionally removes old version labels to keep issues clean
10
+ - ๐Ÿ’ฌ Can add release comments to issues (optional)
11
+ - ๐Ÿ” Configurable team key filtering
12
+ - โšก Batch operations for efficiency
13
+ - ๐Ÿ“ Full TypeScript support with type definitions
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install --save-dev semantic-release-linear-app
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ Add the plugin to your semantic-release configuration:
24
+
25
+ ```json
26
+ {
27
+ "plugins": [
28
+ "@semantic-release/commit-analyzer",
29
+ "@semantic-release/release-notes-generator",
30
+ ["semantic-release-linear-app", {
31
+ "apiKey": "lin_api_xxx"
32
+ }],
33
+ "@semantic-release/github"
34
+ ]
35
+ }
36
+ ```
37
+
38
+ ### TypeScript Configuration
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';
44
+
45
+ const config: PluginConfig = {
46
+ apiKey: process.env.LINEAR_API_KEY,
47
+ teamKeys: ['ENG', 'FEAT'],
48
+ labelPrefix: 'v',
49
+ removeOldLabels: true,
50
+ addComment: false
51
+ };
52
+ ```
53
+
54
+ ## Configuration
55
+
56
+ ### Authentication
57
+
58
+ Set your Linear API key via environment variable:
59
+
60
+ ```bash
61
+ export LINEAR_API_KEY=lin_api_xxx
62
+ ```
63
+
64
+ Or pass it in the plugin configuration:
65
+
66
+ ```json
67
+ {
68
+ "apiKey": "lin_api_xxx"
69
+ }
70
+ ```
71
+
72
+ ### Options
73
+
74
+ | Option | Default | Description |
75
+ |--------|---------|-------------|
76
+ | `apiKey` | - | Linear API key (or use `LINEAR_API_KEY` env var) |
77
+ | `teamKeys` | `[]` | Array of team keys to filter issues (e.g., `["ENG", "FEAT"]`) |
78
+ | `labelPrefix` | `"v"` | Prefix for version labels |
79
+ | `removeOldLabels` | `true` | Remove previous version labels from issues |
80
+ | `addComment` | `false` | Add a comment to issues with release info |
81
+ | `dryRun` | `false` | Preview changes without updating Linear |
82
+
83
+ ### Example Configuration
84
+
85
+ ```javascript
86
+ // .releaserc.js
87
+ module.exports = {
88
+ branches: ['main', 'next'],
89
+ plugins: [
90
+ '@semantic-release/commit-analyzer',
91
+ '@semantic-release/release-notes-generator',
92
+ ['semantic-release-linear-app', {
93
+ teamKeys: ['ENG', 'FEAT', 'BUG'],
94
+ labelPrefix: 'version:',
95
+ removeOldLabels: true,
96
+ addComment: true
97
+ }],
98
+ '@semantic-release/npm',
99
+ '@semantic-release/github'
100
+ ]
101
+ };
102
+ ```
103
+
104
+ ## How It Works
105
+
106
+ 1. **Issue Detection**: The plugin scans commit messages for Linear issue IDs (e.g., `ENG-123`)
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:
114
+
115
+ ```bash
116
+ # In commit message
117
+ git commit -m "feat: add new feature ENG-123"
118
+
119
+ # In commit body
120
+ git commit -m "feat: add new feature" -m "Closes ENG-123"
121
+
122
+ # Multiple issues
123
+ git commit -m "fix: resolve bugs ENG-123, FEAT-456"
124
+
125
+ # In PR title (when squash merging)
126
+ "feat: add feature (#123) ENG-456"
127
+ ```
128
+
129
+ ### Label Colors
130
+
131
+ Labels are automatically color-coded based on the release type:
132
+
133
+ - ๐Ÿ”ด **Major** releases (breaking changes) - Red
134
+ - ๐ŸŸ  **Minor** releases (new features) - Orange
135
+ - ๐ŸŸข **Patch** releases (bug fixes) - Green
136
+ - ๐ŸŸฃ **Prerelease** versions - Purple
137
+
138
+ ## Advanced Usage
139
+
140
+ ### Channel-Specific Configuration
141
+
142
+ For different behavior on different release channels:
143
+
144
+ ```javascript
145
+ // Coming in v2.0
146
+ {
147
+ channelConfig: {
148
+ next: {
149
+ labelPrefix: 'next:',
150
+ addComment: false
151
+ },
152
+ latest: {
153
+ labelPrefix: 'stable:',
154
+ addComment: true
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ### Dry Run
161
+
162
+ Test what issues would be updated without making changes:
163
+
164
+ ```javascript
165
+ {
166
+ dryRun: true
167
+ }
168
+ ```
169
+
170
+ ## Linear API Setup
171
+
172
+ 1. Go to Linear Settings โ†’ API โ†’ Personal API keys
173
+ 2. Create a new key with "write" access
174
+ 3. Add to your CI environment as `LINEAR_API_KEY`
175
+
176
+ ## CI Configuration
177
+
178
+ ### GitHub Actions
179
+
180
+ ```yaml
181
+ - name: Release
182
+ env:
183
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
184
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
185
+ LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
186
+ run: npx semantic-release
187
+ ```
188
+
189
+ ## Troubleshooting
190
+
191
+ ### Issues not being detected
192
+
193
+ - Ensure issue IDs follow the format `TEAM-NUMBER` (e.g., `ENG-123`)
194
+ - Check that team keys match if using the `teamKeys` filter
195
+ - Verify the issues exist in Linear
196
+
197
+ ### API errors
198
+
199
+ - Confirm your API key has write access
200
+ - Check that the Linear workspace is accessible
201
+ - Ensure network connectivity from CI environment
202
+
203
+ ## License
204
+
205
+ MIT
@@ -0,0 +1,8 @@
1
+ /**
2
+ * semantic-release-linear-app
3
+ * A semantic-release plugin to update Linear issues with version labels
4
+ */
5
+ import { verifyConditions } from "./lib/verify";
6
+ import { success } from "./lib/success";
7
+ export { verifyConditions, success };
8
+ export type { PluginConfig } from "./types";
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /**
3
+ * semantic-release-linear-app
4
+ * A semantic-release plugin to update Linear issues with version labels
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.success = exports.verifyConditions = void 0;
8
+ const verify_1 = require("./lib/verify");
9
+ Object.defineProperty(exports, "verifyConditions", { enumerable: true, get: function () { return verify_1.verifyConditions; } });
10
+ const success_1 = require("./lib/success");
11
+ Object.defineProperty(exports, "success", { enumerable: true, get: function () { return success_1.success; } });
@@ -0,0 +1,39 @@
1
+ import { LinearIssue, LinearLabel, LinearViewer } from "../types";
2
+ /**
3
+ * Linear API client for GraphQL operations
4
+ */
5
+ export declare class LinearClient {
6
+ private apiKey;
7
+ private apiUrl;
8
+ constructor(apiKey: string);
9
+ /**
10
+ * Execute a GraphQL query
11
+ */
12
+ private query;
13
+ /**
14
+ * Test the API connection
15
+ */
16
+ testConnection(): Promise<LinearViewer>;
17
+ /**
18
+ * Find or create a label
19
+ */
20
+ ensureLabel(name: string, color?: string): Promise<LinearLabel>;
21
+ /**
22
+ * Get issue by identifier
23
+ */
24
+ getIssue(identifier: string): Promise<LinearIssue | null>;
25
+ /**
26
+ * Add label to issue
27
+ */
28
+ addLabelToIssue(issueId: string, labelId: string): Promise<LinearIssue>;
29
+ /**
30
+ * Remove old version labels from issue
31
+ */
32
+ removeVersionLabels(issueId: string, labelPrefix: string): Promise<LinearIssue | null>;
33
+ /**
34
+ * Add comment to issue
35
+ */
36
+ addComment(issueId: string, body: string): Promise<{
37
+ id: string;
38
+ }>;
39
+ }
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LinearClient = void 0;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ /**
9
+ * Linear API client for GraphQL operations
10
+ */
11
+ class LinearClient {
12
+ apiKey;
13
+ apiUrl = "https://api.linear.app/graphql";
14
+ constructor(apiKey) {
15
+ this.apiKey = apiKey;
16
+ }
17
+ /**
18
+ * Execute a GraphQL query
19
+ */
20
+ async query(query, variables = {}) {
21
+ const response = await (0, node_fetch_1.default)(this.apiUrl, {
22
+ method: "POST",
23
+ headers: {
24
+ Authorization: this.apiKey,
25
+ "Content-Type": "application/json",
26
+ },
27
+ body: JSON.stringify({ query, variables }),
28
+ });
29
+ const data = (await response.json());
30
+ if (data.errors) {
31
+ throw new Error(`Linear API error: ${data.errors[0].message}`);
32
+ }
33
+ if (!data.data) {
34
+ throw new Error("No data returned from Linear API");
35
+ }
36
+ return data.data;
37
+ }
38
+ /**
39
+ * Test the API connection
40
+ */
41
+ async testConnection() {
42
+ const query = `
43
+ query TestConnection {
44
+ viewer {
45
+ id
46
+ name
47
+ }
48
+ }
49
+ `;
50
+ const data = await this.query(query);
51
+ return data.viewer;
52
+ }
53
+ /**
54
+ * Find or create a label
55
+ */
56
+ async ensureLabel(name, color = "#4752C4") {
57
+ // First, try to find existing label
58
+ const searchQuery = `
59
+ query FindLabel($name: String!) {
60
+ issueLabels(filter: { name: { eq: $name } }) {
61
+ nodes {
62
+ id
63
+ name
64
+ }
65
+ }
66
+ }
67
+ `;
68
+ const searchData = await this.query(searchQuery, { name });
69
+ if (searchData.issueLabels.nodes.length > 0) {
70
+ return searchData.issueLabels.nodes[0];
71
+ }
72
+ // Create new label if it doesn't exist
73
+ const createMutation = `
74
+ mutation CreateLabel($name: String!, $color: String!) {
75
+ issueLabelCreate(input: { name: $name, color: $color }) {
76
+ issueLabel {
77
+ id
78
+ name
79
+ }
80
+ }
81
+ }
82
+ `;
83
+ const createData = await this.query(createMutation, { name, color });
84
+ return createData.issueLabelCreate.issueLabel;
85
+ }
86
+ /**
87
+ * Get issue by identifier
88
+ */
89
+ async getIssue(identifier) {
90
+ const query = `
91
+ query GetIssue($identifier: String!) {
92
+ issue(id: $identifier) {
93
+ id
94
+ identifier
95
+ title
96
+ labels {
97
+ nodes {
98
+ id
99
+ name
100
+ }
101
+ }
102
+ }
103
+ }
104
+ `;
105
+ try {
106
+ const data = await this.query(query, {
107
+ identifier,
108
+ });
109
+ return data.issue;
110
+ }
111
+ catch {
112
+ // Issue might not exist, return null
113
+ return null;
114
+ }
115
+ }
116
+ /**
117
+ * Add label to issue
118
+ */
119
+ async addLabelToIssue(issueId, labelId) {
120
+ const mutation = `
121
+ mutation AddLabel($issueId: String!, $labelId: String!) {
122
+ issueUpdate(
123
+ id: $issueId,
124
+ input: { labelIds: [$labelId] }
125
+ ) {
126
+ issue {
127
+ id
128
+ identifier
129
+ title
130
+ labels {
131
+ nodes {
132
+ id
133
+ name
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ `;
140
+ const data = await this.query(mutation, { issueId, labelId });
141
+ return data.issueUpdate.issue;
142
+ }
143
+ /**
144
+ * Remove old version labels from issue
145
+ */
146
+ async removeVersionLabels(issueId, labelPrefix) {
147
+ const issue = await this.getIssue(issueId);
148
+ if (!issue)
149
+ return null;
150
+ const versionLabels = issue.labels.nodes.filter((label) => label.name.startsWith(labelPrefix));
151
+ if (versionLabels.length === 0)
152
+ return issue;
153
+ const mutation = `
154
+ mutation RemoveLabels($issueId: String!, $labelIds: [String!]!) {
155
+ issueRemoveLabel(id: $issueId, labelIds: $labelIds) {
156
+ issue {
157
+ id
158
+ identifier
159
+ title
160
+ labels {
161
+ nodes {
162
+ id
163
+ name
164
+ }
165
+ }
166
+ }
167
+ }
168
+ }
169
+ `;
170
+ const labelIds = versionLabels.map((label) => label.id);
171
+ const data = await this.query(mutation, { issueId, labelIds });
172
+ return data.issueRemoveLabel.issue;
173
+ }
174
+ /**
175
+ * Add comment to issue
176
+ */
177
+ async addComment(issueId, body) {
178
+ const mutation = `
179
+ mutation AddComment($issueId: String!, $body: String!) {
180
+ commentCreate(input: { issueId: $issueId, body: $body }) {
181
+ comment {
182
+ id
183
+ }
184
+ }
185
+ }
186
+ `;
187
+ const data = await this.query(mutation, { issueId, body });
188
+ return data.commentCreate.comment;
189
+ }
190
+ }
191
+ exports.LinearClient = LinearClient;
@@ -0,0 +1,15 @@
1
+ import { Commit } from "semantic-release";
2
+ /**
3
+ * Extract Linear issue IDs from a commit
4
+ * @param commit - The commit object from semantic-release
5
+ * @param teamKeys - Optional list of team keys to filter by
6
+ * @returns Set of unique issue identifiers
7
+ */
8
+ export declare function parseCommit(commit: Commit, teamKeys?: string[] | null): Set<string>;
9
+ /**
10
+ * Extract all Linear issue IDs from a list of commits
11
+ * @param commits - Array of commit objects
12
+ * @param teamKeys - Optional list of team keys to filter by
13
+ * @returns Array of unique issue identifiers
14
+ */
15
+ export declare function parseIssues(commits: readonly Commit[], teamKeys?: string[] | null): string[];
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCommit = parseCommit;
4
+ exports.parseIssues = parseIssues;
5
+ /**
6
+ * Extract Linear issue IDs from a commit
7
+ * @param commit - The commit object from semantic-release
8
+ * @param teamKeys - Optional list of team keys to filter by
9
+ * @returns Set of unique issue identifiers
10
+ */
11
+ function parseCommit(commit, teamKeys = null) {
12
+ const issues = new Set();
13
+ // Build regex pattern based on team keys
14
+ const teamPattern = teamKeys ? `(?:${teamKeys.join("|")})` : "[A-Z]+";
15
+ // Pattern matches: ENG-123, FEAT-45, etc.
16
+ const issuePattern = new RegExp(`\\b(${teamPattern}-\\d+)\\b`, "gi");
17
+ // Search in commit message
18
+ if (commit.message) {
19
+ const messageMatches = Array.from(commit.message.matchAll(issuePattern));
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
+ }
30
+ }
31
+ return issues;
32
+ }
33
+ /**
34
+ * Extract all Linear issue IDs from a list of commits
35
+ * @param commits - Array of commit objects
36
+ * @param teamKeys - Optional list of team keys to filter by
37
+ * @returns Array of unique issue identifiers
38
+ */
39
+ function parseIssues(commits, teamKeys = null) {
40
+ const allIssues = new Set();
41
+ commits.forEach((commit) => {
42
+ const commitIssues = parseCommit(commit, teamKeys);
43
+ commitIssues.forEach((issue) => allIssues.add(issue));
44
+ });
45
+ return Array.from(allIssues);
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const parse_issues_1 = require("./parse-issues");
4
+ describe("parse-issues", () => {
5
+ test("extracts Linear issue IDs from commits", () => {
6
+ const commits = [
7
+ { message: "fix: solve ENG-123 and FEAT-456" },
8
+ {
9
+ message: "feat: new feature",
10
+ body: "Closes BUG-789",
11
+ },
12
+ { message: "chore: no issues here" },
13
+ ];
14
+ const result = (0, parse_issues_1.parseIssues)(commits);
15
+ expect(result).toEqual(expect.arrayContaining(["ENG-123", "FEAT-456", "BUG-789"]));
16
+ expect(result).toHaveLength(3);
17
+ });
18
+ test("filters by team keys when provided", () => {
19
+ const commits = [{ message: "fix: ENG-123 OTHER-456" }];
20
+ const result = (0, parse_issues_1.parseIssues)(commits, ["ENG"]);
21
+ expect(result).toEqual(["ENG-123"]);
22
+ });
23
+ });
@@ -0,0 +1,10 @@
1
+ import { SuccessContext } from "semantic-release";
2
+ import { PluginConfig, LinearContext } from "../types";
3
+ interface ExtendedContext extends SuccessContext {
4
+ linear?: LinearContext;
5
+ }
6
+ /**
7
+ * Update Linear issues after a successful release
8
+ */
9
+ export declare function success(pluginConfig: PluginConfig, context: ExtendedContext): Promise<void>;
10
+ export {};
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.success = success;
4
+ const linear_client_1 = require("./linear-client");
5
+ const parse_issues_1 = require("./parse-issues");
6
+ /**
7
+ * Update Linear issues after a successful release
8
+ */
9
+ async function success(pluginConfig, context) {
10
+ const { logger, nextRelease, commits, linear } = context;
11
+ if (!linear) {
12
+ logger.log("Linear context not found, skipping issue updates");
13
+ return;
14
+ }
15
+ const { removeOldLabels = true, addComment = false, dryRun = false, } = pluginConfig;
16
+ const client = new linear_client_1.LinearClient(linear.apiKey);
17
+ const version = nextRelease.version;
18
+ const channel = nextRelease.channel || "latest";
19
+ // Format the label based on configuration
20
+ const labelName = `${linear.labelPrefix}${version}`;
21
+ const labelColor = getLabelColor(nextRelease.type);
22
+ logger.log(`Updating Linear issues for release ${version} (${channel})`);
23
+ // Extract issue IDs from commits
24
+ const issueIds = (0, parse_issues_1.parseIssues)(commits, linear.teamKeys);
25
+ if (issueIds.length === 0) {
26
+ logger.log("No Linear issues found in commits");
27
+ return;
28
+ }
29
+ logger.log(`Found ${issueIds.length} Linear issue(s): ${issueIds.join(", ")}`);
30
+ if (dryRun) {
31
+ logger.log("[Dry run] Would update issues:", issueIds);
32
+ logger.log(`[Dry run] Would apply label: ${labelName}`);
33
+ return;
34
+ }
35
+ // Ensure the version label exists
36
+ const label = await client.ensureLabel(labelName, labelColor);
37
+ logger.log(`โœ“ Ensured label exists: ${labelName}`);
38
+ // Update each issue
39
+ const results = await Promise.allSettled(issueIds.map(async (issueId) => {
40
+ try {
41
+ // Get the issue first
42
+ const issue = await client.getIssue(issueId);
43
+ if (!issue) {
44
+ logger.warn(`Issue ${issueId} not found, skipping`);
45
+ return { issueId, status: "not_found" };
46
+ }
47
+ // Remove old version labels if configured
48
+ if (removeOldLabels) {
49
+ await client.removeVersionLabels(issue.id, linear.labelPrefix);
50
+ }
51
+ // Add the new version label
52
+ await client.addLabelToIssue(issue.id, label.id);
53
+ // Add comment if configured
54
+ if (addComment) {
55
+ const comment = formatComment(version, channel, nextRelease);
56
+ await client.addComment(issue.id, comment);
57
+ }
58
+ logger.log(`โœ“ Updated issue ${issueId}`);
59
+ return { issueId, status: "updated" };
60
+ }
61
+ catch (error) {
62
+ const message = error instanceof Error ? error.message : String(error);
63
+ logger.error(`Failed to update issue ${issueId}: ${message}`);
64
+ return { issueId, status: "failed", error: message };
65
+ }
66
+ }));
67
+ // Log summary
68
+ const updated = results.filter((r) => r.status === "fulfilled" && r.value.status === "updated").length;
69
+ const failed = results.filter((r) => r.status === "rejected" ||
70
+ (r.status === "fulfilled" && r.value.status === "failed")).length;
71
+ const notFound = results.filter((r) => r.status === "fulfilled" && r.value.status === "not_found").length;
72
+ logger.log(`Linear update complete: ${updated} updated, ${failed} failed, ${notFound} not found`);
73
+ }
74
+ /**
75
+ * Get label color based on release type
76
+ */
77
+ function getLabelColor(releaseType) {
78
+ const colors = {
79
+ major: "#F44336", // Red for breaking changes
80
+ premajor: "#E91E63", // Pink for pre-major
81
+ minor: "#FF9800", // Orange for new features
82
+ preminor: "#FFC107", // Amber for pre-minor
83
+ patch: "#4CAF50", // Green for fixes
84
+ prepatch: "#8BC34A", // Light green for pre-patch
85
+ prerelease: "#9C27B0", // Purple for prereleases
86
+ };
87
+ return colors[releaseType] || "#4752C4"; // Default blue
88
+ }
89
+ /**
90
+ * Format comment for Linear issue
91
+ */
92
+ function formatComment(version, channel, release) {
93
+ const emoji = channel === "latest" ? "๐Ÿš€" : "๐Ÿ”ฌ";
94
+ const channelText = channel === "latest" ? "stable" : channel;
95
+ let comment = `${emoji} **Released in \`v${version}\`** (${channelText})\n\n`;
96
+ const githubRepo = process.env.GITHUB_REPOSITORY;
97
+ if (release.gitTag && githubRepo) {
98
+ comment += `[View release โ†’](https://github.com/${githubRepo}/releases/tag/${release.gitTag})`;
99
+ }
100
+ return comment;
101
+ }
@@ -0,0 +1,8 @@
1
+ import { VerifyConditionsContext } from "semantic-release";
2
+ import { PluginConfig, LinearContext } from "../types";
3
+ /**
4
+ * Verify the plugin configuration and Linear API access
5
+ */
6
+ export declare function verifyConditions(pluginConfig: PluginConfig, context: VerifyConditionsContext & {
7
+ linear?: LinearContext;
8
+ }): Promise<void>;