사용자 지정 CLI 모듈
자체 CLI 모듈을 만들어 사용자 지정 명령으로 Backstage CLI를 확장할 수 있습니다.
출처: 문서
본문
자체 CLI 모듈을 만들어 사용자 지정 명령으로 Backstage CLI를 확장할 수 있습니다. CLI 모듈은 @backstage/cli-node의 createCliModule API를 사용해 하나 이상의 명령을 등록하는 패키지입니다. 프로젝트에 의존성으로 설치되면 CLI가 자동으로 이를 발견하고 로드합니다.
새 모듈 스캐폴딩 (Scaffolding a new module)
모듈을 스캐폴딩하려면 내장 템플릿을 사용합니다.
yarn new
대화형 메뉴에서 cli-module 템플릿을 선택합니다. 그러면 샘플 명령, 독립 실행형 bin 스크립트, 필요한 모든 구성을 포함한 올바른 구조의 새 패키지가 생성됩니다.
모듈 구조 (Module structure)
CLI 모듈 패키지는 다음과 같은 구조를 가집니다.
packages/cli-module-example/ bin/ backstage-cli-module-example # Standalone bin script src/ commands/ example.ts # Command implementation index.ts # Module definition package.json
package.json
package.json은 backstage.role을 "cli-module"로 설정해야 합니다. 이것이 CLI가 의존성 스캔 중에 패키지를 모듈로 식별하는 방법입니다.
package.json
{ "name": "@mycompany/cli-module-example", "version": "0.1.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, "backstage": { "role": "cli-module" }, "bin": "bin/backstage-cli-module-example", "files": ["dist", "bin"], "dependencies": { "@backstage/cli-common": "...", "@backstage/cli-node": "...", "cleye": "..." }, "devDependencies": { "@backstage/cli": "..." }}
모듈 정의 (Module definition)
모듈 진입점은 createCliModule을 사용해 명령을 등록합니다.
src/index.ts
import { createCliModule } from '@backstage/cli-node';import packageJson from '../package.json';export default createCliModule({ packageJson, init: async reg => { reg.addCommand({ path: ['example'], description: 'An example command', execute: { loader: () => import('./commands/example') }, }); },});
createCliModule API
createCliModule 함수는 두 필드를 가진 옵션 객체를 받아들입니다.
packageJson— 최소한name필드를 가진 객체로, 일반적으로 가져온package.json입니다. 명령 충돌이 발생할 때 오류 메시지에서 이름이 사용됩니다.init— 레지스트리 객체를 받는 비동기 콜백입니다. 레지스트리의addCommand메서드를 사용해 명령을 등록합니다.
init 콜백은 명령이 실행될 때가 아니라 모듈이 로드될 때 실행됩니다. 가벼운 상태로 유지하고, 무거운 의존성을 가진 명령에는 지연 로더(deferred loader) 패턴을 사용하세요.
명령 정의 (Defining commands)
각 명령은 reg.addCommand에 전달되는 CliCommand 객체로 정의됩니다.
reg.addCommand({ path: ['my-tool', 'run'], description: 'Run the tool', execute: async ({ args, info }) => { // Command implementation },});
명령 경로 (Command path)
path 배열은 명령이 호출되는 방식을 정의합니다. 각 요소는 명령 계층의 한 수준이 됩니다.
['info']는backstage-cli info를 등록합니다.['repo', 'test']는backstage-cli repo test를 등록합니다.['actions', 'sources', 'add']는backstage-cli actions sources add를 등록합니다.
경로의 중간 노드는 자동으로 생성되며 도움말 출력에서 명령 그룹으로 나타납니다.
설명 (Description)
도움말 출력에서 명령 이름 옆에 표시되는 짧은 문자열입니다.
Deprecated 및 experimental 플래그
명령은 deprecated: true 또는 experimental: true로 표시할 수 있습니다. 이 명령들은 --help 출력에서 숨겨지지만 여전히 사용할 수 있습니다.
실행 함수 (Execute function)
execute 필드는 두 가지 패턴을 지원합니다.
직접 실행(Direct execution) — 인라인 비동기 함수입니다.
reg.addCommand({ path: ['greet'], description: 'Print a greeting', execute: async ({ args, info }) => { console.log('Hello!'); },});
지연 로딩(Deferred loading) — 명령 구현을 동적으로 가져오는 로더입니다. 명령이 실제로 호출될 때까지 무거운 의존성을 로드하지 않으므로 권장되는 패턴입니다.
reg.addCommand({ path: ['greet'], description: 'Print a greeting', execute: { loader: () => import('./commands/greet') },});
지연 로더 패턴을 사용할 때 명령 파일은 기본 내보내기로 execute 함수를 내보내야 합니다.
src/commands/greet.ts
import type { CliCommandContext } from '@backstage/cli-node';export default async ({ args, info }: CliCommandContext) => { console.log('Hello!');};
명령 컨텍스트 (Command context)
execute 함수는 두 필드를 가진 CliCommandContext를 받습니다.
args— 명령 경로가 해석된 후 남은 명령줄 인수의 배열입니다. 예를 들어 사용자가backstage-cli greet --name World를 실행하면args배열은['--name', 'World']가 됩니다.info—usage(도움말에 표시된 전체 명령 경로, 예:"backstage-cli greet")와name(명령 이름, 예:"greet")을 가진 객체입니다.
플래그 구문 분석 (Parsing flags)
명령은 일반적으로 cleye를 사용해 args 배열에서 플래그를 구문 분석합니다. 이는 모든 내장 CLI 모듈이 사용하는 것과 같은 라이브러리입니다.
src/commands/greet.ts
import { cli } from 'cleye';import type { CliCommandContext } from '@backstage/cli-node';export default async ({ args, info }: CliCommandContext) => { const { flags } = cli( { name: info.usage, flags: { name: { type: String, description: 'Name to greet', default: 'World', }, loud: { type: Boolean, description: 'Shout the greeting', }, }, }, undefined, args, ); const greeting = `Hello, ${flags.name}!`; console.log(flags.loud ? greeting.toUpperCase() : greeting);};
독립 실행 (Standalone execution)
각 CLI 모듈은 전체 @backstage/cli 없이 독립 실행 프로그램으로도 실행할 수 있습니다. 이는 모듈을 독립적으로 배포할 때 유용합니다. 스캐폴딩된 템플릿에는 이를 수행하는 bin 스크립트가 포함됩니다.
bin/backstage-cli-module-example
#!/usr/bin/env nodeconst path = require('node:path');/* eslint-disable-next-line no-restricted-syntax */const isLocal = require('node:fs').existsSync( path.resolve(__dirname, '../src'),);if (isLocal) { require('@backstage/cli-node/config/nodeTransform.cjs');}const { runCli } = require('@backstage/cli-node');const cliModule = require(isLocal ? '../src/index' : '..').default;const pkg = require('../package.json');runCli({ modules: [cliModule], name: pkg.name, version: pkg.version });
isLocal 확인은 bin 스크립트 옆에 src/ 디렉터리가 존재하는지 감지합니다. 개발 중 소스에서 실행할 때는 TypeScript 파일을 직접 로드할 수 있도록 Node.js 변환을 등록합니다. 게시된 패키지에서 실행할 때는 컴파일된 출력을 로드합니다.
모듈 설치 (Installing your module)
모듈이 게시되었거나 워크스페이스 패키지로 사용 가능하다면, 프로젝트 루트의 package.json에 의존성으로 추가합니다.
package.json
{ "devDependencies": { "@backstage/cli": "...", "@mycompany/cli-module-example": "..." }}
CLI는 다음 실행 시 자동으로 이를 발견합니다. 사용자 지정 명령이 기본 명령과 함께 --help 출력에 나타납니다.
모듈이 @backstage/cli-defaults의 기본 모듈과 충돌하는 명령 경로를 등록하면, 모듈이 우선하며 충돌하는 기본 모듈은 조용히 건너뜁니다. 충돌 해결에 대한 자세한 내용은 CLI Modules를 참조하세요.