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.
- package/CHANGELOG.md +39 -0
- package/README.md +16 -9
- package/dist/browser.js +14 -16
- package/dist/cli.css +2 -968
- package/dist/cli.js +92 -2318
- package/dist/config.js +1 -0
- package/dist/constant/copy.d.ts +100 -0
- package/dist/constant/site.d.ts +20 -0
- package/dist/constant/status.d.ts +19 -0
- package/dist/copy-C1uzvEnr.js +103 -0
- package/dist/group-path-XAThmzvC.js +10 -0
- package/dist/load-site-B-u5-jv0.js +2343 -0
- package/dist/logger-Dv1i9xSV.js +9 -0
- package/dist/mermaid/THIRD-PARTY-NOTICES.txt +3134 -0
- package/dist/mermaid/chunk--TN-qHgG.js +2 -0
- package/dist/mermaid/chunk-0kM1Lv4D.js +2 -0
- package/dist/mermaid/chunk-3FHei3pH.js +2001 -0
- package/dist/mermaid/chunk-3_2p2GqN.js +2 -0
- package/dist/mermaid/chunk-6HLei9i0.js +84 -0
- package/dist/mermaid/chunk-7Sz3kAwl.js +2214 -0
- package/dist/mermaid/chunk-7Tarqbpa.js +18047 -0
- package/dist/mermaid/chunk-9kkt8lzL.js +10 -0
- package/dist/mermaid/chunk-B--pNBQF.js +884 -0
- package/dist/mermaid/chunk-B4F-pysn.js +4241 -0
- package/dist/mermaid/chunk-B58sEIY_.js +14 -0
- package/dist/mermaid/chunk-B9_UWzSH.js +1517 -0
- package/dist/mermaid/chunk-BB8uZHwY.js +75 -0
- package/dist/mermaid/chunk-BFfk0LEc.js +717 -0
- package/dist/mermaid/chunk-BKri0qr7.js +89 -0
- package/dist/mermaid/chunk-BL404pH5.js +26 -0
- package/dist/mermaid/chunk-BLeiQSWo.js +354 -0
- package/dist/mermaid/chunk-BOmH_UFd.js +2 -0
- package/dist/mermaid/chunk-BSej9Nhy.js +1361 -0
- package/dist/mermaid/chunk-BYzrzvCI.js +12 -0
- package/dist/mermaid/chunk-BariMtWr.js +3020 -0
- package/dist/mermaid/chunk-BcrBoMwH.js +2352 -0
- package/dist/mermaid/chunk-Bok8z-Yo.js +67 -0
- package/dist/mermaid/chunk-BpTHLKQM.js +1970 -0
- package/dist/mermaid/chunk-BqjuxRsC.js +733 -0
- package/dist/mermaid/chunk-BuvPd-s5.js +1073 -0
- package/dist/mermaid/chunk-BvVehae3.js +5069 -0
- package/dist/mermaid/chunk-C-JABYpd.js +3075 -0
- package/dist/mermaid/chunk-C9XlTm4w.js +2 -0
- package/dist/mermaid/chunk-CApNbSU9.js +6 -0
- package/dist/mermaid/chunk-CCM2AH-C.js +9 -0
- package/dist/mermaid/chunk-CD1E0F_N.js +2 -0
- package/dist/mermaid/chunk-CDWvKG7D.js +2 -0
- package/dist/mermaid/chunk-CEVPpC1F.js +34 -0
- package/dist/mermaid/chunk-CLDr12lf.js +47504 -0
- package/dist/mermaid/chunk-CLVYqOBI.js +61 -0
- package/dist/mermaid/chunk-CLeOkFb5.js +15 -0
- package/dist/mermaid/chunk-CLgRAtOz.js +37 -0
- package/dist/mermaid/chunk-CMqFNUBl.js +134 -0
- package/dist/mermaid/chunk-CTPaBJXB.js +2 -0
- package/dist/mermaid/chunk-CVi6IGBL.js +2 -0
- package/dist/mermaid/chunk-CWV_EN-4.js +4745 -0
- package/dist/mermaid/chunk-CZ0dAs3g.js +2 -0
- package/dist/mermaid/chunk-C_YGy4Fn.js +236 -0
- package/dist/mermaid/chunk-CcVH22Yt.js +100 -0
- package/dist/mermaid/chunk-CgG6CD9_.js +7175 -0
- package/dist/mermaid/chunk-CgOP6IDB.js +3397 -0
- package/dist/mermaid/chunk-Cgg2UTSf.js +13 -0
- package/dist/mermaid/chunk-ChJfJJ1A.js +214 -0
- package/dist/mermaid/chunk-Ck5XRA46.js +447 -0
- package/dist/mermaid/chunk-Ckuw16cl.js +203 -0
- package/dist/mermaid/chunk-CpAbRCbF.js +1880 -0
- package/dist/mermaid/chunk-CuYekbSv.js +2 -0
- package/dist/mermaid/chunk-CwS4wZ5a.js +2 -0
- package/dist/mermaid/chunk-D-bcrBO3.js +82 -0
- package/dist/mermaid/chunk-D3IEAkCW.js +76 -0
- package/dist/mermaid/chunk-D8XfBJ_K.js +645 -0
- package/dist/mermaid/chunk-D9Ddd9y4.js +2228 -0
- package/dist/mermaid/chunk-DBh1mIbK.js +12 -0
- package/dist/mermaid/chunk-DD6Df6Ic.js +5 -0
- package/dist/mermaid/chunk-DGVDaXjx.js +8 -0
- package/dist/mermaid/chunk-DK9KqIXj.js +632 -0
- package/dist/mermaid/chunk-DORnIAsy.js +4487 -0
- package/dist/mermaid/chunk-DU69_VcF.js +363 -0
- package/dist/mermaid/chunk-DVNvSFoz.js +2144 -0
- package/dist/mermaid/chunk-DZe-4kde.js +20 -0
- package/dist/mermaid/chunk-D_a06Rz4.js +287 -0
- package/dist/mermaid/chunk-DfyFJJi2.js +972 -0
- package/dist/mermaid/chunk-DhKYPp1Z.js +2 -0
- package/dist/mermaid/chunk-DjFqgNTG.js +94 -0
- package/dist/mermaid/chunk-DmbDDrr-.js +65 -0
- package/dist/mermaid/chunk-DpuxlyL_.js +696 -0
- package/dist/mermaid/chunk-DqGhppWe.js +2 -0
- package/dist/mermaid/chunk-DqPZxHXa.js +5 -0
- package/dist/mermaid/chunk-Dt_1Jyif.js +293 -0
- package/dist/mermaid/chunk-DyQBE_NW.js +1935 -0
- package/dist/mermaid/chunk-JckyC4mW.js +1009 -0
- package/dist/mermaid/chunk-K7Myuj2-.js +1334 -0
- package/dist/mermaid/chunk-KuPYb48V.js +275 -0
- package/dist/mermaid/chunk-MFVRUoO0.js +4020 -0
- package/dist/mermaid/chunk-MbInCS_6.js +28 -0
- package/dist/mermaid/chunk-MexOLyKV.js +355 -0
- package/dist/mermaid/chunk-NnbDtJzg.js +21856 -0
- package/dist/mermaid/chunk-SGpOYZbI.js +2139 -0
- package/dist/mermaid/chunk-TbxLDlcU.js +198 -0
- package/dist/mermaid/chunk-TftHqGLg.js +490 -0
- package/dist/mermaid/chunk-UjQ8vA_Z.js +4138 -0
- package/dist/mermaid/chunk-YgmVTKqc.js +16 -0
- package/dist/mermaid/chunk-_a9EZE1v.js +1798 -0
- package/dist/mermaid/chunk-dTb8jqeP.js +2 -0
- package/dist/mermaid/chunk-fFdMfF8S.js +2240 -0
- package/dist/mermaid/chunk-feYbMzBj.js +1267 -0
- package/dist/mermaid/chunk-g3Pn8DkW.js +46 -0
- package/dist/mermaid/chunk-hM77LvCb.js +19832 -0
- package/dist/mermaid/chunk-hUDb-eAW.js +527 -0
- package/dist/mermaid/chunk-tpABtZ-3.js +4252 -0
- package/dist/mermaid/chunk-xIbz3oFK.js +59 -0
- package/dist/mermaid/chunk-yBz-vst0.js +976 -0
- package/dist/mermaid/mermaid-BhtR3wyB.js +1145 -0
- package/dist/run-site-command-cqCjpIlc.js +129 -0
- package/dist/site-config-bllORGpm.js +90 -0
- package/dist/site.js +1 -0
- package/dist/start-dev-server-x7Ja5uwv.js +184 -0
- package/dist/to-change-queue-DbEnZGNK.js +34 -0
- package/dist/to-error-message-pF6_7wGK.js +16 -0
- package/dist/type/group-path.d.ts +7 -0
- package/dist/type/site-config.d.ts +57 -0
- package/package.json +34 -17
- package/src/component/widget/cursor-face/_constant/cursor-face.ts +0 -14
- package/src/component/widget/cursor-face/wg-cursor-face.css +0 -15
- package/src/component/widget/cursor-face/wg-cursor-face.tsx +0 -153
- package/src/component/widget/navigation/_constant/locale.ts +0 -9
- package/src/component/widget/navigation/_constant/navigation-data-schema.ts +0 -33
- package/src/component/widget/navigation/_constant/navigation.ts +0 -9
- package/src/component/widget/navigation/_constant/reading-line.ts +0 -4
- package/src/component/widget/navigation/_function/restore-navigation-scroll.ts +0 -59
- package/src/component/widget/navigation/_function/to-doc-groups/_to-doc-branches.ts +0 -15
- package/src/component/widget/navigation/_function/to-doc-groups/_to-doc-group-branches.ts +0 -70
- package/src/component/widget/navigation/_function/to-doc-groups/to-doc-groups.ts +0 -15
- package/src/component/widget/navigation/_function/to-navigation-data/_to-navigation-document.ts +0 -13
- package/src/component/widget/navigation/_function/to-navigation-data/_to-navigation-group.ts +0 -13
- package/src/component/widget/navigation/_function/to-navigation-data/to-navigation-data.ts +0 -18
- package/src/component/widget/navigation/_function/to-navigation-json.ts +0 -8
- package/src/component/widget/navigation/_function/to-toc-groups.ts +0 -38
- package/src/component/widget/navigation/_type/doc-branch.ts +0 -11
- package/src/component/widget/navigation/_type/doc-group.ts +0 -24
- package/src/component/widget/navigation/_type/navigation-data.ts +0 -75
- package/src/component/widget/navigation/_type/navigation-focus-snapshot.ts +0 -17
- package/src/component/widget/navigation/_type/toc-group.ts +0 -39
- package/src/component/widget/navigation/_wg-navigation-nav-list.css +0 -48
- package/src/component/widget/navigation/_wg-navigation-nav-list.tsx +0 -61
- package/src/component/widget/navigation/_wg-navigation-nav.css +0 -21
- package/src/component/widget/navigation/_wg-navigation-nav.tsx +0 -33
- package/src/component/widget/navigation/_wg-navigation-scroll.tsx +0 -117
- package/src/component/widget/navigation/_wg-navigation-toc.css +0 -91
- package/src/component/widget/navigation/_wg-navigation-toc.tsx +0 -114
- package/src/component/widget/navigation/wg-navigation.css +0 -282
- package/src/component/widget/navigation/wg-navigation.tsx +0 -420
- package/src/component/widget/prose/_constant/heading-highlight.ts +0 -4
- package/src/component/widget/prose/_constant/section.ts +0 -7
- package/src/component/widget/prose/_constant/status.ts +0 -9
- package/src/component/widget/prose/_function/has-block-content.ts +0 -30
- package/src/component/widget/prose/_function/rehype-head.ts +0 -31
- package/src/component/widget/prose/_function/rehype-sections/_to-section-headings.ts +0 -20
- package/src/component/widget/prose/_function/rehype-sections/rehype-sections.ts +0 -21
- package/src/component/widget/prose/_function/rehype-tables.ts +0 -22
- package/src/component/widget/prose/_function/remark-blocks.ts +0 -112
- package/src/component/widget/prose/_function/remark-flow.ts +0 -26
- package/src/component/widget/prose/_function/remark-links.ts +0 -33
- package/src/component/widget/prose/_function/remark-parts.ts +0 -26
- package/src/component/widget/prose/_function/remark-status.ts +0 -59
- package/src/component/widget/prose/_function/remark-swatch.ts +0 -16
- package/src/component/widget/prose/_function/remark-unknown-directives.ts +0 -25
- package/src/component/widget/prose/wg-prose.css +0 -484
- package/src/component/widget/prose/wg-prose.tsx +0 -43
- package/src/component/widget/shell/wg-shell.css +0 -63
- package/src/component/widget/shell/wg-shell.tsx +0 -88
- package/src/component/widget/shell-controls/wg-shell-controls.css +0 -4
- package/src/component/widget/shell-controls/wg-shell-controls.tsx +0 -170
- package/src/constant/asset.ts +0 -63
- package/src/constant/copy.ts +0 -133
- package/src/constant/doc.ts +0 -13
- package/src/constant/font.ts +0 -34
- package/src/constant/site.ts +0 -24
- package/src/constant/status.ts +0 -15
- package/src/entry/browser/browser.tsx +0 -22
- package/src/entry/cli/_constant/cli.ts +0 -19
- package/src/entry/cli/_constant/crawling.ts +0 -14
- package/src/entry/cli/_constant/snippet.ts +0 -21
- package/src/entry/cli/_function/create-app.tsx +0 -57
- package/src/entry/cli/_function/create-dev-app.ts +0 -87
- package/src/entry/cli/_function/create-processor/_rehype-assets.ts +0 -70
- package/src/entry/cli/_function/create-processor/_rehype-heading-ids.ts +0 -34
- package/src/entry/cli/_function/create-processor/_remark-report.ts +0 -15
- package/src/entry/cli/_function/create-processor/_remark-snippets.ts +0 -117
- package/src/entry/cli/_function/create-processor/_shiki-transformers.ts +0 -91
- package/src/entry/cli/_function/create-processor/create-processor.ts +0 -64
- package/src/entry/cli/_function/read-docs.ts +0 -128
- package/src/entry/cli/_function/read-home.ts +0 -32
- package/src/entry/cli/_function/to-crawling-app.ts +0 -42
- package/src/entry/cli/cli.test.ts +0 -804
- package/src/entry/cli/cli.ts +0 -206
- package/src/env.d.ts +0 -6
- package/src/page/doc/pg-doc.tsx +0 -35
- package/src/page/home/pg-home.tsx +0 -40
- package/src/store/navigation/create-navigation-stores.test.ts +0 -74
- package/src/store/navigation/create-navigation-stores.ts +0 -59
- package/src/store/navigation/navigation-state.ts +0 -36
- package/src/store/navigation/navigation-storage.ts +0 -9
- package/src/store/navigation/to-navigation-scroll-state.ts +0 -42
- package/src/style/base.css +0 -28
- package/src/style/token.css +0 -188
- package/src/type/doc-content.ts +0 -15
- package/src/type/doc-data.ts +0 -38
- package/src/type/doc-file-data.ts +0 -21
- package/src/type/doc-heading.ts +0 -17
- package/src/type/doc-outline.ts +0 -16
- package/src/type/doc-section.ts +0 -9
- package/src/type/doc.ts +0 -20
- package/src/type/group-path.ts +0 -7
- package/src/type/site-assets.ts +0 -25
- package/src/type/site-config.ts +0 -73
- package/src/type/vfile-data.d.ts +0 -10
- package/src/util/async/to-change-queue.test.ts +0 -58
- package/src/util/async/to-change-queue.ts +0 -43
- package/src/util/dom/find-hash-target.ts +0 -18
- package/src/util/dom/reveal-hash-target.ts +0 -15
- package/src/util/error/to-error-message.ts +0 -10
- package/src/util/file/to-asset-path.ts +0 -26
- package/src/util/font/to-font-css.test.ts +0 -41
- package/src/util/font/to-font-css.ts +0 -28
- 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
|
-
};
|