Skip to content
Draft
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
12 changes: 12 additions & 0 deletions src/commands/issue/issue-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ export const listCommand = new Command()
default: 50,
},
)
.option(
"--created-after <date:string>",
"Filter issues created after this date (ISO 8601 or YYYY-MM-DD)",
)
.option(
"--updated-after <date:string>",
"Filter issues updated after this date (ISO 8601 or YYYY-MM-DD)",
)
.option("-w, --web", "Open in web browser")
.option("-a, --app", "Open in Linear.app")
.option("--no-pager", "Disable automatic paging for long output")
Expand All @@ -117,6 +125,8 @@ export const listCommand = new Command()
milestone,
limit,
pager,
createdAfter,
updatedAfter,
},
) => {
const usePager = pager !== false
Expand Down Expand Up @@ -221,6 +231,8 @@ export const listCommand = new Command()
sort,
cycleId,
milestoneId,
createdAfter,
updatedAfter,
)
spinner?.stop()
const issues = result.issues?.nodes || []
Expand Down
24 changes: 24 additions & 0 deletions src/utils/linear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,8 @@ export async function fetchIssuesForState(
sortParam?: "manual" | "priority",
cycleId?: string,
milestoneId?: string,
createdAfter?: string,
updatedAfter?: string,
) {
const sort = sortParam ??
getOption("issue_sort") as "manual" | "priority" | undefined
Expand Down Expand Up @@ -469,6 +471,28 @@ export async function fetchIssuesForState(
filter.projectMilestone = { id: { eq: milestoneId } }
}

if (createdAfter) {
const parsed = new Date(createdAfter)
if (isNaN(parsed.getTime())) {
throw new ValidationError(
`Invalid date for --created-after: "${createdAfter}"`,
{ suggestion: "Use ISO 8601 format (e.g. 2024-01-15) or a valid date string." },
)
}
filter.createdAt = { gte: parsed.toISOString() }
}

if (updatedAfter) {
const parsed = new Date(updatedAfter)
if (isNaN(parsed.getTime())) {
throw new ValidationError(
`Invalid date for --updated-after: "${updatedAfter}"`,
{ suggestion: "Use ISO 8601 format (e.g. 2024-01-15) or a valid date string." },
)
}
filter.updatedAt = { gte: parsed.toISOString() }
}

const query = gql(/* GraphQL */ `
query GetIssuesForState($sort: [IssueSortInput!], $filter: IssueFilter!, $first: Int, $after: String) {
issues(filter: $filter, sort: $sort, first: $first, after: $after) {
Expand Down
2 changes: 2 additions & 0 deletions test/commands/issue/__snapshots__/issue-list.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Options:
--cycle <cycle> - Filter by cycle name, number, or 'active'
--milestone <milestone> - Filter by project milestone name (requires --project)
--limit <limit> - Maximum number of issues to fetch (default: 50, use 0 for unlimited) (Default: \\x1b[33m50\\x1b[39m)
--created-after <date> - Filter issues created after this date (ISO 8601 or YYYY-MM-DD)
--updated-after <date> - Filter issues updated after this date (ISO 8601 or YYYY-MM-DD)
-w, --web - Open in web browser
-a, --app - Open in Linear.app
--no-pager - Disable automatic paging for long output
Expand Down
49 changes: 49 additions & 0 deletions test/commands/issue/issue-list.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { snapshotTest } from "@cliffy/testing"
import { assertRejects } from "@std/assert"
import { listCommand } from "../../../src/commands/issue/issue-list.ts"
import { fetchIssuesForState } from "../../../src/utils/linear.ts"
import { ValidationError } from "../../../src/utils/errors.ts"
import { commonDenoArgs } from "../../utils/test-helpers.ts"

// Test help output
Expand All @@ -13,3 +16,49 @@ await snapshotTest({
await listCommand.parse()
},
})

// Test invalid --created-after date
Deno.test("fetchIssuesForState - rejects invalid createdAfter date", async () => {
await assertRejects(
() =>
fetchIssuesForState(
"TEST",
["unstarted"],
undefined,
false,
false,
50,
undefined,
"manual",
undefined,
undefined,
"not-a-date",
undefined,
),
ValidationError,
'Invalid date for --created-after: "not-a-date"',
)
})

// Test invalid --updated-after date
Deno.test("fetchIssuesForState - rejects invalid updatedAfter date", async () => {
await assertRejects(
() =>
fetchIssuesForState(
"TEST",
["unstarted"],
undefined,
false,
false,
50,
undefined,
"manual",
undefined,
undefined,
undefined,
"xyz-bad-date",
),
ValidationError,
'Invalid date for --updated-after: "xyz-bad-date"',
)
})
Loading