@mandujs/core 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +4 -4
- package/README.md +653 -653
- package/package.json +1 -1
- package/src/bundler/build.ts +91 -91
- package/src/bundler/css.ts +302 -302
- package/src/client/Link.tsx +227 -227
- package/src/client/globals.ts +44 -44
- package/src/client/hooks.ts +267 -267
- package/src/client/index.ts +5 -5
- package/src/client/island.ts +8 -8
- package/src/client/router.ts +435 -435
- package/src/client/runtime.ts +23 -23
- package/src/client/serialize.ts +404 -404
- package/src/client/window-state.ts +101 -101
- package/src/config/mandu.ts +9 -0
- package/src/config/validate.ts +12 -0
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/content-layer.ts +314 -314
- package/src/content/content.test.ts +433 -433
- package/src/content/data-store.ts +245 -245
- package/src/content/digest.ts +133 -133
- package/src/content/index.ts +164 -164
- package/src/content/loader-context.ts +172 -172
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +169 -169
- package/src/content/loaders/glob.ts +252 -252
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/client.ts +16 -16
- package/src/contract/define.ts +459 -459
- package/src/contract/handler.ts +10 -10
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +404 -404
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/registry.ts +568 -568
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/contract/validator.ts +32 -32
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +465 -465
- package/src/devtools/client/catchers/error-catcher.ts +327 -327
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/catchers/network-proxy.ts +363 -363
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/kitchen-root.tsx +362 -362
- package/src/devtools/client/components/mandu-character.tsx +241 -241
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +244 -244
- package/src/devtools/client/components/panel/index.ts +32 -32
- package/src/devtools/client/components/panel/islands-panel.tsx +304 -304
- package/src/devtools/client/components/panel/network-panel.tsx +292 -292
- package/src/devtools/client/components/panel/panel-container.tsx +259 -259
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/client/state-manager.ts +478 -478
- package/src/devtools/design-tokens.ts +263 -263
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +266 -266
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +444 -444
- package/src/devtools/types.ts +319 -319
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +222 -222
- package/src/devtools/worker/worker-manager.ts +409 -409
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +46 -46
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/auth.ts +308 -308
- package/src/filling/context.ts +24 -1
- package/src/filling/deps.ts +238 -238
- package/src/filling/index.ts +4 -0
- package/src/filling/sse-catchup.test.ts +56 -0
- package/src/filling/sse-catchup.ts +67 -0
- package/src/filling/sse.test.ts +168 -0
- package/src/filling/sse.ts +162 -0
- package/src/generator/index.ts +3 -3
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/ast-analyzer.ts +806 -806
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/presets/index.ts +291 -291
- package/src/guard/reporter.ts +445 -445
- package/src/guard/rules.ts +12 -12
- package/src/guard/statistics.ts +578 -578
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/types.ts +348 -348
- package/src/guard/validator.ts +834 -834
- package/src/guard/watcher.ts +404 -404
- package/src/index.ts +6 -1
- package/src/intent/index.ts +310 -310
- package/src/island/index.ts +304 -304
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/plugins/index.ts +38 -38
- package/src/plugins/registry.ts +377 -377
- package/src/plugins/types.ts +363 -363
- package/src/report/index.ts +1 -1
- package/src/router/fs-patterns.ts +387 -387
- package/src/router/fs-scanner.ts +497 -497
- package/src/runtime/boundary.tsx +232 -232
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/escape.ts +44 -0
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/logger.ts +677 -677
- package/src/runtime/router.test.ts +476 -476
- package/src/runtime/router.ts +105 -105
- package/src/runtime/security.ts +155 -155
- package/src/runtime/server.ts +257 -0
- package/src/runtime/session-key.ts +328 -328
- package/src/runtime/ssr.ts +16 -21
- package/src/runtime/streaming-ssr.ts +24 -33
- package/src/runtime/trace.ts +144 -144
- package/src/seo/index.ts +214 -214
- package/src/seo/integration/ssr.ts +307 -307
- package/src/seo/render/basic.ts +427 -427
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +191 -191
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +126 -126
- package/src/seo/resolve/index.ts +353 -353
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +97 -97
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +575 -575
- package/src/slot/validator.ts +39 -39
- package/src/spec/index.ts +3 -3
- package/src/spec/load.ts +76 -76
- package/src/spec/lock.ts +56 -56
- package/src/utils/bun.ts +8 -8
- package/src/utils/lru-cache.ts +75 -75
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu SEO - Open Graph Meta Tags Rendering
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { ResolvedMetadata, ResolvedOpenGraph } from '../types'
|
|
6
|
-
import { urlToString } from '../resolve/url'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* HTML 이스케이프
|
|
10
|
-
*/
|
|
11
|
-
function escapeHtml(str: string): string {
|
|
12
|
-
return str
|
|
13
|
-
.replace(/&/g, '&')
|
|
14
|
-
.replace(/</g, '<')
|
|
15
|
-
.replace(/>/g, '>')
|
|
16
|
-
.replace(/"/g, '"')
|
|
17
|
-
.replace(/'/g, ''')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* OG 메타 태그 생성 헬퍼
|
|
22
|
-
*/
|
|
23
|
-
function og(property: string, content: string | number): string {
|
|
24
|
-
return `<meta property="og:${property}" content="${escapeHtml(String(content))}" />`
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Open Graph 메타 태그 렌더링
|
|
29
|
-
*/
|
|
30
|
-
export function renderOpenGraph(metadata: ResolvedMetadata): string {
|
|
31
|
-
const openGraph = metadata.openGraph
|
|
32
|
-
if (!openGraph) return ''
|
|
33
|
-
|
|
34
|
-
const tags: string[] = []
|
|
35
|
-
|
|
36
|
-
// Basic OG tags
|
|
37
|
-
if (openGraph.type) {
|
|
38
|
-
tags.push(og('type', openGraph.type))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// title - fallback to metadata.title
|
|
42
|
-
const title = openGraph.title || metadata.title?.absolute
|
|
43
|
-
if (title) {
|
|
44
|
-
tags.push(og('title', title))
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// description - fallback to metadata.description
|
|
48
|
-
const description = openGraph.description || metadata.description
|
|
49
|
-
if (description) {
|
|
50
|
-
tags.push(og('description', description))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (openGraph.url) {
|
|
54
|
-
tags.push(og('url', openGraph.url.href))
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (openGraph.siteName) {
|
|
58
|
-
tags.push(og('site_name', openGraph.siteName))
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (openGraph.locale) {
|
|
62
|
-
tags.push(og('locale', openGraph.locale))
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (openGraph.determiner) {
|
|
66
|
-
tags.push(og('determiner', openGraph.determiner))
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Images
|
|
70
|
-
if (openGraph.images) {
|
|
71
|
-
for (const image of openGraph.images) {
|
|
72
|
-
tags.push(og('image', urlToString(image.url)))
|
|
73
|
-
if (image.secureUrl) {
|
|
74
|
-
tags.push(og('image:secure_url', urlToString(image.secureUrl)))
|
|
75
|
-
}
|
|
76
|
-
if (image.type) {
|
|
77
|
-
tags.push(og('image:type', image.type))
|
|
78
|
-
}
|
|
79
|
-
if (image.width) {
|
|
80
|
-
tags.push(og('image:width', image.width))
|
|
81
|
-
}
|
|
82
|
-
if (image.height) {
|
|
83
|
-
tags.push(og('image:height', image.height))
|
|
84
|
-
}
|
|
85
|
-
if (image.alt) {
|
|
86
|
-
tags.push(og('image:alt', image.alt))
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Videos
|
|
92
|
-
if (openGraph.videos) {
|
|
93
|
-
for (const video of openGraph.videos) {
|
|
94
|
-
tags.push(og('video', urlToString(video.url)))
|
|
95
|
-
if (video.secureUrl) {
|
|
96
|
-
tags.push(og('video:secure_url', urlToString(video.secureUrl)))
|
|
97
|
-
}
|
|
98
|
-
if (video.type) {
|
|
99
|
-
tags.push(og('video:type', video.type))
|
|
100
|
-
}
|
|
101
|
-
if (video.width) {
|
|
102
|
-
tags.push(og('video:width', video.width))
|
|
103
|
-
}
|
|
104
|
-
if (video.height) {
|
|
105
|
-
tags.push(og('video:height', video.height))
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Audio
|
|
111
|
-
if (openGraph.audio) {
|
|
112
|
-
for (const audio of openGraph.audio) {
|
|
113
|
-
tags.push(og('audio', urlToString(audio.url)))
|
|
114
|
-
if (audio.secureUrl) {
|
|
115
|
-
tags.push(og('audio:secure_url', urlToString(audio.secureUrl)))
|
|
116
|
-
}
|
|
117
|
-
if (audio.type) {
|
|
118
|
-
tags.push(og('audio:type', audio.type))
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Article specific
|
|
124
|
-
if (openGraph.article) {
|
|
125
|
-
const article = openGraph.article
|
|
126
|
-
if (article.publishedTime) {
|
|
127
|
-
tags.push(og('article:published_time', article.publishedTime))
|
|
128
|
-
}
|
|
129
|
-
if (article.modifiedTime) {
|
|
130
|
-
tags.push(og('article:modified_time', article.modifiedTime))
|
|
131
|
-
}
|
|
132
|
-
if (article.expirationTime) {
|
|
133
|
-
tags.push(og('article:expiration_time', article.expirationTime))
|
|
134
|
-
}
|
|
135
|
-
if (article.section) {
|
|
136
|
-
tags.push(og('article:section', article.section))
|
|
137
|
-
}
|
|
138
|
-
if (article.authors) {
|
|
139
|
-
const authors = Array.isArray(article.authors) ? article.authors : [article.authors]
|
|
140
|
-
for (const author of authors) {
|
|
141
|
-
tags.push(og('article:author', author))
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (article.tags) {
|
|
145
|
-
for (const tag of article.tags) {
|
|
146
|
-
tags.push(og('article:tag', tag))
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Profile specific
|
|
152
|
-
if (openGraph.profile) {
|
|
153
|
-
const profile = openGraph.profile
|
|
154
|
-
if (profile.firstName) {
|
|
155
|
-
tags.push(og('profile:first_name', profile.firstName))
|
|
156
|
-
}
|
|
157
|
-
if (profile.lastName) {
|
|
158
|
-
tags.push(og('profile:last_name', profile.lastName))
|
|
159
|
-
}
|
|
160
|
-
if (profile.username) {
|
|
161
|
-
tags.push(og('profile:username', profile.username))
|
|
162
|
-
}
|
|
163
|
-
if (profile.gender) {
|
|
164
|
-
tags.push(og('profile:gender', profile.gender))
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Book specific
|
|
169
|
-
if (openGraph.book) {
|
|
170
|
-
const book = openGraph.book
|
|
171
|
-
if (book.isbn) {
|
|
172
|
-
tags.push(og('book:isbn', book.isbn))
|
|
173
|
-
}
|
|
174
|
-
if (book.releaseDate) {
|
|
175
|
-
tags.push(og('book:release_date', book.releaseDate))
|
|
176
|
-
}
|
|
177
|
-
if (book.authors) {
|
|
178
|
-
const authors = Array.isArray(book.authors) ? book.authors : [book.authors]
|
|
179
|
-
for (const author of authors) {
|
|
180
|
-
tags.push(og('book:author', author))
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (book.tags) {
|
|
184
|
-
for (const tag of book.tags) {
|
|
185
|
-
tags.push(og('book:tag', tag))
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return tags.join('\n')
|
|
191
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu SEO - Open Graph Meta Tags Rendering
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ResolvedMetadata, ResolvedOpenGraph } from '../types'
|
|
6
|
+
import { urlToString } from '../resolve/url'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* HTML 이스케이프
|
|
10
|
+
*/
|
|
11
|
+
function escapeHtml(str: string): string {
|
|
12
|
+
return str
|
|
13
|
+
.replace(/&/g, '&')
|
|
14
|
+
.replace(/</g, '<')
|
|
15
|
+
.replace(/>/g, '>')
|
|
16
|
+
.replace(/"/g, '"')
|
|
17
|
+
.replace(/'/g, ''')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* OG 메타 태그 생성 헬퍼
|
|
22
|
+
*/
|
|
23
|
+
function og(property: string, content: string | number): string {
|
|
24
|
+
return `<meta property="og:${property}" content="${escapeHtml(String(content))}" />`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Open Graph 메타 태그 렌더링
|
|
29
|
+
*/
|
|
30
|
+
export function renderOpenGraph(metadata: ResolvedMetadata): string {
|
|
31
|
+
const openGraph = metadata.openGraph
|
|
32
|
+
if (!openGraph) return ''
|
|
33
|
+
|
|
34
|
+
const tags: string[] = []
|
|
35
|
+
|
|
36
|
+
// Basic OG tags
|
|
37
|
+
if (openGraph.type) {
|
|
38
|
+
tags.push(og('type', openGraph.type))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// title - fallback to metadata.title
|
|
42
|
+
const title = openGraph.title || metadata.title?.absolute
|
|
43
|
+
if (title) {
|
|
44
|
+
tags.push(og('title', title))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// description - fallback to metadata.description
|
|
48
|
+
const description = openGraph.description || metadata.description
|
|
49
|
+
if (description) {
|
|
50
|
+
tags.push(og('description', description))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (openGraph.url) {
|
|
54
|
+
tags.push(og('url', openGraph.url.href))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (openGraph.siteName) {
|
|
58
|
+
tags.push(og('site_name', openGraph.siteName))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (openGraph.locale) {
|
|
62
|
+
tags.push(og('locale', openGraph.locale))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (openGraph.determiner) {
|
|
66
|
+
tags.push(og('determiner', openGraph.determiner))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Images
|
|
70
|
+
if (openGraph.images) {
|
|
71
|
+
for (const image of openGraph.images) {
|
|
72
|
+
tags.push(og('image', urlToString(image.url)))
|
|
73
|
+
if (image.secureUrl) {
|
|
74
|
+
tags.push(og('image:secure_url', urlToString(image.secureUrl)))
|
|
75
|
+
}
|
|
76
|
+
if (image.type) {
|
|
77
|
+
tags.push(og('image:type', image.type))
|
|
78
|
+
}
|
|
79
|
+
if (image.width) {
|
|
80
|
+
tags.push(og('image:width', image.width))
|
|
81
|
+
}
|
|
82
|
+
if (image.height) {
|
|
83
|
+
tags.push(og('image:height', image.height))
|
|
84
|
+
}
|
|
85
|
+
if (image.alt) {
|
|
86
|
+
tags.push(og('image:alt', image.alt))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Videos
|
|
92
|
+
if (openGraph.videos) {
|
|
93
|
+
for (const video of openGraph.videos) {
|
|
94
|
+
tags.push(og('video', urlToString(video.url)))
|
|
95
|
+
if (video.secureUrl) {
|
|
96
|
+
tags.push(og('video:secure_url', urlToString(video.secureUrl)))
|
|
97
|
+
}
|
|
98
|
+
if (video.type) {
|
|
99
|
+
tags.push(og('video:type', video.type))
|
|
100
|
+
}
|
|
101
|
+
if (video.width) {
|
|
102
|
+
tags.push(og('video:width', video.width))
|
|
103
|
+
}
|
|
104
|
+
if (video.height) {
|
|
105
|
+
tags.push(og('video:height', video.height))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Audio
|
|
111
|
+
if (openGraph.audio) {
|
|
112
|
+
for (const audio of openGraph.audio) {
|
|
113
|
+
tags.push(og('audio', urlToString(audio.url)))
|
|
114
|
+
if (audio.secureUrl) {
|
|
115
|
+
tags.push(og('audio:secure_url', urlToString(audio.secureUrl)))
|
|
116
|
+
}
|
|
117
|
+
if (audio.type) {
|
|
118
|
+
tags.push(og('audio:type', audio.type))
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Article specific
|
|
124
|
+
if (openGraph.article) {
|
|
125
|
+
const article = openGraph.article
|
|
126
|
+
if (article.publishedTime) {
|
|
127
|
+
tags.push(og('article:published_time', article.publishedTime))
|
|
128
|
+
}
|
|
129
|
+
if (article.modifiedTime) {
|
|
130
|
+
tags.push(og('article:modified_time', article.modifiedTime))
|
|
131
|
+
}
|
|
132
|
+
if (article.expirationTime) {
|
|
133
|
+
tags.push(og('article:expiration_time', article.expirationTime))
|
|
134
|
+
}
|
|
135
|
+
if (article.section) {
|
|
136
|
+
tags.push(og('article:section', article.section))
|
|
137
|
+
}
|
|
138
|
+
if (article.authors) {
|
|
139
|
+
const authors = Array.isArray(article.authors) ? article.authors : [article.authors]
|
|
140
|
+
for (const author of authors) {
|
|
141
|
+
tags.push(og('article:author', author))
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (article.tags) {
|
|
145
|
+
for (const tag of article.tags) {
|
|
146
|
+
tags.push(og('article:tag', tag))
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Profile specific
|
|
152
|
+
if (openGraph.profile) {
|
|
153
|
+
const profile = openGraph.profile
|
|
154
|
+
if (profile.firstName) {
|
|
155
|
+
tags.push(og('profile:first_name', profile.firstName))
|
|
156
|
+
}
|
|
157
|
+
if (profile.lastName) {
|
|
158
|
+
tags.push(og('profile:last_name', profile.lastName))
|
|
159
|
+
}
|
|
160
|
+
if (profile.username) {
|
|
161
|
+
tags.push(og('profile:username', profile.username))
|
|
162
|
+
}
|
|
163
|
+
if (profile.gender) {
|
|
164
|
+
tags.push(og('profile:gender', profile.gender))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Book specific
|
|
169
|
+
if (openGraph.book) {
|
|
170
|
+
const book = openGraph.book
|
|
171
|
+
if (book.isbn) {
|
|
172
|
+
tags.push(og('book:isbn', book.isbn))
|
|
173
|
+
}
|
|
174
|
+
if (book.releaseDate) {
|
|
175
|
+
tags.push(og('book:release_date', book.releaseDate))
|
|
176
|
+
}
|
|
177
|
+
if (book.authors) {
|
|
178
|
+
const authors = Array.isArray(book.authors) ? book.authors : [book.authors]
|
|
179
|
+
for (const author of authors) {
|
|
180
|
+
tags.push(og('book:author', author))
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (book.tags) {
|
|
184
|
+
for (const tag of book.tags) {
|
|
185
|
+
tags.push(og('book:tag', tag))
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return tags.join('\n')
|
|
191
|
+
}
|
package/src/seo/render/robots.ts
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu SEO - Robots.txt Rendering
|
|
3
|
-
*
|
|
4
|
-
* RobotsFile 객체를 텍스트 문자열로 변환
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { RobotsFile, RobotsRule } from '../types'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 값을 배열로 정규화
|
|
11
|
-
*/
|
|
12
|
-
function toArray<T>(value: T | T[] | undefined): T[] {
|
|
13
|
-
if (!value) return []
|
|
14
|
-
return Array.isArray(value) ? value : [value]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 단일 규칙을 텍스트로 변환
|
|
19
|
-
*/
|
|
20
|
-
function renderRule(rule: RobotsRule): string {
|
|
21
|
-
const lines: string[] = []
|
|
22
|
-
|
|
23
|
-
// User-agent
|
|
24
|
-
const userAgents = toArray(rule.userAgent)
|
|
25
|
-
if (userAgents.length === 0) {
|
|
26
|
-
userAgents.push('*')
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
for (const ua of userAgents) {
|
|
30
|
-
lines.push(`User-agent: ${ua}`)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Allow
|
|
34
|
-
const allows = toArray(rule.allow)
|
|
35
|
-
for (const allow of allows) {
|
|
36
|
-
lines.push(`Allow: ${allow}`)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Disallow
|
|
40
|
-
const disallows = toArray(rule.disallow)
|
|
41
|
-
for (const disallow of disallows) {
|
|
42
|
-
lines.push(`Disallow: ${disallow}`)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Crawl-delay
|
|
46
|
-
if (rule.crawlDelay !== undefined) {
|
|
47
|
-
lines.push(`Crawl-delay: ${rule.crawlDelay}`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return lines.join('\n')
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* RobotsFile 객체를 robots.txt 문자열로 렌더링
|
|
55
|
-
*
|
|
56
|
-
* @param robots - Robots 설정 객체
|
|
57
|
-
* @returns robots.txt 문자열
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```typescript
|
|
61
|
-
* const txt = renderRobots({
|
|
62
|
-
* rules: [
|
|
63
|
-
* { userAgent: '*', allow: '/', disallow: '/admin' },
|
|
64
|
-
* { userAgent: 'Googlebot', allow: '/' },
|
|
65
|
-
* ],
|
|
66
|
-
* sitemap: 'https://example.com/sitemap.xml',
|
|
67
|
-
* })
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
export function renderRobots(robots: RobotsFile): string {
|
|
71
|
-
const sections: string[] = []
|
|
72
|
-
|
|
73
|
-
// 규칙들
|
|
74
|
-
const rules = toArray(robots.rules)
|
|
75
|
-
for (const rule of rules) {
|
|
76
|
-
sections.push(renderRule(rule))
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Host (Yandex 전용)
|
|
80
|
-
if (robots.host) {
|
|
81
|
-
sections.push(`Host: ${robots.host}`)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Sitemap
|
|
85
|
-
const sitemaps = toArray(robots.sitemap)
|
|
86
|
-
for (const sitemap of sitemaps) {
|
|
87
|
-
sections.push(`Sitemap: ${sitemap}`)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return sections.join('\n\n')
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 기본 robots.txt 생성 (모든 크롤러 허용)
|
|
95
|
-
*/
|
|
96
|
-
export function createDefaultRobots(sitemapUrl?: string): RobotsFile {
|
|
97
|
-
return {
|
|
98
|
-
rules: {
|
|
99
|
-
userAgent: '*',
|
|
100
|
-
allow: '/',
|
|
101
|
-
},
|
|
102
|
-
sitemap: sitemapUrl,
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 개발 환경용 robots.txt 생성 (모든 크롤러 차단)
|
|
108
|
-
*/
|
|
109
|
-
export function createDevRobots(): RobotsFile {
|
|
110
|
-
return {
|
|
111
|
-
rules: {
|
|
112
|
-
userAgent: '*',
|
|
113
|
-
disallow: '/',
|
|
114
|
-
},
|
|
115
|
-
}
|
|
116
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu SEO - Robots.txt Rendering
|
|
3
|
+
*
|
|
4
|
+
* RobotsFile 객체를 텍스트 문자열로 변환
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RobotsFile, RobotsRule } from '../types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 값을 배열로 정규화
|
|
11
|
+
*/
|
|
12
|
+
function toArray<T>(value: T | T[] | undefined): T[] {
|
|
13
|
+
if (!value) return []
|
|
14
|
+
return Array.isArray(value) ? value : [value]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 단일 규칙을 텍스트로 변환
|
|
19
|
+
*/
|
|
20
|
+
function renderRule(rule: RobotsRule): string {
|
|
21
|
+
const lines: string[] = []
|
|
22
|
+
|
|
23
|
+
// User-agent
|
|
24
|
+
const userAgents = toArray(rule.userAgent)
|
|
25
|
+
if (userAgents.length === 0) {
|
|
26
|
+
userAgents.push('*')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const ua of userAgents) {
|
|
30
|
+
lines.push(`User-agent: ${ua}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Allow
|
|
34
|
+
const allows = toArray(rule.allow)
|
|
35
|
+
for (const allow of allows) {
|
|
36
|
+
lines.push(`Allow: ${allow}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Disallow
|
|
40
|
+
const disallows = toArray(rule.disallow)
|
|
41
|
+
for (const disallow of disallows) {
|
|
42
|
+
lines.push(`Disallow: ${disallow}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Crawl-delay
|
|
46
|
+
if (rule.crawlDelay !== undefined) {
|
|
47
|
+
lines.push(`Crawl-delay: ${rule.crawlDelay}`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return lines.join('\n')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* RobotsFile 객체를 robots.txt 문자열로 렌더링
|
|
55
|
+
*
|
|
56
|
+
* @param robots - Robots 설정 객체
|
|
57
|
+
* @returns robots.txt 문자열
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* const txt = renderRobots({
|
|
62
|
+
* rules: [
|
|
63
|
+
* { userAgent: '*', allow: '/', disallow: '/admin' },
|
|
64
|
+
* { userAgent: 'Googlebot', allow: '/' },
|
|
65
|
+
* ],
|
|
66
|
+
* sitemap: 'https://example.com/sitemap.xml',
|
|
67
|
+
* })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function renderRobots(robots: RobotsFile): string {
|
|
71
|
+
const sections: string[] = []
|
|
72
|
+
|
|
73
|
+
// 규칙들
|
|
74
|
+
const rules = toArray(robots.rules)
|
|
75
|
+
for (const rule of rules) {
|
|
76
|
+
sections.push(renderRule(rule))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Host (Yandex 전용)
|
|
80
|
+
if (robots.host) {
|
|
81
|
+
sections.push(`Host: ${robots.host}`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Sitemap
|
|
85
|
+
const sitemaps = toArray(robots.sitemap)
|
|
86
|
+
for (const sitemap of sitemaps) {
|
|
87
|
+
sections.push(`Sitemap: ${sitemap}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return sections.join('\n\n')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 기본 robots.txt 생성 (모든 크롤러 허용)
|
|
95
|
+
*/
|
|
96
|
+
export function createDefaultRobots(sitemapUrl?: string): RobotsFile {
|
|
97
|
+
return {
|
|
98
|
+
rules: {
|
|
99
|
+
userAgent: '*',
|
|
100
|
+
allow: '/',
|
|
101
|
+
},
|
|
102
|
+
sitemap: sitemapUrl,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 개발 환경용 robots.txt 생성 (모든 크롤러 차단)
|
|
108
|
+
*/
|
|
109
|
+
export function createDevRobots(): RobotsFile {
|
|
110
|
+
return {
|
|
111
|
+
rules: {
|
|
112
|
+
userAgent: '*',
|
|
113
|
+
disallow: '/',
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
}
|