feat(ado): add work item attachment upload - #205
Conversation
Adds `pncli ado work add-attachment --id <n> --file <path> [--comment <text>]` using the two-step ADO API: upload file to the attachment store, then link it to the work item via a JSON-patch relation. Also adds `adoUpload` to HttpClient for binary uploads through the ADO authenticating fetcher. Closes #204 Co-authored-by: Sunny Kolattukudy <kolatts@users.noreply.github.com>
|
Claude finished @claude[bot]'s task in 3m 53s —— View job Review Complete
Overall verdict: Approve — the implementation is correct and clean. Two minor inline suggestions. What was verified:
Inline comments posted:
Neither is blocking. |
| filePath: string, | ||
| comment?: string | ||
| ): Promise<AdoWorkItemAttachment> { | ||
| const fileContent = readFileSync(filePath); |
There was a problem hiding this comment.
Suggestion — no file-existence check before the synchronous read
readFileSync throws a raw Node.js ENOENT error (not a PncliError) when the file doesn't exist. The fail() handler will still catch it, but the output will be a raw system error message rather than the usual PncliError format.
A lightweight pre-check improves the UX without much overhead:
| const fileContent = readFileSync(filePath); | |
| if (!require('fs').existsSync(filePath)) { | |
| throw new PncliError(`File not found: ${filePath}`, 1); | |
| } | |
| const fileContent = readFileSync(filePath); |
Or even simpler — import existsSync alongside readFileSync at the top. Not a blocker since the error still surfaces, just noisier than a PncliError.
|
|
||
| // Second request: PATCH work item to link the attachment | ||
| expect(capturedRequests[1].url).toContain('_apis/wit/workitems/42'); | ||
| expect(capturedRequests[1].method).toBe('PATCH'); |
There was a problem hiding this comment.
Nit — temp file isn't cleaned up if an assertion throws
fs.unlinkSync(tmpFile) is called after the assertions, so a failing assertion will leak the temp file. Wrapping in try/finally is the safe pattern here:
| expect(capturedRequests[1].method).toBe('PATCH'); | |
| try { | |
| expect(result.id).toBe('att-guid-123'); | |
| expect(result.url).toBe('https://ado.example.com/myorg/_apis/wit/attachments/att-guid-123'); | |
| // First request: upload to attachments endpoint | |
| expect(capturedRequests[0].url).toContain('_apis/wit/attachments'); | |
| expect(capturedRequests[0].url).toContain('fileName=pncli-test-report.txt'); | |
| expect(capturedRequests[0].method).toBe('POST'); | |
| expect(capturedRequests[0].contentType).toBe('text/plain'); | |
| // Second request: PATCH work item to link the attachment | |
| expect(capturedRequests[1].url).toContain('_apis/wit/workitems/42'); | |
| expect(capturedRequests[1].method).toBe('PATCH'); | |
| const patch = capturedRequests[1].body as Array<{ op: string; path: string; value: { rel: string; url: string } }>; | |
| expect(patch[0].op).toBe('add'); | |
| expect(patch[0].path).toBe('/relations/-'); | |
| expect(patch[0].value.rel).toBe('AttachedFile'); | |
| expect(patch[0].value.url).toBe(attachmentResponse.url); | |
| } finally { | |
| fs.unlinkSync(tmpFile); | |
| } |
Same applies to the second test (pncli-test-notes.txt). Low-risk since these run in tmpdir, but worth doing right.
| } | ||
| } | ||
|
|
||
| function guessMimeType(filePath: string): string { |
There was a problem hiding this comment.
@claude[agent] can we abstract this outside the ado client
There was a problem hiding this comment.
Abstracted guessMimeType to a shared utility module at src/lib/mime.ts. The function is now used by both ADO and Jira clients, eliminating code duplication. Added 9 comprehensive unit tests covering various file types and edge cases.
Commit: 7f596bb
Moved MIME type detection logic from ADO and Jira clients to a shared utility module (src/lib/mime.ts) to eliminate code duplication and improve maintainability. Added comprehensive test coverage (9 tests) for the shared utility. Agent-Logs-Url: https://github.com/kolatts/pncli/sessions/1727a88d-99be-44b4-a1cf-c47d12625e34
|
@claude[agent] add this to the scale reference. |
Summary
pncli ado work add-attachment --id <n> --file <path> [--comment <text>]command_apis/wit/attachments, then PATCH the work item to add theAttachedFilerelationadoUpload<T>method toHttpClientfor binary uploads through the ADO authenticating fetcherCloses #204
Generated with Claude Code