toolcraft 0.0.22 → 0.0.24
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 +2 -2
- package/dist/cli.compile-check.js +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +57 -14
- package/dist/error-report.js +32 -3
- package/dist/human-in-loop/approval-tasks.d.ts +1 -0
- package/dist/human-in-loop/approval-tasks.js +7 -5
- package/dist/human-in-loop/approvals-commands.js +51 -8
- package/dist/human-in-loop/runner.js +24 -19
- package/dist/human-in-loop/state-machine.d.ts +3 -3
- package/dist/human-in-loop/state-machine.js +13 -5
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -1
- package/dist/mcp-proxy.js +85 -19
- package/dist/mcp.compile-check.js +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +50 -8
- package/dist/renderer.js +119 -13
- package/dist/sdk.compile-check.js +1 -0
- package/dist/sdk.d.ts +1 -0
- package/dist/sdk.js +56 -11
- package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +1 -1
- package/node_modules/@poe-code/agent-defs/dist/registry.js +22 -11
- package/node_modules/@poe-code/agent-defs/package.json +1 -1
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +5 -1
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +1 -1
- package/node_modules/@poe-code/agent-human-in-loop/package.json +1 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +1 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +41 -92
- package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +4 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +14 -2
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +11 -4
- package/node_modules/@poe-code/agent-mcp-config/package.json +1 -1
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +200 -22
- package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +7 -1
- package/node_modules/@poe-code/config-mutations/dist/formats/index.js +1 -1
- package/node_modules/@poe-code/config-mutations/dist/formats/json.js +11 -7
- package/node_modules/@poe-code/config-mutations/dist/formats/object.d.ts +4 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/object.js +27 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +12 -9
- package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +12 -9
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +11 -1
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +10 -1
- package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +25 -1
- package/node_modules/@poe-code/config-mutations/dist/types.d.ts +12 -2
- package/node_modules/@poe-code/config-mutations/package.json +1 -1
- package/node_modules/@poe-code/design-system/dist/acp/components.js +3 -1
- package/node_modules/@poe-code/design-system/dist/components/browser.d.ts +1 -1
- package/node_modules/@poe-code/design-system/dist/components/browser.js +6 -1
- package/node_modules/@poe-code/design-system/dist/components/color.js +9 -8
- package/node_modules/@poe-code/design-system/dist/components/command-errors.js +3 -2
- package/node_modules/@poe-code/design-system/dist/components/detail-card.d.ts +22 -0
- package/node_modules/@poe-code/design-system/dist/components/detail-card.js +69 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +88 -11
- package/node_modules/@poe-code/design-system/dist/components/index.d.ts +1 -1
- package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
- package/node_modules/@poe-code/design-system/dist/components/table.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/components/table.js +82 -5
- package/node_modules/@poe-code/design-system/dist/components/template.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/components/template.js +198 -32
- package/node_modules/@poe-code/design-system/dist/components/text.js +29 -5
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +2 -2
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +77 -32
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +28 -5
- package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +45 -28
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.js +71 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +32 -10
- package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +3 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +57 -6
- package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/state.js +12 -15
- package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -1
- package/node_modules/@poe-code/design-system/dist/index.js +2 -1
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -1
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +8 -5
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +1 -1
- package/node_modules/@poe-code/design-system/dist/static/menu.js +8 -2
- package/node_modules/@poe-code/design-system/dist/static/spinner.js +10 -4
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +9 -2
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +19 -2
- package/node_modules/@poe-code/design-system/package.json +2 -1
- package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +244 -110
- package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +16 -4
- package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +3 -2
- package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +16 -1
- package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
- package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
- package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +30 -8
- package/node_modules/@poe-code/process-runner/dist/types.d.ts +3 -0
- package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +57 -0
- package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +484 -0
- package/node_modules/@poe-code/process-runner/package.json +1 -1
- package/node_modules/@poe-code/task-list/README.md +0 -2
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +3 -0
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +89 -59
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +9 -3
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +460 -99
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +156 -154
- package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/backends/utils.js +79 -0
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +120 -132
- package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
- package/node_modules/@poe-code/task-list/dist/index.js +2 -0
- package/node_modules/@poe-code/task-list/dist/move.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/move.js +215 -0
- package/node_modules/@poe-code/task-list/dist/open.js +3 -4
- package/node_modules/@poe-code/task-list/dist/state-machine.js +3 -1
- package/node_modules/@poe-code/task-list/dist/state.js +9 -0
- package/node_modules/@poe-code/task-list/dist/types.d.ts +48 -13
- package/node_modules/@poe-code/task-list/package.json +1 -2
- package/node_modules/auth-store/dist/create-secret-store.js +4 -1
- package/node_modules/auth-store/dist/encrypted-file-store.d.ts +7 -0
- package/node_modules/auth-store/dist/encrypted-file-store.js +69 -7
- package/node_modules/auth-store/dist/index.d.ts +1 -1
- package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
- package/node_modules/auth-store/dist/keychain-store.js +18 -16
- package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
- package/node_modules/auth-store/dist/provider-store.js +55 -7
- package/node_modules/auth-store/dist/types.d.ts +3 -1
- package/node_modules/auth-store/package.json +2 -1
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -15
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +49 -12
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -1
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +1 -1
- package/node_modules/mcp-oauth/package.json +1 -0
- package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +1 -1
- package/node_modules/tiny-mcp-client/dist/internal.d.ts +8 -4
- package/node_modules/tiny-mcp-client/dist/internal.js +237 -67
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +1 -1
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +4 -7
- package/node_modules/tiny-mcp-client/package.json +2 -1
- package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +1 -1
- package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +46 -0
- package/node_modules/tiny-mcp-client/src/internal.ts +279 -77
- package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +1 -1
- package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +5 -10
- package/node_modules/tiny-mcp-client/src/transports.test.ts +588 -6
- package/package.json +10 -12
- package/node_modules/@poe-code/file-lock/README.md +0 -52
- package/node_modules/@poe-code/file-lock/dist/index.d.ts +0 -1
- package/node_modules/@poe-code/file-lock/dist/index.js +0 -1
- package/node_modules/@poe-code/file-lock/dist/lock.d.ts +0 -27
- package/node_modules/@poe-code/file-lock/dist/lock.js +0 -203
- package/node_modules/@poe-code/file-lock/package.json +0 -23
|
@@ -11,7 +11,7 @@ export const PROJECT_ORGANIZATION_QUERY = `query Project($owner: String!, $numbe
|
|
|
11
11
|
... on ProjectV2SingleSelectField {
|
|
12
12
|
id
|
|
13
13
|
name
|
|
14
|
-
options { id name }
|
|
14
|
+
options { id name color description }
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
fields(first: 100) {
|
|
@@ -19,7 +19,7 @@ export const PROJECT_ORGANIZATION_QUERY = `query Project($owner: String!, $numbe
|
|
|
19
19
|
... on ProjectV2SingleSelectField {
|
|
20
20
|
id
|
|
21
21
|
name
|
|
22
|
-
options { id name }
|
|
22
|
+
options { id name color description }
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -35,7 +35,7 @@ export const PROJECT_USER_QUERY = `query Project($owner: String!, $number: Int!)
|
|
|
35
35
|
... on ProjectV2SingleSelectField {
|
|
36
36
|
id
|
|
37
37
|
name
|
|
38
|
-
options { id name }
|
|
38
|
+
options { id name color description }
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
fields(first: 100) {
|
|
@@ -43,7 +43,7 @@ export const PROJECT_USER_QUERY = `query Project($owner: String!, $number: Int!)
|
|
|
43
43
|
... on ProjectV2SingleSelectField {
|
|
44
44
|
id
|
|
45
45
|
name
|
|
46
|
-
options { id name }
|
|
46
|
+
options { id name color description }
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -80,9 +80,28 @@ const PROJECT_ITEMS_QUERY = `query Items($projectId: ID!, $after: String) {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
}`;
|
|
83
|
-
const
|
|
83
|
+
const REPOSITORY_ISSUES_QUERY = `query Issues($owner: String!, $repo: String!, $labels: [String!], $after: String) {
|
|
84
|
+
repository(owner: $owner, name: $repo) {
|
|
85
|
+
issues(first: 100, after: $after, labels: $labels, states: OPEN) {
|
|
86
|
+
nodes {
|
|
87
|
+
__typename
|
|
88
|
+
number
|
|
89
|
+
title
|
|
90
|
+
body
|
|
91
|
+
url
|
|
92
|
+
createdAt
|
|
93
|
+
labels(first: 50) { nodes { name } }
|
|
94
|
+
assignees(first: 20) { nodes { login } }
|
|
95
|
+
milestone { title }
|
|
96
|
+
}
|
|
97
|
+
pageInfo { hasNextPage endCursor }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}`;
|
|
101
|
+
const ISSUE_QUERY = `query Issue($owner: String!, $repo: String!, $number: Int!, $after: String) {
|
|
84
102
|
repository(owner: $owner, name: $repo) {
|
|
85
103
|
issue(number: $number) {
|
|
104
|
+
id
|
|
86
105
|
number
|
|
87
106
|
title
|
|
88
107
|
body
|
|
@@ -91,7 +110,7 @@ const ISSUE_QUERY = `query Issue($owner: String!, $repo: String!, $number: Int!)
|
|
|
91
110
|
labels(first: 50) { nodes { name } }
|
|
92
111
|
assignees(first: 20) { nodes { login } }
|
|
93
112
|
milestone { title }
|
|
94
|
-
projectItems(first:
|
|
113
|
+
projectItems(first: 100, after: $after) {
|
|
95
114
|
nodes {
|
|
96
115
|
id
|
|
97
116
|
project { id }
|
|
@@ -101,40 +120,80 @@ const ISSUE_QUERY = `query Issue($owner: String!, $repo: String!, $number: Int!)
|
|
|
101
120
|
}
|
|
102
121
|
}
|
|
103
122
|
}
|
|
123
|
+
pageInfo { hasNextPage endCursor }
|
|
104
124
|
}
|
|
105
125
|
}
|
|
106
126
|
}
|
|
107
127
|
}`;
|
|
108
|
-
const
|
|
109
|
-
repository(owner: $owner, name: $repo) {
|
|
110
|
-
id
|
|
111
|
-
}
|
|
112
|
-
}`;
|
|
113
|
-
const ISSUE_ID_QUERY = `query IssueId($owner: String!, $repo: String!, $number: Int!) {
|
|
128
|
+
const REPOSITORY_ISSUE_QUERY = `query Issue($owner: String!, $repo: String!, $number: Int!) {
|
|
114
129
|
repository(owner: $owner, name: $repo) {
|
|
115
130
|
issue(number: $number) {
|
|
116
131
|
id
|
|
132
|
+
number
|
|
133
|
+
title
|
|
134
|
+
body
|
|
135
|
+
url
|
|
136
|
+
createdAt
|
|
137
|
+
labels(first: 50) { nodes { name } }
|
|
138
|
+
assignees(first: 20) { nodes { login } }
|
|
139
|
+
milestone { title }
|
|
117
140
|
}
|
|
118
141
|
}
|
|
119
142
|
}`;
|
|
120
|
-
const
|
|
143
|
+
const ISSUE_STATE_LABELS_QUERY = `query IssueStateLabels($owner: String!, $repo: String!, $number: Int!, $after: String) {
|
|
121
144
|
repository(owner: $owner, name: $repo) {
|
|
122
145
|
issue(number: $number) {
|
|
123
146
|
id
|
|
124
|
-
|
|
147
|
+
labels(first: 50) { nodes { id name } }
|
|
148
|
+
projectItems(first: 100, after: $after) {
|
|
125
149
|
nodes {
|
|
126
150
|
id
|
|
127
151
|
project { id }
|
|
128
152
|
}
|
|
153
|
+
pageInfo { hasNextPage endCursor }
|
|
129
154
|
}
|
|
130
155
|
}
|
|
131
156
|
}
|
|
132
157
|
}`;
|
|
158
|
+
const REPOSITORY_ISSUE_STATE_LABELS_QUERY = `query IssueStateLabels($owner: String!, $repo: String!, $number: Int!) {
|
|
159
|
+
repository(owner: $owner, name: $repo) {
|
|
160
|
+
issue(number: $number) {
|
|
161
|
+
id
|
|
162
|
+
labels(first: 50) { nodes { id name } }
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}`;
|
|
166
|
+
const REPOSITORY_QUERY = `query Repository($owner: String!, $repo: String!) {
|
|
167
|
+
repository(owner: $owner, name: $repo) {
|
|
168
|
+
id
|
|
169
|
+
}
|
|
170
|
+
}`;
|
|
171
|
+
const ISSUE_ID_QUERY = `query IssueId($owner: String!, $repo: String!, $number: Int!) {
|
|
172
|
+
repository(owner: $owner, name: $repo) {
|
|
173
|
+
issue(number: $number) {
|
|
174
|
+
id
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}`;
|
|
178
|
+
const REPOSITORY_LABEL_QUERY = `query RepositoryLabel($owner: String!, $repo: String!, $name: String!) {
|
|
179
|
+
repository(owner: $owner, name: $repo) {
|
|
180
|
+
label(name: $name) {
|
|
181
|
+
id
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}`;
|
|
133
185
|
const CREATE_ISSUE_MUTATION = `mutation CreateIssue($input: CreateIssueInput!) {
|
|
134
186
|
createIssue(input: $input) {
|
|
135
187
|
issue {
|
|
136
188
|
id
|
|
137
189
|
number
|
|
190
|
+
title
|
|
191
|
+
body
|
|
192
|
+
url
|
|
193
|
+
createdAt
|
|
194
|
+
labels(first: 50) { nodes { name } }
|
|
195
|
+
assignees(first: 20) { nodes { login } }
|
|
196
|
+
milestone { title }
|
|
138
197
|
}
|
|
139
198
|
}
|
|
140
199
|
}`;
|
|
@@ -159,6 +218,16 @@ const UPDATE_ISSUE_MUTATION = `mutation UpdateIssue($input: UpdateIssueInput!) {
|
|
|
159
218
|
}
|
|
160
219
|
}
|
|
161
220
|
}`;
|
|
221
|
+
const ADD_LABELS_MUTATION = `mutation AddLabels($input: AddLabelsToLabelableInput!) {
|
|
222
|
+
addLabelsToLabelable(input: $input) {
|
|
223
|
+
clientMutationId
|
|
224
|
+
}
|
|
225
|
+
}`;
|
|
226
|
+
const REMOVE_LABELS_MUTATION = `mutation RemoveLabels($input: RemoveLabelsFromLabelableInput!) {
|
|
227
|
+
removeLabelsFromLabelable(input: $input) {
|
|
228
|
+
clientMutationId
|
|
229
|
+
}
|
|
230
|
+
}`;
|
|
162
231
|
const ADD_COMMENT_MUTATION = `mutation AddComment($input: AddCommentInput!) {
|
|
163
232
|
addComment(input: $input) {
|
|
164
233
|
commentEdge {
|
|
@@ -179,39 +248,52 @@ const DELETE_PROJECT_ITEM_MUTATION = `mutation DeleteProjectItem($input: DeleteP
|
|
|
179
248
|
}
|
|
180
249
|
}`;
|
|
181
250
|
export async function ghIssuesBackend(deps) {
|
|
251
|
+
if (deps.state?.labelPrefix === "") {
|
|
252
|
+
throw new Error("gh-issues state.labelPrefix must be a non-empty string when configured.");
|
|
253
|
+
}
|
|
182
254
|
const client = createGhClient({
|
|
183
255
|
token: deps.token,
|
|
184
256
|
endpoint: deps.endpoint,
|
|
185
257
|
fetch: deps.fetch
|
|
186
258
|
});
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
if (project === null) {
|
|
199
|
-
throw new Error(`Project ${listName} not found or inaccessible.`);
|
|
200
|
-
}
|
|
201
|
-
const field = project.field;
|
|
202
|
-
if (!isStatusField(field)) {
|
|
203
|
-
throw new Error(`Project ${listName} has no Status field; gh-issues requires one.`);
|
|
259
|
+
const repoParts = parseRepo(deps.repo);
|
|
260
|
+
const project = deps.project;
|
|
261
|
+
let listName;
|
|
262
|
+
let session;
|
|
263
|
+
if (project === undefined) {
|
|
264
|
+
if (deps.state?.labelPrefix === undefined || deps.stateMachine === undefined) {
|
|
265
|
+
throw new Error("gh-issues requires project or label-backed stateMachine configuration.");
|
|
266
|
+
}
|
|
267
|
+
listName = deps.repo;
|
|
268
|
+
session = createLabelSession(deps.stateMachine, deps.state.labelPrefix);
|
|
204
269
|
}
|
|
205
|
-
|
|
206
|
-
|
|
270
|
+
else {
|
|
271
|
+
listName = `${project.owner}/${project.number}`;
|
|
272
|
+
const variables = { owner: project.owner, number: project.number };
|
|
273
|
+
const organizationResult = await client.graphql(PROJECT_ORGANIZATION_QUERY, variables);
|
|
274
|
+
let resolvedProject = organizationResult.organization?.projectV2 ?? null;
|
|
275
|
+
if (resolvedProject === null) {
|
|
276
|
+
const userResult = await client.graphql(PROJECT_USER_QUERY, variables);
|
|
277
|
+
resolvedProject = userResult.user?.projectV2 ?? null;
|
|
278
|
+
}
|
|
279
|
+
if (resolvedProject === null) {
|
|
280
|
+
throw new Error(`Project ${listName} not found or inaccessible.`);
|
|
281
|
+
}
|
|
282
|
+
const field = resolvedProject.field;
|
|
283
|
+
if (!isStatusField(field)) {
|
|
284
|
+
throw new Error(`Project ${listName} has no Status field; gh-issues requires one.`);
|
|
285
|
+
}
|
|
286
|
+
if (field.options.length === 0) {
|
|
287
|
+
throw new Error(`Project ${listName} Status field has no options.`);
|
|
288
|
+
}
|
|
289
|
+
session = createProjectSession(resolvedProject, field, deps.state?.labelPrefix);
|
|
207
290
|
}
|
|
208
|
-
const session = createSession(project, field);
|
|
209
|
-
const repoParts = parseRepo(deps.repo);
|
|
210
291
|
const context = {
|
|
211
292
|
client,
|
|
212
293
|
repoOwner: repoParts.owner,
|
|
213
294
|
repoName: repoParts.name,
|
|
214
|
-
issueIds: new Map()
|
|
295
|
+
issueIds: new Map(),
|
|
296
|
+
labels: resolveLabelsFilter(deps.filter)
|
|
215
297
|
};
|
|
216
298
|
function list(name) {
|
|
217
299
|
assertSingleList(name, listName);
|
|
@@ -234,7 +316,7 @@ export async function ghIssuesBackend(deps) {
|
|
|
234
316
|
}
|
|
235
317
|
};
|
|
236
318
|
}
|
|
237
|
-
function
|
|
319
|
+
function createProjectSession(project, field, labelPrefix) {
|
|
238
320
|
const statusOptions = new Map(field.options.map((option) => [option.name, option.id]));
|
|
239
321
|
const states = field.options.map((option) => option.name);
|
|
240
322
|
const events = Object.fromEntries(states.map((state) => [state, Object.freeze({ from: "*", to: state })]));
|
|
@@ -247,7 +329,17 @@ function createSession(project, field) {
|
|
|
247
329
|
projectId: project.id,
|
|
248
330
|
statusFieldId: field.id,
|
|
249
331
|
statusOptions,
|
|
250
|
-
stateMachine
|
|
332
|
+
stateMachine,
|
|
333
|
+
labelPrefix,
|
|
334
|
+
labelIds: new Map()
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function createLabelSession(stateMachine, labelPrefix) {
|
|
338
|
+
return Object.freeze({
|
|
339
|
+
statusOptions: new Map(stateMachine.states.map((state) => [state, state])),
|
|
340
|
+
stateMachine,
|
|
341
|
+
labelPrefix,
|
|
342
|
+
labelIds: new Map()
|
|
251
343
|
});
|
|
252
344
|
}
|
|
253
345
|
function createTasksView(name, session, context) {
|
|
@@ -258,7 +350,7 @@ function createTasksView(name, session, context) {
|
|
|
258
350
|
if (filter?.includeArchived === true) {
|
|
259
351
|
return [];
|
|
260
352
|
}
|
|
261
|
-
const tasks = await
|
|
353
|
+
const tasks = await fetchTasks(name, session, context);
|
|
262
354
|
const filteredTasks = filter?.state === undefined ? tasks : tasks.filter((task) => task.state === filter.state);
|
|
263
355
|
if (filter?.order === "alphabetical") {
|
|
264
356
|
return sortTasks(filteredTasks);
|
|
@@ -281,34 +373,83 @@ function createTasksView(name, session, context) {
|
|
|
281
373
|
*/
|
|
282
374
|
async create(input) {
|
|
283
375
|
const repositoryId = await resolveRepositoryId(context);
|
|
376
|
+
const labelIds = await resolveConfiguredLabelIds(session, context);
|
|
284
377
|
const created = await context.client.graphql(CREATE_ISSUE_MUTATION, {
|
|
285
378
|
input: {
|
|
286
379
|
repositoryId,
|
|
287
380
|
title: input.name,
|
|
288
|
-
body: input.description ?? ""
|
|
381
|
+
body: input.description ?? "",
|
|
382
|
+
...(labelIds.length === 0 ? {} : { labelIds })
|
|
289
383
|
}
|
|
290
384
|
});
|
|
291
385
|
const issue = created.createIssue?.issue;
|
|
292
386
|
const issueId = issue?.id ?? null;
|
|
293
387
|
const issueNumber = issue?.number ?? null;
|
|
294
|
-
if (issueId === null || issueNumber === null) {
|
|
388
|
+
if (issue === undefined || issue === null || issueId === null || issueNumber === null) {
|
|
295
389
|
throw new Error("GitHub createIssue response did not include issue id and number.");
|
|
296
390
|
}
|
|
297
391
|
context.issueIds.set(issueNumber, issueId);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
392
|
+
let projectItemId;
|
|
393
|
+
try {
|
|
394
|
+
if (session.projectId !== undefined) {
|
|
395
|
+
const added = await context.client.graphql(ADD_PROJECT_ITEM_MUTATION, {
|
|
396
|
+
input: {
|
|
397
|
+
projectId: session.projectId,
|
|
398
|
+
contentId: issueId
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
projectItemId = added.addProjectV2ItemById?.item?.id ?? undefined;
|
|
402
|
+
if (projectItemId === undefined) {
|
|
403
|
+
throw new Error("GitHub addProjectV2ItemById response did not include project item id.");
|
|
404
|
+
}
|
|
302
405
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
406
|
+
if (session.labelPrefix === undefined) {
|
|
407
|
+
if (projectItemId === undefined) {
|
|
408
|
+
throw new Error("gh-issues project-backed state requires a project item id.");
|
|
409
|
+
}
|
|
410
|
+
await updateProjectItemStatus(projectItemId, session.stateMachine.initial, session, context);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
await addStateLabel(issueId, session.stateMachine.initial, session, context);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
if (projectItemId !== undefined && session.projectId !== undefined) {
|
|
418
|
+
await context.client.graphql(DELETE_PROJECT_ITEM_MUTATION, {
|
|
419
|
+
input: { projectId: session.projectId, itemId: projectItemId }
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
await context.client.graphql(UPDATE_ISSUE_MUTATION, {
|
|
423
|
+
input: { id: issueId, state: "CLOSED" }
|
|
424
|
+
});
|
|
425
|
+
throw error;
|
|
307
426
|
}
|
|
308
|
-
|
|
309
|
-
|
|
427
|
+
const task = mapIssueToTask({
|
|
428
|
+
issue: {
|
|
429
|
+
...issue,
|
|
430
|
+
number: issueNumber,
|
|
431
|
+
title: input.name,
|
|
432
|
+
body: input.description ?? "",
|
|
433
|
+
url: issue.url,
|
|
434
|
+
createdAt: issue.createdAt,
|
|
435
|
+
labels: {
|
|
436
|
+
nodes: [
|
|
437
|
+
...(context.labels ?? []).map((label) => ({ name: label })),
|
|
438
|
+
...(session.labelPrefix === undefined
|
|
439
|
+
? []
|
|
440
|
+
: [{ name: `${session.labelPrefix}${session.stateMachine.initial}` }])
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
projectItemId,
|
|
445
|
+
statusName: session.stateMachine.initial,
|
|
446
|
+
listName: name,
|
|
447
|
+
session
|
|
448
|
+
});
|
|
449
|
+
return session.labelPrefix === undefined ? task : { ...task, state: session.stateMachine.initial };
|
|
310
450
|
},
|
|
311
451
|
async update(id, patch) {
|
|
452
|
+
const task = await fetchIssueTask(id, name, session, context);
|
|
312
453
|
const input = {};
|
|
313
454
|
if (patch.name !== undefined) {
|
|
314
455
|
input.title = patch.name;
|
|
@@ -323,7 +464,11 @@ function createTasksView(name, session, context) {
|
|
|
323
464
|
input
|
|
324
465
|
});
|
|
325
466
|
}
|
|
326
|
-
return
|
|
467
|
+
return {
|
|
468
|
+
...task,
|
|
469
|
+
name: patch.name ?? task.name,
|
|
470
|
+
description: patch.description ?? task.description
|
|
471
|
+
};
|
|
327
472
|
},
|
|
328
473
|
async fire(id, event, _opts) {
|
|
329
474
|
if (!session.statusOptions.has(event)) {
|
|
@@ -333,12 +478,28 @@ function createTasksView(name, session, context) {
|
|
|
333
478
|
reason: `Unknown gh-issues Status state "${event}".`
|
|
334
479
|
});
|
|
335
480
|
}
|
|
336
|
-
const
|
|
481
|
+
const task = await fetchIssueTask(id, name, session, context);
|
|
482
|
+
const transition = findEvent(session.stateMachine, task.state, event);
|
|
483
|
+
if (transition === undefined) {
|
|
484
|
+
throw new InvalidTransitionError({
|
|
485
|
+
task,
|
|
486
|
+
event,
|
|
487
|
+
to: event,
|
|
488
|
+
reason: `Cannot fire event "${event}" from task state "${task.state}".`
|
|
489
|
+
});
|
|
490
|
+
}
|
|
337
491
|
// opts.metadataPatch writes are out of scope for v1 on gh-issues.
|
|
338
|
-
|
|
339
|
-
|
|
492
|
+
if (session.labelPrefix === undefined) {
|
|
493
|
+
const projectItemId = projectItemIdFromTask(task);
|
|
494
|
+
await updateProjectItemStatus(projectItemId, event, session, context);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
await updateIssueStateLabel(id, name, event, session, context);
|
|
498
|
+
}
|
|
499
|
+
return { ...task, state: event };
|
|
340
500
|
},
|
|
341
501
|
async comment(id, body) {
|
|
502
|
+
await fetchIssueTask(id, name, session, context);
|
|
342
503
|
await context.client.graphql(ADD_COMMENT_MUTATION, {
|
|
343
504
|
input: {
|
|
344
505
|
subjectId: await resolveIssueId(id, name, context),
|
|
@@ -347,12 +508,23 @@ function createTasksView(name, session, context) {
|
|
|
347
508
|
});
|
|
348
509
|
},
|
|
349
510
|
async canFire(id, event) {
|
|
350
|
-
|
|
511
|
+
const task = await fetchIssueTask(id, name, session, context);
|
|
512
|
+
return findEvent(session.stateMachine, task.state, event) !== undefined;
|
|
351
513
|
},
|
|
352
514
|
async events(id) {
|
|
353
|
-
|
|
515
|
+
const task = await fetchIssueTask(id, name, session, context);
|
|
516
|
+
return eventsFromState(session.stateMachine, task.state);
|
|
354
517
|
},
|
|
355
518
|
async delete(id) {
|
|
519
|
+
if (session.projectId === undefined) {
|
|
520
|
+
await context.client.graphql(UPDATE_ISSUE_MUTATION, {
|
|
521
|
+
input: {
|
|
522
|
+
id: await resolveIssueId(id, name, context),
|
|
523
|
+
state: "CLOSED"
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
356
528
|
const projectItemId = await resolveProjectItemId(id, name, session, context);
|
|
357
529
|
await context.client.graphql(DELETE_PROJECT_ITEM_MUTATION, {
|
|
358
530
|
input: {
|
|
@@ -362,12 +534,15 @@ function createTasksView(name, session, context) {
|
|
|
362
534
|
});
|
|
363
535
|
},
|
|
364
536
|
async move(id, anchor) {
|
|
365
|
-
|
|
537
|
+
assertProjectBacked(session, "move");
|
|
538
|
+
const task = await fetchIssueTask(id, name, session, context);
|
|
539
|
+
const projectItemId = projectItemIdFromTask(task);
|
|
366
540
|
const afterId = await resolveMoveAfterId(id, anchor, name, session, context);
|
|
367
541
|
await updateProjectItemPosition(projectItemId, afterId, session, context);
|
|
368
|
-
return
|
|
542
|
+
return task;
|
|
369
543
|
},
|
|
370
544
|
async reorder(ids) {
|
|
545
|
+
assertProjectBacked(session, "reorder");
|
|
371
546
|
const currentTasks = await fetchProjectTasks(name, session, context);
|
|
372
547
|
const currentIds = currentTasks.map((task) => task.id);
|
|
373
548
|
const currentSet = new Set(currentIds);
|
|
@@ -389,18 +564,32 @@ function createTasksView(name, session, context) {
|
|
|
389
564
|
}
|
|
390
565
|
const itemIdsByTaskId = new Map(currentTasks.map((task) => [task.id, projectItemIdFromTask(task)]));
|
|
391
566
|
let afterId = null;
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
567
|
+
try {
|
|
568
|
+
for (const id of ids) {
|
|
569
|
+
const projectItemId = itemIdsByTaskId.get(id);
|
|
570
|
+
if (projectItemId === undefined) {
|
|
571
|
+
throw new OrderMismatchError({ missing: [id], extra: [] });
|
|
572
|
+
}
|
|
573
|
+
await updateProjectItemPosition(projectItemId, afterId, session, context);
|
|
574
|
+
afterId = projectItemId;
|
|
396
575
|
}
|
|
397
|
-
|
|
398
|
-
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
await restoreProjectOrder(currentTasks, session, context);
|
|
579
|
+
throw error;
|
|
399
580
|
}
|
|
400
581
|
return fetchProjectTasks(name, session, context);
|
|
401
582
|
}
|
|
402
583
|
};
|
|
403
584
|
}
|
|
585
|
+
async function restoreProjectOrder(tasks, session, context) {
|
|
586
|
+
let afterId = null;
|
|
587
|
+
for (const task of tasks) {
|
|
588
|
+
const projectItemId = projectItemIdFromTask(task);
|
|
589
|
+
await updateProjectItemPosition(projectItemId, afterId, session, context);
|
|
590
|
+
afterId = projectItemId;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
404
593
|
async function resolveRepositoryId(context) {
|
|
405
594
|
if (context.repositoryId !== undefined) {
|
|
406
595
|
return context.repositoryId;
|
|
@@ -435,26 +624,36 @@ async function resolveIssueId(id, listName, context) {
|
|
|
435
624
|
return issueId;
|
|
436
625
|
}
|
|
437
626
|
async function resolveProjectItemId(id, listName, session, context) {
|
|
627
|
+
assertProjectBacked(session, "resolve project item");
|
|
438
628
|
const issueNumber = parseIssueNumber(id, listName);
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
629
|
+
let after;
|
|
630
|
+
while (true) {
|
|
631
|
+
const result = await context.client.graphql(ISSUE_STATE_LABELS_QUERY, {
|
|
632
|
+
owner: context.repoOwner,
|
|
633
|
+
repo: context.repoName,
|
|
634
|
+
number: issueNumber,
|
|
635
|
+
after
|
|
636
|
+
});
|
|
637
|
+
const issue = result.repository?.issue ?? null;
|
|
638
|
+
if (issue === null) {
|
|
639
|
+
throw new TaskNotFoundError(`Task "${listName}/${id}" not found.`);
|
|
640
|
+
}
|
|
641
|
+
if (issue.id !== undefined && issue.id !== null) {
|
|
642
|
+
context.issueIds.set(issueNumber, issue.id);
|
|
643
|
+
}
|
|
644
|
+
const projectItem = issue.projectItems?.nodes?.find((item) => item.project?.id === session.projectId) ?? null;
|
|
645
|
+
if (projectItem !== null) {
|
|
646
|
+
return projectItem.id;
|
|
647
|
+
}
|
|
648
|
+
const pageInfo = issue.projectItems?.pageInfo;
|
|
649
|
+
if (pageInfo?.hasNextPage !== true || pageInfo.endCursor === undefined || pageInfo.endCursor === null) {
|
|
650
|
+
throw new TaskNotFoundError(`Task "${listName}/${id}" not found.`);
|
|
651
|
+
}
|
|
652
|
+
after = pageInfo.endCursor;
|
|
454
653
|
}
|
|
455
|
-
return projectItem.id;
|
|
456
654
|
}
|
|
457
655
|
async function updateProjectItemStatus(projectItemId, state, session, context) {
|
|
656
|
+
assertProjectBacked(session, "set project status");
|
|
458
657
|
const optionId = session.statusOptions.get(state);
|
|
459
658
|
if (optionId === undefined) {
|
|
460
659
|
throw new InvalidTransitionError({
|
|
@@ -473,7 +672,103 @@ async function updateProjectItemStatus(projectItemId, state, session, context) {
|
|
|
473
672
|
}
|
|
474
673
|
});
|
|
475
674
|
}
|
|
675
|
+
async function addStateLabel(issueId, state, session, context) {
|
|
676
|
+
const labelId = await resolveStateLabelId(state, session, context);
|
|
677
|
+
await context.client.graphql(ADD_LABELS_MUTATION, {
|
|
678
|
+
input: {
|
|
679
|
+
labelableId: issueId,
|
|
680
|
+
labelIds: [labelId]
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
async function updateIssueStateLabel(id, listName, state, session, context) {
|
|
685
|
+
const issueNumber = parseIssueNumber(id, listName);
|
|
686
|
+
let after;
|
|
687
|
+
let issue = null;
|
|
688
|
+
let isInProject = session.projectId === undefined;
|
|
689
|
+
while (true) {
|
|
690
|
+
const result = await context.client.graphql(session.projectId === undefined
|
|
691
|
+
? REPOSITORY_ISSUE_STATE_LABELS_QUERY
|
|
692
|
+
: ISSUE_STATE_LABELS_QUERY, {
|
|
693
|
+
owner: context.repoOwner,
|
|
694
|
+
repo: context.repoName,
|
|
695
|
+
number: issueNumber,
|
|
696
|
+
after
|
|
697
|
+
});
|
|
698
|
+
const currentIssue = result.repository?.issue ?? null;
|
|
699
|
+
if (currentIssue === null) {
|
|
700
|
+
throw new TaskNotFoundError(`Task "${listName}/${id}" not found.`);
|
|
701
|
+
}
|
|
702
|
+
if (typeof currentIssue.id === "string") {
|
|
703
|
+
context.issueIds.set(issueNumber, currentIssue.id);
|
|
704
|
+
}
|
|
705
|
+
issue ??= currentIssue;
|
|
706
|
+
if (session.projectId === undefined) {
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
if (currentIssue.projectItems?.nodes?.some((item) => item.project?.id === session.projectId)) {
|
|
710
|
+
isInProject = true;
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
const pageInfo = currentIssue.projectItems?.pageInfo;
|
|
714
|
+
if (pageInfo?.hasNextPage !== true || pageInfo.endCursor === undefined || pageInfo.endCursor === null) {
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
after = pageInfo.endCursor;
|
|
718
|
+
}
|
|
719
|
+
if (issue === null) {
|
|
720
|
+
throw new TaskNotFoundError(`Task "${listName}/${id}" not found.`);
|
|
721
|
+
}
|
|
722
|
+
const issueId = issue.id ?? null;
|
|
723
|
+
if (issueId === null) {
|
|
724
|
+
throw new TaskNotFoundError(`Task "${listName}/${id}" not found.`);
|
|
725
|
+
}
|
|
726
|
+
if (!isInProject) {
|
|
727
|
+
throw new TaskNotFoundError(`Task "${listName}/${id}" not found.`);
|
|
728
|
+
}
|
|
729
|
+
const targetLabel = `${session.labelPrefix}${state}`;
|
|
730
|
+
const stateLabels = (issue.labels?.nodes ?? []).filter((node) => node !== null && node.name.startsWith(session.labelPrefix ?? ""));
|
|
731
|
+
const targetNode = stateLabels.find((node) => node.name === targetLabel);
|
|
732
|
+
if (targetNode === undefined) {
|
|
733
|
+
await addStateLabel(issueId, state, session, context);
|
|
734
|
+
}
|
|
735
|
+
const labelIdsToRemove = stateLabels
|
|
736
|
+
.filter((node) => node.name !== targetLabel && node.id !== undefined)
|
|
737
|
+
.map((node) => node.id);
|
|
738
|
+
if (labelIdsToRemove.length > 0) {
|
|
739
|
+
await context.client.graphql(REMOVE_LABELS_MUTATION, {
|
|
740
|
+
input: {
|
|
741
|
+
labelableId: issueId,
|
|
742
|
+
labelIds: labelIdsToRemove
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async function resolveStateLabelId(state, session, context) {
|
|
748
|
+
return resolveLabelId(`${session.labelPrefix}${state}`, session, context);
|
|
749
|
+
}
|
|
750
|
+
async function resolveConfiguredLabelIds(session, context) {
|
|
751
|
+
return Promise.all((context.labels ?? []).map((name) => resolveLabelId(name, session, context)));
|
|
752
|
+
}
|
|
753
|
+
async function resolveLabelId(name, session, context) {
|
|
754
|
+
const cachedLabelId = session.labelIds.get(name);
|
|
755
|
+
if (cachedLabelId !== undefined) {
|
|
756
|
+
return cachedLabelId;
|
|
757
|
+
}
|
|
758
|
+
const result = await context.client.graphql(REPOSITORY_LABEL_QUERY, {
|
|
759
|
+
owner: context.repoOwner,
|
|
760
|
+
repo: context.repoName,
|
|
761
|
+
name
|
|
762
|
+
});
|
|
763
|
+
const labelId = result.repository?.label?.id ?? null;
|
|
764
|
+
if (labelId === null) {
|
|
765
|
+
throw new Error(`GitHub label "${name}" not found or inaccessible.`);
|
|
766
|
+
}
|
|
767
|
+
session.labelIds.set(name, labelId);
|
|
768
|
+
return labelId;
|
|
769
|
+
}
|
|
476
770
|
async function updateProjectItemPosition(projectItemId, afterId, session, context) {
|
|
771
|
+
assertProjectBacked(session, "update project position");
|
|
477
772
|
await context.client.graphql(UPDATE_PROJECT_ITEM_POSITION_MUTATION, {
|
|
478
773
|
input: {
|
|
479
774
|
projectId: session.projectId,
|
|
@@ -524,6 +819,7 @@ async function resolveMoveAfterId(movingId, anchor, listName, session, context)
|
|
|
524
819
|
return null;
|
|
525
820
|
}
|
|
526
821
|
async function fetchProjectTasks(listName, session, context) {
|
|
822
|
+
assertProjectBacked(session, "list project items");
|
|
527
823
|
const tasks = [];
|
|
528
824
|
let after = null;
|
|
529
825
|
do {
|
|
@@ -542,27 +838,67 @@ async function fetchProjectTasks(listName, session, context) {
|
|
|
542
838
|
} while (after !== null);
|
|
543
839
|
return tasks;
|
|
544
840
|
}
|
|
841
|
+
async function fetchTasks(listName, session, context) {
|
|
842
|
+
if (session.projectId !== undefined) {
|
|
843
|
+
return fetchProjectTasks(listName, session, context);
|
|
844
|
+
}
|
|
845
|
+
const tasks = [];
|
|
846
|
+
let after = null;
|
|
847
|
+
do {
|
|
848
|
+
const result = await context.client.graphql(REPOSITORY_ISSUES_QUERY, {
|
|
849
|
+
owner: context.repoOwner,
|
|
850
|
+
repo: context.repoName,
|
|
851
|
+
labels: context.labels,
|
|
852
|
+
after
|
|
853
|
+
});
|
|
854
|
+
const issues = result.repository?.issues;
|
|
855
|
+
for (const issue of issues?.nodes ?? []) {
|
|
856
|
+
tasks.push(mapIssueToTask({ issue, statusName: null, listName, session }));
|
|
857
|
+
}
|
|
858
|
+
after = issues?.pageInfo?.hasNextPage === true ? (issues.pageInfo.endCursor ?? null) : null;
|
|
859
|
+
} while (after !== null);
|
|
860
|
+
return tasks;
|
|
861
|
+
}
|
|
545
862
|
async function fetchIssueTask(id, listName, session, context) {
|
|
546
863
|
const issueNumber = parseIssueNumber(id, listName);
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
864
|
+
let after;
|
|
865
|
+
let issue = null;
|
|
866
|
+
let projectItem = null;
|
|
867
|
+
while (true) {
|
|
868
|
+
const result = await context.client.graphql(session.projectId === undefined ? REPOSITORY_ISSUE_QUERY : ISSUE_QUERY, {
|
|
869
|
+
owner: context.repoOwner,
|
|
870
|
+
repo: context.repoName,
|
|
871
|
+
number: issueNumber,
|
|
872
|
+
after
|
|
873
|
+
});
|
|
874
|
+
const currentIssue = result.repository?.issue ?? null;
|
|
875
|
+
if (currentIssue === null) {
|
|
876
|
+
throw new TaskNotFoundError(`Task "${listName}/${id}" not found.`);
|
|
877
|
+
}
|
|
878
|
+
if (currentIssue.id !== undefined) {
|
|
879
|
+
context.issueIds.set(issueNumber, currentIssue.id);
|
|
880
|
+
}
|
|
881
|
+
issue ??= currentIssue;
|
|
882
|
+
if (session.projectId === undefined) {
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
projectItem =
|
|
886
|
+
currentIssue.projectItems?.nodes?.find((item) => item.project?.id === session.projectId) ?? null;
|
|
887
|
+
if (projectItem !== null) {
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
const pageInfo = currentIssue.projectItems?.pageInfo;
|
|
891
|
+
if (pageInfo?.hasNextPage !== true || pageInfo.endCursor === undefined || pageInfo.endCursor === null) {
|
|
892
|
+
throw new TaskNotFoundError(`Task "${listName}/${id}" not found.`);
|
|
893
|
+
}
|
|
894
|
+
after = pageInfo.endCursor;
|
|
559
895
|
}
|
|
560
896
|
return mapIssueToTask({
|
|
561
897
|
issue,
|
|
562
|
-
projectItemId: projectItem
|
|
563
|
-
statusName: projectItem
|
|
898
|
+
projectItemId: projectItem?.id,
|
|
899
|
+
statusName: projectItem?.fieldValueByName?.name ?? null,
|
|
564
900
|
listName,
|
|
565
|
-
|
|
901
|
+
session
|
|
566
902
|
});
|
|
567
903
|
}
|
|
568
904
|
function parseIssueNumber(id, listName) {
|
|
@@ -582,7 +918,7 @@ function mapProjectItemToTask(item, listName, session) {
|
|
|
582
918
|
projectItemId: item.id,
|
|
583
919
|
statusName: item.fieldValueByName?.name ?? null,
|
|
584
920
|
listName,
|
|
585
|
-
|
|
921
|
+
session
|
|
586
922
|
});
|
|
587
923
|
}
|
|
588
924
|
function isIssueNode(value) {
|
|
@@ -607,17 +943,23 @@ function mapIssueToTask(options) {
|
|
|
607
943
|
qualifiedId: `${options.listName}#${id}`,
|
|
608
944
|
name: options.issue.title,
|
|
609
945
|
description: options.issue.body ?? "",
|
|
610
|
-
state: options.statusName
|
|
946
|
+
state: resolveTaskState(labels, options.statusName, options.session),
|
|
611
947
|
metadata: {
|
|
612
948
|
url: options.issue.url,
|
|
613
949
|
labels,
|
|
614
950
|
assignees,
|
|
615
951
|
milestone: options.issue.milestone?.title ?? null,
|
|
616
|
-
projectItemId: options.projectItemId,
|
|
952
|
+
...(options.projectItemId === undefined ? {} : { projectItemId: options.projectItemId }),
|
|
617
953
|
created: options.issue.createdAt
|
|
618
954
|
}
|
|
619
955
|
};
|
|
620
956
|
}
|
|
957
|
+
function resolveTaskState(labels, statusName, session) {
|
|
958
|
+
if (session.labelPrefix === undefined) {
|
|
959
|
+
return statusName ?? session.stateMachine.initial;
|
|
960
|
+
}
|
|
961
|
+
return (session.stateMachine.states.find((state) => labels.includes(`${session.labelPrefix}${state}`)) ?? session.stateMachine.initial);
|
|
962
|
+
}
|
|
621
963
|
function projectItemIdFromTask(task) {
|
|
622
964
|
const projectItemId = task.metadata.projectItemId;
|
|
623
965
|
if (typeof projectItemId !== "string") {
|
|
@@ -625,6 +967,25 @@ function projectItemIdFromTask(task) {
|
|
|
625
967
|
}
|
|
626
968
|
return projectItemId;
|
|
627
969
|
}
|
|
970
|
+
function assertProjectBacked(session, operation) {
|
|
971
|
+
if (session.projectId === undefined || session.statusFieldId === undefined) {
|
|
972
|
+
throw new Error(`gh-issues ${operation} requires a configured GitHub Project.`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
function resolveLabelsFilter(filter) {
|
|
976
|
+
if (filter === undefined) {
|
|
977
|
+
return undefined;
|
|
978
|
+
}
|
|
979
|
+
const prefix = "label:";
|
|
980
|
+
if (!filter.startsWith(prefix)) {
|
|
981
|
+
throw new Error('gh-issues filter currently supports only "label:<name>".');
|
|
982
|
+
}
|
|
983
|
+
const label = filter.slice(prefix.length).trim();
|
|
984
|
+
if (label.length === 0) {
|
|
985
|
+
throw new Error('gh-issues filter requires a non-empty label after "label:".');
|
|
986
|
+
}
|
|
987
|
+
return [label];
|
|
988
|
+
}
|
|
628
989
|
function parseRepo(repo) {
|
|
629
990
|
const parts = repo.split("/");
|
|
630
991
|
if (parts.length !== 2 || parts[0] === "" || parts[1] === "") {
|