From 35c351e7165af5ed7b1d1115b1505b979552739d Mon Sep 17 00:00:00 2001 From: yangzhaohan Date: Mon, 22 Jun 2026 16:46:44 +0800 Subject: [PATCH] refactor(cli): migrate to commander framework and simplify config loading Replace hand-rolled CLI argument parsing with Commander for command registration and option handling. Streamline config resolution by removing the BrickConfig type and accepting a plain source string with BRICK_SOURCE env var support. - Replace manual CLI dispatch with Commander-based command tree - Simplify resolveConfig/resolveSource to a single resolveSource function - Remove BrickConfig interface in favor of direct source string - Move isRemote check into index-loader as a local utility - Update loadIndex signature to accept source string directly --- .gitea/workflows/publish.yml | 7 ---- package.json | 1 + src/commands/list.ts | 69 ++++++++++++++++++------------------ src/index.ts | 44 +++++------------------ src/lib/config.ts | 30 +++------------- src/lib/index-loader.ts | 19 +++++----- src/types/index.ts | 11 ------ 7 files changed, 59 insertions(+), 122 deletions(-) diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml index 16efc01..e09e0d7 100644 --- a/.gitea/workflows/publish.yml +++ b/.gitea/workflows/publish.yml @@ -12,15 +12,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 18 - - run: npm ci - - run: npm run build - - name: Publish to Gitea npm registry run: | echo "//gitea.synoth.com/api/packages/synoth/npm/:_authToken=${{ secrets.GITEA_TOKEN }}" > ~/.npmrc diff --git a/package.json b/package.json index b8dd808..eb8884f 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "license": "MIT", "dependencies": { + "commander": "^15.0.0", "js-yaml": "^5.0.0" } } diff --git a/src/commands/list.ts b/src/commands/list.ts index 284567a..288fe76 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -1,41 +1,42 @@ -import { resolveConfig } from "../lib/config.js"; +import { Command } from "commander"; +import { resolveSource } from "../lib/config.js"; import { loadIndex } from "../lib/index-loader.js"; import { formatTable } from "../lib/table.js"; -import type { Command } from "../types/index.js"; -export const listCommand: Command = { - name: "list", - description: "显示可用积木列表", +export function registerList(program: Command): void { + program + .command("list") + .description("显示可用积木列表") + .option("--source ", "index source path or URL") + .action(async (options) => { + const source = resolveSource(options.source); + const index = await loadIndex(source); - run: async (args: string[]) => { - const config = resolveConfig(args); - const index = await loadIndex(config); + if (index.blocks.length === 0) { + console.log("(no blocks found)"); + return; + } - if (index.blocks.length === 0) { - console.log("(no blocks found)"); - return; - } + const columns = [ + { key: "name", header: "Name", minWidth: 20, maxWidth: 30 }, + { key: "module", header: "Module", minWidth: 8, maxWidth: 12 }, + { key: "variants", header: "Variants", minWidth: 15, maxWidth: 45 }, + { key: "dependsOn", header: "Dependencies", minWidth: 15, maxWidth: 30 }, + { key: "description", header: "Description", minWidth: 15, maxWidth: 55 }, + ]; - const columns = [ - { key: "name", header: "Name", minWidth: 20, maxWidth: 30 }, - { key: "module", header: "Module", minWidth: 8, maxWidth: 12 }, - { key: "variants", header: "Variants", minWidth: 15, maxWidth: 45 }, - { key: "dependsOn", header: "Dependencies", minWidth: 15, maxWidth: 30 }, - { key: "description", header: "Description", minWidth: 15, maxWidth: 55 }, - ]; + const rows = index.blocks.map((block) => ({ + name: block.name, + module: block.module, + variants: + block.variants.length > 0 + ? block.variants.map((v) => v.label).join(", ") + : "-", + dependsOn: + block.dependsOn.length > 0 ? block.dependsOn.join(", ") : "-", + description: block.description, + })); - const rows = index.blocks.map((block) => ({ - name: block.name, - module: block.module, - variants: - block.variants.length > 0 - ? block.variants.map((v) => v.label).join(", ") - : "-", - dependsOn: - block.dependsOn.length > 0 ? block.dependsOn.join(", ") : "-", - description: block.description, - })); - - console.log(formatTable(columns, rows)); - }, -}; + console.log(formatTable(columns, rows)); + }); +} diff --git a/src/index.ts b/src/index.ts index b320102..9db2718 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,42 +1,14 @@ #!/usr/bin/env node -import { listCommand } from "./commands/list.js"; -import type { Command } from "./types/index.js"; +import { Command } from "commander"; +import { registerList } from "./commands/list.js"; -const USAGE = ` -Brick CLI — 源码级 Spring Boot 脚手架 +const program = new Command(); -Usage: - brick create 创建新项目 - brick add [:variant] 向已有项目增量添加积木 - brick list 显示可用积木列表 +program + .name("brick") + .description("Brick CLI — 源码级 Spring Boot 脚手架"); -Examples: - brick create my-app - brick add cache:redisson - brick list -`; +registerList(program); -const commands = new Map(); -commands.set(listCommand.name, listCommand); - -async function main(): Promise { - const args = process.argv.slice(2); - const commandName = args[0]; - - if (!commandName) { - console.log(USAGE); - process.exit(0); - } - - const command = commands.get(commandName); - if (!command) { - console.error(`Unknown command: ${commandName}`); - console.log(USAGE); - process.exit(1); - } - - await command.run(args.slice(1)); -} - -main(); +program.parse(); diff --git a/src/lib/config.ts b/src/lib/config.ts index 5e5a25e..7f94b0f 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,30 +1,8 @@ -import type { BrickConfig } from "../types/index.js"; - const DEFAULT_SOURCE = "https://gitea.synoth.com/synoth/brick-index/raw/branch/master/blocks.yaml"; -function parseSourceFromArgs(args: string[]): string | undefined { - const idx = args.indexOf("--source"); - if (idx !== -1 && idx + 1 < args.length) { - return args[idx + 1]; - } - return undefined; -} - -function isRemoteSource(source: string): boolean { - return source.startsWith("http://") || source.startsWith("https://"); -} - -export function resolveConfig(args: string[]): BrickConfig { - const cliSource = parseSourceFromArgs(args); - if (cliSource) { - return { source: cliSource, isRemote: isRemoteSource(cliSource) }; - } - - const envSource = process.env.BRICK_SOURCE; - if (envSource) { - return { source: envSource, isRemote: isRemoteSource(envSource) }; - } - - return { source: DEFAULT_SOURCE, isRemote: true }; +export function resolveSource(cliSource?: string): string { + if (cliSource) return cliSource; + if (process.env.BRICK_SOURCE) return process.env.BRICK_SOURCE; + return DEFAULT_SOURCE; } diff --git a/src/lib/index-loader.ts b/src/lib/index-loader.ts index 93cfd3e..fe7894b 100644 --- a/src/lib/index-loader.ts +++ b/src/lib/index-loader.ts @@ -1,24 +1,27 @@ import { readFile } from "node:fs/promises"; import { load } from "js-yaml"; -import type { BlockIndex, BrickConfig } from "../types/index.js"; +import type { BlockIndex } from "../types/index.js"; -export async function loadIndex(config: BrickConfig): Promise { +const isRemote = (source: string): boolean => + source.startsWith("http://") || source.startsWith("https://"); + +export async function loadIndex(source: string): Promise { let raw: string; - if (config.isRemote) { + if (isRemote(source)) { let response: Response; try { - response = await fetch(config.source); + response = await fetch(source); } catch { process.stderr.write( - `Error: Failed to fetch index from "${config.source}"\n` + `Error: Failed to fetch index from "${source}"\n` ); process.exit(1); } if (!response.ok) { process.stderr.write( - `Error: Failed to fetch index from "${config.source}" (HTTP ${response.status})\n` + `Error: Failed to fetch index from "${source}" (HTTP ${response.status})\n` ); process.exit(1); } @@ -26,12 +29,12 @@ export async function loadIndex(config: BrickConfig): Promise { raw = await response.text(); } else { try { - raw = await readFile(config.source, "utf-8"); + raw = await readFile(source, "utf-8"); } catch (err: unknown) { const code = (err as NodeJS.ErrnoException)?.code; if (code === "ENOENT") { process.stderr.write( - `Error: Index file not found at "${config.source}"\n` + `Error: Index file not found at "${source}"\n` ); } else { process.stderr.write(`Error: ${(err as Error).message}\n`); diff --git a/src/types/index.ts b/src/types/index.ts index fde5e80..7906fc9 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -17,14 +17,3 @@ export interface Block { export interface BlockIndex { blocks: Block[]; } - -export interface BrickConfig { - source: string; - isRemote: boolean; -} - -export interface Command { - name: string; - description: string; - run: (args: string[]) => Promise; -}