Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- every command that takes a team now accepts its key, name, or UUID, resolved through one shared lookup: `team states`, `team members`, `team delete`, `label list/create/delete --team`, `cycle list/view --team`, `project list/create/update --team`, `document list/create/update --team`, and `issue query/mine/create/update --team`. Keys stay canonical and win over a same-spelled name; an unknown team errors with the list of valid keys instead of an empty result or a raw API error. Previously only keys worked, which is why [#276](https://github.com/schpet/linear-cli/issues/276) asked for `team list --json` as a name-to-key lookup
- `issue query --state` and `issue mine --state` accept a workflow state name or ID as well as the six state types, looked up within the queried team scope (all teams under `--all-teams`, where a name matches every team's same-named state). An unknown name errors and lists the scope's states, and types and names can be mixed
- `project update --content <markdown>` and `--content-file <path>` replace a project's long-form overview body, matching the flags `project create` already had. Previously the only way to change the body after creation was a hand-written `projectUpdate` mutation through `linear api`
- `issue update --clear-due-date`, `--clear-estimate`, `--clear-parent`, `--clear-project`, and `--clear-milestone`, plus `project update --clear-lead`, `--clear-start-date`, and `--clear-target-date`, to remove a value the way `--unassign` and `--clear-cycle` already do. Each sends an explicit `null` to Linear and errors when combined with its set flag; `--clear-project` also rejects `--milestone`, since a milestone belongs to the project being removed. Previously these fields could only be changed, never cleared: cliffy treats `--due-date ""` as a missing value
- `--json` (`-j`) on `team list`, `cycle list`, `cycle view`, `milestone list`, `milestone view`, and `project view`, the last read commands without machine-readable output. List commands emit the same `{ nodes, pageInfo }` connection shape as the other list commands, after the same filtering and ordering as the table; view commands emit the GraphQL object as fetched, including every issue rather than the ten-item preview, and `milestone view --all --json` includes every page. (A 2.0.0 entry claimed `cycle list --json`; that change never actually landed.) ([#276](https://github.com/schpet/linear-cli/issues/276); thanks @lakardion)

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ linear issue create -t "title" -d "description" # create with flags
linear issue create --project "My Project" --milestone "Phase 1" # create with milestone
linear issue update # update an issue (interactive prompts)
linear issue update ENG-123 --milestone "Phase 2" # set milestone on existing issue
linear issue update ENG-123 --clear-due-date --clear-parent # remove values (also --clear-estimate, --clear-project, --clear-milestone, --clear-cycle, --unassign)
linear issue delete # delete an issue
linear issue comment list # list comments on current issue
linear issue comment add # add a comment to current issue
Expand Down Expand Up @@ -202,6 +203,7 @@ linear project view <projectId> --json # project details as JSON
linear project create --name "API v2" --team ENG --content-file overview.md
linear project create --name "Mobile launch" --team APP --priority high --label Launch --member jane@example.com
linear project update <projectId> --content-file overview.md # replace the project's overview body
linear project update <projectId> --clear-lead --clear-target-date # remove values (also --clear-start-date)
linear project comment list <project> # list the project's discussion thread (UUID, slug, or name)
linear project comment add <project> --body "Kickoff Monday" # comment on a project
linear project comment add <project> --body "+1" --reply-to <commentId> # reply in a thread
Expand Down
18 changes: 18 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,21 @@ linear issue update TEAM-123 --remove-label sprint-42 --add-label sprint-43
linear issue update TEAM-123 --label bug --label frontend
```

clear optional fields (each `--clear-*` flag conflicts with its set flag):

```bash
# Remove the due date, estimate, parent, project, or milestone
linear issue update TEAM-123 --clear-due-date
linear issue update TEAM-123 --clear-estimate --clear-parent
linear issue update TEAM-123 --clear-project --clear-milestone

# Move to another project and detach the milestone in one update
linear issue update TEAM-123 --project "Mobile App" --clear-milestone

# Assignee and cycle have their own clearing flags
linear issue update TEAM-123 --unassign --clear-cycle
```

#### other issue commands

get issue id from current git branch:
Expand Down Expand Up @@ -299,6 +314,9 @@ linear project update PROJECT-ID --description "Short summary" --content "## Ove

# Replace the overview body from a markdown file
linear project update PROJECT-ID --content-file overview.md

# Remove the lead, start date, or target date (each conflicts with its set flag)
linear project update PROJECT-ID --clear-lead --clear-start-date --clear-target-date
```

#### list projects
Expand Down
18 changes: 13 additions & 5 deletions skills/linear-cli/references/issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -598,10 +598,13 @@ Options:
--workspace <slug> - Target workspace (uses credentials)
-a, --assignee <assignee> - Assign the issue to 'self' or someone (by username or name)
--unassign - Clear the issue's assignee (cannot be combined with --assignee)
--due-date <dueDate> - Due date of the issue
--parent <parent> - Parent issue (if any) as a team_number code
--due-date <dueDate> - Due date of the issue. Use --clear-due-date to remove it
--clear-due-date - Remove the issue's due date (cannot be combined with --due-date)
--parent <parent> - Parent issue (if any) as a team_number code. Use --clear-parent to remove it
--clear-parent - Remove the issue's parent (cannot be combined with --parent)
-p, --priority <priority> - Priority of the issue (1-4, descending priority)
--estimate <estimate> - Points estimate of the issue
--estimate <estimate> - Points estimate of the issue. Use --clear-estimate to remove it
--clear-estimate - Remove the issue's estimate (cannot be combined with --estimate)
-d, --description <description> - Description of the issue
--description-file <path> - Read description from a file (preferred for markdown content)
-l, --label <label> - Issue label associated with the issue; replaces the issue's entire label set.
Expand All @@ -610,10 +613,15 @@ Options:
--remove-label <label> - Remove a label from the issue, keeping its other labels (does not delete the
label from the team). May be repeated.
--team <team> - Team (key, name, or ID) to move the issue to
--project <project> - Project to assign the issue to (UUID, slug ID, or name)
--project <project> - Project to assign the issue to (UUID, slug ID, or name). Use --clear-project to
remove it
--clear-project - Remove the issue from its project (cannot be combined with --project or
--milestone)
-s, --state <state> - Workflow state for the issue (by name or type)
--milestone <milestone> - Project milestone (UUID, or name when --project is set or the issue already has
a project)
a project). Use --clear-milestone to remove it
--clear-milestone - Remove the issue from its project milestone (cannot be combined with
--milestone)
--cycle <cycle> - Cycle name, number, 'active'/'now', 'next', 'previous', or a relative offset
like +1 (use --cycle=-1 for negatives). Use --clear-cycle to remove the issue
from its cycle
Expand Down
9 changes: 6 additions & 3 deletions skills/linear-cli/references/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,12 @@ Options:
--content <markdown> - Project overview markdown
--content-file <path> - Read project overview markdown from a file
-s, --status <status> - Status (planned, started, paused, completed, canceled, backlog)
-l, --lead <lead> - Project lead (username, email, or @me)
--start-date <startDate> - Start date (YYYY-MM-DD)
--target-date <targetDate> - Target date (YYYY-MM-DD)
-l, --lead <lead> - Project lead (username, email, or @me). Use --clear-lead to remove it
--clear-lead - Remove the project's lead (cannot be combined with --lead)
--start-date <startDate> - Start date (YYYY-MM-DD). Use --clear-start-date to remove it
--clear-start-date - Remove the project's start date (cannot be combined with --start-date)
--target-date <targetDate> - Target date (YYYY-MM-DD). Use --clear-target-date to remove it
--clear-target-date - Remove the project's target date (cannot be combined with --target-date)
-t, --team <team> - Team key, name, or ID (can be repeated for multiple teams)
--label <label> - Replace the project's labels. May be repeated to set multiple labels.
```
Expand Down
125 changes: 115 additions & 10 deletions src/commands/issue/issue-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,31 @@ export const updateCommand = new Command()
)
.option(
"--due-date <dueDate:string>",
"Due date of the issue",
"Due date of the issue. Use --clear-due-date to remove it",
)
.option(
"--clear-due-date",
"Remove the issue's due date (cannot be combined with --due-date)",
)
.option(
"--parent <parent:string>",
"Parent issue (if any) as a team_number code",
"Parent issue (if any) as a team_number code. Use --clear-parent to remove it",
)
.option(
"--clear-parent",
"Remove the issue's parent (cannot be combined with --parent)",
)
.option(
"-p, --priority <priority:number>",
"Priority of the issue (1-4, descending priority)",
)
.option(
"--estimate <estimate:number>",
"Points estimate of the issue",
"Points estimate of the issue. Use --clear-estimate to remove it",
)
.option(
"--clear-estimate",
"Remove the issue's estimate (cannot be combined with --estimate)",
)
.option(
"-d, --description <description:string>",
Expand Down Expand Up @@ -83,15 +95,23 @@ export const updateCommand = new Command()
)
.option(
"--project <project:string>",
"Project to assign the issue to (UUID, slug ID, or name)",
"Project to assign the issue to (UUID, slug ID, or name). Use --clear-project to remove it",
)
.option(
"--clear-project",
"Remove the issue from its project (cannot be combined with --project or --milestone)",
)
.option(
"-s, --state <state:string>",
"Workflow state for the issue (by name or type)",
)
.option(
"--milestone <milestone:string>",
"Project milestone (UUID, or name when --project is set or the issue already has a project)",
"Project milestone (UUID, or name when --project is set or the issue already has a project). Use --clear-milestone to remove it",
)
.option(
"--clear-milestone",
"Remove the issue from its project milestone (cannot be combined with --milestone)",
)
.option(
"--cycle <cycle:string>",
Expand All @@ -109,18 +129,23 @@ export const updateCommand = new Command()
unassign,
clearCycle,
dueDate,
clearDueDate,
parent,
clearParent,
priority,
estimate,
clearEstimate,
description,
descriptionFile,
label: labels,
addLabel,
removeLabel,
team,
project,
clearProject,
state,
milestone,
clearMilestone,
cycle,
title,
},
Expand All @@ -147,6 +172,70 @@ export const updateCommand = new Command()
)
}

if (clearDueDate && dueDate != null) {
throw new ValidationError(
"Cannot specify both --due-date and --clear-due-date",
{
suggestion:
"Use --due-date <date> to set a due date, or --clear-due-date on its own to remove it.",
},
)
}

// `!= null`, not truthiness: `--estimate 0` is an explicit value.
if (clearEstimate && estimate != null) {
throw new ValidationError(
"Cannot specify both --estimate and --clear-estimate",
{
suggestion:
"Use --estimate <points> to set an estimate, or --clear-estimate on its own to remove it.",
},
)
}

if (clearParent && parent != null) {
throw new ValidationError(
"Cannot specify both --parent and --clear-parent",
{
suggestion:
"Use --parent <issue> to set a parent, or --clear-parent on its own to remove it.",
},
)
}

if (clearProject && project != null) {
throw new ValidationError(
"Cannot specify both --project and --clear-project",
{
suggestion:
"Use --project <project> to set a project, or --clear-project on its own to remove it.",
},
)
}

// A milestone belongs to a project, so keeping one while removing the
// project is contradictory (and a milestone name would resolve against
// the project being removed).
if (clearProject && milestone != null) {
throw new ValidationError(
"Cannot specify --milestone while clearing the issue's project",
{
suggestion:
"Drop --milestone, or replace it with --clear-milestone to remove both the project and the milestone.",
},
)
}

if (clearMilestone && milestone != null) {
throw new ValidationError(
"Cannot specify both --milestone and --clear-milestone",
{
suggestion:
"Use --milestone <milestone> to set a milestone, or --clear-milestone on its own to remove it.",
},
)
}

if (labels != null && (addLabel != null || removeLabel != null)) {
throw new ValidationError(
"Cannot combine --label with --add-label or --remove-label",
Expand Down Expand Up @@ -329,8 +418,14 @@ export const updateCommand = new Command()
} else if (assigneeId != null) {
input.assigneeId = assigneeId
}
if (dueDate !== undefined) input.dueDate = dueDate
if (parent !== undefined) {
if (clearDueDate) {
input.dueDate = null
} else if (dueDate !== undefined) {
input.dueDate = dueDate
}
if (clearParent) {
input.parentId = null
} else if (parent !== undefined) {
const parentIdentifier = await getIssueIdentifier(parent)
if (!parentIdentifier) {
throw new ValidationError(
Expand All @@ -344,7 +439,11 @@ export const updateCommand = new Command()
input.parentId = parentId
}
if (priority !== undefined) input.priority = priority
if (estimate !== undefined) input.estimate = estimate
if (clearEstimate) {
input.estimate = null
} else if (estimate !== undefined) {
input.estimate = estimate
}
if (finalDescription !== undefined) input.description = finalDescription
if (labels != null) {
input.labelIds = labelIds
Expand All @@ -353,8 +452,14 @@ export const updateCommand = new Command()
if (removeLabel != null) input.removedLabelIds = removedLabelIds
}
if (teamId !== undefined) input.teamId = teamId
if (projectId !== undefined) input.projectId = projectId
if (projectMilestoneId !== undefined) {
if (clearProject) {
input.projectId = null
} else if (projectId !== undefined) {
input.projectId = projectId
}
if (clearMilestone) {
input.projectMilestoneId = null
} else if (projectMilestoneId !== undefined) {
input.projectMilestoneId = projectMilestoneId
}
if (clearCycle) {
Expand Down
Loading
Loading