@commandable/integration-data 0.0.4 → 0.0.6
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/dist/credentials-index.d.ts.map +1 -1
- package/dist/credentials-index.js +64 -0
- package/dist/credentials-index.js.map +1 -1
- package/integrations/airtable/__tests__/get_handlers.test.ts +25 -16
- package/integrations/github/__tests__/get_handlers.test.ts +202 -7
- package/integrations/github/__tests__/write_handlers.test.ts +178 -24
- package/integrations/github/handlers/create_commit.js +5 -12
- package/integrations/github/handlers/create_pull_request_review.js +10 -0
- package/integrations/github/handlers/create_release.js +14 -0
- package/integrations/github/handlers/delete_branch.js +8 -0
- package/integrations/github/handlers/delete_file.js +9 -0
- package/integrations/github/handlers/fork_repo.js +10 -0
- package/integrations/github/handlers/get_commit.js +8 -0
- package/integrations/github/handlers/get_file_contents.js +21 -0
- package/integrations/github/handlers/get_job_logs.js +6 -0
- package/integrations/github/handlers/get_latest_release.js +4 -0
- package/integrations/github/handlers/get_me.js +4 -0
- package/integrations/github/handlers/get_pull_request.js +4 -0
- package/integrations/github/handlers/get_pull_request_diff.js +8 -0
- package/integrations/github/handlers/get_repo_tree.js +12 -0
- package/integrations/github/handlers/get_workflow_run.js +4 -0
- package/integrations/github/handlers/list_branches.js +6 -1
- package/integrations/github/handlers/list_commits.js +5 -6
- package/integrations/github/handlers/list_issue_comments.js +8 -0
- package/integrations/github/handlers/list_issues.js +5 -6
- package/integrations/github/handlers/list_labels.js +8 -0
- package/integrations/github/handlers/list_pull_request_comments.js +8 -0
- package/integrations/github/handlers/list_pull_request_files.js +8 -0
- package/integrations/github/handlers/list_pull_requests.js +7 -2
- package/integrations/github/handlers/list_releases.js +8 -0
- package/integrations/github/handlers/list_tags.js +8 -0
- package/integrations/github/handlers/list_workflow_runs.js +11 -0
- package/integrations/github/handlers/request_pull_request_reviewers.js +10 -0
- package/integrations/github/handlers/search_code.js +8 -0
- package/integrations/github/handlers/search_issues.js +8 -0
- package/integrations/github/handlers/search_pull_requests.js +8 -0
- package/integrations/github/handlers/search_repos.js +10 -0
- package/integrations/github/handlers/update_pull_request.js +13 -0
- package/integrations/github/manifest.json +58 -20
- package/integrations/github/schemas/create_pull_request_review.json +17 -0
- package/integrations/github/schemas/create_release.json +16 -0
- package/integrations/github/schemas/delete_branch.json +10 -0
- package/integrations/github/schemas/delete_file.json +13 -0
- package/integrations/github/schemas/fork_repo.json +11 -0
- package/integrations/github/schemas/get_commit.json +12 -0
- package/integrations/github/schemas/get_file_contents.json +11 -0
- package/integrations/github/schemas/get_job_logs.json +10 -0
- package/integrations/github/schemas/get_pull_request.json +10 -0
- package/integrations/github/schemas/get_pull_request_diff.json +10 -0
- package/integrations/github/schemas/get_repo_tree.json +12 -0
- package/integrations/github/schemas/get_workflow_run.json +10 -0
- package/integrations/github/schemas/list_branches.json +12 -0
- package/integrations/github/schemas/list_commits.json +5 -3
- package/integrations/github/schemas/list_issue_comments.json +12 -0
- package/integrations/github/schemas/list_issues.json +4 -2
- package/integrations/github/schemas/list_labels.json +11 -0
- package/integrations/github/schemas/list_pull_request_comments.json +12 -0
- package/integrations/github/schemas/list_pull_request_files.json +12 -0
- package/integrations/github/schemas/list_pull_requests.json +7 -1
- package/integrations/github/schemas/list_releases.json +11 -0
- package/integrations/github/schemas/list_tags.json +11 -0
- package/integrations/github/schemas/list_workflow_runs.json +18 -0
- package/integrations/github/schemas/request_pull_request_reviewers.json +20 -0
- package/integrations/github/schemas/search_code.json +10 -0
- package/integrations/github/schemas/search_issues.json +10 -0
- package/integrations/github/schemas/search_pull_requests.json +10 -0
- package/integrations/github/schemas/search_repos.json +12 -0
- package/integrations/github/schemas/update_pull_request.json +15 -0
- package/integrations/google-calendar/__tests__/write_and_admin_handlers.test.ts +0 -13
- package/integrations/google-calendar/handlers/get_event.js +5 -1
- package/integrations/google-calendar/handlers/list_events.js +2 -0
- package/integrations/google-calendar/manifest.json +17 -18
- package/integrations/google-calendar/prompt.md +68 -0
- package/integrations/google-calendar/schemas/id_calendar_event.json +4 -2
- package/integrations/google-calendar/schemas/list_events.json +10 -8
- package/integrations/google-docs/__tests__/get_handlers.test.ts +5 -20
- package/integrations/google-docs/__tests__/write_handlers.test.ts +38 -52
- package/integrations/google-docs/handlers/insert_inline_image_after_first_match.js +1 -1
- package/integrations/google-docs/handlers/read_document.js +189 -0
- package/integrations/google-docs/manifest.json +16 -31
- package/integrations/google-docs/prompt.md +49 -0
- package/integrations/google-docs/schemas/insert_inline_image_after_first_match.json +0 -1
- package/integrations/google-docs/schemas/{get_document_text.json → read_document.json} +5 -2
- package/integrations/google-docs/todo.md +18 -0
- package/integrations/google-drive/__tests__/handlers.test.ts +145 -0
- package/integrations/google-drive/__tests__/usage_parity.test.ts +9 -0
- package/integrations/google-drive/handlers/get_file.js +2 -4
- package/integrations/google-drive/handlers/get_file_content.js +41 -0
- package/integrations/google-drive/handlers/list_files.js +15 -0
- package/integrations/google-drive/handlers/search_files.js +20 -0
- package/integrations/google-drive/handlers/share_file.js +20 -0
- package/integrations/google-drive/manifest.json +37 -10
- package/integrations/google-drive/prompt.md +59 -0
- package/integrations/google-drive/schemas/get_file.json +2 -2
- package/integrations/google-drive/schemas/get_file_content.json +11 -0
- package/integrations/google-drive/schemas/list_files.json +12 -0
- package/integrations/google-drive/schemas/search_files.json +14 -0
- package/integrations/google-drive/schemas/share_file.json +23 -0
- package/integrations/google-gmail/__tests__/get_handlers.test.ts +134 -0
- package/integrations/google-gmail/__tests__/usage_parity.test.ts +9 -0
- package/integrations/google-gmail/__tests__/write_and_admin_handlers.test.ts +211 -0
- package/integrations/google-gmail/credentials.json +57 -0
- package/integrations/google-gmail/credentials_hint_oauth_token.md +8 -0
- package/integrations/google-gmail/credentials_hint_service_account.md +10 -0
- package/integrations/google-gmail/handlers/create_draft_email.js +27 -0
- package/integrations/google-gmail/handlers/create_label.js +12 -0
- package/integrations/google-gmail/handlers/delete_draft.js +13 -0
- package/integrations/google-gmail/handlers/delete_label.js +13 -0
- package/integrations/google-gmail/handlers/delete_message.js +13 -0
- package/integrations/google-gmail/handlers/delete_thread.js +13 -0
- package/integrations/google-gmail/handlers/get_draft.js +6 -0
- package/integrations/google-gmail/handlers/get_label.js +6 -0
- package/integrations/google-gmail/handlers/get_message.js +14 -0
- package/integrations/google-gmail/handlers/get_profile.js +5 -0
- package/integrations/google-gmail/handlers/get_thread.js +14 -0
- package/integrations/google-gmail/handlers/list_drafts.js +15 -0
- package/integrations/google-gmail/handlers/list_labels.js +5 -0
- package/integrations/google-gmail/handlers/list_messages.js +19 -0
- package/integrations/google-gmail/handlers/list_threads.js +19 -0
- package/integrations/google-gmail/handlers/modify_message.js +11 -0
- package/integrations/google-gmail/handlers/modify_thread.js +11 -0
- package/integrations/google-gmail/handlers/read_email.js +56 -0
- package/integrations/google-gmail/handlers/send_draft.js +15 -0
- package/integrations/google-gmail/handlers/send_email.js +22 -0
- package/integrations/google-gmail/handlers/trash_message.js +6 -0
- package/integrations/google-gmail/handlers/trash_thread.js +6 -0
- package/integrations/google-gmail/handlers/untrash_message.js +6 -0
- package/integrations/google-gmail/handlers/untrash_thread.js +6 -0
- package/integrations/google-gmail/handlers/update_label.js +15 -0
- package/integrations/google-gmail/manifest.json +33 -0
- package/integrations/google-gmail/prompt.md +52 -0
- package/integrations/google-gmail/schemas/create_draft_email.json +16 -0
- package/integrations/google-gmail/schemas/create_label.json +26 -0
- package/integrations/google-gmail/schemas/get_message.json +20 -0
- package/integrations/{google-docs/schemas/get_document_structured.json → google-gmail/schemas/get_profile.json} +4 -2
- package/integrations/google-gmail/schemas/get_thread.json +20 -0
- package/integrations/google-gmail/schemas/id_draft.json +16 -0
- package/integrations/google-gmail/schemas/id_label.json +16 -0
- package/integrations/google-gmail/schemas/id_message.json +16 -0
- package/integrations/google-gmail/schemas/id_thread.json +16 -0
- package/integrations/google-gmail/schemas/list_drafts.json +30 -0
- package/integrations/{google-sheet/schemas/get_developer_metadata.json → google-gmail/schemas/list_labels.json} +4 -3
- package/integrations/google-gmail/schemas/list_messages.json +35 -0
- package/integrations/google-gmail/schemas/list_threads.json +35 -0
- package/integrations/google-gmail/schemas/modify_message.json +24 -0
- package/integrations/google-gmail/schemas/modify_thread.json +24 -0
- package/integrations/google-gmail/schemas/read_email.json +10 -0
- package/integrations/google-gmail/schemas/send_draft.json +29 -0
- package/integrations/google-gmail/schemas/send_email.json +17 -0
- package/integrations/google-gmail/schemas/update_label.json +33 -0
- package/integrations/google-sheet/__tests__/get_handlers.test.ts +7 -52
- package/integrations/google-sheet/__tests__/write_handlers.test.ts +1 -20
- package/integrations/google-sheet/handlers/get_spreadsheet.js +2 -0
- package/integrations/google-sheet/handlers/read_sheet.js +75 -0
- package/integrations/google-sheet/manifest.json +13 -62
- package/integrations/google-sheet/prompt.md +49 -0
- package/integrations/google-sheet/schemas/get_spreadsheet.json +5 -4
- package/integrations/google-sheet/schemas/read_sheet.json +21 -0
- package/integrations/google-slides/__tests__/get_handlers.test.ts +13 -9
- package/integrations/google-slides/__tests__/write_handlers.test.ts +4 -5
- package/integrations/google-slides/handlers/read_presentation.js +51 -0
- package/integrations/google-slides/manifest.json +13 -13
- package/integrations/google-slides/prompt.md +56 -0
- package/integrations/new_integration_prompt.md +5 -1
- package/package.json +1 -1
- package/integrations/google-calendar/handlers/update_event.js +0 -5
- package/integrations/google-calendar/schemas/update_event.json +0 -10
- package/integrations/google-docs/handlers/get_document.js +0 -12
- package/integrations/google-docs/handlers/get_document_structured.js +0 -6
- package/integrations/google-docs/handlers/get_document_text.js +0 -17
- package/integrations/google-docs/schemas/get_document.json +0 -11
- package/integrations/google-sheet/handlers/batch_clear_values_by_data_filter.js +0 -6
- package/integrations/google-sheet/handlers/batch_get_values.js +0 -16
- package/integrations/google-sheet/handlers/batch_update_values_by_data_filter.js +0 -16
- package/integrations/google-sheet/handlers/get_developer_metadata.js +0 -6
- package/integrations/google-sheet/handlers/get_spreadsheet_by_data_filter.js +0 -10
- package/integrations/google-sheet/handlers/get_values.js +0 -14
- package/integrations/google-sheet/handlers/get_values_by_data_filter.js +0 -14
- package/integrations/google-sheet/handlers/search_developer_metadata.js +0 -7
- package/integrations/google-sheet/schemas/batch_clear_values_by_data_filter.json +0 -10
- package/integrations/google-sheet/schemas/batch_get_values.json +0 -13
- package/integrations/google-sheet/schemas/batch_update_values_by_data_filter.json +0 -25
- package/integrations/google-sheet/schemas/get_spreadsheet_by_data_filter.json +0 -11
- package/integrations/google-sheet/schemas/get_values.json +0 -13
- package/integrations/google-sheet/schemas/get_values_by_data_filter.json +0 -17
- package/integrations/google-sheet/schemas/search_developer_metadata.json +0 -14
- package/integrations/google-slides/handlers/get_presentation.js +0 -6
|
@@ -24,6 +24,27 @@ const variants: VariantConfig[] = [
|
|
|
24
24
|
const hasWriteEnv = hasEnv('GITHUB_TEST_OWNER', 'GITHUB_TEST_REPO')
|
|
25
25
|
const suiteOrSkip = (variants.length > 0 && hasWriteEnv) ? describe : describe.skip
|
|
26
26
|
|
|
27
|
+
async function withRetry<T>(
|
|
28
|
+
fn: () => Promise<T>,
|
|
29
|
+
{ maxAttempts = 3, delayMs = 2000, retryIf = (_e: unknown): boolean => true } = {},
|
|
30
|
+
): Promise<T> {
|
|
31
|
+
let lastError: unknown
|
|
32
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
33
|
+
try {
|
|
34
|
+
return await fn()
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
lastError = error
|
|
38
|
+
if (attempt < maxAttempts && retryIf(error)) {
|
|
39
|
+
await new Promise(resolve => setTimeout(resolve, delayMs * attempt))
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
break
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
throw lastError
|
|
46
|
+
}
|
|
47
|
+
|
|
27
48
|
suiteOrSkip('github write handlers (live)', () => {
|
|
28
49
|
for (const variant of variants) {
|
|
29
50
|
describe(`variant: ${variant.key}`, () => {
|
|
@@ -40,7 +61,7 @@ suiteOrSkip('github write handlers (live)', () => {
|
|
|
40
61
|
toolbox = createToolbox('github', proxy, node, variant.key)
|
|
41
62
|
}, 30000)
|
|
42
63
|
|
|
43
|
-
it('create_issue -> update_issue -> comment_on_issue -> close_issue roundtrip', async () => {
|
|
64
|
+
it('create_issue -> update_issue -> comment_on_issue -> list_issue_comments -> close_issue roundtrip', async () => {
|
|
44
65
|
if (!ctx.owner || !ctx.repo)
|
|
45
66
|
return expect(true).toBe(true)
|
|
46
67
|
|
|
@@ -59,18 +80,60 @@ suiteOrSkip('github write handlers (live)', () => {
|
|
|
59
80
|
const comment = await comment_on_issue({ owner: ctx.owner, repo: ctx.repo, issue_number, body: 'A comment from test.' })
|
|
60
81
|
expect(comment?.id).toBeTruthy()
|
|
61
82
|
|
|
83
|
+
const list_issue_comments = toolbox.read('list_issue_comments')
|
|
84
|
+
const comments = await list_issue_comments({ owner: ctx.owner, repo: ctx.repo, issue_number })
|
|
85
|
+
expect(Array.isArray(comments)).toBe(true)
|
|
86
|
+
expect(comments.length).toBeGreaterThan(0)
|
|
87
|
+
|
|
62
88
|
const close_issue = toolbox.write('close_issue')
|
|
63
89
|
const closed = await close_issue({ owner: ctx.owner, repo: ctx.repo, issue_number })
|
|
64
90
|
expect(closed?.state).toBe('closed')
|
|
65
91
|
}, 90000)
|
|
66
92
|
|
|
93
|
+
it('fork_repo forks a public repo (best effort)', async () => {
|
|
94
|
+
if (!ctx.owner || !ctx.repo)
|
|
95
|
+
return expect(true).toBe(true)
|
|
96
|
+
const fork_repo = toolbox.write('fork_repo')
|
|
97
|
+
try {
|
|
98
|
+
const result = await fork_repo({ owner: ctx.owner, repo: ctx.repo })
|
|
99
|
+
// Fork returns the forked repo details
|
|
100
|
+
expect(result).toBeTruthy()
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// May fail if repo is private or fork already exists -- that's ok
|
|
104
|
+
expect(true).toBe(true)
|
|
105
|
+
}
|
|
106
|
+
}, 30000)
|
|
107
|
+
|
|
108
|
+
it('create_release creates a draft release (classic_pat only)', async () => {
|
|
109
|
+
if (!toolbox.hasTool('write', 'create_repo'))
|
|
110
|
+
return expect(true).toBe(true)
|
|
111
|
+
if (!ctx.owner || !ctx.repo)
|
|
112
|
+
return expect(true).toBe(true)
|
|
113
|
+
const create_release = toolbox.write('create_release')
|
|
114
|
+
const tagName = `v0.0.0-test-${Date.now()}`
|
|
115
|
+
try {
|
|
116
|
+
const result = await create_release({
|
|
117
|
+
owner: ctx.owner,
|
|
118
|
+
repo: ctx.repo,
|
|
119
|
+
tag_name: tagName,
|
|
120
|
+
name: `Test Release ${tagName}`,
|
|
121
|
+
body: 'Draft release created by integration tests.',
|
|
122
|
+
draft: true,
|
|
123
|
+
})
|
|
124
|
+
expect(result?.tag_name).toBe(tagName)
|
|
125
|
+
expect(result?.draft).toBe(true)
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// May fail if insufficient permissions -- that's ok
|
|
129
|
+
expect(true).toBe(true)
|
|
130
|
+
}
|
|
131
|
+
}, 30000)
|
|
132
|
+
|
|
67
133
|
it('create_repo -> delete_repo lifecycle (classic_pat only)', async () => {
|
|
68
134
|
if (!toolbox.hasTool('write', 'create_repo')) {
|
|
69
|
-
// This tool is not available for fine_grained_pat -- expected.
|
|
70
135
|
return expect(true).toBe(true)
|
|
71
136
|
}
|
|
72
|
-
if (!ctx.owner)
|
|
73
|
-
return expect(true).toBe(true)
|
|
74
137
|
|
|
75
138
|
const repoName = `cmdtest-repo-${Date.now()}`
|
|
76
139
|
|
|
@@ -82,10 +145,15 @@ suiteOrSkip('github write handlers (live)', () => {
|
|
|
82
145
|
auto_init: true,
|
|
83
146
|
})
|
|
84
147
|
expect(created?.name).toBe(repoName)
|
|
85
|
-
|
|
148
|
+
|
|
149
|
+
const createdOwner = created?.owner?.login
|
|
150
|
+
expect(createdOwner).toBeTruthy()
|
|
151
|
+
expect(created?.full_name).toBe(`${createdOwner}/${repoName}`)
|
|
152
|
+
|
|
153
|
+
await new Promise(resolve => setTimeout(resolve, 3000))
|
|
86
154
|
|
|
87
155
|
const delete_repo = toolbox.write('delete_repo')
|
|
88
|
-
const deleted = await delete_repo({ owner:
|
|
156
|
+
const deleted = await delete_repo({ owner: createdOwner, repo: repoName })
|
|
89
157
|
expect(deleted?.success).toBe(true)
|
|
90
158
|
expect(deleted?.status).toBe(204)
|
|
91
159
|
}, 90000)
|
|
@@ -113,17 +181,34 @@ suiteOrSkip('github write handlers (live)', () => {
|
|
|
113
181
|
expect(file?.commit?.message).toBe(`Add single test file ${timestamp}`)
|
|
114
182
|
expect(file?.content?.path).toBe(`test-single-${timestamp}.txt`)
|
|
115
183
|
|
|
116
|
-
|
|
184
|
+
// get_file_contents and delete_file roundtrip
|
|
185
|
+
const get_file_contents = toolbox.read('get_file_contents')
|
|
186
|
+
const contents = await get_file_contents({
|
|
117
187
|
owner: ctx.owner,
|
|
118
188
|
repo: ctx.repo,
|
|
119
189
|
path: `test-single-${timestamp}.txt`,
|
|
120
|
-
|
|
121
|
-
|
|
190
|
+
ref: branchName,
|
|
191
|
+
})
|
|
192
|
+
expect(contents?.encoding).toBe('utf-8')
|
|
193
|
+
expect(contents?.content).toContain('Hello')
|
|
194
|
+
const fileSha = contents?.sha
|
|
195
|
+
|
|
196
|
+
const delete_file = toolbox.write('delete_file')
|
|
197
|
+
const deleted = await delete_file({
|
|
198
|
+
owner: ctx.owner,
|
|
199
|
+
repo: ctx.repo,
|
|
200
|
+
path: `test-single-${timestamp}.txt`,
|
|
201
|
+
message: `Delete test file ${timestamp}`,
|
|
202
|
+
sha: fileSha,
|
|
122
203
|
branch: branchName,
|
|
123
|
-
sha: file.content.sha,
|
|
124
204
|
})
|
|
125
|
-
expect(
|
|
126
|
-
|
|
205
|
+
expect(deleted?.commit?.message).toBe(`Delete test file ${timestamp}`)
|
|
206
|
+
|
|
207
|
+
// delete_branch cleanup
|
|
208
|
+
const delete_branch = toolbox.write('delete_branch')
|
|
209
|
+
const deletedBranch = await delete_branch({ owner: ctx.owner, repo: ctx.repo, branch: branchName })
|
|
210
|
+
expect(deletedBranch?.success).toBe(true)
|
|
211
|
+
}, 120000)
|
|
127
212
|
|
|
128
213
|
it('create_commit: multiple files in one commit', async () => {
|
|
129
214
|
if (!ctx.owner || !ctx.repo)
|
|
@@ -152,22 +237,36 @@ suiteOrSkip('github write handlers (live)', () => {
|
|
|
152
237
|
expect(commit?.commit?.message).toBe(`Add multiple files ${timestamp}`)
|
|
153
238
|
expect(commit?.files?.length).toBe(3)
|
|
154
239
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
240
|
+
// Retry second commit: GitHub's backend sometimes needs a moment to settle
|
|
241
|
+
// after the first commit before accepting a follow-up on the same branch.
|
|
242
|
+
const commit2 = await withRetry(
|
|
243
|
+
() => create_commit({
|
|
244
|
+
owner: ctx.owner,
|
|
245
|
+
repo: ctx.repo,
|
|
246
|
+
branch: branchName,
|
|
247
|
+
message: `Update and delete files ${timestamp}`,
|
|
248
|
+
files: [
|
|
249
|
+
{ path: `multi-test/file1-${timestamp}.txt`, content: 'Updated content of file 1' },
|
|
250
|
+
{ path: `multi-test/file2-${timestamp}.txt` },
|
|
251
|
+
{ path: `multi-test/file4-${timestamp}.txt`, content: 'New file 4' },
|
|
252
|
+
],
|
|
253
|
+
}),
|
|
254
|
+
{
|
|
255
|
+
maxAttempts: 3,
|
|
256
|
+
delayMs: 2000,
|
|
257
|
+
retryIf: (e: unknown) => String((e as { message?: string })?.message || '').includes('GitRPC::BadObjectState'),
|
|
258
|
+
},
|
|
259
|
+
)
|
|
166
260
|
expect(commit2?.commit?.sha).toBeTruthy()
|
|
167
261
|
expect(commit2?.commit?.message).toBe(`Update and delete files ${timestamp}`)
|
|
262
|
+
|
|
263
|
+
// get_commit verifies the commit details
|
|
264
|
+
const get_commit = toolbox.read('get_commit')
|
|
265
|
+
const commitDetails = await get_commit({ owner: ctx.owner, repo: ctx.repo, sha: commit2.commit.sha })
|
|
266
|
+
expect(commitDetails?.sha).toBe(commit2.commit.sha)
|
|
168
267
|
}, 120000)
|
|
169
268
|
|
|
170
|
-
it('full PR workflow: create_branch -> create_commit -> create_pull_request -> merge_pull_request', async () => {
|
|
269
|
+
it('full PR workflow: create_branch -> create_commit -> create_pull_request -> update_pull_request -> create_pull_request_review -> merge_pull_request -> delete_branch', async () => {
|
|
171
270
|
if (!ctx.owner || !ctx.repo)
|
|
172
271
|
return expect(true).toBe(true)
|
|
173
272
|
|
|
@@ -207,6 +306,27 @@ suiteOrSkip('github write handlers (live)', () => {
|
|
|
207
306
|
expect(pr?.number).toBeTruthy()
|
|
208
307
|
const prNumber = pr.number
|
|
209
308
|
|
|
309
|
+
// update_pull_request
|
|
310
|
+
const update_pull_request = toolbox.write('update_pull_request')
|
|
311
|
+
const updated = await update_pull_request({
|
|
312
|
+
owner: ctx.owner,
|
|
313
|
+
repo: ctx.repo,
|
|
314
|
+
pull_number: prNumber,
|
|
315
|
+
body: 'Updated description by integration test.',
|
|
316
|
+
})
|
|
317
|
+
expect(updated?.number).toBe(prNumber)
|
|
318
|
+
|
|
319
|
+
// get_pull_request verifies state
|
|
320
|
+
const get_pull_request = toolbox.read('get_pull_request')
|
|
321
|
+
const prDetails = await get_pull_request({ owner: ctx.owner, repo: ctx.repo, pull_number: prNumber })
|
|
322
|
+
expect(prDetails?.number).toBe(prNumber)
|
|
323
|
+
|
|
324
|
+
// list_pull_request_files
|
|
325
|
+
const list_pull_request_files = toolbox.read('list_pull_request_files')
|
|
326
|
+
const files = await list_pull_request_files({ owner: ctx.owner, repo: ctx.repo, pull_number: prNumber })
|
|
327
|
+
expect(Array.isArray(files)).toBe(true)
|
|
328
|
+
|
|
329
|
+
// add_labels_to_issue (labels on PR)
|
|
210
330
|
const add_labels_to_issue = toolbox.write('add_labels_to_issue')
|
|
211
331
|
try {
|
|
212
332
|
await add_labels_to_issue({ owner: ctx.owner, repo: ctx.repo, issue_number: prNumber, labels: ['test'] })
|
|
@@ -215,9 +335,43 @@ suiteOrSkip('github write handlers (live)', () => {
|
|
|
215
335
|
// Label might not exist -- that's ok for this test
|
|
216
336
|
}
|
|
217
337
|
|
|
338
|
+
// request_pull_request_reviewers (may fail if requesting from self)
|
|
339
|
+
const request_pull_request_reviewers = toolbox.write('request_pull_request_reviewers')
|
|
340
|
+
try {
|
|
341
|
+
await request_pull_request_reviewers({
|
|
342
|
+
owner: ctx.owner,
|
|
343
|
+
repo: ctx.repo,
|
|
344
|
+
pull_number: prNumber,
|
|
345
|
+
reviewers: [],
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
// May fail if requesting from self or insufficient permissions -- that's ok
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// create_pull_request_review (comment only -- can't APPROVE own PRs typically)
|
|
353
|
+
const create_pull_request_review = toolbox.write('create_pull_request_review')
|
|
354
|
+
try {
|
|
355
|
+
await create_pull_request_review({
|
|
356
|
+
owner: ctx.owner,
|
|
357
|
+
repo: ctx.repo,
|
|
358
|
+
pull_number: prNumber,
|
|
359
|
+
event: 'COMMENT',
|
|
360
|
+
body: 'LGTM from integration test',
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// May fail if author is same as reviewer in some repo configs -- that's ok
|
|
365
|
+
}
|
|
366
|
+
|
|
218
367
|
const merge_pull_request = toolbox.write('merge_pull_request')
|
|
219
368
|
const merged = await merge_pull_request({ owner: ctx.owner, repo: ctx.repo, pull_number: prNumber, merge_method: 'squash' })
|
|
220
369
|
expect(merged?.merged).toBe(true)
|
|
370
|
+
|
|
371
|
+
// delete_branch after merge
|
|
372
|
+
const delete_branch = toolbox.write('delete_branch')
|
|
373
|
+
const deletedBranch = await delete_branch({ owner: ctx.owner, repo: ctx.repo, branch: branchName })
|
|
374
|
+
expect(deletedBranch?.success).toBe(true)
|
|
221
375
|
}, 150000)
|
|
222
376
|
})
|
|
223
377
|
}
|
|
@@ -11,24 +11,17 @@ async (input) => {
|
|
|
11
11
|
const commitData = await commitRes.json()
|
|
12
12
|
const currentTreeSha = commitData.tree.sha
|
|
13
13
|
|
|
14
|
-
// 3.
|
|
14
|
+
// 3. Build tree entries.
|
|
15
|
+
// Always create blobs explicitly rather than using inline tree content, because mixing inline
|
|
16
|
+
// content with sha:null deletion entries in the same tree request causes GitRPC::BadObjectState.
|
|
15
17
|
const tree = []
|
|
16
18
|
for (const file of files) {
|
|
17
19
|
if (file.content !== undefined && file.content !== null) {
|
|
18
|
-
// Create a blob for this file
|
|
19
|
-
const contentBase64 = typeof Buffer !== 'undefined'
|
|
20
|
-
? Buffer.from(file.content).toString('base64')
|
|
21
|
-
: btoa(unescape(encodeURIComponent(file.content)))
|
|
22
|
-
|
|
23
20
|
const blobRes = await integration.fetch(`/repos/${owner}/${repo}/git/blobs`, {
|
|
24
21
|
method: 'POST',
|
|
25
|
-
body: {
|
|
26
|
-
content: contentBase64,
|
|
27
|
-
encoding: 'base64',
|
|
28
|
-
},
|
|
22
|
+
body: { content: file.content, encoding: 'utf-8' },
|
|
29
23
|
})
|
|
30
24
|
const blobData = await blobRes.json()
|
|
31
|
-
|
|
32
25
|
tree.push({
|
|
33
26
|
path: file.path,
|
|
34
27
|
mode: file.mode || '100644',
|
|
@@ -36,7 +29,7 @@ async (input) => {
|
|
|
36
29
|
sha: blobData.sha,
|
|
37
30
|
})
|
|
38
31
|
} else {
|
|
39
|
-
//
|
|
32
|
+
// sha: null removes the file from the tree
|
|
40
33
|
tree.push({
|
|
41
34
|
path: file.path,
|
|
42
35
|
mode: '100644',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = { event: input.event }
|
|
3
|
+
if (input.body !== undefined) body.body = input.body
|
|
4
|
+
if (input.commit_id !== undefined) body.commit_id = input.commit_id
|
|
5
|
+
const res = await integration.fetch(
|
|
6
|
+
`/repos/${input.owner}/${input.repo}/pulls/${input.pull_number}/reviews`,
|
|
7
|
+
{ method: 'POST', body }
|
|
8
|
+
)
|
|
9
|
+
return await res.json()
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = { tag_name: input.tag_name }
|
|
3
|
+
if (input.name !== undefined) body.name = input.name
|
|
4
|
+
if (input.body !== undefined) body.body = input.body
|
|
5
|
+
if (input.draft !== undefined) body.draft = input.draft
|
|
6
|
+
if (input.prerelease !== undefined) body.prerelease = input.prerelease
|
|
7
|
+
if (input.target_commitish !== undefined) body.target_commitish = input.target_commitish
|
|
8
|
+
if (input.generate_release_notes !== undefined) body.generate_release_notes = input.generate_release_notes
|
|
9
|
+
const res = await integration.fetch(
|
|
10
|
+
`/repos/${input.owner}/${input.repo}/releases`,
|
|
11
|
+
{ method: 'POST', body }
|
|
12
|
+
)
|
|
13
|
+
return await res.json()
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = { message: input.message, sha: input.sha }
|
|
3
|
+
if (input.branch) body.branch = input.branch
|
|
4
|
+
const res = await integration.fetch(
|
|
5
|
+
`/repos/${input.owner}/${input.repo}/contents/${input.path}`,
|
|
6
|
+
{ method: 'DELETE', body }
|
|
7
|
+
)
|
|
8
|
+
return await res.json()
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = {}
|
|
3
|
+
if (input.organization) body.organization = input.organization
|
|
4
|
+
if (input.name) body.name = input.name
|
|
5
|
+
const res = await integration.fetch(
|
|
6
|
+
`/repos/${input.owner}/${input.repo}/forks`,
|
|
7
|
+
{ method: 'POST', body }
|
|
8
|
+
)
|
|
9
|
+
return await res.json()
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.page) params.set('page', String(input.page))
|
|
4
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
5
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
6
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/commits/${input.sha}${query}`)
|
|
7
|
+
return await res.json()
|
|
8
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.ref) params.set('ref', input.ref)
|
|
4
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
5
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/contents/${input.path}${query}`)
|
|
6
|
+
const data = await res.json()
|
|
7
|
+
if (data && data.content && data.encoding === 'base64') {
|
|
8
|
+
try {
|
|
9
|
+
const b64 = data.content.replace(/\n/g, '')
|
|
10
|
+
// Decode base64 → binary string → percent-encode each byte → UTF-8 decode
|
|
11
|
+
data.content = decodeURIComponent(
|
|
12
|
+
atob(b64).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')
|
|
13
|
+
)
|
|
14
|
+
data.encoding = 'utf-8'
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
// Binary file — leave content as base64
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return data
|
|
21
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
// GitHub returns a redirect to the actual log blob URL; fetch follows it automatically
|
|
3
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/actions/jobs/${input.job_id}/logs`)
|
|
4
|
+
const logs = await res.text()
|
|
5
|
+
return { logs }
|
|
6
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const ref = input.ref || 'HEAD'
|
|
3
|
+
const params = new URLSearchParams()
|
|
4
|
+
if (input.recursive !== false) params.set('recursive', '1')
|
|
5
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
6
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/git/trees/${ref}${query}`)
|
|
7
|
+
const data = await res.json()
|
|
8
|
+
if (input.path_filter && Array.isArray(data.tree)) {
|
|
9
|
+
data.tree = data.tree.filter((item) => item.path && item.path.startsWith(input.path_filter))
|
|
10
|
+
}
|
|
11
|
+
return data
|
|
12
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
async (input) => {
|
|
2
|
-
const
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.protected !== undefined) params.set('protected', String(input.protected))
|
|
4
|
+
if (input.page) params.set('page', String(input.page))
|
|
5
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
6
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
7
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/branches${query}`)
|
|
3
8
|
return await res.json()
|
|
4
9
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
async (input) => {
|
|
2
2
|
const params = new URLSearchParams()
|
|
3
|
-
if (input.sha)
|
|
4
|
-
|
|
5
|
-
if (
|
|
6
|
-
|
|
7
|
-
if (input.
|
|
8
|
-
params.set('author', input.author)
|
|
3
|
+
if (input.sha) params.set('sha', input.sha)
|
|
4
|
+
if (typeof input.path === 'string' && input.path.length > 0) params.set('path', input.path)
|
|
5
|
+
if (input.author) params.set('author', input.author)
|
|
6
|
+
if (input.page) params.set('page', String(input.page))
|
|
7
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
9
8
|
const query = params.toString() ? `?${params.toString()}` : ''
|
|
10
9
|
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/commits${query}`)
|
|
11
10
|
return await res.json()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.page) params.set('page', String(input.page))
|
|
4
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
5
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
6
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/issues/${input.issue_number}/comments${query}`)
|
|
7
|
+
return await res.json()
|
|
8
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
async (input) => {
|
|
2
2
|
const params = new URLSearchParams()
|
|
3
|
-
if (input.state)
|
|
4
|
-
|
|
5
|
-
if (input.
|
|
6
|
-
|
|
7
|
-
if (input.
|
|
8
|
-
params.set('assignee', input.assignee)
|
|
3
|
+
if (input.state) params.set('state', input.state)
|
|
4
|
+
if (input.labels) params.set('labels', input.labels)
|
|
5
|
+
if (input.assignee) params.set('assignee', input.assignee)
|
|
6
|
+
if (input.page) params.set('page', String(input.page))
|
|
7
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
9
8
|
const query = params.toString() ? `?${params.toString()}` : ''
|
|
10
9
|
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/issues${query}`)
|
|
11
10
|
return await res.json()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.page) params.set('page', String(input.page))
|
|
4
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
5
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
6
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/labels${query}`)
|
|
7
|
+
return await res.json()
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.page) params.set('page', String(input.page))
|
|
4
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
5
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
6
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/pulls/${input.pull_number}/comments${query}`)
|
|
7
|
+
return await res.json()
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.page) params.set('page', String(input.page))
|
|
4
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
5
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
6
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/pulls/${input.pull_number}/files${query}`)
|
|
7
|
+
return await res.json()
|
|
8
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
async (input) => {
|
|
2
2
|
const params = new URLSearchParams()
|
|
3
|
-
if (input.state)
|
|
4
|
-
|
|
3
|
+
if (input.state) params.set('state', input.state)
|
|
4
|
+
if (input.head) params.set('head', input.head)
|
|
5
|
+
if (input.base) params.set('base', input.base)
|
|
6
|
+
if (input.sort) params.set('sort', input.sort)
|
|
7
|
+
if (input.direction) params.set('direction', input.direction)
|
|
8
|
+
if (input.page) params.set('page', String(input.page))
|
|
9
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
5
10
|
const query = params.toString() ? `?${params.toString()}` : ''
|
|
6
11
|
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/pulls${query}`)
|
|
7
12
|
return await res.json()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.page) params.set('page', String(input.page))
|
|
4
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
5
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
6
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/releases${query}`)
|
|
7
|
+
return await res.json()
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.page) params.set('page', String(input.page))
|
|
4
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
5
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
6
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/tags${query}`)
|
|
7
|
+
return await res.json()
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.branch) params.set('branch', input.branch)
|
|
4
|
+
if (input.status) params.set('status', input.status)
|
|
5
|
+
if (input.event) params.set('event', input.event)
|
|
6
|
+
if (input.page) params.set('page', String(input.page))
|
|
7
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
8
|
+
const query = params.toString() ? `?${params.toString()}` : ''
|
|
9
|
+
const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/actions/runs${query}`)
|
|
10
|
+
return await res.json()
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = {}
|
|
3
|
+
if (input.reviewers) body.reviewers = input.reviewers
|
|
4
|
+
if (input.team_reviewers) body.team_reviewers = input.team_reviewers
|
|
5
|
+
const res = await integration.fetch(
|
|
6
|
+
`/repos/${input.owner}/${input.repo}/pulls/${input.pull_number}/requested_reviewers`,
|
|
7
|
+
{ method: 'POST', body }
|
|
8
|
+
)
|
|
9
|
+
return await res.json()
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
params.set('q', input.query)
|
|
4
|
+
if (input.page) params.set('page', String(input.page))
|
|
5
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
6
|
+
const res = await integration.fetch(`/search/code?${params.toString()}`)
|
|
7
|
+
return await res.json()
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
params.set('q', input.query)
|
|
4
|
+
if (input.page) params.set('page', String(input.page))
|
|
5
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
6
|
+
const res = await integration.fetch(`/search/issues?${params.toString()}`)
|
|
7
|
+
return await res.json()
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
params.set('q', input.query)
|
|
4
|
+
if (input.page) params.set('page', String(input.page))
|
|
5
|
+
if (input.per_page) params.set('per_page', String(input.per_page))
|
|
6
|
+
const res = await integration.fetch(`/search/issues?${params.toString()}`)
|
|
7
|
+
return await res.json()
|
|
8
|
+
}
|