diff --git a/src/__tests__/empty-output.test.ts b/src/__tests__/empty-output.test.ts new file mode 100644 index 0000000..feb01bc --- /dev/null +++ b/src/__tests__/empty-output.test.ts @@ -0,0 +1,28 @@ +import { describeEmptyMachineOutput } from '@doist/cli-core/testing' +import { Command } from 'commander' +import { vi } from 'vitest' + +vi.mock('../lib/auth.js', () => ({ + getApiToken: async () => 'test-token', + getBaseUrl: async () => 'https://test.outline.com', + getOAuthClientId: async () => undefined, + getTokenSource: async () => 'config' as const, + saveConfig: vi.fn(), + clearConfig: vi.fn(), +})) + +vi.mock('../lib/api.js', () => ({ + apiRequest: vi.fn().mockResolvedValue({ data: [], pagination: undefined }), +})) + +describeEmptyMachineOutput('ol document list', { + setup: () => {}, + run: async (extraArgs) => { + const { registerDocumentCommand } = await import('../commands/document.js') + const program = new Command() + program.exitOverride() + registerDocumentCommand(program) + await program.parseAsync(['node', 'ol', 'document', 'list', ...extraArgs]) + }, + humanMessage: 'No documents found.', +}) diff --git a/src/__tests__/spinner.test.ts b/src/__tests__/spinner.test.ts index 7ce9c77..889db0a 100644 --- a/src/__tests__/spinner.test.ts +++ b/src/__tests__/spinner.test.ts @@ -79,4 +79,24 @@ describe('spinner wiring', () => { process.argv = ['node', 'ol', 'auth', 'status', '--no-spinner'] expect((await loadIsDisabled())()).toBe(true) }) + + it('disables when --progress-jsonl is in argv', async () => { + process.argv = ['node', 'ol', 'search', 'foo', '--progress-jsonl'] + expect((await loadIsDisabled())()).toBe(true) + }) + + it('disables when --progress-jsonl=path is in argv', async () => { + process.argv = ['node', 'ol', 'search', 'foo', '--progress-jsonl=/tmp/p.jsonl'] + expect((await loadIsDisabled())()).toBe(true) + }) + + it('disables when --verbose is in argv', async () => { + process.argv = ['node', 'ol', 'search', 'foo', '--verbose'] + expect((await loadIsDisabled())()).toBe(true) + }) + + it('disables when -v short flag is in argv', async () => { + process.argv = ['node', 'ol', 'search', 'foo', '-v'] + expect((await loadIsDisabled())()).toBe(true) + }) }) diff --git a/src/commands/document.ts b/src/commands/document.ts index dce30ed..d3404ee 100644 --- a/src/commands/document.ts +++ b/src/commands/document.ts @@ -79,7 +79,14 @@ export function registerDocumentCommand(program: Command): void { const { data, pagination } = await apiRequest('documents.list', body) - outputList(data, formatDoc, essentialKeys, getOutputOptions(opts), pagination) + outputList( + data, + formatDoc, + essentialKeys, + getOutputOptions(opts), + pagination, + 'No documents found.', + ) }) doc.command('get ') diff --git a/src/index.ts b/src/index.ts index e908b71..45d253f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ program .version('0.1.0') .description('CLI for the Outline wiki/knowledge base API') .option('--no-spinner', 'Disable loading animations') + .option('--accessible', 'Render output in screen-reader-friendly mode') .addHelpText( 'after', ` diff --git a/src/lib/global-args.ts b/src/lib/global-args.ts new file mode 100644 index 0000000..6f7e55d --- /dev/null +++ b/src/lib/global-args.ts @@ -0,0 +1,16 @@ +import { createAccessibleGate, createGlobalArgsStore, createSpinnerGate } from '@doist/cli-core' + +const store = createGlobalArgsStore() + +export const getGlobalArgs = store.get +export const resetGlobalArgs = store.reset + +export const isAccessible = createAccessibleGate({ + envVar: 'OL_ACCESSIBLE', + getArgs: store.get, +}) + +export const shouldDisableSpinner = createSpinnerGate({ + envVar: 'OL_SPINNER', + getArgs: store.get, +}) diff --git a/src/lib/output.ts b/src/lib/output.ts index 54e4a64..c0a90c6 100644 --- a/src/lib/output.ts +++ b/src/lib/output.ts @@ -1,10 +1,8 @@ -import { formatJson, formatNdjson } from '@doist/cli-core' +import { formatJson, formatNdjson, printEmpty, type ViewOptions } from '@doist/cli-core' import chalk from 'chalk' import type { Pagination } from './api.js' -interface OutputOptions { - json?: boolean - ndjson?: boolean +export type OutputOptions = ViewOptions & { full?: boolean } @@ -41,7 +39,13 @@ export function outputList( essentialKeys?: (keyof T)[], opts: OutputOptions = {}, pagination?: Pagination, + emptyMessage?: string, ): void { + if (items.length === 0 && emptyMessage !== undefined) { + printEmpty({ options: opts, message: emptyMessage }) + return + } + const project = (item: T) => (opts.full || !essentialKeys ? item : pick(item, essentialKeys)) if (opts.ndjson) { diff --git a/src/lib/spinner.ts b/src/lib/spinner.ts index bc7fee7..08d89ff 100644 --- a/src/lib/spinner.ts +++ b/src/lib/spinner.ts @@ -1,19 +1,8 @@ import { createSpinner } from '@doist/cli-core' +import { shouldDisableSpinner } from './global-args.js' export type { SpinnerColor, SpinnerOptions } from '@doist/cli-core' -function shouldDisableSpinner(): boolean { - if (process.env.OL_SPINNER === 'false') return true - if (process.env.CI && process.env.CI !== 'false') return true - - const args = process.argv - if (args.includes('--json') || args.includes('--ndjson') || args.includes('--no-spinner')) { - return true - } - - return false -} - const spinner = createSpinner({ isDisabled: shouldDisableSpinner }) export const { LoadingSpinner, withSpinner, startEarlySpinner, stopEarlySpinner } = spinner