「Storymesh」로 Story 파일 누락 및 사양 누락을 감지하는 도구 소개
요약
Storymesh는 Storybook 사용을 전제로 개발된 OSS 도구로, UI 컴포넌트 라이브러리 제작 과정에서 발생하는 스토리 파일 누락 및 사양 미정의 문제를 해결합니다. 이 도구는 CI 환경에서 stories 파일 부족분을 기계적으로 감지하고, 필요한 경우 자동 스켈레톤 생성을 지원하며, BDD 방식으로 Story의 사양 정의와 구현 여부를 검증하는 것을 목표로 합니다.
핵심 포인트
- Storybook 기반으로 컴포넌트 라이브러리 품질 관리 강화
- CI 환경에서 stories 파일 누락을 기계적으로 감지 및 자동 스켈레톤 생성 지원
- BDD 방식으로 Story 사양 정의를 강제하고 구현 여부를 검증하여 개발 하네스 구축에 기여
서론
안녕하세요. yuno입니다.
이번에는 개인 OSS로 제작한 Storymesh라는 도구를 소개해 드리고자 합니다.
※ 본 도구는 Storybook 사용을 전제로 하는 도구입니다.
리포지토리: https://github.com/y2o-lab/storymesh
npm: https://www.npmjs.com/package/storymesh
배경 및 결론
현재 소속된 팀에서 공통으로 사용할 UI 컴포넌트 라이브러리를 만들고 있습니다.
이 라이브러리를 만드는 과정에서 다음과 같은 사례가 자주 발생했습니다.
- 컴포넌트는 생성했지만,
stories.tsx파일만 생성을 잊는 경우 - 애초에 해당 Story가 필요한지, 또는 Story가 충분한지 감지할 수 없습니다.
AI를 활용하여 개발하다 보니 양이 상당하게 나오기 때문에 시스템화하지 않으면 리뷰 자체가 따라잡을 수 없습니다.
개발 초기에 하네스(Harness)가 잘 갖춰져 있지 않아 AI가 대량의 컴포넌트를 생성하면서 위와 같은 사례가 많이 발생했고, 이는 리뷰어에게 큰 부담이 되었습니다. 이에 후술할 콘셉트를 바탕으로 기계적으로 이를 감지하고, 애초에 AI 에이전트 측에 전달하여 리뷰어의 부담을 줄이고 근본적인 품질 향상으로 연결할 수 있지 않을까 하는 생각으로 이 도구를 만들게 되었습니다.
하네스의 일부가 될 수 있는 도구를 만드는 것이 중요합니다.
Storymesh
Storymesh는 Storybook 사용을 전제로, 기계적으로 stories 파일의 누락을 감지하고, 부족한 부분의 자동 스켈레톤(Skeleton) 생성을 지원하며, Story의 사양을 정의하고, 이들이 실제 구현되었는지 여부를 감지하는 OSS입니다.
콘셉트
- stories 파일 부족분을 CI에서 기계적으로 포착하고 싶다.
- 없다면 자동으로 스켈레톤을 준비한다.
- 어떤 Story가 필요한지는 최종적으로 인간이 결정해야 한다고 생각하며, 애초에 그 사양(Spec)을 BDD(Behavior-Driven Development)처럼 정의하여 AI에게 이를 충족하도록 구현하게 하고, 이것이 실제로 구현되었는지 CI 등에서 기계적으로 감지하여 누락을 방지하고 싶다.
구현된 Story는 애초에 Storybook 공식에서 제공하는 Story의 테스트 코드 등을 통해 구현 내용이 올바른지는 검증할 수 있다.
AI 에이전트의 발전으로 개발 방식은 크게 바뀌었습니다. 사람이 확인하고 코딩하던 때보다 훨씬 빠르고, 번거롭다고 느껴지던 사소한 정의까지 한 번에 출력할 수 있게 되었습니다.
하지만 AI 에이전트를 하네스로 제어하지 않는 이상, 원하는 결과를 정확하게 얻을 수는 없습니다.
또한, 본질적으로 '무엇이 정말 필요한가'를 판단하고 정의하는 것은 어디까지나 인간이라고 저는 생각합니다.
따라서 본 도구는 하네스의 일부가 되며, 사람이 정의한 '무엇이 정말 필요한가'를 누락 없이 구현하는 것을 큰 콘셉트로 하고 있습니다.
그럼 이제 사용법을 살펴보겠습니다.
지원 프레임워크
- React
- Vue
- Angular
- Svelte
이용 방법
설치
이번에는 pnpm을 예시로 설명하겠습니다.
(다른 방법은 여기를 참고해 주세요.)
pnpm add -D storymesh
pnpm exec storymesh --help

Skills 도입
본 도구를 AI 에이전트가 사용하기 위한 skills는 다음 명령어로 도입할 수 있습니다.
npx skills add y2o-lab/storymesh --skill storymesh
실제로 실행해 보기
1. Story가 없는 컴포넌트를 찾기
예를 들어, 다음과 같은 디렉토리가 있다고 가정합니다.
src/components/
├── Button.tsx
├── Button.stories.tsx
...
Button에는 Story가 있지만, Card에는 아직 없습니다. 이 상태에서 check를 실행합니다.
npx storymesh check src/components --framework react
이 구성이라면 다음과 같이 Card.tsx가 표시됩니다. 기사 내 출력은 이 샘플 구성을 가정한 예시입니다.
check는 부족할 경우 종료 코드 1을 반환합니다.
Missing stories for 1 React component(s):
Card.tsx
판단은 주로 파일명과 배치를 기반으로 합니다. 예를 들어 Button.tsx에는 Button.stories.tsx가 대응합니다. 컴포넌트와 같은 위치 외에도, 바로 아래의 stories나 __stories__ 디렉터리에 놓인 스토리도 찾습니다.
2. 얼마나 갖춰져 있는지 확인하기
부족한 목록뿐만 아니라 전체 비율도 보고 싶을 때는 coverage를 사용합니다.
npx storymesh coverage src/components --framework react
React Storybook coverage: 50.0% (1/2 components)
이번 예시에서는 컴포넌트 2개 중 1개에 스토리가 있으므로 50%입니다.
비율과 부족 파일을 한 번에 보고 싶다면, report로 둘 다 표시할 수 있습니다.
npx storymesh report src/components --framework react
React Storybook coverage: 50.0% (1/2 components)
Missing: 1
Card.tsx
3. 부족한 스토리의 초안 만들기
--generate를 붙여서 초안을 만들 수 있습니다. 이 명령어는 대상 디렉터리에 파일을 작성합니다.
npx storymesh check src/components --framework react --generate
Missing stories for 1 React component(s):
Card.tsx
Generated 1 story skeleton(s):
...
Card.tsx 옆에 Card.stories.tsx가 추가됩니다. 기존의 스토리 파일은 덮어쓰지 않습니다.
생성되는 것은 최소한의 스토리입니다. 필수 props가 있다면 args를 보충하고, 로딩 중이거나 에러 시 등 확인하고 싶은 상태를 추가해 나갑니다. React의 export를 제대로 인식하지 못했을 경우, render: () => null의 플레이스홀더가 되므로 import나 렌더링 처리도 확인해주세요.
4. 대상에서 제외하고 싶은 파일은 제외하기
React에서는 .tsx나 .jsx 파일이 컴포넌트로 계산됩니다. 자동 생성된 파일 등, 스토리를 관리하는 대상에 포함시키고 싶지 않은 것도 있을 것입니다.
한 번만 제외하려면 --ignore로 지정할 수 있습니다.
npx storymesh check src/components --framework react --ignore 'generated/**'
매번 같은 규칙을 사용할 거라면, 스캔 대상 루트에 .storymeshignore를 놓습니다. 이번 명령어의 경우, 위치는 src/components/.storymeshignore입니다.
# 자동 생성된 컴포넌트는 제외
generated/
패턴은 스캔 대상 루트로부터의 상대 경로로 작성합니다. .storymeshignore는 .gitignore 형식이며, 주석이나 !를 이용한 재포함도 사용할 수 있습니다.
5. YAML로 필요한 Story 정의 및 스토리 부족 확인하기
Storybook의 스토리 파일이 갖춰져 있어도, 확인하고 싶은 상태가 모두 준비되어 있는 것은 아닙니다. Button에 일반 상태의 스토리는 있지만, 로딩 중이나 비활성화 상태의 스토리가 빠져 있을 수 있습니다.
예시로, src/components/Button.tsx를 다음과 같이 합니다.
type ButtonProps = {
loading?: boolean;
disabled?: boolean;
...
src/components/Button.stories.tsx에는 먼저 Default만 정의합니다.
import type { Meta, StoryObj } from '@storybook/react';
import Button from './Button';
const meta = {
...
컴포넌트의 import 원본은 프로젝트에서 사용하고 있는 Storybook 구성에 맞춰 조정해 주세요.
이 시점에서는 스토리 파일 자체가 존재합니다. 일반적인 check만으로는 Button에 스토리가 있다고 판단되지만, Loading이나 Disabled 여부는 알 수 없습니다.
이러한 케이스를 포착하기 위해 YAML에 '필요한 Story'(spec)를 정의하고 그것들이 부족하지 않은지 체크합니다.
여기서는 spec의 정의와 그 검출 방법에 대해 소개하겠습니다.
.storymesh/specs/button.yml을 생성합니다. 확장자는 .yaml도 사용할 수 있습니다.
version: 1
component:
id: button
...
title은 Storybook 측과 완전히 일치시켜야 합니다. Story의 ID는 표시명에서 만들며, Default라면 default, Empty State라면 empty-state입니다. 소문자로 작성하고, 공백·언더스코어·하이픈 연속은 하이픈으로 통일합니다.
각 Story는 기본적으로 필수입니다. 임의로 만들려면 required: false를 붙입니다.
npx storybook index -o storybook-static/index.json &&
npx storymesh spec check --spec-dir .storymesh/specs --index storybook-static/index.json
Default만 있다면, Loading의 부족이 표시됩니다. 출력 예시를 발췌했습니다.
Missing stories
✗ Button/loading
결과적으로, FAIL하고 종료 코드는 1을 반환합니다.
Button.stories.tsx에 예를 들어 다음 Story를 추가합니다.
여기서는 loading 속성을 가진 Button과 정의된 Story 타입을 가정하고 있습니다.
export const Loading: Story = { args: { loading: true } };
같은 명령어로 index를 다시 생성하여 체크하면, 필요한 Story가 갖춰져서 PASS (종료 코드 0)가 됩니다.
Story를 변경할 때마다 index도 업데이트해 주세요.
6. 기존 Story에서 YAML을 만들기
이미 Story가 있는 경우에는, 생성된 index로부터 YAML을 만들 수 있습니다.
npx storymesh spec import --index storybook-static/index.json
기존의 사양 파일은 덮어쓰지 않습니다. 아직 만들지 않은 상태는 포함되지 않으므로, 생성한 YAML에 필요한 상태를 추가합니다.
⚠️ CI에서도 index 생성과 spec check을 연속으로 실행하면, 필수 Story의 누락으로 실패시킬 수 있습니다.
미선언된 Story에 대한 경고만으로는 실패하지 않습니다.
확인할 수 있는 것은 Story의 등록까지이며, 렌더링이나 조작의 정확성은 Storybook이나 다른 테스트로 확인합니다.
마지막으로
Storymesh는 다음 콘셉트를 기반으로 작성된 OSS입니다.
※ Storybook 사용이 전제
- 기계적인 stories 파일 누락 감지
- Story의 사양 정의와 이에 따른 기계적인 구현 누락 감지
- 하네스(Harness)의 일부가 됨
앞으로는 Story를 어떻게 더 기계적으로 검출할 수 있을지에 대해 생각하고 있습니다.
꼭 사용해 보세요!
또한 함께 개발해 주실 분도 모집 중입니다!!
의견 등도 기다리고 있겠습니다mm
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기