Skip to content

Commit 0486efa

Browse files
committed
重构 CLI 命令注册,拆分为多个模块以提高可维护性
1 parent 0386930 commit 0486efa

File tree

7 files changed

+314
-317
lines changed

7 files changed

+314
-317
lines changed

packages/tools/cli/src/index.ts

Lines changed: 13 additions & 317 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,10 @@
11
import { Command } from 'commander';
2-
import chalk from 'chalk';
3-
import { generateTypes } from './commands/generate';
4-
import { startRepl } from './commands/repl';
5-
import { serve } from './commands/serve';
6-
import { dev } from './commands/dev';
7-
import { start } from './commands/start';
8-
import { build } from './commands/build';
9-
import { test } from './commands/test';
10-
import { lint } from './commands/lint';
11-
import { format } from './commands/format';
12-
import { initProject } from './commands/init';
13-
import { newMetadata } from './commands/new';
14-
import { i18nExtract, i18nInit, i18nValidate } from './commands/i18n';
15-
import { migrate, migrateCreate, migrateStatus } from './commands/migrate';
16-
import { aiGenerate, aiValidate, aiChat, aiConversational } from './commands/ai';
17-
import { syncDatabase } from './commands/sync';
18-
import { doctorCommand, validateCommand } from './commands/doctor';
19-
import { dbPushCommand } from './commands/database-push';
2+
import { registerLifecycleCommands } from './register/lifecycle';
3+
import { registerScaffoldCommands } from './register/scaffold';
4+
import { registerDatabaseCommands } from './register/database';
5+
import { registerAiCommands } from './register/ai';
6+
import { registerToolsCommands } from './register/tools';
7+
import { registerI18nCommands } from './register/i18n';
208

219
const program = new Command();
2210

@@ -25,304 +13,12 @@ program
2513
.description('ObjectQL CLI tool - The ObjectStack AI Protocol Interface')
2614
.version('1.5.0');
2715

28-
// ==========================================
29-
// 1. Lifecycle Commands
30-
// ==========================================
31-
32-
program
33-
.command('init')
34-
.description('Create a new ObjectQL project (Deprecated)')
35-
.action(async () => {
36-
console.log(chalk.red('\n⚠️ DEPRECATED: The "objectql init" command has been removed.'));
37-
console.log(chalk.white('Please use the standard initializer instead:\n'));
38-
console.log(chalk.cyan(' npm create @objectql@latest <my-app>'));
39-
console.log('');
40-
process.exit(1);
41-
});
42-
43-
program
44-
.command('dev')
45-
.description('Start development server with hot reload and type generation')
46-
.option('-p, --port <number>', 'Port to listen on', '3000')
47-
.option('-d, --dir <path>', 'Directory containing schema', '.')
48-
.option('-c, --config <path>', 'Path to objectql.config.ts/js')
49-
.option('--modules <items>', 'Comma-separated list of modules to load')
50-
.option('--no-watch', 'Disable file watching')
51-
.action(async (options) => {
52-
await dev({
53-
port: parseInt(options.port),
54-
dir: options.dir,
55-
config: options.config,
56-
modules: options.modules,
57-
watch: options.watch
58-
});
59-
});
60-
61-
program
62-
.command('build')
63-
.description('Build project for production')
64-
.option('-d, --dir <path>', 'Source directory', '.')
65-
.option('-o, --output <path>', 'Output directory', './dist')
66-
.option('--no-types', 'Skip TypeScript type generation')
67-
.option('--no-validate', 'Skip metadata validation')
68-
.action(async (options) => {
69-
await build({
70-
dir: options.dir,
71-
output: options.output,
72-
types: options.types,
73-
validate: options.validate
74-
});
75-
});
76-
77-
program
78-
.command('start')
79-
.description('Start production server')
80-
.option('-p, --port <number>', 'Port to listen on', '3000')
81-
.option('-d, --dir <path>', 'Directory containing schema', '.')
82-
.option('-c, --config <path>', 'Path to objectql.config.ts/js')
83-
.action(async (options) => {
84-
await start({
85-
port: parseInt(options.port),
86-
dir: options.dir,
87-
config: options.config
88-
});
89-
});
90-
91-
// ==========================================
92-
// 2. Scaffolding & Generators
93-
// ==========================================
94-
95-
program
96-
.command('generate <schematic> <name>')
97-
.alias('g')
98-
.description('Generate a new metadata element (object, action, etc.)')
99-
.option('-d, --dir <path>', 'Output directory', '.')
100-
.action(async (schematic, name, options) => {
101-
try {
102-
// Maps to existing newMetadata which accepts (type, name, dir)
103-
await newMetadata({ type: schematic, name, dir: options.dir });
104-
} catch (error) {
105-
console.error(error);
106-
process.exit(1);
107-
}
108-
});
109-
110-
program
111-
.command('types')
112-
.description('Force regenerate TypeScript definitions')
113-
.option('-s, --source <path>', 'Source directory', '.')
114-
.option('-o, --output <path>', 'Output directory', './src/generated')
115-
.action(async (options) => {
116-
try {
117-
await generateTypes(options.source, options.output);
118-
} catch (error) {
119-
console.error(error);
120-
process.exit(1);
121-
}
122-
});
123-
124-
// ==========================================
125-
// 3. Database Operations
126-
// ==========================================
127-
128-
const dbCmd = program.command('db').description('Database operations');
129-
130-
dbCmd
131-
.command('push')
132-
.description('Push metadata schema changes to the database')
133-
.option('--force', 'Bypass safety checks')
134-
.action(async (options) => {
135-
try {
136-
await dbPushCommand(options);
137-
} catch (error) {
138-
console.error(error);
139-
process.exit(1);
140-
}
141-
});
142-
143-
dbCmd
144-
.command('pull')
145-
.description('Introspect database and generate metadata (Reverse Engineering)')
146-
.option('-c, --config <path>', 'Path to objectql.config.ts/js')
147-
.option('-o, --output <path>', 'Output directory', './src/objects')
148-
.option('-t, --tables <tables...>', 'Specific tables to sync')
149-
.option('-f, --force', 'Overwrite existing files')
150-
.action(async (options) => {
151-
try {
152-
// Maps to existing syncDatabase
153-
await syncDatabase(options);
154-
} catch (error) {
155-
console.error(error);
156-
process.exit(1);
157-
}
158-
});
159-
160-
// Migration commands - kept as top level or move to db:migrate?
161-
// Staying top level or db:migrate is fine. Let's keep `migrate` top level for familiarity with typeorm/prisma users or move to db?
162-
// User request: "Declarative > Imperative".
163-
// Let's alias db:migrate to migrate for now, or just keep migrate.
164-
// Standard in many tools is `migrate` or `db migrate`.
165-
// Let's keep `migrate` as top level group for explicit control.
166-
167-
const migrateCmd = program
168-
.command('migrate')
169-
.description('Manage database migrations');
170-
171-
migrateCmd
172-
.command('up') // Changed from default action to explicit 'up'
173-
.description('Run pending migrations')
174-
.option('-c, --config <path>', 'Path to objectql.config.ts/js')
175-
.option('-d, --dir <path>', 'Migrations directory', './migrations')
176-
.action(async (options) => {
177-
await migrate(options);
178-
});
179-
180-
migrateCmd
181-
.command('create <name>')
182-
.description('Create a new migration file')
183-
.option('-d, --dir <path>', 'Migrations directory', './migrations')
184-
.action(async (name, options) => {
185-
await migrateCreate({ name, dir: options.dir });
186-
});
187-
188-
migrateCmd
189-
.command('status')
190-
.description('Show migration status')
191-
.action(async (options) => {
192-
await migrateStatus(options);
193-
});
194-
195-
196-
// ==========================================
197-
// 4. AI Architect
198-
// ==========================================
199-
200-
const aiCmd = program
201-
.command('ai')
202-
.description('AI Architect capabilities');
203-
204-
aiCmd
205-
.command('chat')
206-
.description('Interactive architecture chat')
207-
.option('-p, --prompt <text>', 'Initial prompt')
208-
.action(async (options) => {
209-
await aiChat({ initialPrompt: options.prompt });
210-
});
211-
212-
aiCmd
213-
.command('run <prompt>') // Changed from generate to run/exec for simple "Do this"
214-
.description('Execute an AI modification on the project (e.g. "Add a blog module")')
215-
.option('-o, --output <path>', 'Output directory', './src')
216-
.action(async (prompt, options) => {
217-
// Maps to simple conversational or generate
218-
// Let's map to aiGenerate but pass description
219-
await aiGenerate({ description: prompt, output: options.output, type: 'custom' });
220-
});
221-
222-
// ==========================================
223-
// 5. Diagnostics & Tools
224-
// ==========================================
225-
226-
program
227-
.command('doctor')
228-
.description('Check environment and configuration health')
229-
.action(async () => {
230-
await doctorCommand();
231-
});
232-
233-
program
234-
.command('validate')
235-
.description('Validate all metadata files')
236-
.option('-d, --dir <path>', 'Directory to validate', '.')
237-
.action(async (options) => {
238-
await validateCommand(options);
239-
});
240-
241-
program
242-
.command('repl')
243-
.description('Start interactive REPL')
244-
.option('-c, --config <path>', 'Path to objectql.config.ts')
245-
.action(async (options) => {
246-
await startRepl(options.config);
247-
});
248-
249-
program
250-
.command('test')
251-
.description('Run tests')
252-
.action(async (options) => {
253-
await test(options);
254-
});
255-
256-
program
257-
.command('lint')
258-
.description('Lint metadata files')
259-
.action(async (options) => {
260-
await lint(options);
261-
});
262-
263-
program
264-
.command('format')
265-
.description('Format metadata files')
266-
.action(async (options) => {
267-
await format(options);
268-
});
269-
270-
// ==========================================
271-
// 6. I18n
272-
// ==========================================
273-
274-
const i18nCmd = program
275-
.command('i18n')
276-
.description('Internationalization commands');
277-
278-
i18nCmd
279-
.command('extract')
280-
.description('Extract translatable strings from metadata files')
281-
.option('-s, --source <path>', 'Source directory', '.')
282-
.option('-o, --output <path>', 'Output directory', './src/i18n')
283-
.option('-l, --lang <lang>', 'Language code', 'en')
284-
.action(async (options) => {
285-
try {
286-
await i18nExtract(options);
287-
} catch (error) {
288-
console.error(error);
289-
process.exit(1);
290-
}
291-
});
292-
293-
i18nCmd
294-
.command('init <lang>')
295-
.description('Initialize i18n for a new language')
296-
.option('-b, --base-dir <path>', 'Base i18n directory', './src/i18n')
297-
.action(async (lang, options) => {
298-
try {
299-
await i18nInit({ lang, baseDir: options.baseDir });
300-
} catch (error) {
301-
console.error(error);
302-
process.exit(1);
303-
}
304-
});
305-
306-
i18nCmd
307-
.command('validate <lang>')
308-
.description('Validate translation completeness')
309-
.option('-b, --base-dir <path>', 'Base i18n directory', './src/i18n')
310-
.option('--base-lang <lang>', 'Base language to compare against', 'en')
311-
.action(async (lang, options) => {
312-
try {
313-
await i18nValidate({ lang, baseDir: options.baseDir, baseLang: options.baseLang });
314-
} catch (error) {
315-
console.error(error);
316-
process.exit(1);
317-
}
318-
});
319-
320-
// Backward compatibility (Hidden or Deprecated)
321-
program
322-
.command('new <type> <name>', { hidden: true })
323-
.action(async (type, name, options) => {
324-
console.warn(chalk.yellow('Deprecated: Use "objectql generate" instead.'));
325-
await newMetadata({ type, name, dir: options.dir });
326-
});
16+
// Register all command groups
17+
registerLifecycleCommands(program);
18+
registerScaffoldCommands(program);
19+
registerDatabaseCommands(program);
20+
registerAiCommands(program);
21+
registerToolsCommands(program);
22+
registerI18nCommands(program);
32723

32824
program.parse(process.argv);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Command } from 'commander';
2+
import { aiGenerate, aiChat } from '../commands/ai';
3+
4+
export function registerAiCommands(program: Command) {
5+
const aiCmd = program
6+
.command('ai')
7+
.description('AI Architect capabilities');
8+
9+
aiCmd
10+
.command('chat')
11+
.description('Interactive architecture chat')
12+
.option('-p, --prompt <text>', 'Initial prompt')
13+
.action(async (options) => {
14+
await aiChat({ initialPrompt: options.prompt });
15+
});
16+
17+
aiCmd
18+
.command('run <prompt>')
19+
.description('Execute an AI modification on the project (e.g. "Add a blog module")')
20+
.option('-o, --output <path>', 'Output directory', './src')
21+
.action(async (prompt, options) => {
22+
await aiGenerate({ description: prompt, output: options.output, type: 'custom' });
23+
});
24+
}

0 commit comments

Comments
 (0)