for-humanity 0.12.0 → 0.13.1

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 (226) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +16 -9
  3. package/dist/browser.js +14 -16
  4. package/dist/cli.css +2 -968
  5. package/dist/cli.js +92 -2318
  6. package/dist/config.js +1 -0
  7. package/dist/constant/copy.d.ts +100 -0
  8. package/dist/constant/site.d.ts +20 -0
  9. package/dist/constant/status.d.ts +19 -0
  10. package/dist/copy-C1uzvEnr.js +103 -0
  11. package/dist/group-path-XAThmzvC.js +10 -0
  12. package/dist/load-site-B-u5-jv0.js +2343 -0
  13. package/dist/logger-Dv1i9xSV.js +9 -0
  14. package/dist/mermaid/THIRD-PARTY-NOTICES.txt +3134 -0
  15. package/dist/mermaid/chunk--TN-qHgG.js +2 -0
  16. package/dist/mermaid/chunk-0kM1Lv4D.js +2 -0
  17. package/dist/mermaid/chunk-3FHei3pH.js +2001 -0
  18. package/dist/mermaid/chunk-3_2p2GqN.js +2 -0
  19. package/dist/mermaid/chunk-6HLei9i0.js +84 -0
  20. package/dist/mermaid/chunk-7Sz3kAwl.js +2214 -0
  21. package/dist/mermaid/chunk-7Tarqbpa.js +18047 -0
  22. package/dist/mermaid/chunk-9kkt8lzL.js +10 -0
  23. package/dist/mermaid/chunk-B--pNBQF.js +884 -0
  24. package/dist/mermaid/chunk-B4F-pysn.js +4241 -0
  25. package/dist/mermaid/chunk-B58sEIY_.js +14 -0
  26. package/dist/mermaid/chunk-B9_UWzSH.js +1517 -0
  27. package/dist/mermaid/chunk-BB8uZHwY.js +75 -0
  28. package/dist/mermaid/chunk-BFfk0LEc.js +717 -0
  29. package/dist/mermaid/chunk-BKri0qr7.js +89 -0
  30. package/dist/mermaid/chunk-BL404pH5.js +26 -0
  31. package/dist/mermaid/chunk-BLeiQSWo.js +354 -0
  32. package/dist/mermaid/chunk-BOmH_UFd.js +2 -0
  33. package/dist/mermaid/chunk-BSej9Nhy.js +1361 -0
  34. package/dist/mermaid/chunk-BYzrzvCI.js +12 -0
  35. package/dist/mermaid/chunk-BariMtWr.js +3020 -0
  36. package/dist/mermaid/chunk-BcrBoMwH.js +2352 -0
  37. package/dist/mermaid/chunk-Bok8z-Yo.js +67 -0
  38. package/dist/mermaid/chunk-BpTHLKQM.js +1970 -0
  39. package/dist/mermaid/chunk-BqjuxRsC.js +733 -0
  40. package/dist/mermaid/chunk-BuvPd-s5.js +1073 -0
  41. package/dist/mermaid/chunk-BvVehae3.js +5069 -0
  42. package/dist/mermaid/chunk-C-JABYpd.js +3075 -0
  43. package/dist/mermaid/chunk-C9XlTm4w.js +2 -0
  44. package/dist/mermaid/chunk-CApNbSU9.js +6 -0
  45. package/dist/mermaid/chunk-CCM2AH-C.js +9 -0
  46. package/dist/mermaid/chunk-CD1E0F_N.js +2 -0
  47. package/dist/mermaid/chunk-CDWvKG7D.js +2 -0
  48. package/dist/mermaid/chunk-CEVPpC1F.js +34 -0
  49. package/dist/mermaid/chunk-CLDr12lf.js +47504 -0
  50. package/dist/mermaid/chunk-CLVYqOBI.js +61 -0
  51. package/dist/mermaid/chunk-CLeOkFb5.js +15 -0
  52. package/dist/mermaid/chunk-CLgRAtOz.js +37 -0
  53. package/dist/mermaid/chunk-CMqFNUBl.js +134 -0
  54. package/dist/mermaid/chunk-CTPaBJXB.js +2 -0
  55. package/dist/mermaid/chunk-CVi6IGBL.js +2 -0
  56. package/dist/mermaid/chunk-CWV_EN-4.js +4745 -0
  57. package/dist/mermaid/chunk-CZ0dAs3g.js +2 -0
  58. package/dist/mermaid/chunk-C_YGy4Fn.js +236 -0
  59. package/dist/mermaid/chunk-CcVH22Yt.js +100 -0
  60. package/dist/mermaid/chunk-CgG6CD9_.js +7175 -0
  61. package/dist/mermaid/chunk-CgOP6IDB.js +3397 -0
  62. package/dist/mermaid/chunk-Cgg2UTSf.js +13 -0
  63. package/dist/mermaid/chunk-ChJfJJ1A.js +214 -0
  64. package/dist/mermaid/chunk-Ck5XRA46.js +447 -0
  65. package/dist/mermaid/chunk-Ckuw16cl.js +203 -0
  66. package/dist/mermaid/chunk-CpAbRCbF.js +1880 -0
  67. package/dist/mermaid/chunk-CuYekbSv.js +2 -0
  68. package/dist/mermaid/chunk-CwS4wZ5a.js +2 -0
  69. package/dist/mermaid/chunk-D-bcrBO3.js +82 -0
  70. package/dist/mermaid/chunk-D3IEAkCW.js +76 -0
  71. package/dist/mermaid/chunk-D8XfBJ_K.js +645 -0
  72. package/dist/mermaid/chunk-D9Ddd9y4.js +2228 -0
  73. package/dist/mermaid/chunk-DBh1mIbK.js +12 -0
  74. package/dist/mermaid/chunk-DD6Df6Ic.js +5 -0
  75. package/dist/mermaid/chunk-DGVDaXjx.js +8 -0
  76. package/dist/mermaid/chunk-DK9KqIXj.js +632 -0
  77. package/dist/mermaid/chunk-DORnIAsy.js +4487 -0
  78. package/dist/mermaid/chunk-DU69_VcF.js +363 -0
  79. package/dist/mermaid/chunk-DVNvSFoz.js +2144 -0
  80. package/dist/mermaid/chunk-DZe-4kde.js +20 -0
  81. package/dist/mermaid/chunk-D_a06Rz4.js +287 -0
  82. package/dist/mermaid/chunk-DfyFJJi2.js +972 -0
  83. package/dist/mermaid/chunk-DhKYPp1Z.js +2 -0
  84. package/dist/mermaid/chunk-DjFqgNTG.js +94 -0
  85. package/dist/mermaid/chunk-DmbDDrr-.js +65 -0
  86. package/dist/mermaid/chunk-DpuxlyL_.js +696 -0
  87. package/dist/mermaid/chunk-DqGhppWe.js +2 -0
  88. package/dist/mermaid/chunk-DqPZxHXa.js +5 -0
  89. package/dist/mermaid/chunk-Dt_1Jyif.js +293 -0
  90. package/dist/mermaid/chunk-DyQBE_NW.js +1935 -0
  91. package/dist/mermaid/chunk-JckyC4mW.js +1009 -0
  92. package/dist/mermaid/chunk-K7Myuj2-.js +1334 -0
  93. package/dist/mermaid/chunk-KuPYb48V.js +275 -0
  94. package/dist/mermaid/chunk-MFVRUoO0.js +4020 -0
  95. package/dist/mermaid/chunk-MbInCS_6.js +28 -0
  96. package/dist/mermaid/chunk-MexOLyKV.js +355 -0
  97. package/dist/mermaid/chunk-NnbDtJzg.js +21856 -0
  98. package/dist/mermaid/chunk-SGpOYZbI.js +2139 -0
  99. package/dist/mermaid/chunk-TbxLDlcU.js +198 -0
  100. package/dist/mermaid/chunk-TftHqGLg.js +490 -0
  101. package/dist/mermaid/chunk-UjQ8vA_Z.js +4138 -0
  102. package/dist/mermaid/chunk-YgmVTKqc.js +16 -0
  103. package/dist/mermaid/chunk-_a9EZE1v.js +1798 -0
  104. package/dist/mermaid/chunk-dTb8jqeP.js +2 -0
  105. package/dist/mermaid/chunk-fFdMfF8S.js +2240 -0
  106. package/dist/mermaid/chunk-feYbMzBj.js +1267 -0
  107. package/dist/mermaid/chunk-g3Pn8DkW.js +46 -0
  108. package/dist/mermaid/chunk-hM77LvCb.js +19832 -0
  109. package/dist/mermaid/chunk-hUDb-eAW.js +527 -0
  110. package/dist/mermaid/chunk-tpABtZ-3.js +4252 -0
  111. package/dist/mermaid/chunk-xIbz3oFK.js +59 -0
  112. package/dist/mermaid/chunk-yBz-vst0.js +976 -0
  113. package/dist/mermaid/mermaid-BhtR3wyB.js +1145 -0
  114. package/dist/run-site-command-cqCjpIlc.js +129 -0
  115. package/dist/site-config-bllORGpm.js +90 -0
  116. package/dist/site.js +1 -0
  117. package/dist/start-dev-server-x7Ja5uwv.js +184 -0
  118. package/dist/to-change-queue-DbEnZGNK.js +34 -0
  119. package/dist/to-error-message-pF6_7wGK.js +16 -0
  120. package/dist/type/group-path.d.ts +7 -0
  121. package/dist/type/site-config.d.ts +57 -0
  122. package/package.json +34 -17
  123. package/src/component/widget/cursor-face/_constant/cursor-face.ts +0 -14
  124. package/src/component/widget/cursor-face/wg-cursor-face.css +0 -15
  125. package/src/component/widget/cursor-face/wg-cursor-face.tsx +0 -153
  126. package/src/component/widget/navigation/_constant/locale.ts +0 -9
  127. package/src/component/widget/navigation/_constant/navigation-data-schema.ts +0 -33
  128. package/src/component/widget/navigation/_constant/navigation.ts +0 -9
  129. package/src/component/widget/navigation/_constant/reading-line.ts +0 -4
  130. package/src/component/widget/navigation/_function/restore-navigation-scroll.ts +0 -59
  131. package/src/component/widget/navigation/_function/to-doc-groups/_to-doc-branches.ts +0 -15
  132. package/src/component/widget/navigation/_function/to-doc-groups/_to-doc-group-branches.ts +0 -70
  133. package/src/component/widget/navigation/_function/to-doc-groups/to-doc-groups.ts +0 -15
  134. package/src/component/widget/navigation/_function/to-navigation-data/_to-navigation-document.ts +0 -13
  135. package/src/component/widget/navigation/_function/to-navigation-data/_to-navigation-group.ts +0 -13
  136. package/src/component/widget/navigation/_function/to-navigation-data/to-navigation-data.ts +0 -18
  137. package/src/component/widget/navigation/_function/to-navigation-json.ts +0 -8
  138. package/src/component/widget/navigation/_function/to-toc-groups.ts +0 -38
  139. package/src/component/widget/navigation/_type/doc-branch.ts +0 -11
  140. package/src/component/widget/navigation/_type/doc-group.ts +0 -24
  141. package/src/component/widget/navigation/_type/navigation-data.ts +0 -75
  142. package/src/component/widget/navigation/_type/navigation-focus-snapshot.ts +0 -17
  143. package/src/component/widget/navigation/_type/toc-group.ts +0 -39
  144. package/src/component/widget/navigation/_wg-navigation-nav-list.css +0 -48
  145. package/src/component/widget/navigation/_wg-navigation-nav-list.tsx +0 -61
  146. package/src/component/widget/navigation/_wg-navigation-nav.css +0 -21
  147. package/src/component/widget/navigation/_wg-navigation-nav.tsx +0 -33
  148. package/src/component/widget/navigation/_wg-navigation-scroll.tsx +0 -117
  149. package/src/component/widget/navigation/_wg-navigation-toc.css +0 -91
  150. package/src/component/widget/navigation/_wg-navigation-toc.tsx +0 -114
  151. package/src/component/widget/navigation/wg-navigation.css +0 -282
  152. package/src/component/widget/navigation/wg-navigation.tsx +0 -420
  153. package/src/component/widget/prose/_constant/heading-highlight.ts +0 -4
  154. package/src/component/widget/prose/_constant/section.ts +0 -7
  155. package/src/component/widget/prose/_constant/status.ts +0 -9
  156. package/src/component/widget/prose/_function/has-block-content.ts +0 -30
  157. package/src/component/widget/prose/_function/rehype-head.ts +0 -31
  158. package/src/component/widget/prose/_function/rehype-sections/_to-section-headings.ts +0 -20
  159. package/src/component/widget/prose/_function/rehype-sections/rehype-sections.ts +0 -21
  160. package/src/component/widget/prose/_function/rehype-tables.ts +0 -22
  161. package/src/component/widget/prose/_function/remark-blocks.ts +0 -112
  162. package/src/component/widget/prose/_function/remark-flow.ts +0 -26
  163. package/src/component/widget/prose/_function/remark-links.ts +0 -33
  164. package/src/component/widget/prose/_function/remark-parts.ts +0 -26
  165. package/src/component/widget/prose/_function/remark-status.ts +0 -59
  166. package/src/component/widget/prose/_function/remark-swatch.ts +0 -16
  167. package/src/component/widget/prose/_function/remark-unknown-directives.ts +0 -25
  168. package/src/component/widget/prose/wg-prose.css +0 -484
  169. package/src/component/widget/prose/wg-prose.tsx +0 -43
  170. package/src/component/widget/shell/wg-shell.css +0 -63
  171. package/src/component/widget/shell/wg-shell.tsx +0 -88
  172. package/src/component/widget/shell-controls/wg-shell-controls.css +0 -4
  173. package/src/component/widget/shell-controls/wg-shell-controls.tsx +0 -170
  174. package/src/constant/asset.ts +0 -63
  175. package/src/constant/copy.ts +0 -133
  176. package/src/constant/doc.ts +0 -13
  177. package/src/constant/font.ts +0 -34
  178. package/src/constant/site.ts +0 -24
  179. package/src/constant/status.ts +0 -15
  180. package/src/entry/browser/browser.tsx +0 -22
  181. package/src/entry/cli/_constant/cli.ts +0 -19
  182. package/src/entry/cli/_constant/crawling.ts +0 -14
  183. package/src/entry/cli/_constant/snippet.ts +0 -21
  184. package/src/entry/cli/_function/create-app.tsx +0 -57
  185. package/src/entry/cli/_function/create-dev-app.ts +0 -87
  186. package/src/entry/cli/_function/create-processor/_rehype-assets.ts +0 -70
  187. package/src/entry/cli/_function/create-processor/_rehype-heading-ids.ts +0 -34
  188. package/src/entry/cli/_function/create-processor/_remark-report.ts +0 -15
  189. package/src/entry/cli/_function/create-processor/_remark-snippets.ts +0 -117
  190. package/src/entry/cli/_function/create-processor/_shiki-transformers.ts +0 -91
  191. package/src/entry/cli/_function/create-processor/create-processor.ts +0 -64
  192. package/src/entry/cli/_function/read-docs.ts +0 -128
  193. package/src/entry/cli/_function/read-home.ts +0 -32
  194. package/src/entry/cli/_function/to-crawling-app.ts +0 -42
  195. package/src/entry/cli/cli.test.ts +0 -804
  196. package/src/entry/cli/cli.ts +0 -206
  197. package/src/env.d.ts +0 -6
  198. package/src/page/doc/pg-doc.tsx +0 -35
  199. package/src/page/home/pg-home.tsx +0 -40
  200. package/src/store/navigation/create-navigation-stores.test.ts +0 -74
  201. package/src/store/navigation/create-navigation-stores.ts +0 -59
  202. package/src/store/navigation/navigation-state.ts +0 -36
  203. package/src/store/navigation/navigation-storage.ts +0 -9
  204. package/src/store/navigation/to-navigation-scroll-state.ts +0 -42
  205. package/src/style/base.css +0 -28
  206. package/src/style/token.css +0 -188
  207. package/src/type/doc-content.ts +0 -15
  208. package/src/type/doc-data.ts +0 -38
  209. package/src/type/doc-file-data.ts +0 -21
  210. package/src/type/doc-heading.ts +0 -17
  211. package/src/type/doc-outline.ts +0 -16
  212. package/src/type/doc-section.ts +0 -9
  213. package/src/type/doc.ts +0 -20
  214. package/src/type/group-path.ts +0 -7
  215. package/src/type/site-assets.ts +0 -25
  216. package/src/type/site-config.ts +0 -73
  217. package/src/type/vfile-data.d.ts +0 -10
  218. package/src/util/async/to-change-queue.test.ts +0 -58
  219. package/src/util/async/to-change-queue.ts +0 -43
  220. package/src/util/dom/find-hash-target.ts +0 -18
  221. package/src/util/dom/reveal-hash-target.ts +0 -15
  222. package/src/util/error/to-error-message.ts +0 -10
  223. package/src/util/file/to-asset-path.ts +0 -26
  224. package/src/util/font/to-font-css.test.ts +0 -41
  225. package/src/util/font/to-font-css.ts +0 -28
  226. package/src/util/mermaid/render-flow.js +0 -321
@@ -1,70 +0,0 @@
1
- import {existsSync, realpathSync, statSync} from "node:fs";
2
- import {dirname, extname, isAbsolute, relative, resolve, sep} from "node:path";
3
- import type {Root} from "hast";
4
- import {visit} from "unist-util-visit";
5
- import type {VFile} from "vfile";
6
- import {asset_media_dir, asset_media_extensions} from "@/constant/asset";
7
- import {copy_error_asset} from "@/constant/copy";
8
- import {toAssetPath} from "@/util/file/to-asset-path";
9
-
10
- /**
11
- * 명시적으로 참조한 로컬 이미지·첨부의 수집 계약
12
- */
13
- export interface AssetOptions {
14
- /**
15
- * 자원을 읽을 수 있는 문서 루트
16
- */
17
- root: string;
18
- /**
19
- * dev 응답과 정적 복사에서 공유할 URL → 파일 목록
20
- */
21
- files: Map<string, string>;
22
- }
23
-
24
- /**
25
- * 파일 위치 기준 이미지·첨부 → 내용 지문 URL · 원시 HTML도 같은 규칙
26
- * 없는 파일, 폴더 밖 경로와 symlink, 지원하지 않는 이미지 형식은 빌드 실패
27
- * 절대 URL·외부 URL은 작성자가 제공하며 수집하지 않음
28
- */
29
- export const rehypeAssets = (options: AssetOptions) => (tree: Root, file: VFile) => {
30
- visit(tree, "element", (node) => {
31
- const property = node.tagName === "img" ? "src" : "href";
32
- const url = node.properties[property];
33
-
34
- if ((node.tagName !== "img" && node.tagName !== "a") || typeof url !== "string" || /^(?:[a-z][a-z\d+.-]*:|\/|#)/i.test(url)) {
35
- return;
36
- }
37
-
38
- const match = /^([^?#]+)([?#].*)?$/.exec(url);
39
- if (match === null) {
40
- return;
41
- }
42
-
43
- const path = decodeURIComponent(match[1]);
44
- const extension = extname(path).toLowerCase();
45
- if (!asset_media_extensions.has(extension)) {
46
- if (node.tagName === "img") {
47
- file.fail(`${copy_error_asset}: 지원하지 않는 이미지 형식 · ${url}`, node);
48
- }
49
- return;
50
- }
51
-
52
- const target = resolve(dirname(file.path), path);
53
- const location = relative(options.root, target);
54
- if (location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location)) {
55
- file.fail(`${copy_error_asset}: 문서 폴더 밖 · ${url}`, node);
56
- }
57
- if (!existsSync(target)) {
58
- file.fail(`${copy_error_asset}: 파일이 없다 · ${url}`, node);
59
- }
60
- const actual = realpathSync(target);
61
- const actualLocation = relative(realpathSync(options.root), actual);
62
- if (actualLocation === ".." || actualLocation.startsWith(`..${sep}`) || isAbsolute(actualLocation) || !statSync(actual).isFile()) {
63
- file.fail(`${copy_error_asset}: 문서 폴더 밖이거나 일반 파일이 아니다 · ${url}`, node);
64
- }
65
-
66
- const assetUrl = toAssetPath({path: `${asset_media_dir}/asset${extension}`, file: actual});
67
- options.files.set(assetUrl, actual);
68
- node.properties[property] = match[2] === undefined ? assetUrl : `${assetUrl}${match[2]}`;
69
- });
70
- };
@@ -1,34 +0,0 @@
1
- import Slugger from "github-slugger";
2
- import type {Root} from "hast";
3
- import {toString as toPlainText} from "hast-util-to-string";
4
- import {visit} from "unist-util-visit";
5
- import type {VFile} from "vfile";
6
- import type {DocHeading} from "@/type/doc-heading";
7
-
8
- /**
9
- * 제목 id와 목차 수집 · 같은 제목은 -1, -2 접미사
10
- * 작성한 제목 텍스트를 그대로 사용
11
- */
12
- export const rehypeHeadingIds = () => (tree: Root, file: VFile) => {
13
- const slugger = new Slugger();
14
- const headings: DocHeading[] = [];
15
-
16
- visit(tree, "element", (node) => {
17
- const depth = /^h([1-6])$/.exec(node.tagName)?.[1];
18
-
19
- if (depth === undefined) {
20
- return;
21
- }
22
-
23
- const text = toPlainText(node);
24
-
25
- node.properties.id = typeof node.properties.id === "string" ? node.properties.id : slugger.slug(text);
26
- headings.push({depth: Number(depth), slug: node.properties.id, text});
27
- // 텍스트 노드에는 className을 줄 수 없어 강조 영역을 분리 · 제목의 기존 줄바꿈 유지
28
- node.children = [{type: "element", tagName: "span", properties: {className: ["wg_prose__headingText"]}, children: node.children}];
29
- });
30
-
31
- if (file.data.fh) {
32
- file.data.fh.headings = headings;
33
- }
34
- };
@@ -1,15 +0,0 @@
1
- import {relative} from "node:path";
2
- import type {Root} from "mdast";
3
- import type {VFile} from "vfile";
4
-
5
- /**
6
- * 파일 경고의 터미널 출력 · 파일:줄 경고 · 빌드 계속
7
- * 경고 수집 뒤 실행하는 마지막 remark 플러그인
8
- */
9
- export const remarkReport = (options: {root: string}) => (_tree: Root, file: VFile) => {
10
- const path = relative(options.root, file.path);
11
-
12
- for (const message of file.messages) {
13
- console.warn(`${message.line === undefined ? path : `${path}:${message.line}`} ${message.reason}`);
14
- }
15
- };
@@ -1,117 +0,0 @@
1
- import {existsSync, readFileSync, realpathSync, statSync} from "node:fs";
2
- import {dirname, extname, isAbsolute, relative, resolve, sep} from "node:path";
3
- import type {Root} from "mdast";
4
- import {visit} from "unist-util-visit";
5
- import type {VFile} from "vfile";
6
- import {copy_error_snippet} from "@/constant/copy";
7
- import {snippet_language_by_extension, snippet_pattern, snippet_region_pattern} from "@/entry/cli/_constant/snippet";
8
-
9
- /**
10
- * 스니펫 수집 계약
11
- */
12
- export interface SnippetOptions {
13
- /**
14
- * 스니펫을 읽을 수 있는 문서 루트
15
- */
16
- root: string;
17
- /**
18
- * dev 새로고침이 지켜볼 불러온 파일 경로
19
- */
20
- snippets: Set<string>;
21
- }
22
-
23
- /**
24
- * region 이름이 있으면 그 안만, 없으면 전체 · region 표시 줄은 항상 제거 · 공통 들여쓰기 제거
25
- */
26
- const toSnippetBody = (source: string, region: string | undefined): string | undefined => {
27
- const lines = source.replace(/\r\n/g, "\n").split("\n");
28
- let inside = region === undefined;
29
- let found = region === undefined;
30
- const kept: string[] = [];
31
-
32
- for (const line of lines) {
33
- const marker = snippet_region_pattern.exec(line);
34
-
35
- if (marker !== null) {
36
- if (region !== undefined && marker[2] === region) {
37
- inside = marker[1] === undefined;
38
- found = true;
39
- }
40
-
41
- continue;
42
- }
43
-
44
- if (inside) {
45
- kept.push(line);
46
- }
47
- }
48
-
49
- if (!found) {
50
- return undefined;
51
- }
52
-
53
- while (kept.length > 0 && kept[0]?.trim() === "") {
54
- kept.shift();
55
- }
56
-
57
- while (kept.length > 0 && kept.at(-1)?.trim() === "") {
58
- kept.pop();
59
- }
60
-
61
- const indents = kept.filter((line) => line.trim() !== "").map((line) => line.length - line.trimStart().length);
62
- const indent = indents.length === 0 ? 0 : Math.min(...indents);
63
-
64
- return kept.map((line) => line.slice(Math.min(indent, line.length - line.trimStart().length))).join("\n");
65
- };
66
-
67
- /**
68
- * `<<< ./path.ext#region 메타` 문단 → 코드 펜스 · 파일 위치 기준 경로 · 문서 폴더 밖과 없는 파일은 빌드 실패
69
- * 불러온 파일은 dev 감시 목록에 올림
70
- */
71
- export const remarkSnippets = (options: SnippetOptions) => (tree: Root, file: VFile) => {
72
- visit(tree, "paragraph", (node, index, parent) => {
73
- const [child] = node.children;
74
-
75
- if (node.children.length !== 1 || child === undefined || child.type !== "text" || parent === undefined || index === undefined) {
76
- return;
77
- }
78
-
79
- const match = snippet_pattern.exec(child.value.trim());
80
-
81
- if (match === null) {
82
- return;
83
- }
84
-
85
- const [, path = "", region, meta] = match;
86
- const target = resolve(dirname(file.path), path);
87
- const location = relative(options.root, target);
88
-
89
- if (location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location)) {
90
- file.fail(`${copy_error_snippet}: 문서 폴더 밖 · ${path}`, node);
91
- }
92
-
93
- if (!existsSync(target)) {
94
- file.fail(`${copy_error_snippet}: 파일이 없다 · ${path}`, node);
95
- }
96
-
97
- const actual = realpathSync(target);
98
- const actualLocation = relative(realpathSync(options.root), actual);
99
-
100
- if (actualLocation === ".." || actualLocation.startsWith(`..${sep}`) || isAbsolute(actualLocation) || !statSync(actual).isFile()) {
101
- file.fail(`${copy_error_snippet}: 문서 폴더 밖이거나 일반 파일이 아니다 · ${path}`, node);
102
- }
103
-
104
- const body = toSnippetBody(readFileSync(actual, "utf8"), region);
105
-
106
- if (body === undefined) {
107
- file.fail(`${copy_error_snippet}: region 이 없다 · ${path}#${region}`, node);
108
- return;
109
- }
110
-
111
- const extension = extname(actual).toLowerCase();
112
- const lang = snippet_language_by_extension[extension] === undefined ? extension.slice(1) : snippet_language_by_extension[extension];
113
-
114
- options.snippets.add(actual);
115
- parent.children.splice(index, 1, {type: "code", lang, meta: meta === undefined ? null : meta, value: body});
116
- });
117
- };
@@ -1,91 +0,0 @@
1
- import {
2
- transformerMetaHighlight,
3
- transformerNotationDiff,
4
- transformerNotationErrorLevel,
5
- transformerNotationHighlight,
6
- transformerNotationWordHighlight,
7
- } from "@shikijs/transformers";
8
- import type {Element, ElementContent} from "hast";
9
- import type {ShikiTransformer} from "shiki";
10
-
11
- /**
12
- * 펜스 메타에 `line-numbers` 가 있으면 pre 에 줄 번호 클래스 · 번호는 CSS counter
13
- */
14
- const lineNumbers: ShikiTransformer = {
15
- name: "for-humanity:line-numbers",
16
- pre(node) {
17
- const meta = this.options.meta?.__raw;
18
-
19
- if (typeof meta === "string" && /\bline-numbers\b/.test(meta)) {
20
- this.addClassToHast(node, "wg_prose__lineNumbers");
21
- }
22
- },
23
- };
24
-
25
- /**
26
- * 펜스 메타 `bad`·`good` → pre 에 블록 색 클래스 · 바탕색은 token.css 가 바꿈
27
- */
28
- const tone: ShikiTransformer = {
29
- name: "for-humanity:code-tone",
30
- pre(node) {
31
- const meta = this.options.meta?.__raw;
32
-
33
- if (typeof meta !== "string") {
34
- return;
35
- }
36
-
37
- for (const word of ["bad", "good"]) {
38
- if (new RegExp(`\\b${word}\\b`).test(meta)) {
39
- this.addClassToHast(node, `wg_prose__code--${word}`);
40
- }
41
- }
42
- },
43
- };
44
-
45
- /**
46
- * 펜스 메타 `[파일 이름]` → figure 로 감싸고 제목 띠에 파일 이름과 언어 · 블록 색 클래스는 figure 에도 둠
47
- */
48
- const fileTitle: ShikiTransformer = {
49
- name: "for-humanity:code-title",
50
- root(root) {
51
- const meta = this.options.meta?.__raw;
52
- const title = typeof meta === "string" ? /\[([^\]]+)\]/.exec(meta)?.[1] : undefined;
53
-
54
- if (title === undefined) {
55
- return;
56
- }
57
-
58
- const tones = typeof meta === "string" ? ["bad", "good"].filter((word) => new RegExp(`\\b${word}\\b`).test(meta)) : [];
59
- const caption: Element = {
60
- type: "element",
61
- tagName: "figcaption",
62
- properties: {className: ["wg_prose__codeFileTitle"]},
63
- children: [
64
- {type: "text", value: title},
65
- {type: "element", tagName: "span", properties: {className: ["wg_prose__codeFileLang"]}, children: [{type: "text", value: this.options.lang}]},
66
- ],
67
- };
68
- const figure: Element = {
69
- type: "element",
70
- tagName: "figure",
71
- properties: {className: ["wg_prose__codeFile", ...tones.map((tone) => `wg_prose__codeFile--${tone}`)]},
72
- children: [caption, ...root.children.filter((child): child is ElementContent => child.type !== "doctype")],
73
- };
74
-
75
- root.children = [figure];
76
- },
77
- };
78
-
79
- /**
80
- * 코드 줄 표기 · `// [!code highlight|error|warning|++|--]`, `// [!code word:x]`, 메타 `{2,4-6}`, `line-numbers`, 블록 색 `bad`·`good`, 제목 `[파일 이름]`
81
- */
82
- export const shikiTransformers: ShikiTransformer[] = [
83
- transformerNotationHighlight({matchAlgorithm: "v3"}),
84
- transformerNotationErrorLevel({matchAlgorithm: "v3"}),
85
- transformerNotationDiff({matchAlgorithm: "v3"}),
86
- transformerNotationWordHighlight({matchAlgorithm: "v3"}),
87
- transformerMetaHighlight(),
88
- lineNumbers,
89
- tone,
90
- fileTitle,
91
- ];
@@ -1,64 +0,0 @@
1
- import rehypeShiki from "@shikijs/rehype";
2
- import rehypeRaw from "rehype-raw";
3
- import rehypeStringify from "rehype-stringify";
4
- import remarkDirective from "remark-directive";
5
- import remarkFrontmatter from "remark-frontmatter";
6
- import remarkGfm from "remark-gfm";
7
- import remarkParse from "remark-parse";
8
- import remarkRehype from "remark-rehype";
9
- import remarkSmartypants from "remark-smartypants";
10
- import {createCssVariablesTheme} from "shiki";
11
- import {unified} from "unified";
12
- import {rehypeHead} from "@/component/widget/prose/_function/rehype-head";
13
- import {rehypeSections} from "@/component/widget/prose/_function/rehype-sections/rehype-sections";
14
- import {rehypeTables} from "@/component/widget/prose/_function/rehype-tables";
15
- import {remarkBlocks} from "@/component/widget/prose/_function/remark-blocks";
16
- import {remarkFlow} from "@/component/widget/prose/_function/remark-flow";
17
- import {remarkLinks} from "@/component/widget/prose/_function/remark-links";
18
- import {remarkParts} from "@/component/widget/prose/_function/remark-parts";
19
- import {remarkStatus} from "@/component/widget/prose/_function/remark-status";
20
- import {remarkSwatch} from "@/component/widget/prose/_function/remark-swatch";
21
- import {remarkUnknownDirectives} from "@/component/widget/prose/_function/remark-unknown-directives";
22
- import {rehypeAssets} from "@/entry/cli/_function/create-processor/_rehype-assets";
23
- import {rehypeHeadingIds} from "@/entry/cli/_function/create-processor/_rehype-heading-ids";
24
- import {remarkReport} from "@/entry/cli/_function/create-processor/_remark-report";
25
- import {remarkSnippets} from "@/entry/cli/_function/create-processor/_remark-snippets";
26
- import {shikiTransformers} from "@/entry/cli/_function/create-processor/_shiki-transformers";
27
- import type {SiteConfig} from "@/type/site-config";
28
-
29
- /**
30
- * Markdown 처리 순서: remark(스니펫·부품·흐름도) → 코드 강조(줄 표기·블록 색 transformer) → 머리 → 제목 id → 가름 수집 → 표
31
- * 플러그인 HTML 조각은 마지막 rehype-raw에서 해석
32
- * Shiki inline style: token.css의 --app-code-* 참조
33
- */
34
- export const createProcessor = (options: {site: SiteConfig; root: string; files?: Map<string, string>; snippets?: Set<string>}) => {
35
- return unified()
36
- .use(remarkParse)
37
- .use(remarkFrontmatter, ["yaml"])
38
- .use(remarkGfm)
39
- .use(remarkSnippets, {root: options.root, snippets: options.snippets ?? new Set<string>()})
40
- .use(remarkSmartypants)
41
- .use(remarkDirective)
42
- .use(remarkBlocks)
43
- .use(remarkParts)
44
- .use(remarkFlow)
45
- .use(remarkStatus, {status: options.site.status})
46
- .use(remarkSwatch)
47
- .use(remarkLinks, {root: options.root})
48
- .use(remarkUnknownDirectives)
49
- .use(remarkReport, {root: options.root})
50
- .use(remarkRehype, {allowDangerousHtml: true})
51
- .use(rehypeShiki, {theme: createCssVariablesTheme({name: "for-humanity", variablePrefix: "--app-code-"}), transformers: shikiTransformers})
52
- .use(rehypeHead)
53
- .use(rehypeHeadingIds)
54
- .use(rehypeSections)
55
- .use(rehypeTables)
56
- .use(rehypeRaw)
57
- .use(rehypeAssets, {root: options.root, files: options.files ?? new Map<string, string>()})
58
- .use(rehypeStringify, {allowDangerousHtml: true});
59
- };
60
-
61
- /**
62
- * 플러그인 조립을 마친 Markdown 처리기
63
- */
64
- export type Processor = ReturnType<typeof createProcessor>;
@@ -1,128 +0,0 @@
1
- import {readdir, readFile} from "node:fs/promises";
2
- import {join, posix, sep} from "node:path";
3
- import {VFile} from "vfile";
4
- import {parse as parseYaml} from "yaml";
5
- import {asset_dir, asset_favicon_path} from "@/constant/asset";
6
- import {
7
- copy_error_doc_id,
8
- copy_error_doc_id_clash,
9
- copy_error_doc_parent_cycle,
10
- copy_error_doc_parent_group,
11
- copy_error_doc_parent_missing,
12
- copy_error_frontmatter,
13
- } from "@/constant/copy";
14
- import type {Processor} from "@/entry/cli/_function/create-processor/create-processor";
15
- import type {Doc} from "@/type/doc";
16
- import {docDataSchema} from "@/type/doc-data";
17
- import type {DocFileData} from "@/type/doc-file-data";
18
-
19
- /**
20
- * 문서 폴더를 읽고 명령별 출력 경로와 함께 검증하는 입력
21
- */
22
- export interface ReadDocsParams {
23
- /**
24
- * CLI에 넘긴 문서 폴더의 절대 경로
25
- */
26
- root: string;
27
- /**
28
- * 설정·자원 목록을 연결한 Markdown 처리기
29
- */
30
- processor: Processor;
31
- /**
32
- * 명령이 생성할 파일과 충돌하는 첫 경로 조각 · 생략 시 추가 예약 없음
33
- */
34
- reserved?: readonly string[];
35
- }
36
-
37
- /**
38
- * Markdown 수집과 렌더링 · 루트 README는 홈, AGENTS·CLAUDE는 작성 지침으로 분리
39
- * 문서 목록에서 루트 안내 파일·node_modules·dist 제외
40
- * 머리말·문서 식별자 중복·플러그인 오류 시 실패
41
- */
42
- export const readDocs = async (options: ReadDocsParams): Promise<Doc[]> => {
43
- const paths = (await readdir(options.root, {recursive: true})).filter(
44
- (path) => /\.mdx?$/i.test(path) && !/^(readme|agents|claude)\.md$/i.test(path) && !path.split(sep).includes("node_modules") && !path.startsWith(`dist${sep}`),
45
- );
46
- const docs = await Promise.all(
47
- paths.map(async (path): Promise<Doc> => {
48
- const id = path
49
- .replace(/\.mdx?$/i, "")
50
- .split(sep)
51
- .join("/")
52
- .toLowerCase();
53
- const segments = id.split("/");
54
-
55
- // Hono·SSG·정적 제공의 공통 URL 계약 · 예약 문자에 의한 페이지 누락 방지
56
- if (
57
- segments.some((segment) => segment === "" || segment === "." || segment === ".." || segment === "index.html") ||
58
- /[#?%*:\\]/.test(id) ||
59
- /[\p{Cc}]/u.test(id) ||
60
- options.reserved?.includes(segments[0]) === true ||
61
- segments[0] === asset_dir ||
62
- segments[0] === asset_favicon_path.slice(1)
63
- ) {
64
- throw new Error(`${copy_error_doc_id}: ${path}`);
65
- }
66
-
67
- const source = await readFile(join(options.root, path), "utf8");
68
- // remark가 제외하는 UTF-8 BOM을 원문에서도 제거 · 지시문의 offset 기준 일치
69
- const file = new VFile({value: source.replace(/^\uFEFF/, ""), path: join(options.root, path)});
70
- const tree = options.processor.parse(file);
71
- const frontmatter = tree.children[0];
72
- const parsed = docDataSchema.safeParse(frontmatter?.type === "yaml" ? parseYaml(frontmatter.value) : {});
73
-
74
- if (!parsed.success) {
75
- throw new Error(`${copy_error_frontmatter}: ${path}: ${parsed.error.issues.map((issue) => `${issue.path.join(".")} ${issue.message}`).join(", ")}`);
76
- }
77
-
78
- // 상대 부모 id는 파일 위치 기준으로 한 번 해소 · 문서 루트를 바꿔도 같은 관계 유지
79
- const data = {
80
- ...parsed.data,
81
- parent: parsed.data.parent !== undefined && /^\.\.?\//.test(parsed.data.parent) ? posix.join(posix.dirname(id), parsed.data.parent) : parsed.data.parent,
82
- };
83
-
84
- // 플러그인의 제목·절 정보 공유
85
- const fh: DocFileData = {frontmatter: data, headings: [], sections: []};
86
- file.data.fh = fh;
87
-
88
- // YAML 노드만 제외 · 본문 위치와 오류의 원본 줄·열·offset 유지
89
- tree.children = tree.children.filter((node) => node.type !== "yaml");
90
- const rendered = await options.processor.run(tree, file);
91
-
92
- return {
93
- id,
94
- data,
95
- ancestors: [],
96
- html: options.processor.stringify(rendered, file),
97
- outline: {headings: fh.headings, sections: fh.sections},
98
- };
99
- }),
100
- );
101
- const duplicateIds = [...Map.groupBy(docs, (doc) => doc.id)].filter((entry) => entry[1].length > 1);
102
-
103
- if (duplicateIds.length > 0) {
104
- throw new Error(`${copy_error_doc_id_clash}: ${duplicateIds.map((entry) => entry[0]).join(", ")}`);
105
- }
106
-
107
- // 모든 파일을 수집한 뒤 부모 참조 검증 · 파일 읽기 순서와 무관한 문서 경로
108
- const docsById = new Map(docs.map((doc) => [doc.id, doc]));
109
- return docs.map((doc) => {
110
- const ancestors = new Set([doc.id]);
111
- for (let parentId = doc.data.parent; parentId !== undefined; ) {
112
- const parent = docsById.get(parentId);
113
- if (parent === undefined) {
114
- throw new Error(`${copy_error_doc_parent_missing}: ${doc.id} → ${parentId}`);
115
- }
116
- if (ancestors.has(parent.id)) {
117
- throw new Error(`${copy_error_doc_parent_cycle}: ${[...ancestors, parent.id].join(" → ")}`);
118
- }
119
- if (JSON.stringify(parent.data.group) !== JSON.stringify(doc.data.group)) {
120
- throw new Error(`${copy_error_doc_parent_group}: ${doc.id} → ${parent.id}`);
121
- }
122
- ancestors.add(parent.id);
123
- parentId = parent.data.parent;
124
- }
125
-
126
- return {...doc, ancestors: [...ancestors].slice(1).toReversed()};
127
- });
128
- };
@@ -1,32 +0,0 @@
1
- import {readdir, readFile} from "node:fs/promises";
2
- import {join} from "node:path";
3
- import {VFile} from "vfile";
4
- import {copy_error_home_clash} from "@/constant/copy";
5
- import type {Processor} from "@/entry/cli/_function/create-processor/create-processor";
6
- import type {DocContent} from "@/type/doc-content";
7
- import type {DocFileData} from "@/type/doc-file-data";
8
-
9
- /**
10
- * 문서 폴더의 README.md → 홈 · 대소문자 무시 · frontmatter 불필요
11
- * 파일이 없으면 생성 안내 표시, 이름만 다른 README 중복은 오류
12
- */
13
- export const readHome = async (options: {root: string; processor: Processor}): Promise<DocContent | undefined> => {
14
- const paths = (await readdir(options.root)).filter((path) => path.toLowerCase() === "readme.md");
15
-
16
- if (paths.length > 1) {
17
- throw new Error(`${copy_error_home_clash}: ${paths.join(", ")}`);
18
- }
19
-
20
- if (paths.length === 0) {
21
- return undefined;
22
- }
23
-
24
- const path = join(options.root, paths[0]);
25
- const fh: DocFileData = {headings: [], sections: []};
26
- // 파서와 지시문 원문 복원의 offset 기준을 맞추기 위해 BOM 제외
27
- const file = new VFile({value: (await readFile(path, "utf8")).replace(/^\uFEFF/, ""), path, data: {fh}});
28
-
29
- await options.processor.process(file);
30
-
31
- return {html: String(file), outline: {headings: fh.headings, sections: fh.sections}};
32
- };
@@ -1,42 +0,0 @@
1
- import {Hono} from "hono";
2
- import {html} from "hono/html";
3
- import {crawling_robots_path, crawling_sitemap_path} from "@/entry/cli/_constant/crawling";
4
- import type {AppOptions} from "@/entry/cli/_function/create-app";
5
- import type {SiteConfig} from "@/type/site-config";
6
-
7
- /**
8
- * URL을 명시한 공개 프로젝트의 크롤러 응답 입력
9
- */
10
- export interface CrawlingParams {
11
- /**
12
- * 설정 검증에서 루트 origin으로 정규화한 공개 HTTP(S) URL
13
- */
14
- url: NonNullable<SiteConfig["url"]>;
15
- /**
16
- * dev 파일 변경 시 교체되는 현재 문서 목록 · 본문은 사이트맵에 포함하지 않음
17
- */
18
- store: AppOptions["store"];
19
- }
20
-
21
- /**
22
- * dev와 정적 빌드에서 같은 크롤러 파일 생성 · 정책·수정 시각은 추정하지 않음
23
- */
24
- export const toCrawlingApp = (params: CrawlingParams) => {
25
- const app = new Hono();
26
-
27
- app.get(crawling_robots_path, (c) => c.text(`User-agent: *\nAllow: /\n\nSitemap: ${params.url}${crawling_sitemap_path}\n`));
28
- app.get(
29
- crawling_sitemap_path,
30
- (_c) =>
31
- new Response(
32
- html`<?xml version="1.0" encoding="UTF-8"?>
33
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
34
- <url><loc>${params.url}/</loc></url>
35
- ${params.store.docs.map((doc) => html`<url><loc>${params.url}/${doc.id.split("/").map(encodeURIComponent).join("/")}/</loc></url>`)}
36
- </urlset>`.toString(),
37
- {headers: {"Content-Type": "application/xml; charset=utf-8"}},
38
- ),
39
- );
40
-
41
- return app;
42
- };