LabHub
배우기 러닝패스 코스

CBA — Backstage Associate

Source Changed, but the Local Backend Still Answers the Same

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

Backstage 앱 저장소와 같은 모양의 Yarn 4 워크스페이스를 직접 세우고, 의존성 설치와 잠금 파일, TypeScript 컴파일, 빌드 산출물, 로컬 실행으로 이어지는 개발 흐름을 한 바퀴 돌립니다. 채점기는 파일만 보지 않고 yarn·tsc·node 를 다시 실행한 결과로 판정합니다.

왜 중요한가

"내 PC 에서는 되는데 CI 에서 설치가 실패한다", "타입 오류가 났는데 dist 는 왜 생겼지", "소스를 고쳤는데 응답이 그대로다" — Backstage 를 개발하며 가장 자주 부딪히는 것은 플러그인 코드보다 이 흐름입니다. 잠금 파일이 무엇을 고정하고, tsc 가 무엇을 잡고 무엇을 놓치며, 백엔드가 실제로 어떤 파일을 실행하는지 알아야 원인을 빨리 찾습니다.

처음 VM 이 뜨는 데 4분쯤 걸리고, 2단계 설치는 인터넷에서 수십 초 걸립니다. 실제 프로젝트의 backstage-cli(yarn start·yarn build)와 프런트엔드 앱(packages/app)은 설치 용량과 빌드 시간이 커서 쓰지 않고, 같은 일을 yarn·tsc·node 로 손으로 합니다. 이 VM 에는 docker 가 없어 이미지 빌드는 하지 않습니다.

단계

  1. 루트·backend·플러그인 세 package.json 과 .yarnrc.yml 로 워크스페이스를 세웁니다.
  2. yarn install 로 잠금 파일과 워크스페이스 링크를 만듭니다.
  3. 잠금 파일과 어긋난 package.json 이 --immutable 에서 막히는 것을 기록하고 되돌립니다.
  4. 잘못된 정책 값을 tsc 가 잡는 것과, 그래도 JS 가 만들어지는 것을 기록합니다.
  5. noEmitOnError 를 켜고 고쳐서 빌드합니다.
  6. 플러그인을 붙인 백엔드를 로컬에서 띄웁니다.
  7. 소스만 고치고 재시작하면 안 바뀌고, 빌드해야 바뀌는 것을 확인합니다.
  8. 지금 저장소에서 잰 값으로 개발 흐름 점검표를 씁니다.

참고

앱 저장소와 같은 모양의 워크스페이스를 세운다

/usr/local/cba-dev 에 Yarn 워크스페이스를 만드세요. 루트 package.jsonprivate: true, packageManager: "yarn@4.9.2", workspaces: ["packages/*", "plugins/*"]. 루트 .yarnrc.ymlnodeLinker: node-modules, enableTelemetry: false, enableGlobalCache: false, enableMirror: false, globalFolder: /usr/local/cba-dev/.yarn-global. packages/backend/package.json(이름 backend)의 dependencies 는 @backstage/backend-defaults 0.17.8, better-sqlite3 12.4.1, @internal/plugin-hello-backend workspace:^. plugins/hello-backend/package.json(이름 @internal/plugin-hello-backend, main dist/index.js, types dist/index.d.ts, scripts build: tsc -p tsconfig.json)의 dependencies 는 @backstage/backend-plugin-api 1.10.0·express 4.22.3, devDependencies 는 typescript 5.9.3·@types/express 4.17.25. 모든 외부 버전은 범위 기호 없이 적습니다. corepack yarn workspaces list 에 세 워크스페이스가 나와야 합니다.

Backstage 앱은 packages/app(프런트엔드)·packages/backend 와 plugins/* 로 나뉜 Yarn 워크스페이스입니다. 이 VM 은 앱 번들을 굽지 않으니 backend 와 백엔드 플러그인만 둡니다. corepack 이 packageManager 필드를 보고 그 버전의 yarn 을 받아 씁니다. 루트 디스크가 작으니 셸에서 먼저 export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 COREPACK_HOME=/usr/local/cba-dev/.corepack TMPDIR=/usr/local/cba-dev/.tmp (그리고 mkdir -p /usr/local/cba-dev/.tmp)를 해 두세요. 전역 캐시·미러를 끄지 않으면 홈 디렉터리로 복사하다 공간이 부족해집니다.

한 번의 설치로 잠금 파일과 워크스페이스 링크가 생긴다

/usr/local/cba-dev 에서 corepack yarn install 을 실행하세요. 루트에 yarn.lock 이 생기고, node_modules/@internal/plugin-hello-backendplugins/hello-backend 를 가리키는 링크여야 하며, node_modules/@backstage/backend-defaults 는 0.17.8 이어야 합니다. 이어서 corepack yarn install --immutable 도 성공해야 합니다.

Yarn 워크스페이스는 의존성을 루트 node_modules 에 모으고, workspace: 프로토콜 의존성은 복사하지 않고 링크합니다. ls -l node_modules/@internalgrep -n 'backend-defaults@npm' yarn.lock 으로 확인하세요. peer 의존성 경고(YN0086)는 설치 실패가 아닙니다.

잠금 파일과 어긋난 package.json 은 CI 에서 막힌다

plugins/hello-backend/package.json 의 express 버전만 4.21.2 로 바꾸고 yarn.lock 은 그대로 둔 채 corepack yarn install --immutable 을 실행해, 그 출력 전체를 /root/cba-dev/immutable.txt 에 저장하세요(종료 코드도 보세요). 그다음 express 를 4.22.3 으로 되돌려 --immutable 이 다시 성공하게 하세요. yarn.lock 은 끝까지 바뀌지 않아야 합니다.

--immutable 은 설치 결과가 잠금 파일을 바꿔야 한다면 쓰지 않고 실패합니다. CI 에서 이 옵션을 쓰는 이유는 개발자 PC 에서만 풀린 의존성 버전이 몰래 배포에 섞이지 않게 하기 위해서입니다. 되돌린 뒤에는 옵션 없이 install 하지 마세요 — 그러면 어긋난 버전이 잠금 파일에 기록돼 버립니다. 실패 코드는 YN 으로 시작하는 번호로 나옵니다.

tsc 는 잘못된 정책 값을 잡지만 JS 는 그래도 만든다

plugins/hello-backend/tsconfig.json(target ES2022, module commonjs, moduleResolution node, strict, esModuleInterop, skipLibCheck, declaration, rootDir src, outDir dist, include ["src"], noEmitOnError 없이)과 src/index.ts 를 만드세요. index.ts 는 createBackendPlugin 으로 pluginId hello 플러그인을 만들어 GET /ping{"version": 1} 을 돌려주게 하고, 일부러 http.addAuthPolicy({ path: '/ping', allow: 'public' }) 로 씁니다. helloPlugin 을 이름으로도, default 로도 export 합니다. dist 를 지운 뒤 플러그인 디렉터리에서 corepack yarn tsc -p tsconfig.json 을 실행해 출력을 /root/cba-dev/tsc-error.txt 에 저장하고, 끝에 emitted_despite_error=<dist/index.js 가 생겼으면 yes, 아니면 no> 한 줄을 덧붙이세요.

addAuthPolicy 의 allow 는 문자열 아무거나가 아니라 정해진 리터럴 유니온 타입입니다. 자바스크립트였다면 기동한 뒤에야 알았을 실수를 컴파일 단계에서 잡는 것이 TypeScript 를 쓰는 이유입니다. 다만 tsc 의 기본값은 타입 오류가 있어도 출력을 씁니다 — 종료 코드와 dist 를 함께 보세요.

타입 오류가 있으면 아무것도 내보내지 않게 빌드한다

tsconfig 에 "noEmitOnError": true 를 넣고, index.ts 의 정책 값을 올바른 'unauthenticated' 로 고친 뒤 corepack yarn workspace @internal/plugin-hello-backend build 로 빌드하세요. dist/index.jsdist/index.d.ts 가 지금의 src 로 만들어져야 하고, 플러그인 디렉터리에서 corepack yarn tsc -p tsconfig.json --noEmit 이 오류 없이 끝나야 합니다.

빌드 산출물(dist)은 소스보다 새것이어야 합니다. yarn workspace <이름> <스크립트> 는 루트에서 특정 패키지의 스크립트를 돌립니다. noEmitOnError 를 켠 채 일부러 틀려 보면 dist 가 갱신되지 않는 것도 확인할 수 있습니다.

워크스페이스 플러그인을 붙인 백엔드를 로컬에서 띄운다

packages/backend/index.js 에서 createBackend()require('@internal/plugin-hello-backend') 를 add 하고 start 하세요. app-config.yaml(backend.baseUrl http://localhost:7007, listen.port 7007, DB better-sqlite3·':memory:')은 워크스페이스 루트 /usr/local/cba-dev/app-config.yaml 에 둡니다. packages/backend 에서 node index.js 로 띄워 출력은 packages/backend/backend.log 에 이어 쓰고, 인증 없이 GET http://127.0.0.1:7007/api/hello/ping 이 200 {"version":1} 이면 됩니다.

--config 를 주지 않으면 백엔드는 실행 디렉터리가 아니라 저장소 루트의 app-config.yaml 을 찾습니다 — 설정을 packages/backend 에 두면 어떤 오류가 나는지 로그 첫 줄에서 보세요. require 는 node_modules 의 링크를 따라 플러그인 package.json 의 main 으로 갑니다. node -p "require('fs').realpathSync(require.resolve('@internal/plugin-hello-backend'))" 로 실제 파일을 확인하세요.

소스를 고치고 빌드하고 다시 띄워야 응답이 바뀐다

index.ts 의 /ping 응답을 {"version": 2} 로 고치세요. 먼저 빌드하지 않고 백엔드만 재시작해 응답이 여전히 1 인 것을 보고, 그다음 플러그인을 빌드한 뒤 백엔드를 재시작해 2 가 되게 하세요. /root/cba-dev/dev-loop.txtwithout_build=<빌드 없이 재시작했을 때 version>, after_build=<빌드 뒤 재시작했을 때 version> 두 줄을 남깁니다.

백엔드가 실행하는 것은 src 의 TypeScript 가 아니라 main 이 가리키는 dist 의 JavaScript 입니다. 실제 Backstage 저장소에서는 yarn start(backstage-cli)가 이 변환과 재시작을 대신 해 주지만, 이 실습은 그 도구 없이 단계를 손으로 밟습니다.

개발 흐름 점검표를 지금 저장소로 채운다

/root/cba-dev/report.md 첫 다섯 줄에 지금 잰 값을 적으세요 — yarn_version=(루트에서 corepack yarn --version), workspace_count=(workspaces list 줄 수), immutable_install=(지금 --immutable 이 성공하면 pass, 아니면 fail), plugin_entry=(packages/backend 에서 require.resolve 한 플러그인의 실제 경로), ping_version=(지금 /api/hello/ping 의 version). 그 아래에 잠금 파일·타입 검사·빌드 산출물이 개발 흐름에서 각각 무엇을 막는지, 그리고 이 VM 에서 Docker 이미지 빌드를 하지 않은 이유를 쓰세요.

모든 값은 지금 명령을 실행해 얻습니다. 실제 경로는 심볼릭 링크를 따라간 realpath 입니다. command -v docker 로 도구가 있는지 보세요.