@spectrabrain/eds 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/QUICKSTART.md ADDED
@@ -0,0 +1,47 @@
1
+ # EDS QUICKSTART
2
+
3
+ `@spectrabrain/eds`를 사용하는 저장소의 AI 에이전트가 UI 작업 전에 읽는 유일한 필수 문서입니다. 설치된 버전의 문서와 TypeScript contract가 최종 기준입니다.
4
+
5
+ ## package root
6
+
7
+ `require.resolve("@spectrabrain/eds/package.json")`이 가리키는 디렉터리이며 보통 `node_modules/@spectrabrain/eds`입니다. 아래 문서는 모두 이 경로에서 읽습니다. `dist/` 아래 파일은 `@spectrabrain/eds/tokens.json`처럼 export subpath로도 resolve됩니다.
8
+
9
+ ## 규칙
10
+
11
+ 1. EDS 컴포넌트를 유사한 자체 구현보다 우선 사용합니다. 새 코드와 수정 중인 코드에서는 `Button`을 먼저 검토합니다. 소비처 저장소 규칙이 특정 컴포넌트를 지정하더라도 EDS에 같은 역할의 컴포넌트가 있으면 EDS를 먼저 선택하고, 소비처 규칙이 접근성·보안·제품 요구사항인 경우에만 그것을 우선합니다.
12
+ 2. `Button`의 `role`·`variant`는 행동의 의미로 고릅니다.
13
+
14
+ | 행동 | 조합 |
15
+ | --- | --- |
16
+ | 화면의 핵심 행동(저장·생성·확인) | `brand/solid` |
17
+ | 일반 주요 행동(검색·조회) | `neutral/solid` |
18
+ | 취소·닫기·수정 같은 보조 행동 | `neutral/outline` |
19
+ | 초기화·갱신·되돌리기처럼 부담이 낮은 반복 행동 | `neutral/weak` |
20
+ | 되돌리기 어려운 삭제·초기화 | `critical/weak` |
21
+ | 비동기 처리 중 | 선택한 조합에 `loading` |
22
+
23
+ 3. 색상은 `text-eds-fg-*`, `bg-eds-bg-*`, `border-eds-stroke-*`, typography는 `eds-typography-*` class를 사용합니다. exact token이 없으면 `fg/bg/stroke` 역할, 상태, 행동 의미, `neutral` 순서로 가까운 공개 token을 고릅니다.
24
+ 4. raw 색상·typography 값, 임의 CSS variable, 패키지 비공개 경로를 만들거나 복사하지 않습니다.
25
+ 5. `className`은 배치·컨테이너 보정에만 사용합니다. 문서에 없는 prop·variant·상태를 `className`이나 inline style로 우회하지 않습니다.
26
+ 6. 새 UI 스타일은 Tailwind `className`으로 작성하고 `index.css`에 컴포넌트별 규칙을 추가하지 않습니다.
27
+ 7. 작업 전후에 `npm run eds:doctor`를 실행합니다. 실패하면 `npm run sync:eds`를 먼저 실행하고 diff를 검토합니다.
28
+ 8. 완료 보고에 변경 파일, 사용한 EDS component·token, typecheck·test·build·doctor 결과를 기록합니다.
29
+
30
+ ## 명령
31
+
32
+ | 명령 | 용도 |
33
+ | --- | --- |
34
+ | `npm run eds:doctor` | 동기화 상태와 `eds-*` class 이름을 읽기 전용으로 점검합니다. exit 1이면 sync가 필요하거나 공개 token이 없는 class가 있습니다. `-- --scan`은 raw 색상 class도 나열합니다. |
35
+ | `npm run sync:eds` | Tailwind root CSS import, `src/styles/eds-tokens.css`, `AGENTS.md` managed block을 갱신합니다. |
36
+ | `npx eds init` | 위 script가 없을 때 등록하고 첫 sync를 실행합니다. |
37
+
38
+ `npm run`은 소비처 패키지 매니저의 runner(`pnpm`, `yarn`, `bun run`)로 바꿔 읽습니다.
39
+
40
+ ## 더 읽을 문서
41
+
42
+ - `CONSUMER_AGENT.md`: setup·sync·doctor 절차, 패키지 매니저별 `init`, font 정책이 필요할 때 읽습니다.
43
+ - `AI.md`: 공개 범위와 금지 사례를 확인할 때 읽습니다.
44
+ - `TOKEN_CONTEXT.md`: token을 직접 고를 때 읽습니다.
45
+ - `TAILWIND_V4.md`: Tailwind CSS v4 설정을 다룰 때 읽습니다.
46
+ - `CHANGELOG.md`: EDS 버전을 올린 뒤 바뀐 내용을 확인할 때 읽습니다.
47
+ - `dist/components/Button/README.md`와 `dist/components/Button/spec.json`: Button을 사용할 때 읽습니다. `spec.json`의 `api.iconNames`가 이름으로 쓸 수 있는 아이콘 목록이며, 목록 밖 아이콘은 `icon`·`startIcon`·`endIcon`에 ReactElement로 전달합니다.
package/README.md CHANGED
@@ -8,13 +8,20 @@ EVA 서비스의 일관된 UI/UX를 위한 React 웹 디자인 시스템입니
8
8
  npm install @spectrabrain/eds
9
9
  ~~~
10
10
 
11
+ 소비처 AI와 자동화 도구는 설치 후 [QUICKSTART.md](QUICKSTART.md)를 먼저 읽습니다. 4 KB 이하의 유일한 필수 문서이며, [AI_CONTEXT.json](AI_CONTEXT.json)의 조건에 해당할 때만 [CONSUMER_AGENT.md](CONSUMER_AGENT.md) 등 나머지 문서를 읽습니다. npm 10·11에서는 EDS `postinstall`이 소비처 `package.json`에 `sync:eds`·`eds:doctor` script를 등록하고, 의존성의 install script가 실행되지 않는 npm 12·pnpm·yarn berry·bun에서는 `npx @spectrabrain/eds init`이 같은 script를 등록한 뒤 패키지 매니저별 승인 절차를 기록하거나 안내합니다. 설치·업데이트 후 `npm run sync:eds`를 실행하면 Tailwind root CSS의 EDS import, Tailwind token alias, `AGENTS.md` managed block과 `CLAUDE.md`·`GEMINI.md`의 import 줄을 갱신하고, `npm run eds:doctor`가 동기화 상태와 EDS font import를 읽기 전용으로 점검합니다. Tailwind를 사용하지 않는 저장소에서는 sync가 Tailwind 배선을 건너뛰고 `AGENTS.md`만 갱신합니다.
12
+
13
+ 최초 설치와 동기화를 한 번에 실행하려면 `npm install @spectrabrain/eds && npm run sync:eds`를 사용합니다.
14
+
15
+ `@spectrabrain/eds/styles.css`는 Pretendard font, semantic CSS custom property, `html` 기본 font family, Button CSS를 함께 불러옵니다. 개별 파일로 구성할 때는 `@spectrabrain/eds/tokens.css`, `@spectrabrain/eds/components.css`, `@spectrabrain/eds/fonts.css`를 함께 import하고 `@spectrabrain/eds/base.css`는 선택합니다. EDS text style은 `fonts.css`가 선언하는 font family `Pretendard`를 참조하므로, 앱이 Pretendard를 다른 경로로 불러오더라도 EDS `fonts.css`를 생략하지 않습니다.
16
+
11
17
  ## 현재 제공 범위
12
18
 
13
19
  - foreground, background, stroke 역할의 semantic color token을 제공합니다.
14
20
  - Display, Heading, Title, Body, Caption semantic text style을 제공합니다. 각 style은 font family, weight, size, line height, letter spacing token으로 구성합니다.
15
21
  - `styles.css`에서 사용할 수 있는 Pretendard Variable v1.3.9 webfont를 제공합니다.
16
22
  - 자동완성을 지원하는 TypeScript token API와 CSS custom property를 제공합니다.
17
- - React `Button` 컴포넌트를 제공합니다.
23
+ - React `Button` 컴포넌트를 제공합니다. 이름으로 지정하는 lucide 아이콘은 승인된 allowlist로 제한합니다.
24
+ - 소비처 setup용 `eds` CLI(`npx @spectrabrain/eds init`, `npm run sync:eds`, `npm run eds:doctor`)를 제공합니다.
18
25
 
19
26
  ## Button 사용
20
27
 
@@ -33,9 +40,9 @@ export function SaveButton() {
33
40
  }
34
41
  ~~~
35
42
 
36
- 아이콘만 사용하는 경우 `icon`과 비어 있지 않은 `aria-label`을 함께 제공합니다. `style`과 임의 스타일 override는 지원하지 않습니다. 전체 contract와 접근성 규칙은 설치된 버전의 `AI.md`를 확인합니다.
43
+ 아이콘만 사용하는 경우 접근성 이름을 제공하려면 `icon` 또는 `iconName`과 `aria-label`을 함께 지정합니다. `aria-label`은 필수로 강제하지 않습니다. `style`과 임의 스타일 override는 지원하지 않습니다. 전체 contract와 접근성 규칙은 설치된 버전의 `@spectrabrain/eds/components/Button/README.md`와 `@spectrabrain/eds/components/Button/spec.json`(package root의 `dist/components/Button/`)을 확인합니다.
37
44
 
38
- lucide 아이콘은 PascalCase 이름을 `iconName`, `startIconName`, `endIconName`으로 지정합니다. `ButtonIconName`이 TypeScript 자동완성을 제공하며, 같은 슬롯에 custom ReactElement와 이름을 함께 지정하지 않습니다. 이름으로 지정한 아이콘은 `lucide-react`의 기본 시각 설정을 사용하고 Button이 아이콘 영역 크기를 조정합니다.
45
+ lucide 아이콘은 승인된 allowlist의 PascalCase 이름을 `iconName`, `startIconName`, `endIconName`으로 지정합니다. `ButtonIconName`은 이 allowlist와 같은 union이며 TypeScript 자동완성을 제공하고, 설치된 `dist/components/Button/spec.json`(`@spectrabrain/eds/components/Button/spec.json`)의 `api.iconNames`에서 같은 목록을 확인합니다. allowlist 밖 아이콘은 `icon`/`startIcon`/`endIcon`에 ReactElement로 전달합니다. 같은 아이콘이 두 화면 이상에서 반복되면 allowlist PR을 엽니다. 같은 슬롯에 custom ReactElement와 이름을 함께 지정하지 않으며, 이름으로 지정한 아이콘은 `lucide-react`의 기본 시각 설정을 사용하고 Button이 아이콘 영역 크기를 조정합니다.
39
46
 
40
47
  처리 중인 버튼은 `loading`을 지정합니다. 라벨 공간을 유지한 2색 progress circle을 표시하고 상호작용을 차단하며, link는 href를 제거합니다. `prefers-reduced-motion: reduce` 환경에서는 애니메이션이 정지합니다.
41
48
 
@@ -67,10 +74,10 @@ lucide 아이콘은 PascalCase 이름을 `iconName`, `startIconName`, `endIconNa
67
74
  | --- | --- |
68
75
  | `children` | `ReactNode` label입니다. 렌더링 가능한 내용이 하나 이상 필요하며 `startIcon`·`endIcon`과 조합할 수 있습니다. |
69
76
  | `icon` | icon-only용 단일 custom `ReactElement`입니다. children이나 다른 아이콘 슬롯과 함께 사용하지 않습니다. |
70
- | `iconName` | icon-only용 `ButtonIconName` lucide 이름입니다. |
77
+ | `iconName` | icon-only용 `ButtonIconName` lucide 이름입니다. allowlist에 있는 이름만 허용합니다. |
71
78
  | `startIcon` / `endIcon` | label 앞·뒤에 배치할 단일 custom `ReactElement` 슬롯입니다. |
72
79
  | `startIconName` / `endIconName` | label 앞·뒤에 배치할 `ButtonIconName` lucide 이름입니다. |
73
- | `aria-label` | icon-only에서 비어 있지 않은 accessible name으로 필수입니다. text label에서는 필요할 때 지정합니다. |
80
+ | `aria-label` | 선택적인 accessible name입니다. icon-only의 접근성 이름을 제공할 때 지정하며, text label에서도 필요할 때 사용할 수 있습니다. |
74
81
  | `disabled` | 실행할 수 없는 상태입니다. native button은 `disabled`, link는 href 제거·`aria-disabled="true"`·`tabIndex={-1}`를 적용합니다. |
75
82
  | `loading` | 처리 중 상태입니다. label과 아이콘 공간을 유지하고 progress circle을 표시하며 상호작용을 차단합니다. `aria-busy="true"`를 적용합니다. |
76
83
  | `className` | 배치·컨테이너 보정용으로만 사용합니다. |
@@ -89,7 +96,7 @@ lucide 아이콘은 PascalCase 이름을 `iconName`, `startIconName`, `endIconNa
89
96
  | children과 앞·뒤 아이콘을 모두 지정합니다. | `iconBoth` |
90
97
  | children 없이 `icon` 또는 `iconName`만 지정합니다. | `iconOnly` |
91
98
 
92
- 잘못된 슬롯 조합, 빈 label·accessible name·icon은 모든 환경에서 오류로 처리합니다. Button의 개요와 사용 맥락은 설치된 패키지의 `llms.txt`에서 안내하는 Button 문서를 기준으로 합니다.
99
+ 잘못된 슬롯 조합, 빈 label·icon 또는 제공된 빈 `aria-label`은 모든 환경에서 오류로 처리합니다. Button의 개요와 사용 맥락은 설치된 패키지의 `@spectrabrain/eds/components/Button/README.md`(package root의 `dist/components/Button/README.md`)를 기준으로 합니다.
93
100
 
94
101
  ## 토큰 사용
95
102
 
@@ -105,8 +112,14 @@ const color = cssVar(token); // var(--eds-color-fg-brand-contrast)
105
112
 
106
113
  `SemanticTokenName`을 사용하면 승인된 token 이름을 TypeScript 자동완성으로 선택할 수 있습니다. `var(--eds-...)`의 직접 자동완성은 에디터의 CSS import 색인 방식에 따라 달라질 수 있습니다.
107
114
 
115
+ `@spectrabrain/eds/tokens`의 타입은 `exports`의 `types`와 함께 `typesVersions`로도 `dist/tokens.d.ts`에 연결되어 있으므로, `moduleResolution`이 `node`(node10)인 소비처에서도 해석됩니다. `bundler`·`node16`에서는 `exports`가 우선합니다.
116
+
108
117
  필요한 역할의 token이 없으면 임의 색상값을 추가하지 말고 디자인 시스템 담당자에게 요청합니다.
109
118
 
119
+ ## Tailwind CSS v4에서 token 사용
120
+
121
+ EDS는 Tailwind utility API를 패키지에 포함하지 않습니다. Tailwind CSS v4에서 공개 semantic token을 `className`으로 사용하려면 [Tailwind CSS v4 token 설정 가이드](./TAILWIND_V4.md)를 확인합니다. 소비처의 `npm run sync:eds`가 `tokens.json`에서 `src/styles/eds-tokens.css`를 생성해 `@import "tailwindcss"`가 있는 Tailwind root CSS에 연결하며, `text-eds-fg-*`, `bg-eds-bg-*`, `border-eds-stroke-*`, `eds-typography-*` class를 사용합니다. 생성된 `@theme inline`에는 `--font-sans: var(--eds-typography-body1-fontFamily);`도 들어가므로 Tailwind의 기본 sans font가 Pretendard가 됩니다. root 후보가 둘 이상이면 `EDS_TAILWIND_ROOT`로 한 번 지정하며, sync가 그 경로를 소비처 `package.json`의 `eds.tailwindRoot`에 기록해 이후 doctor·hook이 재사용합니다. root가 없으면 sync는 Tailwind 배선을 건너뜁니다. 새 컴포넌트별 스타일은 `index.css`보다 `className`을 우선 작성합니다.
122
+
110
123
  ## Text style 사용
111
124
 
112
125
  하나의 text style은 다섯 속성을 함께 적용합니다.
@@ -121,7 +134,7 @@ const color = cssVar(token); // var(--eds-color-fg-brand-contrast)
121
134
  }
122
135
  ~~~
123
136
 
124
- `styles.css`는 `fonts.css`, `tokens.css`, Button 내부 CSS를 함께 import하며 Pretendard Variable font에 `font-display: swap`을 사용합니다. 이 파일이 애플리케이션의 `font-family`를 자동으로 지정하지는 않습니다. `tokens.css`만 import한 소비처가 text style을 사용할 때는 `fonts.css`를 추가합니다. font의 license와 저작권 고지는 패키지의 [NOTICE](./NOTICE)에서 확인합니다.
137
+ `styles.css`는 `fonts.css`, `tokens.css`, `base.css`, `components.css`를 순서대로 import하며 Pretendard Variable font에 `font-display: swap`을 사용합니다. `base.css`는 `:where(html)`에 body1 font family를 지정하는 specificity 0 규칙 하나이므로 소비처의 `html`·`body` font-family 선언이 있으면 그 선언이 우선합니다. 개별 파일로 구성할 때는 `tokens.css`, `components.css`, `fonts.css`를 함께 import하고 `base.css`는 선택합니다. EDS text style은 `fonts.css`가 선언하는 family `Pretendard`를 참조하므로 다른 출처의 Pretendard로 `fonts.css`를 대체하지 않습니다. font의 license와 저작권 고지는 패키지의 [NOTICE](./NOTICE)에서 확인합니다.
125
138
 
126
139
  ## 공개 경로
127
140
 
@@ -130,9 +143,19 @@ const color = cssVar(token); // var(--eds-color-fg-brand-contrast)
130
143
  | `@spectrabrain/eds` | `Button`과 token runtime 진입점입니다. |
131
144
  | `@spectrabrain/eds/tokens` | semantic token runtime API와 타입입니다. |
132
145
  | `@spectrabrain/eds/tokens.css` | semantic CSS custom property입니다. |
133
- | `@spectrabrain/eds/styles.css` | Pretendard font와 semantic CSS custom property를 함께 불러옵니다. |
134
- | `@spectrabrain/eds/fonts.css` | Pretendard font만 불러옵니다. |
146
+ | `@spectrabrain/eds/styles.css` | `fonts.css`, `tokens.css`, `base.css`, `components.css`를 순서대로 불러옵니다. |
147
+ | `@spectrabrain/eds/components.css` | Button CSS만 불러옵니다. font는 포함하지 않으며 `tokens.css`·`fonts.css`와 함께 사용합니다. |
148
+ | `@spectrabrain/eds/fonts.css` | Pretendard font만 불러옵니다. EDS text style이 참조하는 family를 선언하므로 생략하지 않습니다. |
149
+ | `@spectrabrain/eds/base.css` | `:where(html)`에 body1 font family를 지정합니다. `styles.css`에 포함되며 개별 구성에서는 선택입니다. |
135
150
  | `@spectrabrain/eds/tokens.json` | 도구 연동용 semantic token manifest입니다. |
151
+ | `@spectrabrain/eds/QUICKSTART.md` | 소비처 AI의 유일한 필수 읽기 문서입니다. 4 KB 이하입니다. |
152
+ | `@spectrabrain/eds/CONSUMER_AGENT.md` | 소비처 AI의 setup·sync·doctor 계약과 managed block 템플릿입니다. |
153
+ | `@spectrabrain/eds/AI.md` | 소비처 AI의 공개 범위와 금지 사례입니다. |
154
+ | `@spectrabrain/eds/CHANGELOG.md` | 버전별 변경 내용입니다. EDS 버전을 올린 뒤 읽습니다. |
155
+ | `@spectrabrain/eds/AI_CONTEXT.json` | AI 문서 읽기 순서·우선순위·공개 manifest 경로를 제공합니다. |
156
+ | `@spectrabrain/eds/TOKEN_CONTEXT.md` | semantic token의 역할과 화면 맥락입니다. |
157
+ | `@spectrabrain/eds/TAILWIND_V4.md` | Tailwind CSS v4에서 공개 token을 연결하는 설치 가이드입니다. |
158
+ | `eds` CLI (`npx @spectrabrain/eds init`) | 소비처 setup CLI입니다. `init`은 script 등록과 첫 동기화, `npm run sync:eds`는 동기화, `npm run eds:doctor`는 동기화 상태와 `eds-*` class 이름의 읽기 전용 점검을 실행합니다. 모듈로 import하지 않습니다. |
136
159
  | `@spectrabrain/eds/components/Button/README.md` | Button 개요·사용 맥락·공개 API 문서입니다. |
137
160
  | `@spectrabrain/eds/components/Button/spec.json` | Button 공개 API와 접근성 계약을 위한 manifest입니다. |
138
161
  | `@spectrabrain/eds/llms.txt` | AI 문서 읽기 순서입니다. |
@@ -142,7 +165,7 @@ const color = cssVar(token); // var(--eds-color-fg-brand-contrast)
142
165
 
143
166
  ## 소비자 가이드
144
167
 
145
- AI와 자동화 도구를 위한 규칙은 [AI.md](AI.md)와 [llms.txt](llms.txt)에서 확인합니다. React 컴포넌트를 사용하기 전에는 해당 컴포넌트의 공식 API와 사용 규칙을 먼저 확인합니다.
168
+ AI와 자동화 도구를 위한 시작 규칙은 [QUICKSTART.md](QUICKSTART.md), setup·sync·doctor 계약은 [CONSUMER_AGENT.md](CONSUMER_AGENT.md), 공통 공개 범위는 [AI.md](AI.md), token 사용 맥락은 [TOKEN_CONTEXT.md](TOKEN_CONTEXT.md), 문서 읽기 순서는 [llms.txt](llms.txt)에서 확인합니다. React 컴포넌트를 사용하기 전에는 해당 컴포넌트의 공식 API와 사용 규칙을 먼저 확인합니다.
146
169
 
147
170
  ## 라이선스
148
171
 
package/TAILWIND_V4.md ADDED
@@ -0,0 +1,233 @@
1
+ # EVA token을 Tailwind CSS v4에서 사용하는 가이드
2
+
3
+ 이 문서는 소비처가 `@spectrabrain/eds`의 공개 semantic token을 Tailwind CSS v4 `className`으로 사용하는 방법을 안내합니다. EDS는 Tailwind utility API를 패키지에 포함하지 않으며, 소비처가 공개 CSS variable을 자체 alias로 연결합니다. 필수 읽기는 package root의 `QUICKSTART.md`이며, 이 문서는 Tailwind CSS v4 설정을 다룰 때 읽습니다.
4
+
5
+ ## 공개 경로
6
+
7
+ 소비처는 다음 경로만 사용합니다.
8
+
9
+ | 경로 | 용도 |
10
+ | --- | --- |
11
+ | `@spectrabrain/eds/styles.css` | `fonts.css`, `tokens.css`, `base.css`, `components.css`를 순서대로 불러옵니다. |
12
+ | `@spectrabrain/eds/tokens.css` | semantic CSS variable만 불러옵니다. |
13
+ | `@spectrabrain/eds/components.css` | Button CSS만 불러옵니다. font는 포함하지 않습니다. |
14
+ | `@spectrabrain/eds/fonts.css` | Pretendard font만 불러옵니다. EDS text style이 참조하는 family를 선언하므로 생략하지 않습니다. |
15
+ | `@spectrabrain/eds/base.css` | `:where(html)`에 body1 font family를 지정합니다. `styles.css`에 포함되며 개별 구성에서는 선택입니다. |
16
+ | `@spectrabrain/eds/tokens.json` | 공개 token 이름과 타입을 생성 도구에서 확인합니다. |
17
+ | `@spectrabrain/eds/tokens` | TypeScript token 이름과 `cssVar()`를 사용합니다. |
18
+
19
+ 개별 파일로 구성할 때는 `@spectrabrain/eds/tokens.css`, `@spectrabrain/eds/components.css`, `@spectrabrain/eds/fonts.css`를 함께 import하고 `@spectrabrain/eds/base.css`는 선택합니다. 다른 출처의 Pretendard로 EDS `fonts.css`를 대체하는 구성은 지원하지 않습니다. 패키지 내부의 다른 경로와 token 값을 소비처에 복사하지 않습니다.
20
+
21
+ ## 사전 조건과 설치
22
+
23
+ React 웹 프로젝트와 Tailwind CSS v4를 준비합니다. 프로젝트의 bundler에 맞는 adapter 하나만 선택합니다.
24
+
25
+ ```sh
26
+ npm install @spectrabrain/eds
27
+ ```
28
+
29
+ Vite를 사용한다면 다음을 설치합니다.
30
+
31
+ ```sh
32
+ npm install -D tailwindcss @tailwindcss/vite
33
+ ```
34
+
35
+ PostCSS를 사용한다면 다음을 설치합니다.
36
+
37
+ ```sh
38
+ npm install -D tailwindcss @tailwindcss/postcss postcss
39
+ ```
40
+
41
+ Vite adapter와 PostCSS adapter를 동시에 추가하지 않습니다. 기존 framework plugin과 PostCSS 설정은 제거하지 않고 adapter만 추가합니다.
42
+
43
+ ## 소비처 setup과 생성 구조
44
+
45
+ npm 10·11에서는 EDS의 `postinstall`이 소비처 `package.json`에 setup script만 등록합니다. npm 12, pnpm 10, yarn berry, bun처럼 의존성의 install script를 기본으로 실행하지 않는 환경과 `npm install --ignore-scripts` 환경에서는 `npx @spectrabrain/eds init`이 같은 script를 등록하고 첫 동기화까지 실행합니다. `init`은 패키지 매니저를 감지해 npm 12 이상에서는 `allowScripts`를 기록하고, pnpm·bun·yarn berry에서는 승인 설정을 안내만 합니다. 소비처 `package.json`에 `workspaces`가 있으면 postinstall은 script를 등록하지 않고 앱 package 안에서 `npx eds init`을 실행하도록 안내하며, pnpm 설치에서는 pnpm이 설치 중의 `package.json` 편집을 버리므로 `pnpm exec eds init`을 안내합니다. 세부 동작과 `--no-allow-scripts`, `--print-ci gitlab|github`, `eds doctor --warn`은 `CONSUMER_AGENT.md`를 따릅니다. Tailwind root CSS, 생성 CSS, `AGENTS.md`는 `npm run sync:eds`를 명시적으로 실행할 때만 바뀝니다.
46
+
47
+ ```json
48
+ {
49
+ "scripts": {
50
+ "sync:eds": "eds sync",
51
+ "eds:doctor": "eds doctor",
52
+ "pretypecheck": "eds doctor",
53
+ "prebuild": "eds doctor"
54
+ }
55
+ }
56
+ ```
57
+
58
+ `sync:eds`·`eds:doctor`는 같은 이름의 script가 `eds sync`·`eds doctor`를 포함하면 그대로 두고, 다른 명령이면 덮어쓰지 않고 안내만 출력합니다. 설치는 실패하지 않으며 `eds doctor`가 `scripts:` 항목으로 보고합니다. `pretypecheck`·`prebuild`는 소비처에 `typecheck`·`build` script가 있고 같은 이름의 pre-hook이 없을 때만 등록하며, 이미 다른 pre-hook이 있으면 그대로 두고 안내만 출력합니다. yarn berry는 pre-hook을 실행하지 않으므로 `typecheck`·`build` script 앞에 `eds doctor && `를 직접 붙입니다. 소비처는 별도 generator 파일을 소유하지 않으며, `sync:eds`가 만드는 파일은 다음 하나입니다.
59
+
60
+ ```text
61
+ src/styles/
62
+ eds-tokens.css
63
+ ```
64
+
65
+ `eds sync`는 설치된 package의 공개 `@spectrabrain/eds/tokens.json`에서 이름만 읽어 이 파일을 생성합니다. 현재 공개 manifest의 목록을 생성하며, 다음 버전에서 개수가 달라져도 manifest가 source of truth가 됩니다. 생성 결과의 형태는 다음과 같습니다.
66
+
67
+ ```css
68
+ /* Generated from @spectrabrain/eds@<version>. Do not edit manually. */
69
+ @theme inline {
70
+ --color-eds-fg-brand: var(--eds-color-fg-brand);
71
+ /* 공개 color token마다 한 줄씩 생성합니다. */
72
+ --font-sans: var(--eds-typography-body1-fontFamily);
73
+ }
74
+
75
+ @utility eds-typography-body2 {
76
+ font-family: var(--eds-typography-body2-fontFamily);
77
+ font-weight: var(--eds-typography-body2-fontWeight);
78
+ font-size: var(--eds-typography-body2-fontSize);
79
+ line-height: var(--eds-typography-body2-lineHeight);
80
+ letter-spacing: var(--eds-typography-body2-letterSpacing);
81
+ }
82
+ ```
83
+
84
+ `--font-sans`는 `@theme inline`의 마지막 줄이며 Tailwind의 기본 sans font를 EDS body1 font family로 바꿉니다. 생성물에는 `$value`, resolved color, 내부 경로, 비공개 token 이름을 기록하지 않습니다. 수동으로 alias를 추가하지 않고 EDS를 업데이트할 때마다 `npm run sync:eds`를 다시 실행합니다.
85
+
86
+ ## Tailwind root 지정
87
+
88
+ `eds sync`와 `eds doctor`는 환경 변수 `EDS_TAILWIND_ROOT`, 소비처 `package.json`의 `eds.tailwindRoot`, 자동 검출 순서로 Tailwind root를 정합니다. 자동 검출은 `@import "tailwindcss"`가 있는 CSS 파일 하나를 root로 사용하며, `@import "tailwindcss/theme.css"`·`@import "tailwindcss/utilities.css"`와 `@import url("tailwindcss")` 형태도 인식하고 `/* */` 주석 안의 import는 세지 않습니다. `node_modules`, `dist`, `build`, `.git`, `storybook-static`, `coverage`와 생성 파일은 후보에서 제외합니다. 후보가 둘 이상이면 후보 목록을 출력하고 아무 파일도 쓰지 않은 채 실패하므로 경로를 한 번 명시합니다. 후보가 없고 `EDS_TAILWIND_ROOT`와 `eds.tailwindRoot`도 없으면 Tailwind를 사용하지 않는 저장소로 보고 이 문서의 배선을 건너뛰며, `eds doctor`도 Tailwind 항목을 보고하지 않습니다.
89
+
90
+ ```sh
91
+ EDS_TAILWIND_ROOT=src/app/styles/index.css npm run sync:eds
92
+ ```
93
+
94
+ `EDS_TAILWIND_ROOT`로 sync에 성공하면 sync가 `package.json`에 `"eds": { "tailwindRoot": "src/app/styles/index.css" }`를 기록하므로, 이후 `eds doctor`, `eds sync`, `pretypecheck`·`prebuild` hook은 환경 변수 없이 같은 root를 사용합니다. 기록된 경로나 `EDS_TAILWIND_ROOT`가 존재하지 않는 파일을 가리키면 exit 2로 실패합니다.
95
+
96
+ ## Tailwind adapter 연결
97
+
98
+ Vite에서는 기존 plugin을 유지하면서 Tailwind plugin을 추가합니다.
99
+
100
+ ```ts
101
+ import tailwindcss from "@tailwindcss/vite";
102
+ import { defineConfig } from "vite";
103
+
104
+ export default defineConfig({
105
+ plugins: [tailwindcss()],
106
+ });
107
+ ```
108
+
109
+ PostCSS에서는 기존 설정을 유지하면서 adapter를 추가합니다.
110
+
111
+ ```js
112
+ export default {
113
+ plugins: {
114
+ "@tailwindcss/postcss": {},
115
+ },
116
+ };
117
+ ```
118
+
119
+ Tailwind root CSS에는 Tailwind를 한 번만 불러오고, 마지막 Tailwind import 바로 다음 줄에 EDS style과 소비처 생성 CSS를 각각 한 번씩 `@import`합니다. `tailwindcss/theme.css`와 `tailwindcss/utilities.css`를 따로 불러오는 root에서는 `utilities.css` 다음 줄에 들어갑니다. `npm run sync:eds`가 이 두 줄을 추가하며, 이미 있으면 다시 추가하지 않습니다. `src/index.css`가 root인 구조에서는 다음과 같습니다.
120
+
121
+ ```css
122
+ @import "tailwindcss";
123
+ @import "@spectrabrain/eds/styles.css";
124
+ @import "./styles/eds-tokens.css";
125
+ ```
126
+
127
+ `src/app/styles/index.css`가 root이고 생성 CSS가 `src/styles/eds-tokens.css`에 있다면 상대 경로가 달라집니다.
128
+
129
+ ```css
130
+ @import "tailwindcss";
131
+ @import "@spectrabrain/eds/styles.css";
132
+ @import "../../styles/eds-tokens.css";
133
+ ```
134
+
135
+ `eds-tokens.css`는 `@theme`과 `@utility`를 담고 있으므로 Tailwind root CSS에서 import해야 compile됩니다. `src/main.tsx` 같은 JS·TS 진입점에서 `import "./styles/eds-tokens.css";`로 불러오던 이전 구조는 `sync:eds`가 해당 줄을 제거합니다. `@spectrabrain/eds/styles.css`는 root CSS와 JS 진입점 중 한 곳에서만 불러오며, 기존 JS import가 있거나 `tokens.css`·`components.css`·`fonts.css`가 모두 import되어 있으면 sync가 root CSS에 다시 추가하지 않습니다. `styles.css`와 `eds-tokens.css`를 여러 경로에서 중복 import하지 않습니다.
136
+
137
+ ## Color alias
138
+
139
+ 생성된 `@theme inline`은 공개 CSS variable을 Tailwind alias로 연결합니다.
140
+
141
+ | EDS token | Tailwind alias | utility 예시 |
142
+ | --- | --- | --- |
143
+ | `color-fg-brand` | `--color-eds-fg-brand` | `text-eds-fg-brand` |
144
+ | `color-bg-brand-primary` | `--color-eds-bg-brand-primary` | `bg-eds-bg-brand-primary` |
145
+ | `color-stroke-neutral-secondary` | `--color-eds-stroke-neutral-secondary` | `border-eds-stroke-neutral-secondary` |
146
+
147
+ ```tsx
148
+ <div className="bg-eds-bg-neutral-primary text-eds-fg-neutral border border-eds-stroke-neutral-secondary">
149
+ 콘텐츠입니다.
150
+ </div>
151
+ ```
152
+
153
+ 상태가 있는 표현은 해당 semantic 상태 token을 사용합니다.
154
+
155
+ ```tsx
156
+ <a className="text-eds-fg-brand hover:text-eds-fg-brand-contrast" href="/details">
157
+ 자세히 보기
158
+ </a>
159
+ ```
160
+
161
+ ## Typography preset
162
+
163
+ 생성된 `@utility`는 font family, weight, size, line height, letter spacing을 함께 적용합니다.
164
+
165
+ ```tsx
166
+ <h1 className="eds-typography-heading1 text-eds-fg-neutral">
167
+ 제목입니다.
168
+ </h1>
169
+
170
+ <p className="eds-typography-body2 text-eds-fg-neutral-secondary">
171
+ 본문입니다.
172
+ </p>
173
+ ```
174
+
175
+ 생성되는 preset 이름은 다음 다섯 그룹입니다.
176
+
177
+ ```text
178
+ display1, display2
179
+ heading1, heading2, heading3, heading4
180
+ title1, title2, title3, title4
181
+ body1, body2, body3, body4
182
+ caption1, caption2, caption3
183
+ ```
184
+
185
+ ## Spacing과 radius
186
+
187
+ EDS의 공개 semantic token에는 소비처용 spacing·radius alias가 없습니다. 소비처 Tailwind 기본 scale을 사용하고, 기존 scale로 표현할 수 없는 요구가 있을 때만 디자인 시스템 담당자에게 목적과 대체 가능성을 요청합니다.
188
+
189
+ ## TypeScript token 자동완성
190
+
191
+ CSS가 아닌 CSS-in-JS 또는 inline style에서 token을 선택해야 하면 공개 TypeScript API를 사용합니다.
192
+
193
+ ```ts
194
+ import { cssVar, type SemanticTokenName } from "@spectrabrain/eds/tokens";
195
+
196
+ const token: SemanticTokenName = "color-fg-brand";
197
+ const color = cssVar(token);
198
+ // var(--eds-color-fg-brand)
199
+ ```
200
+
201
+ ## 금지 사항
202
+
203
+ - EDS 내부 파일을 import하거나 token 값을 소비처에 복사하지 않습니다.
204
+ - raw hex·px·rem 값을 EDS token 대신 작성하지 않습니다.
205
+ - `@theme`에 EDS token의 resolved 값을 직접 입력하지 않습니다.
206
+ - `@spectrabrain/eds`의 공개 목록에 없는 deep import를 사용하지 않습니다.
207
+ - Tailwind class로 EDS가 관리하는 컴포넌트 내부 스타일을 임의로 덮어쓰지 않습니다.
208
+ - EDS token 이름과 다른 의미의 alias를 만들지 않습니다.
209
+ - 새 컴포넌트별 규칙을 `index.css`에 추가하지 않고 `className`을 우선 작성합니다.
210
+ - 공개 token이 없는 `eds-*` class는 Tailwind가 조용히 버립니다. `npm run eds:doctor`가 `unknown:`으로 보고하며, `-- --scan`은 raw 색상 class도 나열합니다.
211
+
212
+ ## 버전 업데이트와 검증
213
+
214
+ EDS 버전을 업데이트하면 다음 명령으로 root CSS import, alias, managed block을 함께 갱신합니다.
215
+
216
+ ```sh
217
+ npm run sync:eds
218
+ ```
219
+
220
+ 생성 파일의 diff를 확인하고 소비처의 typecheck·lint·test·build를 실행합니다. 등록된 `prebuild`·`pretypecheck` hook이 `eds doctor`를 먼저 실행하므로, sync를 빠뜨린 업데이트는 build 전에 `[EDS] Run npm run sync:eds to fix.`와 `[EDS] Then read the AGENTS.md managed block and QUICKSTART.md in the package root before UI work.` 안내와 함께 실패합니다. CI에서 같은 점검을 실행하려면 `npx eds init --print-ci gitlab` 또는 `--print-ci github`가 출력하는 job snippet을 붙입니다. `sync:eds`는 소비처 CI에 연결할 수 있지만 EDS package의 `postinstall`에서 자동 실행하지 않습니다. 설치 script가 실행되지 않는 환경을 위해 생성 파일은 커밋하거나 CI에서 먼저 생성합니다.
221
+
222
+ setup이 끝나면 다음을 확인합니다.
223
+
224
+ 1. framework와 Tailwind v4 adapter가 현재 프로젝트 방식에 맞게 연결되어 있습니다.
225
+ 2. EDS style과 소비처 생성 CSS가 Tailwind root CSS에서 각각 한 번씩 import되어 있습니다.
226
+ 3. color alias가 `var(--eds-...)`를 참조합니다.
227
+ 4. 모든 typography preset이 다섯 속성을 함께 적용합니다.
228
+ 5. 생성 CSS에 raw 값이나 비공개 경로가 없습니다.
229
+ 6. 예시 `className`이 실제 Tailwind compile 결과에 포함됩니다.
230
+ 7. 새 UI 코드가 token과 `className`을 우선 사용합니다.
231
+ 8. `npm run eds:doctor`가 exit 0으로 끝납니다.
232
+
233
+ 설정 후 변경 파일, 설치 package, 사용할 수 있는 `eds-*` class, 추가하지 않은 alias, 검증 결과를 작업 요약에 기록합니다.
@@ -0,0 +1,71 @@
1
+ # EVA semantic token 사용 맥락
2
+
3
+ 이 문서는 `@spectrabrain/eds`의 공개 semantic token을 소비처의 화면 맥락에 맞춰 선택하기 위한 보조 안내입니다. 필수 읽기는 package root의 `QUICKSTART.md`이며, 이 문서는 token을 직접 고를 때 읽습니다. 정확한 이름과 값은 설치된 버전의 `dist/tokens.json`(`@spectrabrain/eds/tokens.json`)과 `@spectrabrain/eds/tokens`를 기준으로 합니다.
4
+
5
+ ## 색상 역할
6
+
7
+ | 역할 | 적용 대상 | Tailwind class 패턴 | 선택 기준 |
8
+ | --- | --- | --- | --- |
9
+ | `fg` | 제목, 본문, placeholder, 아이콘, inverted content | `text-eds-fg-*` | 콘텐츠가 전달하는 의미와 대비를 먼저 결정합니다. |
10
+ | `bg` | 페이지, 카드, 입력, 버튼, 상태 surface | `bg-eds-bg-*` | surface의 위계와 interaction state를 결정합니다. |
11
+ | `stroke` | border, divider, focus ring | `border-eds-stroke-*` | 경계의 중요도와 keyboard focus 여부를 결정합니다. |
12
+
13
+ 역할을 고른 뒤 `brand`, `neutral`, `critical`, `informative`, `positive`, `warning`의 의미와 `primary`, `weak`, `disabled`, `hover`, `focus` 상태를 맞춥니다. 색상 취향만으로 역할을 고르지 않습니다.
14
+
15
+ ### 행동 맥락 예시
16
+
17
+ | 화면 맥락 | 우선 검토할 표현 | 이유 |
18
+ | --- | --- | --- |
19
+ | 생성·저장·관리·확인 같은 화면의 핵심 행동 | `brand`의 primary 표현 | 사용자가 완료해야 할 대표 행동을 분명히 합니다. |
20
+ | 검색·조회·일반적인 주요 실행 | `neutral`의 primary 표현 | 핵심 행동보다 낮은 브랜드 주목도로 일반 행동을 표현합니다. |
21
+ | 취소·닫기·수정 같은 보조 작업 | `neutral`의 outline 표현 | 주 행동과 시각적 위계를 분리합니다. |
22
+ | 초기화·갱신·되돌리기처럼 부담이 낮은 반복 행동 | `neutral`의 weak 표현 | 주 행동을 보조하는 반복 가능한 행동을 낮은 위계로 표현합니다. |
23
+ | 되돌리기 어려운 삭제·초기화 | `critical`의 weak 표현 | 위험을 분명히 하고 확인 절차와 함께 사용합니다. |
24
+ | 비동기 처리 중 | 컴포넌트의 `loading` contract | 라벨 공간과 접근성 이름을 유지하면서 중복 실행을 막습니다. |
25
+
26
+ 기존 화면에서 다른 역할이 사용되고 있더라도 새 코드에서는 행동 의미에 맞는 공개 역할을 먼저 검토합니다. exact token이 없으면 `fg/bg/stroke` 역할, 상태, 행동 의미, `neutral` 순서로 가까운 공개 token을 선택합니다.
27
+
28
+ ## Typography
29
+
30
+ text style은 다섯 속성을 함께 적용하는 preset입니다. `fontFamily`, `fontWeight`, `fontSize`, `lineHeight`, `letterSpacing`을 각각 임의로 조합하지 않습니다.
31
+
32
+ Tailwind utility는 `eds-typography-*` 패턴으로 사용하며, 설치된 manifest에 있는 preset 이름만 선택합니다. `npm run sync:eds`가 생성하는 `@theme inline`에는 `--font-sans: var(--eds-typography-body1-fontFamily);`가 포함되므로 Tailwind의 `font-sans`와 기본 sans font도 Pretendard가 됩니다. 이 font family는 `@spectrabrain/eds/fonts.css`가 선언합니다.
33
+
34
+ | 순서 | class 패턴 | 권장 맥락 |
35
+ | --- | --- | --- |
36
+ | Display | `eds-typography-display1`, `eds-typography-display2` | 제품의 큰 메시지와 페이지 진입점입니다. |
37
+ | Heading | `eds-typography-heading1` … `eds-typography-heading4` | 페이지·섹션·그룹의 구조를 나타냅니다. |
38
+ | Title | `eds-typography-title1` … `eds-typography-title4` | 카드·행·대화상자의 핵심 제목과 control label입니다. |
39
+ | Body | `eds-typography-body1` … `eds-typography-body4` | 설명, 입력 안내, 일반 본문입니다. |
40
+ | Caption | `eds-typography-caption1` … `eds-typography-caption3` | 보조 설명, 메타데이터, 짧은 상태 안내입니다. |
41
+
42
+ 한글과 영문이 함께 있는 화면에서도 실제 Pretendard preset을 그대로 사용합니다. 문장의 중요도와 정보 구조를 먼저 판단하고 글자 크기만 임의로 조정하지 않습니다.
43
+
44
+ ## Tailwind className 예시
45
+
46
+ ```tsx
47
+ <section className="bg-eds-bg-neutral-primary text-eds-fg-neutral">
48
+ <h2 className="eds-typography-heading2 text-eds-fg-neutral">
49
+ 카메라 그룹
50
+ </h2>
51
+ <p className="eds-typography-body2 text-eds-fg-neutral-secondary">
52
+ 등록된 장치를 관리합니다.
53
+ </p>
54
+ <div className="border border-eds-stroke-neutral-secondary" />
55
+ </section>
56
+ ```
57
+
58
+ 새 UI는 위처럼 `className`으로 조합합니다. 컴포넌트별 규칙을 `index.css`에 추가하거나 공개 token의 값을 복사하지 않습니다.
59
+
60
+ ## Spacing과 radius
61
+
62
+ 현재 공개 semantic token에는 소비처용 spacing·radius alias가 없습니다. 소비처 Tailwind의 기본 scale을 사용하고, 기존 scale로 표현할 수 없는 요구가 있을 때만 디자인 시스템 담당자에게 목적과 대체 가능성을 요청합니다.
63
+
64
+ ## 확인 목록
65
+
66
+ - `@spectrabrain/eds/styles.css`가 Tailwind root CSS에서 한 번만 import되어 있습니다. 개별 파일로 구성했다면 `@spectrabrain/eds/tokens.css`, `@spectrabrain/eds/components.css`, `@spectrabrain/eds/fonts.css`가 함께 import되어 있고 `@spectrabrain/eds/base.css`는 선택입니다. EDS text style이 참조하는 font family는 EDS `fonts.css`가 선언하므로 다른 출처의 Pretendard로 대신하지 않습니다.
67
+ - 생성된 `eds-tokens.css`가 설치된 `dist/tokens.json`의 공개 이름만 참조합니다.
68
+ - 색상은 `fg/bg/stroke` 역할과 상태를 설명할 수 있습니다.
69
+ - typography preset은 다섯 속성을 함께 적용합니다.
70
+ - 새 컴포넌트가 EDS component를 대체 구현하고 있지 않습니다.
71
+ - raw 색상·typography·임의 CSS variable이 새 코드에 없습니다.
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import { runDoctor } from "./doctor.mjs";
3
+ import { runInit } from "./init.mjs";
4
+ import { runSync } from "./sync.mjs";
5
+
6
+ const usage = [
7
+ "Usage: eds <command> [options]",
8
+ "",
9
+ "Commands:",
10
+ " init Register sync:eds, eds:doctor and doctor hooks in package.json, approve the postinstall",
11
+ " for the detected package manager (npm 12+ writes allowScripts; others print steps), then run sync",
12
+ " sync Generate src/styles/eds-tokens.css, wire the Tailwind root CSS and update AGENTS.md",
13
+ " and the @AGENTS.md import in CLAUDE.md and GEMINI.md when they exist (without a Tailwind root only those change)",
14
+ " doctor Check whether sync is up to date, the EDS font is imported and every eds-* class in the sources",
15
+ " is a public token (exit 1 when something is off)",
16
+ "",
17
+ "Options:",
18
+ " --only tailwind|agents sync: apply only that part",
19
+ " --dry-run Print the files sync or init would write without writing them",
20
+ " --no-sync init: register scripts only",
21
+ " --no-allow-scripts init: do not write allowScripts to package.json on npm 12+",
22
+ " --print-ci github|gitlab init: print a CI job that runs npx eds doctor, then exit",
23
+ " --warn doctor: report findings but exit 0 (same as EDS_DOCTOR=warn)",
24
+ " --scan doctor: also list Tailwind palette and arbitrary color classes that bypass the EDS tokens",
25
+ " --help Show this message",
26
+ "",
27
+ "Environment:",
28
+ " EDS_TAILWIND_ROOT=src/index.css Tailwind root CSS when it cannot be detected (needed once: sync records",
29
+ " it in package.json \"eds\": { \"tailwindRoot\" }, which doctor and the hooks reuse)",
30
+ " EDS_DOCTOR=warn|off Downgrade or silence doctor failures",
31
+ ].join("\n");
32
+
33
+ function parseArguments(argv) {
34
+ const [command, ...rest] = argv;
35
+ const options = { only: null, dryRun: false, noSync: false, allowScripts: true, printCi: null, warn: false, scan: false, help: false };
36
+ for (let index = 0; index < rest.length; index += 1) {
37
+ const argument = rest[index];
38
+ if (argument === "--dry-run") {
39
+ options.dryRun = true;
40
+ } else if (argument === "--no-sync") {
41
+ options.noSync = true;
42
+ } else if (argument === "--no-allow-scripts") {
43
+ options.allowScripts = false;
44
+ } else if (argument === "--warn") {
45
+ options.warn = true;
46
+ } else if (argument === "--scan") {
47
+ options.scan = true;
48
+ } else if (argument === "--help" || argument === "-h") {
49
+ options.help = true;
50
+ } else if (argument === "--only") {
51
+ index += 1;
52
+ options.only = rest[index] ?? "";
53
+ } else if (argument.startsWith("--only=")) {
54
+ options.only = argument.slice("--only=".length);
55
+ } else if (argument === "--print-ci") {
56
+ index += 1;
57
+ options.printCi = rest[index] ?? "";
58
+ } else if (argument.startsWith("--print-ci=")) {
59
+ options.printCi = argument.slice("--print-ci=".length);
60
+ } else {
61
+ throw new Error("Unknown option: " + argument);
62
+ }
63
+ }
64
+ return { command, options };
65
+ }
66
+
67
+ async function main() {
68
+ const consumerRoot = process.cwd();
69
+ let parsed;
70
+ try {
71
+ parsed = parseArguments(process.argv.slice(2));
72
+ } catch (error) {
73
+ console.error(error.message + "\n\n" + usage);
74
+ return 2;
75
+ }
76
+ const { command, options } = parsed;
77
+
78
+ if (command === "--help" || command === "-h" || options.help) {
79
+ console.log(usage);
80
+ return 0;
81
+ }
82
+ if (command === "sync") {
83
+ return runSync({ consumerRoot, only: options.only, dryRun: options.dryRun });
84
+ }
85
+ if (command === "doctor") {
86
+ return runDoctor({ consumerRoot, warn: options.warn, scan: options.scan });
87
+ }
88
+ if (command === "init") {
89
+ return runInit({
90
+ consumerRoot,
91
+ noSync: options.noSync,
92
+ dryRun: options.dryRun,
93
+ allowScripts: options.allowScripts,
94
+ printCi: options.printCi,
95
+ });
96
+ }
97
+
98
+ console.error((command ? "Unknown command: " + command + "\n\n" : "") + usage);
99
+ return 2;
100
+ }
101
+
102
+ try {
103
+ process.exitCode = await main();
104
+ } catch (error) {
105
+ console.error("[EDS] " + (error instanceof Error ? error.message : String(error)));
106
+ process.exitCode = typeof error?.exitCode === "number" ? error.exitCode : 1;
107
+ }