@useairfoil/producer-linear 0.0.1 → 0.0.3
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.txt +201 -0
- package/README.md +71 -0
- package/dist/index.d.mts +4027 -0
- package/dist/index.mjs +3 -0
- package/dist/main.d.mts +1 -0
- package/dist/main.mjs +1305 -0
- package/dist/main.mjs.map +1 -0
- package/dist/manifest.d.mts +114 -0
- package/dist/manifest.mjs +161 -0
- package/dist/manifest.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/src-BbzySPR7.mjs +1688 -0
- package/dist/src-BbzySPR7.mjs.map +1 -0
- package/package.json +46 -8
- package/src/client/client.ts +200 -0
- package/src/connector.ts +151 -0
- package/src/create-table.ts +10 -0
- package/src/demo/index.ts +137 -0
- package/src/demo/initiatives.ts +116 -0
- package/src/demo/issues.ts +320 -0
- package/src/demo/projects.ts +215 -0
- package/src/demo/setup.ts +151 -0
- package/src/demo/shared.ts +122 -0
- package/src/demo/teams.ts +173 -0
- package/src/index.ts +83 -0
- package/src/main.ts +28 -0
- package/src/manifest.ts +58 -0
- package/src/resources/comments.ts +32 -0
- package/src/resources/customers.ts +105 -0
- package/src/resources/documents.ts +31 -0
- package/src/resources/initiatives.ts +136 -0
- package/src/resources/issue-history.ts +119 -0
- package/src/resources/issues.ts +131 -0
- package/src/resources/pages.ts +264 -0
- package/src/resources/projects.ts +161 -0
- package/src/resources/releases.ts +114 -0
- package/src/resources/teams.ts +140 -0
- package/src/sandbox.ts +35 -0
- package/src/schemas/comments.ts +52 -0
- package/src/schemas/customers.ts +96 -0
- package/src/schemas/documents.ts +40 -0
- package/src/schemas/initiatives.ts +155 -0
- package/src/schemas/issues.ts +262 -0
- package/src/schemas/projects.ts +210 -0
- package/src/schemas/releases.ts +132 -0
- package/src/schemas/shared.ts +68 -0
- package/src/schemas/teams.ts +177 -0
- package/src/start.ts +47 -0
- package/src/tables.ts +83 -0
- package/src/webhook/route.ts +50 -0
- package/src/webhook/signature.ts +28 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Demo } from "@useairfoil/connector-kit";
|
|
2
|
+
|
|
3
|
+
import { DateTime, Duration } from "effect";
|
|
4
|
+
|
|
5
|
+
import type { Comment } from "../schemas/comments";
|
|
6
|
+
import type { CustomerNeed } from "../schemas/customers";
|
|
7
|
+
import type { Attachment, Issue, IssueHistory, IssueRelation } from "../schemas/issues";
|
|
8
|
+
import type { IssueToRelease } from "../schemas/releases";
|
|
9
|
+
|
|
10
|
+
export const hour = Duration.toMillis(Duration.hours(1));
|
|
11
|
+
export const day = Duration.toMillis(Duration.days(1));
|
|
12
|
+
export const workspace = "https://linear.app/acme";
|
|
13
|
+
|
|
14
|
+
// Linear IDs are UUIDs. Each kind gets its own first block, and the index ends the ID.
|
|
15
|
+
export const uuid = (kind: number, index: number, part = 0) =>
|
|
16
|
+
`${kind.toString(16).padStart(8, "0")}-${part.toString(16).padStart(4, "0")}-4000-8000-${index.toString(16).padStart(12, "0")}`;
|
|
17
|
+
|
|
18
|
+
export const dateOnly = (date: Date) => DateTime.formatIsoDateUtc(DateTime.fromDateUnsafe(date));
|
|
19
|
+
|
|
20
|
+
export const teams = [
|
|
21
|
+
{ key: "ENG", name: "Engineering", cycles: true, triage: false },
|
|
22
|
+
{ key: "DES", name: "Design", cycles: true, triage: false },
|
|
23
|
+
{ key: "PRD", name: "Product", cycles: false, triage: false },
|
|
24
|
+
{ key: "SUP", name: "Support", cycles: false, triage: true },
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
export const states = [
|
|
28
|
+
{ name: "Triage", type: "triage", color: "#FC7840" },
|
|
29
|
+
{ name: "Backlog", type: "backlog", color: "#BEC2C8" },
|
|
30
|
+
{ name: "Todo", type: "unstarted", color: "#E2E2E2" },
|
|
31
|
+
{ name: "In Progress", type: "started", color: "#F2C94C" },
|
|
32
|
+
{ name: "In Review", type: "started", color: "#0F783C" },
|
|
33
|
+
{ name: "Done", type: "completed", color: "#5E6AD2" },
|
|
34
|
+
{ name: "Canceled", type: "canceled", color: "#95A2B3" },
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
export const issueLabels = [
|
|
38
|
+
{ name: "Bug", color: "#EB5757" },
|
|
39
|
+
{ name: "Feature", color: "#BB87FC" },
|
|
40
|
+
{ name: "Improvement", color: "#4EA7FC" },
|
|
41
|
+
{ name: "Docs", color: "#4CB782" },
|
|
42
|
+
] as const;
|
|
43
|
+
|
|
44
|
+
export const projectStatuses = [
|
|
45
|
+
{ name: "Backlog", type: "backlog", color: "#bec2c8" },
|
|
46
|
+
{ name: "Planned", type: "planned", color: "#e2e2e2" },
|
|
47
|
+
{ name: "In Progress", type: "started", color: "#f2c94c" },
|
|
48
|
+
{ name: "Paused", type: "paused", color: "#95a2b3" },
|
|
49
|
+
{ name: "Completed", type: "completed", color: "#5e6ad2" },
|
|
50
|
+
{ name: "Canceled", type: "canceled", color: "#95a2b3" },
|
|
51
|
+
] as const;
|
|
52
|
+
|
|
53
|
+
export const projects = [
|
|
54
|
+
"Onboarding revamp",
|
|
55
|
+
"Billing v2",
|
|
56
|
+
"Mobile offline mode",
|
|
57
|
+
"Search relevance",
|
|
58
|
+
"SSO for enterprise",
|
|
59
|
+
"Design system 2.0",
|
|
60
|
+
"Usage analytics",
|
|
61
|
+
"API rate limits",
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
64
|
+
export const priorities = ["No priority", "Urgent", "High", "Medium", "Low"] as const;
|
|
65
|
+
export const userCount = 16;
|
|
66
|
+
export const customerCount = 12;
|
|
67
|
+
export const months = 12;
|
|
68
|
+
export const cycleWeeks = 2;
|
|
69
|
+
export const cyclesBack = 26;
|
|
70
|
+
|
|
71
|
+
export const teamId = (t: number) => uuid(1, t);
|
|
72
|
+
export const stateId = (t: number, s: number) => uuid(2, t, s);
|
|
73
|
+
export const userId = (u: number) => uuid(3, u);
|
|
74
|
+
export const labelId = (l: number) => uuid(4, l);
|
|
75
|
+
export const projectId = (p: number) => uuid(5, p);
|
|
76
|
+
export const milestoneId = (p: number, m: number) => uuid(6, p, m);
|
|
77
|
+
export const cycleId = (t: number, n: number) => uuid(7, t, n);
|
|
78
|
+
export const customerId = (c: number) => uuid(8, c);
|
|
79
|
+
export const pipelineId = uuid(9, 0);
|
|
80
|
+
export const releaseId = (m: number) => uuid(10, m);
|
|
81
|
+
export const initiativeId = (i: number) => uuid(11, i);
|
|
82
|
+
export const issueId = (index: number) => uuid(12, index);
|
|
83
|
+
|
|
84
|
+
// Cycles end on the Monday nearest now, so the last one is current.
|
|
85
|
+
export const cycleStart = (now: Date, n: number) =>
|
|
86
|
+
new Date(now.getTime() - (cyclesBack - n) * cycleWeeks * 7 * day);
|
|
87
|
+
|
|
88
|
+
export const stamp = (created: Date, updated: Date) => ({
|
|
89
|
+
version: updated,
|
|
90
|
+
created_at: created,
|
|
91
|
+
updated_at: updated,
|
|
92
|
+
archived_at: null,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export const nameFor = (faker: Demo.DemoContext["faker"]) => {
|
|
96
|
+
const first = faker.person.firstName();
|
|
97
|
+
const last = faker.person.lastName();
|
|
98
|
+
|
|
99
|
+
return { first, last, name: `${first} ${last}` };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Date helpers for one run. `at` keeps a time from going past now. */
|
|
103
|
+
export const clock = ({ faker, now }: Demo.DemoContext) => {
|
|
104
|
+
const at = (time: number) => new Date(Math.min(time, now.getTime()));
|
|
105
|
+
|
|
106
|
+
const later = (from: Date, min: number, max: number) =>
|
|
107
|
+
at(from.getTime() + faker.number.int({ min, max }));
|
|
108
|
+
|
|
109
|
+
return { at, later };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type Rows = {
|
|
113
|
+
readonly issues: Array<Issue>;
|
|
114
|
+
readonly issue_history: Array<IssueHistory>;
|
|
115
|
+
readonly comments: Array<Comment>;
|
|
116
|
+
readonly issue_relations: Array<IssueRelation>;
|
|
117
|
+
readonly attachments: Array<Attachment>;
|
|
118
|
+
readonly customer_needs: Array<CustomerNeed>;
|
|
119
|
+
readonly issue_to_releases: Array<IssueToRelease>;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const verbs = ["Fix", "Add", "Improve", "Update", "Remove", "Support"] as const;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { Demo } from "@useairfoil/connector-kit";
|
|
2
|
+
|
|
3
|
+
import type { IssueLabel } from "../schemas/issues";
|
|
4
|
+
import type {
|
|
5
|
+
Cycle,
|
|
6
|
+
ExternalUser,
|
|
7
|
+
Team,
|
|
8
|
+
TeamMembership,
|
|
9
|
+
User,
|
|
10
|
+
WorkflowState,
|
|
11
|
+
} from "../schemas/teams";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
cycleId,
|
|
15
|
+
cyclesBack,
|
|
16
|
+
cycleStart,
|
|
17
|
+
cycleWeeks,
|
|
18
|
+
day,
|
|
19
|
+
issueLabels,
|
|
20
|
+
labelId,
|
|
21
|
+
nameFor,
|
|
22
|
+
stamp,
|
|
23
|
+
stateId,
|
|
24
|
+
states,
|
|
25
|
+
teamId,
|
|
26
|
+
teams,
|
|
27
|
+
userCount,
|
|
28
|
+
userId,
|
|
29
|
+
uuid,
|
|
30
|
+
workspace,
|
|
31
|
+
} from "./shared";
|
|
32
|
+
|
|
33
|
+
/** Teams with their workflow states, users, labels, and cycles. */
|
|
34
|
+
export const teamTables = ({ faker, now }: Demo.DemoContext, founded: Date) => {
|
|
35
|
+
const set = stamp(founded, founded);
|
|
36
|
+
|
|
37
|
+
const teamRows: Array<Team> = teams.map((team, t) => ({
|
|
38
|
+
id: teamId(t),
|
|
39
|
+
...set,
|
|
40
|
+
name: team.name,
|
|
41
|
+
key: team.key,
|
|
42
|
+
display_name: team.name,
|
|
43
|
+
description: null,
|
|
44
|
+
visibility: "public",
|
|
45
|
+
parent_id: null,
|
|
46
|
+
timezone: "America/New_York",
|
|
47
|
+
cycles_enabled: team.cycles,
|
|
48
|
+
cycle_duration: cycleWeeks,
|
|
49
|
+
cycle_cooldown_time: 0,
|
|
50
|
+
cycle_start_day: 1,
|
|
51
|
+
triage_enabled: team.triage,
|
|
52
|
+
issue_estimation_type: team.key === "ENG" ? "fibonacci" : "notUsed",
|
|
53
|
+
default_issue_estimate: team.key === "ENG" ? 1 : 0,
|
|
54
|
+
auto_archive_period: 6,
|
|
55
|
+
auto_close_period: 6,
|
|
56
|
+
retired_at: null,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
const stateRows: Array<WorkflowState> = teams.flatMap((team, t) =>
|
|
60
|
+
states
|
|
61
|
+
.filter((state) => state.type !== "triage" || team.triage)
|
|
62
|
+
.map((state, s) => ({
|
|
63
|
+
id: stateId(t, states.indexOf(state)),
|
|
64
|
+
...set,
|
|
65
|
+
name: state.name,
|
|
66
|
+
type: state.type,
|
|
67
|
+
color: state.color,
|
|
68
|
+
description: null,
|
|
69
|
+
position: s,
|
|
70
|
+
team_id: teamId(t),
|
|
71
|
+
})),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const users: Array<User> = Array.from({ length: userCount }, (_, u) => {
|
|
75
|
+
const person = nameFor(faker);
|
|
76
|
+
const joined = new Date(founded.getTime() + u * 20 * day);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id: userId(u),
|
|
80
|
+
...stamp(joined, joined),
|
|
81
|
+
_deleted: false,
|
|
82
|
+
name: person.name,
|
|
83
|
+
display_name: person.first.toLowerCase(),
|
|
84
|
+
email: faker.internet
|
|
85
|
+
.exampleEmail({ firstName: person.first, lastName: person.last })
|
|
86
|
+
.toLowerCase(),
|
|
87
|
+
avatar_url: null,
|
|
88
|
+
title: faker.person.jobTitle(),
|
|
89
|
+
timezone: faker.helpers.arrayElement(["America/New_York", "Europe/London", "Europe/Berlin"]),
|
|
90
|
+
active: true,
|
|
91
|
+
admin: u < 2,
|
|
92
|
+
owner: u === 0,
|
|
93
|
+
guest: false,
|
|
94
|
+
app: false,
|
|
95
|
+
last_seen: new Date(now.getTime() - faker.number.int({ min: 60_000, max: 3 * day })),
|
|
96
|
+
url: `${workspace}/profiles/${person.first.toLowerCase()}`,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Each user is on one team; the first user of each team owns it.
|
|
101
|
+
const memberships: Array<TeamMembership> = users.map((user, u) => ({
|
|
102
|
+
id: uuid(13, u),
|
|
103
|
+
...stamp(user.created_at, user.created_at),
|
|
104
|
+
team_id: teamId(u % teams.length),
|
|
105
|
+
user_id: user.id,
|
|
106
|
+
owner: u < teams.length,
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
const externalUsers: Array<ExternalUser> = ["Slack", "Intercom", "Zendesk"].map((source, e) => {
|
|
110
|
+
const person = nameFor(faker);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
id: uuid(14, e),
|
|
114
|
+
...set,
|
|
115
|
+
name: person.name,
|
|
116
|
+
display_name: `${person.name} (${source})`,
|
|
117
|
+
email: faker.internet
|
|
118
|
+
.exampleEmail({ firstName: person.first, lastName: person.last })
|
|
119
|
+
.toLowerCase(),
|
|
120
|
+
avatar_url: null,
|
|
121
|
+
last_seen: new Date(now.getTime() - faker.number.int({ min: day, max: 30 * day })),
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const labelRows: Array<IssueLabel> = issueLabels.map((label, l) => ({
|
|
126
|
+
id: labelId(l),
|
|
127
|
+
...set,
|
|
128
|
+
_deleted: false,
|
|
129
|
+
name: label.name,
|
|
130
|
+
description: null,
|
|
131
|
+
color: label.color,
|
|
132
|
+
is_group: false,
|
|
133
|
+
parent_id: null,
|
|
134
|
+
team_id: null,
|
|
135
|
+
creator_id: userId(0),
|
|
136
|
+
retired_at: null,
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
const cycles: Array<Cycle> = teams.flatMap((team, t) =>
|
|
140
|
+
team.cycles
|
|
141
|
+
? Array.from({ length: cyclesBack + 1 }, (_, n) => {
|
|
142
|
+
const starts = cycleStart(now, n);
|
|
143
|
+
const ends = new Date(starts.getTime() + cycleWeeks * 7 * day);
|
|
144
|
+
const done = ends <= now;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
id: cycleId(t, n),
|
|
148
|
+
...stamp(starts, done ? ends : now),
|
|
149
|
+
_deleted: false,
|
|
150
|
+
number: n + 1,
|
|
151
|
+
name: null,
|
|
152
|
+
description: null,
|
|
153
|
+
team_id: teamId(t),
|
|
154
|
+
starts_at: starts,
|
|
155
|
+
ends_at: ends,
|
|
156
|
+
completed_at: done ? ends : null,
|
|
157
|
+
auto_archived_at: null,
|
|
158
|
+
progress: done ? 1 : 0.4,
|
|
159
|
+
};
|
|
160
|
+
})
|
|
161
|
+
: [],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
teams: teamRows,
|
|
166
|
+
workflow_states: stateRows,
|
|
167
|
+
users,
|
|
168
|
+
team_memberships: memberships,
|
|
169
|
+
external_users: externalUsers,
|
|
170
|
+
issue_labels: labelRows,
|
|
171
|
+
cycles,
|
|
172
|
+
};
|
|
173
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export * as LinearClient from "./client/client";
|
|
2
|
+
export * as LinearConnector from "./connector";
|
|
3
|
+
export { manifest } from "./manifest";
|
|
4
|
+
export { webhookPath } from "./webhook/route";
|
|
5
|
+
export type { Comment } from "./schemas/comments";
|
|
6
|
+
export { CommentSchema } from "./schemas/comments";
|
|
7
|
+
export type { Customer, CustomerNeed, CustomerStatus, CustomerTier } from "./schemas/customers";
|
|
8
|
+
export {
|
|
9
|
+
CustomerNeedSchema,
|
|
10
|
+
CustomerSchema,
|
|
11
|
+
CustomerStatusSchema,
|
|
12
|
+
CustomerTierSchema,
|
|
13
|
+
} from "./schemas/customers";
|
|
14
|
+
export type { Document } from "./schemas/documents";
|
|
15
|
+
export { DocumentSchema } from "./schemas/documents";
|
|
16
|
+
export type {
|
|
17
|
+
Initiative,
|
|
18
|
+
InitiativeLabel,
|
|
19
|
+
InitiativeRelation,
|
|
20
|
+
InitiativeToProject,
|
|
21
|
+
InitiativeUpdate,
|
|
22
|
+
} from "./schemas/initiatives";
|
|
23
|
+
export {
|
|
24
|
+
InitiativeLabelSchema,
|
|
25
|
+
InitiativeRelationSchema,
|
|
26
|
+
InitiativeSchema,
|
|
27
|
+
InitiativeToProjectSchema,
|
|
28
|
+
InitiativeUpdateSchema,
|
|
29
|
+
} from "./schemas/initiatives";
|
|
30
|
+
export type { Attachment, Issue, IssueHistory, IssueLabel, IssueRelation } from "./schemas/issues";
|
|
31
|
+
export {
|
|
32
|
+
AttachmentSchema,
|
|
33
|
+
IssueHistorySchema,
|
|
34
|
+
IssueLabelSchema,
|
|
35
|
+
IssueRelationSchema,
|
|
36
|
+
IssueSchema,
|
|
37
|
+
} from "./schemas/issues";
|
|
38
|
+
export type {
|
|
39
|
+
Project,
|
|
40
|
+
ProjectLabel,
|
|
41
|
+
ProjectMilestone,
|
|
42
|
+
ProjectRelation,
|
|
43
|
+
ProjectStatus,
|
|
44
|
+
ProjectUpdate,
|
|
45
|
+
} from "./schemas/projects";
|
|
46
|
+
export {
|
|
47
|
+
ProjectLabelSchema,
|
|
48
|
+
ProjectMilestoneSchema,
|
|
49
|
+
ProjectRelationSchema,
|
|
50
|
+
ProjectSchema,
|
|
51
|
+
ProjectStatusSchema,
|
|
52
|
+
ProjectUpdateSchema,
|
|
53
|
+
} from "./schemas/projects";
|
|
54
|
+
export type {
|
|
55
|
+
IssueToRelease,
|
|
56
|
+
Release,
|
|
57
|
+
ReleaseNote,
|
|
58
|
+
ReleasePipeline,
|
|
59
|
+
ReleaseStage,
|
|
60
|
+
} from "./schemas/releases";
|
|
61
|
+
export {
|
|
62
|
+
IssueToReleaseSchema,
|
|
63
|
+
ReleaseNoteSchema,
|
|
64
|
+
ReleasePipelineSchema,
|
|
65
|
+
ReleaseSchema,
|
|
66
|
+
ReleaseStageSchema,
|
|
67
|
+
} from "./schemas/releases";
|
|
68
|
+
export type {
|
|
69
|
+
Cycle,
|
|
70
|
+
ExternalUser,
|
|
71
|
+
Team,
|
|
72
|
+
TeamMembership,
|
|
73
|
+
User,
|
|
74
|
+
WorkflowState,
|
|
75
|
+
} from "./schemas/teams";
|
|
76
|
+
export {
|
|
77
|
+
CycleSchema,
|
|
78
|
+
ExternalUserSchema,
|
|
79
|
+
TeamMembershipSchema,
|
|
80
|
+
TeamSchema,
|
|
81
|
+
UserSchema,
|
|
82
|
+
WorkflowStateSchema,
|
|
83
|
+
} from "./schemas/teams";
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
4
|
+
import { Effect, Layer } from "effect";
|
|
5
|
+
import { Command } from "effect/cli";
|
|
6
|
+
import { FetchHttpClient } from "effect/http";
|
|
7
|
+
|
|
8
|
+
import packageJson from "../package.json";
|
|
9
|
+
import { createTableCommand, setupCommand } from "./create-table";
|
|
10
|
+
import { demoCommand } from "./demo";
|
|
11
|
+
import { sandboxCommand } from "./sandbox";
|
|
12
|
+
import { startCommand } from "./start";
|
|
13
|
+
|
|
14
|
+
const BootstrapLayer = Layer.mergeAll(FetchHttpClient.layer, NodeServices.layer);
|
|
15
|
+
|
|
16
|
+
const program = Command.make("producer-linear", {}, () => Effect.void).pipe(
|
|
17
|
+
Command.withSubcommands([
|
|
18
|
+
setupCommand,
|
|
19
|
+
startCommand,
|
|
20
|
+
sandboxCommand,
|
|
21
|
+
createTableCommand,
|
|
22
|
+
demoCommand,
|
|
23
|
+
]),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const cli = Command.run(program, { version: packageJson.version });
|
|
27
|
+
|
|
28
|
+
NodeRuntime.runMain(cli.pipe(Effect.provide(BootstrapLayer), Effect.scoped));
|
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as Manifest from "@useairfoil/connector-kit/manifest";
|
|
2
|
+
|
|
3
|
+
export const LinearConfigDef = Manifest.defineConfig({
|
|
4
|
+
apiKey: Manifest.secret({
|
|
5
|
+
runtimeKey: "LINEAR_API_KEY",
|
|
6
|
+
description: "Linear personal API key with Read permission, for example lin_api_...",
|
|
7
|
+
}),
|
|
8
|
+
webhookSecret: Manifest.secret({
|
|
9
|
+
runtimeKey: "LINEAR_WEBHOOK_SECRET",
|
|
10
|
+
description: "Signing secret of the Linear webhook.",
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type LinearConfig = Manifest.ConfigValuesOf<typeof LinearConfigDef>;
|
|
15
|
+
|
|
16
|
+
const capabilities = ["backfill", "changes"] as const;
|
|
17
|
+
const withWebhook = ["backfill", "changes", "webhook"] as const;
|
|
18
|
+
|
|
19
|
+
export const manifest = Manifest.define({
|
|
20
|
+
name: "producer-linear",
|
|
21
|
+
title: "Linear",
|
|
22
|
+
config: LinearConfigDef.spec,
|
|
23
|
+
resources: [
|
|
24
|
+
{ name: "issues", capabilities: withWebhook },
|
|
25
|
+
{ name: "issue_history", capabilities: withWebhook },
|
|
26
|
+
{ name: "comments", capabilities: withWebhook },
|
|
27
|
+
{ name: "issue_relations", capabilities },
|
|
28
|
+
{ name: "attachments", capabilities: withWebhook },
|
|
29
|
+
{ name: "issue_labels", capabilities: withWebhook },
|
|
30
|
+
{ name: "workflow_states", capabilities },
|
|
31
|
+
{ name: "teams", capabilities },
|
|
32
|
+
{ name: "team_memberships", capabilities },
|
|
33
|
+
{ name: "users", capabilities: withWebhook },
|
|
34
|
+
{ name: "external_users", capabilities },
|
|
35
|
+
{ name: "cycles", capabilities: withWebhook },
|
|
36
|
+
{ name: "projects", capabilities: withWebhook },
|
|
37
|
+
{ name: "project_milestones", capabilities },
|
|
38
|
+
{ name: "project_updates", capabilities: withWebhook },
|
|
39
|
+
{ name: "project_statuses", capabilities },
|
|
40
|
+
{ name: "project_labels", capabilities: withWebhook },
|
|
41
|
+
{ name: "project_relations", capabilities },
|
|
42
|
+
{ name: "initiatives", capabilities: withWebhook },
|
|
43
|
+
{ name: "initiative_updates", capabilities: withWebhook },
|
|
44
|
+
{ name: "initiative_labels", capabilities },
|
|
45
|
+
{ name: "initiative_relations", capabilities },
|
|
46
|
+
{ name: "initiative_to_projects", capabilities },
|
|
47
|
+
{ name: "documents", capabilities: withWebhook },
|
|
48
|
+
{ name: "customers", capabilities: withWebhook },
|
|
49
|
+
{ name: "customer_needs", capabilities: withWebhook },
|
|
50
|
+
{ name: "customer_statuses", capabilities },
|
|
51
|
+
{ name: "customer_tiers", capabilities },
|
|
52
|
+
{ name: "releases", capabilities },
|
|
53
|
+
{ name: "release_notes", capabilities },
|
|
54
|
+
{ name: "release_pipelines", capabilities },
|
|
55
|
+
{ name: "release_stages", capabilities },
|
|
56
|
+
{ name: "issue_to_releases", capabilities },
|
|
57
|
+
],
|
|
58
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Resource } from "@useairfoil/connector-kit";
|
|
2
|
+
|
|
3
|
+
import type { LinearClientService } from "../client/client";
|
|
4
|
+
|
|
5
|
+
import { CommentObjectSchema, CommentSchema } from "../schemas/comments";
|
|
6
|
+
import { backfillList, changesList, checkList, listSpec, rowsOf, webhookList } from "./pages";
|
|
7
|
+
|
|
8
|
+
export const commentSpec = listSpec({
|
|
9
|
+
connection: "comments",
|
|
10
|
+
single: "comment",
|
|
11
|
+
fields: `id created_at: createdAt updated_at: updatedAt archived_at: archivedAt
|
|
12
|
+
body user_id: user { id } external_user_id: externalUser { id }
|
|
13
|
+
issue_id: issueId project_id: projectId project_update_id: projectUpdateId
|
|
14
|
+
initiative_id: initiativeId initiative_update_id: initiativeUpdateId
|
|
15
|
+
document_content_id: documentContentId parent_id: parentId quoted_text: quotedText
|
|
16
|
+
edited_at: editedAt resolved_at: resolvedAt resolving_user_id: resolvingUser { id } url`,
|
|
17
|
+
item: CommentObjectSchema,
|
|
18
|
+
updatedAt: (comment) => comment.updated_at,
|
|
19
|
+
rows: rowsOf((comment) => ({ ...comment, version: comment.updated_at, _deleted: false })),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const makeComments = (client: LinearClientService) =>
|
|
23
|
+
Resource.entity({
|
|
24
|
+
name: "comments",
|
|
25
|
+
rowSchema: CommentSchema,
|
|
26
|
+
version: "version",
|
|
27
|
+
|
|
28
|
+
check: checkList(client, commentSpec),
|
|
29
|
+
backfill: backfillList(client, commentSpec),
|
|
30
|
+
changes: changesList(client, commentSpec),
|
|
31
|
+
webhook: webhookList(client, commentSpec),
|
|
32
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Resource } from "@useairfoil/connector-kit";
|
|
2
|
+
|
|
3
|
+
import type { LinearClientService } from "../client/client";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
CustomerNeedObjectSchema,
|
|
7
|
+
CustomerNeedSchema,
|
|
8
|
+
CustomerObjectSchema,
|
|
9
|
+
CustomerSchema,
|
|
10
|
+
CustomerStatusObjectSchema,
|
|
11
|
+
CustomerStatusSchema,
|
|
12
|
+
CustomerTierObjectSchema,
|
|
13
|
+
CustomerTierSchema,
|
|
14
|
+
} from "../schemas/customers";
|
|
15
|
+
import {
|
|
16
|
+
backfillList,
|
|
17
|
+
changesList,
|
|
18
|
+
checkList,
|
|
19
|
+
listSources,
|
|
20
|
+
listSpec,
|
|
21
|
+
rowsOf,
|
|
22
|
+
webhookList,
|
|
23
|
+
} from "./pages";
|
|
24
|
+
|
|
25
|
+
const customerSpec = listSpec({
|
|
26
|
+
connection: "customers",
|
|
27
|
+
single: "customer",
|
|
28
|
+
fields: `id created_at: createdAt updated_at: updatedAt archived_at: archivedAt
|
|
29
|
+
name domains external_ids: externalIds logo_url: logoUrl owner_id: owner { id }
|
|
30
|
+
status_id: status { id } tier_id: tier { id } revenue size
|
|
31
|
+
approximate_need_count: approximateNeedCount url`,
|
|
32
|
+
item: CustomerObjectSchema,
|
|
33
|
+
updatedAt: (customer) => customer.updated_at,
|
|
34
|
+
rows: rowsOf((customer) => ({ ...customer, version: customer.updated_at, _deleted: false })),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const makeCustomers = (client: LinearClientService) =>
|
|
38
|
+
Resource.entity({
|
|
39
|
+
name: "customers",
|
|
40
|
+
rowSchema: CustomerSchema,
|
|
41
|
+
version: "version",
|
|
42
|
+
|
|
43
|
+
check: checkList(client, customerSpec),
|
|
44
|
+
backfill: backfillList(client, customerSpec),
|
|
45
|
+
changes: changesList(client, customerSpec),
|
|
46
|
+
webhook: webhookList(client, customerSpec),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const customerNeedSpec = listSpec({
|
|
50
|
+
connection: "customerNeeds",
|
|
51
|
+
single: "customerNeed",
|
|
52
|
+
fields: `id created_at: createdAt updated_at: updatedAt archived_at: archivedAt
|
|
53
|
+
customer_id: customer { id } issue_id: issue { id } project_id: project { id }
|
|
54
|
+
original_issue_id: originalIssue { id } comment_id: comment { id }
|
|
55
|
+
attachment_id: attachment { id } creator_id: creator { id } body priority url`,
|
|
56
|
+
item: CustomerNeedObjectSchema,
|
|
57
|
+
updatedAt: (need) => need.updated_at,
|
|
58
|
+
rows: rowsOf((need) => ({ ...need, version: need.updated_at, _deleted: false })),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export const makeCustomerNeeds = (client: LinearClientService) =>
|
|
62
|
+
Resource.entity({
|
|
63
|
+
name: "customer_needs",
|
|
64
|
+
rowSchema: CustomerNeedSchema,
|
|
65
|
+
version: "version",
|
|
66
|
+
|
|
67
|
+
check: checkList(client, customerNeedSpec),
|
|
68
|
+
backfill: backfillList(client, customerNeedSpec),
|
|
69
|
+
changes: changesList(client, customerNeedSpec),
|
|
70
|
+
webhook: webhookList(client, customerNeedSpec),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const levelFields = `id created_at: createdAt updated_at: updatedAt archived_at: archivedAt name display_name: displayName description color position`;
|
|
74
|
+
|
|
75
|
+
const customerStatusSpec = listSpec({
|
|
76
|
+
connection: "customerStatuses",
|
|
77
|
+
fields: levelFields,
|
|
78
|
+
item: CustomerStatusObjectSchema,
|
|
79
|
+
updatedAt: (status) => status.updated_at,
|
|
80
|
+
rows: rowsOf((status) => ({ ...status, version: status.updated_at })),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const makeCustomerStatuses = (client: LinearClientService) =>
|
|
84
|
+
Resource.entity({
|
|
85
|
+
name: "customer_statuses",
|
|
86
|
+
rowSchema: CustomerStatusSchema,
|
|
87
|
+
version: "version",
|
|
88
|
+
...listSources(client, customerStatusSpec),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const customerTierSpec = listSpec({
|
|
92
|
+
connection: "customerTiers",
|
|
93
|
+
fields: levelFields,
|
|
94
|
+
item: CustomerTierObjectSchema,
|
|
95
|
+
updatedAt: (tier) => tier.updated_at,
|
|
96
|
+
rows: rowsOf((tier) => ({ ...tier, version: tier.updated_at })),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export const makeCustomerTiers = (client: LinearClientService) =>
|
|
100
|
+
Resource.entity({
|
|
101
|
+
name: "customer_tiers",
|
|
102
|
+
rowSchema: CustomerTierSchema,
|
|
103
|
+
version: "version",
|
|
104
|
+
...listSources(client, customerTierSpec),
|
|
105
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Resource } from "@useairfoil/connector-kit";
|
|
2
|
+
|
|
3
|
+
import type { LinearClientService } from "../client/client";
|
|
4
|
+
|
|
5
|
+
import { DocumentObjectSchema, DocumentSchema } from "../schemas/documents";
|
|
6
|
+
import { backfillList, changesList, checkList, listSpec, rowsOf, webhookList } from "./pages";
|
|
7
|
+
|
|
8
|
+
const documentSpec = listSpec({
|
|
9
|
+
connection: "documents",
|
|
10
|
+
single: "document",
|
|
11
|
+
fields: `id created_at: createdAt updated_at: updatedAt archived_at: archivedAt
|
|
12
|
+
title content url icon color creator_id: creator { id } updated_by_id: updatedBy { id }
|
|
13
|
+
project_id: project { id } initiative_id: initiative { id } issue_id: issue { id }
|
|
14
|
+
cycle_id: cycle { id } team_id: team { id } parent_document_id: parentDocument { id }
|
|
15
|
+
hidden_at: hiddenAt trashed`,
|
|
16
|
+
item: DocumentObjectSchema,
|
|
17
|
+
updatedAt: (document) => document.updated_at,
|
|
18
|
+
rows: rowsOf((document) => ({ ...document, version: document.updated_at, _deleted: false })),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const makeDocuments = (client: LinearClientService) =>
|
|
22
|
+
Resource.entity({
|
|
23
|
+
name: "documents",
|
|
24
|
+
rowSchema: DocumentSchema,
|
|
25
|
+
version: "version",
|
|
26
|
+
|
|
27
|
+
check: checkList(client, documentSpec),
|
|
28
|
+
backfill: backfillList(client, documentSpec),
|
|
29
|
+
changes: changesList(client, documentSpec),
|
|
30
|
+
webhook: webhookList(client, documentSpec),
|
|
31
|
+
});
|