for-humanity 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +41 -66
- package/dist/cli.js +6 -6
- package/dist/config.js +1 -1
- package/dist/constant/site.d.ts +8 -0
- package/dist/{load-site-B-u5-jv0.js → load-site-CBJGMNfH.js} +185 -13
- package/dist/{run-site-command-cqCjpIlc.js → run-site-command-DPG5RNIN.js} +39 -16
- package/dist/{site-config-bllORGpm.js → site-config-FmStUQ2W.js} +44 -0
- package/dist/site.js +1 -1
- package/dist/type/content-config.d.ts +9 -0
- package/dist/type/site-config.d.ts +9 -0
- package/package.json +4 -3
- package/template/content/README.md +5 -0
- package/template/content/asset/.gitkeep +0 -0
- package/template/content/private/.gitkeep +0 -0
- package/template/content/public/getting-started.md +26 -0
- package/template/for-humanity.config.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.14.0 — 2026-10-19
|
|
6
|
+
|
|
7
|
+
**Breaking change:** Configuration now belongs in the project root. CLI paths identify a project, not a Markdown folder. Move published documents into `content/public`, local-only documents into `content/private`, and the site home into `content/README.md`; use `content.root`, `content.public` and `content.private` to customize these paths. Existing flat document folders are not automatically published. See the [migration guide](https://for-humanity.fyi/migration/). Node.js 22.12.0 or later is still required.
|
|
8
|
+
|
|
9
|
+
- Focus public documentation on installation, configuration, authoring and deployment. Keep internal plans, release operations, development history and fictional project studies in local-only content.
|
|
10
|
+
- Show both document collections in `dev`; build and preview only the public site in `content/dist`.
|
|
11
|
+
- Preserve public document URLs and reserve `/private/` for local internal pages. Exclude internal pages, navigation data, sitemap entries and their assets from public builds.
|
|
12
|
+
- Reject public references to internal Markdown, images and snippets, including raw HTML and symlink targets.
|
|
13
|
+
- Move this repository's documentation to `content` and its configuration to the project root.
|
|
14
|
+
- Include a reusable `template/` starter with public/private folders and document migration from flat Markdown projects.
|
|
15
|
+
- Reject content-root symlinks into build output, Git metadata and installed dependencies.
|
|
16
|
+
|
|
5
17
|
## 0.13.1 — 2026-10-16
|
|
6
18
|
|
|
7
19
|
**Upgrade:** CLI commands, configuration and Node.js requirements remain unchanged. Navigation scroll positions saved by older versions may reset once after upgrading.
|
package/README.md
CHANGED
|
@@ -13,122 +13,97 @@ Write Markdown. Keep the context. Share a static site.
|
|
|
13
13
|
[](https://github.com/minu-ha/for-humanity/blob/main/LICENSE)
|
|
14
14
|
[](https://www.npmjs.com/package/for-humanity)
|
|
15
15
|

|
|
16
|
-

|
|
17
16
|

|
|
18
17
|
|
|
19
|
-
[Quick start](#quick-start) · [Guide](https://github.com/minu-ha/for-humanity/blob/main/
|
|
18
|
+
[Quick start](#quick-start) · [Guide](https://github.com/minu-ha/for-humanity/blob/main/content/public/writing.md) · [Reference](https://github.com/minu-ha/for-humanity/blob/main/content/public/api.md) · [Deployment](https://github.com/minu-ha/for-humanity/blob/main/content/public/deployment.md) · [Korean](https://github.com/minu-ha/for-humanity/blob/main/language/README.ko.md)
|
|
20
19
|
|
|
21
20
|
</div>
|
|
22
21
|
|
|
23
|
-
##
|
|
22
|
+
## Write, organize, publish
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- **Share the result.** Static HTML, self-hosted fonts and a small browser script. Browser interactions stay separate from Markdown rendering.
|
|
29
|
-
- **Read your way.** System-matched light and dark themes, keyboard navigation and a layout for narrow screens.
|
|
24
|
+
Write Markdown with code examples, tables, images and Mermaid diagrams. Organize pages into groups and nested navigation. Use notes for context and collapsible details for longer explanations.
|
|
25
|
+
|
|
26
|
+
The result is a static site with bundled fonts, light and dark themes, keyboard navigation and a layout that adapts to narrow screens.
|
|
30
27
|
|
|
31
28
|
## Quick start
|
|
32
29
|
|
|
33
|
-
Node.js 22.12.0 or later and pnpm.
|
|
30
|
+
You need Node.js 22.12.0 or later and pnpm. In a project with a `package.json`, install the package:
|
|
34
31
|
|
|
35
32
|
```sh
|
|
36
|
-
pnpm add -D --save-exact for-humanity@0.
|
|
37
|
-
mkdir docs
|
|
33
|
+
pnpm add -D --save-exact for-humanity@0.14.0
|
|
38
34
|
```
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
Copy `for-humanity.config.ts` and the `content` folder from `node_modules/for-humanity/template` into your project. If you already have documentation, follow [Migration](https://github.com/minu-ha/for-humanity/blob/main/content/public/migration.md) before moving files.
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
pnpm exec for-humanity dev docs
|
|
44
|
-
```
|
|
38
|
+
Edit `content/README.md` for your home page:
|
|
45
39
|
|
|
46
|
-
|
|
40
|
+
```markdown
|
|
41
|
+
# Project notes
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
pnpm exec for-humanity build docs # Generate docs/dist
|
|
50
|
-
pnpm exec for-humanity preview docs
|
|
43
|
+
Welcome to the project documentation.
|
|
51
44
|
```
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Version `0.13.1` supports `dev`, `build` and `preview`. `init` is planned; see the [Roadmap](https://github.com/minu-ha/for-humanity/blob/main/templates/document/roadmap.md) and [Changelog](https://github.com/minu-ha/for-humanity/blob/main/CHANGELOG.md).
|
|
56
|
-
Cloudflare Pages can host the generated site. See [Deployment](https://github.com/minu-ha/for-humanity/blob/main/templates/document/deployment.md) for the build settings.
|
|
57
|
-
Set an optional public root `url` in the site configuration to generate `robots.txt` and `sitemap.xml`; projects without a URL emit neither file. See [Crawling](https://github.com/minu-ha/for-humanity/blob/main/templates/document/crawling.md).
|
|
58
|
-
|
|
59
|
-
### Develop the kit
|
|
60
|
-
|
|
61
|
-
Version 0.13.0 requires Node.js 22.12.0 or later. Repository development uses one Vite server with Hono, source TSX reloads and CSS HMR.
|
|
62
|
-
|
|
63
|
-
To work on the CLI or read this library's guides and examples locally, run the project from source:
|
|
46
|
+
Start the local server:
|
|
64
47
|
|
|
65
48
|
```sh
|
|
66
|
-
|
|
67
|
-
cd for-humanity
|
|
68
|
-
pnpm install
|
|
69
|
-
pnpm dev
|
|
49
|
+
pnpm exec for-humanity dev
|
|
70
50
|
```
|
|
71
51
|
|
|
72
|
-
Open [localhost:4321](http://localhost:4321) to
|
|
52
|
+
Open [localhost:4321](http://localhost:4321). Edit the Markdown file and save to update the page. Follow [Commands](https://github.com/minu-ha/for-humanity/blob/main/content/public/commands.md) to add your first document.
|
|
53
|
+
|
|
54
|
+
Build and inspect the site before deployment:
|
|
73
55
|
|
|
74
56
|
```sh
|
|
75
|
-
pnpm
|
|
76
|
-
pnpm
|
|
77
|
-
pnpm build:pages # Add Cloudflare Pages headers and a 404 page
|
|
57
|
+
pnpm exec for-humanity build
|
|
58
|
+
pnpm exec for-humanity preview
|
|
78
59
|
```
|
|
79
60
|
|
|
80
|
-
|
|
81
|
-
|
|
61
|
+
Deploy the generated `content/dist` directory and add it to `.gitignore`. See [Deployment](https://github.com/minu-ha/for-humanity/blob/main/content/public/deployment.md) for hosting settings.
|
|
62
|
+
|
|
63
|
+
Keep published documents in `content/public` and local notes in `content/private`. The local server shows both; builds include only public documents and the home page. Folder names are configurable.
|
|
82
64
|
|
|
83
65
|
## Make it yours
|
|
84
66
|
|
|
85
|
-
|
|
86
|
-
The folder's `README.md` becomes the home page at `/`, with no frontmatter required. Click the site name to return home.
|
|
87
|
-
This repository generates [the documentation home](https://github.com/minu-ha/for-humanity/blob/main/templates/document/README.md) from its root README.md and [the site changelog](https://github.com/minu-ha/for-humanity/blob/main/templates/document/changelog.md) from CHANGELOG.md. Edit those source files, then run `pnpm sync:docs`; `pnpm dev` and `pnpm build` also sync them on startup.
|
|
88
|
-
Set your own name in `for-humanity.config.ts`, using the exported `SiteConfig` type for editor completion and static checking:
|
|
67
|
+
Set the site name and the order of navigation groups:
|
|
89
68
|
|
|
90
69
|
```ts
|
|
91
70
|
import type {SiteConfig} from "for-humanity";
|
|
92
71
|
|
|
93
72
|
export default {
|
|
94
73
|
title: "Project notes",
|
|
95
|
-
description: "
|
|
96
|
-
navigation: ["Getting started", "Guide", "Reference"
|
|
74
|
+
description: "A guide to using the project.",
|
|
75
|
+
navigation: ["Getting started", "Guide", "Reference"],
|
|
97
76
|
} satisfies SiteConfig;
|
|
98
77
|
```
|
|
99
78
|
|
|
100
|
-
|
|
101
|
-
Use one configuration file per document folder. The CLI validates values at startup; run `tsc --noEmit` in your project for static type checking.
|
|
79
|
+
Save this as `for-humanity.config.ts` in the project root, alongside `package.json`. Existing `.mjs` configuration is also supported; keep only one configuration file.
|
|
102
80
|
|
|
103
|
-
See [
|
|
104
|
-
Each document other than the root README chooses a `group` and optional `order`. Pages stay independent; groups do not need parent documents or nested folders. Document names may share the same first letter.
|
|
105
|
-
Use `group: Guide` for one group or `group: [Projects, Example project, Research]` for nested navigation. Group paths do not change document URLs.
|
|
106
|
-
Use `parent: writing` to place a document below another document in the same group. File-relative references such as `parent: ./readme` also work. Parent pages remain clickable and each page keeps its own URL; see [Navigation](https://github.com/minu-ha/for-humanity/blob/main/templates/document/navigation.md).
|
|
81
|
+
Each document declares a title, description and navigation group in frontmatter. See [Writing](https://github.com/minu-ha/for-humanity/blob/main/content/public/writing.md) for a complete example and [Settings](https://github.com/minu-ha/for-humanity/blob/main/content/public/settings.md) for available options.
|
|
107
82
|
|
|
108
|
-
##
|
|
83
|
+
## Keep useful detail
|
|
109
84
|
|
|
110
85
|
```markdown
|
|
111
|
-
:::note[
|
|
112
|
-
|
|
86
|
+
:::note[Before you start]
|
|
87
|
+
Check the requirements before following the steps.
|
|
113
88
|
:::
|
|
114
89
|
|
|
115
|
-
:::details[
|
|
116
|
-
|
|
90
|
+
:::details[More information]
|
|
91
|
+
Add examples or explanations readers can open when needed.
|
|
117
92
|
:::
|
|
118
93
|
```
|
|
119
94
|
|
|
120
|
-
[Parts](https://github.com/minu-ha/for-humanity/blob/main/
|
|
95
|
+
[Parts](https://github.com/minu-ha/for-humanity/blob/main/content/public/parts.md) shows the syntax alongside its rendered output.
|
|
121
96
|
|
|
122
97
|
## Documentation
|
|
123
98
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
99
|
+
| Task | Guide |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| Install and run | [Commands](https://github.com/minu-ha/for-humanity/blob/main/content/public/commands.md) |
|
|
102
|
+
| Write and organize pages | [Writing](https://github.com/minu-ha/for-humanity/blob/main/content/public/writing.md) · [Navigation](https://github.com/minu-ha/for-humanity/blob/main/content/public/navigation.md) |
|
|
103
|
+
| Add rich content | [Parts](https://github.com/minu-ha/for-humanity/blob/main/content/public/parts.md) · [Diagrams](https://github.com/minu-ha/for-humanity/blob/main/content/public/diagrams.md) · [Images](https://github.com/minu-ha/for-humanity/blob/main/content/public/images.md) |
|
|
104
|
+
| Configure the site | [Settings](https://github.com/minu-ha/for-humanity/blob/main/content/public/settings.md) · [Reference](https://github.com/minu-ha/for-humanity/blob/main/content/public/api.md) |
|
|
105
|
+
| Publish | [Deployment](https://github.com/minu-ha/for-humanity/blob/main/content/public/deployment.md) · [Crawling](https://github.com/minu-ha/for-humanity/blob/main/content/public/crawling.md) |
|
|
106
|
+
| Update | [Releases](https://github.com/minu-ha/for-humanity/blob/main/content/public/releases.md) · [Migration](https://github.com/minu-ha/for-humanity/blob/main/content/public/migration.md) |
|
|
132
107
|
|
|
133
108
|
## License
|
|
134
109
|
|
package/dist/cli.js
CHANGED
|
@@ -5,11 +5,11 @@ import { defineCommand, runMain } from "citty";
|
|
|
5
5
|
import { z } from "zod/mini";
|
|
6
6
|
//#region src/entry/cli/_constant/cli.ts
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* 프로젝트 폴더 생략 시 현재 폴더
|
|
9
9
|
*/
|
|
10
10
|
var cli_docs_args = { dir: {
|
|
11
11
|
type: "positional",
|
|
12
|
-
description: "
|
|
12
|
+
description: "프로젝트 폴더",
|
|
13
13
|
default: "."
|
|
14
14
|
} };
|
|
15
15
|
//#endregion
|
|
@@ -50,7 +50,7 @@ var main = defineCommand({
|
|
|
50
50
|
* 도움말 조회와 분리한 사이트 실행
|
|
51
51
|
*/
|
|
52
52
|
run: async (context) => {
|
|
53
|
-
await (await import("./run-site-command-
|
|
53
|
+
await (await import("./run-site-command-DPG5RNIN.js")).runSiteCommand({
|
|
54
54
|
command: "dev",
|
|
55
55
|
args: context.args
|
|
56
56
|
});
|
|
@@ -59,14 +59,14 @@ var main = defineCommand({
|
|
|
59
59
|
build: defineCommand({
|
|
60
60
|
meta: {
|
|
61
61
|
name: "build",
|
|
62
|
-
description: "
|
|
62
|
+
description: "content/dist에 공개 문서 사이트 생성"
|
|
63
63
|
},
|
|
64
64
|
args: cli_docs_args,
|
|
65
65
|
/**
|
|
66
66
|
* 도움말 조회와 분리한 사이트 실행
|
|
67
67
|
*/
|
|
68
68
|
run: async (context) => {
|
|
69
|
-
await (await import("./run-site-command-
|
|
69
|
+
await (await import("./run-site-command-DPG5RNIN.js")).runSiteCommand({
|
|
70
70
|
command: "build",
|
|
71
71
|
args: context.args
|
|
72
72
|
});
|
|
@@ -82,7 +82,7 @@ var main = defineCommand({
|
|
|
82
82
|
* 도움말 조회와 분리한 사이트 실행
|
|
83
83
|
*/
|
|
84
84
|
run: async (context) => {
|
|
85
|
-
await (await import("./run-site-command-
|
|
85
|
+
await (await import("./run-site-command-DPG5RNIN.js")).runSiteCommand({
|
|
86
86
|
command: "preview",
|
|
87
87
|
args: context.args
|
|
88
88
|
});
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as siteConfigSchema } from "./site-config-
|
|
1
|
+
export { t as siteConfigSchema } from "./site-config-FmStUQ2W.js";
|
package/dist/constant/site.d.ts
CHANGED
|
@@ -106,6 +106,23 @@ var asset_media_extensions = /* @__PURE__ */ new Set([
|
|
|
106
106
|
".zip"
|
|
107
107
|
]);
|
|
108
108
|
//#endregion
|
|
109
|
+
//#region src/util/file/assert-content-path.ts
|
|
110
|
+
/**
|
|
111
|
+
* 파일 경로와 symlink 대상의 공개 범위 검사
|
|
112
|
+
*/
|
|
113
|
+
var assertContentPath = (params) => {
|
|
114
|
+
for (const path of [resolve(params.path), ...existsSync(params.path) ? [realpathSync(params.path)] : []]) {
|
|
115
|
+
const location = relative(params.scope.root, path);
|
|
116
|
+
const internal = relative(params.scope.privateRoot, path);
|
|
117
|
+
if (location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location) || location.split(sep).some((part) => [
|
|
118
|
+
"dist",
|
|
119
|
+
".git",
|
|
120
|
+
"node_modules"
|
|
121
|
+
].includes(part))) throw new Error(`문서 범위 밖의 참조: ${params.path}`);
|
|
122
|
+
if (!params.scope.includePrivate && internal !== ".." && !internal.startsWith(`..${sep}`) && !isAbsolute(internal)) throw new Error(`공개 문서에서 private 자료를 참조할 수 없습니다: ${params.path}`);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
//#endregion
|
|
109
126
|
//#region src/util/file/to-asset-path.ts
|
|
110
127
|
/**
|
|
111
128
|
* 파일명에 내용 지문 추가. 같은 내용은 같은 URL, 변경한 내용은 새 캐시 키 사용
|
|
@@ -137,6 +154,14 @@ var rehypeAssets = (options) => (tree, file) => {
|
|
|
137
154
|
return;
|
|
138
155
|
}
|
|
139
156
|
const target = resolve(dirname(file.path), path);
|
|
157
|
+
if (options.scope !== void 0) try {
|
|
158
|
+
assertContentPath({
|
|
159
|
+
scope: options.scope,
|
|
160
|
+
path: target
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
file.fail(error instanceof Error ? error.message : String(error), node);
|
|
164
|
+
}
|
|
140
165
|
const location = relative(options.root, target);
|
|
141
166
|
if (location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location)) file.fail(`${copy_error_asset}: 문서 폴더 밖 · ${url}`, node);
|
|
142
167
|
if (!existsSync(target)) file.fail(`${copy_error_asset}: 파일이 없다 · ${url}`, node);
|
|
@@ -152,6 +177,69 @@ var rehypeAssets = (options) => (tree, file) => {
|
|
|
152
177
|
});
|
|
153
178
|
};
|
|
154
179
|
//#endregion
|
|
180
|
+
//#region src/util/file/to-content-id.ts
|
|
181
|
+
/**
|
|
182
|
+
* 폴더 이름과 독립적인 공개 URL, 내부 문서의 private 접두사
|
|
183
|
+
*/
|
|
184
|
+
var toContentId = (params) => {
|
|
185
|
+
if (relative(params.scope.root, params.path).toLowerCase() === "readme.md") return "";
|
|
186
|
+
for (const [root, prefix] of [[params.scope.publicRoot, ""], [params.scope.privateRoot, "private/"]]) {
|
|
187
|
+
const location = relative(root, params.path);
|
|
188
|
+
if (location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location)) continue;
|
|
189
|
+
const id = location.replace(/\.mdx?$/i, "").split(sep).join("/").toLowerCase();
|
|
190
|
+
if (prefix === "" && id.split("/")[0] === "private") throw new Error(`private URL은 내부 문서 전용입니다: ${params.path}`);
|
|
191
|
+
return `${prefix}${id}`;
|
|
192
|
+
}
|
|
193
|
+
throw new Error(`공개 또는 내부 문서 폴더에 없는 Markdown 참조: ${params.path}`);
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/app/_function/create-processor/_rehype-content-links.ts
|
|
197
|
+
/**
|
|
198
|
+
* 원시 HTML의 내부 참조 차단과 Markdown 링크의 사이트 URL 변환
|
|
199
|
+
*/
|
|
200
|
+
var rehypeContentLinks = (options) => (tree, file) => {
|
|
201
|
+
const scope = options.scope;
|
|
202
|
+
if (scope === void 0) return;
|
|
203
|
+
visit(tree, "element", (node) => {
|
|
204
|
+
for (const property of [
|
|
205
|
+
"href",
|
|
206
|
+
"src",
|
|
207
|
+
"poster",
|
|
208
|
+
"data",
|
|
209
|
+
"srcSet"
|
|
210
|
+
]) {
|
|
211
|
+
const value = node.properties[property];
|
|
212
|
+
if (typeof value !== "string") continue;
|
|
213
|
+
const urls = property === "srcSet" ? value.split(",").map((entry) => entry.trim().split(/\s+/)[0]) : [value];
|
|
214
|
+
for (const url of urls) {
|
|
215
|
+
if (/^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(url)) continue;
|
|
216
|
+
const path = decodeURIComponent(url.split(/[?#]/)[0]);
|
|
217
|
+
try {
|
|
218
|
+
if (path.startsWith("/")) {
|
|
219
|
+
const pathname = new URL(path, "https://content.invalid").pathname.toLowerCase();
|
|
220
|
+
if (!scope.includePrivate && (pathname === "/private" || pathname.startsWith("/private/"))) throw new Error(`공개 문서에서 private URL을 참조할 수 없습니다: ${url}`);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const target = resolve(dirname(file.path), path);
|
|
224
|
+
assertContentPath({
|
|
225
|
+
scope,
|
|
226
|
+
path: target
|
|
227
|
+
});
|
|
228
|
+
if (property === "href" && /\.mdx?$/i.test(path)) {
|
|
229
|
+
const id = toContentId({
|
|
230
|
+
scope,
|
|
231
|
+
path: target
|
|
232
|
+
});
|
|
233
|
+
node.properties[property] = `${id === "" ? "/" : `/${id}/`}${url.slice(url.split(/[?#]/)[0].length)}`;
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
file.fail(error instanceof Error ? error.message : String(error), node);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
//#endregion
|
|
155
243
|
//#region src/app/_function/create-processor/_rehype-heading-ids.ts
|
|
156
244
|
/**
|
|
157
245
|
* 제목 id와 목차 수집. 같은 제목은 -1, -2 접미사
|
|
@@ -259,6 +347,14 @@ var remarkSnippets = (options) => (tree, file) => {
|
|
|
259
347
|
if (match === null) return;
|
|
260
348
|
const [, path = "", region, meta] = match;
|
|
261
349
|
const target = resolve(dirname(file.path), path);
|
|
350
|
+
if (options.scope !== void 0) try {
|
|
351
|
+
assertContentPath({
|
|
352
|
+
scope: options.scope,
|
|
353
|
+
path: target
|
|
354
|
+
});
|
|
355
|
+
} catch (error) {
|
|
356
|
+
file.fail(error instanceof Error ? error.message : String(error), node);
|
|
357
|
+
}
|
|
262
358
|
const location = relative(options.root, target);
|
|
263
359
|
if (location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location)) file.fail(`${copy_error_snippet}: 문서 폴더 밖 · ${path}`, node);
|
|
264
360
|
if (!existsSync(target)) file.fail(`${copy_error_snippet}: 파일이 없다 · ${path}`, node);
|
|
@@ -513,6 +609,24 @@ var remarkLinks = (options) => (tree, file) => {
|
|
|
513
609
|
if (node.type !== "link" && node.type !== "definition") return;
|
|
514
610
|
const match = /^(?![a-z]+:|\/|#)([^#?]+)(\.mdx?)([?#].*|)$/i.exec(node.url);
|
|
515
611
|
if (match) {
|
|
612
|
+
if (options.scope !== void 0) {
|
|
613
|
+
const target = resolve(dirname(file.path), decodeURIComponent(`${match[1]}${match[2]}`));
|
|
614
|
+
try {
|
|
615
|
+
assertContentPath({
|
|
616
|
+
scope: options.scope,
|
|
617
|
+
path: target
|
|
618
|
+
});
|
|
619
|
+
const id = toContentId({
|
|
620
|
+
scope: options.scope,
|
|
621
|
+
path: target
|
|
622
|
+
});
|
|
623
|
+
if (!existsSync(target)) file.message(`${copy_warn_broken_link}: ${node.url}`, node);
|
|
624
|
+
node.url = `${id === "" ? "/" : `/${id}/`}${match[3]}`;
|
|
625
|
+
} catch (error) {
|
|
626
|
+
file.fail(error instanceof Error ? error.message : String(error), node);
|
|
627
|
+
}
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
516
630
|
if (!existsSync(resolve(dirname(file.path), `${match[1]}${match[2]}`))) file.message(`${copy_warn_broken_link}: ${node.url}`, node);
|
|
517
631
|
const target = posix.join(here, `${match[1]}${match[2]}`).toLowerCase();
|
|
518
632
|
node.url = target === "readme.md" ? `/${match[3]}` : `/${target.slice(0, -match[2].length)}/${match[3]}`;
|
|
@@ -630,18 +744,25 @@ var remarkUnknownDirectives = () => (tree, file) => {
|
|
|
630
744
|
*/
|
|
631
745
|
var createProcessor = (options) => {
|
|
632
746
|
const files = options.files ?? /* @__PURE__ */ new Map();
|
|
633
|
-
return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkSnippets, {
|
|
747
|
+
return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkSnippets, {
|
|
748
|
+
root: options.root,
|
|
749
|
+
scope: options.scope
|
|
750
|
+
}).use(remarkSmartypants).use(remarkDirective).use(remarkBlocks).use(remarkParts).use(remarkMermaid, {
|
|
634
751
|
assets: options.mermaid,
|
|
635
752
|
files
|
|
636
|
-
}).use(remarkStatus, { status: options.site.status }).use(remarkSwatch).use(remarkLinks, {
|
|
753
|
+
}).use(remarkStatus, { status: options.site.status }).use(remarkSwatch).use(remarkLinks, {
|
|
754
|
+
root: options.root,
|
|
755
|
+
scope: options.scope
|
|
756
|
+
}).use(remarkUnknownDirectives).use(remarkReport, { root: options.root }).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeShiki, {
|
|
637
757
|
theme: createCssVariablesTheme({
|
|
638
758
|
name: "for-humanity",
|
|
639
759
|
variablePrefix: "--app-code-"
|
|
640
760
|
}),
|
|
641
761
|
transformers: shikiTransformers
|
|
642
|
-
}).use(rehypeHead).use(rehypeHeadingIds).use(rehypeSections).use(rehypeTables).use(rehypeRaw).use(rehypeAssets, {
|
|
762
|
+
}).use(rehypeHead).use(rehypeHeadingIds).use(rehypeSections).use(rehypeTables).use(rehypeRaw).use(rehypeContentLinks, { scope: options.scope }).use(rehypeAssets, {
|
|
643
763
|
root: options.root,
|
|
644
|
-
files
|
|
764
|
+
files,
|
|
765
|
+
scope: options.scope
|
|
645
766
|
}).use(rehypeStringify, { allowDangerousHtml: true });
|
|
646
767
|
};
|
|
647
768
|
//#endregion
|
|
@@ -660,6 +781,32 @@ var loadMermaidAssets = (kitRoot) => {
|
|
|
660
781
|
};
|
|
661
782
|
};
|
|
662
783
|
//#endregion
|
|
784
|
+
//#region src/app/_function/read-content-scope.ts
|
|
785
|
+
/**
|
|
786
|
+
* 공개 폴더 필수, 내부 폴더 선택, 범위 폴더의 symlink 별칭 금지
|
|
787
|
+
*/
|
|
788
|
+
var readContentScope = (params) => {
|
|
789
|
+
if (!existsSync(params.root) || !statSync(params.root).isDirectory()) throw new Error(`content 문서 루트가 없습니다: ${params.root}`);
|
|
790
|
+
const root = realpathSync(params.root);
|
|
791
|
+
const publicRoot = join(root, params.content.public);
|
|
792
|
+
const privateRoot = join(root, params.content.private);
|
|
793
|
+
for (const path of [publicRoot, privateRoot]) {
|
|
794
|
+
if (!existsSync(path)) {
|
|
795
|
+
if (path === privateRoot) continue;
|
|
796
|
+
throw new Error(`공개 문서 폴더가 없습니다: ${path}. content/public 구조 또는 content 설정을 확인하세요`);
|
|
797
|
+
}
|
|
798
|
+
const actual = realpathSync(path);
|
|
799
|
+
const location = relative(root, actual);
|
|
800
|
+
if (!statSync(path).isDirectory() || actual !== path || location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location)) throw new Error(`문서 폴더는 content 내부의 실제 폴더여야 합니다: ${path}`);
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
root,
|
|
804
|
+
publicRoot,
|
|
805
|
+
privateRoot,
|
|
806
|
+
includePrivate: params.includePrivate
|
|
807
|
+
};
|
|
808
|
+
};
|
|
809
|
+
//#endregion
|
|
663
810
|
//#region src/constant/doc.ts
|
|
664
811
|
/**
|
|
665
812
|
* 기존 frontmatter type의 호환 값. 모두 같은 Markdown 렌더링
|
|
@@ -708,15 +855,23 @@ var docDataSchema = z.object({
|
|
|
708
855
|
//#endregion
|
|
709
856
|
//#region src/app/_function/read-docs.ts
|
|
710
857
|
/**
|
|
711
|
-
* Markdown 수집과
|
|
858
|
+
* 공개 범위의 Markdown 수집과 렌더링, 범위 미지정 시 독립 Markdown 처리
|
|
712
859
|
*
|
|
713
|
-
* -
|
|
860
|
+
* - 사이트 실행에서 공개·내부 문서만 수집, content 루트 안내 파일 제외
|
|
714
861
|
* - 머리말, 문서 식별자 중복, 플러그인 오류 시 실패
|
|
715
862
|
*/
|
|
716
863
|
var readDocs = async (options) => {
|
|
717
|
-
const
|
|
864
|
+
const roots = options.scope === void 0 ? [options.root] : [options.scope.publicRoot, ...options.scope.includePrivate && existsSync(options.scope.privateRoot) ? [options.scope.privateRoot] : []];
|
|
865
|
+
const paths = (await Promise.all(roots.map(async (root) => (await readdir(root, { recursive: true })).map((path) => relative(options.root, join(root, path)))))).flat().filter((path) => /\.mdx?$/i.test(path) && !/^(readme|agents|claude)\.md$/i.test(path) && !path.split(sep).includes("node_modules") && !path.startsWith(`dist${sep}`));
|
|
718
866
|
const docs = await Promise.all(paths.map(async (path) => {
|
|
719
|
-
|
|
867
|
+
if (options.scope !== void 0) assertContentPath({
|
|
868
|
+
scope: options.scope,
|
|
869
|
+
path: join(options.root, path)
|
|
870
|
+
});
|
|
871
|
+
const id = options.scope === void 0 ? path.replace(/\.mdx?$/i, "").split(sep).join("/").toLowerCase() : toContentId({
|
|
872
|
+
scope: options.scope,
|
|
873
|
+
path: join(options.root, path)
|
|
874
|
+
});
|
|
720
875
|
const segments = id.split("/");
|
|
721
876
|
if (segments.some((segment) => segment === "" || segment === "." || segment === ".." || segment === "index.html") || /[#?%*:\\]/.test(id) || /[\p{Cc}]/u.test(id) || options.reserved?.includes(segments[0]) === true || segments[0] === "_fh" || segments[0] === "/favicon.svg".slice(1)) throw new Error(`${copy_error_doc_id}: ${path}`);
|
|
722
877
|
const source = await readFile(join(options.root, path), "utf8");
|
|
@@ -728,9 +883,14 @@ var readDocs = async (options) => {
|
|
|
728
883
|
const frontmatter = tree.children[0];
|
|
729
884
|
const parsed = docDataSchema.safeParse(frontmatter?.type === "yaml" ? parse(frontmatter.value) : {});
|
|
730
885
|
if (!parsed.success) throw new Error(`${copy_error_frontmatter}: ${path}: ${parsed.error.issues.map((issue) => `${issue.path.join(".")} ${issue.message}`).join(", ")}`);
|
|
886
|
+
const parentPath = parsed.data.parent !== void 0 && /^\.\.?\//.test(parsed.data.parent) ? resolve(dirname(file.path), parsed.data.parent) : void 0;
|
|
887
|
+
const parent = parsed.data.parent !== void 0 && parentPath !== void 0 ? posix.join(posix.dirname(id), parsed.data.parent) : parsed.data.parent;
|
|
731
888
|
const data = {
|
|
732
889
|
...parsed.data,
|
|
733
|
-
parent:
|
|
890
|
+
parent: options.scope !== void 0 && parentPath !== void 0 ? toContentId({
|
|
891
|
+
scope: options.scope,
|
|
892
|
+
path: parentPath
|
|
893
|
+
}) : parent
|
|
734
894
|
};
|
|
735
895
|
const fh = {
|
|
736
896
|
frontmatter: data,
|
|
@@ -783,6 +943,10 @@ var readHome = async (options) => {
|
|
|
783
943
|
if (paths.length > 1) throw new Error(`${copy_error_home_clash}: ${paths.join(", ")}`);
|
|
784
944
|
if (paths.length === 0) return;
|
|
785
945
|
const path = join(options.root, paths[0]);
|
|
946
|
+
if (options.scope !== void 0) assertContentPath({
|
|
947
|
+
scope: options.scope,
|
|
948
|
+
path
|
|
949
|
+
});
|
|
786
950
|
const fh = {
|
|
787
951
|
headings: [],
|
|
788
952
|
sections: []
|
|
@@ -2285,18 +2449,26 @@ var loadSite = async (params) => {
|
|
|
2285
2449
|
...brand.files
|
|
2286
2450
|
]);
|
|
2287
2451
|
if (params.stylePaths === void 0) files.set(stylePaths[0], join(params.kitRoot, "dist/cli.css"));
|
|
2452
|
+
const scope = readContentScope({
|
|
2453
|
+
root: params.root,
|
|
2454
|
+
content: params.site.content,
|
|
2455
|
+
includePrivate: params.includePrivate === true
|
|
2456
|
+
});
|
|
2288
2457
|
const processor = createProcessor({
|
|
2289
2458
|
site: params.site,
|
|
2290
|
-
root:
|
|
2459
|
+
root: scope.root,
|
|
2291
2460
|
files,
|
|
2292
|
-
mermaid: loadMermaidAssets(params.kitRoot)
|
|
2461
|
+
mermaid: loadMermaidAssets(params.kitRoot),
|
|
2462
|
+
scope
|
|
2293
2463
|
});
|
|
2294
2464
|
const [docs, home] = await Promise.all([readDocs({
|
|
2295
|
-
root:
|
|
2465
|
+
root: scope.root,
|
|
2466
|
+
scope,
|
|
2296
2467
|
processor,
|
|
2297
2468
|
reserved: params.site.url === void 0 ? void 0 : crawling_reserved_roots
|
|
2298
2469
|
}), readHome({
|
|
2299
|
-
root:
|
|
2470
|
+
root: scope.root,
|
|
2471
|
+
scope,
|
|
2300
2472
|
processor
|
|
2301
2473
|
})]);
|
|
2302
2474
|
const store = {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { d as copy_error_missing_build, n as copy_error_config } from "./copy-C1uzvEnr.js";
|
|
2
2
|
import { t as logger } from "./logger-Dv1i9xSV.js";
|
|
3
|
-
import { n as site_config_absent, t as siteConfigSchema } from "./site-config-
|
|
4
|
-
import { n as font_cache_control, r as asset_font_dir, t as loadSite } from "./load-site-
|
|
3
|
+
import { n as site_config_absent, t as siteConfigSchema } from "./site-config-FmStUQ2W.js";
|
|
4
|
+
import { n as font_cache_control, r as asset_font_dir, t as loadSite } from "./load-site-CBJGMNfH.js";
|
|
5
5
|
import { n as config_file_names, t as toErrorMessage } from "./to-error-message-pF6_7wGK.js";
|
|
6
6
|
import { copyFile, mkdir, rm, writeFile } from "node:fs/promises";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
8
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
9
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
10
10
|
import { toSSG } from "hono/ssg";
|
|
11
11
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
12
|
import { createJiti } from "jiti";
|
|
13
13
|
//#region src/app/_function/read-site-config.ts
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* 프로젝트 루트의 설정 조회, TypeScript 실행과 스키마 검증
|
|
16
16
|
*
|
|
17
17
|
* - 중복 파일 거부, 실제 적용되는 설정의 모호성 방지
|
|
18
18
|
* - Node의 기본 TypeScript 지원 여부와 무관하게 같은 설정 실행
|
|
@@ -31,6 +31,29 @@ var readSiteConfig = async (root) => {
|
|
|
31
31
|
return parsed.data.default;
|
|
32
32
|
};
|
|
33
33
|
//#endregion
|
|
34
|
+
//#region src/app/_function/read-project.ts
|
|
35
|
+
/**
|
|
36
|
+
* 프로젝트 설정과 문서 루트 해소, 외부·시스템 폴더 symlink 거부
|
|
37
|
+
*/
|
|
38
|
+
var readProject = async (directory) => {
|
|
39
|
+
const projectRoot = realpathSync(resolve(directory));
|
|
40
|
+
const site = await readSiteConfig(projectRoot);
|
|
41
|
+
const root = resolve(projectRoot, site.content.root);
|
|
42
|
+
if (existsSync(root)) {
|
|
43
|
+
const location = relative(projectRoot, realpathSync(root));
|
|
44
|
+
if (location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location)) throw new Error(`content 루트는 프로젝트 내부여야 합니다: ${root}`);
|
|
45
|
+
if (location.split(sep).some((part) => [
|
|
46
|
+
"dist",
|
|
47
|
+
".git",
|
|
48
|
+
"node_modules"
|
|
49
|
+
].includes(part.toLowerCase()))) throw new Error(`content 루트는 dist, .git, node_modules 내부일 수 없습니다: ${root}`);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
root,
|
|
53
|
+
site
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
//#endregion
|
|
34
57
|
//#region src/entry/preview/to-preview-config.ts
|
|
35
58
|
/**
|
|
36
59
|
* 빌드된 사이트 전용 Vite 설정. 소비 프로젝트의 설정과 환경 파일 자동 로드 제외
|
|
@@ -75,31 +98,31 @@ var toPreviewConfig = (params) => {
|
|
|
75
98
|
*/
|
|
76
99
|
var runSiteCommand = async (params) => {
|
|
77
100
|
try {
|
|
78
|
-
if (params.args._.length > 1) throw new Error("
|
|
101
|
+
if (params.args._.length > 1) throw new Error("프로젝트 폴더는 하나만 지정하세요");
|
|
79
102
|
const kitRoot = fileURLToPath(new URL("../", import.meta.url));
|
|
80
|
-
const
|
|
81
|
-
const outDir = join(
|
|
82
|
-
const siteConfig = await readSiteConfig(docsRoot);
|
|
103
|
+
const project = await readProject(params.args.dir);
|
|
104
|
+
const outDir = join(project.root, "dist");
|
|
83
105
|
if (params.command === "preview") {
|
|
84
106
|
(await (await import("vite")).preview(toPreviewConfig({
|
|
85
|
-
root:
|
|
86
|
-
port:
|
|
107
|
+
root: project.root,
|
|
108
|
+
port: project.site.port
|
|
87
109
|
}))).printUrls();
|
|
88
110
|
return;
|
|
89
111
|
}
|
|
90
112
|
if (params.command === "dev") {
|
|
91
113
|
await (await import("./start-dev-server-x7Ja5uwv.js")).startDevServer({
|
|
92
|
-
root:
|
|
114
|
+
root: project.root,
|
|
93
115
|
kitRoot,
|
|
94
|
-
site:
|
|
95
|
-
source: false
|
|
116
|
+
site: project.site,
|
|
117
|
+
source: false,
|
|
118
|
+
includePrivate: true
|
|
96
119
|
});
|
|
97
120
|
return;
|
|
98
121
|
}
|
|
99
122
|
const snapshot = await loadSite({
|
|
100
|
-
root:
|
|
123
|
+
root: project.root,
|
|
101
124
|
kitRoot,
|
|
102
|
-
site:
|
|
125
|
+
site: project.site
|
|
103
126
|
});
|
|
104
127
|
await rm(outDir, {
|
|
105
128
|
recursive: true,
|
|
@@ -18,6 +18,14 @@ var site_port_default = 4321;
|
|
|
18
18
|
* 포트 번호 상한. TCP
|
|
19
19
|
*/
|
|
20
20
|
var site_port_max = 65535;
|
|
21
|
+
/**
|
|
22
|
+
* 기본 문서 루트와 공개 범위 폴더
|
|
23
|
+
*/
|
|
24
|
+
var content_default = {
|
|
25
|
+
root: "content",
|
|
26
|
+
public: "public",
|
|
27
|
+
private: "private"
|
|
28
|
+
};
|
|
21
29
|
//#endregion
|
|
22
30
|
//#region src/constant/status.ts
|
|
23
31
|
/**
|
|
@@ -39,11 +47,47 @@ var status_default_phrases = [{
|
|
|
39
47
|
kind: status_kind.unverified
|
|
40
48
|
}];
|
|
41
49
|
//#endregion
|
|
50
|
+
//#region src/type/content-config.ts
|
|
51
|
+
/**
|
|
52
|
+
* 프로젝트 내부의 상대 경로, 출력 폴더와 시스템 폴더 제외
|
|
53
|
+
*/
|
|
54
|
+
var contentPathSchema = z.string().trim().min(1).refine((value) => !/^[\\/]|[:?#%*\p{Cc}]/u.test(value) && !value.split(/[\\/]/).some((part) => [
|
|
55
|
+
"..",
|
|
56
|
+
"dist",
|
|
57
|
+
"node_modules",
|
|
58
|
+
".git"
|
|
59
|
+
].includes(part.toLowerCase())), "프로젝트 내부 상대 경로만 사용하세요. dist, node_modules, .git 경로는 사용할 수 없습니다").transform((value) => value.replaceAll("\\", "/").split("/").filter((part) => part !== "" && part !== ".").join("/"));
|
|
60
|
+
/**
|
|
61
|
+
* 공개와 내부 문서 폴더의 상대 경로 계약
|
|
62
|
+
*/
|
|
63
|
+
var contentConfigSchema = z.object({
|
|
64
|
+
/**
|
|
65
|
+
* 프로젝트 루트 기준 문서 폴더, 점 하나는 프로젝트 루트
|
|
66
|
+
*/
|
|
67
|
+
root: contentPathSchema.transform((value) => value === "" ? "." : value).default(content_default.root),
|
|
68
|
+
/**
|
|
69
|
+
* 문서 루트 아래 공개 문서 폴더
|
|
70
|
+
*/
|
|
71
|
+
public: contentPathSchema.refine((value) => value !== "", "공개 문서는 별도 하위 폴더가 필요합니다").default(content_default.public),
|
|
72
|
+
/**
|
|
73
|
+
* 문서 루트 아래 로컬 전용 문서 폴더
|
|
74
|
+
*/
|
|
75
|
+
private: contentPathSchema.refine((value) => value !== "", "내부 문서는 별도 하위 폴더가 필요합니다").default(content_default.private)
|
|
76
|
+
}).refine((value) => {
|
|
77
|
+
const published = value.public.toLowerCase();
|
|
78
|
+
const internal = value.private.toLowerCase();
|
|
79
|
+
return published !== internal && !published.startsWith(`${internal}/`) && !internal.startsWith(`${published}/`);
|
|
80
|
+
}, "공개 문서와 내부 문서 폴더는 같거나 중첩될 수 없습니다");
|
|
81
|
+
//#endregion
|
|
42
82
|
//#region src/type/site-config.ts
|
|
43
83
|
/**
|
|
44
84
|
* 사이트 설정의 입력 검증과 기본값 적용
|
|
45
85
|
*/
|
|
46
86
|
var siteConfigSchema = z.object({
|
|
87
|
+
/**
|
|
88
|
+
* 프로젝트 기준 문서 루트와 공개 범위
|
|
89
|
+
*/
|
|
90
|
+
content: contentConfigSchema.default(content_default),
|
|
47
91
|
/**
|
|
48
92
|
* 사이드바 이름과 탭 제목
|
|
49
93
|
*/
|
package/dist/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as loadSite } from "./load-site-
|
|
1
|
+
export { t as loadSite } from "./load-site-CBJGMNfH.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* 공개와 내부 문서 폴더의 상대 경로 계약
|
|
4
|
+
*/
|
|
5
|
+
export declare const contentConfigSchema: z.ZodObject<{
|
|
6
|
+
root: z.ZodDefault<z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodTransform<string, string>>>;
|
|
7
|
+
public: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
8
|
+
private: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
9
|
+
}, z.core.$strip>;
|
|
@@ -3,6 +3,11 @@ import { z } from "zod";
|
|
|
3
3
|
* 사이트 설정의 입력 검증과 기본값 적용
|
|
4
4
|
*/
|
|
5
5
|
export declare const siteConfigSchema: z.ZodObject<{
|
|
6
|
+
content: z.ZodDefault<z.ZodObject<{
|
|
7
|
+
root: z.ZodDefault<z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodTransform<string, string>>>;
|
|
8
|
+
public: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
9
|
+
private: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
10
|
+
}, z.core.$strip>>;
|
|
6
11
|
title: z.ZodDefault<z.ZodString>;
|
|
7
12
|
description: z.ZodOptional<z.ZodString>;
|
|
8
13
|
url: z.ZodOptional<z.ZodPipe<z.ZodPipe<z.ZodURL, z.ZodTransform<URL, string>>, z.ZodTransform<string, URL>>>;
|
|
@@ -26,6 +31,10 @@ export type ResolvedSiteConfig = z.infer<typeof siteConfigSchema>;
|
|
|
26
31
|
* 작성자가 지정하는 사이트 설정, 기본값 적용 전 입력
|
|
27
32
|
*/
|
|
28
33
|
export interface SiteConfig {
|
|
34
|
+
/**
|
|
35
|
+
* 기본 content/public, content/private 구조의 경로 변경
|
|
36
|
+
*/
|
|
37
|
+
content?: z.input<typeof siteConfigSchema>["content"];
|
|
29
38
|
/**
|
|
30
39
|
* 사이드바 이름과 탭 제목, 생략 시 for humanity
|
|
31
40
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "for-humanity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Documents written for humans to read: a docs kit with a sidebar, parts, sections and Mermaid diagrams, built on Hono JSX",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"files": [
|
|
28
28
|
"dist",
|
|
29
29
|
"src/asset",
|
|
30
|
+
"template",
|
|
30
31
|
"CHANGELOG.md"
|
|
31
32
|
],
|
|
32
33
|
"engines": {
|
|
@@ -37,9 +38,9 @@
|
|
|
37
38
|
"sync:readme": "tsx util/sync-readme.ts",
|
|
38
39
|
"sync:docs": "pnpm sync:readme && tsx util/sync-changelog.ts",
|
|
39
40
|
"dev": "pnpm sync:docs && pnpm build:kit && tsx src/entry/dev/dev.ts",
|
|
40
|
-
"build": "pnpm sync:docs && pnpm build:kit && node dist/cli.js build
|
|
41
|
+
"build": "pnpm sync:docs && pnpm build:kit && node dist/cli.js build .",
|
|
41
42
|
"build:pages": "pnpm build && tsx util/prepare-pages.ts",
|
|
42
|
-
"preview": "node dist/cli.js preview
|
|
43
|
+
"preview": "node dist/cli.js preview .",
|
|
43
44
|
"test": "pnpm build:kit && vitest run",
|
|
44
45
|
"test:watch": "pnpm build:kit && vitest",
|
|
45
46
|
"test:browser": "pnpm build:kit && playwright test",
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Getting started
|
|
3
|
+
label: 문서 작성과 공개 범위
|
|
4
|
+
group: Guide
|
|
5
|
+
order: 10
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Getting started
|
|
9
|
+
|
|
10
|
+
`content/public`은 공개 문서, `content/private`는 로컬 전용 문서입니다.
|
|
11
|
+
공용 이미지와 첨부는 `content/asset`, 내부 자료는 `content/private/asset`에 보관합니다.
|
|
12
|
+
|
|
13
|
+
프로젝트 루트에서 실행합니다.
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pnpm exec for-humanity dev
|
|
17
|
+
pnpm exec for-humanity build
|
|
18
|
+
pnpm exec for-humanity preview
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`dev`는 전체 문서, `build`와 `preview`는 공개 결과를 제공합니다.
|
|
22
|
+
배포할 폴더는 `content/dist`입니다. `content` 전체를 업로드하지 않습니다.
|
|
23
|
+
`private`는 사이트 배포 제외이며 Git 저장소 접근 제어가 아닙니다.
|
|
24
|
+
|
|
25
|
+
설정은 프로젝트 루트의 `for-humanity.config.ts`, 홈은 `content/README.md`입니다.
|
|
26
|
+
공개 문서의 URL에는 `public`이 붙지 않습니다. 이 문서는 `/getting-started/`에서 확인할 수 있습니다.
|