关于 cli-architecture-pattern
这是一份将基础设施操作组织为 CLI 工具的参考技能,面向开发者。它按项目复杂度介绍单层、双层和三层设计,并提供 JavaScript/TypeScript 与 Python 示例。
主要功能
- 01单层、双层和三层 CLI 模式
- 02按项目规模选择架构
- 03分离参数解析与业务逻辑
- 04组合职责单一的核心模块
- 05JavaScript/TypeScript 与 Python 示例
适用场景
- 01整理分散的基础设施命令
- 02为项目选择 CLI 结构
- 03拆分共享逻辑与命令入口
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ loxosceles-dev/ dev-skills --skill cli-architecture-pattern安装后在 Claude Code 中输入/cli-architecture-pattern确认已加载。
详细介绍
CLI 架构模式
这是一种参考模式。 借鉴这种方法,并根据你的情况进行调整——不要照搬。
问题:基础设施操作散落在 shell 脚本、手动命令和部署流水线中,难以维护且容易出现不一致。
解决方案:使用结构化的 CLI 工具,提供统一的入口。
为什么采用这种模式?
优势:
- 一致性:相同的命令可在本地、CI/CD 和部署脚本中使用
- 可读性:
pnpm deploy:dev比原始 AWS CLI 命令更清晰 - 可测试性:可以独立测试 CLI 命令
- 可复用性:在不同入口之间共享逻辑
- 文档:命令通过
--help自我说明
集成点:
- 根目录下的
package.json脚本(JS/TS 项目) pyproject.toml脚本(Python 项目)- GitHub Actions 工作流
- 部署脚本(bash 等)
- 本地开发
集成示例:
// package.json
{
"scripts": {
"deploy:dev": "ts-node cli/bin/deploy.ts --env=dev",
"export-params:dev": "ts-node cli/bin/ssm-params.ts export dev frontend"
}
}
# GitHub Actions
- name: Deploy infrastructure
run: pnpm deploy:dev
- name: Export parameters
run: pnpm export-params:dev
1 层架构(适用于简单项目)
结构:
lib/
infra.ts # Single entry point: arg parsing + business logic
core/
environment-manager.ts # Bootstrap config loader (shared utility)
单个文件——在一个 switch 中包含所有命令:
// lib/infra.ts
import { EnvironmentManager } from './core/environment-manager';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { CloudFormationClient, DescribeStacksCommand } from '@aws-sdk/client-cloudformation';
import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront';
import { spawn } from 'child_process';
const [command] = process.argv.slice(2);
async function main() {
switch (command) {
case 'deploy':
await deploy();
break;
case 'publish':
await publish();
break;
case 'synth':
await run('npx', ['cdk', 'synth']);
break;
default:
console.log('Commands: deploy | publish | synth');
process.exit(1);
}
}
async function deploy() {
console.log('📦 Provisioning infrastructure...');
await run('npx', ['cdk', 'deploy', '--all', '--require-approval', 'never']);
console.log('🏗️ Publishing frontend...');
await publish();
}
async function publish() {
const stage = getStage();
const stackName = `myproject-web-${stage}`;
const region = new EnvironmentManager(stage).load().cdkDefaultRegion;
await run('pnpm', ['--filter', 'frontend', 'run', 'build']);
const bucketName = await getStackOutput(stackName, 'WebBucketName', region);
await syncToS3('frontend/out', bucketName, region);
const distId = await getStackOutput(stackName, 'CloudfrontDistributionId', region);
await invalidate(distId, region);
console.log('✅ Published');
}
// ... helper functions (run, getStackOutput, syncToS3, invalidate)
main().catch((err) => {
console.error(err.message);
process.exit(1);
});
集成:
// package.json
{
"scripts": {
"deploy:dev": "ENVIRONMENT=dev ts-node infrastructure/lib/infra.ts deploy",
"deploy:prod": "ENVIRONMENT=prod ts-node infrastructure/lib/infra.ts deploy",
"publish:dev": "ENVIRONMENT=dev ts-node infrastructure/lib/infra.ts publish",
"synth": "ENVIRONMENT=dev ts-node infrastructure/lib/infra.ts synth"
}
}
适用场景:
- 简单项目(静态前端、单栈项目)
- 命令数 ≤5
- 命令之间没有值得抽象的共享逻辑
- 优先快速实现
何时迁移到 2 层架构:
- 命令总量增长到约 200 行以上
- 需要
--verbose、--dry-run或其他共享标志 - 多个命令共享大量逻辑
2 层架构(大多数项目的推荐选择)
结构:
cli/
bin/ # Tier 1: Argument parsing
commands/ # Tier 2: Business logic + SDK calls
shared/
env-loader.ts # Simple utilities
configs.ts
第 1 层:CLI 二进制文件——仅作为入口
// bin/ssm-params.ts
import { Command } from 'commander';
import { getParameter } from '../commands/ssm-params';
const program = new Command();
program
.command('get <name>')
.action(async (name: string) => {
const value = await getParameter(name);
console.log(value);
});
program.parse();
第 2 层:命令——直接使用 SDK
// commands/ssm-params.ts
import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';
import { loadEnv } from '../shared/env-loader';
loadEnv();
const ssmClient = new SSMClient({
region: process.env.CDK_DEFAULT_REGION
});
export async function getParameter(name: string): Promise<string> {
const command = new GetParameterCommand({ Name: name });
const response = await ssmClient.send(command);
return response.Parameter?.Value ?? '';
}
适用场景:
- 中型项目
- 2-5 个 CLI 工具
- 操作简单直接
- 优先快速开发
3 层架构(适用于复杂项目)
采用组合而非继承——使用小型、专注的模块,由命令将它们组合起来。
结构:
cli/
bin/ # Tier 1: Argument parsing + error handling ONLY
commands/ # Tier 2: Business logic, composes core modules
core/
logger.ts # Tier 3: Standalone utilities
env.ts
aws/
ssm.ts
s3.ts
第 1 层:CLI 二进制文件——仅作为入口(不包含业务逻辑)
// bin/ssm-params.ts
import { Command } from 'commander';
import { getParameter, exportParams } from '../commands/ssm-params';
const program = new Command();
program
.command('get <name>')
.option('-v, --verbose', 'verbose output')
.action(async (name, opts) => {
const value = await getParameter(name, { verbose: opts.verbose });
console.log(value);
});
program
.command('export <stage> <service>')
.action(async (stage, service) => {
await exportParams(stage, service);
});
program.parse();
第 3 层:核心模块——小巧、专注、不使用继承
每个模块只负责一件事,并导出普通函数或简单的工厂函数:
// core/logger.ts
export function createLogger(verbose: boolean) {
return {
log: (msg: string) => { if (verbose) console.log(msg); },
error: (msg: string) => console.error(msg),
};
}
// core/env.ts
export function loadEnv(stage: string): Record<string, string> {
const envPath = `.env.${stage}`;
// Load from file, fall back to process.env
return { ...parseEnvFile(envPath), ...process.env };
}
export function requireEnv(key: string): string {
const value = process.env[key];
if (!value) throw new Error(`${key} is required`);
return value;
}
// core/aws/ssm.ts
import { SSMClient, GetParameterCommand, GetParametersByPathCommand } from '@aws-sdk/client-ssm';
export function createSSMClient(region: string) {
const client = new SSMClient({ region });
return {
async getParameter(name: string): Promise<string> {
const res = await client.send(new GetParameterCommand({ Name: name }));
return res.Parameter?.Value ?? '';
},
async getParametersByPath(path: string): Promise<Record<string, string>> {
const res = await client.send(new GetParametersByPathCommand({ Path: path }));
const params: Record<string, string> = {};
for (const p of res.Parameters ?? []) {
if (p.Name && p.Value) params[p.Name] = p.Value;
}
return params;
},
};
}
第 2 层:命令——组合核心模块,包含所有业务逻辑
// commands/ssm-params.ts
import { createLogger } from '../core/logger';
import { requireEnv } from '../core/env';
import { createSSMClient } from '../core/aws/ssm';
export async function getParameter(name: string, opts: { verbose?: boolean } = {}) {
const log = createLogger(opts.verbose ?? false);
const region = requireEnv('AWS_REGION');
const ssm = createSSMClient(region);
log.log(`Fetching parameter: ${name}`);
return ssm.getParameter(name);
}
export async function exportParams(stage: string, service: string) {
const region = requireEnv('AWS_REGION');
const projectId = requireEnv('PROJECT_ID');
const ssm = createSSMClient(region);
const params = await ssm.getParametersByPath(`/${projectId}/${stage}/${service}/`);
for (const [key, value] of Object.entries(params)) {
const envKey = key.split('/').pop();
console.log(`${envKey}=${value}`);
}
}
为什么选择组合而非继承:
- 每个模块都可以独立测试——无需模拟基类
- 不会出现由父类带来的隐式行为
- 易于替换实现(例如,使用不同的云服务提供商)
- 命令明确声明自身需求——不会从基类隐式调用
this.log()
适用场景:
- 拥有 5 个以上 CLI 工具的大型项目
- 多个命令共享核心模块(SSM、S3、日志记录)
- 需要统一模式的团队环境
决策指南
| 因素 | 1 层 | 2 层 | 3 层 |
|---|---|---|---|
| 项目规模 | 简单 | 中型 | 大型 |
| CLI 命令数 | ≤5 | 2-5 | 5+ |
| 团队规模 | 1 | 1-3 | 3+ |
| 复杂度 | 极低 | 中等 | 复杂 |
| 开发速度 | 最快 | 快 | 初期较慢 |
| 可复用性 | 无 | 较低 | 较高 |
| 共享逻辑 | 极少 | 一些 | 大量 |
对于静态前端和单栈项目,从 1 层架构开始。 当命令超过约 200 行或共享大量逻辑时,迁移到 2 层架构。 在以下情况下,迁移到 3 层架构:
- 拥有 5 个以上 CLI 工具
- 命令之间存在重复逻辑
- 团队需要统一的模式
- 抽象能够带来明确的价值
特定语言的集成方式
JavaScript/TypeScript——package.json:
{
"scripts": {
"deploy:dev": "ts-node cli/bin/deploy.ts --env=dev",
"params:get": "ts-node cli/bin/ssm-params.ts get"
}
}
Python——pyproject.toml:
[tool.poetry.scripts]
deploy-dev = "cli.bin.deploy:main"
params-get = "cli.bin.ssm_params:get_parameter"
Makefile——通用:
deploy-dev:
python cli/bin/deploy.py --env=dev
params-get:
python cli/bin/ssm_params.py get $(NAME)
相关模式
渐进式改进
如果开发者纠正了本技能本应避免的行为,请提出一项具体的技能修订建议,以防今后再次出现同样的问题。
评论
支持 Markdown