Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 8 additions & 67 deletions src/commands/document/document-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Command } from "@cliffy/command"
import { Input, Select } from "@cliffy/prompt"
import { gql } from "../../__codegen__/gql.ts"
import { getGraphQLClient } from "../../utils/graphql.ts"
import { resolveProjectId } from "../../utils/linear.ts"
import { getEditor, openEditor } from "../../utils/editor.ts"
import { readIdsFromStdin } from "../../utils/bulk.ts"
import {
Expand Down Expand Up @@ -43,7 +44,10 @@ export const createCommand = new Command()
.option("-t, --title <title:string>", "Document title (required)")
.option("-c, --content <content:string>", "Markdown content (inline)")
.option("-f, --content-file <path:string>", "Read content from file")
.option("--project <project:string>", "Attach to project (slug or ID)")
.option(
"--project <project:string>",
"Attach to project (UUID, slug ID, or name)",
)
.option("--issue <issue:string>", "Attach to issue (identifier like TC-123)")
.option("--icon <icon:string>", "Document icon (emoji)")
.option("-i, --interactive", "Interactive mode with prompts")
Expand Down Expand Up @@ -146,12 +150,7 @@ export const createCommand = new Command()
// Resolve project ID if provided
let projectId: string | undefined
if (project) {
projectId = await resolveProjectId(client, project)
if (!projectId) {
throw new NotFoundError("Project", project, {
suggestion: "Provide a valid project slug or ID.",
})
}
projectId = await resolveProjectId(project)
}

// Resolve issue ID if provided
Expand Down Expand Up @@ -273,15 +272,9 @@ async function promptInteractiveCreate(): Promise<{

if (attachTo === "project") {
const projectInput = await Input.prompt({
message: "Project slug or ID",
message: "Project (UUID, slug ID, or name)",
})
const client = getGraphQLClient()
projectId = await resolveProjectId(client, projectInput)
if (!projectId) {
throw new NotFoundError("Project", projectInput, {
suggestion: "Provide a valid project slug or ID.",
})
}
projectId = await resolveProjectId(projectInput)
} else if (attachTo === "issue") {
const issueInput = await Input.prompt({
message: "Issue identifier (e.g., TC-123)",
Expand All @@ -304,58 +297,6 @@ async function promptInteractiveCreate(): Promise<{
}
}

async function resolveProjectId(
// deno-lint-ignore no-explicit-any
client: any,
projectInput: string,
): Promise<string | undefined> {
// First try to get by slug/ID directly
const projectQuery = gql(`
query GetProjectForDocument($slugId: String!) {
project(id: $slugId) {
id
name
}
}
`)

try {
const result = await client.request(projectQuery, { slugId: projectInput })
if (result.project) {
return result.project.id
}
} catch {
// Project not found by ID, try searching by name
}

// Search by name
const searchQuery = gql(`
query SearchProjectsForDocument($filter: ProjectFilter) {
projects(filter: $filter, first: 1) {
nodes {
id
name
}
}
}
`)

try {
const result = await client.request(searchQuery, {
filter: {
name: { containsIgnoreCase: projectInput },
},
})
if (result.projects.nodes.length > 0) {
return result.projects.nodes[0].id
}
} catch {
// Search failed
}

return undefined
}

async function resolveIssueId(
// deno-lint-ignore no-explicit-any
client: any,
Expand Down
33 changes: 19 additions & 14 deletions src/commands/issue/issue-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import {
getIssueLabelIdByNameForTeam,
getIssueLabelOptionsByNameForTeam,
getLabelsForTeam,
getMilestoneIdByName,
getProjectIdByName,
getProjectOptionsByName,
isLinearUuid,
resolveMilestoneId,
getTeamIdByKey,
getTeamKey,
getWorkflowStateByNameOrType,
Expand Down Expand Up @@ -490,15 +491,15 @@ export const createCommand = new Command()
)
.option(
"--project <project:string>",
"Name or slug ID of the project with the issue",
"Project for the issue (UUID, slug ID, or name)",
)
.option(
"-s, --state <state:string>",
"Workflow state for the issue (by name or type)",
)
.option(
"--milestone <milestone:string>",
"Name of the project milestone",
"Project milestone (UUID, or name when --project is set)",
)
.option(
"--cycle <cycle:string>",
Expand Down Expand Up @@ -752,19 +753,23 @@ export const createCommand = new Command()

let projectMilestoneId: string | undefined
if (milestone != null) {
if (projectId == null) {
throw new ValidationError(
"--milestone requires --project to be set",
{
suggestion:
"Use --project to specify which project the milestone belongs to.",
},
if (isLinearUuid(milestone)) {
projectMilestoneId = milestone
} else {
if (projectId == null) {
throw new ValidationError(
"--milestone requires --project to be set",
{
suggestion:
"Use --project to specify which project the milestone belongs to, or pass a milestone UUID directly.",
},
)
}
projectMilestoneId = await resolveMilestoneId(
milestone,
projectId,
)
}
projectMilestoneId = await getMilestoneIdByName(
milestone,
projectId,
)
}

let cycleId: string | undefined
Expand Down
29 changes: 17 additions & 12 deletions src/commands/issue/issue-mine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
import {
fetchIssuesForState,
getCycleIdByNameOrNumber,
getMilestoneIdByName,
getProjectIdByName,
isLinearUuid,
resolveMilestoneId,
getProjectOptionsByName,
getTeamIdByKey,
getTeamKey,
Expand Down Expand Up @@ -69,7 +70,7 @@ export const mineCommand = new Command()
)
.option(
"--project <project:string>",
"Filter by project name",
"Filter by project (UUID, slug ID, or name)",
)
.option(
"--project-label <projectLabel:string>",
Expand All @@ -81,7 +82,7 @@ export const mineCommand = new Command()
)
.option(
"--milestone <milestone:string>",
"Filter by project milestone name (requires --project)",
"Filter by project milestone (UUID, or name when --project is set)",
)
.option(
"-l, --label <label:string>",
Expand Down Expand Up @@ -243,16 +244,20 @@ export const mineCommand = new Command()
},
)
}
if (projectId == null) {
throw new ValidationError(
"--milestone requires --project to be set",
{
suggestion:
"Use --project to specify which project the milestone belongs to.",
},
)
if (isLinearUuid(milestone)) {
milestoneId = milestone
} else {
if (projectId == null) {
throw new ValidationError(
"--milestone requires --project to be set",
{
suggestion:
"Use --project to specify which project the milestone belongs to, or pass a milestone UUID directly.",
},
)
}
milestoneId = await resolveMilestoneId(milestone, projectId)
}
milestoneId = await getMilestoneIdByName(milestone, projectId)
}

const labelNames = labels && labels.length > 0
Expand Down
17 changes: 10 additions & 7 deletions src/commands/issue/issue-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
import {
fetchIssuesForQuery,
getCycleIdByNameOrNumber,
getMilestoneIdByName,
getProjectIdByName,
isLinearUuid,
resolveMilestoneId,
getProjectOptionsByName,
getTeamIdByKey,
getTeamKey,
Expand Down Expand Up @@ -77,7 +78,7 @@ export const queryCommand = new Command()
)
.option(
"--project <project:string>",
"Filter by project name",
"Filter by project (UUID, slug ID, or name)",
)
.option(
"--project-label <projectLabel:string>",
Expand All @@ -89,7 +90,7 @@ export const queryCommand = new Command()
)
.option(
"--milestone <milestone:string>",
"Filter by project milestone name (requires --project)",
"Filter by project milestone (UUID, or name when --project is set)",
)
.option(
"-l, --label <label:string>",
Expand Down Expand Up @@ -182,12 +183,12 @@ export const queryCommand = new Command()
)
}

if (milestone != null && project == null) {
if (milestone != null && project == null && !isLinearUuid(milestone)) {
throw new ValidationError(
"--milestone requires --project to be set",
{
suggestion:
"Use --project to specify which project the milestone belongs to.",
"Use --project to specify which project the milestone belongs to, or pass a milestone UUID directly.",
},
)
}
Expand Down Expand Up @@ -295,8 +296,10 @@ export const queryCommand = new Command()
}

let milestoneId: string | undefined
if (milestone != null && projectId != null) {
milestoneId = await getMilestoneIdByName(milestone, projectId)
if (milestone != null) {
milestoneId = isLinearUuid(milestone)
? milestone
: await resolveMilestoneId(milestone, projectId)
}

const labelNames = label && label.length > 0
Expand Down
42 changes: 25 additions & 17 deletions src/commands/issue/issue-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import {
getIssueIdentifier,
getIssueLabelIdByNameForTeam,
getIssueProjectId,
getMilestoneIdByName,
getProjectIdByName,
isLinearUuid,
resolveMilestoneId,
getTeamIdByKey,
getWorkflowStateByNameOrType,
lookupUserId,
Expand Down Expand Up @@ -64,15 +65,15 @@ export const updateCommand = new Command()
)
.option(
"--project <project:string>",
"Name or slug ID of the project with the issue",
"Project to assign the issue to (UUID, slug ID, or name)",
)
.option(
"-s, --state <state:string>",
"Workflow state for the issue (by name or type)",
)
.option(
"--milestone <milestone:string>",
"Name of the project milestone",
"Project milestone (UUID, or name when --project is set or the issue already has a project)",
)
.option(
"--cycle <cycle:string>",
Expand Down Expand Up @@ -196,27 +197,34 @@ export const updateCommand = new Command()
if (project !== undefined) {
projectId = await getProjectIdByName(project)
if (projectId === undefined) {
throw new NotFoundError("Project", project)
throw new NotFoundError("Project", project, {
suggestion:
"Pass a project UUID, slug ID (from `linear project list`), or exact project name.",
})
}
}

let projectMilestoneId: string | undefined
if (milestone != null) {
const milestoneProjectId = projectId ??
await getIssueProjectId(issueId)
if (milestoneProjectId == null) {
throw new ValidationError(
"--milestone requires --project to be set (issue has no existing project)",
{
suggestion:
"Use --project to specify the project for the milestone.",
},
if (isLinearUuid(milestone)) {
projectMilestoneId = milestone
} else {
const milestoneProjectId = projectId ??
await getIssueProjectId(issueId)
if (milestoneProjectId == null) {
throw new ValidationError(
"--milestone requires --project to be set (issue has no existing project)",
{
suggestion:
"Use --project to specify the project for the milestone, or pass a milestone UUID directly.",
},
)
}
projectMilestoneId = await resolveMilestoneId(
milestone,
milestoneProjectId,
)
}
projectMilestoneId = await getMilestoneIdByName(
milestone,
milestoneProjectId,
)
}

let cycleId: string | undefined
Expand Down
6 changes: 5 additions & 1 deletion src/commands/milestone/milestone-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ const CreateProjectMilestone = gql(`
export const createCommand = new Command()
.name("create")
.description("Create a new project milestone")
.option("--project <projectId:string>", "Project ID", { required: true })
.option(
"--project <project:string>",
"Project (UUID, slug ID, or name)",
{ required: true },
)
.option("--name <name:string>", "Milestone name", { required: true })
.option("--description <description:string>", "Milestone description")
.option("--target-date <date:string>", "Target date (YYYY-MM-DD)")
Expand Down
6 changes: 5 additions & 1 deletion src/commands/milestone/milestone-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ const GetProjectMilestones = gql(`
export const listCommand = new Command()
.name("list")
.description("List milestones for a project")
.option("--project <projectId:string>", "Project ID", { required: true })
.option(
"--project <project:string>",
"Project (UUID, slug ID, or name)",
{ required: true },
)
.action(async ({ project: projectIdOrSlug }) => {
const { Spinner } = await import("@std/cli/unstable-spinner")
const showSpinner = shouldShowSpinner()
Expand Down
Loading