@portosaur/core 0.6.3 → 0.9.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/configSchema.json +354 -0
- package/package.json +7 -3
- package/src/generators/docusaurusConfig.mjs +99 -64
- package/src/generators/generateFavicons.mjs +2 -1
- package/src/index.d.ts +7 -0
- package/src/index.mjs +19 -2
- package/src/utils/fs.mjs +9 -0
- package/src/utils/validate.mjs +103 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Portosaur Project Configuration",
|
|
4
|
+
"description": "Schema for config.yml — validated at build time by porto.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"site": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {
|
|
11
|
+
"url": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"default": "auto"
|
|
14
|
+
},
|
|
15
|
+
"path": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"default": "auto"
|
|
18
|
+
},
|
|
19
|
+
"title": {
|
|
20
|
+
"type": "string"
|
|
21
|
+
},
|
|
22
|
+
"favicon": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"default": ""
|
|
25
|
+
},
|
|
26
|
+
"tagline": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"default": "Short description about you, your passion, your goals etc."
|
|
29
|
+
},
|
|
30
|
+
"on_broken_anchors": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"default": "throw"
|
|
33
|
+
},
|
|
34
|
+
"on_broken_links": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"default": "throw"
|
|
37
|
+
},
|
|
38
|
+
"head_tags": {
|
|
39
|
+
"type": "array"
|
|
40
|
+
},
|
|
41
|
+
"social_card": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"default": ""
|
|
44
|
+
},
|
|
45
|
+
"robots_txt": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"additionalProperties": false,
|
|
48
|
+
"properties": {
|
|
49
|
+
"enable": {
|
|
50
|
+
"type": "boolean",
|
|
51
|
+
"default": true
|
|
52
|
+
},
|
|
53
|
+
"rules": {
|
|
54
|
+
"type": "array"
|
|
55
|
+
},
|
|
56
|
+
"custom_lines": {
|
|
57
|
+
"type": "array"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"rss": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"additionalProperties": false,
|
|
64
|
+
"properties": {
|
|
65
|
+
"enable": {
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"default": true
|
|
68
|
+
},
|
|
69
|
+
"copyright": {
|
|
70
|
+
"type": "string"
|
|
71
|
+
},
|
|
72
|
+
"desc": {
|
|
73
|
+
"type": "string"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"custom": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"additionalProperties": true
|
|
82
|
+
},
|
|
83
|
+
"theme": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"additionalProperties": false,
|
|
86
|
+
"properties": {
|
|
87
|
+
"appearance": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"additionalProperties": false,
|
|
90
|
+
"properties": {
|
|
91
|
+
"default_mode": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"default": "dark"
|
|
94
|
+
},
|
|
95
|
+
"show_theme_switch": {
|
|
96
|
+
"type": "boolean",
|
|
97
|
+
"default": true
|
|
98
|
+
},
|
|
99
|
+
"disable_project_link": {
|
|
100
|
+
"type": "boolean",
|
|
101
|
+
"default": false
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"navigation": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"additionalProperties": false,
|
|
108
|
+
"properties": {
|
|
109
|
+
"hide_navbar_on_scroll": {
|
|
110
|
+
"type": "boolean",
|
|
111
|
+
"default": true
|
|
112
|
+
},
|
|
113
|
+
"collapsable_sidebar": {
|
|
114
|
+
"type": "boolean",
|
|
115
|
+
"default": true
|
|
116
|
+
},
|
|
117
|
+
"breadcrumbs": {
|
|
118
|
+
"type": "boolean",
|
|
119
|
+
"default": true
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"markdown": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"additionalProperties": false,
|
|
126
|
+
"properties": {
|
|
127
|
+
"mermaid": {
|
|
128
|
+
"type": "boolean",
|
|
129
|
+
"default": true
|
|
130
|
+
},
|
|
131
|
+
"render_emoji_shortcodes": {
|
|
132
|
+
"type": "boolean",
|
|
133
|
+
"default": true
|
|
134
|
+
},
|
|
135
|
+
"on_broken_links": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"default": "throw"
|
|
138
|
+
},
|
|
139
|
+
"on_broken_images": {
|
|
140
|
+
"type": "string",
|
|
141
|
+
"default": "throw"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"footer": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"additionalProperties": false,
|
|
148
|
+
"properties": {
|
|
149
|
+
"enable": {
|
|
150
|
+
"type": "boolean",
|
|
151
|
+
"default": true
|
|
152
|
+
},
|
|
153
|
+
"message": {
|
|
154
|
+
"type": "string"
|
|
155
|
+
},
|
|
156
|
+
"disable_project_link": {
|
|
157
|
+
"type": "boolean",
|
|
158
|
+
"default": false
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"home_page": {
|
|
165
|
+
"type": "object",
|
|
166
|
+
"additionalProperties": false,
|
|
167
|
+
"properties": {
|
|
168
|
+
"hero": {
|
|
169
|
+
"type": "object",
|
|
170
|
+
"additionalProperties": false,
|
|
171
|
+
"properties": {
|
|
172
|
+
"title": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"default": "Your Name"
|
|
175
|
+
},
|
|
176
|
+
"profile_pic": {
|
|
177
|
+
"type": "string",
|
|
178
|
+
"default": ""
|
|
179
|
+
},
|
|
180
|
+
"desc": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"default": "Short description about you, your passion, your goals etc."
|
|
183
|
+
},
|
|
184
|
+
"intro": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"default": "Hello there, I'm"
|
|
187
|
+
},
|
|
188
|
+
"subtitle": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"default": "I am a"
|
|
191
|
+
},
|
|
192
|
+
"profession": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"default": "Your Profession"
|
|
195
|
+
},
|
|
196
|
+
"social": {
|
|
197
|
+
"type": "array"
|
|
198
|
+
},
|
|
199
|
+
"learn_more_button_txt": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"default": "Learn More"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"about": {
|
|
206
|
+
"type": "object",
|
|
207
|
+
"additionalProperties": false,
|
|
208
|
+
"properties": {
|
|
209
|
+
"enable": {
|
|
210
|
+
"type": "boolean",
|
|
211
|
+
"default": true
|
|
212
|
+
},
|
|
213
|
+
"heading": {
|
|
214
|
+
"type": "string",
|
|
215
|
+
"default": "About Me"
|
|
216
|
+
},
|
|
217
|
+
"image": {
|
|
218
|
+
"type": "string",
|
|
219
|
+
"default": ""
|
|
220
|
+
},
|
|
221
|
+
"bio": {
|
|
222
|
+
"type": "array"
|
|
223
|
+
},
|
|
224
|
+
"skills": {
|
|
225
|
+
"type": "array"
|
|
226
|
+
},
|
|
227
|
+
"skills_heading": {
|
|
228
|
+
"type": "string",
|
|
229
|
+
"default": "My Skills"
|
|
230
|
+
},
|
|
231
|
+
"resume": {
|
|
232
|
+
"type": "string",
|
|
233
|
+
"default": ""
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
"project_shelf": {
|
|
238
|
+
"type": "object",
|
|
239
|
+
"additionalProperties": false,
|
|
240
|
+
"properties": {
|
|
241
|
+
"enable": {
|
|
242
|
+
"type": "boolean",
|
|
243
|
+
"default": true
|
|
244
|
+
},
|
|
245
|
+
"heading": {
|
|
246
|
+
"type": "string",
|
|
247
|
+
"default": "My Projects"
|
|
248
|
+
},
|
|
249
|
+
"subheading": {
|
|
250
|
+
"type": "string",
|
|
251
|
+
"default": "A collection of all my works"
|
|
252
|
+
},
|
|
253
|
+
"autoplay": {
|
|
254
|
+
"type": "boolean",
|
|
255
|
+
"default": true
|
|
256
|
+
},
|
|
257
|
+
"projects": {
|
|
258
|
+
"type": "array"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
"experience": {
|
|
263
|
+
"type": "object",
|
|
264
|
+
"additionalProperties": false,
|
|
265
|
+
"properties": {
|
|
266
|
+
"enable": {
|
|
267
|
+
"type": "boolean",
|
|
268
|
+
"default": false
|
|
269
|
+
},
|
|
270
|
+
"heading": {
|
|
271
|
+
"type": "string",
|
|
272
|
+
"default": "Experience"
|
|
273
|
+
},
|
|
274
|
+
"subheading": {
|
|
275
|
+
"type": "string",
|
|
276
|
+
"default": "My professional journey"
|
|
277
|
+
},
|
|
278
|
+
"list": {
|
|
279
|
+
"type": "array"
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
"social": {
|
|
284
|
+
"type": "object",
|
|
285
|
+
"additionalProperties": false,
|
|
286
|
+
"properties": {
|
|
287
|
+
"enable": {
|
|
288
|
+
"type": "boolean",
|
|
289
|
+
"default": true
|
|
290
|
+
},
|
|
291
|
+
"heading": {
|
|
292
|
+
"type": "string",
|
|
293
|
+
"default": "Get In Touch"
|
|
294
|
+
},
|
|
295
|
+
"subheading": {
|
|
296
|
+
"type": "string",
|
|
297
|
+
"default": "Feel free to reach out"
|
|
298
|
+
},
|
|
299
|
+
"links": {
|
|
300
|
+
"type": "array"
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"tasks": {
|
|
307
|
+
"type": "object",
|
|
308
|
+
"additionalProperties": false,
|
|
309
|
+
"properties": {
|
|
310
|
+
"enable": {
|
|
311
|
+
"type": "boolean",
|
|
312
|
+
"default": false
|
|
313
|
+
},
|
|
314
|
+
"title": {
|
|
315
|
+
"type": "string",
|
|
316
|
+
"default": "Tasks"
|
|
317
|
+
},
|
|
318
|
+
"subtitle": {
|
|
319
|
+
"type": "string",
|
|
320
|
+
"default": "My current focus"
|
|
321
|
+
},
|
|
322
|
+
"list": {
|
|
323
|
+
"type": "array"
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
"tools": {
|
|
328
|
+
"type": "object",
|
|
329
|
+
"additionalProperties": false,
|
|
330
|
+
"properties": {
|
|
331
|
+
"link_shortener": {
|
|
332
|
+
"type": "object",
|
|
333
|
+
"additionalProperties": false,
|
|
334
|
+
"properties": {
|
|
335
|
+
"enable": {
|
|
336
|
+
"type": "boolean",
|
|
337
|
+
"default": false
|
|
338
|
+
},
|
|
339
|
+
"deploy_path": {
|
|
340
|
+
"type": "string",
|
|
341
|
+
"default": "/l"
|
|
342
|
+
},
|
|
343
|
+
"short_links": {
|
|
344
|
+
"type": "object",
|
|
345
|
+
"additionalProperties": true
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
"required": [],
|
|
353
|
+
"additionalProperties": false
|
|
354
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portosaur/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "The engine of portosaur that translates YAML configuration into Docusaurus structures.",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"author": "soymadip",
|
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
"url": "https://github.com/soymadip/portosaur"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
|
-
"src"
|
|
13
|
+
"src",
|
|
14
|
+
"configSchema.json"
|
|
14
15
|
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"prepack": "bun ../cli/bin/porto.mjs schema"
|
|
18
|
+
},
|
|
15
19
|
"type": "module",
|
|
16
20
|
"exports": {
|
|
17
21
|
".": {
|
|
@@ -27,7 +31,7 @@
|
|
|
27
31
|
},
|
|
28
32
|
"types": "./src/index.d.ts",
|
|
29
33
|
"dependencies": {
|
|
30
|
-
"@portosaur/logger": "^0.
|
|
34
|
+
"@portosaur/logger": "^0.9.1",
|
|
31
35
|
"favicons": "^7.2.0",
|
|
32
36
|
"js-yaml": "^4.1.1",
|
|
33
37
|
"rehype-katex": "7",
|
|
@@ -4,6 +4,7 @@ import { createRequire } from "module";
|
|
|
4
4
|
import { getGitDate } from "../utils/system.mjs";
|
|
5
5
|
import { porto } from "../app.mjs";
|
|
6
6
|
import { resolveVars, getNestedValue } from "../utils/config.mjs";
|
|
7
|
+
import { getPortoDotDir } from "../utils/fs.mjs";
|
|
7
8
|
import {
|
|
8
9
|
resolveSiteUrl,
|
|
9
10
|
resolveBasePath,
|
|
@@ -22,14 +23,17 @@ import rehypeKatex from "rehype-katex";
|
|
|
22
23
|
export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
23
24
|
const { portoPaths = {}, gitDate = null, env = process.env } = context;
|
|
24
25
|
|
|
26
|
+
const rawGet = (key, ...fallbacks) =>
|
|
27
|
+
getNestedValue(rawUserConfig, key, ...fallbacks);
|
|
28
|
+
|
|
25
29
|
const portoVersion = porto.version ?? "0.0.0";
|
|
26
30
|
const lastUpdated = gitDate ?? getGitDate(projectDir);
|
|
27
31
|
|
|
28
32
|
const staticDir = path.resolve(projectDir, "static");
|
|
29
33
|
const assetsDir = portoPaths.assets ?? "";
|
|
30
34
|
|
|
31
|
-
const siteUrl = resolveSiteUrl(
|
|
32
|
-
const sitePath = resolveBasePath(
|
|
35
|
+
const siteUrl = resolveSiteUrl(rawGet("site.url", "auto"), env);
|
|
36
|
+
const sitePath = resolveBasePath(rawGet("site.path", "auto"), env);
|
|
33
37
|
|
|
34
38
|
const resolveAsset = createStaticAssetResolver(
|
|
35
39
|
projectDir,
|
|
@@ -50,39 +54,44 @@ export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
|
50
54
|
isProd: env.NODE_ENV === "production",
|
|
51
55
|
isDev: env.NODE_ENV === "development",
|
|
52
56
|
nodeEnv: env.NODE_ENV ?? "development",
|
|
53
|
-
custom:
|
|
57
|
+
custom: rawGet("custom", {}),
|
|
54
58
|
});
|
|
55
59
|
|
|
56
60
|
const get = (key, ...fallbacks) =>
|
|
57
61
|
getNestedValue(userConfig, key, ...fallbacks);
|
|
58
62
|
|
|
59
|
-
const
|
|
63
|
+
const defaultTheme =
|
|
64
|
+
get("theme.appearance.default_mode", "dark") === "light" ? "light" : "dark";
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
const titleName = get("home_page.hero.title", "Your Name");
|
|
67
|
+
const siteName = get("site.title", titleName);
|
|
68
|
+
const siteFavicon = resolveAsset(
|
|
69
|
+
get("site.favicon", ""),
|
|
70
|
+
resolveAsset(get("home_page.hero.profile_pic", ""), "img/icon.png"),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const siteTagline = get(
|
|
74
|
+
"home_page.hero.desc",
|
|
75
|
+
"site.tagline",
|
|
76
|
+
"Short description about you, your passion, your goals etc.",
|
|
77
|
+
);
|
|
62
78
|
|
|
63
79
|
// Collect static directories: local site static/, theme assets/, and portosaur dot-dir.
|
|
64
80
|
const staticDirectories = [
|
|
65
81
|
"static",
|
|
66
82
|
assetsDir,
|
|
67
|
-
|
|
83
|
+
getPortoDotDir(projectDir),
|
|
68
84
|
].filter((dir) => dir && fs.existsSync(dir));
|
|
69
85
|
|
|
70
|
-
|
|
71
|
-
const disableSwitch = get("theme.appearance.disable_switch", false);
|
|
86
|
+
// ------- Configuration Setup -------
|
|
72
87
|
|
|
73
88
|
return {
|
|
74
89
|
projectName: siteName,
|
|
75
90
|
title: siteName,
|
|
76
|
-
tagline:
|
|
77
|
-
"site.tagline",
|
|
78
|
-
"Short description about you, your passion, your goals etc.",
|
|
79
|
-
),
|
|
91
|
+
tagline: siteTagline,
|
|
80
92
|
url: siteUrl,
|
|
81
93
|
baseUrl: sitePath,
|
|
82
|
-
favicon:
|
|
83
|
-
get("site.favicon", ""),
|
|
84
|
-
resolveAsset("favicon/favicon.ico", "img/icon.png"),
|
|
85
|
-
),
|
|
94
|
+
favicon: siteFavicon,
|
|
86
95
|
organizationName: siteName,
|
|
87
96
|
onBrokenAnchors: get("site.on_broken_anchors", "throw"),
|
|
88
97
|
onBrokenLinks: get("site.on_broken_links", "throw"),
|
|
@@ -90,10 +99,20 @@ export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
|
90
99
|
|
|
91
100
|
staticDirectories,
|
|
92
101
|
|
|
102
|
+
headTags: buildHeadTags([
|
|
103
|
+
...(context.extraHeadTags || []),
|
|
104
|
+
...get("site.head_tags", []),
|
|
105
|
+
]),
|
|
106
|
+
|
|
93
107
|
themeConfig: {
|
|
108
|
+
image: resolveAsset(get("site.social_card", "")) || undefined,
|
|
109
|
+
metadata: [
|
|
110
|
+
{ name: "generator", content: `Portosaur v${porto.version}` },
|
|
111
|
+
{ name: "theme-color", content: "var(--ifm-background-color)" },
|
|
112
|
+
],
|
|
94
113
|
colorMode: {
|
|
95
|
-
defaultMode:
|
|
96
|
-
disableSwitch,
|
|
114
|
+
defaultMode: defaultTheme,
|
|
115
|
+
disableSwitch: !get("theme.appearance.show_theme_switch", true),
|
|
97
116
|
respectPrefersColorScheme: false,
|
|
98
117
|
},
|
|
99
118
|
|
|
@@ -101,7 +120,7 @@ export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
|
101
120
|
title: siteName,
|
|
102
121
|
logo: {
|
|
103
122
|
alt: `${siteName} logo`,
|
|
104
|
-
src:
|
|
123
|
+
src: siteFavicon,
|
|
105
124
|
},
|
|
106
125
|
hideOnScroll: get("theme.navigation.hide_navbar_on_scroll", true),
|
|
107
126
|
items: [
|
|
@@ -161,14 +180,12 @@ export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
|
161
180
|
...(get("tasks.enable", false)
|
|
162
181
|
? [{ label: "Tasks", to: "/tasks" }]
|
|
163
182
|
: []),
|
|
164
|
-
...(!get("theme.appearance.
|
|
183
|
+
...(!get("theme.appearance.disable_project_link", false)
|
|
165
184
|
? [
|
|
166
185
|
{
|
|
167
186
|
label: `Portosaur v${portoVersion}`,
|
|
168
187
|
className: "_nav-portosaur-version",
|
|
169
|
-
href:
|
|
170
|
-
porto?.homepage ||
|
|
171
|
-
"https://github.com/soymadip/portosaur",
|
|
188
|
+
href: porto?.homepage ?? "#",
|
|
172
189
|
},
|
|
173
190
|
]
|
|
174
191
|
: []),
|
|
@@ -177,53 +194,45 @@ export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
|
177
194
|
],
|
|
178
195
|
},
|
|
179
196
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
`© ${new Date().getFullYear()} ${siteName}. Built with Portosaur.`,
|
|
185
|
-
),
|
|
197
|
+
docs: {
|
|
198
|
+
sidebar: {
|
|
199
|
+
hideable: get("theme.navigation.collapsable_sidebar", true),
|
|
200
|
+
},
|
|
186
201
|
},
|
|
187
|
-
},
|
|
188
202
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
203
|
+
tableOfContents: {
|
|
204
|
+
minHeadingLevel: 2,
|
|
205
|
+
maxHeadingLevel: 3,
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
markdown: {
|
|
209
|
+
mermaid: get("theme.markdown.mermaid", true),
|
|
210
|
+
emoji: get("theme.markdown.render_emoji_shortcodes", true),
|
|
211
|
+
on_broken_links: get("theme.markdown.on_broken_links", "throw"),
|
|
212
|
+
on_broken_images: get("theme.markdown.on_broken_images", "throw"),
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
...(get("theme.footer.enable", true)
|
|
216
|
+
? {
|
|
217
|
+
footer: {
|
|
218
|
+
copyright: get(
|
|
219
|
+
"theme.footer.message",
|
|
220
|
+
`© ${new Date().getFullYear()} ${titleName}.${
|
|
221
|
+
!get("theme.footer.disable_project_link", false)
|
|
222
|
+
? ` | Built with <a href="${porto?.homepage ?? "#"}" target="_blank" rel="noopener noreferrer">Portosaur.</a>`
|
|
223
|
+
: ""
|
|
224
|
+
}`,
|
|
225
|
+
),
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
: {}),
|
|
229
|
+
},
|
|
195
230
|
|
|
196
231
|
// ------- Custom Fields -------
|
|
197
232
|
|
|
198
233
|
customFields: {
|
|
199
234
|
portoVersion,
|
|
200
235
|
|
|
201
|
-
theme: {
|
|
202
|
-
markdown: {
|
|
203
|
-
mermaid: get("theme.markdown.mermaid", true),
|
|
204
|
-
on_broken_links: get("theme.markdown.on_broken_links", "throw"),
|
|
205
|
-
on_broken_images: get("theme.markdown.on_broken_images", "throw"),
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
navigation: {
|
|
209
|
-
collapsable_sidebar: get(
|
|
210
|
-
"theme.navigation.collapsable_sidebar",
|
|
211
|
-
true,
|
|
212
|
-
),
|
|
213
|
-
|
|
214
|
-
hide_navbar_on_scroll: get(
|
|
215
|
-
"theme.navigation.hide_navbar_on_scroll",
|
|
216
|
-
true,
|
|
217
|
-
),
|
|
218
|
-
},
|
|
219
|
-
|
|
220
|
-
appearance: {
|
|
221
|
-
dark_mode: get("theme.appearance.dark_mode", true),
|
|
222
|
-
disable_switch: get("theme.appearance.disable_switch", false),
|
|
223
|
-
disable_branding: get("theme.appearance.disable_branding", false),
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
|
|
227
236
|
heroSection: {
|
|
228
237
|
profilePic: resolveAsset(
|
|
229
238
|
get("home_page.hero.profile_pic", ""),
|
|
@@ -231,7 +240,7 @@ export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
|
231
240
|
),
|
|
232
241
|
|
|
233
242
|
intro: get("home_page.hero.intro", "Hello there, I'm"),
|
|
234
|
-
title:
|
|
243
|
+
title: titleName,
|
|
235
244
|
subtitle: get("home_page.hero.subtitle", "I am a"),
|
|
236
245
|
profession: get("home_page.hero.profession", "Your Profession"),
|
|
237
246
|
desc: get("home_page.hero.desc", "Welcome to my portfolio."),
|
|
@@ -290,6 +299,22 @@ export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
|
290
299
|
subtitle: get("tasks.subtitle", "My current focus"),
|
|
291
300
|
list: get("tasks.list", []),
|
|
292
301
|
},
|
|
302
|
+
|
|
303
|
+
toolsConfig: {
|
|
304
|
+
linkShortener: {
|
|
305
|
+
enable: get("tools.link_shortener.enable", false),
|
|
306
|
+
deployPath: get("tools.link_shortener.deploy_path", "/l"),
|
|
307
|
+
shortLinks: get("tools.link_shortener.short_links", {}),
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
// site.robots_txt is consumed in build.mjs, but we pass it through here
|
|
312
|
+
// so the schema generator discovers it without hardcoding.
|
|
313
|
+
robotsTxt: {
|
|
314
|
+
enable: rawGet("site.robots_txt.enable", true),
|
|
315
|
+
rules: rawGet("site.robots_txt.rules", []),
|
|
316
|
+
customLines: rawGet("site.robots_txt.custom_lines", []),
|
|
317
|
+
},
|
|
293
318
|
},
|
|
294
319
|
|
|
295
320
|
// ------- Presets -------
|
|
@@ -301,6 +326,7 @@ export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
|
301
326
|
docs: {
|
|
302
327
|
routeBasePath: "notes",
|
|
303
328
|
path: "notes",
|
|
329
|
+
breadcrumbs: get("theme.navigation.breadcrumbs", true),
|
|
304
330
|
sidebarPath: path.resolve(
|
|
305
331
|
portoPaths.theme ?? context.portoRoot ?? "",
|
|
306
332
|
"config/sidebar.jsx",
|
|
@@ -313,6 +339,14 @@ export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
|
313
339
|
showReadingTime: false,
|
|
314
340
|
remarkPlugins: [remarkMath],
|
|
315
341
|
rehypePlugins: [rehypeKatex],
|
|
342
|
+
feedOptions: {
|
|
343
|
+
type: get("site.rss.enable", true) ? "all" : null,
|
|
344
|
+
copyright: get(
|
|
345
|
+
"site.rss.copyright",
|
|
346
|
+
`Copyright © ${new Date().getFullYear()} ${siteName}.`,
|
|
347
|
+
),
|
|
348
|
+
description: get("site.rss.desc", siteTagline),
|
|
349
|
+
},
|
|
316
350
|
},
|
|
317
351
|
theme: {
|
|
318
352
|
customCss: path.resolve(
|
|
@@ -331,9 +365,10 @@ export function buildDocuConfig(rawUserConfig, projectDir, context = {}) {
|
|
|
331
365
|
(() => {
|
|
332
366
|
const require = createRequire(import.meta.url);
|
|
333
367
|
return require.resolve("@easyops-cn/docusaurus-search-local", {
|
|
334
|
-
paths: [portoPaths.theme ?? context.portoRoot ?? ""],
|
|
368
|
+
paths: [projectDir, portoPaths.theme ?? context.portoRoot ?? ""],
|
|
335
369
|
});
|
|
336
370
|
})(),
|
|
371
|
+
|
|
337
372
|
{
|
|
338
373
|
hashed: true,
|
|
339
374
|
indexDocs: true,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { getPortoDotDir } from "../utils/fs.mjs";
|
|
3
4
|
import { favicons } from "favicons";
|
|
4
5
|
import { downloadImage } from "../utils/imageDownloader.mjs";
|
|
5
6
|
import { reshapeImage } from "../utils/imageProcessor.mjs";
|
|
@@ -121,7 +122,7 @@ export async function generateFavicons(siteDir, options = {}) {
|
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
const cacheDir = path.join(siteDir, "
|
|
125
|
+
const cacheDir = path.join(getPortoDotDir(siteDir), "cache");
|
|
125
126
|
createDirectoryIfNotExists(cacheDir);
|
|
126
127
|
const reshapedImagePath = path.join(cacheDir, "profile_pic_reshaped.png");
|
|
127
128
|
const tempFiles = [];
|
package/src/index.d.ts
CHANGED
|
@@ -61,3 +61,10 @@ export function getNestedValue(
|
|
|
61
61
|
pathStr: string,
|
|
62
62
|
...fallbacks: string[]
|
|
63
63
|
): any;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validates the raw user config against the generated JSON Schema.
|
|
67
|
+
* Returns dot-notation paths for any unknown keys found.
|
|
68
|
+
* Freeform blocks (custom, tools.link_shortener.short_links) are skipped.
|
|
69
|
+
*/
|
|
70
|
+
export function validateUserConfig(rawConfig: Record<string, any>): string[];
|
package/src/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import yaml from "js-yaml";
|
|
4
|
+
import { validateUserConfig } from "./utils/validate.mjs";
|
|
4
5
|
|
|
5
|
-
export { mirrorSync, loadPkg } from "./utils/fs.mjs";
|
|
6
|
+
export { mirrorSync, loadPkg, getPortoDotDir } from "./utils/fs.mjs";
|
|
6
7
|
|
|
7
8
|
export {
|
|
8
9
|
deepMerge,
|
|
@@ -11,6 +12,7 @@ export {
|
|
|
11
12
|
useEnabled,
|
|
12
13
|
openInBrowser,
|
|
13
14
|
} from "./utils/system.mjs";
|
|
15
|
+
import pkg from "../package.json";
|
|
14
16
|
|
|
15
17
|
export * from "./app.mjs";
|
|
16
18
|
|
|
@@ -28,11 +30,26 @@ export {
|
|
|
28
30
|
} from "./utils/docusaurus.mjs";
|
|
29
31
|
|
|
30
32
|
export { resolveVars, getNestedValue } from "./utils/config.mjs";
|
|
33
|
+
export { validateUserConfig } from "./utils/validate.mjs";
|
|
31
34
|
|
|
32
35
|
export function loadUserConfig(projectDir) {
|
|
33
36
|
const configPath = path.resolve(projectDir, "config.yml");
|
|
37
|
+
|
|
34
38
|
if (!fs.existsSync(configPath)) {
|
|
35
39
|
throw new Error(`No config.yml found at ${configPath}`);
|
|
36
40
|
}
|
|
37
|
-
|
|
41
|
+
|
|
42
|
+
const rawConfig = yaml.load(fs.readFileSync(configPath, "utf8"));
|
|
43
|
+
|
|
44
|
+
// Validate for unknown keys and error early with a clear message.
|
|
45
|
+
const violations = validateUserConfig(rawConfig);
|
|
46
|
+
|
|
47
|
+
if (violations.length > 0) {
|
|
48
|
+
const list = violations.map((v) => ` - ${v}`).join("\n");
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Unknown key(s) in config:\n${list}\n\nCheck the config reference: ${pkg.homepage}/guide/config`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return rawConfig;
|
|
38
55
|
}
|
package/src/utils/fs.mjs
CHANGED
|
@@ -2,6 +2,15 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { text } from "../app.mjs";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Gets the standardized path to the hidden Portosaur data directory.
|
|
7
|
+
* @param {string} siteDir - The project root directory.
|
|
8
|
+
* @returns {string} The resolved path.
|
|
9
|
+
*/
|
|
10
|
+
export function getPortoDotDir(siteDir) {
|
|
11
|
+
return path.join(siteDir, ".docusaurus", ".portosaur");
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
/**
|
|
6
15
|
* Loads a package.json file from a directory.
|
|
7
16
|
* @param {string} dir - The directory to look in.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
// ------- Schema Loading -------
|
|
6
|
+
|
|
7
|
+
let _cachedSchema = null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Loads and caches the generated configSchema.json from the package root.
|
|
11
|
+
* The schema is committed to the repo and shipped with @portosaur/core.
|
|
12
|
+
*
|
|
13
|
+
* @returns {object} The parsed JSON Schema object.
|
|
14
|
+
*/
|
|
15
|
+
function loadSchema() {
|
|
16
|
+
if (_cachedSchema) {
|
|
17
|
+
return _cachedSchema;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const schemaPath = path.resolve(
|
|
21
|
+
fileURLToPath(new URL("../..", import.meta.url)),
|
|
22
|
+
"configSchema.json",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(schemaPath)) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Config schema not found at ${schemaPath}. Run \`porto schema\` to regenerate it.`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_cachedSchema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
|
|
32
|
+
return _cachedSchema;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ------- Validation Walker -------
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Recursively walks the user's raw config and collects dot-path strings for
|
|
39
|
+
* any key not present in the provided JSON Schema node.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} configNode - Current slice of the user's raw config.
|
|
42
|
+
* @param {object} schemaNode - Corresponding JSON Schema node.
|
|
43
|
+
* @param {string} prefix - Dot-notation prefix accumulated so far.
|
|
44
|
+
* @param {string[]} violations - Accumulated list of unknown key paths.
|
|
45
|
+
*/
|
|
46
|
+
function walk(configNode, schemaNode, prefix, violations) {
|
|
47
|
+
if (
|
|
48
|
+
!configNode ||
|
|
49
|
+
typeof configNode !== "object" ||
|
|
50
|
+
Array.isArray(configNode)
|
|
51
|
+
) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const schemaProperties = schemaNode?.properties ?? {};
|
|
56
|
+
const allowsAdditional = schemaNode?.additionalProperties !== false;
|
|
57
|
+
|
|
58
|
+
// Skip validation for freeform blocks (custom, short_links, etc.)
|
|
59
|
+
if (allowsAdditional) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const key of Object.keys(configNode)) {
|
|
64
|
+
const dotPath = prefix ? `${prefix}.${key}` : key;
|
|
65
|
+
|
|
66
|
+
if (!(key in schemaProperties)) {
|
|
67
|
+
violations.push(dotPath);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const childSchema = schemaProperties[key];
|
|
72
|
+
|
|
73
|
+
// Recurse into object nodes (skip arrays — no item schema)
|
|
74
|
+
if (childSchema?.type === "object") {
|
|
75
|
+
walk(configNode[key], childSchema, dotPath, violations);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ------- Public API -------
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validates the raw user config object against the generated JSON Schema.
|
|
84
|
+
* Returns a list of dot-notation paths for any unknown keys found.
|
|
85
|
+
*
|
|
86
|
+
* Unknown keys inside freeform blocks (custom, tools.link_shortener.short_links)
|
|
87
|
+
* are intentionally ignored — those blocks allow arbitrary content.
|
|
88
|
+
*
|
|
89
|
+
* @param {object} rawConfig - The parsed config.yml object.
|
|
90
|
+
* @returns {string[]} Array of unknown key paths, empty if config is valid.
|
|
91
|
+
*/
|
|
92
|
+
export function validateUserConfig(rawConfig) {
|
|
93
|
+
if (!rawConfig || typeof rawConfig !== "object") {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const schema = loadSchema();
|
|
98
|
+
const violations = [];
|
|
99
|
+
|
|
100
|
+
walk(rawConfig, schema, "", violations);
|
|
101
|
+
|
|
102
|
+
return violations;
|
|
103
|
+
}
|