semantic-release-linear-app 0.3.0 → 0.4.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/README.md CHANGED
@@ -25,7 +25,7 @@ npm install --save-dev semantic-release-linear-app
25
25
  }
26
26
  ```
27
27
 
28
- 2. Set `LINEAR_API_KEY` environment variable (create at Linear Settings > API)
28
+ 2. Set `LINEAR_TOKEN` environment variable (see [Authentication](#authentication) below)
29
29
 
30
30
  3. Use branch names with Linear issue IDs:
31
31
 
@@ -45,6 +45,29 @@ ENG-789
45
45
  | `addComment` | `false` | Add release comment to issues |
46
46
  | `dryRun` | `false` | Preview without making changes |
47
47
 
48
+ ## How it Works
49
+
50
+ This plugin hooks into two semantic-release lifecycle stages:
51
+
52
+ ```mermaid
53
+ flowchart LR
54
+ subgraph semantic-release
55
+ A[verifyConditions] --> B[analyzeCommits]
56
+ B --> C[generateNotes]
57
+ C --> D[publish]
58
+ D --> E[success]
59
+ end
60
+
61
+ A -. "validate Linear auth" .-> A
62
+ E -. "update Linear issues" .-> E
63
+ ```
64
+
65
+ 1. **verifyConditions** - Validates your `LINEAR_TOKEN` and tests the API connection
66
+ 2. **success** - After release is published:
67
+ - Finds branches containing the release commits
68
+ - Extracts issue IDs from branch names (e.g., `ENG-123`)
69
+ - Creates/applies version label to each issue
70
+
48
71
  ## Labels
49
72
 
50
73
  Labels are created based on version and channel:
@@ -54,8 +77,23 @@ Labels are created based on version and channel:
54
77
 
55
78
  Colors indicate release type: red (major), orange (minor), green (patch), purple (prerelease).
56
79
 
57
- ## License
80
+ ## Authentication
81
+
82
+ Set `LINEAR_TOKEN` environment variable with either:
83
+
84
+ ### API Key (Simple)
85
+ Actions appear as your personal account.
86
+ 1. Go to Linear Settings > API > Personal API keys
87
+ 2. Create new key
88
+ 3. Set as `LINEAR_TOKEN`
58
89
 
59
- MIT
90
+ ### OAuth Access Token (Recommended for CI)
91
+ Actions appear as your app name instead of a user.
92
+ 1. Go to Linear Settings > API > Applications
93
+ 2. Create new application, enable "Client credentials tokens"
94
+ 3. Use client credentials to get an access token ([docs](https://linear.app/developers/oauth-2-0-authentication))
95
+ 4. Set the access token as `LINEAR_TOKEN`
96
+
97
+ ## License
60
98
 
61
- <!-- SD-1135 e2e test v3 -->
99
+ MIT
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * semantic-release-linear-app
3
3
  * A semantic-release plugin to update Linear issues with version labels
4
4
  */
5
- import { verifyConditions } from "./lib/verify";
6
- import { success } from "./lib/success";
5
+ import { verifyConditions } from './lib/verify.js';
6
+ import { success } from './lib/success.js';
7
7
  export { verifyConditions, success };
8
- export type { PluginConfig } from "./types";
8
+ export type { PluginConfig } from './types.js';
package/dist/index.js CHANGED
@@ -1,11 +1,7 @@
1
- "use strict";
2
1
  /**
3
2
  * semantic-release-linear-app
4
3
  * A semantic-release plugin to update Linear issues with version labels
5
4
  */
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; } });
5
+ import { verifyConditions } from './lib/verify.js';
6
+ import { success } from './lib/success.js';
7
+ export { verifyConditions, success };
@@ -1,4 +1,4 @@
1
- import { LinearContext } from '../types';
1
+ import { LinearContext } from '../types.js';
2
2
  /** Store Linear context for access across hooks */
3
3
  export declare function setLinearContext(ctx: LinearContext): void;
4
4
  export declare function getLinearContext(): LinearContext | null;
@@ -1,7 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setLinearContext = setLinearContext;
4
- exports.getLinearContext = getLinearContext;
5
1
  /**
6
2
  * Module-level storage for Linear context between semantic-release hooks.
7
3
  * semantic-release creates separate context objects per hook, so we need
@@ -9,9 +5,9 @@ exports.getLinearContext = getLinearContext;
9
5
  */
10
6
  let linearContext = null;
11
7
  /** Store Linear context for access across hooks */
12
- function setLinearContext(ctx) {
8
+ export function setLinearContext(ctx) {
13
9
  linearContext = ctx;
14
10
  }
15
- function getLinearContext() {
11
+ export function getLinearContext() {
16
12
  return linearContext;
17
13
  }
@@ -1,4 +1,4 @@
1
- import { LinearIssue, LinearLabel, LinearViewer } from '../types';
1
+ import { LinearIssue, LinearLabel, LinearViewer } from '../types.js';
2
2
  /**
3
3
  * Linear API client for GraphQL operations
4
4
  */
@@ -1,14 +1,8 @@
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"));
1
+ import fetch from 'node-fetch';
8
2
  /**
9
3
  * Linear API client for GraphQL operations
10
4
  */
11
- class LinearClient {
5
+ export class LinearClient {
12
6
  apiKey;
13
7
  apiUrl = 'https://api.linear.app/graphql';
14
8
  constructor(apiKey) {
@@ -18,7 +12,7 @@ class LinearClient {
18
12
  * Execute a GraphQL query
19
13
  */
20
14
  async query(query, variables = {}) {
21
- const response = await (0, node_fetch_1.default)(this.apiUrl, {
15
+ const response = await fetch(this.apiUrl, {
22
16
  method: 'POST',
23
17
  headers: {
24
18
  Authorization: this.apiKey,
@@ -192,4 +186,3 @@ class LinearClient {
192
186
  return data.commentCreate.comment;
193
187
  }
194
188
  }
195
- exports.LinearClient = LinearClient;
@@ -1,17 +1,14 @@
1
- "use strict";
2
1
  /**
3
2
  * Extract Linear issue IDs from branch name ONLY
4
3
  * This enforces a single source of truth for issue tracking
5
4
  */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.parseIssuesFromBranch = parseIssuesFromBranch;
8
5
  /**
9
6
  * Extract Linear issue IDs from a branch name
10
7
  * @param branchName - The branch name to parse
11
8
  * @param teamKeys - Optional list of team keys to filter by
12
9
  * @returns Array of unique issue identifiers
13
10
  */
14
- function parseIssuesFromBranch(branchName, teamKeys = null) {
11
+ export function parseIssuesFromBranch(branchName, teamKeys = null) {
15
12
  const issues = new Set();
16
13
  // Build regex pattern based on team keys
17
14
  const teamPattern = teamKeys ? `(?:${teamKeys.join('|')})` : '[A-Z]+';
@@ -1,26 +1,25 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const parse_issues_1 = require("./parse-issues");
1
+ import { describe, test, expect } from '@jest/globals';
2
+ import { parseIssuesFromBranch } from './parse-issues.js';
4
3
  describe('parse-issues', () => {
5
4
  test('extracts Linear issue IDs from branch name', () => {
6
5
  const branchName = 'feature/ENG-123-add-new-feature';
7
- const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName);
6
+ const result = parseIssuesFromBranch(branchName);
8
7
  expect(result).toEqual(['ENG-123']);
9
8
  });
10
9
  test('extracts multiple issue IDs from branch name', () => {
11
10
  const branchName = 'fix/ENG-123-FEAT-456-bug-fix';
12
- const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName);
11
+ const result = parseIssuesFromBranch(branchName);
13
12
  expect(result).toEqual(expect.arrayContaining(['ENG-123', 'FEAT-456']));
14
13
  expect(result).toHaveLength(2);
15
14
  });
16
15
  test('filters by team keys when provided', () => {
17
16
  const branchName = 'feature/ENG-123-OTHER-456';
18
- const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName, ['ENG']);
17
+ const result = parseIssuesFromBranch(branchName, ['ENG']);
19
18
  expect(result).toEqual(['ENG-123']);
20
19
  });
21
20
  test('returns empty array for branch without issues', () => {
22
21
  const branchName = 'feature/no-issues-here';
23
- const result = (0, parse_issues_1.parseIssuesFromBranch)(branchName);
22
+ const result = parseIssuesFromBranch(branchName);
24
23
  expect(result).toEqual([]);
25
24
  });
26
25
  });
@@ -1,5 +1,5 @@
1
1
  import { SuccessContext } from 'semantic-release';
2
- import { PluginConfig } from '../types';
2
+ import { PluginConfig } from '../types.js';
3
3
  /**
4
4
  * Update Linear issues after a successful release
5
5
  */
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.success = success;
4
- const execa_1 = require("execa");
5
- const linear_client_1 = require("./linear-client");
6
- const parse_issues_1 = require("./parse-issues");
7
- const context_1 = require("./context");
1
+ import { execa } from 'execa';
2
+ import { LinearClient } from './linear-client.js';
3
+ import { parseIssuesFromBranch } from './parse-issues.js';
4
+ import { getLinearContext } from './context.js';
8
5
  /**
9
6
  * Find source branches that contain the given commits
10
7
  */
@@ -16,7 +13,7 @@ async function findSourceBranches(commits, logger) {
16
13
  // Check all commits to find all source branches
17
14
  for (const commit of commits) {
18
15
  try {
19
- const { stdout } = await (0, execa_1.execa)('git', [
16
+ const { stdout } = await execa('git', [
20
17
  'branch',
21
18
  '-r',
22
19
  '--contains',
@@ -43,9 +40,9 @@ async function findSourceBranches(commits, logger) {
43
40
  /**
44
41
  * Update Linear issues after a successful release
45
42
  */
46
- async function success(pluginConfig, context) {
43
+ export async function success(pluginConfig, context) {
47
44
  const { logger, nextRelease, commits } = context;
48
- const linear = (0, context_1.getLinearContext)();
45
+ const linear = getLinearContext();
49
46
  if (!linear) {
50
47
  logger.log('Linear context not found, skipping issue updates');
51
48
  return;
@@ -64,7 +61,7 @@ async function success(pluginConfig, context) {
64
61
  // Extract Linear issue IDs from all found branches
65
62
  const issueIds = new Set();
66
63
  for (const branchName of Array.from(sourceBranches)) {
67
- const branchIssues = (0, parse_issues_1.parseIssuesFromBranch)(branchName, linear.teamKeys);
64
+ const branchIssues = parseIssuesFromBranch(branchName, linear.teamKeys);
68
65
  branchIssues.forEach((id) => issueIds.add(id));
69
66
  }
70
67
  if (issueIds.size === 0) {
@@ -82,7 +79,7 @@ async function success(pluginConfig, context) {
82
79
  return;
83
80
  }
84
81
  // Initialize Linear client and prepare label
85
- const client = new linear_client_1.LinearClient(linear.apiKey);
82
+ const client = new LinearClient(linear.apiKey);
86
83
  const labelColor = getLabelColor(nextRelease.type);
87
84
  // Ensure the version label exists
88
85
  const label = await client.ensureLabel(labelName, labelColor);
@@ -1,5 +1,5 @@
1
1
  import { VerifyConditionsContext } from 'semantic-release';
2
- import { PluginConfig } from '../types';
2
+ import { PluginConfig } from '../types.js';
3
3
  /**
4
4
  * Verify the plugin configuration and Linear API access
5
5
  */
@@ -1,44 +1,38 @@
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.verifyConditions = verifyConditions;
7
- const error_1 = __importDefault(require("@semantic-release/error"));
8
- const linear_client_1 = require("./linear-client");
9
- const context_1 = require("./context");
1
+ import SemanticReleaseError from '@semantic-release/error';
2
+ import { LinearClient } from './linear-client.js';
3
+ import { setLinearContext } from './context.js';
10
4
  /**
11
5
  * Verify the plugin configuration and Linear API access
12
6
  */
13
- async function verifyConditions(pluginConfig, context) {
7
+ export async function verifyConditions(pluginConfig, context) {
14
8
  const { logger } = context;
15
- const { apiKey, teamKeys = [] } = pluginConfig;
16
- // Check for API key in config or environment
17
- const linearApiKey = apiKey || process.env.LINEAR_API_KEY;
18
- if (!linearApiKey) {
19
- throw new error_1.default('No Linear API key found', 'ENOLINEARTOKEN', 'Please provide a Linear API key via plugin config or LINEAR_API_KEY environment variable.');
9
+ const { token, teamKeys = [] } = pluginConfig;
10
+ // Check for token in config or environment
11
+ const linearToken = token || process.env.LINEAR_TOKEN;
12
+ if (!linearToken) {
13
+ throw new SemanticReleaseError('No Linear token found', 'ENOLINEARTOKEN', 'Please provide LINEAR_TOKEN environment variable.');
20
14
  }
21
15
  // Validate team keys format if provided
22
16
  const teamKeyPattern = /^[A-Z]+$/;
23
17
  const branchPattern = /^[A-Za-z0-9._-]+\/[A-Za-z0-9][A-Za-z0-9._-]*$/;
24
18
  const invalidTeamKeys = teamKeys.filter((key) => !teamKeyPattern.test(key) && !branchPattern.test(key));
25
19
  if (invalidTeamKeys.length > 0) {
26
- throw new error_1.default('Invalid team key format', 'EINVALIDTEAMKEY', 'Team keys must be uppercase letters (e.g. SD) or branch names (e.g. caio/tk-519-title). ' +
20
+ throw new SemanticReleaseError('Invalid team key format', 'EINVALIDTEAMKEY', 'Team keys must be uppercase letters (e.g. SD) or branch names (e.g. caio/tk-519-title). ' +
27
21
  `Invalid: ${invalidTeamKeys.join(', ')}`);
28
22
  }
29
23
  // Test API connection
30
- const client = new linear_client_1.LinearClient(linearApiKey);
24
+ const client = new LinearClient(linearToken);
31
25
  try {
32
26
  logger.log('Verifying Linear API access...');
33
27
  await client.testConnection();
34
28
  logger.log('✓ Linear API access verified');
35
29
  }
36
30
  catch (error) {
37
- throw new error_1.default('Failed to connect to Linear API', 'ELINEARCONNECTION', `Could not connect to Linear API: ${error.message}`);
31
+ throw new SemanticReleaseError('Failed to connect to Linear API', 'ELINEARCONNECTION', `Could not connect to Linear API: ${error.message}`);
38
32
  }
39
33
  // Store validated config for other lifecycle methods
40
- (0, context_1.setLinearContext)({
41
- apiKey: linearApiKey,
34
+ setLinearContext({
35
+ apiKey: linearToken,
42
36
  teamKeys: teamKeys.length > 0 ? teamKeys : null,
43
37
  labelPrefix: pluginConfig.labelPrefix || 'v',
44
38
  });
@@ -1,7 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- // Minimal ESM mocks
4
- jest.mock('@semantic-release/error', () => {
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ // ESM mocks must be set up before imports
3
+ jest.unstable_mockModule('@semantic-release/error', () => {
5
4
  class SemanticReleaseError extends Error {
6
5
  code;
7
6
  constructor(message, code) {
@@ -9,33 +8,33 @@ jest.mock('@semantic-release/error', () => {
9
8
  this.code = code;
10
9
  }
11
10
  }
12
- return { __esModule: true, default: SemanticReleaseError };
11
+ return { default: SemanticReleaseError };
13
12
  });
14
- jest.mock('./linear-client', () => ({
15
- LinearClient: jest.fn().mockImplementation(() => ({
16
- testConnection: jest.fn().mockResolvedValue({}),
13
+ jest.unstable_mockModule('./linear-client.js', () => ({
14
+ LinearClient: jest.fn(() => ({
15
+ testConnection: jest.fn(() => Promise.resolve({})),
17
16
  })),
18
17
  }));
19
- jest.mock('node-fetch', () => ({ __esModule: true, default: jest.fn() }));
20
- const verify_1 = require("./verify");
18
+ jest.unstable_mockModule('node-fetch', () => ({ default: jest.fn() }));
19
+ const { verifyConditions } = await import('./verify.js');
21
20
  describe('verify', () => {
22
21
  const mockContext = {
23
22
  logger: { log: jest.fn(), error: jest.fn() },
24
23
  };
25
24
  beforeEach(() => {
26
- delete process.env.LINEAR_API_KEY;
25
+ delete process.env.LINEAR_TOKEN;
27
26
  });
28
- test('throws without API key', async () => {
29
- await expect((0, verify_1.verifyConditions)({}, mockContext)).rejects.toThrow('No Linear API key found');
27
+ test('throws without token', async () => {
28
+ await expect(verifyConditions({}, mockContext)).rejects.toThrow('No Linear token found');
30
29
  });
31
30
  test('validates team key format', async () => {
32
31
  // Validation happens BEFORE the API call, so it fails fast
33
- await expect((0, verify_1.verifyConditions)({ apiKey: 'test', teamKeys: ['eng-123'] }, mockContext)).rejects.toThrow('Invalid team key format');
32
+ await expect(verifyConditions({ token: 'test', teamKeys: ['eng-123'] }, mockContext)).rejects.toThrow('Invalid team key format');
34
33
  });
35
34
  test('accepts valid branch like team key', async () => {
36
35
  // Accepts branch patterns without hitting API
37
- await expect((0, verify_1.verifyConditions)({
38
- apiKey: 'test',
36
+ await expect(verifyConditions({
37
+ token: 'test',
39
38
  teamKeys: ['caio/tk-519-title'],
40
39
  }, mockContext)).resolves.toBeUndefined();
41
40
  });
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export interface PluginConfig {
2
- /** Linear API key (can also use LINEAR_API_KEY env var) */
3
- apiKey?: string;
2
+ /** Linear token - API key or OAuth access token (can also use LINEAR_TOKEN env var) */
3
+ token?: string;
4
4
  /** Team keys to filter issues (e.g., ["ENG", "FEAT"]) */
5
5
  teamKeys?: string[];
6
6
  /** Prefix for version labels (default: "v") */
package/dist/types.js CHANGED
@@ -1,2 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "semantic-release-linear-app",
3
- "version": "0.3.0",
3
+ "version": "0.4.0-next.2",
4
4
  "description": "Semantic-release plugin to update Linear issues with version labels",
5
+ "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "engines": {
@@ -17,12 +18,27 @@
17
18
  "lint": "eslint src/**/*.ts",
18
19
  "format": "prettier --write src/**/*.ts",
19
20
  "prepare": "husky",
20
- "test": "jest",
21
- "test:watch": "jest --watch"
21
+ "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
22
+ "test:watch": "jest --watch",
23
+ "validate": "pnpm lint && pnpm type-check && pnpm build && pnpm test"
22
24
  },
23
25
  "jest": {
24
- "preset": "ts-jest",
26
+ "preset": "ts-jest/presets/default-esm",
25
27
  "testEnvironment": "node",
28
+ "extensionsToTreatAsEsm": [
29
+ ".ts"
30
+ ],
31
+ "moduleNameMapper": {
32
+ "^(\\.{1,2}/.*)\\.js$": "$1"
33
+ },
34
+ "transform": {
35
+ "^.+\\.tsx?$": [
36
+ "ts-jest",
37
+ {
38
+ "useESM": true
39
+ }
40
+ ]
41
+ },
26
42
  "testMatch": [
27
43
  "**/*.test.ts"
28
44
  ]
@@ -39,7 +55,7 @@
39
55
  "license": "MIT",
40
56
  "repository": {
41
57
  "type": "git",
42
- "url": "https://github.com/caiopizzol/semantic-release-linear.git"
58
+ "url": "https://github.com/caiopizzol/semantic-release-linear-app.git"
43
59
  },
44
60
  "peerDependencies": {
45
61
  "semantic-release": ">=20.0.0"
@@ -50,6 +66,7 @@
50
66
  "node-fetch": "^3.3.2"
51
67
  },
52
68
  "devDependencies": {
69
+ "@jest/globals": "^30.2.0",
53
70
  "@semantic-release/commit-analyzer": "^13.0.1",
54
71
  "@semantic-release/git": "^10.0.1",
55
72
  "@semantic-release/github": "^12.0.2",
@@ -77,8 +94,7 @@
77
94
  "lint-staged": {
78
95
  "*.ts": [
79
96
  "eslint --fix",
80
- "prettier --write",
81
- "tsc --noEmit"
97
+ "prettier --write"
82
98
  ]
83
99
  }
84
100
  }
package/src/index.ts CHANGED
@@ -3,10 +3,10 @@
3
3
  * A semantic-release plugin to update Linear issues with version labels
4
4
  */
5
5
 
6
- import { verifyConditions } from "./lib/verify";
7
- import { success } from "./lib/success";
6
+ import { verifyConditions } from './lib/verify.js';
7
+ import { success } from './lib/success.js';
8
8
 
9
9
  export { verifyConditions, success };
10
10
 
11
11
  // Also export types for consumers who use TypeScript
12
- export type { PluginConfig } from "./types";
12
+ export type { PluginConfig } from './types.js';
@@ -1,4 +1,4 @@
1
- import { LinearContext } from '../types';
1
+ import { LinearContext } from '../types.js';
2
2
 
3
3
  /**
4
4
  * Module-level storage for Linear context between semantic-release hooks.
@@ -1,5 +1,5 @@
1
1
  import fetch from 'node-fetch';
2
- import { LinearIssue, LinearLabel, LinearViewer } from '../types';
2
+ import { LinearIssue, LinearLabel, LinearViewer } from '../types.js';
3
3
 
4
4
  interface GraphQLResponse<T> {
5
5
  data?: T;
@@ -1,4 +1,5 @@
1
- import { parseIssuesFromBranch } from './parse-issues';
1
+ import { describe, test, expect } from '@jest/globals';
2
+ import { parseIssuesFromBranch } from './parse-issues.js';
2
3
 
3
4
  describe('parse-issues', () => {
4
5
  test('extracts Linear issue IDs from branch name', () => {
@@ -1,9 +1,9 @@
1
1
  import { SuccessContext } from 'semantic-release';
2
2
  import { execa } from 'execa';
3
- import { LinearClient } from './linear-client';
4
- import { parseIssuesFromBranch } from './parse-issues';
5
- import { PluginConfig, ReleaseType } from '../types';
6
- import { getLinearContext } from './context';
3
+ import { LinearClient } from './linear-client.js';
4
+ import { parseIssuesFromBranch } from './parse-issues.js';
5
+ import { PluginConfig, ReleaseType } from '../types.js';
6
+ import { getLinearContext } from './context.js';
7
7
 
8
8
  /**
9
9
  * Find source branches that contain the given commits
@@ -1,5 +1,7 @@
1
- // Minimal ESM mocks
2
- jest.mock('@semantic-release/error', () => {
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+
3
+ // ESM mocks must be set up before imports
4
+ jest.unstable_mockModule('@semantic-release/error', () => {
3
5
  class SemanticReleaseError extends Error {
4
6
  code: string;
5
7
  constructor(message: string, code: string) {
@@ -7,37 +9,37 @@ jest.mock('@semantic-release/error', () => {
7
9
  this.code = code;
8
10
  }
9
11
  }
10
- return { __esModule: true, default: SemanticReleaseError };
12
+ return { default: SemanticReleaseError };
11
13
  });
12
14
 
13
- jest.mock('./linear-client', () => ({
14
- LinearClient: jest.fn().mockImplementation(() => ({
15
- testConnection: jest.fn().mockResolvedValue({}),
15
+ jest.unstable_mockModule('./linear-client.js', () => ({
16
+ LinearClient: jest.fn(() => ({
17
+ testConnection: jest.fn(() => Promise.resolve({})),
16
18
  })),
17
19
  }));
18
20
 
19
- jest.mock('node-fetch', () => ({ __esModule: true, default: jest.fn() }));
21
+ jest.unstable_mockModule('node-fetch', () => ({ default: jest.fn() }));
20
22
 
21
- import { verifyConditions } from './verify';
22
- import { VerifyConditionsContext } from 'semantic-release';
23
+ const { verifyConditions } = await import('./verify.js');
24
+ import type { VerifyConditionsContext } from 'semantic-release';
23
25
 
24
26
  describe('verify', () => {
25
27
  const mockContext = {
26
28
  logger: { log: jest.fn(), error: jest.fn() },
27
- } as VerifyConditionsContext;
29
+ } as unknown as VerifyConditionsContext;
28
30
 
29
31
  beforeEach(() => {
30
- delete process.env.LINEAR_API_KEY;
32
+ delete process.env.LINEAR_TOKEN;
31
33
  });
32
34
 
33
- test('throws without API key', async () => {
34
- await expect(verifyConditions({}, mockContext)).rejects.toThrow('No Linear API key found');
35
+ test('throws without token', async () => {
36
+ await expect(verifyConditions({}, mockContext)).rejects.toThrow('No Linear token found');
35
37
  });
36
38
 
37
39
  test('validates team key format', async () => {
38
40
  // Validation happens BEFORE the API call, so it fails fast
39
41
  await expect(
40
- verifyConditions({ apiKey: 'test', teamKeys: ['eng-123'] }, mockContext),
42
+ verifyConditions({ token: 'test', teamKeys: ['eng-123'] }, mockContext),
41
43
  ).rejects.toThrow('Invalid team key format');
42
44
  });
43
45
 
@@ -46,7 +48,7 @@ describe('verify', () => {
46
48
  await expect(
47
49
  verifyConditions(
48
50
  {
49
- apiKey: 'test',
51
+ token: 'test',
50
52
  teamKeys: ['caio/tk-519-title'],
51
53
  },
52
54
  mockContext,
package/src/lib/verify.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import SemanticReleaseError from '@semantic-release/error';
2
2
  import { VerifyConditionsContext } from 'semantic-release';
3
- import { LinearClient } from './linear-client';
4
- import { PluginConfig } from '../types';
5
- import { setLinearContext } from './context';
3
+ import { LinearClient } from './linear-client.js';
4
+ import { PluginConfig } from '../types.js';
5
+ import { setLinearContext } from './context.js';
6
6
 
7
7
  /**
8
8
  * Verify the plugin configuration and Linear API access
@@ -12,16 +12,16 @@ export async function verifyConditions(
12
12
  context: VerifyConditionsContext,
13
13
  ): Promise<void> {
14
14
  const { logger } = context;
15
- const { apiKey, teamKeys = [] } = pluginConfig;
15
+ const { token, teamKeys = [] } = pluginConfig;
16
16
 
17
- // Check for API key in config or environment
18
- const linearApiKey = apiKey || process.env.LINEAR_API_KEY;
17
+ // Check for token in config or environment
18
+ const linearToken = token || process.env.LINEAR_TOKEN;
19
19
 
20
- if (!linearApiKey) {
20
+ if (!linearToken) {
21
21
  throw new SemanticReleaseError(
22
- 'No Linear API key found',
22
+ 'No Linear token found',
23
23
  'ENOLINEARTOKEN',
24
- 'Please provide a Linear API key via plugin config or LINEAR_API_KEY environment variable.',
24
+ 'Please provide LINEAR_TOKEN environment variable.',
25
25
  );
26
26
  }
27
27
 
@@ -42,7 +42,7 @@ export async function verifyConditions(
42
42
  }
43
43
 
44
44
  // Test API connection
45
- const client = new LinearClient(linearApiKey);
45
+ const client = new LinearClient(linearToken);
46
46
 
47
47
  try {
48
48
  logger.log('Verifying Linear API access...');
@@ -58,7 +58,7 @@ export async function verifyConditions(
58
58
 
59
59
  // Store validated config for other lifecycle methods
60
60
  setLinearContext({
61
- apiKey: linearApiKey,
61
+ apiKey: linearToken,
62
62
  teamKeys: teamKeys.length > 0 ? teamKeys : null,
63
63
  labelPrefix: pluginConfig.labelPrefix || 'v',
64
64
  });
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export interface PluginConfig {
2
- /** Linear API key (can also use LINEAR_API_KEY env var) */
3
- apiKey?: string;
2
+ /** Linear token - API key or OAuth access token (can also use LINEAR_TOKEN env var) */
3
+ token?: string;
4
4
 
5
5
  /** Team keys to filter issues (e.g., ["ENG", "FEAT"]) */
6
6
  teamKeys?: string[];