55 * A git-like CLI that demonstrates:
66 *
77 * - Nested command groups (e.g., `git remote add`)
8+ * - Factory pattern for full type inference of parent globals
89 * - Unlimited nesting depth
910 * - Parent globals flowing to nested handlers
1011 * - Default subcommands
@@ -31,124 +32,143 @@ const config: Map<string, string> = new Map([
3132] ) ;
3233
3334// ═══════════════════════════════════════════════════════════════════════════════
34- // NESTED COMMAND GROUPS
35- // ═══════════════════════════════════════════════════════════════════════════════
36-
37- // "remote" command group with subcommands: add, remove, list
38- const remoteCommands = bargs ( 'remote' )
39- . command (
40- 'add' ,
41- pos . positionals (
42- pos . string ( { name : 'name' , required : true } ) ,
43- pos . string ( { name : 'url' , required : true } ) ,
44- ) ,
45- ( { positionals, values } ) => {
46- const [ name , url ] = positionals ;
47- if ( remotes . has ( name ) ) {
48- console . error ( `Remote '${ name } ' already exists` ) ;
49- process . exit ( 1 ) ;
50- }
51- remotes . set ( name , url ) ;
52- // We can access parent globals (verbose) in nested handlers!
53- if ( values . verbose ) {
54- console . log ( `Added remote '${ name } ' with URL: ${ url } ` ) ;
55- } else {
56- console . log ( `Added remote '${ name } '` ) ;
57- }
58- } ,
59- 'Add a remote' ,
60- )
61- . command (
62- 'remove' ,
63- pos . positionals ( pos . string ( { name : 'name' , required : true } ) ) ,
64- ( { positionals, values } ) => {
65- const [ name ] = positionals ;
66- if ( ! remotes . has ( name ) ) {
67- console . error ( `Remote '${ name } ' not found` ) ;
68- process . exit ( 1 ) ;
69- }
70- remotes . delete ( name ) ;
71- if ( values . verbose ) {
72- console . log ( `Removed remote '${ name } '` ) ;
73- }
74- } ,
75- 'Remove a remote' ,
76- )
77- . command (
78- 'list' ,
79- opt . options ( { } ) ,
80- ( { values } ) => {
81- if ( remotes . size === 0 ) {
82- console . log ( 'No remotes configured' ) ;
83- return ;
84- }
85- for ( const [ name , url ] of remotes ) {
86- if ( values . verbose ) {
87- console . log ( `${ name } \t${ url } ` ) ;
88- } else {
89- console . log ( name ) ;
90- }
91- }
92- } ,
93- 'List remotes' ,
94- )
95- . defaultCommand ( 'list' ) ;
96-
97- // "config" command group with subcommands: get, set
98- const configCommands = bargs ( 'config' )
99- . command (
100- 'get' ,
101- pos . positionals ( pos . string ( { name : 'key' , required : true } ) ) ,
102- ( { positionals } ) => {
103- const [ key ] = positionals ;
104- const value = config . get ( key ) ;
105- if ( value === undefined ) {
106- console . error ( `Config key '${ key } ' not found` ) ;
107- process . exit ( 1 ) ;
108- }
109- console . log ( value ) ;
110- } ,
111- 'Get a config value' ,
112- )
113- . command (
114- 'set' ,
115- pos . positionals (
116- pos . string ( { name : 'key' , required : true } ) ,
117- pos . string ( { name : 'value' , required : true } ) ,
118- ) ,
119- ( { positionals, values } ) => {
120- const [ key , value ] = positionals ;
121- config . set ( key , value ) ;
122- if ( values . verbose ) {
123- console . log ( `Set ${ key } = ${ value } ` ) ;
124- }
125- } ,
126- 'Set a config value' ,
127- ) ;
128-
129- // ═══════════════════════════════════════════════════════════════════════════════
130- // MAIN CLI
35+ // GLOBAL OPTIONS
13136// ═══════════════════════════════════════════════════════════════════════════════
13237
13338// Global options that flow down to ALL nested commands
13439const globals = opt . options ( {
13540 verbose : opt . boolean ( { aliases : [ 'v' ] , default : false } ) ,
13641} ) ;
13742
43+ // ═══════════════════════════════════════════════════════════════════════════════
44+ // MAIN CLI
45+ // ═══════════════════════════════════════════════════════════════════════════════
46+
13847await bargs ( 'git-like' , {
13948 description : 'A git-like CLI demonstrating nested commands' ,
14049 version : '1.0.0' ,
14150} )
14251 . globals ( globals )
143- // Register nested command groups
144- . command ( 'remote' , remoteCommands , 'Manage remotes' )
145- . command ( 'config' , configCommands , 'Manage configuration' )
52+
53+ // ─────────────────────────────────────────────────────────────────────────────
54+ // FACTORY PATTERN: Full type inference for parent globals!
55+ // The factory receives a builder that already has parent globals typed.
56+ // ─────────────────────────────────────────────────────────────────────────────
57+ . command (
58+ 'remote' ,
59+ ( remote ) =>
60+ remote
61+ . command (
62+ 'add' ,
63+ pos . positionals (
64+ pos . string ( { name : 'name' , required : true } ) ,
65+ pos . string ( { name : 'url' , required : true } ) ,
66+ ) ,
67+ ( { positionals, values } ) => {
68+ const [ name , url ] = positionals ;
69+ if ( remotes . has ( name ) ) {
70+ console . error ( `Remote '${ name } ' already exists` ) ;
71+ process . exit ( 1 ) ;
72+ }
73+ remotes . set ( name , url ) ;
74+ // values.verbose is fully typed! (from parent globals)
75+ if ( values . verbose ) {
76+ console . log ( `Added remote '${ name } ' with URL: ${ url } ` ) ;
77+ } else {
78+ console . log ( `Added remote '${ name } '` ) ;
79+ }
80+ } ,
81+ 'Add a remote' ,
82+ )
83+ . command (
84+ 'remove' ,
85+ pos . positionals ( pos . string ( { name : 'name' , required : true } ) ) ,
86+ ( { positionals, values } ) => {
87+ const [ name ] = positionals ;
88+ if ( ! remotes . has ( name ) ) {
89+ console . error ( `Remote '${ name } ' not found` ) ;
90+ process . exit ( 1 ) ;
91+ }
92+ remotes . delete ( name ) ;
93+ // values.verbose is typed!
94+ if ( values . verbose ) {
95+ console . log ( `Removed remote '${ name } '` ) ;
96+ }
97+ } ,
98+ 'Remove a remote' ,
99+ )
100+ . command (
101+ 'list' ,
102+ opt . options ( { } ) ,
103+ ( { values } ) => {
104+ if ( remotes . size === 0 ) {
105+ console . log ( 'No remotes configured' ) ;
106+ return ;
107+ }
108+ for ( const [ name , url ] of remotes ) {
109+ // values.verbose is typed!
110+ if ( values . verbose ) {
111+ console . log ( `${ name } \t${ url } ` ) ;
112+ } else {
113+ console . log ( name ) ;
114+ }
115+ }
116+ } ,
117+ 'List remotes' ,
118+ )
119+ . defaultCommand ( 'list' ) ,
120+ 'Manage remotes' ,
121+ )
122+
123+ // ─────────────────────────────────────────────────────────────────────────────
124+ // Another nested command group using the factory pattern
125+ // ─────────────────────────────────────────────────────────────────────────────
126+ . command (
127+ 'config' ,
128+ ( cfg ) =>
129+ cfg
130+ . command (
131+ 'get' ,
132+ pos . positionals ( pos . string ( { name : 'key' , required : true } ) ) ,
133+ ( { positionals } ) => {
134+ const [ key ] = positionals ;
135+ const value = config . get ( key ) ;
136+ if ( value === undefined ) {
137+ console . error ( `Config key '${ key } ' not found` ) ;
138+ process . exit ( 1 ) ;
139+ }
140+ console . log ( value ) ;
141+ } ,
142+ 'Get a config value' ,
143+ )
144+ . command (
145+ 'set' ,
146+ pos . positionals (
147+ pos . string ( { name : 'key' , required : true } ) ,
148+ pos . string ( { name : 'value' , required : true } ) ,
149+ ) ,
150+ ( { positionals, values } ) => {
151+ const [ key , value ] = positionals ;
152+ config . set ( key , value ) ;
153+ // values.verbose is typed!
154+ if ( values . verbose ) {
155+ console . log ( `Set ${ key } = ${ value } ` ) ;
156+ }
157+ } ,
158+ 'Set a config value' ,
159+ ) ,
160+ 'Manage configuration' ,
161+ )
162+
163+ // ─────────────────────────────────────────────────────────────────────────────
146164 // Regular leaf commands work alongside nested ones
165+ // ─────────────────────────────────────────────────────────────────────────────
147166 . command (
148167 'status' ,
149168 opt . options ( { } ) ,
150169 ( { values } ) => {
151170 console . log ( 'On branch main' ) ;
171+ // values.verbose is typed for leaf commands too!
152172 if ( values . verbose ) {
153173 console . log ( `Remotes: ${ remotes . size } ` ) ;
154174 console . log ( `Config entries: ${ config . size } ` ) ;
0 commit comments