import { linearGraphql } from './client.ts'
import { ISSUE_FIELDS, mapIssue } from './models.ts'
import type {
DryRunResult,
JsonRecord,
LinearAuthInput,
LinearIssueSummary,
} from './types.ts'
import {
compactRecord,
optionalBoolean,
optionalNumber,
optionalPriority,
optionalString,
optionalStringArray,
requireRecord,
requireString,
} from './types.ts'
import { requireAuthMode } from './auth.ts'
export type UpdateIssueInput = LinearAuthInput & {
/** Issue UUID or identifier (`ENG-123`). */
id: string
title?: string
description?: string
assigneeId?: string
stateId?: string
projectId?: string
teamId?: string
priority?: number
labelIds?: Array<string>
dueDate?: string
parentId?: string
estimate?: number
confirm?: boolean
dryRun?: boolean
}
function issueUpdatePayload(input: UpdateIssueInput): JsonRecord {
const payload = compactRecord({
title: optionalString(input.title, 'title'),
description: optionalString(input.description, 'description'),
assigneeId: optionalString(input.assigneeId, 'assigneeId'),
stateId: optionalString(input.stateId, 'stateId'),
projectId: optionalString(input.projectId, 'projectId'),
teamId: optionalString(input.teamId, 'teamId'),
priority: optionalPriority(input.priority),
labelIds: optionalStringArray(input.labelIds, 'labelIds'),
dueDate: optionalString(input.dueDate, 'dueDate'),
parentId: optionalString(input.parentId, 'parentId'),
estimate: optionalNumber(input.estimate, 'estimate'),
})
if (Object.keys(payload).length === 0) {
throw new Error('update-issue requires at least one field to change.')
}
return payload
}
/**
* Update a Linear issue. Requires `confirm: true`, or use `dryRun: true`.
* Needs `write`.
* @example
* import updateIssue from 'kody:@kody/linear/update-issue'
* const preview = await updateIssue({
* id: 'ENG-123',
* stateId: 'state-uuid',
* dryRun: true,
* })
*/
export async function updateIssue(
input: UpdateIssueInput,
): Promise<LinearIssueSummary | DryRunResult<{ mutation: string; id: string; input: JsonRecord }>> {
const id = requireString(input.id, 'id')
const payload = issueUpdatePayload(input)
if (input.dryRun) {
return { dryRun: true, wouldCall: { mutation: 'issueUpdate', id, input: payload } }
}
if (input.confirm !== true) {
throw new Error(
'Updating a Linear issue requires confirm: true after explicit user approval, or dryRun: true.',
)
}
const result = await linearGraphql<{
issueUpdate: { success?: boolean; issue?: unknown }
}>({
...input,
operation: 'issueUpdate',
query: `mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
issueUpdate(id: $id, input: $input) {
success
issue { ${ISSUE_FIELDS} }
}
}`,
variables: { id, input: payload },
})
if (!result.data.issueUpdate?.success || !result.data.issueUpdate.issue) {
throw new Error('Linear issueUpdate did not return an issue.')
}
return mapIssue(result.data.issueUpdate.issue)
}
/**
* Update a Linear issue. Requires `confirm: true`, or use `dryRun: true`.
* @example
* import updateIssue from 'kody:@kody/linear/update-issue'
* const preview = await updateIssue({ id: 'ENG-123', title: 'Renamed', dryRun: true })
*/
export default async function updateIssueEntrypoint(
params: Partial<UpdateIssueInput> & Record<string, unknown> = {},
) {
const input = requireRecord(params, 'update-issue')
return updateIssue({
id: requireString(input.id, 'id'),
title: optionalString(input.title, 'title'),
description: optionalString(input.description, 'description'),
assigneeId: optionalString(input.assigneeId, 'assigneeId'),
stateId: optionalString(input.stateId, 'stateId'),
projectId: optionalString(input.projectId, 'projectId'),
teamId: optionalString(input.teamId, 'teamId'),
priority: optionalPriority(input.priority),
labelIds: optionalStringArray(input.labelIds, 'labelIds'),
dueDate: optionalString(input.dueDate, 'dueDate'),
parentId: optionalString(input.parentId, 'parentId'),
estimate: optionalNumber(input.estimate, 'estimate'),
integrationName: optionalString(input.integrationName, 'integrationName'),
integration: optionalString(input.integration, 'integration'),
account: optionalString(input.account, 'account'),
secretName: optionalString(input.secretName, 'secretName'),
auth: requireAuthMode(input.auth),
confirm: optionalBoolean(input.confirm, 'confirm'),
dryRun: optionalBoolean(input.dryRun, 'dryRun'),
})
}