@h-rig/linear-plugin 0.0.6-alpha.78 → 0.0.6-alpha.80

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.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Fixture-driven fake transport for testing the Linear adapter (and plugins
3
+ * built on the same pattern) without any live API calls.
4
+ *
5
+ * The adapter names every GraphQL operation (`RigListIssues`,
6
+ * `RigTeamStates`, …), so fixtures route on the operation name instead of
7
+ * fragile query-string matching. Each route returns either:
8
+ * - a plain object → wrapped as `{ data: <object> }` with HTTP 200
9
+ * - `graphqlErrors([...])` → `{ errors: [...] }` with HTTP 200
10
+ * - `httpFailure(status, body)` → a raw non-200 HTTP response
11
+ */
12
+ import type { LinearTransport } from "./linear-issues-source";
13
+ export interface RecordedLinearCall {
14
+ url: string;
15
+ /** Raw Authorization header the adapter sent. */
16
+ authorization: string | undefined;
17
+ /** GraphQL operation name parsed from the request body. */
18
+ operation: string;
19
+ query: string;
20
+ variables: Record<string, unknown>;
21
+ }
22
+ declare const HTTP_FAILURE: unique symbol;
23
+ declare const GRAPHQL_ERRORS: unique symbol;
24
+ interface HttpFailureFixture {
25
+ [HTTP_FAILURE]: true;
26
+ status: number;
27
+ body: string;
28
+ }
29
+ interface GraphQLErrorsFixture {
30
+ [GRAPHQL_ERRORS]: true;
31
+ errors: ReadonlyArray<{
32
+ message: string;
33
+ extensions?: {
34
+ code?: string;
35
+ };
36
+ }>;
37
+ }
38
+ /** Make a route fail at the HTTP layer (e.g. `httpFailure(401, "Unauthorized")`). */
39
+ export declare function httpFailure(status: number, body: string): HttpFailureFixture;
40
+ /** Make a route return a GraphQL-level errors payload with HTTP 200. */
41
+ export declare function graphqlErrors(errors: ReadonlyArray<{
42
+ message: string;
43
+ extensions?: {
44
+ code?: string;
45
+ };
46
+ }>): GraphQLErrorsFixture;
47
+ export type FixtureRoute = (variables: Record<string, unknown>, call: RecordedLinearCall) => unknown;
48
+ export interface FixtureTransport {
49
+ transport: LinearTransport;
50
+ /** Every request the adapter made, in order. Assert payloads against this. */
51
+ calls: RecordedLinearCall[];
52
+ /** Calls filtered to one operation name. */
53
+ callsFor(operation: string): RecordedLinearCall[];
54
+ }
55
+ /**
56
+ * Build a transport whose responses come from fixtures keyed by GraphQL
57
+ * operation name. Unrouted operations fail loudly so tests never silently
58
+ * exercise a request they didn't mean to allow.
59
+ */
60
+ export declare function createFixtureTransport(routes: Record<string, FixtureRoute>): FixtureTransport;
61
+ export {};
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Shared GraphQL fixtures for the test suite — shaped exactly like real
3
+ * Linear API responses (the `data` payload of each operation). No live API
4
+ * calls happen anywhere in this package's tests.
5
+ */
6
+ export declare const TEAM_KEY = "ENG";
7
+ export declare const STATE_IDS: {
8
+ readonly triage: "11111111-1111-4111-8111-111111111101";
9
+ readonly backlog: "11111111-1111-4111-8111-111111111102";
10
+ readonly todo: "11111111-1111-4111-8111-111111111103";
11
+ readonly inProgress: "11111111-1111-4111-8111-111111111104";
12
+ readonly inReview: "11111111-1111-4111-8111-111111111105";
13
+ readonly done: "11111111-1111-4111-8111-111111111106";
14
+ readonly canceled: "11111111-1111-4111-8111-111111111107";
15
+ };
16
+ /** Response payload for the RigTeamStates operation. */
17
+ export declare const teamStatesFixture: {
18
+ teams: {
19
+ nodes: {
20
+ id: string;
21
+ key: string;
22
+ states: {
23
+ nodes: ({
24
+ id: "11111111-1111-4111-8111-111111111101";
25
+ name: string;
26
+ type: string;
27
+ position: number;
28
+ } | {
29
+ id: "11111111-1111-4111-8111-111111111102";
30
+ name: string;
31
+ type: string;
32
+ position: number;
33
+ } | {
34
+ id: "11111111-1111-4111-8111-111111111103";
35
+ name: string;
36
+ type: string;
37
+ position: number;
38
+ } | {
39
+ id: "11111111-1111-4111-8111-111111111105";
40
+ name: string;
41
+ type: string;
42
+ position: number;
43
+ } | {
44
+ id: "11111111-1111-4111-8111-111111111104";
45
+ name: string;
46
+ type: string;
47
+ position: number;
48
+ } | {
49
+ id: "11111111-1111-4111-8111-111111111106";
50
+ name: string;
51
+ type: string;
52
+ position: number;
53
+ } | {
54
+ id: "11111111-1111-4111-8111-111111111107";
55
+ name: string;
56
+ type: string;
57
+ position: number;
58
+ })[];
59
+ };
60
+ }[];
61
+ };
62
+ };
63
+ export declare const ISSUE_UUIDS: {
64
+ readonly eng1: "33333333-3333-4333-8333-333333333301";
65
+ readonly eng2: "33333333-3333-4333-8333-333333333302";
66
+ readonly eng3: "33333333-3333-4333-8333-333333333303";
67
+ readonly eng4: "33333333-3333-4333-8333-333333333304";
68
+ readonly eng5: "33333333-3333-4333-8333-333333333305";
69
+ readonly eng6: "33333333-3333-4333-8333-333333333306";
70
+ readonly eng7: "33333333-3333-4333-8333-333333333307";
71
+ };
72
+ export interface FixtureIssue {
73
+ id: string;
74
+ identifier: string;
75
+ title: string;
76
+ description: string | null;
77
+ priority: number | null;
78
+ priorityLabel: string | null;
79
+ url: string;
80
+ state: {
81
+ id: string;
82
+ name: string;
83
+ type: string;
84
+ };
85
+ labels: {
86
+ nodes: Array<{
87
+ name: string;
88
+ }>;
89
+ };
90
+ assignee: {
91
+ name: string;
92
+ email: string;
93
+ } | null;
94
+ }
95
+ export declare const issueFixtures: FixtureIssue[];
96
+ /** Single-page RigListIssues payload containing every fixture issue. */
97
+ export declare function listIssuesPage(issues: readonly FixtureIssue[], pageInfo?: {
98
+ hasNextPage: boolean;
99
+ endCursor?: string | null;
100
+ }): {
101
+ issues: {
102
+ pageInfo: {
103
+ hasNextPage: boolean;
104
+ endCursor?: string | null;
105
+ };
106
+ nodes: readonly FixtureIssue[];
107
+ };
108
+ };
109
+ export declare function issueByIdentifier(identifier: string): FixtureIssue;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * `@rig/linear-plugin` — Linear as a Rig task source.
3
+ *
4
+ * This package is the canonical example of a plugin that adapts an external
5
+ * HTTP API into Rig's task-source contract. The README walks through the
6
+ * anatomy; `linear-issues-source.ts` holds the adapter, this file holds the
7
+ * `definePlugin` wiring (metadata channel + executable runtime channel).
8
+ *
9
+ * Usage in rig.config.ts:
10
+ *
11
+ * import { defineConfig } from "@rig/core";
12
+ * import linear from "@rig/linear-plugin";
13
+ *
14
+ * export default defineConfig({
15
+ * plugins: [linear()],
16
+ * taskSource: {
17
+ * kind: "linear",
18
+ * options: { teamKey: "ENG", states: ["Todo"], labels: ["rig"] },
19
+ * },
20
+ * // ...
21
+ * });
22
+ *
23
+ * The API key comes from LINEAR_API_KEY (or `linear({ apiKey })`) — never
24
+ * from rig.config.ts, which is committed.
25
+ */
26
+ import { type RigPluginWithRuntime } from "@rig/core";
27
+ import { type LinearIssuesOptions, type LinearTransport } from "./linear-issues-source";
28
+ export { createLinearIssuesTaskSource, linearStateTypeForRigStatus, rigStatusForLinearStateType, updateRigOwnedMetadataBlock, RIG_METADATA_START, RIG_METADATA_END, } from "./linear-issues-source";
29
+ export type { LinearIssuesOptions, LinearIssuesTaskSource, LinearTransport, LinearTransportRequest, LinearTransportResponse, } from "./linear-issues-source";
30
+ export { createFixtureTransport, graphqlErrors, httpFailure, } from "./fixture-transport";
31
+ export type { FixtureTransport, FixtureRoute, RecordedLinearCall } from "./fixture-transport";
32
+ /**
33
+ * Process-level options passed where the plugin is instantiated (rig.config.ts).
34
+ * Per-project source configuration (team key, filters) lives in
35
+ * `taskSource.options` instead — see the factory below.
36
+ */
37
+ export interface LinearPluginOptions {
38
+ /** Linear API key. Defaults to process.env.LINEAR_API_KEY at request time. */
39
+ apiKey?: string;
40
+ /** GraphQL endpoint override (self-hosted proxies, test servers). */
41
+ endpoint?: string;
42
+ /** fetch-shaped transport override — tests inject a fixture transport here. */
43
+ transport?: LinearTransport;
44
+ /** Notify the host that issue-backed task state changed. */
45
+ onTaskChanged?: LinearIssuesOptions["onTaskChanged"];
46
+ }
47
+ export default function linearPlugin(opts?: LinearPluginOptions): RigPluginWithRuntime;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Linear issues task source — the canonical external-HTTP-API adapter.
3
+ *
4
+ * Everything goes through one GraphQL endpoint (`https://api.linear.app/graphql`)
5
+ * via an injectable fetch-shaped transport, so the entire adapter is testable
6
+ * from fixtures with zero live API calls. See the package README for the
7
+ * walk-through; this file is deliberately self-contained (no SDK dependency).
8
+ */
9
+ import type { RegisteredTaskSource, TaskRecord, TaskSourceUpdate } from "@rig/contracts";
10
+ /** The exact request shape the adapter sends. */
11
+ export interface LinearTransportRequest {
12
+ method: "POST";
13
+ headers: Record<string, string>;
14
+ body: string;
15
+ }
16
+ /** The minimal response surface the adapter reads. `Response` satisfies it. */
17
+ export interface LinearTransportResponse {
18
+ ok: boolean;
19
+ status: number;
20
+ text(): Promise<string>;
21
+ }
22
+ /**
23
+ * A fetch-shaped function. Global `fetch` is assignable as-is; tests inject a
24
+ * fixture-driven fake (see `createFixtureTransport` in `fixture-transport.ts`).
25
+ */
26
+ export type LinearTransport = (url: string, init: LinearTransportRequest) => Promise<LinearTransportResponse>;
27
+ export interface LinearIssuesOptions {
28
+ /** Linear team key (the "ENG" in ENG-123). Issues are listed for this team. */
29
+ teamKey: string;
30
+ /**
31
+ * Linear API key. Defaults to `process.env.LINEAR_API_KEY`, resolved at
32
+ * request time. Personal API keys (`lin_api_…`) are sent raw in the
33
+ * `Authorization` header; OAuth tokens (`lin_oauth_…`) get a `Bearer` prefix.
34
+ */
35
+ apiKey?: string;
36
+ /** Filter: workflow state names (e.g. `["Todo", "In Progress"]`). */
37
+ states?: readonly string[];
38
+ /** Filter: assignee email (anything containing "@") or display name. */
39
+ assignee?: string;
40
+ /** Filter: label names. An issue must carry every listed label (AND). */
41
+ labels?: readonly string[];
42
+ /** GraphQL endpoint override. Defaults to the public Linear API. */
43
+ endpoint?: string;
44
+ /** fetch-shaped transport. Defaults to global `fetch`; tests inject a fake. */
45
+ transport?: LinearTransport;
46
+ /** Issues per GraphQL page (Linear caps pages at 250). Defaults to 50. */
47
+ pageSize?: number;
48
+ /** Maximum issue-list rows before Rig fails loudly instead of silently truncating. Defaults to 1,000. */
49
+ listLimit?: number;
50
+ /** Notify the host that issue-backed task state changed and snapshots should refresh. */
51
+ onTaskChanged?: (event: {
52
+ teamKey: string;
53
+ id: string;
54
+ status?: TaskRecord["status"];
55
+ reason: "linear-issue-updated";
56
+ }) => void;
57
+ }
58
+ /**
59
+ * Linear workflow-state *type* → Rig task status.
60
+ * triage / backlog / unstarted → ready
61
+ * started → in_progress
62
+ * completed → completed
63
+ * canceled → cancelled
64
+ * Unknown types fall back to "open" rather than failing the whole list.
65
+ */
66
+ export declare function rigStatusForLinearStateType(stateType: string | undefined): TaskRecord["status"];
67
+ /**
68
+ * Rig task status → Linear workflow-state type for lifecycle writeback.
69
+ * Returns null when Linear has no workflow equivalent (e.g. "blocked") — the
70
+ * adapter then leaves the workflow state untouched and relies on comments.
71
+ */
72
+ export declare function linearStateTypeForRigStatus(status: TaskRecord["status"]): "unstarted" | "started" | "completed" | "canceled" | null;
73
+ export declare const RIG_METADATA_START = "<!-- rig:metadata:start -->";
74
+ export declare const RIG_METADATA_END = "<!-- rig:metadata:end -->";
75
+ /** Insert or replace the Rig-owned metadata block in an issue description. */
76
+ export declare function updateRigOwnedMetadataBlock(body: string, metadata: Record<string, unknown>): string;
77
+ export interface LinearIssuesTaskSource extends RegisteredTaskSource {
78
+ get(id: string): Promise<TaskRecord | undefined>;
79
+ updateStatus(id: string, status: TaskRecord["status"]): Promise<void>;
80
+ updateTask(id: string, update: TaskSourceUpdate): Promise<void>;
81
+ }
82
+ export declare function createLinearIssuesTaskSource(opts: LinearIssuesOptions): LinearIssuesTaskSource;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/linear-plugin",
3
- "version": "0.0.6-alpha.78",
3
+ "version": "0.0.6-alpha.80",
4
4
  "type": "module",
5
5
  "description": "Rig package",
6
6
  "license": "UNLICENSED",
@@ -10,6 +10,7 @@
10
10
  ],
11
11
  "exports": {
12
12
  ".": {
13
+ "types": "./dist/src/index.d.ts",
13
14
  "import": "./dist/src/index.js"
14
15
  }
15
16
  },
@@ -18,9 +19,10 @@
18
19
  },
19
20
  "main": "./dist/src/index.js",
20
21
  "module": "./dist/src/index.js",
22
+ "types": "./dist/src/index.d.ts",
21
23
  "dependencies": {
22
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.78",
23
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.78",
24
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.80",
25
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.80",
24
26
  "effect": "4.0.0-beta.78"
25
27
  }
26
28
  }