diff --git a/package.json b/package.json index 81cf1577..c049d242 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "resend-cli", - "version": "2.26.0", + "version": "2.26.1", "description": "The official CLI for Resend", "license": "MIT", "repository": { diff --git a/src/commands/automations/runs/list.ts b/src/commands/automations/runs/list.ts index b48185b4..5e1781eb 100644 --- a/src/commands/automations/runs/list.ts +++ b/src/commands/automations/runs/list.ts @@ -37,6 +37,7 @@ export const listAutomationRunsCommand = new Command('list') ) .action(async (automationIdArg, opts, cmd) => { const globalOpts = cmd.optsWithGlobals() as GlobalOpts; + const status = opts.status?.replace(/\s+/g, ''); const automationId = await pickId( automationIdArg, automationPickerConfig, @@ -56,9 +57,9 @@ export const listAutomationRunsCommand = new Command('list') resend.automations.runs.list({ automationId, ...paginationOpts, - ...(opts.status + ...(status ? { - status: opts.status as Parameters< + status: status as Parameters< typeof resend.automations.runs.list >[0]['status'], } @@ -83,6 +84,7 @@ export const listAutomationRunsCommand = new Command('list') before: opts.before, apiKey: globalOpts.apiKey, profile: globalOpts.profile, + ...(status && { extraFlags: `--status ${status}` }), }); }, }, diff --git a/src/commands/contacts/imports/list.ts b/src/commands/contacts/imports/list.ts index 52112487..c57d603a 100644 --- a/src/commands/contacts/imports/list.ts +++ b/src/commands/contacts/imports/list.ts @@ -70,6 +70,7 @@ export const listContactImportsCommand = new Command('list') before: opts.before, apiKey: globalOpts.apiKey, profile: globalOpts.profile, + ...(opts.status && { extraFlags: `--status ${opts.status}` }), }); }, }, diff --git a/tests/commands/automations/runs/list.test.ts b/tests/commands/automations/runs/list.test.ts new file mode 100644 index 00000000..aa9e9978 --- /dev/null +++ b/tests/commands/automations/runs/list.test.ts @@ -0,0 +1,99 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { listAutomationRunsCommand } from '../../../../src/commands/automations/runs/list'; +import { captureTestEnv, setupOutputSpies } from '../../../helpers'; + +const run = { + object: 'automation_run', + id: 'run-1', + status: 'failed', + started_at: '2026-05-15 18:32:37.823+00', + completed_at: '2026-05-15 18:33:42.916+00', +}; + +const mockList = vi.fn(async () => ({ + data: { object: 'list', has_more: false, data: [run] }, + error: null, +})); + +vi.mock('resend', () => ({ + Resend: class MockResend { + constructor(public key: string) {} + automations = { runs: { list: mockList } }; + }, +})); + +describe('automations runs list command', () => { + const restoreEnv = captureTestEnv(); + + beforeEach(() => { + process.env.RESEND_API_KEY = 're_test_key'; + mockList.mockClear(); + }); + + afterEach(() => { + restoreEnv(); + }); + + it('passes --status filter to the SDK', async () => { + setupOutputSpies(); + + await listAutomationRunsCommand.parseAsync( + ['auto-1', '--status', 'failed'], + { from: 'user' }, + ); + + expect(mockList).toHaveBeenCalledWith({ + automationId: 'auto-1', + limit: 10, + status: 'failed', + }); + }); + + it('strips spaces from a comma-separated --status', async () => { + setupOutputSpies(); + + await listAutomationRunsCommand.parseAsync( + ['auto-1', '--status', 'running, failed'], + { from: 'user' }, + ); + + expect(mockList).toHaveBeenCalledWith({ + automationId: 'auto-1', + limit: 10, + status: 'running,failed', + }); + }); + + it.each([ + ['failed', '--status failed'], + ['running, failed', '--status running,failed'], + ])('preserves --status %j in the next-page hint', async (status, expected) => { + mockList.mockResolvedValueOnce({ + data: { object: 'list', has_more: true, data: [run] }, + error: null, + }); + // Interactive mode: the hint only prints when TTY and not a CI env. + Object.defineProperty(process.stdin, 'isTTY', { + value: true, + writable: true, + }); + Object.defineProperty(process.stdout, 'isTTY', { + value: true, + writable: true, + }); + delete process.env.CI; + delete process.env.GITHUB_ACTIONS; + delete process.env.TERM; + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + + await listAutomationRunsCommand.parseAsync(['auto-1', '--status', status], { + from: 'user', + }); + + const output = logSpy.mock.calls.map((c) => c[0]).join('\n'); + logSpy.mockRestore(); + expect(output).toContain( + `automations runs list auto-1 --after run-1 --limit 10 ${expected}`, + ); + }); +}); diff --git a/tests/commands/contacts/imports/list.test.ts b/tests/commands/contacts/imports/list.test.ts index 76c969b1..3f7b03bd 100644 --- a/tests/commands/contacts/imports/list.test.ts +++ b/tests/commands/contacts/imports/list.test.ts @@ -80,6 +80,36 @@ describe('contacts imports list command', () => { expect(mockList).toHaveBeenCalledWith({ limit: 10, status: 'completed' }); }); + it('preserves --status in the next-page hint', async () => { + mockList.mockResolvedValueOnce({ + data: { object: 'list', has_more: true, data: [contactImport] }, + error: null, + }); + // Interactive mode: the hint only prints when TTY and not a CI env. + Object.defineProperty(process.stdin, 'isTTY', { + value: true, + writable: true, + }); + Object.defineProperty(process.stdout, 'isTTY', { + value: true, + writable: true, + }); + delete process.env.CI; + delete process.env.GITHUB_ACTIONS; + delete process.env.TERM; + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + + await listContactImportsCommand.parseAsync(['--status', 'failed'], { + from: 'user', + }); + + const output = logSpy.mock.calls.map((c) => c[0]).join('\n'); + logSpy.mockRestore(); + expect(output).toContain( + 'contacts imports list --after 479e3145-dd38-476b-932c-529ceb705947 --limit 10 --status failed', + ); + }); + it('passes --after cursor to the SDK', async () => { spies = setupOutputSpies();