@no-k/hermes 0.2.0 → 0.3.2
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 +166 -200
- package/catalog.json +49 -1
- package/dist/bin/hermes.js +1 -1
- package/dist/lib/barrel.js +77 -0
- package/dist/lib/contracts.js +1 -1
- package/dist/lib/install.js +7 -2
- package/docs/development.md +176 -0
- package/docs/interactive-install.md +6 -6
- package/docs/module-versioning.md +27 -11
- package/package.json +2 -1
- 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/README.md
CHANGED
|
@@ -1,251 +1,217 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Hermes
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
선택한 유틸과 필요한 보조 파일은 npm 패키지에 함께 들어 있습니다.
|
|
5
|
-
CLI는 터미널 화면에 Ink·React, 버전 범위 검사에 `semver`를 사용합니다.
|
|
3
|
+
필요한 TypeScript 코드를 골라 프로젝트에 추가하세요.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
Hermes는 유틸리티, Tailwind CSS 헬퍼, FSD lint 규칙의 소스를 생성하는 CLI입니다.
|
|
6
|
+
생성된 파일을 직접 읽고 수정하면서 프로젝트 코드와 함께 관리할 수 있습니다.
|
|
7
|
+
선택한 항목에 필요한 보조 코드도 함께 가져옵니다.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
[시작하기](#시작하기) · [Tailwind CSS](#tailwind-css-헬퍼) · [FSD lint](#fsd-lint-규칙) · [명령어](#명령어)
|
|
10
|
+
|
|
11
|
+
## 시작하기
|
|
12
|
+
|
|
13
|
+
**Node.js 22 이상**이 필요합니다. 코드를 추가할 프로젝트 디렉터리의 터미널에서 실행하세요.
|
|
10
14
|
|
|
11
15
|
```bash
|
|
12
16
|
npx @no-k/hermes@latest
|
|
13
|
-
# 최신 카탈로그를 탐색하고 설치 계획만 확인
|
|
14
|
-
npx @no-k/hermes@latest --dry-run
|
|
15
|
-
# 특정 항목을 미리 체크한 상태로 시작
|
|
16
|
-
npx @no-k/hermes@0.1.0 add array-chunk
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
1. **코드 선택** — Core 또는 라이브러리에서 필요한 항목을 고릅니다.
|
|
20
|
+
2. **경로 확인** — 파일을 생성할 위치와 기존 파일의 처리 방법을 확인합니다.
|
|
21
|
+
3. **생성** — 최종 목록에서 `Yes`를 선택하고 Enter를 누릅니다.
|
|
22
|
+
|
|
23
|
+
`No`를 선택하면 앞 화면으로 돌아가 선택 내용을 수정할 수 있습니다.
|
|
24
|
+
|
|
25
|
+
| 키 | 동작 |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| ↑ / ↓ | 목록 이동 |
|
|
28
|
+
| Space | 항목 선택·해제 |
|
|
29
|
+
| Enter | 검색 입력 완료 또는 다음 단계 |
|
|
30
|
+
| / | 이름이나 한국어 설명으로 검색 |
|
|
31
|
+
| Tab | 항목 설명·의존성 또는 파일 차이 보기 |
|
|
32
|
+
| Esc | 상세 보기 닫기 또는 이전 단계 |
|
|
33
|
+
| Ctrl+C | 취소 |
|
|
34
|
+
|
|
35
|
+
### 첫 번째 유틸 추가하기
|
|
36
|
+
|
|
37
|
+
배열을 일정한 크기로 나누는 `array-chunk`를 추가해 보세요.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx @no-k/hermes@latest add array-chunk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
해당 항목이 선택된 상태로 시작합니다. 기본 경로를 사용하고 생성을 확인하면 다음 파일이 생깁니다.
|
|
26
44
|
|
|
27
45
|
```text
|
|
28
46
|
src/utils/hermes/
|
|
29
|
-
├── array/chunk/
|
|
30
|
-
├──
|
|
47
|
+
├── array/chunk/
|
|
48
|
+
│ ├── chunk.ts
|
|
49
|
+
│ └── index.ts
|
|
50
|
+
├── index.ts
|
|
31
51
|
└── LICENSE
|
|
52
|
+
|
|
53
|
+
no-k.config.ts
|
|
32
54
|
```
|
|
33
55
|
|
|
34
|
-
|
|
56
|
+
설치 루트의 `index.ts` 배럴에서 생성한 유틸을 바로 import해 사용하세요.
|
|
57
|
+
이후 항목을 추가하면 기존 export와 주석을 보존하면서 필요한 export를 추가합니다.
|
|
35
58
|
|
|
36
59
|
```ts
|
|
37
|
-
|
|
60
|
+
// src/example.ts
|
|
61
|
+
import { chunk } from "./utils/hermes";
|
|
38
62
|
|
|
39
|
-
chunk([1, 2, 3, 4, 5], 2);
|
|
63
|
+
chunk([1, 2, 3, 4, 5], 2);
|
|
64
|
+
// [[1, 2], [3, 4], [5]]
|
|
40
65
|
```
|
|
41
66
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
경로 입력의 초기값은 `--dir` → 현재 디렉터리의 저장된 설정 → 기본 경로 순서입니다.
|
|
45
|
-
Core 기본값은 `src/utils/hermes`, 라이브러리는 `src/lib/hermes`이고 화면에서 각각 수정할 수 있습니다.
|
|
46
|
-
경로 입력에서 Enter는 현재 값 사용, Ctrl+U는 전체 지우기, Esc는 이전 화면입니다.
|
|
47
|
-
모노레포에서는 설치할 앱·패키지 디렉터리에서 실행합니다. 상위 디렉터리의 설정은 찾지 않습니다.
|
|
48
|
-
설정 파일이나 package.json이 없는 디렉터리에서도 파일 생성이 가능합니다.
|
|
49
|
-
설치가 성공하면 `no-k.config.ts`에 목적지, 선택한 유틸과 함께 복사한 모듈의 버전·해시를 저장합니다.
|
|
50
|
-
기존 설정이 있으면 읽어서 갱신하며, `--dry-run`에서는 설정도 쓰지 않습니다.
|
|
67
|
+
`no-k.config.ts`는 설치 위치와 항목의 버전을 기억합니다.
|
|
68
|
+
생성된 소스와 이 설정 파일을 함께 커밋하면 팀에서도 같은 상태를 공유할 수 있습니다.
|
|
51
69
|
|
|
52
|
-
##
|
|
70
|
+
## 추가할 수 있는 코드
|
|
53
71
|
|
|
54
|
-
|
|
|
55
|
-
| --- | --- |
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
| --help | 도움말 |
|
|
63
|
-
| --version | CLI 버전 |
|
|
72
|
+
| 분류 | 예시 | 용도 |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| Core | `array-chunk`, `object-pick`, `number-lcm` | 배열·객체·문자열·숫자 처리 |
|
|
75
|
+
| 알고리즘·자료구조 | `algorithm-graph-dijkstra`, `data-structure-heap` | 그래프 탐색, 힙, 큐 등 |
|
|
76
|
+
| Tailwind CSS | `tailwindcss-cn`, `tailwindcss-create-tailwind-css` | 조건부 클래스와 variant 구성 |
|
|
77
|
+
| FSD lint | `lint-fsd` | 레이어 의존성과 디렉터리 구조 검사 |
|
|
78
|
+
|
|
79
|
+
전체 목록은 다음 명령으로 확인할 수 있습니다.
|
|
64
80
|
|
|
65
81
|
```bash
|
|
66
|
-
npx @no-k/hermes@
|
|
67
|
-
npx @no-k/hermes@0.1.0 add array-chunk --dir "src/my utilities" --dry-run
|
|
82
|
+
npx @no-k/hermes@latest list
|
|
68
83
|
```
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
기본 선택은 No이며 인자나 옵션을 모두 전달해도 최종 확인을 건너뛰지 않습니다.
|
|
74
|
-
검토 중 파일이나 설정이 바뀌면 다시 계획하고 확인받습니다. 변경된 충돌에는 이전 교체 승인을 적용하지 않습니다.
|
|
75
|
-
목적지의 심볼릭 링크를 통해 파일을 쓰지 않습니다.
|
|
85
|
+
Core 유틸에는 외부 런타임 npm 의존성이 없습니다.
|
|
86
|
+
라이브러리 항목에 필요한 npm 패키지는 설치 결과에 안내되므로, 사용하는 패키지 매니저로 추가하세요.
|
|
87
|
+
Hermes는 프로젝트의 `package.json`과 lockfile을 자동으로 수정하지 않습니다.
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
항상 바로 출력합니다. `q` 취소는 종료 코드 0, Ctrl+C는 130, 입력 종료는 1입니다.
|
|
80
|
-
경로·검색 입력 중에는 `q`도 입력 문자이므로 Ctrl+C로 중단합니다.
|
|
89
|
+
`string-uuid`처럼 Node.js가 필요한 항목과 `dom-*`처럼 브라우저 DOM이 필요한 항목은
|
|
90
|
+
선택 화면에서 실행 환경을 확인할 수 있습니다.
|
|
81
91
|
|
|
82
|
-
|
|
92
|
+
### Tailwind CSS 헬퍼
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
Tailwind 항목에는 tailwindcss- 접두어를 붙입니다.
|
|
94
|
+
`tailwindcss-cn`은 조건부 클래스를 합치고, 충돌하는 Tailwind 클래스를 정리합니다.
|
|
86
95
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
string-uuid는 정적 node:crypto import 때문에 Node.js에서 사용해야 합니다.
|
|
109
|
-
dom-* 항목에는 DOM API와 DOM TypeScript 타입이 필요합니다.
|
|
110
|
-
현재 Tailwind 헬퍼의 병합 규칙은 Tailwind CSS 4 기준입니다.
|
|
111
|
-
|
|
112
|
-
## 업데이트와 라이선스
|
|
113
|
-
|
|
114
|
-
새 유틸을 찾을 때는 `@latest`로 최신 CLI에 포함된 카탈로그를 탐색합니다.
|
|
115
|
-
카탈로그는 실행 중 원격에서 갱신되지 않으므로 이전 버전으로 실행하면 그 버전의 항목만 보입니다.
|
|
116
|
-
같은 카탈로그와 템플릿을 다시 사용하려면 CLI 버전을 확인하고 명령에서 고정하세요.
|
|
96
|
+
```bash
|
|
97
|
+
npx @no-k/hermes@latest add tailwindcss-cn
|
|
98
|
+
npm install "clsx@^2.1.1" "tailwind-merge@^3.6.0"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// styles.ts — 프로젝트 루트의 파일
|
|
103
|
+
import { cn } from "./libs/hermes/tailwindcss/cn";
|
|
104
|
+
|
|
105
|
+
cn("px-2 py-1", "px-4"); // "py-1 px-4"
|
|
106
|
+
cn("text-sm", { "font-bold": true }); // "text-sm font-bold"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
기본 병합 규칙은 Tailwind CSS 4를 기준으로 합니다.
|
|
110
|
+
프로젝트에 맞춘 병합 설정을 `cn`과 `classVariant`에서 함께 사용하려면
|
|
111
|
+
`tailwindcss-create-tailwind-css`를 선택하세요.
|
|
112
|
+
|
|
113
|
+
### FSD lint 규칙
|
|
114
|
+
|
|
115
|
+
`lint-fsd`는 Feature-Sliced Design의 레이어 의존 방향, 같은 레이어의 slice 간 참조,
|
|
116
|
+
public API 사용과 디렉터리 구조를 검사합니다. **Hermes 0.3.0 이상**에서 사용할 수 있습니다.
|
|
117
117
|
|
|
118
118
|
```bash
|
|
119
|
-
npx @no-k/hermes@latest
|
|
120
|
-
|
|
121
|
-
npx @no-k/hermes@0.1.0
|
|
122
|
-
npx @no-k/hermes@0.1.0 list
|
|
119
|
+
npx @no-k/hermes@latest add lint-fsd
|
|
120
|
+
npm install --save-dev eslint typescript-eslint typescript "jiti@^2.2.0" "@types/node@^22"
|
|
123
121
|
```
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
업데이트할 때는 직접 수정한 부분과 공통 보조 파일의 호환성을 함께 검토하세요.
|
|
129
|
-
유틸별 버전은 CLI 배포 버전과 별개입니다. 예를 들어 `number-lcm@1.0.0`과
|
|
130
|
-
`number-gcd@1.2.0`을 같은 CLI가 포함할 수 있습니다. 한 설치 위치에는 모듈별 한 버전만 둡니다.
|
|
131
|
-
과거 버전을 별도로 내려받아 조합하는 기능은 제공하지 않습니다.
|
|
123
|
+
기본 위치는 **`libs/hermes/lint/fsd`**입니다.
|
|
124
|
+
ESLint 9 또는 10의 flat config에 생성된 플러그인을 연결하세요.
|
|
125
|
+
다음은 TypeScript 프로젝트의 설정 예시입니다.
|
|
132
126
|
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
```ts
|
|
128
|
+
// eslint.config.ts
|
|
129
|
+
import tseslint from "typescript-eslint";
|
|
130
|
+
import fsd from "./libs/hermes/lint/fsd";
|
|
131
|
+
|
|
132
|
+
export default [
|
|
133
|
+
...tseslint.configs.recommended,
|
|
134
|
+
fsd.configs.recommended,
|
|
135
|
+
];
|
|
136
|
+
```
|
|
135
137
|
|
|
136
|
-
|
|
138
|
+
```bash
|
|
139
|
+
npx eslint src
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
기존 ESLint 설정이 있다면 그 설정에 `fsd.configs.recommended`를 추가하세요.
|
|
143
|
+
위 예시는 `jiti`로 읽는 `eslint.config.ts`용입니다. `.js`·`.mjs` 설정에 연결하는 방법은
|
|
144
|
+
[FSD 사용 가이드](https://github.com/no-k/hermes/tree/main/packages/lib/lint/fsd#설치와-eslint-설정)를 참고하세요.
|
|
137
145
|
|
|
138
|
-
|
|
146
|
+
기본 검사 루트는 `src`, 경로 별칭은 `@`와 `~`입니다.
|
|
147
|
+
`createConfig`로 프로젝트 구조에 맞춰 설정하거나, 생성된 `options.ts`와 `rules/`를 직접 수정할 수 있습니다.
|
|
148
|
+
[프로젝트별 옵션과 Oxlint 사용법](https://github.com/no-k/hermes/tree/main/packages/lib/lint/fsd)도 제공합니다.
|
|
139
149
|
|
|
140
|
-
|
|
150
|
+
## 생성 위치 지정하기
|
|
141
151
|
|
|
142
|
-
|
|
|
152
|
+
| 분류 | 기본 경로 |
|
|
143
153
|
| --- | --- |
|
|
144
|
-
| `src/
|
|
145
|
-
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
| `src/lib/project-config.ts`, `config-syntax.ts` | 설정 읽기·검증, 원문 보존 갱신과 설치 목록 병합 |
|
|
150
|
-
| `src/lib/report.ts` | 파일 결과·외부 의존성·실행 환경 안내 |
|
|
151
|
-
| `src/lib/catalog.ts`, `json.ts` | catalog 타입과 JSON 검증 |
|
|
152
|
-
| `src/lib/catalog-query.ts` | 분류 계층 조회, 이름·전체 설명 검색, 분류 필터와 표시용 요약 |
|
|
153
|
-
| `src/lib/catalog-selection.ts` | 대분류·하위 분류·유틸 선택과 이전 선택 복원 |
|
|
154
|
-
| `src/lib/picker.ts`, `picker-view.tsx`, `terminal.ts`, `terminal-text.ts` | 선택 상태, Ink 화면, 터미널 입력과 화면 복원 |
|
|
155
|
-
| `src/lib/installation-status.ts` | 설치 기록과 실제 파일을 대조한 상태·위치 표시 |
|
|
156
|
-
| `src/lib/conflict-selection.ts` | 기존 파일과 생성 후보의 차이 검토, 파일별 유지·교체 선택 |
|
|
157
|
-
| `src/lib/versions.ts` | 실제 파일 해시와 설치 버전 대조, 내부 의존성·외부 peer 호환성 검사 |
|
|
158
|
-
| `scripts/catalog-lib.ts` | 유틸 탐색, 의존성 추적과 README 설명 추출 |
|
|
159
|
-
| `scripts/build-cli.ts` | catalog와 템플릿 생성 |
|
|
160
|
-
| `scripts/version-modules.ts` | Changesets 버전 적용 시 유틸 변경을 CLI 배포 버전에 연결 |
|
|
161
|
-
| `scripts/preview-ui.ts` | 파일을 생성하지 않는 개발용 선택 화면 미리보기 |
|
|
162
|
-
| `scripts/pack-cli.ts` | npm tarball 생성 및 포함 파일 검증 |
|
|
163
|
-
| `scripts/smoke-cli.ts` | tarball을 설치해 소비 프로젝트에서 컴파일·실행 |
|
|
164
|
-
| `test/*.test.ts` | CLI·catalog·패키징 회귀 테스트 |
|
|
165
|
-
|
|
166
|
-
대화형 설치는 [단계별 계약](docs/interactive-install.md)에 따라 설치 엔진·설정, 카탈로그 탐색,
|
|
167
|
-
독립 모듈 버전 관리와 터미널 선택 화면을 연결합니다.
|
|
168
|
-
`hermes`와 `hermes add` 모두 대화형 선택 후 최종 Yes에서 설치하고 설정과 버전을 저장합니다.
|
|
169
|
-
|
|
170
|
-
개발용 화면은 다음 명령으로 확인할 수 있습니다. 선택 결과를 표시하고 파일을 생성하지 않습니다.
|
|
171
|
-
매번 전체 빌드하지 않고 `tsx`로 화면 소스를 실행합니다. 기존 catalog.json을 읽으며,
|
|
172
|
-
아직 빌드하지 않았다면 소스에서 카탈로그를 메모리에 구성합니다.
|
|
154
|
+
| Core | `src/utils/hermes` |
|
|
155
|
+
| 라이브러리 — Tailwind CSS, lint | `libs/hermes` |
|
|
156
|
+
|
|
157
|
+
경로는 선택 화면에서 바꾸거나 `--dir`로 지정할 수 있습니다.
|
|
158
|
+
그 아래에는 `array/chunk`, `lint/fsd`처럼 항목별 폴더 구조가 만들어집니다.
|
|
173
159
|
|
|
174
160
|
```bash
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
pnpm --filter @no-k/hermes run preview:ui number-lcm tailwindcss-cn
|
|
178
|
-
# 결과와 재진입 상태를 JSON으로 확인합니다.
|
|
179
|
-
pnpm --filter @no-k/hermes run preview:ui --json number-lcm
|
|
161
|
+
npx @no-k/hermes@latest add array-chunk number-lcm --dir src/shared/hermes
|
|
162
|
+
npx @no-k/hermes@latest add lint-fsd --dir libs/hermes
|
|
180
163
|
```
|
|
181
164
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
165
|
+
경로는 **`--dir` → 현재 프로젝트에 저장된 설정 → 기본값** 순서로 적용합니다.
|
|
166
|
+
이전에 다른 경로를 사용했다면 `--dir libs/hermes`로 라이브러리 위치를 다시 지정할 수 있습니다.
|
|
167
|
+
공백이 포함된 경로는 따옴표로 감싸세요.
|
|
185
168
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
Enter로 검색 입력을 끝냅니다. Tab은 상세 보기, Esc는 상세·검색 닫기 또는 이전 화면,
|
|
189
|
-
`a`는 검색 결과 전체 선택·해제, Ctrl+L은 검색 초기화, `q`는 취소입니다.
|
|
190
|
-
목록에서는 `j`/`k`로도 이동할 수 있습니다. 한국어 검색과 bracketed paste를 지원합니다.
|
|
191
|
-
분류를 해제하면 그 안의 선택은 설치 대상에서 제외되고, 다시 선택하면 체크가 복원됩니다.
|
|
192
|
-
상세 화면에서 함께 생성되는 의존 모듈, npm·peer 의존성, 실행 환경과 기존 설치 위치를 확인할 수 있습니다.
|
|
169
|
+
모노레포에서는 코드를 사용할 앱이나 패키지 디렉터리에서 실행하세요.
|
|
170
|
+
각 디렉터리의 `no-k.config.ts`로 설치 위치를 관리합니다.
|
|
193
171
|
|
|
194
|
-
|
|
195
|
-
다른 앱의 설치 기록을 보려면 CLI를 빌드한 뒤 그 앱 디렉터리에서
|
|
196
|
-
`node /저장소/절대경로/packages/cli/.build/scripts/preview-ui.js`를 실행합니다.
|
|
172
|
+
## 수정한 코드와 업데이트
|
|
197
173
|
|
|
198
|
-
|
|
199
|
-
지원합니다. 사용자 필드와 주석을 보존하기 위해 필요한 값만 수정하며, import·함수·변수 참조 등
|
|
200
|
-
지원하지 않는 구문은 위치를 안내하고 원본을 유지합니다. 설정 파일의 코드를 실행하지 않습니다.
|
|
174
|
+
생성된 소스는 자유롭게 수정할 수 있습니다. Hermes의 새 버전이 나와도 기존 파일이 자동으로 바뀌지 않습니다.
|
|
201
175
|
|
|
202
|
-
|
|
176
|
+
업데이트하려면 최신 CLI에서 같은 항목을 다시 선택하세요.
|
|
177
|
+
내용이 다른 파일은 기본적으로 유지되며, 차이를 확인한 뒤 교체할 파일만 고를 수 있습니다.
|
|
178
|
+
실제 파일을 쓰기 전에 확인하려면 `--dry-run`을 사용하세요.
|
|
203
179
|
|
|
204
180
|
```bash
|
|
205
|
-
|
|
206
|
-
pnpm run build:cli
|
|
207
|
-
pnpm run check
|
|
208
|
-
pnpm run test:run
|
|
209
|
-
pnpm run check:cli
|
|
210
|
-
pnpm run pack:cli
|
|
211
|
-
pnpm run test:cli-install
|
|
181
|
+
npx @no-k/hermes@latest add lint-fsd --dry-run
|
|
212
182
|
```
|
|
213
183
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
`
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
한국어 탐색과 여러 분류 선택, 별도 경로 저장·재사용, No 이후 파일별 결정 유지,
|
|
249
|
-
최종 확인 중 파일 변경, 화면별 취소, 입력·출력 각각의 리다이렉트를 검사합니다.
|
|
250
|
-
최종 Yes 이전에는 파일이 생기거나 기존 파일이 바뀌지 않는지도 실제 실행 중 확인합니다.
|
|
251
|
-
Windows에서는 이 PTY 테스트를 건너뛰며 `test:cli-install`은 macOS·Linux에서 실행합니다.
|
|
184
|
+
팀에서 같은 CLI 버전을 사용하려면 버전을 명시할 수 있습니다.
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
npx @no-k/hermes@0.3.0 add lint-fsd
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
항목에 필요한 공통 보조 파일도 함께 생성됩니다.
|
|
191
|
+
여러 유틸이 공유하는 코드를 수정했다면 업데이트할 때 함께 사용하는 유틸도 확인하세요.
|
|
192
|
+
|
|
193
|
+
## 명령어
|
|
194
|
+
|
|
195
|
+
| 명령·옵션 | 설명 |
|
|
196
|
+
| --- | --- |
|
|
197
|
+
| `npx @no-k/hermes@latest` | 전체 목록에서 선택 시작 |
|
|
198
|
+
| `add <항목...>` | 지정한 항목을 미리 선택 |
|
|
199
|
+
| `list` | 전체 항목과 설명 출력 |
|
|
200
|
+
| `--dir <경로>` | 생성 위치 지정 |
|
|
201
|
+
| `--dry-run` | 파일과 설정을 쓰지 않고 설치 계획 확인 |
|
|
202
|
+
| `--overwrite` | 파일 검토 화면에서 교체 후보를 미리 선택 |
|
|
203
|
+
| `--help` | 도움말 |
|
|
204
|
+
| `--version` | CLI 버전 확인 |
|
|
205
|
+
|
|
206
|
+
설치는 대화형 터미널에서 진행합니다. `--overwrite`를 사용해도 최종 확인 단계는 유지됩니다.
|
|
207
|
+
|
|
208
|
+
## 더 알아보기
|
|
209
|
+
|
|
210
|
+
- [FSD 설정과 Oxlint 사용법](https://github.com/no-k/hermes/tree/main/packages/lib/lint/fsd)
|
|
211
|
+
- [변경 이력](https://github.com/no-k/hermes/blob/main/packages/cli/CHANGELOG.md)
|
|
212
|
+
- [문제 신고·기능 제안](https://github.com/no-k/hermes/issues)
|
|
213
|
+
- [기여자를 위한 개발 가이드](https://github.com/no-k/hermes/blob/main/packages/cli/docs/development.md)
|
|
214
|
+
|
|
215
|
+
## 라이선스
|
|
216
|
+
|
|
217
|
+
[ISC](https://github.com/no-k/hermes/blob/main/packages/cli/LICENSE). 생성된 코드와 함께 제공되는 `LICENSE`의 저작권·허가 문구를 유지해 주세요.
|
package/catalog.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 3,
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"items": [
|
|
5
5
|
{
|
|
6
6
|
"name": "algorithm-geometry-ccw",
|
|
@@ -1726,6 +1726,54 @@
|
|
|
1726
1726
|
"http/status/status.ts"
|
|
1727
1727
|
]
|
|
1728
1728
|
},
|
|
1729
|
+
{
|
|
1730
|
+
"name": "lint-fsd",
|
|
1731
|
+
"packageName": "@hermes/lint-fsd",
|
|
1732
|
+
"version": "0.1.0",
|
|
1733
|
+
"modules": [
|
|
1734
|
+
{
|
|
1735
|
+
"name": "lint-fsd",
|
|
1736
|
+
"packageName": "@hermes/lint-fsd",
|
|
1737
|
+
"version": "0.1.0",
|
|
1738
|
+
"scope": "lib",
|
|
1739
|
+
"files": {
|
|
1740
|
+
"lint/fsd/fsd.ts": "7bca7aae487a8d2bad71f2d152b8420a2cd7a8259c514224b6df764fe7642164",
|
|
1741
|
+
"lint/fsd/import-listener.ts": "b07c96183e23f6e8902b1451fc350cd0eb5e87c0ed77626376fffa870c6434bc",
|
|
1742
|
+
"lint/fsd/index.ts": "b5a08b38570e5094eacf089ecbe5bf0ac12692671bc266c923796172b8a436a8",
|
|
1743
|
+
"lint/fsd/options.ts": "e24e3fb0cf0ab12cda93614ae19fcd1762d2b23c746a087390f612113195193b",
|
|
1744
|
+
"lint/fsd/rules/fsd-layer-imports.ts": "23cee966cf7d230e2762c70d5c115fc49a26a9ccf8ba33a2d2bedb41f244ea0d",
|
|
1745
|
+
"lint/fsd/rules/fsd-public-api.ts": "474eb3d91081db8b31a678990c19ddda346125a54c1a10a7cf7d2ed07eb41b28",
|
|
1746
|
+
"lint/fsd/rules/fsd-slice-segments.ts": "57d72120b747d4d091ae09abc99e8f821c7ebca1ee4e992d1fc9fe1ab29ad275"
|
|
1747
|
+
},
|
|
1748
|
+
"requires": [
|
|
1749
|
+
{
|
|
1750
|
+
"packageName": "eslint",
|
|
1751
|
+
"range": "^9.0.0 || ^10.0.0",
|
|
1752
|
+
"kind": "peer",
|
|
1753
|
+
"internal": false,
|
|
1754
|
+
"optional": true
|
|
1755
|
+
}
|
|
1756
|
+
]
|
|
1757
|
+
}
|
|
1758
|
+
],
|
|
1759
|
+
"scope": "lib",
|
|
1760
|
+
"category": "lib/lint",
|
|
1761
|
+
"description": "Feature-Sliced Design의 레이어 의존 방향, slice 경계, public API와 디렉터리 구조를 검사합니다. Hermes CLI가 규칙의 TypeScript 소스를 프로젝트에 생성합니다. 생성된 파일은 자유롭게 수정할 수 있습니다.",
|
|
1762
|
+
"dependencies": [],
|
|
1763
|
+
"runtime": "node",
|
|
1764
|
+
"devDependencies": [
|
|
1765
|
+
"@types/node@^22"
|
|
1766
|
+
],
|
|
1767
|
+
"files": [
|
|
1768
|
+
"lint/fsd/fsd.ts",
|
|
1769
|
+
"lint/fsd/import-listener.ts",
|
|
1770
|
+
"lint/fsd/index.ts",
|
|
1771
|
+
"lint/fsd/options.ts",
|
|
1772
|
+
"lint/fsd/rules/fsd-layer-imports.ts",
|
|
1773
|
+
"lint/fsd/rules/fsd-public-api.ts",
|
|
1774
|
+
"lint/fsd/rules/fsd-slice-segments.ts"
|
|
1775
|
+
]
|
|
1776
|
+
},
|
|
1729
1777
|
{
|
|
1730
1778
|
"name": "number-clamp",
|
|
1731
1779
|
"packageName": "@hermes/number-clamp",
|
package/dist/bin/hermes.js
CHANGED
|
@@ -25,7 +25,7 @@ const help = [
|
|
|
25
25
|
" -h, --help Show help",
|
|
26
26
|
" -v, --version Show version",
|
|
27
27
|
"",
|
|
28
|
-
"Defaults: Core → src/utils/hermes; libraries →
|
|
28
|
+
"Defaults: Core → src/utils/hermes; libraries → libs/hermes.",
|
|
29
29
|
"Saved paths are read from no-k.config.ts in the current directory.",
|
|
30
30
|
"Run in an interactive terminal; choose No to edit your selection.",
|
|
31
31
|
"External npm dependencies are reported; they are not installed automatically.",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readDestination } from "./files.js";
|
|
3
|
+
import { resolveInstallDirectory } from "./project-config.js";
|
|
4
|
+
function appendExports(existing, entries) {
|
|
5
|
+
// 이미 내보내는 경로를 확인하되 주석과 문자열 안의 예시는 export로 취급하지 않는다.
|
|
6
|
+
const exports = new Set([
|
|
7
|
+
...existing.matchAll(/\/\/[^\r\n]*|\/\*[\s\S]*?\*\/|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|\bexport\s*\*\s*from\s*["']([^"'\r\n]+)["']/g),
|
|
8
|
+
].flatMap((match) => (match[1] ? [match[1].replace(/\/index(?:\.[jt]s)?$/, "")] : [])));
|
|
9
|
+
const missing = entries.filter((entry) => !exports.has(entry));
|
|
10
|
+
if (!missing.length)
|
|
11
|
+
return existing;
|
|
12
|
+
const newline = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
13
|
+
return (existing +
|
|
14
|
+
(existing && !existing.endsWith("\n") ? newline : "") +
|
|
15
|
+
missing.map((entry) => "export * from " + JSON.stringify(entry) + ";" + newline).join(""));
|
|
16
|
+
}
|
|
17
|
+
export function planBarrelFiles(request, catalog, sourceFiles, compatibilityGuards) {
|
|
18
|
+
const cwd = path.dirname(request.config.destination);
|
|
19
|
+
const roots = new Map();
|
|
20
|
+
const planned = new Set(sourceFiles.map((file) => file.destination));
|
|
21
|
+
const guards = new Map(compatibilityGuards.map((file) => [file.destination, file]));
|
|
22
|
+
for (const target of request.targets) {
|
|
23
|
+
const directory = resolveInstallDirectory(target.directory, cwd);
|
|
24
|
+
const entries = roots.get(directory) ?? new Set();
|
|
25
|
+
for (const item of catalog.items.filter((item) => target.names.includes(item.name)))
|
|
26
|
+
for (const file of item.files)
|
|
27
|
+
if (file.endsWith("/index.ts"))
|
|
28
|
+
entries.add(file);
|
|
29
|
+
roots.set(directory, entries);
|
|
30
|
+
}
|
|
31
|
+
// 배럴을 만들지 않던 버전의 설치도 복원한다. 다른 목적지의 설치 기록은 섞지 않는다.
|
|
32
|
+
for (const record of request.config.status === "loaded" ? request.config.value.installed : []) {
|
|
33
|
+
const entries = roots.get(resolveInstallDirectory(record.directory, cwd));
|
|
34
|
+
if (entries) {
|
|
35
|
+
const files = record.source
|
|
36
|
+
? record.source.modules.flatMap((module) => Object.keys(module.files))
|
|
37
|
+
: (catalog.items.find((item) => item.name === record.name && item.scope === record.scope)?.files ?? []);
|
|
38
|
+
for (const file of files)
|
|
39
|
+
if (file.endsWith("/index.ts"))
|
|
40
|
+
entries.add(file);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const files = [];
|
|
44
|
+
for (const [directory, candidates] of roots) {
|
|
45
|
+
const entries = [...candidates].sort().flatMap((file) => {
|
|
46
|
+
const destination = path.join(directory, file);
|
|
47
|
+
if (!planned.has(destination)) {
|
|
48
|
+
let guard = guards.get(destination);
|
|
49
|
+
if (!guard) {
|
|
50
|
+
const existingContent = readDestination(destination);
|
|
51
|
+
guard = {
|
|
52
|
+
file,
|
|
53
|
+
destination,
|
|
54
|
+
existingContent,
|
|
55
|
+
content: existingContent ?? "",
|
|
56
|
+
action: existingContent === null ? "create" : "keep",
|
|
57
|
+
};
|
|
58
|
+
guards.set(destination, guard);
|
|
59
|
+
}
|
|
60
|
+
if (guard.existingContent === null)
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return ["./" + file.slice(0, -"/index.ts".length)];
|
|
64
|
+
});
|
|
65
|
+
const destination = path.join(directory, "index.ts");
|
|
66
|
+
const existingContent = readDestination(destination);
|
|
67
|
+
const content = appendExports(existingContent ?? "", entries);
|
|
68
|
+
files.push({
|
|
69
|
+
file: "index.ts",
|
|
70
|
+
destination,
|
|
71
|
+
content,
|
|
72
|
+
existingContent,
|
|
73
|
+
action: existingContent === null ? "create" : existingContent === content ? "keep" : "replace",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return { files, compatibilityGuards: [...guards.values()] };
|
|
77
|
+
}
|
package/dist/lib/contracts.js
CHANGED
package/dist/lib/install.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, realpathSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { planBarrelFiles } from "./barrel.js";
|
|
3
4
|
import { isSourcePath, isUtilityScope } from "./catalog.js";
|
|
4
5
|
import { readDestination, validateFilePlans, writePlannedFile } from "./files.js";
|
|
5
6
|
import { PROJECT_CONFIG_FILENAME, mergeProjectConfig, planProjectConfig, resolveInstallDirectory, } from "./project-config.js";
|
|
@@ -126,15 +127,19 @@ export function createInstallPlan(request, catalog, packageDirectory) {
|
|
|
126
127
|
const utilities = planUtilityFiles(targets, packageDirectory, request.fileDecisions);
|
|
127
128
|
const { config, dependencies, compatibilityErrors, compatibilityGuards } = planInstalledVersions(mergeProjectConfig(request.config, request.targets), targets, utilities.files, catalog, cwd);
|
|
128
129
|
const configFile = planProjectConfig(request.config, config);
|
|
129
|
-
|
|
130
|
+
const barrels = planBarrelFiles(request, catalog, utilities.files, compatibilityGuards);
|
|
131
|
+
const files = [...utilities.files, ...barrels.files].sort((a, b) => a.destination.localeCompare(b.destination));
|
|
132
|
+
validateFilePlans(barrels.compatibilityGuards);
|
|
133
|
+
validateFilePlans([...files, configFile]);
|
|
130
134
|
return {
|
|
131
135
|
...request,
|
|
132
136
|
...utilities,
|
|
137
|
+
files,
|
|
133
138
|
dependencies,
|
|
134
139
|
configFile,
|
|
135
140
|
nextConfig: config,
|
|
136
141
|
compatibilityErrors,
|
|
137
|
-
compatibilityGuards,
|
|
142
|
+
compatibilityGuards: barrels.compatibilityGuards,
|
|
138
143
|
};
|
|
139
144
|
}
|
|
140
145
|
export class InstallExecutionError extends Error {
|