@enrichlayer/el-linear 1.7.0 → 1.10.0
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 +82 -1
- package/claude-skills/linear-operations/SKILL.md +55 -0
- package/dist/auth/oauth-callback.js +14 -7
- package/dist/auth/oauth-fs.d.ts +22 -0
- package/dist/auth/oauth-fs.js +77 -6
- package/dist/auth/oauth-headless.d.ts +13 -8
- package/dist/auth/oauth-headless.js +18 -11
- package/dist/auth/oauth-storage.d.ts +11 -3
- package/dist/auth/oauth-storage.js +15 -8
- package/dist/auth/token-resolver.d.ts +9 -0
- package/dist/auth/token-resolver.js +57 -37
- package/dist/commands/attachments.js +2 -1
- package/dist/commands/comments.js +17 -28
- package/dist/commands/cycles.js +2 -1
- package/dist/commands/documents.js +2 -1
- package/dist/commands/graphql.js +4 -6
- package/dist/commands/init/index.js +2 -0
- package/dist/commands/init/oauth.d.ts +6 -0
- package/dist/commands/init/oauth.js +8 -2
- package/dist/commands/init/token.d.ts +0 -8
- package/dist/commands/init/token.js +13 -1
- package/dist/commands/issue-id.js +1 -3
- package/dist/commands/issues/branch.d.ts +17 -0
- package/dist/commands/issues/branch.js +40 -0
- package/dist/commands/issues/description.d.ts +89 -0
- package/dist/commands/issues/description.js +183 -0
- package/dist/commands/issues/link-references.d.ts +21 -0
- package/dist/commands/issues/link-references.js +171 -0
- package/dist/commands/issues/relations.d.ts +55 -0
- package/dist/commands/issues/relations.js +132 -0
- package/dist/commands/issues.js +97 -497
- package/dist/commands/labels.js +20 -28
- package/dist/commands/profile/migrate-legacy.js +10 -33
- package/dist/commands/profile.js +1 -10
- package/dist/commands/project-milestones.js +13 -20
- package/dist/commands/projects.d.ts +5 -1
- package/dist/commands/projects.js +202 -102
- package/dist/commands/read-shortcut.d.ts +6 -0
- package/dist/commands/read-shortcut.js +6 -1
- package/dist/commands/refs.js +10 -1
- package/dist/commands/releases.js +26 -30
- package/dist/commands/search.js +21 -30
- package/dist/commands/teams.js +2 -1
- package/dist/commands/templates.js +128 -7
- package/dist/commands/users.js +3 -1
- package/dist/config/config.js +31 -9
- package/dist/config/issue-validation.js +1 -1
- package/dist/config/paths.d.ts +11 -0
- package/dist/config/paths.js +44 -6
- package/dist/config/resolver.js +2 -3
- package/dist/config/term-enforcer.js +1 -1
- package/dist/main.js +37 -2
- package/dist/queries/attachments-types.d.ts +30 -0
- package/dist/queries/attachments-types.js +5 -0
- package/dist/queries/comments-types.d.ts +49 -0
- package/dist/queries/comments-types.js +5 -0
- package/dist/queries/documents-types.d.ts +61 -0
- package/dist/queries/documents-types.js +9 -0
- package/dist/queries/introspect-types.d.ts +57 -0
- package/dist/queries/introspect-types.js +10 -0
- package/dist/queries/issues-types.d.ts +416 -0
- package/dist/queries/issues-types.js +23 -0
- package/dist/queries/issues.d.ts +2 -0
- package/dist/queries/issues.js +22 -0
- package/dist/queries/labels-types.d.ts +64 -0
- package/dist/queries/labels-types.js +5 -0
- package/dist/queries/project-milestones-types.d.ts +91 -0
- package/dist/queries/project-milestones-types.js +10 -0
- package/dist/queries/projects-types.d.ts +75 -0
- package/dist/queries/projects-types.js +5 -0
- package/dist/queries/projects.d.ts +2 -0
- package/dist/queries/projects.js +22 -0
- package/dist/queries/releases-types.d.ts +84 -0
- package/dist/queries/releases-types.js +5 -0
- package/dist/queries/search-types.d.ts +86 -0
- package/dist/queries/search-types.js +6 -0
- package/dist/queries/templates-types.d.ts +61 -0
- package/dist/queries/templates-types.js +9 -0
- package/dist/queries/templates.d.ts +3 -0
- package/dist/queries/templates.js +43 -0
- package/dist/types/linear.d.ts +8 -2
- package/dist/utils/auth.js +7 -9
- package/dist/utils/auto-link-references.js +44 -25
- package/dist/utils/disk-cache.js +2 -1
- package/dist/utils/formatters/summary.d.ts +62 -0
- package/dist/utils/formatters/summary.js +755 -0
- package/dist/utils/graphql-attachments-service.js +6 -9
- package/dist/utils/graphql-documents-service.js +19 -25
- package/dist/utils/graphql-issues-service.d.ts +118 -5
- package/dist/utils/graphql-issues-service.js +202 -209
- package/dist/utils/issue-reference-extractor.d.ts +9 -1
- package/dist/utils/issue-reference-extractor.js +16 -9
- package/dist/utils/issue-reference-wrapper.js +1 -54
- package/dist/utils/linear-service.d.ts +7 -3
- package/dist/utils/linear-service.js +27 -5
- package/dist/utils/markdown-prosemirror.js +17 -1
- package/dist/utils/mention-resolver.js +17 -5
- package/dist/utils/output.d.ts +7 -1
- package/dist/utils/output.js +38 -1
- package/dist/utils/protected-ranges.d.ts +33 -0
- package/dist/utils/protected-ranges.js +73 -0
- package/dist/utils/table-formatter.d.ts +36 -0
- package/dist/utils/table-formatter.js +46 -24
- package/dist/utils/validators.d.ts +9 -1
- package/dist/utils/validators.js +10 -0
- package/dist/utils/workspace-url.d.ts +5 -1
- package/dist/utils/workspace-url.js +35 -5
- package/package.json +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed response shapes for the queries in `./project-milestones.ts`.
|
|
3
|
+
* See `./issues-types.ts` for the rationale (ALL-937).
|
|
4
|
+
*
|
|
5
|
+
* The selection sets vary across queries — some include `description`
|
|
6
|
+
* + `createdAt`, others only the lookup-relevant fields. Modelled as
|
|
7
|
+
* a base `MilestoneBaseRef` with extension shapes per query so each
|
|
8
|
+
* consumer reads only what was actually selected.
|
|
9
|
+
*/
|
|
10
|
+
import type { IssueNode } from "./issues-types.js";
|
|
11
|
+
export interface MilestoneProjectRef {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Mirrors the milestone selection set in `LIST_PROJECT_MILESTONES_QUERY`
|
|
17
|
+
* — full milestone fields without the project-back-reference.
|
|
18
|
+
*/
|
|
19
|
+
export interface MilestoneListNode {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
description: string | null;
|
|
23
|
+
targetDate: string | null;
|
|
24
|
+
sortOrder: number;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Mirrors the milestone selection set in `GET_PROJECT_MILESTONE_BY_ID_QUERY`
|
|
30
|
+
* — list shape + project + issues connection.
|
|
31
|
+
*/
|
|
32
|
+
export interface MilestoneDetailNode extends MilestoneListNode {
|
|
33
|
+
project: MilestoneProjectRef;
|
|
34
|
+
issues: {
|
|
35
|
+
nodes: IssueNode[];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Mirrors the milestone selection set in `FIND_PROJECT_MILESTONE_BY_NAME_*`
|
|
40
|
+
* — minimal lookup shape with project back-reference.
|
|
41
|
+
*/
|
|
42
|
+
export interface MilestoneLookupNode {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
targetDate: string | null;
|
|
46
|
+
sortOrder: number;
|
|
47
|
+
project: MilestoneProjectRef;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Mirrors the milestone selection set on the create / update mutations —
|
|
51
|
+
* list shape + project (no issues connection).
|
|
52
|
+
*/
|
|
53
|
+
export interface MutatedMilestoneNode extends MilestoneListNode {
|
|
54
|
+
project: MilestoneProjectRef;
|
|
55
|
+
}
|
|
56
|
+
export interface ListProjectMilestonesResponse {
|
|
57
|
+
project: {
|
|
58
|
+
id: string;
|
|
59
|
+
name: string;
|
|
60
|
+
projectMilestones: {
|
|
61
|
+
nodes: MilestoneListNode[];
|
|
62
|
+
};
|
|
63
|
+
} | null;
|
|
64
|
+
}
|
|
65
|
+
export interface GetProjectMilestoneByIdResponse {
|
|
66
|
+
projectMilestone: MilestoneDetailNode | null;
|
|
67
|
+
}
|
|
68
|
+
export interface FindProjectMilestoneScopedResponse {
|
|
69
|
+
project: {
|
|
70
|
+
projectMilestones: {
|
|
71
|
+
nodes: MilestoneLookupNode[];
|
|
72
|
+
};
|
|
73
|
+
} | null;
|
|
74
|
+
}
|
|
75
|
+
export interface FindProjectMilestoneGlobalResponse {
|
|
76
|
+
projectMilestones: {
|
|
77
|
+
nodes: MilestoneLookupNode[];
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export interface CreateProjectMilestoneResponse {
|
|
81
|
+
projectMilestoneCreate: {
|
|
82
|
+
success: boolean;
|
|
83
|
+
projectMilestone: MutatedMilestoneNode | null;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export interface UpdateProjectMilestoneResponse {
|
|
87
|
+
projectMilestoneUpdate: {
|
|
88
|
+
success: boolean;
|
|
89
|
+
projectMilestone: MutatedMilestoneNode | null;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed response shapes for the queries in `./project-milestones.ts`.
|
|
3
|
+
* See `./issues-types.ts` for the rationale (ALL-937).
|
|
4
|
+
*
|
|
5
|
+
* The selection sets vary across queries — some include `description`
|
|
6
|
+
* + `createdAt`, others only the lookup-relevant fields. Modelled as
|
|
7
|
+
* a base `MilestoneBaseRef` with extension shapes per query so each
|
|
8
|
+
* consumer reads only what was actually selected.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed response shapes for the queries in `./projects.ts`.
|
|
3
|
+
* See `./issues-types.ts` for the rationale (ALL-937).
|
|
4
|
+
*/
|
|
5
|
+
export interface ProjectTeamRef {
|
|
6
|
+
id: string;
|
|
7
|
+
key: string;
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ProjectBaseNode {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
teams: {
|
|
14
|
+
nodes: ProjectTeamRef[];
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Mirrors the project selection set used by `SEARCH_PROJECTS_BY_NAME_QUERY`
|
|
19
|
+
* and `CREATE_PROJECT_MUTATION` — base fields + state.
|
|
20
|
+
*/
|
|
21
|
+
export interface ProjectWithStateNode extends ProjectBaseNode {
|
|
22
|
+
state: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ProjectIssueRef {
|
|
25
|
+
id: string;
|
|
26
|
+
identifier: string;
|
|
27
|
+
title: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ProjectWithIssuesNode extends ProjectBaseNode {
|
|
30
|
+
issues: {
|
|
31
|
+
nodes: ProjectIssueRef[];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface ProjectByIdResponse {
|
|
35
|
+
project: ProjectBaseNode | null;
|
|
36
|
+
}
|
|
37
|
+
export interface GetProjectResponse {
|
|
38
|
+
projects: {
|
|
39
|
+
nodes: ProjectBaseNode[];
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface GetProjectTeamIssuesResponse {
|
|
43
|
+
project: ProjectWithIssuesNode | null;
|
|
44
|
+
}
|
|
45
|
+
export interface SearchProjectsByNameResponse {
|
|
46
|
+
projects: {
|
|
47
|
+
nodes: ProjectWithStateNode[];
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export interface CreateProjectResponse {
|
|
51
|
+
projectCreate: {
|
|
52
|
+
success: boolean;
|
|
53
|
+
project: ProjectWithStateNode | null;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface UpdateProjectResponse {
|
|
57
|
+
projectUpdate: {
|
|
58
|
+
success: boolean;
|
|
59
|
+
project: ProjectBaseNode | null;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export interface ProjectArchiveEntity {
|
|
63
|
+
id: string;
|
|
64
|
+
}
|
|
65
|
+
export interface ProjectArchivePayload {
|
|
66
|
+
success: boolean;
|
|
67
|
+
lastSyncId: number;
|
|
68
|
+
entity: ProjectArchiveEntity | null;
|
|
69
|
+
}
|
|
70
|
+
export interface ArchiveProjectResponse {
|
|
71
|
+
projectArchive: ProjectArchivePayload;
|
|
72
|
+
}
|
|
73
|
+
export interface DeleteProjectResponse {
|
|
74
|
+
projectDelete: ProjectArchivePayload;
|
|
75
|
+
}
|
|
@@ -5,3 +5,5 @@ export declare const GET_PROJECT_TEAM_ISSUES_QUERY = "\n query GetProjectTeamIs
|
|
|
5
5
|
export declare const SEARCH_PROJECTS_BY_NAME_QUERY = "\n query SearchProjectsByName($name: String!) {\n projects(filter: { name: { containsIgnoreCase: $name } }, first: 10) {\n nodes {\n id\n name\n state\n teams {\n nodes { id key name }\n }\n }\n }\n }\n";
|
|
6
6
|
export declare const CREATE_PROJECT_MUTATION = "\n mutation CreateProject($input: ProjectCreateInput!) {\n projectCreate(input: $input) {\n success\n project {\n id\n name\n state\n teams {\n nodes { id key name }\n }\n }\n }\n }\n";
|
|
7
7
|
export declare const UPDATE_PROJECT_MUTATION = "\n mutation UpdateProject($id: String!, $input: ProjectUpdateInput!) {\n projectUpdate(id: $id, input: $input) {\n success\n project {\n id\n name\n teams {\n nodes {\n id\n key\n name\n }\n }\n }\n }\n }\n";
|
|
8
|
+
export declare const ARCHIVE_PROJECT_MUTATION = "\n mutation ArchiveProject($id: String!) {\n projectArchive(id: $id) {\n success\n lastSyncId\n entity {\n id\n }\n }\n }\n";
|
|
9
|
+
export declare const DELETE_PROJECT_MUTATION = "\n mutation DeleteProject($id: String!) {\n projectDelete(id: $id) {\n success\n lastSyncId\n entity {\n id\n }\n }\n }\n";
|
package/dist/queries/projects.js
CHANGED
|
@@ -102,3 +102,25 @@ export const UPDATE_PROJECT_MUTATION = `
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
`;
|
|
105
|
+
export const ARCHIVE_PROJECT_MUTATION = `
|
|
106
|
+
mutation ArchiveProject($id: String!) {
|
|
107
|
+
projectArchive(id: $id) {
|
|
108
|
+
success
|
|
109
|
+
lastSyncId
|
|
110
|
+
entity {
|
|
111
|
+
id
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
export const DELETE_PROJECT_MUTATION = `
|
|
117
|
+
mutation DeleteProject($id: String!) {
|
|
118
|
+
projectDelete(id: $id) {
|
|
119
|
+
success
|
|
120
|
+
lastSyncId
|
|
121
|
+
entity {
|
|
122
|
+
id
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed response shapes for the queries in `./releases.ts`.
|
|
3
|
+
* See `./issues-types.ts` for the rationale (ALL-937).
|
|
4
|
+
*/
|
|
5
|
+
export interface ReleaseStageRef {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
color: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface ReleasePipelineRef {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ReleaseDocumentRef {
|
|
16
|
+
id: string;
|
|
17
|
+
title: string;
|
|
18
|
+
slugId: string;
|
|
19
|
+
}
|
|
20
|
+
/** Mirrors the release selection set used by `GET_RELEASES_QUERY`. */
|
|
21
|
+
export interface ReleaseListNode {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
description: string | null;
|
|
25
|
+
version: string | null;
|
|
26
|
+
url: string | null;
|
|
27
|
+
startDate: string | null;
|
|
28
|
+
targetDate: string | null;
|
|
29
|
+
startedAt: string | null;
|
|
30
|
+
completedAt: string | null;
|
|
31
|
+
canceledAt: string | null;
|
|
32
|
+
stage: ReleaseStageRef | null;
|
|
33
|
+
pipeline: ReleasePipelineRef | null;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
updatedAt: string;
|
|
36
|
+
}
|
|
37
|
+
/** Mirrors `GET_RELEASE_BY_ID_QUERY` — adds `documents`. */
|
|
38
|
+
export interface ReleaseDetailNode extends ReleaseListNode {
|
|
39
|
+
documents: {
|
|
40
|
+
nodes: ReleaseDocumentRef[];
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Mirrors the release selection set on `CREATE_RELEASE_MUTATION` —
|
|
45
|
+
* a subset of `ReleaseListNode` (no startDate/targetDate/lifecycle
|
|
46
|
+
* timestamps; no documents).
|
|
47
|
+
*/
|
|
48
|
+
export interface CreatedReleaseNode {
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
description: string | null;
|
|
52
|
+
version: string | null;
|
|
53
|
+
url: string | null;
|
|
54
|
+
stage: ReleaseStageRef | null;
|
|
55
|
+
pipeline: ReleasePipelineRef | null;
|
|
56
|
+
createdAt: string;
|
|
57
|
+
updatedAt: string;
|
|
58
|
+
}
|
|
59
|
+
export interface ReleasePipelineNode {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
stages: {
|
|
63
|
+
nodes: ReleaseStageRef[];
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export interface GetReleasesResponse {
|
|
67
|
+
releases: {
|
|
68
|
+
nodes: ReleaseListNode[];
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export interface GetReleaseByIdResponse {
|
|
72
|
+
release: ReleaseDetailNode | null;
|
|
73
|
+
}
|
|
74
|
+
export interface CreateReleaseResponse {
|
|
75
|
+
releaseCreate: {
|
|
76
|
+
success: boolean;
|
|
77
|
+
release: CreatedReleaseNode | null;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export interface GetReleasePipelinesResponse {
|
|
81
|
+
releasePipelines: {
|
|
82
|
+
nodes: ReleasePipelineNode[];
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed response shapes for `SEMANTIC_SEARCH_QUERY` and the inline
|
|
3
|
+
* templates listing in `commands/search.ts`. See `./issues-types.ts`
|
|
4
|
+
* for the rationale (ALL-937).
|
|
5
|
+
*/
|
|
6
|
+
export interface SearchIssueRef {
|
|
7
|
+
id: string;
|
|
8
|
+
identifier: string;
|
|
9
|
+
title: string;
|
|
10
|
+
priority: number | null;
|
|
11
|
+
state: {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
} | null;
|
|
15
|
+
team: {
|
|
16
|
+
id: string;
|
|
17
|
+
key: string;
|
|
18
|
+
name: string;
|
|
19
|
+
} | null;
|
|
20
|
+
assignee: {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
url: string | null;
|
|
24
|
+
} | null;
|
|
25
|
+
project: {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
} | null;
|
|
29
|
+
}
|
|
30
|
+
export interface SearchProjectRef {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
state: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SearchInitiativeRef {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
status: string | null;
|
|
39
|
+
}
|
|
40
|
+
export interface SearchDocumentRef {
|
|
41
|
+
id: string;
|
|
42
|
+
title: string;
|
|
43
|
+
slugId: string | null;
|
|
44
|
+
project: {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
} | null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Mirrors a single semanticSearch result. The discriminator is `type`,
|
|
51
|
+
* but Linear returns *all* sub-objects nullable per row — only the one
|
|
52
|
+
* matching `type` is populated. Modelled as fully-optional sub-objects
|
|
53
|
+
* to match runtime reality.
|
|
54
|
+
*/
|
|
55
|
+
export interface SemanticSearchResult {
|
|
56
|
+
type: string;
|
|
57
|
+
issue: SearchIssueRef | null;
|
|
58
|
+
project: SearchProjectRef | null;
|
|
59
|
+
initiative: SearchInitiativeRef | null;
|
|
60
|
+
document: SearchDocumentRef | null;
|
|
61
|
+
}
|
|
62
|
+
export interface SemanticSearchResponse {
|
|
63
|
+
semanticSearch: {
|
|
64
|
+
results: SemanticSearchResult[];
|
|
65
|
+
} | null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Mirrors the inline `TEMPLATES_QUERY` in `commands/search.ts` —
|
|
69
|
+
* a smaller selection set than the full templates query, dropping
|
|
70
|
+
* fields the search command doesn't display.
|
|
71
|
+
*/
|
|
72
|
+
export interface SearchTemplateNode {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
type: string;
|
|
76
|
+
description: string | null;
|
|
77
|
+
team: {
|
|
78
|
+
key: string;
|
|
79
|
+
} | null;
|
|
80
|
+
creator: {
|
|
81
|
+
name: string;
|
|
82
|
+
} | null;
|
|
83
|
+
}
|
|
84
|
+
export interface SearchTemplatesResponse {
|
|
85
|
+
templates: SearchTemplateNode[];
|
|
86
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed response shapes for the queries in `./templates.ts`.
|
|
3
|
+
* See `./issues-types.ts` for the rationale (ALL-937).
|
|
4
|
+
*
|
|
5
|
+
* Note: `templates` is unusual — it returns the array directly rather
|
|
6
|
+
* than wrapped in `{ nodes: ... }`. The response type below mirrors
|
|
7
|
+
* what the API actually returns, not the connection convention.
|
|
8
|
+
*/
|
|
9
|
+
export interface TemplateTeamRef {
|
|
10
|
+
id: string;
|
|
11
|
+
key: string;
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
export interface TemplateCreatorRef {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Mirrors the template selection set used by `TEMPLATES_LIST_QUERY` /
|
|
20
|
+
* `TEMPLATE_BY_ID_QUERY` / `TEMPLATE_CREATE_MUTATION` /
|
|
21
|
+
* `TEMPLATE_UPDATE_MUTATION`. (Some of those omit `creator` and
|
|
22
|
+
* `createdAt`; we model the union — consumers only read what their
|
|
23
|
+
* query selected.)
|
|
24
|
+
*/
|
|
25
|
+
export interface TemplateNode {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
type: string;
|
|
29
|
+
description: string | null;
|
|
30
|
+
templateData: unknown;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
team: TemplateTeamRef | null;
|
|
34
|
+
creator: TemplateCreatorRef | null;
|
|
35
|
+
}
|
|
36
|
+
export interface TemplatesListResponse {
|
|
37
|
+
templates: TemplateNode[];
|
|
38
|
+
}
|
|
39
|
+
export interface GetTemplateResponse {
|
|
40
|
+
template: TemplateNode | null;
|
|
41
|
+
}
|
|
42
|
+
export interface CreateTemplateResponse {
|
|
43
|
+
templateCreate: {
|
|
44
|
+
success: boolean;
|
|
45
|
+
lastSyncId?: number;
|
|
46
|
+
template: TemplateNode | null;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export interface UpdateTemplateResponse {
|
|
50
|
+
templateUpdate: {
|
|
51
|
+
success: boolean;
|
|
52
|
+
lastSyncId?: number;
|
|
53
|
+
template: TemplateNode | null;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface DeleteTemplateResponse {
|
|
57
|
+
templateDelete: {
|
|
58
|
+
success: boolean;
|
|
59
|
+
lastSyncId?: number;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed response shapes for the queries in `./templates.ts`.
|
|
3
|
+
* See `./issues-types.ts` for the rationale (ALL-937).
|
|
4
|
+
*
|
|
5
|
+
* Note: `templates` is unusual — it returns the array directly rather
|
|
6
|
+
* than wrapped in `{ nodes: ... }`. The response type below mirrors
|
|
7
|
+
* what the API actually returns, not the connection convention.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export declare const TEMPLATES_LIST_QUERY = "\n query {\n templates {\n id\n name\n type\n description\n templateData\n createdAt\n updatedAt\n team { id key name }\n creator { id name }\n }\n }\n";
|
|
2
2
|
export declare const TEMPLATE_BY_ID_QUERY = "\n query ($id: String!) {\n template(id: $id) {\n id\n name\n type\n description\n templateData\n createdAt\n updatedAt\n team { id key name }\n creator { id name }\n }\n }\n";
|
|
3
|
+
export declare const TEMPLATE_CREATE_MUTATION = "\n mutation ($input: TemplateCreateInput!) {\n templateCreate(input: $input) {\n success\n lastSyncId\n template {\n id\n name\n type\n description\n templateData\n team { id key name }\n createdAt\n updatedAt\n }\n }\n }\n";
|
|
4
|
+
export declare const TEMPLATE_UPDATE_MUTATION = "\n mutation ($id: String!, $input: TemplateUpdateInput!) {\n templateUpdate(id: $id, input: $input) {\n success\n lastSyncId\n template {\n id\n name\n type\n description\n templateData\n team { id key name }\n updatedAt\n }\n }\n }\n";
|
|
5
|
+
export declare const TEMPLATE_DELETE_MUTATION = "\n mutation ($id: String!) {\n templateDelete(id: $id) {\n success\n lastSyncId\n }\n }\n";
|
|
@@ -28,3 +28,46 @@ export const TEMPLATE_BY_ID_QUERY = `
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
`;
|
|
31
|
+
export const TEMPLATE_CREATE_MUTATION = `
|
|
32
|
+
mutation ($input: TemplateCreateInput!) {
|
|
33
|
+
templateCreate(input: $input) {
|
|
34
|
+
success
|
|
35
|
+
lastSyncId
|
|
36
|
+
template {
|
|
37
|
+
id
|
|
38
|
+
name
|
|
39
|
+
type
|
|
40
|
+
description
|
|
41
|
+
templateData
|
|
42
|
+
team { id key name }
|
|
43
|
+
createdAt
|
|
44
|
+
updatedAt
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
export const TEMPLATE_UPDATE_MUTATION = `
|
|
50
|
+
mutation ($id: String!, $input: TemplateUpdateInput!) {
|
|
51
|
+
templateUpdate(id: $id, input: $input) {
|
|
52
|
+
success
|
|
53
|
+
lastSyncId
|
|
54
|
+
template {
|
|
55
|
+
id
|
|
56
|
+
name
|
|
57
|
+
type
|
|
58
|
+
description
|
|
59
|
+
templateData
|
|
60
|
+
team { id key name }
|
|
61
|
+
updatedAt
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
export const TEMPLATE_DELETE_MUTATION = `
|
|
67
|
+
mutation ($id: String!) {
|
|
68
|
+
templateDelete(id: $id) {
|
|
69
|
+
success
|
|
70
|
+
lastSyncId
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
`;
|
package/dist/types/linear.d.ts
CHANGED
|
@@ -208,8 +208,14 @@ type GraphQLPrimitive = string | number | boolean | null | undefined;
|
|
|
208
208
|
type GraphQLValue = GraphQLPrimitive | GraphQLResponseData | GraphQLResponseData[];
|
|
209
209
|
/**
|
|
210
210
|
* Recursive index type for raw GraphQL responses.
|
|
211
|
-
*
|
|
212
|
-
*
|
|
211
|
+
*
|
|
212
|
+
* **Prefer per-query response types** (see `src/queries/*-types.ts`)
|
|
213
|
+
* over `GraphQLResponseData` for any new consumer. ALL-937 swept the
|
|
214
|
+
* old service/command consumers; this type is kept as the fallback
|
|
215
|
+
* for the few generic transports (e.g. `graphql-service.rawRequest`'s
|
|
216
|
+
* default generic). Don't reach for `as GraphQLResponseData` to skip
|
|
217
|
+
* a typed shape — the per-query types catch query/consumer drift at
|
|
218
|
+
* compile time. This type cannot.
|
|
213
219
|
*/
|
|
214
220
|
export interface GraphQLResponseData {
|
|
215
221
|
[key: string]: GraphQLValue;
|
package/dist/utils/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import { LEGACY_LINCTL_TOKEN_PATH, LEGACY_TOKEN_PATH, resolveActiveProfile,
|
|
2
|
+
import { LEGACY_LINCTL_TOKEN_PATH, LEGACY_TOKEN_PATH, resolveActiveProfile, } from "../config/paths.js";
|
|
3
3
|
import { maybeEmitMigrationHint } from "./migration-hint.js";
|
|
4
4
|
export function getApiToken(options) {
|
|
5
5
|
if (options.apiToken) {
|
|
@@ -9,18 +9,16 @@ export function getApiToken(options) {
|
|
|
9
9
|
return process.env.LINEAR_API_TOKEN;
|
|
10
10
|
}
|
|
11
11
|
// Profile-aware: read from <CONFIG_DIR>/profiles/<name>/token when a
|
|
12
|
-
// profile is active
|
|
13
|
-
// (
|
|
12
|
+
// profile is active. When the user has explicitly selected a profile
|
|
13
|
+
// (--profile / EL_LINEAR_PROFILE / active-profile marker), do NOT
|
|
14
|
+
// fall back to the legacy single-file token — that path silently
|
|
15
|
+
// posts writes to the wrong workspace when a profile token is missing.
|
|
14
16
|
const active = resolveActiveProfile();
|
|
15
17
|
if (fs.existsSync(active.tokenPath)) {
|
|
16
18
|
return fs.readFileSync(active.tokenPath, "utf8").trim();
|
|
17
19
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// suddenly fail. The active-profile name is informational only when
|
|
21
|
-
// no profile-scoped token exists yet.
|
|
22
|
-
if (active.tokenPath !== TOKEN_PATH && fs.existsSync(TOKEN_PATH)) {
|
|
23
|
-
return fs.readFileSync(TOKEN_PATH, "utf8").trim();
|
|
20
|
+
if (active.name !== null) {
|
|
21
|
+
throw new Error(`No token for active profile \`${active.name}\` (expected at ${active.tokenPath}). Run \`el-linear init token --profile ${active.name}\` (or unset --profile / EL_LINEAR_PROFILE / \`el-linear profile use <name>\`) before retrying. Refusing to fall back to the legacy single-file token to avoid posting writes to the wrong workspace.`);
|
|
24
22
|
}
|
|
25
23
|
// Fallback to the linctl-era token (the CLI was briefly published as
|
|
26
24
|
// `@enrichlayer/linctl` then reverted). Kept for one release.
|
|
@@ -26,37 +26,44 @@ function mergeCandidates(...sources) {
|
|
|
26
26
|
}
|
|
27
27
|
return [...byId.values()];
|
|
28
28
|
}
|
|
29
|
-
function
|
|
30
|
-
for (const rel of nodes
|
|
31
|
-
const peer = rel
|
|
29
|
+
function mergeOutgoingRelations(nodes, out) {
|
|
30
|
+
for (const rel of nodes) {
|
|
31
|
+
const peer = rel.relatedIssue;
|
|
32
32
|
if (!peer) {
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const type = normalizeType(rel.type);
|
|
38
|
-
if (id && !out.byIdentifier.has(id)) {
|
|
39
|
-
out.byIdentifier.set(id, type);
|
|
35
|
+
if (!out.byIdentifier.has(peer.identifier)) {
|
|
36
|
+
out.byIdentifier.set(peer.identifier, rel.type);
|
|
40
37
|
}
|
|
41
|
-
if (
|
|
42
|
-
out.byUuid.set(
|
|
38
|
+
if (!out.byUuid.has(peer.id)) {
|
|
39
|
+
out.byUuid.set(peer.id, rel.type);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function mergeIncomingRelations(nodes, out) {
|
|
44
|
+
for (const rel of nodes) {
|
|
45
|
+
const peer = rel.issue;
|
|
46
|
+
if (!peer) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Normalize incoming "blocks" → "blockedBy" so the reported type reads naturally
|
|
50
|
+
const type = rel.type === "blocks" ? "blockedBy" : rel.type;
|
|
51
|
+
if (!out.byIdentifier.has(peer.identifier)) {
|
|
52
|
+
out.byIdentifier.set(peer.identifier, type);
|
|
53
|
+
}
|
|
54
|
+
if (!out.byUuid.has(peer.id)) {
|
|
55
|
+
out.byUuid.set(peer.id, type);
|
|
43
56
|
}
|
|
44
57
|
}
|
|
45
58
|
}
|
|
46
59
|
async function fetchExistingRelations(issueId, graphQLService) {
|
|
47
|
-
const result = await graphQLService.rawRequest(GET_ISSUE_RELATIONS_QUERY, {
|
|
48
|
-
id: issueId,
|
|
49
|
-
});
|
|
50
|
-
const issue = result.issue;
|
|
60
|
+
const result = await graphQLService.rawRequest(GET_ISSUE_RELATIONS_QUERY, { id: issueId });
|
|
51
61
|
const out = { byIdentifier: new Map(), byUuid: new Map() };
|
|
52
|
-
if (!issue) {
|
|
62
|
+
if (!result.issue) {
|
|
53
63
|
return out;
|
|
54
64
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const incoming = issue.inverseRelations;
|
|
58
|
-
// Normalize incoming "blocks" → "blockedBy" so the reported type reads naturally
|
|
59
|
-
mergeRelationNodes(incoming?.nodes, "issue", (raw) => (raw === "blocks" ? "blockedBy" : raw), out);
|
|
65
|
+
mergeOutgoingRelations(result.issue.relations.nodes, out);
|
|
66
|
+
mergeIncomingRelations(result.issue.inverseRelations.nodes, out);
|
|
60
67
|
return out;
|
|
61
68
|
}
|
|
62
69
|
function describeError(err) {
|
|
@@ -70,11 +77,9 @@ async function createTypedRelation(ref, sourceId, resolvedId, graphQLService) {
|
|
|
70
77
|
type: ref.type,
|
|
71
78
|
},
|
|
72
79
|
});
|
|
73
|
-
const
|
|
74
|
-
const issueRelation = create?.issueRelation;
|
|
80
|
+
const issueRelation = result.issueRelationCreate.issueRelation;
|
|
75
81
|
// The peer (the issue NOT identified by `sourceId`) is whichever side wasn't the source
|
|
76
|
-
const
|
|
77
|
-
const peer = issueRelation?.[peerKey];
|
|
82
|
+
const peer = ref.reverse ? issueRelation?.issue : issueRelation?.relatedIssue;
|
|
78
83
|
return {
|
|
79
84
|
identifier: peer?.identifier ?? ref.identifier,
|
|
80
85
|
title: peer?.title ?? "",
|
|
@@ -155,7 +160,21 @@ export async function autoLinkReferences(input) {
|
|
|
155
160
|
const failed = [];
|
|
156
161
|
const descriptionRefs = extractIssueReferences(description ?? "", identifier);
|
|
157
162
|
const commentRefs = (comments ?? []).flatMap((body) => extractIssueReferences(body, identifier));
|
|
158
|
-
const
|
|
163
|
+
const merged = mergeCandidates(descriptionRefs, commentRefs);
|
|
164
|
+
// Cap the number of candidates we'll resolve per call. A malicious
|
|
165
|
+
// (or merely careless) issue body containing hundreds of fake
|
|
166
|
+
// identifiers — `AAA-1, AAA-2, …, AAA-999` — would otherwise trigger
|
|
167
|
+
// 999 GraphQL roundtrips before deciding none resolve. Realistic
|
|
168
|
+
// human-authored bodies stay well under this cap.
|
|
169
|
+
const MAX_CANDIDATES_PER_CALL = 50;
|
|
170
|
+
const candidates = merged.slice(0, MAX_CANDIDATES_PER_CALL);
|
|
171
|
+
if (merged.length > MAX_CANDIDATES_PER_CALL) {
|
|
172
|
+
const overflow = merged.length - MAX_CANDIDATES_PER_CALL;
|
|
173
|
+
failed.push({
|
|
174
|
+
identifier: `+${overflow} more`,
|
|
175
|
+
reason: `Too many candidate references (${merged.length}); only the first ${MAX_CANDIDATES_PER_CALL} are processed.`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
159
178
|
if (candidates.length === 0) {
|
|
160
179
|
return { linked, skipped, failed };
|
|
161
180
|
}
|