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
73 changes: 41 additions & 32 deletions src/commands/list/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,79 @@
import { Command, Flags } from '@oclif/core'

import { daemonListDocs } from '../../daemon/daemon-list-docs.js'
import type { Doc } from '../../daemon/types.js'
import { projectFlag } from '../../flags/project-flag.js'
import {
ensureInitialized,
NotInitializedError,
} from '../../utils/ensure-initialized.js'
import { ensureInitialized, NotInitializedError } from '../../utils/ensure-initialized.js'
import { groupByProject } from '../../utils/group-by-project.js'
import { listAcrossProjects } from '../../utils/list-across-projects.js'
import { resolveProjectPath } from '../../utils/resolve-project-path.js'

/**
* List all documentation files
*/
// eslint-disable-next-line custom/no-default-class-export, class-export/class-export
export default class ListDocs extends Command {
// eslint-disable-next-line no-restricted-syntax
static override description = 'List all documentation files'

// eslint-disable-next-line no-restricted-syntax
static override aliases = ['doc:list']

// eslint-disable-next-line no-restricted-syntax
static override examples = [
'<%= config.bin %> list docs',
'<%= config.bin %> list docs --json',
'<%= config.bin %> list docs --project centy-daemon',
'<%= config.bin %> list docs --all',
'<%= config.bin %> list docs -a --json',
]

// eslint-disable-next-line no-restricted-syntax
static override flags = {
json: Flags.boolean({
description: 'Output as JSON',
default: false,
}),
json: Flags.boolean({ description: 'Output as JSON', default: false }),
all: Flags.boolean({ char: 'a', description: 'List from all projects', default: false }),
project: projectFlag,
}

public async run(): Promise<void> {
const { flags } = await this.parse(ListDocs)
if (flags.all) return this.listAll(flags)
const cwd = await resolveProjectPath(flags.project)

try {
await ensureInitialized(cwd)
} catch (error) {
if (error instanceof NotInitializedError) {
this.error(error.message)
}
if (error instanceof NotInitializedError) this.error(error.message)
throw error instanceof Error ? error : new Error(String(error))
}
const response = await daemonListDocs({ projectPath: cwd })
if (flags.json) return void this.log(JSON.stringify(response.docs, null, 2))
if (response.docs.length === 0) return void this.log('No docs found.')
this.log(`Found ${response.totalCount} doc(s):\n`)
for (const doc of response.docs) this.log(`${doc.slug}: ${doc.title}`)
}

const response = await daemonListDocs({
projectPath: cwd,
private async listAll(flags: { json: boolean }): Promise<void> {
const result = await listAcrossProjects<Doc>({
async listFn(projectPath) {
const r = await daemonListDocs({ projectPath })
return r.docs
},
})

if (flags.json) {
this.log(JSON.stringify(response.docs, null, 2))
return
const docs = result.items.map(i => ({
doc: i.entity, projectName: i.projectName, projectPath: i.projectPath,
}))
return void this.log(JSON.stringify({ docs, totalCount: docs.length, errors: result.errors }, null, 2))
}

if (response.docs.length === 0) {
this.log('No docs found.')
return
if (result.items.length === 0) {
this.log('No docs found across all projects.')
return void this.printErrors(result.errors)
}
this.log(`Found ${result.items.length} doc(s) across all projects:\n`)
for (const [name, items] of groupByProject(result.items)) {
this.log(`--- ${name} (${items.length} doc(s)) ---`)
for (const i of items) this.log(` ${i.entity.slug}: ${i.entity.title}`)
this.log('')
}
this.printErrors(result.errors)
}

this.log(`Found ${response.totalCount} doc(s):\n`)
for (const doc of response.docs) {
this.log(`${doc.slug}: ${doc.title}`)
private printErrors(errors: string[]): void {
if (errors.length > 0) {
this.warn('Some projects could not be searched:')
for (const err of errors) this.warn(` - ${err}`)
}
}
}
121 changes: 59 additions & 62 deletions src/commands/list/issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,101 +2,98 @@
import { Command, Flags } from '@oclif/core'

import { daemonListIssues } from '../../daemon/daemon-list-issues.js'
import type { Issue } from '../../daemon/types.js'
import { projectFlag } from '../../flags/project-flag.js'
import {
ensureInitialized,
NotInitializedError,
} from '../../utils/ensure-initialized.js'
import { ensureInitialized, NotInitializedError } from '../../utils/ensure-initialized.js'
import { groupByProject } from '../../utils/group-by-project.js'
import { listAcrossProjects } from '../../utils/list-across-projects.js'
import { resolveProjectPath } from '../../utils/resolve-project-path.js'

/**
* List all issues in the .centy/issues folder
*/
const formatIssue = (issue: Issue): string => {
const meta = issue.metadata
const priority = meta !== undefined
? (meta.priorityLabel !== '' ? meta.priorityLabel : `P${meta.priority}`)
: 'P?'
const status = meta !== undefined ? meta.status : 'unknown'
const draft = meta !== undefined && meta.draft === true ? ' [DRAFT]' : ''
return `#${issue.displayNumber} [${priority}] [${status}]${draft} ${issue.title}`
}

// eslint-disable-next-line custom/no-default-class-export, class-export/class-export
export default class ListIssues extends Command {
// eslint-disable-next-line no-restricted-syntax
static override description = 'List all issues'

// eslint-disable-next-line no-restricted-syntax
static override aliases = ['issue:list']

// eslint-disable-next-line no-restricted-syntax
static override examples = [
'<%= config.bin %> list issues',
'<%= config.bin %> list issues --status open',
'<%= config.bin %> list issues --priority 1',
'<%= config.bin %> list issues --project centy-daemon',
'<%= config.bin %> list issues --draft',
'<%= config.bin %> list issues --no-draft',
'<%= config.bin %> list issues --all',
'<%= config.bin %> list issues -a --status open',
]

// eslint-disable-next-line no-restricted-syntax
static override flags = {
status: Flags.string({
char: 's',
description: 'Filter by status (e.g., open, in-progress, closed)',
}),
priority: Flags.integer({
char: 'p',
description: 'Filter by priority level (1 = highest)',
}),
json: Flags.boolean({
description: 'Output as JSON',
default: false,
}),
draft: Flags.boolean({
description:
'Filter by draft status (--draft for drafts only, --no-draft for non-drafts)',
allowNo: true,
}),
status: Flags.string({ char: 's', description: 'Filter by status' }),
priority: Flags.integer({ char: 'p', description: 'Filter by priority' }),
json: Flags.boolean({ description: 'Output as JSON', default: false }),
draft: Flags.boolean({ description: 'Filter by draft status', allowNo: true }),
all: Flags.boolean({ char: 'a', description: 'List from all projects', default: false }),
project: projectFlag,
}

public async run(): Promise<void> {
const { flags } = await this.parse(ListIssues)
if (flags.all) return this.listAll(flags)
const cwd = await resolveProjectPath(flags.project)

try {
await ensureInitialized(cwd)
} catch (error) {
if (error instanceof NotInitializedError) {
this.error(error.message)
}
if (error instanceof NotInitializedError) this.error(error.message)
throw error instanceof Error ? error : new Error(String(error))
}

const response = await daemonListIssues({
projectPath: cwd,
status: flags.status,
priority: flags.priority,
draft: flags.draft,
projectPath: cwd, status: flags.status, priority: flags.priority, draft: flags.draft,
})
if (flags.json) return void this.log(JSON.stringify(response.issues, null, 2))
if (response.issues.length === 0) return void this.log('No issues found.')
this.log(`Found ${response.totalCount} issue(s):\n`)
for (const issue of response.issues) this.log(formatIssue(issue))
}

private async listAll(flags: {
status?: string; priority?: number; draft?: boolean; json: boolean
}): Promise<void> {
const result = await listAcrossProjects<Issue>({
async listFn(projectPath) {
const r = await daemonListIssues({
projectPath, status: flags.status, priority: flags.priority, draft: flags.draft,
})
return r.issues
},
})
if (flags.json) {
this.log(JSON.stringify(response.issues, null, 2))
return
const issues = result.items.map(i => ({
issue: i.entity, projectName: i.projectName, projectPath: i.projectPath,
}))
return void this.log(JSON.stringify({ issues, totalCount: issues.length, errors: result.errors }, null, 2))
}

if (response.issues.length === 0) {
this.log('No issues found.')
return
if (result.items.length === 0) {
this.log('No issues found across all projects.')
return void this.printErrors(result.errors)
}
this.log(`Found ${result.items.length} issue(s) across all projects:\n`)
for (const [name, items] of groupByProject(result.items)) {
this.log(`--- ${name} (${items.length} issue(s)) ---`)
for (const i of items) this.log(` ${formatIssue(i.entity)}`)
this.log('')
}
this.printErrors(result.errors)
}

this.log(`Found ${response.totalCount} issue(s):\n`)
for (const issue of response.issues) {
const meta = issue.metadata
const priority =
meta !== undefined
? meta.priorityLabel !== ''
? meta.priorityLabel
: `P${meta.priority}`
: 'P?'
const status = meta !== undefined ? meta.status : 'unknown'
const draftIndicator =
meta !== undefined && meta.draft === true ? ' [DRAFT]' : ''
this.log(
`#${issue.displayNumber} [${priority}] [${status}]${draftIndicator} ${issue.title}`
)
private printErrors(errors: string[]): void {
if (errors.length > 0) {
this.warn('Some projects could not be searched:')
for (const err of errors) this.warn(` - ${err}`)
}
}
}
Loading
Loading