11#!/usr/bin/env node
22// index.ts — FBA CLI 入口:Commander 命令路由
3- import { readFileSync } from 'fs'
3+ import { readFileSync , existsSync } from 'fs'
44import { dirname , resolve } from 'path'
55import { fileURLToPath } from 'url'
66import { Command } from 'commander'
77import chalk from 'chalk'
8- import { readGlobalConfig } from './lib/config.js'
8+ import { readGlobalConfig , readProjectConfig , resolveProjectDir } from './lib/config.js'
99import { initI18nFromConfig , t } from './lib/i18n.js'
1010
1111const currentDir = dirname ( fileURLToPath ( import . meta. url ) )
@@ -55,10 +55,10 @@ program
5555 await addAction ( )
5656 } )
5757
58- // ─── dev ───
59- program
58+ // ─── dev (command group) ───
59+ const devCmd = program
6060 . command ( 'dev' )
61- . description ( t ( 'cmdDev ' ) )
61+ . description ( t ( 'cmdDevGroup ' ) )
6262 . option ( '--host <host>' , t ( 'optHost' ) , '127.0.0.1' )
6363 . option ( '--port <port>' , t ( 'optPort' ) )
6464 . option ( '--no-reload' , t ( 'optNoReload' ) )
@@ -68,9 +68,8 @@ program
6868 await devAction ( { ...options , project : program . opts ( ) . project } )
6969 } )
7070
71- // ─── dev:web ───
72- program
73- . command ( 'dev:web' )
71+ devCmd
72+ . command ( 'web' )
7473 . description ( t ( 'cmdDevWeb' ) )
7574 . option ( '--host <host>' , t ( 'optHost' ) )
7675 . option ( '--port <port>' , t ( 'optPort' ) )
@@ -79,15 +78,43 @@ program
7978 await devWebAction ( { ...options , project : program . opts ( ) . project } )
8079 } )
8180
82- // ─── dev:celery ───
83- program
84- . command ( 'dev:celery <subcommand>' )
81+ devCmd
82+ . command ( 'celery <subcommand>' )
8583 . description ( t ( 'cmdDevCelery' ) )
8684 . action ( async ( subcommand ) => {
8785 const { devCeleryAction } = await import ( './commands/dev.js' )
8886 await devCeleryAction ( subcommand , { project : program . opts ( ) . project } )
8987 } )
9088
89+ // Dynamic dev subcommands from project .fba.json
90+ {
91+ const preProject = ( ( ) => {
92+ const args = process . argv . slice ( 2 )
93+ for ( let i = 0 ; i < args . length ; i ++ ) {
94+ if ( ( args [ i ] === '-p' || args [ i ] === '--project' ) && args [ i + 1 ] ) return args [ i + 1 ]
95+ if ( args [ i ] ?. startsWith ( '--project=' ) ) return args [ i ] ! . slice ( '--project=' . length )
96+ }
97+ return undefined
98+ } ) ( )
99+ const projectDir = resolveProjectDir ( preProject )
100+ if ( projectDir && existsSync ( projectDir ) ) {
101+ const projectConfig = readProjectConfig ( projectDir )
102+ if ( projectConfig . devs ) {
103+ const builtins = new Set ( [ 'web' , 'celery' , 'help' ] )
104+ for ( const [ name , entry ] of Object . entries ( projectConfig . devs ) ) {
105+ if ( builtins . has ( name ) ) continue
106+ devCmd
107+ . command ( name )
108+ . description ( entry . desc ?? entry . cmd )
109+ . action ( async ( ) => {
110+ const { devCustomAction } = await import ( './commands/dev.js' )
111+ await devCustomAction ( name , entry , { project : program . opts ( ) . project } )
112+ } )
113+ }
114+ }
115+ }
116+ }
117+
91118// ─── plugin ───
92119const pluginCmd = program
93120 . command ( 'plugin' )
0 commit comments