Skip to content

Commit 29840b5

Browse files
authored
feat: add Linear GraphQL API emulator (#5)
* feat: add Linear GraphQL API emulator Port vercel-labs/emulate#91 (Phase 1 Linear emulator) to the @pleaseai fork conventions: - New @pleaseai/emulate-linear package under packages/linear/ (bun export condition, @emulators/core ^0.6.0 + graphql dependency, tsgo type-check) - Register linear in the CLI service registry (packages/emulate) - bun:test instead of vitest; explicit 405 handlers for non-POST /graphql since core's router has no `all`; Hono type sourced from @emulators/core - skills/linear/SKILL.md and README entries adapted to fork CLI usage Read-only GraphQL: issues, projects, teams, users, organizations, labels, and workflow states with Relay-style pagination. 26 tests pass. Original PR: vercel-labs/emulate#91 by @mvanhorn * chore(linear): apply code review suggestions - store: index organization_id (users/teams), lead_id (projects), state_id/assignee_id/creator_id (issues) — all fields queried via findBy, per the fork's store convention (avoids full-scan fallback) - resolvers: use Object.hasOwn for directValue field access (own-property only) - tests: cover 405 responses for non-POST methods on /graphql
1 parent 36d5cde commit 29840b5

17 files changed

Lines changed: 1775 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ using [`@emulators/core`](https://www.npmjs.com/package/@emulators/core).
2121
| `tosspayments` | 4002 | Payment confirm/lookup/cancel, order lookup, checkout simulation, webhooks |
2222
| `firebase` | 4003 | Auth (Identity Toolkit REST), Secure Token, FCM v1 |
2323
| `supabase` | 4004 | GoTrue Auth (signup/token/user), PostgREST table CRUD + filters |
24+
| `linear` | 4005 | Linear GraphQL API (read-only): issues, projects, teams, users, orgs, labels, workflow states, Relay pagination |
2425

2526
## Getting started
2627

@@ -111,6 +112,7 @@ packages/
111112
toss-payments/ # @pleaseai/emulate-toss-payments
112113
firebase/ # @pleaseai/emulate-firebase
113114
supabase/ # @pleaseai/emulate-supabase
115+
linear/ # @pleaseai/emulate-linear
114116
docs/
115117
EMULATOR-CONVENTIONS.md # guide for adding new emulators
116118
```

bun.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/emulate/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"tosspayments",
2222
"firebase",
2323
"supabase",
24+
"linear",
2425
"oauth",
2526
"testing",
2627
"ci",
@@ -55,6 +56,7 @@
5556
"@emulators/core": "^0.6.0",
5657
"@pleaseai/emulate-firebase": "workspace:*",
5758
"@pleaseai/emulate-kakao": "workspace:*",
59+
"@pleaseai/emulate-linear": "workspace:*",
5860
"@pleaseai/emulate-naver": "workspace:*",
5961
"@pleaseai/emulate-supabase": "workspace:*",
6062
"@pleaseai/emulate-toss-payments": "workspace:*",

packages/emulate/src/registry.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface ServiceEntry {
2020
initConfig: Record<string, unknown>
2121
}
2222

23-
const SERVICE_NAME_LIST = ['kakao', 'naver', 'tosspayments', 'firebase', 'supabase'] as const
23+
const SERVICE_NAME_LIST = ['kakao', 'naver', 'tosspayments', 'firebase', 'supabase', 'linear'] as const
2424
export type ServiceName = (typeof SERVICE_NAME_LIST)[number]
2525
export const SERVICE_NAMES: readonly ServiceName[] = SERVICE_NAME_LIST
2626

@@ -173,4 +173,30 @@ export const SERVICE_REGISTRY: Record<ServiceName, ServiceEntry> = {
173173
},
174174
},
175175
},
176+
177+
linear: {
178+
label: 'Linear GraphQL API emulator',
179+
endpoints:
180+
'GraphQL queries for issues, projects, teams, users, organizations, labels, workflow states with Relay-style pagination',
181+
async load() {
182+
const mod = await import('@pleaseai/emulate-linear')
183+
return { plugin: mod.linearPlugin, seedFromConfig: widenSeed(mod.seedFromConfig) }
184+
},
185+
defaultFallback() {
186+
return { login: 'linear-admin', id: 1, scopes: ['read'] }
187+
},
188+
initConfig: {
189+
linear: {
190+
api_keys: ['lin_api_test'],
191+
organizations: [{ id: 'org-1', name: 'My Org' }],
192+
teams: [{ id: 'team-1', name: 'Engineering', key: 'ENG', organization: 'org-1' }],
193+
users: [{ id: 'user-1', name: 'Developer', email: 'dev@example.com', organization: 'org-1' }],
194+
workflow_states: [
195+
{ id: 'ws-1', name: 'Todo', type: 'unstarted', team: 'team-1' },
196+
{ id: 'ws-2', name: 'In Progress', type: 'started', team: 'team-1' },
197+
],
198+
issues: [{ id: 'issue-1', title: 'First issue', team: 'team-1', state: 'ws-1', assignee: 'user-1' }],
199+
},
200+
},
201+
},
176202
}

packages/linear/README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# @pleaseai/emulate-linear
2+
3+
Linear GraphQL API emulator for local development and CI.
4+
5+
This package is Phase 1. It includes `POST /graphql`, schema introspection, PAT authentication, read only query resolvers, and Relay style pagination for issues, projects, teams, users, organizations, labels, and workflow states.
6+
7+
Mutations, webhooks, OAuth 2.0, and an inspector UI are follow up work.
8+
9+
## Install
10+
11+
```bash
12+
npm install @pleaseai/emulate-linear
13+
```
14+
15+
Usually you do not install this directly — run it through the `@pleaseai/emulate` CLI.
16+
17+
## Start
18+
19+
```bash
20+
# Through the emulate CLI
21+
npx @pleaseai/emulate --service linear
22+
23+
# From the monorepo (after `bun install && bun run build`)
24+
bun packages/emulate/dist/index.js --service linear
25+
```
26+
27+
A single service starts on the base port (default `4000`); use `-p <port>` to change it.
28+
29+
## Auth
30+
31+
Use the raw Linear PAT header:
32+
33+
```bash
34+
curl http://localhost:4000/graphql \
35+
-H "Authorization: lin_api_test" \
36+
-H "Content-Type: application/json" \
37+
-d '{"query":"{ issues { nodes { id title } } }"}'
38+
```
39+
40+
## Seed Config
41+
42+
```yaml
43+
linear:
44+
api_keys: [lin_api_test]
45+
organizations:
46+
- id: org-1
47+
name: My Org
48+
teams:
49+
- id: team-1
50+
name: Engineering
51+
key: ENG
52+
organization: org-1
53+
workflow_states:
54+
- id: ws-1
55+
name: Todo
56+
type: unstarted
57+
team: team-1
58+
users:
59+
- id: user-1
60+
name: Developer
61+
email: dev@example.com
62+
organization: org-1
63+
issues:
64+
- id: issue-1
65+
title: First issue
66+
team: team-1
67+
state: ws-1
68+
assignee: user-1
69+
```

packages/linear/package.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "@pleaseai/emulate-linear",
3+
"type": "module",
4+
"version": "0.1.0",
5+
"license": "Apache-2.0",
6+
"repository": {
7+
"type": "git",
8+
"url": "https://github.com/pleaseai/emulate.git",
9+
"directory": "packages/linear"
10+
},
11+
"exports": {
12+
".": {
13+
"bun": "./src/index.ts",
14+
"types": "./dist/index.d.ts",
15+
"import": "./dist/index.js"
16+
}
17+
},
18+
"main": "./dist/index.js",
19+
"types": "./dist/index.d.ts",
20+
"publishConfig": {
21+
"access": "public"
22+
},
23+
"files": [
24+
"dist"
25+
],
26+
"scripts": {
27+
"build": "tsup --clean",
28+
"dev": "tsup --watch",
29+
"test": "bun test",
30+
"clean": "rm -rf dist .turbo",
31+
"type-check": "tsgo --noEmit"
32+
},
33+
"dependencies": {
34+
"@emulators/core": "^0.6.0",
35+
"graphql": "^16.13.2"
36+
},
37+
"devDependencies": {
38+
"tsup": "^8",
39+
"typescript": "^6"
40+
}
41+
}

0 commit comments

Comments
 (0)