@no-k/hermes 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.
Files changed (234) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +88 -0
  3. package/bin/hermes.mjs +159 -0
  4. package/catalog.json +1084 -0
  5. package/lib/catalog.mjs +51 -0
  6. package/package.json +28 -0
  7. package/templates/algorithm/geometry/ccw/ccw.ts +25 -0
  8. package/templates/algorithm/geometry/ccw/index.ts +1 -0
  9. package/templates/algorithm/geometry/convex-hull/convex-hull.ts +43 -0
  10. package/templates/algorithm/geometry/convex-hull/index.ts +1 -0
  11. package/templates/algorithm/geometry/polygon-area/index.ts +1 -0
  12. package/templates/algorithm/geometry/polygon-area/polygon-area.ts +13 -0
  13. package/templates/algorithm/graph/bellman-ford/bellman-ford.ts +46 -0
  14. package/templates/algorithm/graph/bellman-ford/index.ts +1 -0
  15. package/templates/algorithm/graph/bfs/bfs.ts +21 -0
  16. package/templates/algorithm/graph/bfs/index.ts +1 -0
  17. package/templates/algorithm/graph/dfs/dfs.ts +22 -0
  18. package/templates/algorithm/graph/dfs/index.ts +1 -0
  19. package/templates/algorithm/graph/dijkstra/dijkstra.ts +37 -0
  20. package/templates/algorithm/graph/dijkstra/index.ts +1 -0
  21. package/templates/algorithm/graph/floyd-warshall/floyd-warshall.ts +17 -0
  22. package/templates/algorithm/graph/floyd-warshall/index.ts +1 -0
  23. package/templates/algorithm/graph/kruskal/index.ts +1 -0
  24. package/templates/algorithm/graph/kruskal/kruskal.ts +29 -0
  25. package/templates/algorithm/graph/prim/index.ts +1 -0
  26. package/templates/algorithm/graph/prim/prim.ts +44 -0
  27. package/templates/algorithm/graph/topology-sort/index.ts +1 -0
  28. package/templates/algorithm/graph/topology-sort/topology-sort.ts +31 -0
  29. package/templates/algorithm/search/binary-search/binary-search.ts +74 -0
  30. package/templates/algorithm/search/binary-search/index.ts +1 -0
  31. package/templates/algorithm/string/kmp/index.ts +1 -0
  32. package/templates/algorithm/string/kmp/kmp.ts +46 -0
  33. package/templates/algorithm/string/z-algorithm/index.ts +1 -0
  34. package/templates/algorithm/string/z-algorithm/z-algorithm.ts +41 -0
  35. package/templates/algorithm/tree/avl-tree/avl-tree.ts +197 -0
  36. package/templates/algorithm/tree/avl-tree/index.ts +1 -0
  37. package/templates/algorithm/tree/binary-search-tree/binary-search-tree.ts +144 -0
  38. package/templates/algorithm/tree/binary-search-tree/index.ts +1 -0
  39. package/templates/algorithm/tree/lca/index.ts +1 -0
  40. package/templates/algorithm/tree/lca/lca.ts +119 -0
  41. package/templates/array/chunk/chunk.ts +13 -0
  42. package/templates/array/chunk/index.ts +1 -0
  43. package/templates/array/compact/compact.ts +8 -0
  44. package/templates/array/compact/index.ts +1 -0
  45. package/templates/array/count-by/count-by.ts +12 -0
  46. package/templates/array/count-by/index.ts +1 -0
  47. package/templates/array/difference/difference.ts +6 -0
  48. package/templates/array/difference/index.ts +1 -0
  49. package/templates/array/drop/drop.ts +7 -0
  50. package/templates/array/drop/index.ts +1 -0
  51. package/templates/array/group-by/group-by.ts +18 -0
  52. package/templates/array/group-by/index.ts +1 -0
  53. package/templates/array/intersection/index.ts +1 -0
  54. package/templates/array/intersection/intersection.ts +15 -0
  55. package/templates/array/key-by/index.ts +1 -0
  56. package/templates/array/key-by/key-by.ts +11 -0
  57. package/templates/array/max-by/index.ts +1 -0
  58. package/templates/array/max-by/max-by.ts +20 -0
  59. package/templates/array/min-by/index.ts +1 -0
  60. package/templates/array/min-by/min-by.ts +20 -0
  61. package/templates/array/partition/index.ts +1 -0
  62. package/templates/array/partition/partition.ts +16 -0
  63. package/templates/array/range/index.ts +1 -0
  64. package/templates/array/range/range.ts +22 -0
  65. package/templates/array/sample/index.ts +1 -0
  66. package/templates/array/sample/sample.ts +10 -0
  67. package/templates/array/shuffle/index.ts +1 -0
  68. package/templates/array/shuffle/shuffle.ts +15 -0
  69. package/templates/array/sort-by/index.ts +1 -0
  70. package/templates/array/sort-by/sort-by.ts +32 -0
  71. package/templates/array/sum-by/index.ts +1 -0
  72. package/templates/array/sum-by/sum-by.ts +9 -0
  73. package/templates/array/take/index.ts +1 -0
  74. package/templates/array/take/take.ts +7 -0
  75. package/templates/array/union/index.ts +1 -0
  76. package/templates/array/union/union.ts +5 -0
  77. package/templates/array/unique/index.ts +1 -0
  78. package/templates/array/unique/unique.ts +5 -0
  79. package/templates/array/unique-by/index.ts +1 -0
  80. package/templates/array/unique-by/unique-by.ts +19 -0
  81. package/templates/array/windowed/index.ts +1 -0
  82. package/templates/array/windowed/windowed.ts +15 -0
  83. package/templates/array/zip/index.ts +1 -0
  84. package/templates/array/zip/zip.ts +11 -0
  85. package/templates/boolean/parse-boolean/index.ts +1 -0
  86. package/templates/boolean/parse-boolean/parse-boolean.ts +17 -0
  87. package/templates/boolean/toggle/index.ts +1 -0
  88. package/templates/boolean/toggle/toggle.ts +2 -0
  89. package/templates/boolean/xor/index.ts +1 -0
  90. package/templates/boolean/xor/xor.ts +2 -0
  91. package/templates/data-structure/deque/deque.ts +79 -0
  92. package/templates/data-structure/deque/index.ts +1 -0
  93. package/templates/data-structure/fenwick-tree/fenwick-tree.ts +76 -0
  94. package/templates/data-structure/fenwick-tree/index.ts +1 -0
  95. package/templates/data-structure/heap/heap.ts +88 -0
  96. package/templates/data-structure/heap/index.ts +3 -0
  97. package/templates/data-structure/heap/max-heap.ts +7 -0
  98. package/templates/data-structure/heap/min-heap.ts +7 -0
  99. package/templates/data-structure/priority-queue/index.ts +1 -0
  100. package/templates/data-structure/priority-queue/priority-queue.ts +44 -0
  101. package/templates/data-structure/queue/index.ts +1 -0
  102. package/templates/data-structure/queue/queue.ts +66 -0
  103. package/templates/data-structure/segment-tree/index.ts +1 -0
  104. package/templates/data-structure/segment-tree/segment-tree.ts +79 -0
  105. package/templates/data-structure/trie/index.ts +1 -0
  106. package/templates/data-structure/trie/trie.ts +104 -0
  107. package/templates/data-structure/union-find/index.ts +1 -0
  108. package/templates/data-structure/union-find/union-find.ts +79 -0
  109. package/templates/dom/attribute/attribute.ts +24 -0
  110. package/templates/dom/attribute/index.ts +1 -0
  111. package/templates/dom/class-name/class-name.ts +48 -0
  112. package/templates/dom/class-name/index.ts +1 -0
  113. package/templates/dom/is-element/index.ts +1 -0
  114. package/templates/dom/is-element/is-element.ts +20 -0
  115. package/templates/dom/query/index.ts +1 -0
  116. package/templates/dom/query/query.ts +7 -0
  117. package/templates/http/content-type/content-type.ts +130 -0
  118. package/templates/http/content-type/index.ts +1 -0
  119. package/templates/http/header/header.ts +56 -0
  120. package/templates/http/header/index.ts +1 -0
  121. package/templates/http/method/index.ts +1 -0
  122. package/templates/http/method/method.ts +25 -0
  123. package/templates/http/status/index.ts +1 -0
  124. package/templates/http/status/status.ts +191 -0
  125. package/templates/number/clamp/clamp.ts +11 -0
  126. package/templates/number/clamp/index.ts +1 -0
  127. package/templates/number/factorial/factorial.ts +15 -0
  128. package/templates/number/factorial/index.ts +1 -0
  129. package/templates/number/format-thousands/format-thousands.ts +24 -0
  130. package/templates/number/format-thousands/index.ts +1 -0
  131. package/templates/number/gcd/gcd.ts +13 -0
  132. package/templates/number/gcd/index.ts +1 -0
  133. package/templates/number/in-range/in-range.ts +3 -0
  134. package/templates/number/in-range/index.ts +1 -0
  135. package/templates/number/is-even/index.ts +1 -0
  136. package/templates/number/is-even/is-even.ts +5 -0
  137. package/templates/number/is-odd/index.ts +1 -0
  138. package/templates/number/is-odd/is-odd.ts +7 -0
  139. package/templates/number/lcm/index.ts +1 -0
  140. package/templates/number/lcm/lcm.ts +12 -0
  141. package/templates/number/lerp/index.ts +1 -0
  142. package/templates/number/lerp/lerp.ts +3 -0
  143. package/templates/number/percentage/index.ts +1 -0
  144. package/templates/number/percentage/percentage.ts +7 -0
  145. package/templates/number/random/index.ts +1 -0
  146. package/templates/number/random/random.ts +9 -0
  147. package/templates/number/random-int/index.ts +1 -0
  148. package/templates/number/random-int/random-int.ts +9 -0
  149. package/templates/number/round/index.ts +1 -0
  150. package/templates/number/round/round.ts +22 -0
  151. package/templates/number/safe-parse-number/index.ts +1 -0
  152. package/templates/number/safe-parse-number/safe-parse-number.ts +29 -0
  153. package/templates/number/to-ordinal/index.ts +1 -0
  154. package/templates/number/to-ordinal/to-ordinal.ts +28 -0
  155. package/templates/object/deep-clone/deep-clone.ts +47 -0
  156. package/templates/object/deep-clone/index.ts +1 -0
  157. package/templates/object/deep-equal/deep-equal.ts +66 -0
  158. package/templates/object/deep-equal/index.ts +1 -0
  159. package/templates/object/deep-merge/deep-merge.ts +49 -0
  160. package/templates/object/deep-merge/index.ts +1 -0
  161. package/templates/object/defaults/defaults.ts +14 -0
  162. package/templates/object/defaults/index.ts +1 -0
  163. package/templates/object/filter-object/filter-object.ts +11 -0
  164. package/templates/object/filter-object/index.ts +1 -0
  165. package/templates/object/get/get.ts +75 -0
  166. package/templates/object/get/index.ts +1 -0
  167. package/templates/object/has/has.ts +60 -0
  168. package/templates/object/has/index.ts +1 -0
  169. package/templates/object/invert/index.ts +1 -0
  170. package/templates/object/invert/invert.ts +13 -0
  171. package/templates/object/map-keys/index.ts +1 -0
  172. package/templates/object/map-keys/map-keys.ts +16 -0
  173. package/templates/object/map-values/index.ts +1 -0
  174. package/templates/object/map-values/map-values.ts +14 -0
  175. package/templates/object/omit/index.ts +1 -0
  176. package/templates/object/omit/omit.ts +15 -0
  177. package/templates/object/omit-by/index.ts +1 -0
  178. package/templates/object/omit-by/omit-by.ts +19 -0
  179. package/templates/object/pick/index.ts +1 -0
  180. package/templates/object/pick/pick.ts +15 -0
  181. package/templates/object/pick-by/index.ts +1 -0
  182. package/templates/object/pick-by/pick-by.ts +19 -0
  183. package/templates/object/set/index.ts +1 -0
  184. package/templates/object/set/set.ts +118 -0
  185. package/templates/string/capitalize/capitalize.ts +4 -0
  186. package/templates/string/capitalize/index.ts +1 -0
  187. package/templates/string/change-case/change-case.ts +98 -0
  188. package/templates/string/change-case/index.ts +1 -0
  189. package/templates/string/char/char.ts +14 -0
  190. package/templates/string/char/index.ts +1 -0
  191. package/templates/string/count-occurrences/count-occurrences.ts +20 -0
  192. package/templates/string/count-occurrences/index.ts +1 -0
  193. package/templates/string/escape-html/escape-html.ts +16 -0
  194. package/templates/string/escape-html/index.ts +1 -0
  195. package/templates/string/get-decorated-string/get-decorated-string.ts +7 -0
  196. package/templates/string/get-decorated-string/index.ts +1 -0
  197. package/templates/string/is-blank/index.ts +1 -0
  198. package/templates/string/is-blank/is-blank.ts +6 -0
  199. package/templates/string/mask/index.ts +1 -0
  200. package/templates/string/mask/mask.ts +30 -0
  201. package/templates/string/pad/index.ts +1 -0
  202. package/templates/string/pad/pad.ts +17 -0
  203. package/templates/string/random-string/index.ts +1 -0
  204. package/templates/string/random-string/random-string.ts +20 -0
  205. package/templates/string/reverse/index.ts +1 -0
  206. package/templates/string/reverse/reverse.ts +6 -0
  207. package/templates/string/slugify/index.ts +1 -0
  208. package/templates/string/slugify/slugify.ts +11 -0
  209. package/templates/string/strip-tags/index.ts +1 -0
  210. package/templates/string/strip-tags/strip-tags.ts +8 -0
  211. package/templates/string/template/index.ts +1 -0
  212. package/templates/string/template/template.ts +14 -0
  213. package/templates/string/truncate/index.ts +1 -0
  214. package/templates/string/truncate/truncate.ts +17 -0
  215. package/templates/string/truncate-middle/index.ts +1 -0
  216. package/templates/string/truncate-middle/truncate-middle.ts +25 -0
  217. package/templates/string/uuid/index.ts +1 -0
  218. package/templates/string/uuid/uuid.ts +9 -0
  219. package/templates/tailwindcss/class-variant/class-variant.ts +35 -0
  220. package/templates/tailwindcss/class-variant/index.ts +1 -0
  221. package/templates/tailwindcss/cn/cn.ts +29 -0
  222. package/templates/tailwindcss/cn/index.ts +1 -0
  223. package/templates/tailwindcss/create-tailwind-css/create-tailwind-css.ts +16 -0
  224. package/templates/tailwindcss/create-tailwind-css/index.ts +1 -0
  225. package/templates/tailwindcss/tw-merge-config/index.ts +1 -0
  226. package/templates/tailwindcss/tw-merge-config/tw-merge-config.ts +14 -0
  227. package/templates/type/get-type/get-type.ts +27 -0
  228. package/templates/type/get-type/index.ts +1 -0
  229. package/templates/type/is-type-of/index.ts +1 -0
  230. package/templates/type/is-type-of/is-type-of.ts +26 -0
  231. package/templates/util/mutex/index.ts +1 -0
  232. package/templates/util/mutex/mutex.ts +109 -0
  233. package/templates/util/url-parser/index.ts +1 -0
  234. package/templates/util/url-parser/url-parser.ts +44 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 no-k contributors
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
14
+ OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15
+ CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @no-k/hermes
2
+
3
+ Hermes CLI로 필요한 TypeScript 유틸 소스를 지정한 디렉터리에 생성합니다.
4
+ 선택한 유틸과 필요한 보조 파일은 npm 패키지에 함께 들어 있습니다.
5
+ CLI는 Node.js 기본 모듈만 사용하며 별도 런타임 의존성이 없습니다.
6
+
7
+ ## 사용
8
+
9
+ Node.js 22 이상이 필요합니다. 아래 명령은 공개 배포 후 사용할 수 있습니다.
10
+
11
+ ```bash
12
+ npx @no-k/hermes@0.1.0 list
13
+ npx @no-k/hermes@0.1.0 add array-chunk --dir src/lib/hermes
14
+ ```
15
+
16
+ 두 번째 명령은 다음 파일을 생성합니다.
17
+
18
+ ```text
19
+ src/lib/hermes/
20
+ ├── array/chunk/chunk.ts
21
+ ├── array/chunk/index.ts
22
+ └── LICENSE
23
+ ```
24
+
25
+ 프로젝트의 실제 경로에 맞춰 import합니다.
26
+
27
+ ```ts
28
+ import { chunk } from "./lib/hermes/array/chunk";
29
+
30
+ chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
31
+ ```
32
+
33
+ `--dir`는 현재 작업 디렉터리 기준 상대 경로나 절대 경로를 받습니다.
34
+ 공백이 있는 경로는 따옴표로 감싸세요. 그 아래에는 유틸의 카테고리/이름 구조를 유지합니다.
35
+ 설정 파일이나 package.json이 없는 디렉터리에서도 파일 생성이 가능합니다.
36
+
37
+ ## 명령과 옵션
38
+
39
+ | 명령/옵션 | 동작 |
40
+ | --- | --- |
41
+ | list | 설치 가능한 항목과 설명 출력 |
42
+ | add <항목...> --dir <경로> | 선택한 항목과 보조 파일 생성 |
43
+ | --dry-run | 생성·유지·교체 예정 파일만 출력 |
44
+ | --overwrite | 내용이 다른 기존 파일 교체를 명시적으로 허용 |
45
+ | --help | 도움말 |
46
+ | --version | CLI 버전 |
47
+
48
+ ```bash
49
+ npx @no-k/hermes@0.1.0 add array-chunk number-lcm --dir src/shared/hermes
50
+ npx @no-k/hermes@0.1.0 add array-chunk --dir "src/my utilities" --dry-run
51
+ ```
52
+
53
+ 내용이 같은 기존 파일은 유지합니다. 내용이 다른 파일이 하나라도 있으면 쓰기 전에 중단합니다.
54
+ 의도적으로 바꾸려면 변경 내용을 검토한 뒤 --overwrite를 사용하세요.
55
+ 목적지의 심볼릭 링크를 통해 파일을 쓰지 않습니다.
56
+
57
+ ## 항목과 의존성
58
+
59
+ 항목 이름은 core 디렉터리 경로를 하이픈으로 연결한 이름입니다.
60
+ Tailwind 항목에는 tailwindcss- 접두어를 붙입니다.
61
+
62
+ | 항목 | 포함하는 소스 |
63
+ | --- | --- |
64
+ | array-chunk | 배열 분할 |
65
+ | number-lcm | 최소공배수와 필요한 gcd |
66
+ | algorithm-graph-dijkstra | 최단 거리와 필요한 우선순위 큐·힙 |
67
+ | tailwindcss-cn | 클래스 병합과 설정 |
68
+ | tailwindcss-create-tailwind-css | 설정을 공유하는 cn·classVariant |
69
+ | string-uuid | Node.js UUID 생성 |
70
+
71
+ core 항목은 외부 런타임 npm 의존성이 없습니다.
72
+ Tailwind 항목은 clsx, tailwind-merge, tailwind-variants 중 해당 항목이 참조하는 패키지가 필요합니다.
73
+ CLI가 필요한 설치 명령을 출력합니다. 소비 프로젝트의 패키지 매니저에 맞춰 직접 설치하세요.
74
+ CLI는 package.json이나 lockfile을 수정하지 않습니다.
75
+
76
+ Node용 항목은 @types/node 설치 명령도 안내합니다.
77
+ string-uuid는 정적 node:crypto import 때문에 Node.js에서 사용해야 합니다.
78
+ dom-* 항목에는 DOM API와 DOM TypeScript 타입이 필요합니다.
79
+ 현재 Tailwind 헬퍼의 병합 규칙은 Tailwind CSS 4 기준입니다.
80
+
81
+ ## 업데이트와 라이선스
82
+
83
+ 명령에서 Hermes 버전을 고정하면 같은 템플릿을 받습니다.
84
+ 안내하는 외부 의존성은 버전 범위를 사용하므로 정확한 설치 버전은 소비 프로젝트 lockfile로 관리합니다.
85
+ 이미 복사한 소스는 새 버전이 나와도 자동 변경되지 않습니다.
86
+ 업데이트할 때는 직접 수정한 부분과 공통 보조 파일의 호환성을 함께 검토하세요.
87
+
88
+ ISC 라이선스입니다. 함께 생성되는 LICENSE의 저작권·허가 문구를 유지해 주세요.
package/bin/hermes.mjs ADDED
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ import { lstatSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { parseArgs } from "node:util";
6
+ import { validateCatalog } from "../lib/catalog.mjs";
7
+
8
+ const packageDirectory = fileURLToPath(new URL("../", import.meta.url));
9
+ const manifest = JSON.parse(readFileSync(path.join(packageDirectory, "package.json"), "utf8"));
10
+ const help = [
11
+ "Hermes " + manifest.version + " — copy TypeScript utilities into your project",
12
+ "",
13
+ "Usage:",
14
+ " hermes list",
15
+ " hermes add <item...> --dir <directory> [--dry-run] [--overwrite]",
16
+ "",
17
+ "Options:",
18
+ " -d, --dir Destination for utility folders and LICENSE (required for add)",
19
+ " --dry-run Show the planned files without writing",
20
+ " --overwrite Replace existing files whose contents differ",
21
+ " -h, --help Show help",
22
+ " -v, --version Show version",
23
+ "",
24
+ "Example:",
25
+ " npx @no-k/hermes add array-chunk --dir src/lib/hermes",
26
+ "",
27
+ "External npm dependencies are reported; they are not installed automatically.",
28
+ ].join("\n");
29
+
30
+ function stat(file) {
31
+ try {
32
+ return lstatSync(file);
33
+ } catch (error) {
34
+ if (error.code === "ENOENT") return undefined;
35
+ throw error;
36
+ }
37
+ }
38
+
39
+ function destinationStat(directory, relative) {
40
+ let current = directory;
41
+ const segments = relative.split("/");
42
+ for (let index = -1; index < segments.length; index += 1) {
43
+ if (index >= 0) current = path.join(current, segments[index]);
44
+ const info = stat(current);
45
+ if (info?.isSymbolicLink()) throw new Error("Refusing to write through a symbolic link: " + current);
46
+ if (index < segments.length - 1 && info && !info.isDirectory()) {
47
+ throw new Error("Expected a directory: " + current);
48
+ }
49
+ if (index === segments.length - 1) {
50
+ if (info && !info.isFile()) throw new Error("Expected a regular file: " + current);
51
+ return info;
52
+ }
53
+ }
54
+ }
55
+
56
+ function sourceContent(relative) {
57
+ const directory = realpathSync(path.join(packageDirectory, "templates"));
58
+ const file = realpathSync(path.join(directory, relative));
59
+ const fromRoot = path.relative(directory, file);
60
+ if (path.isAbsolute(fromRoot) || fromRoot === ".." || fromRoot.startsWith(".." + path.sep)) {
61
+ throw new Error("Source template escapes its directory: " + relative);
62
+ }
63
+ return readFileSync(file, "utf8");
64
+ }
65
+
66
+ function add(items, options) {
67
+ if (!options.dir?.trim()) throw new Error("Specify a destination with --dir <directory>.");
68
+ const directory = path.resolve(options.dir);
69
+ const files = new Map();
70
+ for (const item of items) {
71
+ for (const file of item.files) {
72
+ if (!files.has(file)) files.set(file, sourceContent(file));
73
+ }
74
+ }
75
+ files.set("LICENSE", readFileSync(path.join(packageDirectory, "LICENSE"), "utf8"));
76
+ const plan = [...files]
77
+ .sort(([a], [b]) => a.localeCompare(b))
78
+ .map(([file, content]) => {
79
+ const destination = path.join(directory, file);
80
+ const existing = destinationStat(directory, file);
81
+ const same = existing && readFileSync(destination, "utf8") === content;
82
+ return { file, content, destination, action: same ? "keep" : existing ? "replace" : "create" };
83
+ });
84
+ const conflicts = plan.filter((file) => file.action === "replace");
85
+ if (conflicts.length && !options.overwrite) {
86
+ throw new Error(
87
+ "Existing files differ; no files were written:\n" +
88
+ conflicts.map((file) => " " + file.destination).join("\n") +
89
+ "\nReview the changes, then use --overwrite if you want to replace them.",
90
+ );
91
+ }
92
+ for (const file of plan) {
93
+ if (!options["dry-run"] && file.action !== "keep") {
94
+ mkdirSync(path.dirname(file.destination), { recursive: true });
95
+ writeFileSync(file.destination, file.content, { flag: file.action === "create" ? "wx" : "w" });
96
+ }
97
+ console.log((options["dry-run"] ? "[dry-run] " : "") + file.action + " " + file.destination);
98
+ }
99
+ const dependencies = [...new Set(items.flatMap((item) => item.dependencies))].sort();
100
+ const devDependencies = [...new Set(items.flatMap((item) => item.devDependencies ?? []))].sort();
101
+ if (dependencies.length || devDependencies.length) {
102
+ console.log("\nThese utilities need npm packages. Install them in your consuming project:");
103
+ if (dependencies.length)
104
+ console.log(" npm install " + dependencies.map((value) => JSON.stringify(value)).join(" "));
105
+ if (devDependencies.length) {
106
+ console.log(" npm install --save-dev " + devDependencies.map((value) => JSON.stringify(value)).join(" "));
107
+ }
108
+ }
109
+ for (const item of items) {
110
+ if (item.runtime === "node") console.log("\n" + item.name + ": requires Node.js (imports a Node.js builtin).");
111
+ if (item.runtime === "dom") console.log("\n" + item.name + ": requires DOM APIs and DOM TypeScript types.");
112
+ }
113
+ console.log("\n" + (options["dry-run"] ? "Planned" : "Added") + " " + items.map((item) => item.name).join(", "));
114
+ }
115
+
116
+ function main() {
117
+ const { values, positionals } = parseArgs({
118
+ options: {
119
+ dir: { type: "string", short: "d" },
120
+ "dry-run": { type: "boolean" },
121
+ overwrite: { type: "boolean" },
122
+ help: { type: "boolean", short: "h" },
123
+ version: { type: "boolean", short: "v" },
124
+ },
125
+ allowPositionals: true,
126
+ });
127
+ if (values.help || (!positionals.length && !values.version)) {
128
+ console.log(help);
129
+ return;
130
+ }
131
+ if (values.version) {
132
+ console.log(manifest.version);
133
+ return;
134
+ }
135
+ const [command, ...names] = positionals;
136
+ if (!["list", "add"].includes(command)) throw new Error("Unknown command: " + command + ". Use --help.");
137
+ const catalog = validateCatalog(JSON.parse(readFileSync(path.join(packageDirectory, "catalog.json"), "utf8")));
138
+ if (catalog.version !== manifest.version)
139
+ throw new Error("The catalog version does not match the package. Rebuild the CLI.");
140
+ if (command === "list") {
141
+ if (names.length || values.dir || values.overwrite || values["dry-run"]) throw new Error("Usage: hermes list");
142
+ for (const item of catalog.items) console.log(item.name + "\t" + item.description);
143
+ return;
144
+ }
145
+ if (!names.length) throw new Error("Specify at least one item. Use 'hermes list' to see available items.");
146
+ const items = [...new Set(names)].map((name) => {
147
+ const item = catalog.items.find((candidate) => candidate.name === name);
148
+ if (!item) throw new Error("Unknown item: " + name + ". Use 'hermes list' to see available items.");
149
+ return item;
150
+ });
151
+ add(items, values);
152
+ }
153
+
154
+ try {
155
+ main();
156
+ } catch (error) {
157
+ console.error("Hermes: " + error.message);
158
+ process.exitCode = 1;
159
+ }