@aaronshaf/plane 0.1.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/.github/workflows/ci.yml +36 -0
- package/.github/workflows/publish.yml +36 -0
- package/.husky/pre-commit +7 -0
- package/README.md +120 -0
- package/bin/plane +2 -0
- package/bun.lock +244 -0
- package/package.json +57 -0
- package/scripts/check-coverage.ts +54 -0
- package/scripts/check-file-size.ts +36 -0
- package/src/api.ts +81 -0
- package/src/bin.ts +72 -0
- package/src/commands/cycles.ts +108 -0
- package/src/commands/init.ts +72 -0
- package/src/commands/intake.ts +85 -0
- package/src/commands/issue.ts +409 -0
- package/src/commands/issues.ts +51 -0
- package/src/commands/labels.ts +54 -0
- package/src/commands/members.ts +20 -0
- package/src/commands/modules.ts +129 -0
- package/src/commands/pages.ts +63 -0
- package/src/commands/projects.ts +24 -0
- package/src/commands/states.ts +28 -0
- package/src/config.ts +217 -0
- package/src/format.ts +11 -0
- package/src/resolve.ts +76 -0
- package/tests/api.test.ts +169 -0
- package/tests/cycles-extended.test.ts +183 -0
- package/tests/format.test.ts +60 -0
- package/tests/helpers/mock-api.ts +27 -0
- package/tests/intake.test.ts +157 -0
- package/tests/issue-activity.test.ts +167 -0
- package/tests/issue-commands.test.ts +322 -0
- package/tests/issue-comments-worklogs.test.ts +291 -0
- package/tests/issue-links.test.ts +206 -0
- package/tests/modules.test.ts +255 -0
- package/tests/new-schemas.test.ts +201 -0
- package/tests/new-schemas2.test.ts +205 -0
- package/tests/pages.test.ts +124 -0
- package/tests/resolve.test.ts +178 -0
- package/tests/schemas.test.ts +203 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
|
+
import { Effect, Schema } from "effect"
|
|
3
|
+
import {
|
|
4
|
+
StateSchema,
|
|
5
|
+
IssueSchema,
|
|
6
|
+
IssuesResponseSchema,
|
|
7
|
+
StatesResponseSchema,
|
|
8
|
+
ProjectSchema,
|
|
9
|
+
ProjectsResponseSchema,
|
|
10
|
+
LabelSchema,
|
|
11
|
+
LabelsResponseSchema,
|
|
12
|
+
MemberSchema,
|
|
13
|
+
MembersResponseSchema,
|
|
14
|
+
CycleSchema,
|
|
15
|
+
CyclesResponseSchema,
|
|
16
|
+
} from "@/config"
|
|
17
|
+
|
|
18
|
+
async function decode<A, I>(schema: Schema.Schema<A, I>, data: unknown): Promise<A> {
|
|
19
|
+
return Effect.runPromise(
|
|
20
|
+
Schema.decodeUnknown(schema)(data).pipe(Effect.mapError((e) => new Error(String(e)))),
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("StateSchema", () => {
|
|
25
|
+
it("decodes a valid state", async () => {
|
|
26
|
+
const state = await decode(StateSchema, { id: "s1", name: "In Progress", group: "started" })
|
|
27
|
+
expect(state.id).toBe("s1")
|
|
28
|
+
expect(state.group).toBe("started")
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("accepts optional color", async () => {
|
|
32
|
+
const state = await decode(StateSchema, {
|
|
33
|
+
id: "s1",
|
|
34
|
+
name: "Done",
|
|
35
|
+
group: "completed",
|
|
36
|
+
color: "#00ff00",
|
|
37
|
+
})
|
|
38
|
+
expect(state.color).toBe("#00ff00")
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("rejects missing required fields", async () => {
|
|
42
|
+
await expect(decode(StateSchema, { id: "s1" })).rejects.toThrow()
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe("IssueSchema", () => {
|
|
47
|
+
const base = {
|
|
48
|
+
id: "i1",
|
|
49
|
+
sequence_id: 42,
|
|
50
|
+
name: "Fix bug",
|
|
51
|
+
priority: "high",
|
|
52
|
+
state: "uuid-state",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
it("decodes with string state", async () => {
|
|
56
|
+
const issue = await decode(IssueSchema, base)
|
|
57
|
+
expect(issue.sequence_id).toBe(42)
|
|
58
|
+
expect(issue.state).toBe("uuid-state")
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it("decodes with object state", async () => {
|
|
62
|
+
const issue = await decode(IssueSchema, {
|
|
63
|
+
...base,
|
|
64
|
+
state: { id: "s1", name: "In Progress", group: "started" },
|
|
65
|
+
})
|
|
66
|
+
expect(typeof issue.state).toBe("object")
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it("accepts null description_html", async () => {
|
|
70
|
+
const issue = await decode(IssueSchema, { ...base, description_html: null })
|
|
71
|
+
expect(issue.description_html).toBeNull()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it("rejects missing name", async () => {
|
|
75
|
+
await expect(decode(IssueSchema, { ...base, name: undefined })).rejects.toThrow()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe("IssuesResponseSchema", () => {
|
|
80
|
+
it("decodes results array", async () => {
|
|
81
|
+
const data = {
|
|
82
|
+
results: [
|
|
83
|
+
{ id: "i1", sequence_id: 1, name: "Issue 1", priority: "low", state: "s1" },
|
|
84
|
+
{ id: "i2", sequence_id: 2, name: "Issue 2", priority: "high", state: "s2" },
|
|
85
|
+
],
|
|
86
|
+
}
|
|
87
|
+
const resp = await decode(IssuesResponseSchema, data)
|
|
88
|
+
expect(resp.results).toHaveLength(2)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("decodes empty results", async () => {
|
|
92
|
+
const resp = await decode(IssuesResponseSchema, { results: [] })
|
|
93
|
+
expect(resp.results).toHaveLength(0)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe("StatesResponseSchema", () => {
|
|
98
|
+
it("decodes results", async () => {
|
|
99
|
+
const resp = await decode(StatesResponseSchema, {
|
|
100
|
+
results: [{ id: "s1", name: "Backlog", group: "backlog" }],
|
|
101
|
+
})
|
|
102
|
+
expect(resp.results[0].name).toBe("Backlog")
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe("ProjectSchema", () => {
|
|
107
|
+
it("decodes a project", async () => {
|
|
108
|
+
const p = await decode(ProjectSchema, { id: "p1", identifier: "ACME", name: "Acme Project" })
|
|
109
|
+
expect(p.identifier).toBe("ACME")
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it("accepts optional description", async () => {
|
|
113
|
+
const p = await decode(ProjectSchema, {
|
|
114
|
+
id: "p1",
|
|
115
|
+
identifier: "ACME",
|
|
116
|
+
name: "InstUI",
|
|
117
|
+
description: "desc",
|
|
118
|
+
})
|
|
119
|
+
expect(p.description).toBe("desc")
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
describe("ProjectsResponseSchema", () => {
|
|
124
|
+
it("decodes results", async () => {
|
|
125
|
+
const resp = await decode(ProjectsResponseSchema, {
|
|
126
|
+
results: [{ id: "p1", identifier: "WEB", name: "Web Project" }],
|
|
127
|
+
})
|
|
128
|
+
expect(resp.results[0].identifier).toBe("WEB")
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe("LabelSchema", () => {
|
|
133
|
+
it("decodes a label", async () => {
|
|
134
|
+
const label = await decode(LabelSchema, { id: "l1", name: "bug" })
|
|
135
|
+
expect(label.name).toBe("bug")
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it("accepts null color", async () => {
|
|
139
|
+
const label = await decode(LabelSchema, { id: "l1", name: "bug", color: null })
|
|
140
|
+
expect(label.color).toBeNull()
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
describe("LabelsResponseSchema", () => {
|
|
145
|
+
it("decodes results", async () => {
|
|
146
|
+
const resp = await decode(LabelsResponseSchema, {
|
|
147
|
+
results: [{ id: "l1", name: "bug" }],
|
|
148
|
+
})
|
|
149
|
+
expect(resp.results).toHaveLength(1)
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe("MemberSchema", () => {
|
|
154
|
+
it("decodes a member", async () => {
|
|
155
|
+
const m = await decode(MemberSchema, {
|
|
156
|
+
id: "u1",
|
|
157
|
+
display_name: "Aaron",
|
|
158
|
+
email: "aaron@example.com",
|
|
159
|
+
})
|
|
160
|
+
expect(m.display_name).toBe("Aaron")
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it("accepts null email", async () => {
|
|
164
|
+
const m = await decode(MemberSchema, { id: "u1", display_name: "Aaron", email: null })
|
|
165
|
+
expect(m.email).toBeNull()
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe("MembersResponseSchema (flat array)", () => {
|
|
170
|
+
it("decodes a flat array", async () => {
|
|
171
|
+
const members = await decode(MembersResponseSchema, [
|
|
172
|
+
{ id: "u1", display_name: "Aaron" },
|
|
173
|
+
{ id: "u2", display_name: "Bea" },
|
|
174
|
+
])
|
|
175
|
+
expect(members).toHaveLength(2)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
describe("CycleSchema", () => {
|
|
180
|
+
it("decodes a cycle", async () => {
|
|
181
|
+
const c = await decode(CycleSchema, { id: "c1", name: "Sprint 1" })
|
|
182
|
+
expect(c.name).toBe("Sprint 1")
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it("accepts optional dates", async () => {
|
|
186
|
+
const c = await decode(CycleSchema, {
|
|
187
|
+
id: "c1",
|
|
188
|
+
name: "Sprint 1",
|
|
189
|
+
start_date: "2025-01-01",
|
|
190
|
+
end_date: "2025-01-14",
|
|
191
|
+
})
|
|
192
|
+
expect(c.start_date).toBe("2025-01-01")
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
describe("CyclesResponseSchema", () => {
|
|
197
|
+
it("decodes results", async () => {
|
|
198
|
+
const resp = await decode(CyclesResponseSchema, {
|
|
199
|
+
results: [{ id: "c1", name: "Sprint 1" }],
|
|
200
|
+
})
|
|
201
|
+
expect(resp.results[0].id).toBe("c1")
|
|
202
|
+
})
|
|
203
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["ESNext"],
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"types": ["bun-types"],
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": {
|
|
17
|
+
"@/*": ["src/*"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*.ts", "tests/**/*.ts", "scripts/**/*.ts", "bin/**/*.ts"]
|
|
21
|
+
}
|