@no-k/hermes 0.1.0 → 0.3.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/README.md +210 -13
- package/catalog.json +2640 -98
- package/dist/bin/hermes.js +87 -0
- package/dist/lib/catalog-query.js +63 -0
- package/dist/lib/catalog-selection.js +199 -0
- package/dist/lib/catalog.js +144 -0
- package/dist/lib/command.js +50 -0
- package/dist/lib/config-syntax.js +210 -0
- package/dist/lib/conflict-selection.js +89 -0
- package/dist/lib/contracts.js +4 -0
- package/dist/lib/files.js +94 -0
- package/dist/lib/install-prompts.js +106 -0
- package/dist/lib/install-view.js +61 -0
- package/dist/lib/install-wizard.js +105 -0
- package/dist/lib/install.js +184 -0
- package/dist/lib/installation-status.js +80 -0
- package/dist/lib/json.js +19 -0
- package/dist/lib/picker-view.js +81 -0
- package/dist/lib/picker.js +135 -0
- package/dist/lib/project-config.js +171 -0
- package/dist/lib/report.js +89 -0
- package/dist/lib/terminal-input.js +61 -0
- package/dist/lib/terminal-style.js +15 -0
- package/dist/lib/terminal-text.js +75 -0
- package/dist/lib/terminal.js +133 -0
- package/dist/lib/versions.js +251 -0
- package/docs/interactive-install.md +405 -0
- package/docs/module-versioning.md +105 -0
- package/package.json +25 -4
- package/templates/lint/fsd/fsd.ts +180 -0
- package/templates/lint/fsd/import-listener.ts +41 -0
- package/templates/lint/fsd/index.ts +32 -0
- package/templates/lint/fsd/options.ts +74 -0
- package/templates/lint/fsd/rules/fsd-layer-imports.ts +22 -0
- package/templates/lint/fsd/rules/fsd-public-api.ts +20 -0
- package/templates/lint/fsd/rules/fsd-slice-segments.ts +33 -0
- package/bin/hermes.mjs +0 -159
- package/lib/catalog.mjs +0 -51
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# 카탈로그 탐색과 선택 설치 계약
|
|
2
|
+
|
|
3
|
+
## 단계별 완료 범위와 코드 상태
|
|
4
|
+
|
|
5
|
+
[이슈 #3](https://github.com/no-k/hermes/issues/3)의 1단계에서 사용자와 함께 설치 흐름,
|
|
6
|
+
설정 저장, 파일별 교체 방식, 명령 진입 조건과 공통 타입을 정리했다.
|
|
7
|
+
이 문서와 공통 타입을 2~5단계 구현의 기준으로 사용한다.
|
|
8
|
+
사용자가 요청한 흐름은 대분류 선택 → 하위 분류 선택 → 유틸 선택 →
|
|
9
|
+
전체 선택 목록 확인 → yes/no → yes일 때 codegen이다.
|
|
10
|
+
최종 확인에서 no를 선택하면 기존 선택을 유지하고 이전 선택 단계로 돌아가 수정한다.
|
|
11
|
+
첫 대분류는 사용자가 선택한 **Core / 라이브러리** 기준이며, 둘 다 선택할 수 있다.
|
|
12
|
+
그다음 하위 분류도 별도 단계에서 선택한다.
|
|
13
|
+
Core와 라이브러리의 설치 경로도 각각 지정할 수 있다.
|
|
14
|
+
기본 경로는 Core가 src/utils/hermes, 라이브러리가 libs/hermes이며,
|
|
15
|
+
사용자는 Enter로 그대로 사용하거나 각각 수정할 수 있다.
|
|
16
|
+
설치 시 no-k.config.ts를 생성해 프로젝트별 경로와 설치한 유틸 목록을 기억하고,
|
|
17
|
+
다음 실행에서 경로를 복원하고 기존 설치 항목을 표시한다.
|
|
18
|
+
설정과 상대 설치 경로의 기준은 CLI를 실행한 디렉터리다. 모노레포에서도 사용자가
|
|
19
|
+
설치할 앱·패키지 디렉터리로 이동해 같은 CLI를 실행한다.
|
|
20
|
+
내용이 다른 기존 파일은 기본적으로 유지하며, 교체할 파일만 사용자가 선택한다.
|
|
21
|
+
|
|
22
|
+
1단계 산출물은 설치 계약 문서, resolveCommand의 실행 의도 계산, 공통 타입과 기본 경로 상수다.
|
|
23
|
+
명령 계약 테스트와 타입·포맷 검사를 수행했다.
|
|
24
|
+
2단계에서는 설치 계획 계산과 파일 실행을 분리하고, 설정 읽기·검증·갱신 모듈을 구현했다.
|
|
25
|
+
기존 add는 공통 파일 계획·실행·출력 모듈을 사용하며 기존 명령 동작을 유지한다.
|
|
26
|
+
3단계에서는 원본 경로 기반 분류 메타데이터, 분류 계층 조회, 이름·전체 설명 검색과 필터를 구현했다.
|
|
27
|
+
독립 모듈 버전 보완에서 기존 add의 설정·버전 기록 저장을 연결했다.
|
|
28
|
+
4단계에서는 분류·유틸 선택과 파일 충돌 검토 화면을 구현했다. 화면의 결과는 선택값 또는 취소이며
|
|
29
|
+
파일을 생성하지 않는다. 5단계에서는 이 화면을 명령 진입·경로 입력·최종 확인과 연결했다.
|
|
30
|
+
현재 hermes와 add는 같은 대화형 설치 흐름을 사용하며 최종 Yes에서만 실행한다.
|
|
31
|
+
|
|
32
|
+
## 설치 흐름
|
|
33
|
+
|
|
34
|
+
설치 화면 진입 전에 현재 작업 디렉터리의 no-k.config.ts를 확인한다. 파일이 있으면 읽고 검증해
|
|
35
|
+
저장된 대분류별 경로를 초기값으로 사용하고, 없거나 해당 대분류의 경로가 없으면 기본값을 표시한다.
|
|
36
|
+
|
|
37
|
+
1. **대분류 선택**: “어떤 것들을 추가하시겠어요?”와 함께 Core / 라이브러리를 보여준다.
|
|
38
|
+
한쪽 또는 양쪽을 선택할 수 있다.
|
|
39
|
+
2. **하위 분류 선택**: 고른 대분류에 속한 하위 분류를 보여주고 여러 개 선택한다.
|
|
40
|
+
Core에서는 배열·문자열·객체 등을, 라이브러리에서는 Tailwind CSS 등을 선택한다.
|
|
41
|
+
3. **유틸 선택**: 선택한 하위 분류를 차례로 보여주고, 각 분류에서 여러 유틸을 고른다.
|
|
42
|
+
이름·설명을 검색하고 실행 환경과 의존성을 확인할 수 있다. 설치 기록과 해당 위치를
|
|
43
|
+
확인해 기존 항목에 “이미 설치됨”을 표시하며, 이번 설치 선택과 구분한다.
|
|
44
|
+
4. **설치 경로 확인**: 실제 선택한 유틸이 있는 대분류별로 경로를 입력받는다.
|
|
45
|
+
Core와 라이브러리 경로를 각각 지정하며, 저장된 설정 또는 기본값을 미리 채운다.
|
|
46
|
+
Enter로 표시된 값을 사용하거나 수정할 수 있고, 이전 단계에서 수정한 값은 유지한다.
|
|
47
|
+
5. **기존 파일 처리**: 내용이 다른 기존 파일이 있으면 목록을 보여준다. 기본은 유지이며,
|
|
48
|
+
교체할 파일만 여러 개 선택할 수 있다. 충돌이 없으면 이 단계를 건너뛴다.
|
|
49
|
+
6. **전체 선택 목록 확인**: 모든 분류에서 고른 유틸을 모아 보여준다. 목적지와 자동 포함할
|
|
50
|
+
보조 파일, 파일별 생성·유지·교체 결과, 필요한 npm 패키지, 설정 파일 생성·변경 내역을 함께 표시한다.
|
|
51
|
+
7. **yes/no**: yes일 때만 설치 계획을 실행해 codegen한다. no는 종료가 아니라 수정이다.
|
|
52
|
+
대분류·하위 분류·선택 유틸·대분류별 경로·파일별 교체 선택·옵션을 유지한 채 선택 단계로 돌아간다.
|
|
53
|
+
codegen이 성공하면 최종 경로와 설치한 유틸 목록을 no-k.config.ts에 저장한다.
|
|
54
|
+
|
|
55
|
+
선택을 수정한 후에는 설치 계획을 다시 계산하고 새 전체 목록을 보여준다. 수정 전 계획이나
|
|
56
|
+
이전 확인 결과로 파일을 쓰지 않는다. Ctrl+C 등의 명시적 취소는 파일 생성 없이 종료한다.
|
|
57
|
+
|
|
58
|
+
## 분류 계층
|
|
59
|
+
|
|
60
|
+
| 첫 대분류 | 원본 범위 | 다음 단계에서 선택할 하위 분류 |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| Core | packages/core | array, string, object, number, algorithm 등 |
|
|
63
|
+
| 라이브러리 | packages/lib | 현재는 Tailwind CSS |
|
|
64
|
+
|
|
65
|
+
Core / 라이브러리를 첫 화면에서 고른 후 하위 분류도 별도 화면에서 선택한다.
|
|
66
|
+
고르지 않은 대분류의 하위 분류나 고르지 않은 하위 분류의 유틸은 다음 선택 단계에 표시하지 않는다.
|
|
67
|
+
예를 들어 Core → 배열·문자열 → 각 분류의 유틸 순서로 좁히고,
|
|
68
|
+
라이브러리 → Tailwind CSS → cn·classVariant 등의 유틸 순서로 선택한다.
|
|
69
|
+
|
|
70
|
+
대분류와 하위 묶음은 같은 분류 값으로 섞지 않는다. 카탈로그 생성 단계에서 원본의
|
|
71
|
+
패키지 범위와 내부 경로를 각각 반영하며, 유틸 ID의 문자열 분할로 계층을 추측하지 않는다.
|
|
72
|
+
|
|
73
|
+
## 대분류별 설치 경로
|
|
74
|
+
|
|
75
|
+
- Core 유틸과 라이브러리 유틸의 목적지를 각각 입력받는다. 유틸을 고르지 않은 대분류의
|
|
76
|
+
경로는 요구하지 않는다.
|
|
77
|
+
- 설정이 없을 때도 경로 입력란에 기본값을 제공한다. 사용자가 입력을 바꾸지 않고 Enter를
|
|
78
|
+
누르면 표시된 경로를 사용하며, 최종 yes 후 설치가 성공하면 그 값을 설정 파일에 저장한다.
|
|
79
|
+
- 기본 경로는 Core가 src/utils/hermes, 라이브러리가 libs/hermes다.
|
|
80
|
+
코드에서는 DEFAULT_INSTALL_DIRECTORIES를 공통 정의로 사용한다.
|
|
81
|
+
- 두 대분류에 같은 경로를 지정하는 것도 가능하다. 각 목적지 아래의 기존 유틸 상대 경로는 유지한다.
|
|
82
|
+
- 단일 --dir 옵션은 양쪽 경로 입력의 공통 초기값으로 사용하고, 화면에서 각각 수정하는 것이
|
|
83
|
+
현재 동작이다. 초기값은 명시적인 --dir → 저장된 대분류별 경로 → 기본값 순서로 정하고,
|
|
84
|
+
화면에서 수정한 값이 최종값이다. 인자를 전달해도 경로·최종 설치 목록을 확인하는 흐름은 유지한다.
|
|
85
|
+
- 최종 확인 목록에는 각 유틸이 생성될 실제 목적지를 표시한다. yes 한 번으로 선택한
|
|
86
|
+
모든 대분류의 설치를 진행하고, no에서는 각각 입력한 경로까지 보존한다.
|
|
87
|
+
- 경로를 수정하면 전체 설치 계획을 다시 계산한다. 파일 중복과 충돌은 상대 파일명만이 아닌
|
|
88
|
+
최종 절대 목적지 기준으로 판단하며, 필요한 LICENSE도 목적지별로 계획한다.
|
|
89
|
+
|
|
90
|
+
## 프로젝트 설정 파일
|
|
91
|
+
|
|
92
|
+
CLI를 실행한 디렉터리에 no-k.config.ts를 생성한다. 아래는 두 대분류의 기본 경로로
|
|
93
|
+
array-chunk와 tailwindcss-cn을 설치했을 때의 파일 구조다.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
export default {
|
|
97
|
+
schemaVersion: 1,
|
|
98
|
+
directories: {
|
|
99
|
+
core: "src/utils/hermes",
|
|
100
|
+
lib: "libs/hermes",
|
|
101
|
+
},
|
|
102
|
+
installed: [
|
|
103
|
+
{ name: "array-chunk", scope: "core", directory: "src/utils/hermes" },
|
|
104
|
+
{ name: "tailwindcss-cn", scope: "lib", directory: "libs/hermes" },
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- **최초 설치**: 파일이 없으면 선택과 기본 경로 확인·수정을 거쳐 최종 확인에 설정 파일 생성 내역을
|
|
110
|
+
포함한다. yes로 승인한 codegen이 성공하면 파일을 생성한다.
|
|
111
|
+
- **재실행**: 기존 파일을 읽어 경로 입력의 초기값으로 사용한다. 대분류·하위 분류·유틸 선택과
|
|
112
|
+
최종 yes/no 확인은 동일하게 거치며, 화면에서 바꾼 경로는 설치 성공 후 설정에 반영한다.
|
|
113
|
+
- **일부 대분류만 설치**: 이번에 지정한 경로를 기존 설정에 병합한다. 예를 들어 Core만
|
|
114
|
+
설치해도 저장되어 있던 라이브러리 경로를 지우지 않는다. 값이 같으면 다시 쓰지 않는다.
|
|
115
|
+
- **수정·취소·미리보기**: no로 돌아가거나 취소한 경우, --dry-run인 경우에는 설정을 생성하거나
|
|
116
|
+
갱신하지 않는다. 경로 변경은 다음 실행에서 사용할 목적지를 바꾸며, 기존 파일 이동을 뜻하지 않는다.
|
|
117
|
+
- **설정 위치**: 실행 시점의 현재 작업 디렉터리(cwd)에 있는 no-k.config.ts만 읽고 갱신한다.
|
|
118
|
+
없으면 같은 위치에 생성한다. 상위 디렉터리의 설정을 자동으로 찾아 적용하거나 package.json·
|
|
119
|
+
워크스페이스 구조로 설치할 앱을 추론하지 않는다.
|
|
120
|
+
- **상대 경로**: 설정 파일의 디렉터리, 즉 CLI를 실행한 디렉터리를 기준으로 해석한다.
|
|
121
|
+
기본 경로와 --dir, 화면에서 입력한 상대 경로, 설치 기록의 상대 경로에 같은 기준을 적용한다.
|
|
122
|
+
절대 경로를 직접 지정하는 것도 가능하다.
|
|
123
|
+
- **모노레포 사용**: 사용자가 설치할 앱·패키지 폴더에서 실행한다. 예를 들어 apps/web에서
|
|
124
|
+
실행하면 apps/web/no-k.config.ts와 그 아래 src/utils/hermes·libs/hermes를 사용한다.
|
|
125
|
+
각 실행 위치의 설정과 설치 기록은 독립적이다.
|
|
126
|
+
- **기존 파일 보존**: 로드 시 원문을 보관하고 CLI가 관리하는 필드만 갱신한다. 사용자 정의 필드와
|
|
127
|
+
주석을 유지하며 경로 값, 설치 기록 추가와 버전 출처만 갱신한다. 안전하게 갱신할 수 없는 형식은 통째로 재생성하지
|
|
128
|
+
않고 수정할 위치를 안내한다. 읽기·검증 실패도 파일 부재로 취급해 덮어쓰지 않는다.
|
|
129
|
+
- **실행 중 변경과 실패**: 유틸 파일과 설정 파일 모두 쓰기 전에 상태를 다시 확인한다.
|
|
130
|
+
codegen이 실패하면 설정을 갱신하지 않는다. codegen 후 설정 저장에 실패하면 생성된 파일과
|
|
131
|
+
설정 저장 실패를 구분해 알린다. 전체 쓰기를 원자적이라고 가정하지 않는다.
|
|
132
|
+
|
|
133
|
+
ProjectConfig는 경로와 설치한 유틸 목록을 표현한다. 2단계에서 읽기·갱신 모듈을 구현했으며,
|
|
134
|
+
5단계에서 설치 화면에 연결했다.
|
|
135
|
+
|
|
136
|
+
설정은 export default 객체 리터럴 형식으로 읽는다. 문자열·숫자·불리언·null·배열·객체,
|
|
137
|
+
주석, 작은따옴표·큰따옴표, 마지막 쉼표, 최상위 as const를 지원한다. 현재 Node 지원 범위에서
|
|
138
|
+
런타임 의존성을 추가하지 않고 같은 방식으로 동작하도록 데이터 리터럴을 정적으로 파싱한다.
|
|
139
|
+
import·함수 호출·변수 참조·spread·satisfies 등 실행이나 별도 TypeScript 해석이 필요한 구문은
|
|
140
|
+
지원하지 않으며, 해당 위치를 안내하고 원본을 보존한다. 설정 파일의 코드를 실행하지 않는다.
|
|
141
|
+
|
|
142
|
+
## 설치한 유틸 기록
|
|
143
|
+
|
|
144
|
+
- installed에는 선택한 카탈로그 항목의 안정적인 ID(name), 대분류(scope), 설치 당시
|
|
145
|
+
목적지(directory)를 기록한다. 독립 버전 도입 후에는 source에 요청 버전, 실제 모듈 버전·해시와
|
|
146
|
+
일치 여부도 저장한다. source가 없는 기존 기록은 위치 정보로 보존한다.
|
|
147
|
+
- directories는 다음 설치에 제안할 경로이고, installed의 directory는 실제 설치 당시
|
|
148
|
+
경로다. 기본 경로를 바꿔도 이전 기록의 경로를 일괄 변경하지 않는다. 두 경로 모두 설정
|
|
149
|
+
파일의 디렉터리를 기준으로 해석한다.
|
|
150
|
+
- 전체 codegen이 성공한 뒤 기존 기록에 이번 설치 항목을 병합한다. 파일 내용이 같아 keep으로
|
|
151
|
+
처리된 선택 항목도 성공한 설치에 포함한다. 실패·취소·no·dry-run에서는 기록을 바꾸지 않는다.
|
|
152
|
+
- 병합 키는 대분류·유틸 ID·정규화한 절대 목적지다. 같은 항목을 같은 경로에 다시 설치해도
|
|
153
|
+
중복 기록하지 않는다. 다른 경로에 설치한 경우에는 기존 위치의 기록도 유지한다.
|
|
154
|
+
- 이번에 고르지 않은 항목, 다른 대분류의 기록, 현재 카탈로그에서 사라진 ID도 보존한다.
|
|
155
|
+
선택 화면에서 체크를 해제하는 동작은 기존 유틸이나 설치 기록을 삭제하는 동작이 아니다.
|
|
156
|
+
- 재실행에서는 기록과 실제 설치 위치를 대조해 “이미 설치됨”과 설치 경로를 표시한다.
|
|
157
|
+
기록이 있어도 해당 유틸 파일이 없으면 “설치 기록 있음·파일 없음”으로 구분한다.
|
|
158
|
+
저장된 기록을 자동으로 이번 설치 선택에 추가하거나 재설치하지 않는다.
|
|
159
|
+
- installed가 없는 설정은 빈 목록으로 읽어 호환한다. 이름이나 경로가 잘못된 기록은
|
|
160
|
+
검증 오류로 알리고 원본 설정을 보존한다.
|
|
161
|
+
|
|
162
|
+
설치 기록과 실제 파일 해시를 대조해 버전을 확인한다. 기록만으로 현재 파일 상태나 원격 최신
|
|
163
|
+
버전을 단정하지 않는다. 기존 파일을 다시 선택하면 실제 파일을 검사해 설치 계획을 계산하며,
|
|
164
|
+
내용이 다른 파일은 아래의 파일별 선택을 적용한다. 자세한 계약은 [독립 버전 관리](module-versioning.md)에 있다.
|
|
165
|
+
|
|
166
|
+
## 기존 파일의 유지와 교체
|
|
167
|
+
|
|
168
|
+
| 목적지의 파일 상태 | 기본 처리 | 사용자 선택 |
|
|
169
|
+
| --- | --- | --- |
|
|
170
|
+
| 파일 없음 | create | 최종 확인에서 생성 내역 확인 |
|
|
171
|
+
| 생성할 내용과 같음 | keep | 다시 쓰지 않고 유지 |
|
|
172
|
+
| 생성할 내용과 다름 | keep | 교체할 파일만 선택해 replace로 변경 |
|
|
173
|
+
|
|
174
|
+
- 내용이 다른 파일의 목록에는 목적지, 영향을 받는 유틸, 유지·교체 선택을 표시한다.
|
|
175
|
+
선택하기 전에 기존 내용과 생성할 내용의 차이를 확인할 수 있게 한다.
|
|
176
|
+
- 교체할 파일이 없어도 다음 단계로 진행할 수 있다. 선택하지 않은 충돌 파일은 그대로 두고,
|
|
177
|
+
새 파일 생성과 사용자가 고른 파일의 교체를 하나의 최종 계획으로 보여준다.
|
|
178
|
+
- 공유 보조 파일과 LICENSE도 절대 목적지 기준으로 한 번만 표시한다. 공유 파일의 한 선택은
|
|
179
|
+
그 파일을 사용하는 모든 선택 유틸에 적용되므로, 영향을 받는 유틸 목록도 함께 보여준다.
|
|
180
|
+
- 최종 목록은 “유지 — 내용 동일”과 “유지 — 기존 내용 보존”을 구분한다. 파일을 교체하도록
|
|
181
|
+
선택해도 최종 yes 전에는 쓰지 않는다. 일부 기존 파일을 유지한 유틸을 전체 교체 완료로 표시하지 않는다.
|
|
182
|
+
- no로 돌아가도 교체 선택은 보존한다. 계획 재계산 시 절대 목적지·기존 내용·생성할 내용이
|
|
183
|
+
모두 같은 파일에만 이전 선택을 재사용한다. 달라진 파일은 기본 유지로 돌아가 다시 선택받는다.
|
|
184
|
+
- 실행 직전 파일 상태가 검토 당시와 달라지면 쓰기를 시작하지 않고 계획을 다시 계산해
|
|
185
|
+
확인받는다. 이전 경로나 이전 내용에 대한 교체 선택을 새 파일에 적용하지 않는다.
|
|
186
|
+
- 파일별 교체 선택은 이번 실행의 상태로 보관한다. no-k.config.ts에는 설치 항목과 위치를
|
|
187
|
+
기록하며, 교체 선택을 다음 실행의 자동 승인으로 저장하지 않는다.
|
|
188
|
+
|
|
189
|
+
FileConflict는 기존 내용·생성할 내용·절대 목적지와 영향을 받는 항목을 담고,
|
|
190
|
+
FileConflictDecision은 검토한 충돌 정보와 keep/replace 선택을 함께 보관한다.
|
|
191
|
+
InstallRequest.fileDecisions에 유효한 replace 선택이 없는 충돌 파일은 keep으로 계획한다.
|
|
192
|
+
실행기는 최종 FilePlan.action을 따르며, 일괄 overwrite 플래그로 파일별 결정을 덮지 않는다.
|
|
193
|
+
|
|
194
|
+
승인된 계획을 실행한 후 필수 파일들이 존재하는 선택 항목을 설치 기록에 병합한다.
|
|
195
|
+
사용자가 유지하기로 한 기존 파일이 포함된 경우도 결과에 명시한다. 설치 기록은 위치를
|
|
196
|
+
기억하며, source.status가 modified이면 모든 파일이 현재 요청 버전과 같다는 의미로 사용하지 않는다.
|
|
197
|
+
필수 의존 모듈이 수정되어 버전을 확인할 수 없거나 호환 범위를 벗어나면 실행 전에 중단한다.
|
|
198
|
+
|
|
199
|
+
## 명령별 진입 조건
|
|
200
|
+
|
|
201
|
+
설치 명령의 대화형 진입은 install-wizard 하나다. 항목이나 경로 인자가 모두 있어도
|
|
202
|
+
대분류 선택과 최종 확인을 건너뛰지 않는다.
|
|
203
|
+
|
|
204
|
+
| 입력 | 1단계 계약의 실행 의도 |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| hermes, 항목 없는 hermes add | 초기 선택 없이 대분류 선택부터 시작 |
|
|
207
|
+
| add ID... | 항목을 초기 선택값으로 전달하고 대분류 선택부터 시작 |
|
|
208
|
+
| add ID... --dir 경로 | 초기 선택과 경로를 전달하고 대분류 선택부터 시작 |
|
|
209
|
+
| hermes --dir 경로 | 경로를 전달하고 대분류 선택부터 시작 |
|
|
210
|
+
| list, --help, --version | 해당 정보 출력 |
|
|
211
|
+
|
|
212
|
+
- 인자로 받은 항목도 화면에서 해제하거나 다른 항목을 추가할 수 있다. 해당 항목의 분류를
|
|
213
|
+
카탈로그에서 찾아 대분류와 하위 분류의 초기 선택에 반영한다. 4단계 선택 UI에 구현했으며
|
|
214
|
+
5단계에서 실제 명령 인자를 이 UI에 연결했다.
|
|
215
|
+
- 입력 ID는 순서를 유지해 중복을 제거한다. 실제 카탈로그 존재 여부는 화면 진입 전에 검증한다.
|
|
216
|
+
- 유효한 옵션을 파싱한 후 --help, --version 순으로 우선 처리한다.
|
|
217
|
+
- list에는 설치 옵션이나 항목 인자를 전달하지 않는다. 잘못된 명령·옵션은 오류다.
|
|
218
|
+
- 빈 문자열이나 공백만으로 설치 경로를 지정하면 오류다. 정상 경로의 공백은 보존한다.
|
|
219
|
+
- stdin과 stdout이 모두 TTY일 때 선택 화면을 사용할 수 있다. 이번 범위의 비대화형 add는
|
|
220
|
+
선택·확인이 가능한 터미널을 안내하고 종료하며, 완전한 인자만으로 설치를 자동 승인하지 않는다.
|
|
221
|
+
CI 등 자동화용 설치 지원과 승인 옵션은 후속 범위로 둔다.
|
|
222
|
+
|
|
223
|
+
## 상태와 모듈 간 계약
|
|
224
|
+
|
|
225
|
+
| 공통 정의 | 담는 내용과 책임 |
|
|
226
|
+
| --- | --- |
|
|
227
|
+
| CliArguments / CliOptions | 공통 파서의 위치 인자와 옵션 |
|
|
228
|
+
| TerminalState | 입력·출력 TTY 상태 |
|
|
229
|
+
| CommandIntent | 도움말·버전·목록 또는 설치 화면 진입 |
|
|
230
|
+
| InstallWizardRequest | 초기 선택 ID, --dir로 받은 공통 초기 경로, dry-run과 초기 화면에서 처리할 overwrite 옵션 |
|
|
231
|
+
| UtilityScope | 첫 대분류 ID: core 또는 lib |
|
|
232
|
+
| CatalogCategoryId | 대분류를 포함한 하위 분류 ID: core/array, core/data-structure, lib/tailwindcss 등 |
|
|
233
|
+
| DEFAULT_INSTALL_DIRECTORIES | 최초 경로 기본값: core는 src/utils/hermes, lib는 libs/hermes |
|
|
234
|
+
| InstallDirectories | 대분류별 경로. 아직 설정하지 않은 범위는 생략하며 core와 lib를 독립적으로 보관 |
|
|
235
|
+
| InstalledItem | 설치한 유틸의 ID, 대분류와 설치 당시 목적지 |
|
|
236
|
+
| ProjectConfig | no-k.config.ts의 스키마 버전, 대분류별 경로, 설치한 유틸 기록(installed) |
|
|
237
|
+
| ProjectConfigState | 실행 디렉터리의 no-k.config.ts 절대 경로와 부재 여부. 기존 파일이면 원문과 검증한 값도 보관 |
|
|
238
|
+
| InstallDraft | 읽은 설정 상태, 분류·유틸 선택, 대분류별 경로, 파일별 선택(fileDecisions), dry-run |
|
|
239
|
+
| ItemNames / SelectionResult | 비어 있지 않은 유틸 ID 목록 또는 명시적 취소 |
|
|
240
|
+
| InstallTarget | 한 대분류의 ID, 비어 있지 않은 유틸 ID 목록, 해당 목적지 |
|
|
241
|
+
| InstallRequest | 읽은 설정 상태, InstallTarget 목록, 파일별 선택과 dry-run. 일괄 overwrite 대신 파일별 결정을 전달 |
|
|
242
|
+
| FilePlan | 상대 파일 경로·생성 후보 내용·절대 목적지·검토 당시 내용(existingContent)과 create/keep/replace. null은 파일 부재 |
|
|
243
|
+
| FileConflict | 내용이 다른 파일의 기존·생성 후보 내용, 목적지, 영향을 받는 유틸 ID |
|
|
244
|
+
| FileConflictDecision | 검토한 FileConflict와 사용자가 선택한 keep/replace |
|
|
245
|
+
| InstallPlan | 설치 요청, 유틸 파일 계획(files), 충돌 목록(conflicts), 설정 파일 계획(configFile), 의존성과 런타임 요구사항 |
|
|
246
|
+
| ReviewResult | yes의 confirmed + plan 또는 no의 edit + draft |
|
|
247
|
+
|
|
248
|
+
대분류·하위 분류·유틸 선택은 표시 순번이 아닌 안정적인 ID로 각각 저장한다.
|
|
249
|
+
하위 분류 ID에는 대분류 범위를 포함한다(예: core/array, core/string, lib/tailwindcss).
|
|
250
|
+
분류별 유틸 목록은
|
|
251
|
+
같은 카탈로그에서 구하며, 검색 결과나 화면 이동이 선택을 초기화하지 않게 한다.
|
|
252
|
+
해제한 분류의 유틸은 현재 설치 대상에서 제외한다. 이전 하위 분류·유틸 체크는 따로 보존하며,
|
|
253
|
+
분류를 다시 선택하면 복원한다. `CatalogSelectionResult.names`에는 현재 활성 선택만 담고,
|
|
254
|
+
`state`에는 비활성 분류 안의 체크도 담아 이후 수정 화면에서 복원한다.
|
|
255
|
+
|
|
256
|
+
no의 edit 결과에는 선택 초안만 담는다. 실행할 계획은 전달하지 않으므로 수정 후에는
|
|
257
|
+
다시 계획을 계산하고 확인받아야 한다. 유틸 선택이 비어 있으면 최종 codegen 확인으로
|
|
258
|
+
진행하지 않는다. yes 확인만 파일 생성 권한으로 취급한다.
|
|
259
|
+
|
|
260
|
+
InstallRequest.targets의 각 directory는 입력 경로를 보존하고, 계획 계산 단계에서 각각
|
|
261
|
+
설정 파일의 디렉터리를 기준으로 절대 경로로 바꾼다. 선택 항목은 해당 대분류의 target에 한 번만 포함하고, 유틸이 있는
|
|
262
|
+
모든 대분류의 경로가 유효해야 전체 설치 계획을 만들 수 있다.
|
|
263
|
+
계획 계산 자체는 디렉터리·파일을 생성하지 않는다. 필요한 보조 파일과 LICENSE를 포함하고
|
|
264
|
+
중복 파일·의존성을 합친다. 실제 쓰기 직전에도 파일 상태와 충돌 조건을 확인한다.
|
|
265
|
+
configFile도 같은 FilePlan 구조를 사용하지만 카탈로그 유틸 파일과 구분한다. 저장할 설정은
|
|
266
|
+
기존 설정에 최종 경로와 이번 설치 기록을 병합해 계산하며, 유틸 codegen 성공 이후에만
|
|
267
|
+
이 계획을 실행한다.
|
|
268
|
+
설정 갱신은 유틸 파일의 교체 선택과 별개이며, 최종 확인에 함께 표시한다.
|
|
269
|
+
|
|
270
|
+
## 기존 옵션과 기술 구현 기준
|
|
271
|
+
|
|
272
|
+
- --dry-run은 같은 선택 과정을 거쳐 설치 예정 내역을 출력하고 끝낸다. codegen이나 설정 파일 쓰기를 하지 않는다.
|
|
273
|
+
- --overwrite의 호환안은 최초 충돌 화면에서 교체 후보를 모두 미리 선택하는 것이다.
|
|
274
|
+
사용자는 파일별로 해제할 수 있고, 최종 yes도 그대로 거친다. 이 옵션은 InstallWizardRequest에만
|
|
275
|
+
전달하며, 최종 설치 요청의 파일별 결정을 덮어쓰거나 재검토가 필요한 새 충돌에 자동 적용하지 않는다.
|
|
276
|
+
일반 실행의 기본값은 유지다. 5단계에서 옵션을 초기 화면에 연결했다.
|
|
277
|
+
- 명시적 취소는 cancel, Ctrl+C는 interrupt, 입력 종료는 eof로 구분한다.
|
|
278
|
+
종료 코드는 각각 0, 130, 1이다. 최종 no는 이 취소 결과에 포함하지 않는다.
|
|
279
|
+
- 초기에는 Node 기본 모듈을 사용하는 구현안을 검토했다. 4단계 미리보기의 반응 속도와
|
|
280
|
+
시각적 개선 요청에 따라 Ink·React를 도입했으며, 선택 계약과 파일 생성 경계는 유지한다.
|
|
281
|
+
- 검색·선택 상태와 터미널 출력을 분리한다. 실제 화면 이동·다중 선택·크기 변경·복원 검증은
|
|
282
|
+
4단계에서 수행한다.
|
|
283
|
+
|
|
284
|
+
## 2단계 모듈과 검증
|
|
285
|
+
|
|
286
|
+
| 모듈 | 역할 |
|
|
287
|
+
| --- | --- |
|
|
288
|
+
| project-config.ts | cwd의 설정 읽기·검증, 초기 경로, 기존 설정과 설치 기록 병합, 설정 파일 계획 |
|
|
289
|
+
| config-syntax.ts | 데이터 리터럴 구문 읽기와 원문 위치 기반 부분 수정 |
|
|
290
|
+
| install.ts | 유틸·설정의 전체 계획 계산, 파일별 교체 선택 반영, 유틸 완료 후 설정 저장 |
|
|
291
|
+
| files.ts | 목적지·심볼릭 링크 검사, 파일 상태 재확인, 실제 파일 쓰기 |
|
|
292
|
+
| report.ts | 파일별 결과와 npm 의존성·런타임 요구사항의 텍스트 출력 구성 |
|
|
293
|
+
| versions.ts | 모듈별 버전·파일 해시 기록과 내부 의존성·외부 peer의 호환 범위 검사 |
|
|
294
|
+
|
|
295
|
+
- 계획 계산은 읽기만 수행한다. 실행 시 유틸과 설정을 모두 먼저 검증하고, 각 쓰기 직전에도
|
|
296
|
+
기존 내용과 목적지를 확인한다. 변경된 계획은 PlanChangedError로 알려 다시 검토할 수 있게 한다.
|
|
297
|
+
- 내용이 다른 기존 파일은 유지가 기본이며, 목적지와 양쪽 내용이 일치하는 파일별 선택만 적용한다.
|
|
298
|
+
기존 CLI의 --overwrite 처리는 기존 명령 어댑터가 명시적인 파일별 결정으로 바꿔 전달한다.
|
|
299
|
+
- 기존 파일 교체는 같은 디렉터리의 임시 파일에 쓴 후 이름을 바꾸는 방식으로 처리한다.
|
|
300
|
+
여러 파일과 설정 전체를 하나의 원자적 작업으로 취급하지 않는다.
|
|
301
|
+
- 실행 도중 실패하면 InstallExecutionError에 파일 처리/설정 저장 단계와 처리한 파일 목록을 담는다.
|
|
302
|
+
유틸 처리와 사후 확인이 모두 성공한 뒤에만 설정을 저장한다.
|
|
303
|
+
- 기존 CLI 회귀와 신규 모듈 테스트에서 계획·dry-run의 무쓰기, 경로·설치 기록 복원과 병합,
|
|
304
|
+
주석 보존, 파일별 교체, 상태 변경 감지, 심볼릭 링크, 설정 저장 실패를 검증했다.
|
|
305
|
+
- 3단계에서 추가한 카탈로그의 scope와 InstallTarget의 scope가 일치하는지 확인한다.
|
|
306
|
+
다른 대분류에 속한 항목을 요청하면 유틸·설정 파일을 쓰기 전에 실패한다.
|
|
307
|
+
|
|
308
|
+
## 3단계 카탈로그와 검색
|
|
309
|
+
|
|
310
|
+
카탈로그의 각 항목에는 scope와 category를 필수로 기록한다. 필수 메타데이터가 추가되므로
|
|
311
|
+
3단계에서 생성 catalog.json의 schemaVersion을 2로 올렸고, 독립 버전 정보 추가로 현재는 3이다.
|
|
312
|
+
이전 스키마를 읽으면 CLI 재빌드를 안내한다.
|
|
313
|
+
프로젝트의 no-k.config.ts는 기존 schemaVersion 1을 사용한다.
|
|
314
|
+
|
|
315
|
+
| 원본 진입 경로 | scope | category |
|
|
316
|
+
| --- | --- | --- |
|
|
317
|
+
| packages/core/array/chunk/index.ts | core | core/array |
|
|
318
|
+
| packages/core/data-structure/priority-queue/index.ts | core | core/data-structure |
|
|
319
|
+
| packages/core/algorithm/graph/dijkstra/index.ts | core | core/algorithm |
|
|
320
|
+
| packages/lib/tailwindcss/cn/index.ts | lib | lib/tailwindcss |
|
|
321
|
+
|
|
322
|
+
- Core의 하위 분류는 진입 파일이 속한 첫 디렉터리이며, algorithm/graph처럼 더 깊은 경로는
|
|
323
|
+
algorithm에 속한다. 라이브러리 하위 분류는 packages/lib 바로 아래의 패키지 디렉터리다.
|
|
324
|
+
- 빌드 시 라이브러리 패키지도 탐색한다. 새 유틸, Core 분류, 라이브러리 패키지는 다음 빌드에
|
|
325
|
+
자동 반영된다. 라이브러리는 package.json과 tsconfig.json 및 기존 유틸 진입 구조를 갖춘다.
|
|
326
|
+
심볼릭 링크인 패키지 디렉터리와 package.json이 없는 디렉터리는 탐색 대상에서 제외한다.
|
|
327
|
+
- 유틸 ID의 접두어나 함께 복사되는 보조 파일은 소속 분류를 결정하지 않는다.
|
|
328
|
+
|
|
329
|
+
catalog-query.ts는 파일 접근·터미널 입력 없이 다음 함수를 제공한다.
|
|
330
|
+
|
|
331
|
+
| 함수 | 동작 |
|
|
332
|
+
| --- | --- |
|
|
333
|
+
| filterCatalog(catalog, filter) | query, scopes, categories를 조합해 일치하는 유틸 조회 |
|
|
334
|
+
| getCatalogGroups(catalog, filter) | 같은 필터 결과를 Core / 라이브러리 → 하위 분류 → 유틸 계층으로 구성 |
|
|
335
|
+
| summarizeDescription(description, maxCharacters = 100) | 화면용 한 줄 요약 생성. description 원문은 보존 |
|
|
336
|
+
|
|
337
|
+
- 검색 대상은 유틸 ID(name)와 README 소개 문단 전체(description)다. 표시용 요약, 보조 파일,
|
|
338
|
+
npm 의존성 이름으로 검색 범위를 대체하지 않는다.
|
|
339
|
+
- 영문 대소문자와 Unicode 표현 차이를 정규화한다. 공백으로 나눈 검색어가 모두 이름 또는
|
|
340
|
+
설명에 포함되어야 한다. 예를 들어 chunk 배치로 array-chunk의 이름과 설명을 함께 찾는다.
|
|
341
|
+
- 같은 필터 안의 여러 scope 또는 category는 합집합이고, scope·category·검색 조건 사이는
|
|
342
|
+
교집합이다. 필터 생략은 제한 없음, 빈 배열은 선택 없음이며 빈 결과를 반환한다.
|
|
343
|
+
- 결과는 카탈로그의 유틸 순서를 유지한다. 계층은 Core 다음 라이브러리 순서이며, 하위 분류는
|
|
344
|
+
ID 순으로 정렬한다. 결과가 없는 분류는 생략하고 대분류별 항목 수를 제공한다.
|
|
345
|
+
- 조회 함수는 카탈로그나 선택 상태를 수정하지 않는다. 화면 이동과 선택 유지 상태는 4단계에서 관리한다.
|
|
346
|
+
- 요약은 공백을 정리하고 길이를 넘으면 …를 붙인다. 한글 조합 문자나 이모지를 중간에서 자르지
|
|
347
|
+
않도록 Intl.Segmenter를 사용한다. 이 길이는 글자 단위이며 터미널 열 너비 처리는 4단계 범위다.
|
|
348
|
+
Core의 truncate는 UTF-16 길이를 기준으로 하고 CLI 런타임 배포 범위 밖이므로 직접 사용하지 않는다.
|
|
349
|
+
|
|
350
|
+
현재 카탈로그는 Core 11개 하위 분류의 109개 유틸, Tailwind CSS의 4개 유틸을 제공한다.
|
|
351
|
+
3단계 완료 시 기존 CLI 회귀와 신규 분류·검색 테스트 65개, strict TypeScript 빌드와 정적 검사를 통과했다.
|
|
352
|
+
새 검색 모듈을 포함한 npm tarball 243개 파일과 실행 권한을 확인했고, 기존 유틸 템플릿
|
|
353
|
+
228개의 내용은 변경 전과 동일했다. 이후 독립 버전 관리에서 semver 런타임 의존성과 모듈 전체 복사,
|
|
354
|
+
버전 기록·호환성 검사를 추가했다. 기존 add 명령의 설정 저장은 이 보완 단계에서 연결했다.
|
|
355
|
+
|
|
356
|
+
## 4단계 터미널 선택 화면
|
|
357
|
+
|
|
358
|
+
- `CatalogSelection`은 안정적인 분류·유틸 ID로 선택을 보관한다. 분류를 해제하면 하위 유틸을
|
|
359
|
+
활성 설치 대상에서 제외하지만 체크는 기억한다. 다시 선택하면 하위 분류와 유틸 체크가 복원된다.
|
|
360
|
+
- `selectCatalog`는 대분류, 선택한 대분류별 하위 분류, 선택한 하위 분류별 유틸 화면을 순서대로
|
|
361
|
+
보여준다. 유틸을 하나도 고르지 않으면 완료할 수 없으며, 이전 화면으로 돌아가거나 취소할 수 있다.
|
|
362
|
+
- `Picker`는 검색과 체크를 별도로 보관한다. 검색 결과 밖의 체크, 전체 유틸 선택 수를 표시하며,
|
|
363
|
+
`a`는 현재 검색 결과만 선택·해제한다. 이전 화면에서는 체크와 검색어·포커스를 복원한다.
|
|
364
|
+
- `/`로 검색 입력을 시작하고 Enter로 입력을 끝낸다. 입력 중 Space와 `q`, `a`도 검색어이며,
|
|
365
|
+
Esc는 검색을 비우고 입력 모드를 끝낸다. 목록에서는 Space로 체크하고 Enter로 다음 화면에 간다.
|
|
366
|
+
Tab으로 상세, Esc로 이전, `q`로 취소, Ctrl+L로 검색 초기화를 수행하며 좌우 방향키는 무시한다.
|
|
367
|
+
- 상세 화면은 전체 설명, 함께 생성되는 모듈과 버전, npm·개발·peer 의존성, Node/DOM 요구사항,
|
|
368
|
+
설치 위치와 생성 파일을 보여준다. ↑↓, PageUp/PageDown, Home/End로 스크롤할 수 있다.
|
|
369
|
+
- `inspectInstallations`는 설정에 기록된 위치와 실제 파일을 읽어 설치됨, 로컬 수정, 버전 기록 없음,
|
|
370
|
+
일부 파일 없음, 기록만 남음, 위치 확인 불가를 구분한다. 의존 모듈로 함께 설치한 항목도 표시한다.
|
|
371
|
+
과거 버전의 파일 목록은 설치 당시 기록을 기준으로 확인하며, 설치 기록을 자동 체크로 사용하지 않는다.
|
|
372
|
+
- `selectConflicts`는 절대 목적지별로 한 번만 유지·교체를 선택하고 영향을 받는 모든 유틸을 보여준다.
|
|
373
|
+
기본은 모두 유지다. 이전 선택은 목적지·기존 내용·생성 후보가 모두 같을 때만 복원하며,
|
|
374
|
+
뒤로 가기에도 파일별 결정을 반환한다. 5단계에서 실제 계획과 --overwrite 초기값을 연결했다.
|
|
375
|
+
- 파일 차이는 공통 앞뒤 문맥을 포함한 단일 hunk로 보여준다. 최종 개행 유무와 CRLF 차이도 표시한다.
|
|
376
|
+
아주 긴 차이도 상세 화면에서 스크롤하며, 제어문자는 실행하거나 숨기지 않고 이스케이프 표기로 보여준다.
|
|
377
|
+
- `PickerView`는 Ink·React로 단계 표시, 컬러 포커스·체크, 검색 창과 파일 차이를 표시한다.
|
|
378
|
+
101열 이상에서는 목록과 상세 패널을 나란히 배치하고 좁거나 낮은 화면에서는 한 열로 전환한다.
|
|
379
|
+
입력은 즉시 상태에 반영하고 같은 읽기에서 받은 연속 입력의 렌더링을 합친다.
|
|
380
|
+
Ink의 부분 갱신과 최대 60fps 출력으로 키마다 전체 화면을 지우지 않는다.
|
|
381
|
+
- `withSelectionTerminal`은 Ink로 키 입력과 터미널 크기 변경을 처리한다. 임시 화면에서
|
|
382
|
+
작업하고 정상 완료, 취소, Ctrl+C, EOF, 오류 뒤에 입력 모드·커서·이전 화면을 복원한다.
|
|
383
|
+
입력과 출력이 모두 TTY가 아니면 기다리지 않고 오류를 반환한다. 최소 화면은 21열 × 8행이다.
|
|
384
|
+
직접 런타임 의존성에 `ink`, `react`를 추가했다. 생성되는 유틸의 의존성은 바뀌지 않는다.
|
|
385
|
+
일반·application 모드 방향키, 여러 키가 합쳐진 입력과 분할된 escape sequence를 처리한다.
|
|
386
|
+
검색 중 bracketed paste는 줄바꿈을 공백으로 바꾸고 명령으로 실행하지 않는다.
|
|
387
|
+
|
|
388
|
+
`pnpm --filter @no-k/hermes run preview:ui`로 개발용 화면을 실행할 수 있다. 초기 유틸 ID를
|
|
389
|
+
추가 인자로 받을 수 있고, 완료 후 선택 결과를 표시한다. `--json`은 결과 ID와 재진입용 상태를 출력한다.
|
|
390
|
+
`tsx`로 소스를 실행해 매번 전체 빌드하는 비용을 제거했다. 기존 catalog.json이 없으면 카탈로그를
|
|
391
|
+
메모리에서 생성한다. 이미 생성된 카탈로그에 유틸 변경을 반영하려면 CLI를 다시 빌드한다.
|
|
392
|
+
`add` 명령이나 설치 실행을 호출하지 않는다.
|
|
393
|
+
이 스크립트와 테스트는 npm 배포 파일에 포함하지 않으며, 재사용할 UI 모듈은 dist/lib에 포함한다.
|
|
394
|
+
|
|
395
|
+
자동화 검증은 계층 선택·설치 상태·검색·파일 차이와 기존 설치 회귀를 포함한다. UI 검증에는
|
|
396
|
+
연속 방향키와 분할 입력, 한국어 검색·붙여넣기, 화면 전환 사이의 입력 유지, 넓은·좁은 화면,
|
|
397
|
+
취소·Ctrl+C·EOF·오류 후 입력 모드와 커서 복원이 포함된다. 실제 PTY에서도 미리보기 명령과
|
|
398
|
+
계층 탐색·파일 차이를 확인하며, 선택·검토만으로 소비 프로젝트에 파일을 생성하지 않아야 한다.
|
|
399
|
+
|
|
400
|
+
## 후속 범위
|
|
401
|
+
|
|
402
|
+
비대화형 자동화 설치 지원과 명시적인 승인 옵션은 별도로 논의한다.
|
|
403
|
+
|
|
404
|
+
검사는 계약 코드의 일관성과 기존 CLI의 회귀 여부를 확인한다. 테스트 통과를 사용자와의
|
|
405
|
+
UX 합의 완료로 취급하지 않는다.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# 유틸 독립 버전 관리
|
|
2
|
+
|
|
3
|
+
설치 가능한 유틸 디렉터리가 버전 단위다. 현재 Core 109개, Tailwind 4개, lint 1개가 각각
|
|
4
|
+
`package.json`과 `CHANGELOG.md`를 가진다. 소스 파일을 옮기지 않아 기존 디렉터리의 Git 이력도 유지된다.
|
|
5
|
+
같은 진입점에서 내보내는 함수·타입은 한 모듈이다. 예를 들어 heap·minHeap·maxHeap은
|
|
6
|
+
`@hermes/data-structure-heap`의 공개 API로 함께 버전 관리한다.
|
|
7
|
+
|
|
8
|
+
초기 버전은 기존 Core·Tailwind의 `1.0.0`을 이어받고, `lint-fsd`는 `0.1.0`에서 시작한다.
|
|
9
|
+
유틸은 `private: true` workspace 패키지로
|
|
10
|
+
관리하고 Changesets의 `privatePackages.version`을 활성화한다. 소스를 복사해 배포하므로
|
|
11
|
+
각 유틸을 npm에 따로 publish하지 않는다. CLI `@no-k/hermes`의 버전은 별도로 관리한다.
|
|
12
|
+
|
|
13
|
+
## 의존성 선언
|
|
14
|
+
|
|
15
|
+
`packages/core/number/lcm/package.json`의 예:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"name": "@hermes/number-lcm",
|
|
20
|
+
"version": "1.0.0",
|
|
21
|
+
"private": true,
|
|
22
|
+
"main": "./index.ts",
|
|
23
|
+
"types": "./index.ts",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@hermes/number-gcd": "workspace:^1.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- 필수 내부 구현은 `dependencies`에 명시한다. 상대 import도 선언이 필요하다.
|
|
31
|
+
- 호스트·플러그인 관계나 소비 프로젝트와 공유할 패키지는 `peerDependencies`에 호환 범위를 적는다.
|
|
32
|
+
- 없는 상태에서도 동작하는 peer만 `peerDependenciesMeta[이름].optional: true`로 선언한다.
|
|
33
|
+
Tailwind 모듈은 사용 중인 tailwind-merge·tailwind-variants가 지원하는 `tailwindcss: ^4.0.0`을 optional peer로 명시한다.
|
|
34
|
+
- 내부 workspace 범위에는 기준 버전을 적는다. `workspace:^1.0.0`은 허용하고,
|
|
35
|
+
호환 범위가 다음 major까지 자동 이동하는 `workspace:*`, `workspace:^`, `workspace:~`는 거부한다.
|
|
36
|
+
- 외부 패키지는 npm SemVer 범위를 쓴다. `file:`, `link:`, 태그, Git URL은 카탈로그에 넣지 않는다.
|
|
37
|
+
- 일반 relative import와 등록된 모듈의 패키지 이름 import를 지원한다. 복사 시 상대 경로로 변환한다.
|
|
38
|
+
|
|
39
|
+
빌드는 각 모듈 manifest를 원천으로 의존 그래프를 만든다. 선언 누락, 존재하지 않는 workspace
|
|
40
|
+
의존성, 호환되지 않는 내부 버전, 모듈 밖 소스 참조는 빌드 오류다. 필수 내부 의존성과 실제로
|
|
41
|
+
import한 내부 peer는 전이적으로 포함한다. 의존 모듈은 진입점을 포함한 전체 소스를 복사한다.
|
|
42
|
+
예를 들어 Dijkstra를 선택하면 priority-queue와 heap도 포함되지만 deque는 포함하지 않는다.
|
|
43
|
+
|
|
44
|
+
다른 대분류의 모듈을 참조하더라도 의존 소스는 선택한 유틸의 설치 목적지에 함께 복사된다.
|
|
45
|
+
이렇게 하면 Core와 라이브러리의 기본 설치 경로를 서로 다르게 지정해도 상대 import가 유효하다.
|
|
46
|
+
|
|
47
|
+
## 설치 호환성과 기록
|
|
48
|
+
|
|
49
|
+
`catalog.json` 스키마 3의 각 항목은 `packageName`, `version`, `modules`를 가진다.
|
|
50
|
+
`modules`는 각 모듈의 이름·버전·직접 의존 범위·변환 후 파일별 SHA-256을 담는다.
|
|
51
|
+
CLI는 템플릿과 해시가 일치하는지도 검사한다.
|
|
52
|
+
|
|
53
|
+
`no-k.config.ts` 스키마 1의 `installed` 항목에는 선택적 `source` 필드를 추가한다.
|
|
54
|
+
|
|
55
|
+
| 필드 | 의미 |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `source.catalogVersion` | 설치를 요청한 CLI 카탈로그 버전 |
|
|
58
|
+
| `source.requestedVersion` | 선택한 유틸의 요청 버전 |
|
|
59
|
+
| `source.status` | 요청한 모듈·파일이 전부 일치하면 `verified`, 유지한 구버전·사용자 수정이 있으면 `modified` |
|
|
60
|
+
| `source.modules` | 함께 설치한 모듈의 버전·의존 범위·파일 해시 |
|
|
61
|
+
|
|
62
|
+
기존 파일을 유지한 경우 새 버전을 설치했다고 가정하지 않는다. 이전 기록의 해시와 일치하면
|
|
63
|
+
해당 구버전을 그대로 기록하고 호환 범위를 검사한다. 어떤 버전과도 일치하지 않는 필수 의존
|
|
64
|
+
모듈은 호환성을 확인할 수 없으므로 실행을 막고 공유 파일과 사용처를 함께 검토하도록 안내한다.
|
|
65
|
+
다른 모듈이 의존하지 않는 유틸의 수정 파일은 유지할 수 있으며 `modified`로 기록한다.
|
|
66
|
+
|
|
67
|
+
같은 실제 설치 위치에서 `gcd@2`를 추가하려는데 기존 `lcm@1`이 `gcd@^1`을 요구하면
|
|
68
|
+
쓰기 전에 중단한다. 호환되는 `lcm`도 함께 선택하면 새 계획으로 설치할 수 있다.
|
|
69
|
+
외부 npm 패키지는 목적지가 달라도 같은 프로젝트의 peer 요구 범위를 함께 검사한다.
|
|
70
|
+
optional peer가 없으면 생략하고, 존재하면 그 버전을 검사한다. 미설치 필수 외부 패키지는
|
|
71
|
+
기존처럼 설치 명령으로 안내하며 소비 프로젝트의 package.json·lockfile을 변경하지 않는다.
|
|
72
|
+
동일한 외부 패키지를 여러 모듈이 요구하면 범위의 교집합을 계산해 설치 명령에 한 번만 표시한다.
|
|
73
|
+
|
|
74
|
+
계획에는 호환성 오류 목록과 검사한 기존 파일 상태를 포함한다. 화면에서는 오류를 보고 선택이나
|
|
75
|
+
파일 결정을 수정할 수 있고, 오류가 남은 계획은 실행할 수 없다. 실행 직전 상태를 다시 검사하며
|
|
76
|
+
codegen과 결과 확인이 성공한 뒤에만 설정을 저장한다. dry-run에서는 아무 파일도 쓰지 않는다.
|
|
77
|
+
|
|
78
|
+
`source` 없는 과거 설치 기록은 위치 정보로 보존한다. 그 기록만으로 과거 버전이나 호환성을
|
|
79
|
+
추정하지 않는다. 해당 유틸을 다시 선택하면 확인 가능한 파일 상태와 함께 새 기록을 남긴다.
|
|
80
|
+
현재 카탈로그에는 모듈당 한 버전만 있다. 이전 버전을 다운로드하거나 서로 다른 버전을 같은
|
|
81
|
+
설치 경로에 공존시키는 기능은 제공하지 않는다.
|
|
82
|
+
|
|
83
|
+
## 개발자의 버전 갱신 절차
|
|
84
|
+
|
|
85
|
+
1. 모듈의 공개 API 변경에 맞춰 patch·minor·major를 결정하고 관련 사용처를 수정·검증한다.
|
|
86
|
+
2. `pnpm changeset`으로 변경한 모듈과 설명을 기록한다. 새 유틸에는 manifest와 changelog도 추가한다.
|
|
87
|
+
3. `pnpm run version:status`로 예정된 버전과 영향받는 의존 모듈을 확인한다.
|
|
88
|
+
4. 릴리스를 준비할 때 `pnpm run version:modules`로 버전·내부 의존 범위·changelog를 갱신한다.
|
|
89
|
+
유틸만 변경한 경우에도 CLI patch changeset을 자동 추가해 번들 카탈로그 갱신을 포함한다.
|
|
90
|
+
기존 CLI changeset이 있으면 그 변경 수준을 사용한다.
|
|
91
|
+
5. `pnpm install --lockfile-only`, `pnpm run check`, `pnpm run test:run`, `pnpm run check:cli`,
|
|
92
|
+
`pnpm run test:cli-install`을 실행하고 결과와 변경 이력을 검토한다.
|
|
93
|
+
|
|
94
|
+
`fixed`·`linked` 그룹은 없다. Core·Tailwind·lint의 상위 집계 패키지와 개발 설정 패키지는 Changesets 대상에서
|
|
95
|
+
제외한다. 내부 의존성 변경에 따른 dependent 버전 증가는 Changesets 규칙을 따른다.
|
|
96
|
+
자동으로 바뀐 의존 범위가 실제 API 호환성을 입증하지는 않으므로, major 변경 시 사용처 검증과
|
|
97
|
+
필요한 명시적 major changeset도 작성해야 한다.
|
|
98
|
+
|
|
99
|
+
GitHub changelog 생성기는 프로젝트 설정을 유지하며 `@changesets/changelog-github`를 사용한다.
|
|
100
|
+
릴리스 환경에는 해당 도구가 요구하는 `GITHUB_TOKEN`을 제공한다. 위 명령들은 publish·push·tag를
|
|
101
|
+
실행하지 않는다. 이번 도입에서는 유틸 초기 버전만 등록하고 실제 릴리스 버전 증가는 하지 않는다.
|
|
102
|
+
|
|
103
|
+
참고: [Changesets의 private 패키지 버전 관리](https://changesets.dev/guide/beyond-npm),
|
|
104
|
+
[pnpm workspace 범위](https://pnpm.io/workspaces),
|
|
105
|
+
[npm peerDependencies](https://docs.npmjs.com/cli/v11/configuring-npm/package-json/#peerdependencies).
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@no-k/hermes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Add individual TypeScript utilities to your project with the Hermes CLI.",
|
|
5
5
|
"license": "ISC",
|
|
6
|
-
"files": ["
|
|
6
|
+
"files": ["dist", "templates", "catalog.json", "README.md", "LICENSE", "docs"],
|
|
7
7
|
"keywords": ["cli", "typescript", "utilities"],
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -16,13 +16,34 @@
|
|
|
16
16
|
"provenance": false
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"
|
|
19
|
+
"clean": "node --input-type=module -e \"import { rmSync } from 'node:fs'; for (const dir of ['dist', '.build', 'templates', 'catalog.json']) rmSync(dir, { recursive: true, force: true });\"",
|
|
20
|
+
"build": "pnpm run clean && tsc -p tsconfig.build.json && tsc -p tsconfig.tools.json && node .build/scripts/build-cli.js",
|
|
21
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
22
|
+
"check": "pnpm run build && node --test .build/test/*.test.js",
|
|
23
|
+
"preview:ui": "tsx scripts/preview-ui.ts",
|
|
24
|
+
"pack:archive": "pnpm run build && node .build/scripts/pack-cli.js",
|
|
25
|
+
"test:install": "pnpm run build && node .build/scripts/smoke-cli.js",
|
|
26
|
+
"test:fsd-install": "pnpm run build && node .build/scripts/smoke-fsd.js",
|
|
27
|
+
"prepack": "pnpm run build"
|
|
20
28
|
},
|
|
21
29
|
"type": "module",
|
|
22
30
|
"bin": {
|
|
23
|
-
"hermes": "./bin/hermes.
|
|
31
|
+
"hermes": "./dist/bin/hermes.js"
|
|
24
32
|
},
|
|
25
33
|
"engines": {
|
|
26
34
|
"node": ">=22"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@hermes/tsconfig": "workspace:^",
|
|
38
|
+
"@types/node": "^22.15.21",
|
|
39
|
+
"@types/react": "^19.3.0",
|
|
40
|
+
"@types/semver": "^7.8.0",
|
|
41
|
+
"tsx": "^4.23.15",
|
|
42
|
+
"typescript": "^5.7.2"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"ink": "^7.1.1",
|
|
46
|
+
"react": "^19.3.0",
|
|
47
|
+
"semver": "^7.8.1"
|
|
27
48
|
}
|
|
28
49
|
}
|