mcpField
C
cli-architecture-pattern

cli-architecture-pattern

github.com/loxosceles-dev开发128 次安装
代码仓库

关于 cli-architecture-pattern

这是一份将基础设施操作组织为 CLI 工具的参考技能,面向开发者。它按项目复杂度介绍单层、双层和三层设计,并提供 JavaScript/TypeScript 与 Python 示例。

主要功能

  1. 01单层、双层和三层 CLI 模式
  2. 02按项目规模选择架构
  3. 03分离参数解析与业务逻辑
  4. 04组合职责单一的核心模块
  5. 05JavaScript/TypeScript 与 Python 示例

适用场景

  1. 01整理分散的基础设施命令
  2. 02为项目选择 CLI 结构
  3. 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 命令数≤52-55+
团队规模11-33+
复杂度极低中等复杂
开发速度最快快初期较慢
可复用性无较低较高
共享逻辑极少一些大量

对于静态前端和单栈项目,从 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