2026-03-20 19:53:16 +01:00
import { createCLI } from "@bunli/core"
2026-08-19 20:44:26 +02:00
import { writeError } from "./helpers.js"
2026-03-20 19:53:16 +01:00
import { search } from "./commands/search.js"
import { detail } from "./commands/detail.js"
const cli = await createCLI ({
name : "jobbank-cli" ,
version : "1.0.0" ,
description : "CLI for Akademikernes Jobbank (jobbank.dk) — job search for highly educated candidates" ,
})
2026-08-19 20:44:26 +02:00
const commands = [ search , detail ]
for ( const command of commands ) {
cli . command ( command )
}
2026-09-03 18:40:55 +01:00
// Reject unknown flags before dispatch. bunli silently discards them, and a
2026-08-19 20:44:26 +02:00
// silently discarded filter changes what the search returns without any error
// (a wrong flag name once returned an entire portal's database as if it
// matched the query). add-portal.md's contract requires a bogus flag to exit 1
// with a JSON error on stderr; this enforces it for the reference CLIs too.
2026-09-03 18:40:55 +01:00
//
// Both dash forms are checked. This loop inspected only `--long` tokens until
// #426, so an undefined short flag was discarded in silence: `-q "..."` on a
// portal whose keyword flag is `--search-string` returned the whole database
// as a successful, unfiltered search. Declared shorts and bunli's built-in
// -h/-v stay valid; every other single-dash token is rejected, including a
// negative number. bunli does not consume a `-`-prefixed token as the previous
// flag's value - it discards it - so `--radius -5` silently fell back to the
// default radius rather than failing its own `min(1)` schema. Erroring on it
// is the same trade linkedin-search already makes. A value that must begin
// with a dash uses the `--flag=value` form, which is checked as a long flag.
2026-08-19 20:44:26 +02:00
const argv = process . argv . slice ( 2 )
const invoked = commands . find (( c ) => ( c as { name? : string }). name === argv [ 0 ])
if ( invoked ) {
2026-09-03 18:40:55 +01:00
const options =
( invoked as { options? : Record < string , { short ?: string } | undefined > }). options ?? {}
const known = new Set ([... Object . keys ( options ), "help" , "version" ])
const knownShorts = new Set (
Object . values ( options )
. map (( o ) => o ? . short )
. filter (( s ) : s is string => typeof s === "string" )
. concat ( "h" , "v" ),
)
const rejectFlag = ( rendered : string ) : never => {
writeError (
`unknown flag ${ rendered } for ' ${ argv [ 0 ] } ' - flags are never silently ignored, because a discarded filter changes what the search returns; see --help for the supported flags` ,
"UNKNOWN_FLAG" ,
)
process . exit ( 1 )
}
2026-08-19 20:44:26 +02:00
for ( const token of argv . slice ( 1 )) {
if ( token === "--" ) break
if ( token . startsWith ( "--" )) {
const flag = token . slice ( 2 ). split ( "=" )[ 0 ]
2026-09-03 18:40:55 +01:00
if ( ! known . has ( flag )) rejectFlag ( `-- ${ flag } ` )
} else if ( token . startsWith ( "-" ) && token !== "-" ) {
const flag = token . slice ( 1 ). split ( "=" )[ 0 ]
if ( ! knownShorts . has ( flag )) rejectFlag ( `- ${ flag } ` )
2026-08-19 20:44:26 +02:00
}
}
}
2026-03-20 19:53:16 +01:00
await cli . run ()