@mandujs/core 0.13.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/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 +2 -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,143 +1,143 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu SEO - Open Graph Resolution
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
OpenGraph,
|
|
7
|
-
OpenGraphImage,
|
|
8
|
-
OpenGraphVideo,
|
|
9
|
-
OpenGraphAudio,
|
|
10
|
-
ResolvedOpenGraph,
|
|
11
|
-
} from '../types'
|
|
12
|
-
import { resolveUrl } from './url'
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* OG 이미지 배열로 정규화
|
|
16
|
-
*/
|
|
17
|
-
function resolveOgImages(
|
|
18
|
-
images: OpenGraph['images'],
|
|
19
|
-
metadataBase: URL | null
|
|
20
|
-
): OpenGraphImage[] | null {
|
|
21
|
-
if (!images) return null
|
|
22
|
-
|
|
23
|
-
const arr = Array.isArray(images) ? images : [images]
|
|
24
|
-
const resolved: OpenGraphImage[] = []
|
|
25
|
-
|
|
26
|
-
for (const image of arr) {
|
|
27
|
-
if (typeof image === 'string' || image instanceof URL) {
|
|
28
|
-
const url = resolveUrl(image, metadataBase)
|
|
29
|
-
if (url) {
|
|
30
|
-
resolved.push({ url: url.href })
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
const url = resolveUrl(image.url, metadataBase)
|
|
34
|
-
if (url) {
|
|
35
|
-
resolved.push({
|
|
36
|
-
...image,
|
|
37
|
-
url: url.href,
|
|
38
|
-
secureUrl: image.secureUrl
|
|
39
|
-
? resolveUrl(image.secureUrl, metadataBase)?.href
|
|
40
|
-
: undefined,
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return resolved.length > 0 ? resolved : null
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* OG 비디오 배열로 정규화
|
|
51
|
-
*/
|
|
52
|
-
function resolveOgVideos(
|
|
53
|
-
videos: OpenGraph['videos'],
|
|
54
|
-
metadataBase: URL | null
|
|
55
|
-
): OpenGraphVideo[] | null {
|
|
56
|
-
if (!videos) return null
|
|
57
|
-
|
|
58
|
-
const arr = Array.isArray(videos) ? videos : [videos]
|
|
59
|
-
const resolved: OpenGraphVideo[] = []
|
|
60
|
-
|
|
61
|
-
for (const video of arr) {
|
|
62
|
-
if (typeof video === 'string' || video instanceof URL) {
|
|
63
|
-
const url = resolveUrl(video, metadataBase)
|
|
64
|
-
if (url) {
|
|
65
|
-
resolved.push({ url: url.href })
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
const url = resolveUrl(video.url, metadataBase)
|
|
69
|
-
if (url) {
|
|
70
|
-
resolved.push({
|
|
71
|
-
...video,
|
|
72
|
-
url: url.href,
|
|
73
|
-
secureUrl: video.secureUrl
|
|
74
|
-
? resolveUrl(video.secureUrl, metadataBase)?.href
|
|
75
|
-
: undefined,
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return resolved.length > 0 ? resolved : null
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* OG 오디오 배열로 정규화
|
|
86
|
-
*/
|
|
87
|
-
function resolveOgAudio(
|
|
88
|
-
audio: OpenGraph['audio'],
|
|
89
|
-
metadataBase: URL | null
|
|
90
|
-
): OpenGraphAudio[] | null {
|
|
91
|
-
if (!audio) return null
|
|
92
|
-
|
|
93
|
-
const arr = Array.isArray(audio) ? audio : [audio]
|
|
94
|
-
const resolved: OpenGraphAudio[] = []
|
|
95
|
-
|
|
96
|
-
for (const item of arr) {
|
|
97
|
-
if (typeof item === 'string' || item instanceof URL) {
|
|
98
|
-
const url = resolveUrl(item, metadataBase)
|
|
99
|
-
if (url) {
|
|
100
|
-
resolved.push({ url: url.href })
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
const url = resolveUrl(item.url, metadataBase)
|
|
104
|
-
if (url) {
|
|
105
|
-
resolved.push({
|
|
106
|
-
...item,
|
|
107
|
-
url: url.href,
|
|
108
|
-
secureUrl: item.secureUrl
|
|
109
|
-
? resolveUrl(item.secureUrl, metadataBase)?.href
|
|
110
|
-
: undefined,
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return resolved.length > 0 ? resolved : null
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Open Graph 메타데이터 해석
|
|
121
|
-
*/
|
|
122
|
-
export function resolveOpenGraph(
|
|
123
|
-
openGraph: OpenGraph | null | undefined,
|
|
124
|
-
metadataBase: URL | null
|
|
125
|
-
): ResolvedOpenGraph | null {
|
|
126
|
-
if (!openGraph) return null
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
type: openGraph.type || 'website',
|
|
130
|
-
url: resolveUrl(openGraph.url, metadataBase),
|
|
131
|
-
title: openGraph.title || null,
|
|
132
|
-
description: openGraph.description || null,
|
|
133
|
-
siteName: openGraph.siteName || null,
|
|
134
|
-
locale: openGraph.locale || null,
|
|
135
|
-
images: resolveOgImages(openGraph.images, metadataBase),
|
|
136
|
-
videos: resolveOgVideos(openGraph.videos, metadataBase),
|
|
137
|
-
audio: resolveOgAudio(openGraph.audio, metadataBase),
|
|
138
|
-
determiner: openGraph.determiner || null,
|
|
139
|
-
article: openGraph.article || null,
|
|
140
|
-
profile: openGraph.profile || null,
|
|
141
|
-
book: openGraph.book || null,
|
|
142
|
-
}
|
|
143
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu SEO - Open Graph Resolution
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
OpenGraph,
|
|
7
|
+
OpenGraphImage,
|
|
8
|
+
OpenGraphVideo,
|
|
9
|
+
OpenGraphAudio,
|
|
10
|
+
ResolvedOpenGraph,
|
|
11
|
+
} from '../types'
|
|
12
|
+
import { resolveUrl } from './url'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* OG 이미지 배열로 정규화
|
|
16
|
+
*/
|
|
17
|
+
function resolveOgImages(
|
|
18
|
+
images: OpenGraph['images'],
|
|
19
|
+
metadataBase: URL | null
|
|
20
|
+
): OpenGraphImage[] | null {
|
|
21
|
+
if (!images) return null
|
|
22
|
+
|
|
23
|
+
const arr = Array.isArray(images) ? images : [images]
|
|
24
|
+
const resolved: OpenGraphImage[] = []
|
|
25
|
+
|
|
26
|
+
for (const image of arr) {
|
|
27
|
+
if (typeof image === 'string' || image instanceof URL) {
|
|
28
|
+
const url = resolveUrl(image, metadataBase)
|
|
29
|
+
if (url) {
|
|
30
|
+
resolved.push({ url: url.href })
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
const url = resolveUrl(image.url, metadataBase)
|
|
34
|
+
if (url) {
|
|
35
|
+
resolved.push({
|
|
36
|
+
...image,
|
|
37
|
+
url: url.href,
|
|
38
|
+
secureUrl: image.secureUrl
|
|
39
|
+
? resolveUrl(image.secureUrl, metadataBase)?.href
|
|
40
|
+
: undefined,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return resolved.length > 0 ? resolved : null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* OG 비디오 배열로 정규화
|
|
51
|
+
*/
|
|
52
|
+
function resolveOgVideos(
|
|
53
|
+
videos: OpenGraph['videos'],
|
|
54
|
+
metadataBase: URL | null
|
|
55
|
+
): OpenGraphVideo[] | null {
|
|
56
|
+
if (!videos) return null
|
|
57
|
+
|
|
58
|
+
const arr = Array.isArray(videos) ? videos : [videos]
|
|
59
|
+
const resolved: OpenGraphVideo[] = []
|
|
60
|
+
|
|
61
|
+
for (const video of arr) {
|
|
62
|
+
if (typeof video === 'string' || video instanceof URL) {
|
|
63
|
+
const url = resolveUrl(video, metadataBase)
|
|
64
|
+
if (url) {
|
|
65
|
+
resolved.push({ url: url.href })
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
const url = resolveUrl(video.url, metadataBase)
|
|
69
|
+
if (url) {
|
|
70
|
+
resolved.push({
|
|
71
|
+
...video,
|
|
72
|
+
url: url.href,
|
|
73
|
+
secureUrl: video.secureUrl
|
|
74
|
+
? resolveUrl(video.secureUrl, metadataBase)?.href
|
|
75
|
+
: undefined,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return resolved.length > 0 ? resolved : null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* OG 오디오 배열로 정규화
|
|
86
|
+
*/
|
|
87
|
+
function resolveOgAudio(
|
|
88
|
+
audio: OpenGraph['audio'],
|
|
89
|
+
metadataBase: URL | null
|
|
90
|
+
): OpenGraphAudio[] | null {
|
|
91
|
+
if (!audio) return null
|
|
92
|
+
|
|
93
|
+
const arr = Array.isArray(audio) ? audio : [audio]
|
|
94
|
+
const resolved: OpenGraphAudio[] = []
|
|
95
|
+
|
|
96
|
+
for (const item of arr) {
|
|
97
|
+
if (typeof item === 'string' || item instanceof URL) {
|
|
98
|
+
const url = resolveUrl(item, metadataBase)
|
|
99
|
+
if (url) {
|
|
100
|
+
resolved.push({ url: url.href })
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
const url = resolveUrl(item.url, metadataBase)
|
|
104
|
+
if (url) {
|
|
105
|
+
resolved.push({
|
|
106
|
+
...item,
|
|
107
|
+
url: url.href,
|
|
108
|
+
secureUrl: item.secureUrl
|
|
109
|
+
? resolveUrl(item.secureUrl, metadataBase)?.href
|
|
110
|
+
: undefined,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return resolved.length > 0 ? resolved : null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Open Graph 메타데이터 해석
|
|
121
|
+
*/
|
|
122
|
+
export function resolveOpenGraph(
|
|
123
|
+
openGraph: OpenGraph | null | undefined,
|
|
124
|
+
metadataBase: URL | null
|
|
125
|
+
): ResolvedOpenGraph | null {
|
|
126
|
+
if (!openGraph) return null
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
type: openGraph.type || 'website',
|
|
130
|
+
url: resolveUrl(openGraph.url, metadataBase),
|
|
131
|
+
title: openGraph.title || null,
|
|
132
|
+
description: openGraph.description || null,
|
|
133
|
+
siteName: openGraph.siteName || null,
|
|
134
|
+
locale: openGraph.locale || null,
|
|
135
|
+
images: resolveOgImages(openGraph.images, metadataBase),
|
|
136
|
+
videos: resolveOgVideos(openGraph.videos, metadataBase),
|
|
137
|
+
audio: resolveOgAudio(openGraph.audio, metadataBase),
|
|
138
|
+
determiner: openGraph.determiner || null,
|
|
139
|
+
article: openGraph.article || null,
|
|
140
|
+
profile: openGraph.profile || null,
|
|
141
|
+
book: openGraph.book || null,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu SEO - Robots Resolution
|
|
3
|
-
*
|
|
4
|
-
* robots 메타 태그 값 생성
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Robots, ResolvedRobots } from '../types'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Robots 객체를 문자열로 변환
|
|
11
|
-
*/
|
|
12
|
-
function robotsToString(robots: Robots): string {
|
|
13
|
-
const values: string[] = []
|
|
14
|
-
|
|
15
|
-
// Boolean directives
|
|
16
|
-
if (robots.index === true) values.push('index')
|
|
17
|
-
if (robots.index === false) values.push('noindex')
|
|
18
|
-
if (robots.follow === true) values.push('follow')
|
|
19
|
-
if (robots.follow === false) values.push('nofollow')
|
|
20
|
-
if (robots.noarchive) values.push('noarchive')
|
|
21
|
-
if (robots.nosnippet) values.push('nosnippet')
|
|
22
|
-
if (robots.noimageindex) values.push('noimageindex')
|
|
23
|
-
if (robots.nocache) values.push('nocache')
|
|
24
|
-
if (robots.notranslate) values.push('notranslate')
|
|
25
|
-
|
|
26
|
-
// Numeric directives
|
|
27
|
-
if (robots['max-snippet'] !== undefined) {
|
|
28
|
-
values.push(`max-snippet:${robots['max-snippet']}`)
|
|
29
|
-
}
|
|
30
|
-
if (robots['max-image-preview']) {
|
|
31
|
-
values.push(`max-image-preview:${robots['max-image-preview']}`)
|
|
32
|
-
}
|
|
33
|
-
if (robots['max-video-preview'] !== undefined) {
|
|
34
|
-
values.push(`max-video-preview:${robots['max-video-preview']}`)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return values.join(', ')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Robots 설정 해석
|
|
42
|
-
*/
|
|
43
|
-
export function resolveRobots(
|
|
44
|
-
robots: string | Robots | null | undefined
|
|
45
|
-
): ResolvedRobots | null {
|
|
46
|
-
if (!robots) return null
|
|
47
|
-
|
|
48
|
-
// 문자열 그대로 사용
|
|
49
|
-
if (typeof robots === 'string') {
|
|
50
|
-
return {
|
|
51
|
-
basic: robots,
|
|
52
|
-
googleBot: null,
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 객체에서 문자열 생성
|
|
57
|
-
const basicString = robotsToString(robots)
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
basic: basicString || null,
|
|
61
|
-
googleBot: null, // 향후 googleBot 별도 설정 지원
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 기본 robots 값 생성 (index, follow)
|
|
67
|
-
*/
|
|
68
|
-
export function getDefaultRobots(): ResolvedRobots {
|
|
69
|
-
return {
|
|
70
|
-
basic: 'index, follow',
|
|
71
|
-
googleBot: null,
|
|
72
|
-
}
|
|
73
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu SEO - Robots Resolution
|
|
3
|
+
*
|
|
4
|
+
* robots 메타 태그 값 생성
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Robots, ResolvedRobots } from '../types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Robots 객체를 문자열로 변환
|
|
11
|
+
*/
|
|
12
|
+
function robotsToString(robots: Robots): string {
|
|
13
|
+
const values: string[] = []
|
|
14
|
+
|
|
15
|
+
// Boolean directives
|
|
16
|
+
if (robots.index === true) values.push('index')
|
|
17
|
+
if (robots.index === false) values.push('noindex')
|
|
18
|
+
if (robots.follow === true) values.push('follow')
|
|
19
|
+
if (robots.follow === false) values.push('nofollow')
|
|
20
|
+
if (robots.noarchive) values.push('noarchive')
|
|
21
|
+
if (robots.nosnippet) values.push('nosnippet')
|
|
22
|
+
if (robots.noimageindex) values.push('noimageindex')
|
|
23
|
+
if (robots.nocache) values.push('nocache')
|
|
24
|
+
if (robots.notranslate) values.push('notranslate')
|
|
25
|
+
|
|
26
|
+
// Numeric directives
|
|
27
|
+
if (robots['max-snippet'] !== undefined) {
|
|
28
|
+
values.push(`max-snippet:${robots['max-snippet']}`)
|
|
29
|
+
}
|
|
30
|
+
if (robots['max-image-preview']) {
|
|
31
|
+
values.push(`max-image-preview:${robots['max-image-preview']}`)
|
|
32
|
+
}
|
|
33
|
+
if (robots['max-video-preview'] !== undefined) {
|
|
34
|
+
values.push(`max-video-preview:${robots['max-video-preview']}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return values.join(', ')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Robots 설정 해석
|
|
42
|
+
*/
|
|
43
|
+
export function resolveRobots(
|
|
44
|
+
robots: string | Robots | null | undefined
|
|
45
|
+
): ResolvedRobots | null {
|
|
46
|
+
if (!robots) return null
|
|
47
|
+
|
|
48
|
+
// 문자열 그대로 사용
|
|
49
|
+
if (typeof robots === 'string') {
|
|
50
|
+
return {
|
|
51
|
+
basic: robots,
|
|
52
|
+
googleBot: null,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 객체에서 문자열 생성
|
|
57
|
+
const basicString = robotsToString(robots)
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
basic: basicString || null,
|
|
61
|
+
googleBot: null, // 향후 googleBot 별도 설정 지원
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 기본 robots 값 생성 (index, follow)
|
|
67
|
+
*/
|
|
68
|
+
export function getDefaultRobots(): ResolvedRobots {
|
|
69
|
+
return {
|
|
70
|
+
basic: 'index, follow',
|
|
71
|
+
googleBot: null,
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/seo/resolve/title.ts
CHANGED
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu SEO - Title Resolution
|
|
3
|
-
*
|
|
4
|
-
* title.template 패턴 처리
|
|
5
|
-
* Layout의 template이 Page의 title에 적용됨
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Title, TemplateString, AbsoluteTemplateString, AbsoluteString } from '../types'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Title이 TemplateString 객체인지 확인 (default 필드 있음)
|
|
12
|
-
*/
|
|
13
|
-
function isTemplateString(title: Title): title is TemplateString {
|
|
14
|
-
return typeof title === 'object' && title !== null && 'default' in title
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Title이 AbsoluteString 객체인지 확인 (absolute 필드만 있음, default 없음)
|
|
19
|
-
*/
|
|
20
|
-
function isAbsoluteString(title: Title): title is AbsoluteString {
|
|
21
|
-
return typeof title === 'object' && title !== null && 'absolute' in title && !('default' in title)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Title을 AbsoluteTemplateString으로 변환
|
|
26
|
-
*/
|
|
27
|
-
export function resolveTitle(
|
|
28
|
-
title: Title | null | undefined,
|
|
29
|
-
parentTemplate: string | null
|
|
30
|
-
): AbsoluteTemplateString | null {
|
|
31
|
-
if (title === null || title === undefined) {
|
|
32
|
-
return null
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// String title
|
|
36
|
-
if (typeof title === 'string') {
|
|
37
|
-
// 부모 템플릿 적용
|
|
38
|
-
const absolute = parentTemplate
|
|
39
|
-
? parentTemplate.replace('%s', title)
|
|
40
|
-
: title
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
absolute,
|
|
44
|
-
template: null,
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// AbsoluteString object ({ absolute: string }) - 템플릿 무시
|
|
49
|
-
if (isAbsoluteString(title)) {
|
|
50
|
-
return {
|
|
51
|
-
absolute: title.absolute,
|
|
52
|
-
template: title.template || null,
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// TemplateString object ({ default: string, template?: string })
|
|
57
|
-
if (isTemplateString(title)) {
|
|
58
|
-
// default에 부모 템플릿 적용
|
|
59
|
-
const absolute = parentTemplate
|
|
60
|
-
? parentTemplate.replace('%s', title.default)
|
|
61
|
-
: title.default
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
absolute,
|
|
65
|
-
template: title.template || null,
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return null
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Layout 체인에서 title template 추출
|
|
74
|
-
*/
|
|
75
|
-
export function extractTitleTemplate(title: Title | null | undefined): string | null {
|
|
76
|
-
if (!title) return null
|
|
77
|
-
|
|
78
|
-
if (typeof title === 'string') {
|
|
79
|
-
return null
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (isTemplateString(title)) {
|
|
83
|
-
return title.template || null
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return null
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* title.absolute 값 추출 (렌더링용)
|
|
91
|
-
*/
|
|
92
|
-
export function getTitleString(resolved: AbsoluteTemplateString | null): string | null {
|
|
93
|
-
return resolved?.absolute || null
|
|
94
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu SEO - Title Resolution
|
|
3
|
+
*
|
|
4
|
+
* title.template 패턴 처리
|
|
5
|
+
* Layout의 template이 Page의 title에 적용됨
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Title, TemplateString, AbsoluteTemplateString, AbsoluteString } from '../types'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Title이 TemplateString 객체인지 확인 (default 필드 있음)
|
|
12
|
+
*/
|
|
13
|
+
function isTemplateString(title: Title): title is TemplateString {
|
|
14
|
+
return typeof title === 'object' && title !== null && 'default' in title
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Title이 AbsoluteString 객체인지 확인 (absolute 필드만 있음, default 없음)
|
|
19
|
+
*/
|
|
20
|
+
function isAbsoluteString(title: Title): title is AbsoluteString {
|
|
21
|
+
return typeof title === 'object' && title !== null && 'absolute' in title && !('default' in title)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Title을 AbsoluteTemplateString으로 변환
|
|
26
|
+
*/
|
|
27
|
+
export function resolveTitle(
|
|
28
|
+
title: Title | null | undefined,
|
|
29
|
+
parentTemplate: string | null
|
|
30
|
+
): AbsoluteTemplateString | null {
|
|
31
|
+
if (title === null || title === undefined) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// String title
|
|
36
|
+
if (typeof title === 'string') {
|
|
37
|
+
// 부모 템플릿 적용
|
|
38
|
+
const absolute = parentTemplate
|
|
39
|
+
? parentTemplate.replace('%s', title)
|
|
40
|
+
: title
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
absolute,
|
|
44
|
+
template: null,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// AbsoluteString object ({ absolute: string }) - 템플릿 무시
|
|
49
|
+
if (isAbsoluteString(title)) {
|
|
50
|
+
return {
|
|
51
|
+
absolute: title.absolute,
|
|
52
|
+
template: title.template || null,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// TemplateString object ({ default: string, template?: string })
|
|
57
|
+
if (isTemplateString(title)) {
|
|
58
|
+
// default에 부모 템플릿 적용
|
|
59
|
+
const absolute = parentTemplate
|
|
60
|
+
? parentTemplate.replace('%s', title.default)
|
|
61
|
+
: title.default
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
absolute,
|
|
65
|
+
template: title.template || null,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Layout 체인에서 title template 추출
|
|
74
|
+
*/
|
|
75
|
+
export function extractTitleTemplate(title: Title | null | undefined): string | null {
|
|
76
|
+
if (!title) return null
|
|
77
|
+
|
|
78
|
+
if (typeof title === 'string') {
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isTemplateString(title)) {
|
|
83
|
+
return title.template || null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* title.absolute 값 추출 (렌더링용)
|
|
91
|
+
*/
|
|
92
|
+
export function getTitleString(resolved: AbsoluteTemplateString | null): string | null {
|
|
93
|
+
return resolved?.absolute || null
|
|
94
|
+
}
|