-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement unknown flag detection and enhance search result messaging #106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
843ba0d
a4205bd
5c631ce
be5b057
870c433
f3f801d
3f2b982
87baae6
712e25f
d9764bc
8330c5b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,6 +9,46 @@ import { parseBetween, buildDateFilter } from '../date-filter.ts'; | |||||||||||||||||
|
|
||||||||||||||||||
| export type SearchResult = { items: Array<Record<string, unknown>>; total?: number }; | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Flags that are valid globally (not specific to any search resource). | ||||||||||||||||||
| */ | ||||||||||||||||||
| export const GLOBAL_FLAGS = new Set([ | ||||||||||||||||||
| 'profile', 'sortBy', 'asc', 'desc', 'help', 'version', | ||||||||||||||||||
| ]); | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Valid search filter flags per resource (values keys that are consumed by the search handler). | ||||||||||||||||||
| * This map is used to detect when a user passes a flag that looks valid but is not recognized | ||||||||||||||||||
| * for the specific resource, causing silent filter drops. | ||||||||||||||||||
| */ | ||||||||||||||||||
| export const SEARCH_RESOURCE_FLAGS: Record<string, Set<string>> = { | ||||||||||||||||||
| 'process-definition': new Set(['bpmnProcessId', 'id', 'processDefinitionId', 'name', 'key', 'iid', 'iname']), | ||||||||||||||||||
|
||||||||||||||||||
| 'process-definition': new Set(['bpmnProcessId', 'id', 'processDefinitionId', 'name', 'key', 'iid', 'iname']), | |
| 'process-definition': new Set(['bpmnProcessId', 'id', 'processDefinitionId', 'name', 'key', 'iid', 'iname', 'version_num']), |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing flag in SEARCH_RESOURCE_FLAGS: The search process definitions handler in index.ts (line 680) accepts version_num flag, but 'version_num' is not included in SEARCH_RESOURCE_FLAGS['process-definition']. This will cause detectUnknownSearchFlags to incorrectly report it as an unknown flag if a user tries to use it. Add 'version_num' to the process-definition flag set.
| 'process-definition': new Set(['bpmnProcessId', 'id', 'processDefinitionId', 'name', 'key', 'iid', 'iname']), | |
| 'process-definition': new Set(['bpmnProcessId', 'id', 'processDefinitionId', 'name', 'key', 'iid', 'iname', 'version_num']), |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SEARCH_RESOURCE_FLAGS is missing flags that are actually supported by the corresponding search handlers, so users will get false "unknown flag" warnings. For example, searchProcessInstances/searchUserTasks/searchJobs accept --between and --dateField, and searchIncidents accepts --between, but none of these appear in the per-resource sets. Add the missing flags to the appropriate resource entries so detectUnknownSearchFlags() doesn’t warn on documented options.
| 'process-instance': new Set(['bpmnProcessId', 'id', 'processDefinitionId', 'processDefinitionKey', 'state', 'key', 'parentProcessInstanceKey', 'iid']), | |
| 'user-task': new Set(['state', 'assignee', 'processInstanceKey', 'processDefinitionKey', 'elementId', 'iassignee']), | |
| 'incident': new Set(['state', 'processInstanceKey', 'processDefinitionKey', 'bpmnProcessId', 'id', 'processDefinitionId', 'errorType', 'errorMessage', 'ierrorMessage', 'iid']), | |
| 'jobs': new Set(['state', 'type', 'processInstanceKey', 'processDefinitionKey', 'itype']), | |
| 'process-instance': new Set(['bpmnProcessId', 'id', 'processDefinitionId', 'processDefinitionKey', 'state', 'key', 'parentProcessInstanceKey', 'iid', 'between', 'dateField']), | |
| 'user-task': new Set(['state', 'assignee', 'processInstanceKey', 'processDefinitionKey', 'elementId', 'iassignee', 'between', 'dateField']), | |
| 'incident': new Set(['state', 'processInstanceKey', 'processDefinitionKey', 'bpmnProcessId', 'id', 'processDefinitionId', 'errorType', 'errorMessage', 'ierrorMessage', 'iid', 'between']), | |
| 'jobs': new Set(['state', 'type', 'processInstanceKey', 'processDefinitionKey', 'itype', 'between', 'dateField']), |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,9 @@ | |||||||||||||||||||||||||||
| * Main entry point | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import { realpathSync } from 'node:fs'; | ||||||||||||||||||||||||||||
| import { parseArgs } from 'node:util'; | ||||||||||||||||||||||||||||
| import { fileURLToPath } from 'node:url'; | ||||||||||||||||||||||||||||
| import { getLogger, type SortOrder } from './logger.ts'; | ||||||||||||||||||||||||||||
| import { c8ctl } from './runtime.ts'; | ||||||||||||||||||||||||||||
| import { loadSessionState } from './config.ts'; | ||||||||||||||||||||||||||||
|
|
@@ -28,6 +30,7 @@ import { | |||||||||||||||||||||||||||
| searchIncidents, | ||||||||||||||||||||||||||||
| searchJobs, | ||||||||||||||||||||||||||||
| searchVariables, | ||||||||||||||||||||||||||||
| detectUnknownSearchFlags, | ||||||||||||||||||||||||||||
| } from './commands/search.ts'; | ||||||||||||||||||||||||||||
| import { listUserTasks, completeUserTask } from './commands/user-tasks.ts'; | ||||||||||||||||||||||||||||
| import { listIncidents, getIncident, resolveIncident } from './commands/incidents.ts'; | ||||||||||||||||||||||||||||
|
|
@@ -144,6 +147,17 @@ export function resolveProcessDefinitionId(values: any): string | undefined { | |||||||||||||||||||||||||||
| return (values.id || values.processDefinitionId || values.bpmnProcessId) as string | undefined; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Warn about unrecognized flags for a search resource. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
| function warnUnknownSearchFlags(logger: ReturnType<typeof getLogger>, unknownFlags: string[], resource: string): void { | ||||||||||||||||||||||||||||
| if (unknownFlags.length === 0) return; | ||||||||||||||||||||||||||||
| const flagList = unknownFlags.map(f => `--${f}`).join(', '); | ||||||||||||||||||||||||||||
| logger.warn( | ||||||||||||||||||||||||||||
| `Flag(s) ${flagList} not recognized for 'search ${resource}'. They will be ignored. Run "c8ctl help search" for valid options.`, | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Main CLI handler | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
|
|
@@ -666,18 +680,21 @@ async function main() { | |||||||||||||||||||||||||||
| // Handle search commands | ||||||||||||||||||||||||||||
| if (verb === 'search') { | ||||||||||||||||||||||||||||
| const normalizedSearchResource = normalizeResource(resource); | ||||||||||||||||||||||||||||
| const unknownFlags = detectUnknownSearchFlags(values as Record<string, unknown>, normalizedSearchResource); | ||||||||||||||||||||||||||||
| warnUnknownSearchFlags(logger, unknownFlags, resource); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (normalizedSearchResource === 'process-definition' || normalizedSearchResource === 'process-definitions') { | ||||||||||||||||||||||||||||
| await searchProcessDefinitions({ | ||||||||||||||||||||||||||||
| profile: values.profile as string | undefined, | ||||||||||||||||||||||||||||
| processDefinitionId: resolveProcessDefinitionId(values), | ||||||||||||||||||||||||||||
| name: values.name as string | undefined, | ||||||||||||||||||||||||||||
| version: (values.version_num && typeof values.version_num === 'string') ? parseInt(values.version_num) : undefined, | ||||||||||||||||||||||||||||
| version: (values.version && typeof values.version === 'string') ? parseInt(values.version) : undefined, | ||||||||||||||||||||||||||||
| key: values.key as string | undefined, | ||||||||||||||||||||||||||||
|
Comment on lines
688
to
692
|
||||||||||||||||||||||||||||
| iProcessDefinitionId: values.iid as string | undefined, | ||||||||||||||||||||||||||||
| iName: values.iname as string | undefined, | ||||||||||||||||||||||||||||
| sortBy: values.sortBy as string | undefined, | ||||||||||||||||||||||||||||
| sortOrder, | ||||||||||||||||||||||||||||
| _unknownFlags: unknownFlags, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
@@ -693,6 +710,7 @@ async function main() { | |||||||||||||||||||||||||||
| iProcessDefinitionId: values.iid as string | undefined, | ||||||||||||||||||||||||||||
| sortBy: values.sortBy as string | undefined, | ||||||||||||||||||||||||||||
| sortOrder, | ||||||||||||||||||||||||||||
| _unknownFlags: unknownFlags, | ||||||||||||||||||||||||||||
| between: values.between as string | undefined, | ||||||||||||||||||||||||||||
| dateField: values.dateField as string | undefined, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
@@ -710,6 +728,7 @@ async function main() { | |||||||||||||||||||||||||||
| iAssignee: values.iassignee as string | undefined, | ||||||||||||||||||||||||||||
| sortBy: values.sortBy as string | undefined, | ||||||||||||||||||||||||||||
| sortOrder, | ||||||||||||||||||||||||||||
| _unknownFlags: unknownFlags, | ||||||||||||||||||||||||||||
| between: values.between as string | undefined, | ||||||||||||||||||||||||||||
| dateField: values.dateField as string | undefined, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
@@ -729,6 +748,7 @@ async function main() { | |||||||||||||||||||||||||||
| iProcessDefinitionId: values.iid as string | undefined, | ||||||||||||||||||||||||||||
| sortBy: values.sortBy as string | undefined, | ||||||||||||||||||||||||||||
| sortOrder, | ||||||||||||||||||||||||||||
| _unknownFlags: unknownFlags, | ||||||||||||||||||||||||||||
| between: values.between as string | undefined, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
|
|
@@ -744,6 +764,7 @@ async function main() { | |||||||||||||||||||||||||||
| iType: values.itype as string | undefined, | ||||||||||||||||||||||||||||
| sortBy: values.sortBy as string | undefined, | ||||||||||||||||||||||||||||
| sortOrder, | ||||||||||||||||||||||||||||
| _unknownFlags: unknownFlags, | ||||||||||||||||||||||||||||
| between: values.between as string | undefined, | ||||||||||||||||||||||||||||
| dateField: values.dateField as string | undefined, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
@@ -763,6 +784,7 @@ async function main() { | |||||||||||||||||||||||||||
| sortBy: values.sortBy as string | undefined, | ||||||||||||||||||||||||||||
| sortOrder, | ||||||||||||||||||||||||||||
| limit, | ||||||||||||||||||||||||||||
| _unknownFlags: unknownFlags, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
@@ -790,9 +812,12 @@ async function main() { | |||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Run the CLI only when invoked directly (not when imported) | ||||||||||||||||||||||||||||
| if (process.argv[1] === new URL(import.meta.url).pathname) { | ||||||||||||||||||||||||||||
| main().catch((error) => { | ||||||||||||||||||||||||||||
| console.error('Unexpected error:', error); | ||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| // Use realpathSync to resolve symlinks (e.g. when installed globally via npm link) | ||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||
| if (realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) { | ||||||||||||||||||||||||||||
| main().catch((error) => { | ||||||||||||||||||||||||||||
| console.error('Unexpected error:', error); | ||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } catch { /* not invoked directly */ } | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
| } catch { /* not invoked directly */ } | |
| } catch (error: unknown) { | |
| // Only ignore expected "not a normal direct invocation" errors; surface others | |
| const nodeError = error as { code?: unknown } | null; | |
| const code = nodeError && typeof nodeError.code === 'string' ? nodeError.code : undefined; | |
| if (code === 'ENOENT' || code === 'EINVAL') { | |
| // Treat as "not invoked directly" (e.g. unusual invocation contexts) | |
| return; | |
| } | |
| console.error('Failed to resolve CLI entry point. The CLI could not determine whether it was invoked directly.'); | |
| console.error(error); | |
| process.exit(1); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Including
versioninGLOBAL_FLAGSmeans--versionwill never be reported as unknown for anysearch <resource>invocation, even though it’s only meaningful for certain commands/resources. To make unknown-flag detection effective, consider removingversionfromGLOBAL_FLAGSand instead adding it only to the specificSEARCH_RESOURCE_FLAGSentries where it’s actually consumed (e.g. process-definition if supported there).