@@ -9,6 +9,46 @@ import { parseBetween, buildDateFilter } from '../date-filter.ts';
99
1010export type SearchResult = { items : Array < Record < string , unknown > > ; total ?: number } ;
1111
12+ /**
13+ * Flags that are valid globally (not specific to any search resource).
14+ */
15+ export const GLOBAL_FLAGS = new Set ( [
16+ 'profile' , 'sortBy' , 'asc' , 'desc' , 'help' , 'version' ,
17+ ] ) ;
18+
19+ /**
20+ * Valid search filter flags per resource (values keys that are consumed by the search handler).
21+ * This map is used to detect when a user passes a flag that looks valid but is not recognized
22+ * for the specific resource, causing silent filter drops.
23+ */
24+ export const SEARCH_RESOURCE_FLAGS : Record < string , Set < string > > = {
25+ 'process-definition' : new Set ( [ 'bpmnProcessId' , 'id' , 'processDefinitionId' , 'name' , 'key' , 'iid' , 'iname' ] ) ,
26+ 'process-instance' : new Set ( [ 'bpmnProcessId' , 'id' , 'processDefinitionId' , 'processDefinitionKey' , 'state' , 'key' , 'parentProcessInstanceKey' , 'iid' ] ) ,
27+ 'user-task' : new Set ( [ 'state' , 'assignee' , 'processInstanceKey' , 'processDefinitionKey' , 'elementId' , 'iassignee' ] ) ,
28+ 'incident' : new Set ( [ 'state' , 'processInstanceKey' , 'processDefinitionKey' , 'bpmnProcessId' , 'id' , 'processDefinitionId' , 'errorType' , 'errorMessage' , 'ierrorMessage' , 'iid' ] ) ,
29+ 'jobs' : new Set ( [ 'state' , 'type' , 'processInstanceKey' , 'processDefinitionKey' , 'itype' ] ) ,
30+ 'variable' : new Set ( [ 'name' , 'value' , 'processInstanceKey' , 'scopeKey' , 'fullValue' , 'iname' , 'ivalue' , 'limit' ] ) ,
31+ } ;
32+
33+ /**
34+ * Detect flags the user set that are not recognized for the given search resource.
35+ * Returns the list of unknown flag names (without the --prefix).
36+ */
37+ export function detectUnknownSearchFlags ( values : Record < string , unknown > , normalizedResource : string ) : string [ ] {
38+ const validFlags = SEARCH_RESOURCE_FLAGS [ normalizedResource ]
39+ || SEARCH_RESOURCE_FLAGS [ normalizedResource . replace ( / s $ / , '' ) ] ;
40+ if ( ! validFlags ) return [ ] ;
41+
42+ const unknown : string [ ] = [ ] ;
43+ for ( const [ key , val ] of Object . entries ( values ) ) {
44+ if ( val === undefined || val === false ) continue ; // not set by the user
45+ if ( GLOBAL_FLAGS . has ( key ) ) continue ;
46+ if ( validFlags . has ( key ) ) continue ;
47+ unknown . push ( key ) ;
48+ }
49+ return unknown ;
50+ }
51+
1252/**
1353 * Detect wildcard characters (* or ?) in a string value and return
1454 * a $like filter object for the API. Returns the plain string for exact match.
@@ -71,6 +111,9 @@ const toBigIntSafe = (value: unknown): bigint => {
71111 }
72112} ;
73113
114+ /** Default page size the Camunda REST API uses when no explicit limit is set */
115+ export const API_DEFAULT_PAGE_SIZE = 100 ;
116+
74117/** Max page size for case-insensitive search (client-side filtering needs broader result set) */
75118const CI_PAGE_SIZE = 1000 ;
76119
@@ -111,14 +154,41 @@ function formatCriterion(fieldLabel: string, value: string | number | boolean, i
111154 */
112155function logSearchCriteria ( logger : Logger , resourceName : string , criteria : string [ ] ) : void {
113156 if ( criteria . length === 0 ) {
114- logger . info ( `Searching ${ resourceName } ` ) ;
157+ logger . info ( `Searching ${ resourceName } (no filters) ` ) ;
115158 } else if ( criteria . length === 1 ) {
116159 logger . info ( `Searching ${ resourceName } where ${ criteria [ 0 ] } ` ) ;
117160 } else {
118161 logger . info ( `Searching ${ resourceName } where ${ criteria . join ( ' AND ' ) } ` ) ;
119162 }
120163}
121164
165+ /**
166+ * Log a "no results" message with 🕳️ emoji and contextual hint.
167+ */
168+ export function logNoResults ( logger : Logger , resourceName : string , hasFilters : boolean , unknownFlags ?: string [ ] ) : void {
169+ if ( unknownFlags && unknownFlags . length > 0 ) {
170+ const flagList = unknownFlags . map ( f => `--${ f } ` ) . join ( ', ' ) ;
171+ logger . info ( `🕳️ No ${ resourceName } found matching the criteria (ignored unknown flag(s): ${ flagList } )` ) ;
172+ } else {
173+ logger . info ( `🕳️ No ${ resourceName } found matching the criteria` ) ;
174+ }
175+ if ( ! hasFilters ) {
176+ logger . info ( 'No filters were applied. Use "c8ctl help search" to see available filter flags.' ) ;
177+ }
178+ }
179+
180+ /**
181+ * Log the result count with a truncation warning when the count matches the API default page size.
182+ */
183+ export function logResultCount ( logger : Logger , count : number , resourceName : string , hasFilters : boolean ) : void {
184+ logger . info ( `Found ${ count } ${ resourceName } ` ) ;
185+ if ( count === API_DEFAULT_PAGE_SIZE && ! hasFilters ) {
186+ logger . warn ( `Showing first ${ API_DEFAULT_PAGE_SIZE } results (API default page size). More results may exist — add filters to narrow down.` ) ;
187+ } else if ( count === API_DEFAULT_PAGE_SIZE ) {
188+ logger . warn ( `Result count equals the API default page size (${ API_DEFAULT_PAGE_SIZE } ). There may be more results.` ) ;
189+ }
190+ }
191+
122192/**
123193 * Search process definitions
124194 */
@@ -132,6 +202,7 @@ export async function searchProcessDefinitions(options: {
132202 iName ?: string ;
133203 sortBy ?: string ;
134204 sortOrder ?: SortOrder ;
205+ _unknownFlags ?: string [ ] ;
135206} ) : Promise < SearchResult | undefined > {
136207 const logger = getLogger ( ) ;
137208 const client = createClient ( options . profile ) ;
@@ -218,9 +289,9 @@ export async function searchProcessDefinitions(options: {
218289 } ) ) ;
219290 tableData = sortTableData ( tableData , options . sortBy , logger , options . sortOrder ) ;
220291 logger . table ( tableData ) ;
221- logger . info ( `Found ${ result . items . length } process definition(s)` ) ;
292+ logResultCount ( logger , result . items . length , ' process definition(s)' , criteria . length > 0 ) ;
222293 } else {
223- logger . info ( 'No process definitions found matching the criteria' ) ;
294+ logNoResults ( logger , ' process definitions' , criteria . length > 0 , options . _unknownFlags ) ;
224295 }
225296
226297 return result as SearchResult ;
@@ -243,6 +314,7 @@ export async function searchProcessInstances(options: {
243314 iProcessDefinitionId ?: string ;
244315 sortBy ?: string ;
245316 sortOrder ?: SortOrder ;
317+ _unknownFlags ?: string [ ] ;
246318 between ?: string ;
247319 dateField ?: string ;
248320} ) : Promise < SearchResult | undefined > {
@@ -336,9 +408,9 @@ export async function searchProcessInstances(options: {
336408 } ) ) ;
337409 tableData = sortTableData ( tableData , options . sortBy , logger , options . sortOrder ) ;
338410 logger . table ( tableData ) ;
339- logger . info ( `Found ${ result . items . length } process instance(s)` ) ;
411+ logResultCount ( logger , result . items . length , ' process instance(s)' , criteria . length > 0 ) ;
340412 } else {
341- logger . info ( 'No process instances found matching the criteria' ) ;
413+ logNoResults ( logger , ' process instances' , criteria . length > 0 , options . _unknownFlags ) ;
342414 }
343415
344416 return result as SearchResult ;
@@ -361,6 +433,7 @@ export async function searchUserTasks(options: {
361433 iAssignee ?: string ;
362434 sortBy ?: string ;
363435 sortOrder ?: SortOrder ;
436+ _unknownFlags ?: string [ ] ;
364437 between ?: string ;
365438 dateField ?: string ;
366439} ) : Promise < SearchResult | undefined > {
@@ -455,9 +528,9 @@ export async function searchUserTasks(options: {
455528 } ) ) ;
456529 tableData = sortTableData ( tableData , options . sortBy , logger , options . sortOrder ) ;
457530 logger . table ( tableData ) ;
458- logger . info ( `Found ${ result . items . length } user task(s)` ) ;
531+ logResultCount ( logger , result . items . length , ' user task(s)' , criteria . length > 0 ) ;
459532 } else {
460- logger . info ( 'No user tasks found matching the criteria' ) ;
533+ logNoResults ( logger , ' user tasks' , criteria . length > 0 , options . _unknownFlags ) ;
461534 }
462535
463536 return result as SearchResult ;
@@ -482,6 +555,7 @@ export async function searchIncidents(options: {
482555 iProcessDefinitionId ?: string ;
483556 sortBy ?: string ;
484557 sortOrder ?: SortOrder ;
558+ _unknownFlags ?: string [ ] ;
485559 between ?: string ;
486560} ) : Promise < SearchResult | undefined > {
487561 const logger = getLogger ( ) ;
@@ -587,9 +661,9 @@ export async function searchIncidents(options: {
587661 } ) ) ;
588662 tableData = sortTableData ( tableData , options . sortBy , logger , options . sortOrder ) ;
589663 logger . table ( tableData ) ;
590- logger . info ( `Found ${ result . items . length } incident(s)` ) ;
664+ logResultCount ( logger , result . items . length , ' incident(s)' , criteria . length > 0 ) ;
591665 } else {
592- logger . info ( 'No incidents found matching the criteria' ) ;
666+ logNoResults ( logger , ' incidents' , criteria . length > 0 , options . _unknownFlags ) ;
593667 }
594668
595669 return result as SearchResult ;
@@ -611,6 +685,7 @@ export async function searchJobs(options: {
611685 iType ?: string ;
612686 sortBy ?: string ;
613687 sortOrder ?: SortOrder ;
688+ _unknownFlags ?: string [ ] ;
614689 between ?: string ;
615690 dateField ?: string ;
616691} ) : Promise < SearchResult | undefined > {
@@ -698,9 +773,9 @@ export async function searchJobs(options: {
698773 } ) ) ;
699774 tableData = sortTableData ( tableData , options . sortBy , logger , options . sortOrder ) ;
700775 logger . table ( tableData ) ;
701- logger . info ( `Found ${ result . items . length } job(s)` ) ;
776+ logResultCount ( logger , result . items . length , ' job(s)' , criteria . length > 0 ) ;
702777 } else {
703- logger . info ( 'No jobs found matching the criteria' ) ;
778+ logNoResults ( logger , ' jobs' , criteria . length > 0 , options . _unknownFlags ) ;
704779 }
705780
706781 return result as SearchResult ;
@@ -725,6 +800,7 @@ export async function searchVariables(options: {
725800 sortBy ?: string ;
726801 sortOrder ?: SortOrder ;
727802 limit ?: number ;
803+ _unknownFlags ?: string [ ] ;
728804} ) : Promise < SearchResult | undefined > {
729805 const logger = getLogger ( ) ;
730806 const client = createClient ( options . profile ) ;
@@ -833,13 +909,13 @@ export async function searchVariables(options: {
833909 } ) ;
834910 tableData = sortTableData ( tableData , options . sortBy , logger , options . sortOrder ) ;
835911 logger . table ( tableData ) ;
836- logger . info ( `Found ${ result . items . length } variable(s)` ) ;
912+ logResultCount ( logger , result . items . length , ' variable(s)' , criteria . length > 0 ) ;
837913
838914 if ( ! options . fullValue && result . items . some ( ( v : any ) => v . isTruncated ) ) {
839915 logger . info ( 'Some values are truncated. Use --fullValue to see full values.' ) ;
840916 }
841917 } else {
842- logger . info ( 'No variables found matching the criteria' ) ;
918+ logNoResults ( logger , ' variables' , criteria . length > 0 , options . _unknownFlags ) ;
843919 }
844920
845921 return result as SearchResult ;
0 commit comments