import { linearGraphql } from './client.ts'
import { mapProject, PROJECT_FIELDS } from './models.ts'
import type {
DryRunResult,
JsonRecord,
LinearAuthInput,
LinearProjectSummary,
} from './types.ts'
import {
compactRecord,
optionalBoolean,
optionalPriority,
optionalString,
optionalStringArray,
requireRecord,
requireString,
} from './types.ts'
import { requireAuthMode } from './auth.ts'
export type CreateProjectInput = LinearAuthInput & {
name: string
/** One or more team UUIDs. Required by Linear. */
teamIds: Array<string>
description?: string
content?: string
leadId?: string
memberIds?: Array<string>
priority?: number
startDate?: string
targetDate?: string
statusId?: string
confirm?: boolean
dryRun?: boolean
}
function projectCreatePayload(input: CreateProjectInput): JsonRecord {
return compactRecord({
name: requireString(input.name, 'name'),
teamIds: optionalStringArray(input.teamIds, 'teamIds') ?? [],
description: optionalString(input.description, 'description'),
content: optionalString(input.content, 'content'),
leadId: optionalString(input.leadId, 'leadId'),
memberIds: optionalStringArray(input.memberIds, 'memberIds'),
priority: optionalPriority(input.priority),
startDate: optionalString(input.startDate, 'startDate'),
targetDate: optionalString(input.targetDate, 'targetDate'),
statusId: optionalString(input.statusId, 'statusId'),
})
}
/**
* Create a Linear project. Requires `confirm: true`, or use `dryRun: true`.
* @example
* import createProject from 'kody:@kody/linear/create-project'
* const preview = await createProject({
* name: 'Launch checklist',
* teamIds: ['team-uuid'],
* dryRun: true,
* })
*/
export async function createProject(
input: CreateProjectInput,
): Promise<LinearProjectSummary | DryRunResult<{ mutation: string; input: JsonRecord }>> {
const payload = projectCreatePayload(input)
if (!Array.isArray(payload.teamIds) || payload.teamIds.length === 0) {
throw new Error('teamIds must include at least one Linear team UUID.')
}
if (input.dryRun) {
return { dryRun: true, wouldCall: { mutation: 'projectCreate', input: payload } }
}
if (input.confirm !== true) {
throw new Error(
'Creating a Linear project requires confirm: true after explicit user approval, or dryRun: true.',
)
}
const result = await linearGraphql<{
projectCreate: { success?: boolean; project?: unknown }
}>({
...input,
operation: 'projectCreate',
query: `mutation CreateProject($input: ProjectCreateInput!) {
projectCreate(input: $input) {
success
project { ${PROJECT_FIELDS} }
}
}`,
variables: { input: payload },
})
if (!result.data.projectCreate?.success || !result.data.projectCreate.project) {
throw new Error('Linear projectCreate did not return a project.')
}
return mapProject(result.data.projectCreate.project)
}
/**
* Create a Linear project. Requires `confirm: true`, or use `dryRun: true`.
* @example
* import createProject from 'kody:@kody/linear/create-project'
* const preview = await createProject({ name: 'Launch', teamIds: ['uuid'], dryRun: true })
*/
export default async function createProjectEntrypoint(
params: Partial<CreateProjectInput> & Record<string, unknown> = {},
) {
const input = requireRecord(params, 'create-project')
return createProject({
name: requireString(input.name, 'name'),
teamIds: optionalStringArray(input.teamIds, 'teamIds') ?? [],
description: optionalString(input.description, 'description'),
content: optionalString(input.content, 'content'),
leadId: optionalString(input.leadId, 'leadId'),
memberIds: optionalStringArray(input.memberIds, 'memberIds'),
priority: optionalPriority(input.priority),
startDate: optionalString(input.startDate, 'startDate'),
targetDate: optionalString(input.targetDate, 'targetDate'),
statusId: optionalString(input.statusId, 'statusId'),
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'),
})
}