11import type { Command } from "commander" ;
22import { createContext , getRootOpts } from "../common/context.js" ;
3+ import { invalidParameterError } from "../common/errors.js" ;
34import { handleCommand , outputSuccess , parseLimit } from "../common/output.js" ;
45import { type DomainMeta , formatDomainUsage } from "../common/usage.js" ;
5- import type { DocumentUpdateInput } from "../gql/graphql.js" ;
6+ import type { DocumentFilter , DocumentUpdateInput } from "../gql/graphql.js" ;
67import { resolveIssueId } from "../resolvers/issue-resolver.js" ;
78import { resolveProjectId } from "../resolvers/project-resolver.js" ;
89import { resolveTeamId } from "../resolvers/team-resolver.js" ;
9- import {
10- createAttachment ,
11- listAttachments ,
12- } from "../services/attachment-service.js" ;
10+ import { listAttachments } from "../services/attachment-service.js" ;
1311import {
1412 createDocument ,
1513 deleteDocument ,
1614 getDocument ,
1715 listDocuments ,
18- listDocumentsBySlugIds ,
1916 updateDocument ,
2017} from "../services/document-service.js" ;
2118
@@ -27,6 +24,7 @@ interface DocumentCreateOptions {
2724 icon ?: string ;
2825 color ?: string ;
2926 issue ?: string ;
27+ attachTo ?: string ;
3028}
3129
3230interface DocumentUpdateOptions {
@@ -66,11 +64,30 @@ export function extractDocumentIdFromUrl(url: string): string | null {
6664
6765 return docSlug . substring ( lastHyphenIndex + 1 ) || null ;
6866 } catch {
69- // URL constructor throws on malformed input — treat as unresolvable
67+ // URL constructor throws on malformed input — treat as unresolvable.
7068 return null ;
7169 }
7270}
7371
72+ function buildIssueDocumentFilter (
73+ issueId : string ,
74+ legacyDocumentSlugIds : string [ ] ,
75+ ) : DocumentFilter {
76+ const issueFilter : DocumentFilter = { issue : { id : { eq : issueId } } } ;
77+ if ( legacyDocumentSlugIds . length === 0 ) {
78+ return issueFilter ;
79+ }
80+
81+ return {
82+ or : [
83+ issueFilter ,
84+ ...legacyDocumentSlugIds . map ( ( slugId ) => ( {
85+ slugId : { eq : slugId } ,
86+ } ) ) ,
87+ ] ,
88+ } ;
89+ }
90+
7491export const DOCUMENTS_META : DomainMeta = {
7592 name : "documents" ,
7693 summary : "long-form markdown docs attached to projects or issues" ,
@@ -115,48 +132,35 @@ export function setupDocumentsCommands(program: Command): void {
115132
116133 const limit = parseLimit ( options . limit || "50" ) ;
117134
135+ let projectId : string | undefined ;
136+ if ( options . project ) {
137+ projectId = await resolveProjectId ( ctx . sdk , options . project ) ;
138+ }
139+
140+ let issueId : string | undefined ;
118141 if ( options . issue ) {
119- const issueId = await resolveIssueId ( ctx . sdk , options . issue ) ;
120- const attachments = await listAttachments ( ctx . gql , issueId ) ;
142+ issueId = await resolveIssueId ( ctx . sdk , options . issue ) ;
143+ }
121144
122- const documentSlugIds = [
145+ let filter : DocumentFilter | undefined ;
146+ if ( projectId ) {
147+ filter = { project : { id : { eq : projectId } } } ;
148+ } else if ( issueId ) {
149+ const attachments = await listAttachments ( ctx . gql , issueId ) ;
150+ const legacyDocumentSlugIds = [
123151 ...new Set (
124152 attachments
125153 . map ( ( att ) => extractDocumentIdFromUrl ( att . url ) )
126154 . filter ( ( id ) : id is string => id !== null ) ,
127155 ) ,
128156 ] ;
129-
130- if ( documentSlugIds . length === 0 ) {
131- outputSuccess ( {
132- nodes : [ ] ,
133- pageInfo : { hasNextPage : false , endCursor : null } ,
134- } ) ;
135- return ;
136- }
137-
138- const documents = await listDocumentsBySlugIds (
139- ctx . gql ,
140- documentSlugIds ,
141- ) ;
142- outputSuccess ( {
143- nodes : documents ,
144- pageInfo : { hasNextPage : false , endCursor : null } ,
145- } ) ;
146- return ;
147- }
148-
149- let projectId : string | undefined ;
150- if ( options . project ) {
151- projectId = await resolveProjectId ( ctx . sdk , options . project ) ;
157+ filter = buildIssueDocumentFilter ( issueId , legacyDocumentSlugIds ) ;
152158 }
153159
154160 const documents = await listDocuments ( ctx . gql , {
155161 limit,
156162 after : options . after ,
157- filter : projectId
158- ? { project : { id : { eq : projectId } } }
159- : undefined ,
163+ filter,
160164 } ) ;
161165
162166 outputSuccess ( documents ) ;
@@ -187,9 +191,18 @@ export function setupDocumentsCommands(program: Command): void {
187191 . option ( "--icon <icon>" , "document icon" )
188192 . option ( "--color <color>" , "icon color" )
189193 . option ( "--issue <issue>" , "also attach document to issue (e.g., ABC-123)" )
194+ . option ( "--attach-to <issue>" , "alias for --issue" )
190195 . action (
191196 handleCommand ( async ( ...args : unknown [ ] ) => {
192197 const [ options , command ] = args as [ DocumentCreateOptions , Command ] ;
198+ if ( options . issue && options . attachTo ) {
199+ throw invalidParameterError (
200+ "--attach-to" ,
201+ "cannot be combined with --issue" ,
202+ ) ;
203+ }
204+
205+ const issueIdentifier = options . issue ?? options . attachTo ;
193206 const rootOpts = getRootOpts ( command ) ;
194207 const ctx = createContext ( rootOpts ) ;
195208
@@ -199,36 +212,20 @@ export function setupDocumentsCommands(program: Command): void {
199212 const teamId = options . team
200213 ? await resolveTeamId ( ctx . sdk , options . team )
201214 : undefined ;
215+ const issueId = issueIdentifier
216+ ? await resolveIssueId ( ctx . sdk , issueIdentifier )
217+ : undefined ;
202218
203219 const document = await createDocument ( ctx . gql , {
204220 title : options . title ,
205221 content : options . content ,
206222 projectId,
207223 teamId,
224+ issueId,
208225 icon : options . icon ,
209226 color : options . color ,
210227 } ) ;
211228
212- if ( options . issue ) {
213- const issueId = await resolveIssueId ( ctx . sdk , options . issue ) ;
214-
215- try {
216- await createAttachment ( ctx . gql , {
217- issueId,
218- url : document . url ,
219- title : document . title ,
220- } ) ;
221- } catch ( attachError ) {
222- const errorMessage =
223- attachError instanceof Error
224- ? attachError . message
225- : String ( attachError ) ;
226- throw new Error (
227- `Document created (${ document . id } ) but failed to attach to issue "${ options . issue } ": ${ errorMessage } .` ,
228- ) ;
229- }
230- }
231-
232229 outputSuccess ( document ) ;
233230 } ) ,
234231 ) ;
0 commit comments