@soubiran/vite 0.3.1 → 0.5.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/README.md +243 -0
- package/dist/index.d.mts +7 -0
- package/dist/{vite.config.mjs → index.mjs} +467 -447
- package/dist/types-DznJAci_.d.mts +36 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +1 -0
- package/dist/utils-DYsc-HAW.mjs +21 -0
- package/dist/utils.mjs +2 -0
- package/package.json +29 -25
- package/dist/src/utils.mjs +0 -3
- package/dist/utils-CgX7pSaU.mjs +0 -12
- package/dist/vite.config.d.mts +0 -79
- /package/dist/{src/utils.d.mts → utils.d.mts} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { n as toUrl, t as getUri } from "./utils-
|
|
1
|
+
import { a as vueIncludePatterns, i as markdownExtensionRE, n as toUrl, r as componentIncludePatterns, t as getUri } from "./utils-DYsc-HAW.mjs";
|
|
2
2
|
import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
4
3
|
import ui from "@nuxt/ui/vite";
|
|
5
4
|
import soubiranComposablesImports from "@soubiran/ui/imports";
|
|
6
5
|
import soubiranResolver from "@soubiran/ui/resolver";
|
|
6
|
+
import soubiranWrapperClasses from "@soubiran/ui/wrapper-classes";
|
|
7
7
|
import { unheadVueComposablesImports } from "@unhead/vue";
|
|
8
8
|
import vue from "@vitejs/plugin-vue";
|
|
9
9
|
import matter from "gray-matter";
|
|
@@ -11,8 +11,11 @@ import fonts from "unplugin-fonts/vite";
|
|
|
11
11
|
import icons from "unplugin-icons/vite";
|
|
12
12
|
import markdown from "unplugin-vue-markdown/vite";
|
|
13
13
|
import vueRouter from "unplugin-vue-router/vite";
|
|
14
|
-
import { defineConfig, mergeConfig } from "vite";
|
|
15
14
|
import { joinURL, withoutTrailingSlash } from "ufo";
|
|
15
|
+
import { Buffer } from "node:buffer";
|
|
16
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
17
|
+
import fs from "fs-extra";
|
|
18
|
+
import sharp from "sharp";
|
|
16
19
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
17
20
|
import { blurhashToDataUri } from "@unpic/placeholder";
|
|
18
21
|
import MarkdownItGitHubAlerts from "markdown-it-github-alerts";
|
|
@@ -20,15 +23,11 @@ import implicitFigures from "markdown-it-image-figures";
|
|
|
20
23
|
import linkAttributes from "markdown-it-link-attributes";
|
|
21
24
|
import { fromAsyncCodeToHtml } from "@shikijs/markdown-it/async";
|
|
22
25
|
import { codeToHtml } from "shiki";
|
|
23
|
-
import { Buffer } from "node:buffer";
|
|
24
|
-
import fs from "fs-extra";
|
|
25
|
-
import sharp from "sharp";
|
|
26
|
-
import { cwd } from "node:process";
|
|
27
26
|
import { cyan, dim, green, yellow } from "ansis";
|
|
27
|
+
import { cwd } from "node:process";
|
|
28
28
|
import { createHash } from "node:crypto";
|
|
29
29
|
import { SitemapStream } from "sitemap";
|
|
30
|
-
|
|
31
|
-
//#region src/assert.ts
|
|
30
|
+
//#region src/markdown/assert.ts
|
|
32
31
|
function createAssert(customAssert) {
|
|
33
32
|
return (id, frontmatter) => {
|
|
34
33
|
if (frontmatter.description) {
|
|
@@ -38,9 +37,8 @@ function createAssert(customAssert) {
|
|
|
38
37
|
customAssert?.(id, frontmatter);
|
|
39
38
|
};
|
|
40
39
|
}
|
|
41
|
-
|
|
42
40
|
//#endregion
|
|
43
|
-
//#region src/canonical.ts
|
|
41
|
+
//#region src/markdown/canonical.ts
|
|
44
42
|
function getCanonicalUrl(id, hostname) {
|
|
45
43
|
return joinURL(toUrl(hostname), getUri(id));
|
|
46
44
|
}
|
|
@@ -57,13 +55,232 @@ function canonical(id, frontmatter, hostname) {
|
|
|
57
55
|
href: url
|
|
58
56
|
});
|
|
59
57
|
}
|
|
60
|
-
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/domain/promise.ts
|
|
60
|
+
const promises = [];
|
|
61
|
+
async function resolveAll() {
|
|
62
|
+
await Promise.all(promises);
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/markdown/og.ts
|
|
66
|
+
const ogSVG = fs.readFileSync(new URL("./og-template.svg", import.meta.url), "utf-8");
|
|
67
|
+
const titleBreakRE = /(.{0,30})(?:\s|$)/g;
|
|
68
|
+
const templateTokenRE = /\{\{([^}]+)\}\}/g;
|
|
69
|
+
const titleSuffixRE = /\s-\s.*$/;
|
|
70
|
+
async function generate(title, hostname, output) {
|
|
71
|
+
if (fs.existsSync(output)) return;
|
|
72
|
+
await fs.mkdir(dirname(output), { recursive: true });
|
|
73
|
+
const lines = title.trim().split(titleBreakRE).filter(Boolean);
|
|
74
|
+
const data = {
|
|
75
|
+
line1: lines[0],
|
|
76
|
+
line2: lines[1],
|
|
77
|
+
line3: lines[2],
|
|
78
|
+
headline: "",
|
|
79
|
+
hostname
|
|
80
|
+
};
|
|
81
|
+
const svg = ogSVG.replace(templateTokenRE, (_, name) => data[name] || "");
|
|
82
|
+
console.log(`Generating ${output}`);
|
|
83
|
+
try {
|
|
84
|
+
await sharp(Buffer.from(svg)).resize(1200 * 1.1, 630 * 1.1).png().toFile(output);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error("Failed to generate og image", e);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function og(id, frontmatter, hostname) {
|
|
90
|
+
(() => {
|
|
91
|
+
const path = `og/${basename(id, ".md")}.png`;
|
|
92
|
+
promises.push(generate(frontmatter.title.replace(titleSuffixRE, "").trim(), hostname, `public/${path}`));
|
|
93
|
+
frontmatter.image = `https://${hostname}/${path}`;
|
|
94
|
+
})();
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/markdown/structured-data/constants.ts
|
|
98
|
+
const DEFAULT_PERSON = {
|
|
99
|
+
name: "Estéban Soubiran",
|
|
100
|
+
sameAs: [
|
|
101
|
+
"https://x.com/soubiran_",
|
|
102
|
+
"https://www.linkedin.com/in/esteban25",
|
|
103
|
+
"https://www.twitch.tv/barbapapazes",
|
|
104
|
+
"https://www.youtube.com/@barbapapazes",
|
|
105
|
+
"https://github.com/barbapapazes",
|
|
106
|
+
"https://soubiran.dev",
|
|
107
|
+
"https://esteban-soubiran.site",
|
|
108
|
+
"https://barbapapazes.dev"
|
|
109
|
+
]
|
|
110
|
+
};
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/markdown/structured-data/schemas/article.ts
|
|
113
|
+
/**
|
|
114
|
+
* @see https://developer.yoast.com/features/schema/pieces/article/
|
|
115
|
+
*/
|
|
116
|
+
function article(id, structuredData, properties, options) {
|
|
117
|
+
const { title, description } = properties;
|
|
118
|
+
return { data: {
|
|
119
|
+
"@type": "Article",
|
|
120
|
+
"@id": joinURL(toUrl(options.hostname), "#", "schema", "Article", getUri(id)),
|
|
121
|
+
"headline": title,
|
|
122
|
+
"description": description,
|
|
123
|
+
"isPartOf": { "@id": structuredData.webpage.data["@id"] },
|
|
124
|
+
"mainEntityOfPage": { "@id": structuredData.webpage.data["@id"] },
|
|
125
|
+
"datePublished": structuredData.webpage.data.datePublished ? structuredData.webpage.data.datePublished : void 0,
|
|
126
|
+
"author": { "@id": structuredData.person.data["@id"] },
|
|
127
|
+
"publisher": { "@id": structuredData.person.data["@id"] },
|
|
128
|
+
"inLanguage": structuredData.webpage.data.inLanguage
|
|
129
|
+
} };
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/markdown/structured-data/schemas/breadcrumb.ts
|
|
133
|
+
/**
|
|
134
|
+
* @see https://developer.yoast.com/features/schema/pieces/breadcrumb/
|
|
135
|
+
*/
|
|
136
|
+
function breadcrumb(id, items, options) {
|
|
137
|
+
return { data: {
|
|
138
|
+
"@type": "BreadcrumbList",
|
|
139
|
+
"@id": joinURL(toUrl(options.hostname), "#", "schema", "BreadcrumbList", getUri(id)),
|
|
140
|
+
"itemListElement": items.map((item, index) => ({
|
|
141
|
+
"@type": "ListItem",
|
|
142
|
+
"position": index + 1,
|
|
143
|
+
"name": item.title,
|
|
144
|
+
...item.type && item.url ? { item: {
|
|
145
|
+
"@type": item.type,
|
|
146
|
+
"@id": item.url
|
|
147
|
+
} } : {}
|
|
148
|
+
}))
|
|
149
|
+
} };
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/markdown/structured-data/schemas/person.ts
|
|
153
|
+
/**
|
|
154
|
+
* @see https://developer.yoast.com/features/schema/pieces/person/
|
|
155
|
+
*/
|
|
156
|
+
function person(properties, options) {
|
|
157
|
+
return { data: {
|
|
158
|
+
"@type": "Person",
|
|
159
|
+
"@id": joinURL(options.url, "#", "schema", "Person", "1"),
|
|
160
|
+
"name": properties.name,
|
|
161
|
+
"sameAs": properties.sameAs
|
|
162
|
+
} };
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/markdown/structured-data/schemas/webpage.ts
|
|
166
|
+
/**
|
|
167
|
+
* @see https://developer.yoast.com/features/schema/pieces/webpage/
|
|
168
|
+
*/
|
|
169
|
+
function webpage(id, structuredData, properties, options) {
|
|
170
|
+
const { title, description, datePublished, keywords } = properties;
|
|
171
|
+
const canonicalUrl = getCanonicalUrl(id, options.hostname);
|
|
172
|
+
const data = {
|
|
173
|
+
"@type": "WebPage",
|
|
174
|
+
"@id": canonicalUrl,
|
|
175
|
+
"url": canonicalUrl,
|
|
176
|
+
"name": title,
|
|
177
|
+
"description": description,
|
|
178
|
+
"isPartOf": { "@id": structuredData.website.data["@id"] },
|
|
179
|
+
"inLanguage": "en-US",
|
|
180
|
+
"potentialAction": [{
|
|
181
|
+
"@type": "ReadAction",
|
|
182
|
+
"target": [canonicalUrl]
|
|
183
|
+
}],
|
|
184
|
+
...datePublished ? { datePublished: datePublished.toISOString() } : {},
|
|
185
|
+
...keywords ? { keywords } : {}
|
|
186
|
+
};
|
|
187
|
+
return {
|
|
188
|
+
data,
|
|
189
|
+
setBreadcrumb(breadcrumbData) {
|
|
190
|
+
data.breadcrumb = { "@id": breadcrumbData.data["@id"] };
|
|
191
|
+
},
|
|
192
|
+
setCollection() {
|
|
193
|
+
data["@type"] = "CollectionPage";
|
|
194
|
+
delete data.potentialAction;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/markdown/structured-data/schemas/website.ts
|
|
200
|
+
/**
|
|
201
|
+
* @see https://developer.yoast.com/features/schema/pieces/website/
|
|
202
|
+
*/
|
|
203
|
+
function website(structuredData, options) {
|
|
204
|
+
return { data: {
|
|
205
|
+
"@type": "WebSite",
|
|
206
|
+
"@id": joinURL(options.url, "#", "schema", "WebSite", "1"),
|
|
207
|
+
"url": options.url,
|
|
208
|
+
"name": options.name,
|
|
209
|
+
"inLanguage": ["en-US"],
|
|
210
|
+
"publisher": { "@id": structuredData.person.data["@id"] }
|
|
211
|
+
} };
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/markdown/structured-data/index.ts
|
|
215
|
+
function structuredData(id, frontmatter, options) {
|
|
216
|
+
const { name, hostname, extractPage, getPageConfig } = options;
|
|
217
|
+
const graph = {
|
|
218
|
+
"@context": "https://schema.org",
|
|
219
|
+
"@graph": []
|
|
220
|
+
};
|
|
221
|
+
const structuredDataOptions = {
|
|
222
|
+
name,
|
|
223
|
+
hostname,
|
|
224
|
+
url: toUrl(hostname)
|
|
225
|
+
};
|
|
226
|
+
const personData = person(DEFAULT_PERSON, structuredDataOptions);
|
|
227
|
+
const websiteData = website({ person: personData }, structuredDataOptions);
|
|
228
|
+
const webpageData = webpage(id, { website: websiteData }, {
|
|
229
|
+
title: frontmatter.title,
|
|
230
|
+
description: frontmatter.description,
|
|
231
|
+
datePublished: frontmatter.date ? new Date(frontmatter.date) : void 0,
|
|
232
|
+
keywords: frontmatter.tags
|
|
233
|
+
}, structuredDataOptions);
|
|
234
|
+
const page = extractPage(id);
|
|
235
|
+
const pageConfig = getPageConfig?.(page, frontmatter);
|
|
236
|
+
if (pageConfig?.type === "article") {
|
|
237
|
+
const articleData = article(id, {
|
|
238
|
+
person: personData,
|
|
239
|
+
webpage: webpageData
|
|
240
|
+
}, {
|
|
241
|
+
title: frontmatter.title,
|
|
242
|
+
description: frontmatter.description
|
|
243
|
+
}, structuredDataOptions);
|
|
244
|
+
graph["@graph"].push(articleData.data);
|
|
245
|
+
if (pageConfig.breadcrumbItems) {
|
|
246
|
+
const breadcrumbData = breadcrumb(id, pageConfig.breadcrumbItems, structuredDataOptions);
|
|
247
|
+
graph["@graph"].push(breadcrumbData.data);
|
|
248
|
+
webpageData.setBreadcrumb(breadcrumbData);
|
|
249
|
+
}
|
|
250
|
+
} else if (pageConfig?.type === "collection") webpageData.setCollection();
|
|
251
|
+
graph["@graph"].push(personData.data, websiteData.data, webpageData.data);
|
|
252
|
+
frontmatter.script ??= [];
|
|
253
|
+
frontmatter.script.push({
|
|
254
|
+
type: "application/ld+json",
|
|
255
|
+
innerHTML: JSON.stringify(graph)
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/markdown/frontmatter.ts
|
|
260
|
+
function markdownFrontmatterFactory(options) {
|
|
261
|
+
return (frontmatter, frontmatterOptions, id, defaults) => {
|
|
262
|
+
createAssert(options.assertRules)(id, frontmatter);
|
|
263
|
+
og(id, frontmatter, options.hostname);
|
|
264
|
+
canonical(id, frontmatter, options.hostname);
|
|
265
|
+
structuredData(id, frontmatter, {
|
|
266
|
+
name: options.title,
|
|
267
|
+
hostname: options.hostname,
|
|
268
|
+
extractPage: options.extractPage,
|
|
269
|
+
getPageConfig: options.getPageConfig
|
|
270
|
+
});
|
|
271
|
+
frontmatter.page = options.extractPage(id);
|
|
272
|
+
return {
|
|
273
|
+
head: defaults(frontmatter, frontmatterOptions),
|
|
274
|
+
frontmatter
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
}
|
|
61
278
|
//#endregion
|
|
62
279
|
//#region src/markdown-it/custom-image.ts
|
|
63
280
|
function customImage(md, hostname) {
|
|
64
|
-
md.use((md
|
|
65
|
-
const imageRule = md
|
|
66
|
-
md
|
|
281
|
+
md.use((md) => {
|
|
282
|
+
const imageRule = md.renderer.rules.image;
|
|
283
|
+
md.renderer.rules.image = async (tokens, idx, options, env, self) => {
|
|
67
284
|
const token = tokens[idx];
|
|
68
285
|
const src = token.attrGet("src");
|
|
69
286
|
if (src) {
|
|
@@ -88,16 +305,16 @@ function customImage(md, hostname) {
|
|
|
88
305
|
};
|
|
89
306
|
});
|
|
90
307
|
}
|
|
91
|
-
|
|
92
308
|
//#endregion
|
|
93
309
|
//#region src/markdown-it/custom-link.ts
|
|
310
|
+
const internalLinkRE$1 = /^https?:\/\/(?:[a-z0-9-]+\.)?soubiran\.dev(?:[/?#]|$)/;
|
|
94
311
|
function customLink(md, hostname) {
|
|
95
|
-
md.use((md
|
|
96
|
-
const linkRule = md
|
|
97
|
-
md
|
|
312
|
+
md.use((md) => {
|
|
313
|
+
const linkRule = md.renderer.rules.link_open;
|
|
314
|
+
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
|
|
98
315
|
const token = tokens[idx];
|
|
99
316
|
const href = token.attrGet("href");
|
|
100
|
-
if (href &&
|
|
317
|
+
if (href && internalLinkRE$1.test(href)) {
|
|
101
318
|
let linkText = "";
|
|
102
319
|
let nextIdx = idx + 1;
|
|
103
320
|
while (nextIdx < tokens.length && tokens[nextIdx].type !== "link_close") {
|
|
@@ -118,34 +335,32 @@ function customLink(md, hostname) {
|
|
|
118
335
|
};
|
|
119
336
|
});
|
|
120
337
|
}
|
|
121
|
-
|
|
122
338
|
//#endregion
|
|
123
339
|
//#region src/markdown-it/github-alerts.ts
|
|
124
340
|
function githubAlerts(md) {
|
|
125
341
|
md.use(MarkdownItGitHubAlerts);
|
|
126
342
|
}
|
|
127
|
-
|
|
128
343
|
//#endregion
|
|
129
344
|
//#region src/markdown-it/implicit-figures.ts
|
|
130
345
|
function implicitFiguresRule(md) {
|
|
131
346
|
md.use(implicitFigures, { figcaption: "alt" });
|
|
132
347
|
}
|
|
133
|
-
|
|
134
348
|
//#endregion
|
|
135
349
|
//#region src/markdown-it/link-attributes.ts
|
|
350
|
+
const internalLinkRE = /^https?:\/\/(?:[a-z0-9-]+\.)?soubiran\.dev(?:[/?#]|$)/;
|
|
351
|
+
const externalLinkRE = /^https?:\/\//;
|
|
136
352
|
function linkAttributesRule(md) {
|
|
137
353
|
md.use(linkAttributes, [{
|
|
138
|
-
matcher: (link) =>
|
|
354
|
+
matcher: (link) => internalLinkRE.test(link),
|
|
139
355
|
attrs: { target: "_blank" }
|
|
140
356
|
}, {
|
|
141
|
-
matcher: (link) =>
|
|
357
|
+
matcher: (link) => externalLinkRE.test(link),
|
|
142
358
|
attrs: {
|
|
143
359
|
target: "_blank",
|
|
144
360
|
rel: "noopener"
|
|
145
361
|
}
|
|
146
362
|
}]);
|
|
147
363
|
}
|
|
148
|
-
|
|
149
364
|
//#endregion
|
|
150
365
|
//#region src/markdown-it/shiki-highlight.ts
|
|
151
366
|
async function shikiHighlight(md) {
|
|
@@ -157,7 +372,6 @@ async function shikiHighlight(md) {
|
|
|
157
372
|
}
|
|
158
373
|
}));
|
|
159
374
|
}
|
|
160
|
-
|
|
161
375
|
//#endregion
|
|
162
376
|
//#region ../../node_modules/.pnpm/markdown-it-table-of-contents@1.1.0_patch_hash=bf17e463c2a0f62d05f1686e2d4c458136845ea3b2c32121b585ceededa45171/node_modules/markdown-it-table-of-contents/index.mjs
|
|
163
377
|
/**
|
|
@@ -349,12 +563,11 @@ function flatHeadlineItemsToNestedTree(headlineItems) {
|
|
|
349
563
|
});
|
|
350
564
|
return toc;
|
|
351
565
|
}
|
|
352
|
-
|
|
353
566
|
//#endregion
|
|
354
567
|
//#region src/markdown-it/table-of-contents.ts
|
|
355
568
|
function tableOfContentsRule(md) {
|
|
356
|
-
md.use((md
|
|
357
|
-
md
|
|
569
|
+
md.use((md) => {
|
|
570
|
+
md.renderer.rules.heading_open = (tokens, idx, options, _env, self) => {
|
|
358
571
|
const token = tokens[idx];
|
|
359
572
|
if (token.tag === "h2" || token.tag === "h3") {
|
|
360
573
|
const inlineToken = tokens[idx + 1];
|
|
@@ -364,14 +577,14 @@ function tableOfContentsRule(md) {
|
|
|
364
577
|
}
|
|
365
578
|
return self.renderToken(tokens, idx, options);
|
|
366
579
|
};
|
|
367
|
-
md
|
|
580
|
+
md.renderer.rules.heading_close = (tokens, idx, options, _env, self) => {
|
|
368
581
|
const token = tokens[idx];
|
|
369
582
|
if (token.tag === "h2" || token.tag === "h3") return "</Heading>";
|
|
370
583
|
return self.renderToken(tokens, idx, options);
|
|
371
584
|
};
|
|
372
585
|
});
|
|
373
|
-
md.use((md
|
|
374
|
-
md
|
|
586
|
+
md.use((md) => {
|
|
587
|
+
md.core.ruler.push("toc_to_env", (state) => {
|
|
375
588
|
const options = defaultOptions;
|
|
376
589
|
const tocTree = flatHeadlineItemsToNestedTree(findHeadlineElements(options.includeLevel, state.tokens, options));
|
|
377
590
|
state.env.frontmatter = state.env.frontmatter || {};
|
|
@@ -380,56 +593,31 @@ function tableOfContentsRule(md) {
|
|
|
380
593
|
});
|
|
381
594
|
});
|
|
382
595
|
}
|
|
383
|
-
|
|
384
|
-
//#endregion
|
|
385
|
-
//#region src/promise.ts
|
|
386
|
-
const promises = [];
|
|
387
|
-
async function resolveAll() {
|
|
388
|
-
await Promise.all(promises);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
596
|
//#endregion
|
|
392
|
-
//#region src/
|
|
393
|
-
|
|
394
|
-
async
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
headline: "",
|
|
403
|
-
hostname
|
|
597
|
+
//#region src/markdown/rules.ts
|
|
598
|
+
function markdownRulesFactory(hostname) {
|
|
599
|
+
return async (md) => {
|
|
600
|
+
githubAlerts(md);
|
|
601
|
+
implicitFiguresRule(md);
|
|
602
|
+
linkAttributesRule(md);
|
|
603
|
+
tableOfContentsRule(md);
|
|
604
|
+
customLink(md, hostname);
|
|
605
|
+
customImage(md, hostname);
|
|
606
|
+
await shikiHighlight(md);
|
|
404
607
|
};
|
|
405
|
-
const svg = ogSVG.replace(/\{\{([^}]+)\}\}/g, (_, name) => data[name] || "");
|
|
406
|
-
console.log(`Generating ${output}`);
|
|
407
|
-
try {
|
|
408
|
-
await sharp(Buffer.from(svg)).resize(1200 * 1.1, 630 * 1.1).png().toFile(output);
|
|
409
|
-
} catch (e) {
|
|
410
|
-
console.error("Failed to generate og image", e);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
function og(id, frontmatter, hostname) {
|
|
414
|
-
(() => {
|
|
415
|
-
const path = `og/${basename(id, ".md")}.png`;
|
|
416
|
-
promises.push(generate(frontmatter.title.replace(/\s-\s.*$/, "").trim(), hostname, `public/${path}`));
|
|
417
|
-
frontmatter.image = `https://${hostname}/${path}`;
|
|
418
|
-
})();
|
|
419
608
|
}
|
|
420
|
-
|
|
421
609
|
//#endregion
|
|
422
|
-
//#region src/
|
|
610
|
+
//#region src/domain/api.ts
|
|
423
611
|
/**
|
|
424
612
|
* Recursively scan a directory for markdown files
|
|
425
613
|
*/
|
|
426
|
-
function scanMarkdownFiles(dir
|
|
614
|
+
function scanMarkdownFiles(dir) {
|
|
427
615
|
const files = [];
|
|
428
616
|
try {
|
|
429
617
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
430
618
|
for (const entry of entries) {
|
|
431
619
|
const fullPath = join(dir, entry.name);
|
|
432
|
-
if (entry.isDirectory()) files.push(...scanMarkdownFiles(fullPath
|
|
620
|
+
if (entry.isDirectory()) files.push(...scanMarkdownFiles(fullPath));
|
|
433
621
|
else if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "index.md") files.push(fullPath);
|
|
434
622
|
}
|
|
435
623
|
} catch {}
|
|
@@ -441,26 +629,28 @@ function scanMarkdownFiles(dir, baseDir) {
|
|
|
441
629
|
function processMarkdownFile(filePath, category) {
|
|
442
630
|
const { data } = matter(readFileSync(filePath, "utf-8"));
|
|
443
631
|
return {
|
|
444
|
-
path: `/${category}/${filePath.split("/").pop()?.replace(
|
|
632
|
+
path: `/${category}/${filePath.split("/").pop()?.replace(markdownExtensionRE, "") || ""}`,
|
|
445
633
|
...data
|
|
446
634
|
};
|
|
447
635
|
}
|
|
448
636
|
/**
|
|
449
637
|
* Generates the pages API JSON files in dist/api directory
|
|
450
638
|
*/
|
|
451
|
-
|
|
639
|
+
function generateJsonApi(outDir, categories, logger) {
|
|
452
640
|
const pagesDir = resolve(cwd(), "pages");
|
|
453
|
-
const distDir = resolve(cwd(),
|
|
641
|
+
const distDir = resolve(cwd(), outDir);
|
|
454
642
|
for (const name of categories) {
|
|
455
643
|
const processedFiles = scanMarkdownFiles(join(pagesDir, name)).map((file) => processMarkdownFile(file, name));
|
|
456
644
|
const apiDir = join(distDir, "api");
|
|
457
645
|
const path = join(apiDir, `${name}.json`);
|
|
458
646
|
mkdirSync(apiDir, { recursive: true });
|
|
459
647
|
writeFileSync(path, JSON.stringify(processedFiles, null, 2));
|
|
460
|
-
|
|
648
|
+
logger.info(`${dim(`${outDir}/`)}${cyan(path.replace(`${distDir}/`, ""))}`);
|
|
461
649
|
}
|
|
462
650
|
}
|
|
463
|
-
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region src/plugins/api.ts
|
|
653
|
+
function api_default(categories = []) {
|
|
464
654
|
let config;
|
|
465
655
|
return {
|
|
466
656
|
name: "api",
|
|
@@ -470,85 +660,28 @@ function apiPlugin(categories = []) {
|
|
|
470
660
|
closeBundle() {
|
|
471
661
|
if (this.environment.name !== "client") return;
|
|
472
662
|
if (categories.length === 0) return;
|
|
473
|
-
const time =
|
|
663
|
+
const time = Date.now();
|
|
474
664
|
config.logger.info(yellow("Generate API files"));
|
|
475
|
-
|
|
476
|
-
config.logger.info(green(`✓ generated in ${
|
|
665
|
+
generateJsonApi(config.build.outDir, categories, config.logger);
|
|
666
|
+
config.logger.info(green(`✓ generated in ${Date.now() - time}ms`));
|
|
477
667
|
}
|
|
478
668
|
};
|
|
479
669
|
}
|
|
480
|
-
|
|
481
670
|
//#endregion
|
|
482
|
-
//#region src/plugins/
|
|
483
|
-
|
|
484
|
-
* Sanitize markdown content for LLM consumption
|
|
485
|
-
* - Removes HTML tags while preserving content inside paired tags
|
|
486
|
-
* - Adds title as H1 heading at the top
|
|
487
|
-
* - Safe for build-time processing
|
|
488
|
-
*/
|
|
489
|
-
function sanitizeMarkdown(content, title) {
|
|
490
|
-
let sanitized = content;
|
|
491
|
-
let prevSanitized = "";
|
|
492
|
-
while (sanitized !== prevSanitized) {
|
|
493
|
-
prevSanitized = sanitized;
|
|
494
|
-
sanitized = sanitized.replace(/<[^>]+>([^<]*)<\/[^>]+>/g, "$1");
|
|
495
|
-
sanitized = sanitized.replace(/<[^>]*>/g, "");
|
|
496
|
-
}
|
|
497
|
-
if (title) sanitized = `# ${title}\n\n${sanitized}`;
|
|
498
|
-
return sanitized.trim();
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Recursively copy and sanitize markdown files from source to target directory
|
|
502
|
-
* - Parses frontmatter and removes it
|
|
503
|
-
* - Sanitizes HTML tags
|
|
504
|
-
* - Preserves directory structure
|
|
505
|
-
* - Converts /<something>/index.md to /<something>.md (except for root /index.md)
|
|
506
|
-
*/
|
|
507
|
-
function copyAndSanitizeMarkdownFiles(config, sourceDir, targetDir, isRoot = true) {
|
|
508
|
-
const outDir = join(resolve(cwd()), config.build.outDir);
|
|
509
|
-
const entries = readdirSync(sourceDir);
|
|
510
|
-
for (const entry of entries) {
|
|
511
|
-
const sourcePath = join(sourceDir, entry);
|
|
512
|
-
if (statSync(sourcePath).isDirectory()) {
|
|
513
|
-
const newTargetDir = join(targetDir, entry);
|
|
514
|
-
if (!existsSync(newTargetDir)) mkdirSync(newTargetDir, { recursive: true });
|
|
515
|
-
copyAndSanitizeMarkdownFiles(config, sourcePath, newTargetDir, false);
|
|
516
|
-
} else if (entry.endsWith(".md")) {
|
|
517
|
-
let targetPath;
|
|
518
|
-
if (entry === "index.md" && !isRoot) {
|
|
519
|
-
const parentDirName = basename(sourceDir);
|
|
520
|
-
targetPath = join(dirname(targetDir), `${parentDirName}.md`);
|
|
521
|
-
} else targetPath = join(targetDir, entry);
|
|
522
|
-
const targetDirPath = dirname(targetPath);
|
|
523
|
-
if (!existsSync(targetDirPath)) mkdirSync(targetDirPath, { recursive: true });
|
|
524
|
-
const { data, content } = matter(readFileSync(sourcePath, "utf-8"));
|
|
525
|
-
const sanitizedContent = sanitizeMarkdown(content, data.title);
|
|
526
|
-
writeFileSync(targetPath, sanitizedContent, "utf-8");
|
|
527
|
-
config.logger.info(`${dim(`${config.build.outDir}/`)}${cyan(targetPath.replace(`${outDir}/`, ""))}`);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
function markdownPlugin() {
|
|
532
|
-
let config;
|
|
671
|
+
//#region src/plugins/config.ts
|
|
672
|
+
function config_default() {
|
|
533
673
|
return {
|
|
534
|
-
name: "
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const pagesDir = resolve(cwd(), "pages");
|
|
541
|
-
const distDir = resolve(cwd(), config.build.outDir);
|
|
542
|
-
const time = /* @__PURE__ */ new Date();
|
|
543
|
-
config.logger.info(yellow("Copy and Sanitize Markdown"));
|
|
544
|
-
copyAndSanitizeMarkdownFiles(config, pagesDir, distDir);
|
|
545
|
-
config.logger.info(green(`✓ copied in ${(/* @__PURE__ */ new Date()).getTime() - time.getTime()}ms`));
|
|
674
|
+
name: "soubiran:config",
|
|
675
|
+
config() {
|
|
676
|
+
return {
|
|
677
|
+
optimizeDeps: { exclude: ["@soubiran/ui"] },
|
|
678
|
+
resolve: { alias: { "@": resolve("./src") } }
|
|
679
|
+
};
|
|
546
680
|
}
|
|
547
681
|
};
|
|
548
682
|
}
|
|
549
|
-
|
|
550
683
|
//#endregion
|
|
551
|
-
//#region src/
|
|
684
|
+
//#region src/domain/meta.ts
|
|
552
685
|
/**
|
|
553
686
|
* Generate a hash of the content for change detection
|
|
554
687
|
*/
|
|
@@ -570,15 +703,14 @@ function scanPagesForMeta(pagesDir, baseUri = "") {
|
|
|
570
703
|
} else if (entry.endsWith(".md")) {
|
|
571
704
|
const parsed = matter(readFileSync(fullPath, "utf-8"));
|
|
572
705
|
let uri = baseUri;
|
|
573
|
-
if (entry !== "index.md") uri = joinURL(baseUri, entry.replace(
|
|
706
|
+
if (entry !== "index.md") uri = joinURL(baseUri, entry.replace(markdownExtensionRE, ""));
|
|
574
707
|
uri = withoutTrailingSlash(uri);
|
|
575
708
|
pages.push({
|
|
576
709
|
uri,
|
|
577
710
|
title: parsed.data.title,
|
|
578
711
|
description: parsed.data.description,
|
|
579
712
|
content: parsed.content,
|
|
580
|
-
id: parsed.data.id
|
|
581
|
-
filePath: fullPath
|
|
713
|
+
id: parsed.data.id
|
|
582
714
|
});
|
|
583
715
|
}
|
|
584
716
|
}
|
|
@@ -587,9 +719,9 @@ function scanPagesForMeta(pagesDir, baseUri = "") {
|
|
|
587
719
|
/**
|
|
588
720
|
* Generate meta.json file with all pages metadata
|
|
589
721
|
*/
|
|
590
|
-
|
|
722
|
+
function generateMeta(outDir, hostname, logger) {
|
|
591
723
|
const pagesDir = resolve(cwd(), "pages");
|
|
592
|
-
const distDir = resolve(cwd(),
|
|
724
|
+
const distDir = resolve(cwd(), outDir);
|
|
593
725
|
const pages = scanPagesForMeta(pagesDir).filter((page) => page.title).map((page) => ({
|
|
594
726
|
id: page.id,
|
|
595
727
|
title: page.title,
|
|
@@ -601,9 +733,11 @@ async function generateMeta(config, hostname) {
|
|
|
601
733
|
pages.sort((a, b) => a.uri.localeCompare(b.uri));
|
|
602
734
|
const metaPath = join(distDir, "meta.json");
|
|
603
735
|
writeFileSync(metaPath, JSON.stringify(pages, null, 2));
|
|
604
|
-
|
|
736
|
+
logger.info(`${dim(`${outDir}/`)}${cyan(metaPath.replace(`${distDir}/`, ""))}`);
|
|
605
737
|
}
|
|
606
|
-
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region src/plugins/meta.ts
|
|
740
|
+
function meta_default(hostname) {
|
|
607
741
|
let config;
|
|
608
742
|
return {
|
|
609
743
|
name: "meta",
|
|
@@ -612,330 +746,216 @@ function metaPlugin(hostname) {
|
|
|
612
746
|
},
|
|
613
747
|
closeBundle() {
|
|
614
748
|
if (this.environment.name !== "client") return;
|
|
615
|
-
const time =
|
|
749
|
+
const time = Date.now();
|
|
616
750
|
config.logger.info(yellow("Generate meta.json"));
|
|
617
|
-
generateMeta(config, hostname);
|
|
618
|
-
config.logger.info(green(`✓ generated in ${
|
|
751
|
+
generateMeta(config.build.outDir, hostname, config.logger);
|
|
752
|
+
config.logger.info(green(`✓ generated in ${Date.now() - time}ms`));
|
|
619
753
|
}
|
|
620
754
|
};
|
|
621
755
|
}
|
|
622
|
-
|
|
623
|
-
//#endregion
|
|
624
|
-
//#region src/sitemap.ts
|
|
625
|
-
const routes = /* @__PURE__ */ new Set();
|
|
626
|
-
function sitemap(config, hostname, routes$1) {
|
|
627
|
-
const sitemapStream = new SitemapStream({ hostname: `https://${hostname}` });
|
|
628
|
-
const writeStream = createWriteStream(join(config.build.outDir, "sitemap.xml"));
|
|
629
|
-
sitemapStream.pipe(writeStream);
|
|
630
|
-
routes$1.forEach((item) => sitemapStream.write(item));
|
|
631
|
-
sitemapStream.end();
|
|
632
|
-
}
|
|
633
|
-
|
|
634
756
|
//#endregion
|
|
635
|
-
//#region src/
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
"@id": joinURL(toUrl(options.hostname), "#", "schema", "Article", getUri(id)),
|
|
644
|
-
"headline": title,
|
|
645
|
-
"description": description,
|
|
646
|
-
"isPartOf": { "@id": structuredData$1.webpage.data["@id"] },
|
|
647
|
-
"mainEntityOfPage": { "@id": structuredData$1.webpage.data["@id"] },
|
|
648
|
-
"datePublished": structuredData$1.webpage.data.datePublished ? structuredData$1.webpage.data.datePublished : void 0,
|
|
649
|
-
"author": { "@id": structuredData$1.person.data["@id"] },
|
|
650
|
-
"publisher": { "@id": structuredData$1.person.data["@id"] },
|
|
651
|
-
"inLanguage": structuredData$1.webpage.data.inLanguage
|
|
652
|
-
} };
|
|
757
|
+
//#region src/plugins/promise.ts
|
|
758
|
+
function promise_default() {
|
|
759
|
+
return {
|
|
760
|
+
name: "soubiran:promise",
|
|
761
|
+
async closeBundle() {
|
|
762
|
+
await resolveAll();
|
|
763
|
+
}
|
|
764
|
+
};
|
|
653
765
|
}
|
|
654
|
-
|
|
655
766
|
//#endregion
|
|
656
|
-
//#region src/
|
|
767
|
+
//#region src/domain/raw-markdown.ts
|
|
768
|
+
const pairedHtmlTagRE = /<[^>]+>([^<]*)<\/[^>]+>/g;
|
|
769
|
+
const htmlTagRE = /<[^>]*>/g;
|
|
657
770
|
/**
|
|
658
|
-
*
|
|
771
|
+
* Sanitize markdown content for LLM consumption
|
|
772
|
+
* - Removes HTML tags while preserving content inside paired tags
|
|
773
|
+
* - Adds title as H1 heading at the top
|
|
774
|
+
* - Safe for build-time processing
|
|
659
775
|
*/
|
|
660
|
-
function
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
"@id": item.url
|
|
671
|
-
} } : {}
|
|
672
|
-
}))
|
|
673
|
-
} };
|
|
776
|
+
function sanitizeMarkdown(content, title) {
|
|
777
|
+
let sanitized = content;
|
|
778
|
+
let prevSanitized = "";
|
|
779
|
+
while (sanitized !== prevSanitized) {
|
|
780
|
+
prevSanitized = sanitized;
|
|
781
|
+
sanitized = sanitized.replace(pairedHtmlTagRE, "$1");
|
|
782
|
+
sanitized = sanitized.replace(htmlTagRE, "");
|
|
783
|
+
}
|
|
784
|
+
if (title) sanitized = `# ${title}\n\n${sanitized}`;
|
|
785
|
+
return sanitized.trim();
|
|
674
786
|
}
|
|
675
|
-
|
|
676
|
-
//#endregion
|
|
677
|
-
//#region src/structured-data/person.ts
|
|
678
787
|
/**
|
|
679
|
-
*
|
|
788
|
+
* Recursively copy and sanitize markdown files from source to target directory
|
|
789
|
+
* - Parses frontmatter and removes it
|
|
790
|
+
* - Sanitizes HTML tags
|
|
791
|
+
* - Preserves directory structure
|
|
792
|
+
* - Converts /<something>/index.md to /<something>.md (except for root /index.md)
|
|
680
793
|
*/
|
|
681
|
-
function
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
794
|
+
function copyAndSanitizeMarkdownFiles(outDir, logger, sourceDir, targetDir, isRoot = true) {
|
|
795
|
+
const outDirPath = join(resolve(cwd()), outDir);
|
|
796
|
+
const entries = readdirSync(sourceDir);
|
|
797
|
+
for (const entry of entries) {
|
|
798
|
+
const sourcePath = join(sourceDir, entry);
|
|
799
|
+
if (statSync(sourcePath).isDirectory()) {
|
|
800
|
+
const newTargetDir = join(targetDir, entry);
|
|
801
|
+
if (!existsSync(newTargetDir)) mkdirSync(newTargetDir, { recursive: true });
|
|
802
|
+
copyAndSanitizeMarkdownFiles(outDir, logger, sourcePath, newTargetDir, false);
|
|
803
|
+
} else if (entry.endsWith(".md")) {
|
|
804
|
+
let targetPath;
|
|
805
|
+
if (entry === "index.md" && !isRoot) {
|
|
806
|
+
const parentDirName = basename(sourceDir);
|
|
807
|
+
targetPath = join(dirname(targetDir), `${parentDirName}.md`);
|
|
808
|
+
} else targetPath = join(targetDir, entry);
|
|
809
|
+
const targetDirPath = dirname(targetPath);
|
|
810
|
+
if (!existsSync(targetDirPath)) mkdirSync(targetDirPath, { recursive: true });
|
|
811
|
+
const { data, content } = matter(readFileSync(sourcePath, "utf-8"));
|
|
812
|
+
const sanitizedContent = sanitizeMarkdown(content, data.title);
|
|
813
|
+
writeFileSync(targetPath, sanitizedContent, "utf-8");
|
|
814
|
+
logger.info(`${dim(`${outDir}/`)}${cyan(targetPath.replace(`${outDirPath}/`, ""))}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
688
817
|
}
|
|
689
|
-
|
|
690
818
|
//#endregion
|
|
691
|
-
//#region src/
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
*/
|
|
695
|
-
function webpage(id, structuredData$1, properties, options) {
|
|
696
|
-
const { title, description, datePublished, keywords } = properties;
|
|
697
|
-
const canonicalUrl = getCanonicalUrl(id, options.hostname);
|
|
698
|
-
const data = {
|
|
699
|
-
"@type": "WebPage",
|
|
700
|
-
"@id": canonicalUrl,
|
|
701
|
-
"url": canonicalUrl,
|
|
702
|
-
"name": title,
|
|
703
|
-
"description": description,
|
|
704
|
-
"isPartOf": { "@id": structuredData$1.website.data["@id"] },
|
|
705
|
-
"inLanguage": "en-US",
|
|
706
|
-
"potentialAction": [{
|
|
707
|
-
"@type": "ReadAction",
|
|
708
|
-
"target": [canonicalUrl]
|
|
709
|
-
}],
|
|
710
|
-
...datePublished ? { datePublished: datePublished.toISOString() } : {},
|
|
711
|
-
...keywords ? { keywords } : {}
|
|
712
|
-
};
|
|
819
|
+
//#region src/plugins/raw-markdown.ts
|
|
820
|
+
function raw_markdown_default() {
|
|
821
|
+
let config;
|
|
713
822
|
return {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
823
|
+
name: "markdown",
|
|
824
|
+
configResolved(resolvedConfig) {
|
|
825
|
+
config = resolvedConfig;
|
|
717
826
|
},
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
827
|
+
closeBundle() {
|
|
828
|
+
if (this.environment.name !== "client") return;
|
|
829
|
+
const pagesDir = resolve(cwd(), "pages");
|
|
830
|
+
const distDir = resolve(cwd(), config.build.outDir);
|
|
831
|
+
const time = Date.now();
|
|
832
|
+
config.logger.info(yellow("Copy and Sanitize Markdown"));
|
|
833
|
+
copyAndSanitizeMarkdownFiles(config.build.outDir, config.logger, pagesDir, distDir);
|
|
834
|
+
config.logger.info(green(`✓ copied in ${Date.now() - time}ms`));
|
|
721
835
|
}
|
|
722
836
|
};
|
|
723
837
|
}
|
|
724
|
-
|
|
725
838
|
//#endregion
|
|
726
|
-
//#region src/
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
"@id": joinURL(options.url, "#", "schema", "WebSite", "1"),
|
|
734
|
-
"url": options.url,
|
|
735
|
-
"name": options.name,
|
|
736
|
-
"inLanguage": ["en-US"],
|
|
737
|
-
"publisher": { "@id": structuredData$1.person.data["@id"] }
|
|
738
|
-
} };
|
|
839
|
+
//#region src/domain/sitemap.ts
|
|
840
|
+
function generateSitemap(outDir, hostname, routes) {
|
|
841
|
+
const sitemapStream = new SitemapStream({ hostname: `https://${hostname}` });
|
|
842
|
+
const writeStream = createWriteStream(join(outDir, "sitemap.xml"));
|
|
843
|
+
sitemapStream.pipe(writeStream);
|
|
844
|
+
routes.forEach((item) => sitemapStream.write(item));
|
|
845
|
+
sitemapStream.end();
|
|
739
846
|
}
|
|
740
|
-
|
|
741
847
|
//#endregion
|
|
742
|
-
//#region src/
|
|
743
|
-
function
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
"
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
848
|
+
//#region src/plugins/sitemap.ts
|
|
849
|
+
function sitemap_default(hostname) {
|
|
850
|
+
const routes = /* @__PURE__ */ new Set();
|
|
851
|
+
let config;
|
|
852
|
+
return {
|
|
853
|
+
name: "soubiran:sitemap",
|
|
854
|
+
config() {
|
|
855
|
+
return { ssgOptions: {
|
|
856
|
+
onPageRendered(route, renderedHTML) {
|
|
857
|
+
routes.add(route);
|
|
858
|
+
return renderedHTML;
|
|
859
|
+
},
|
|
860
|
+
onFinished() {
|
|
861
|
+
generateSitemap(config.build.outDir, hostname, Array.from(routes));
|
|
862
|
+
}
|
|
863
|
+
} };
|
|
864
|
+
},
|
|
865
|
+
configResolved(resolvedConfig) {
|
|
866
|
+
config = resolvedConfig;
|
|
867
|
+
}
|
|
753
868
|
};
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
const page = extractPage(id);
|
|
763
|
-
const pageConfig = getPageConfig?.(page, frontmatter);
|
|
764
|
-
if (pageConfig?.type === "article") {
|
|
765
|
-
const articleData = article(id, {
|
|
766
|
-
person: personData,
|
|
767
|
-
webpage: webpageData
|
|
768
|
-
}, {
|
|
769
|
-
title: frontmatter.title,
|
|
770
|
-
description: frontmatter.description
|
|
771
|
-
}, structuredDataOptions);
|
|
772
|
-
graph["@graph"].push(articleData.data);
|
|
773
|
-
if (pageConfig.breadcrumbItems) {
|
|
774
|
-
const breadcrumbData = breadcrumb(id, pageConfig.breadcrumbItems, structuredDataOptions);
|
|
775
|
-
graph["@graph"].push(breadcrumbData.data);
|
|
776
|
-
webpageData.setBreadcrumb(breadcrumbData);
|
|
869
|
+
}
|
|
870
|
+
//#endregion
|
|
871
|
+
//#region src/plugins/ssg.ts
|
|
872
|
+
function ssg_default() {
|
|
873
|
+
return {
|
|
874
|
+
name: "soubiran:ssg",
|
|
875
|
+
config() {
|
|
876
|
+
return { ssgOptions: { formatting: "minify" } };
|
|
777
877
|
}
|
|
778
|
-
}
|
|
779
|
-
graph["@graph"].push(personData.data, websiteData.data, webpageData.data);
|
|
780
|
-
frontmatter.script ??= [];
|
|
781
|
-
frontmatter.script.push({
|
|
782
|
-
type: "application/ld+json",
|
|
783
|
-
innerHTML: JSON.stringify(graph)
|
|
784
|
-
});
|
|
878
|
+
};
|
|
785
879
|
}
|
|
786
|
-
|
|
787
|
-
//#
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
"
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
]
|
|
802
|
-
} };
|
|
803
|
-
return mergeConfig(defineConfig({
|
|
804
|
-
plugins: [
|
|
805
|
-
vueRouter({
|
|
806
|
-
extensions: [".vue", ".md"],
|
|
807
|
-
routesFolder: "pages",
|
|
808
|
-
dts: "src/typed-router.d.ts",
|
|
809
|
-
extendRoute(route) {
|
|
810
|
-
const path = route.components.get("default");
|
|
811
|
-
if (!path) return;
|
|
812
|
-
if (path.endsWith(".vue")) route.addToMeta({ frontmatter: { page: options.extractPage(path) } });
|
|
813
|
-
if (path.endsWith(".md")) {
|
|
814
|
-
const { data } = matter(readFileSync(path, "utf-8"));
|
|
815
|
-
route.addToMeta({ frontmatter: data });
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}),
|
|
819
|
-
vue({ include: [/\.vue$/, /\.md$/] }),
|
|
820
|
-
ui({
|
|
821
|
-
autoImport: {
|
|
822
|
-
dts: "src/auto-imports.d.ts",
|
|
823
|
-
dirs: ["src/composables"],
|
|
824
|
-
imports: [
|
|
825
|
-
"vue",
|
|
826
|
-
"vue-router",
|
|
827
|
-
"@vueuse/core",
|
|
828
|
-
unheadVueComposablesImports,
|
|
829
|
-
{
|
|
830
|
-
from: "tailwind-variants",
|
|
831
|
-
imports: ["tv"]
|
|
832
|
-
},
|
|
833
|
-
soubiranComposablesImports
|
|
834
|
-
]
|
|
835
|
-
},
|
|
836
|
-
components: {
|
|
837
|
-
include: [
|
|
838
|
-
/\.vue$/,
|
|
839
|
-
/\.vue\?vue/,
|
|
840
|
-
/\.md$/
|
|
841
|
-
],
|
|
842
|
-
dts: "src/components.d.ts",
|
|
843
|
-
resolvers: [soubiranResolver()]
|
|
844
|
-
},
|
|
845
|
-
ui: { colors: { neutral: "neutral" } }
|
|
846
|
-
}),
|
|
847
|
-
markdown({
|
|
848
|
-
headEnabled: true,
|
|
849
|
-
wrapperClasses: [
|
|
850
|
-
"slide-enter-content",
|
|
851
|
-
"max-w-none",
|
|
852
|
-
"prose prose-neutral dark:prose-invert",
|
|
853
|
-
"prose-headings:text-default prose-h2:text-[1.125em] prose-h2:mb-[0.5em] prose-h3:text-[1em]",
|
|
854
|
-
"prose-p:my-[1em] dark:prose-p:text-muted",
|
|
855
|
-
"dark:prose-ul:text-muted dark:prose-ol:text-muted",
|
|
856
|
-
"dark:prose-strong:text-default",
|
|
857
|
-
"dark:prose-a:text-muted prose-a:font-semibold prose-a:no-underline prose-a:border-b prose-a:border-muted prose-a:transition-colors prose-a:duration-300 prose-a:ease-out prose-a:hover:border-[var(--ui-text-dimmed)]",
|
|
858
|
-
"prose-hr:max-w-1/2 prose-hr:mx-auto prose-hr:my-[2em]",
|
|
859
|
-
"prose-figure:bg-neutral-100 dark:prose-figure:bg-neutral-800 prose-figure:rounded-lg",
|
|
860
|
-
"prose-img:rounded-lg prose-img:border prose-img:border-accented prose-img:shadow-md",
|
|
861
|
-
"prose-video:rounded-lg prose-video:border prose-video:border-accented prose-video:shadow-md",
|
|
862
|
-
"prose-figcaption:text-center prose-figcaption:py-1 prose-figcaption:m-0",
|
|
863
|
-
"[&_:first-child]:mt-0 [&_:last-child]:mb-0"
|
|
864
|
-
],
|
|
865
|
-
transforms: options.markdown?.transforms ?? {},
|
|
866
|
-
wrapperComponent: options.markdown?.wrapperComponent,
|
|
867
|
-
async markdownItSetup(md) {
|
|
868
|
-
githubAlerts(md);
|
|
869
|
-
implicitFiguresRule(md);
|
|
870
|
-
linkAttributesRule(md);
|
|
871
|
-
tableOfContentsRule(md);
|
|
872
|
-
customLink(md, hostname);
|
|
873
|
-
customImage(md, hostname);
|
|
874
|
-
await shikiHighlight(md);
|
|
875
|
-
},
|
|
876
|
-
frontmatterPreprocess(frontmatter, frontmatterOptions, id, defaults) {
|
|
877
|
-
createAssert(options.seo?.assert?.rules)(id, frontmatter);
|
|
878
|
-
og(id, frontmatter, hostname);
|
|
879
|
-
canonical(id, frontmatter, hostname);
|
|
880
|
-
structuredData(id, frontmatter, {
|
|
881
|
-
name: title,
|
|
882
|
-
hostname,
|
|
883
|
-
person: options.seo?.person ?? seo.person,
|
|
884
|
-
extractPage: options.extractPage,
|
|
885
|
-
getPageConfig: options.seo?.structuredData?.pageConfig
|
|
886
|
-
});
|
|
887
|
-
frontmatter.page = options.extractPage(id);
|
|
888
|
-
return {
|
|
889
|
-
head: defaults(frontmatter, frontmatterOptions),
|
|
890
|
-
frontmatter
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
}),
|
|
894
|
-
fonts({ google: { families: [
|
|
895
|
-
{
|
|
896
|
-
name: "DM Sans",
|
|
897
|
-
styles: "ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000"
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
name: "DM Mono",
|
|
901
|
-
styles: "ital,wght@0,300;0,400;0,500;1,300;1,400;1,500"
|
|
902
|
-
},
|
|
903
|
-
{
|
|
904
|
-
name: "Sofia Sans",
|
|
905
|
-
styles: "ital,wght@0,1..1000;1,1..1000"
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region src/index.ts
|
|
882
|
+
function soubiran(title, hostname, options) {
|
|
883
|
+
return [
|
|
884
|
+
vueRouter({
|
|
885
|
+
extensions: [".vue", ".md"],
|
|
886
|
+
routesFolder: "pages",
|
|
887
|
+
dts: "src/typed-router.d.ts",
|
|
888
|
+
extendRoute(route) {
|
|
889
|
+
const path = route.components.get("default");
|
|
890
|
+
if (!path) return;
|
|
891
|
+
if (path.endsWith(".vue")) route.addToMeta({ frontmatter: { page: options.extractPage(path) } });
|
|
892
|
+
if (path.endsWith(".md")) {
|
|
893
|
+
const { data } = matter(readFileSync(path, "utf-8"));
|
|
894
|
+
route.addToMeta({ frontmatter: data });
|
|
906
895
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
896
|
+
}
|
|
897
|
+
}),
|
|
898
|
+
vue({ include: vueIncludePatterns }),
|
|
899
|
+
ui({
|
|
900
|
+
autoImport: {
|
|
901
|
+
dts: "src/auto-imports.d.ts",
|
|
902
|
+
dirs: ["src/composables"],
|
|
903
|
+
imports: [
|
|
904
|
+
"vue",
|
|
905
|
+
"vue-router",
|
|
906
|
+
"@vueuse/core",
|
|
907
|
+
unheadVueComposablesImports,
|
|
908
|
+
{
|
|
909
|
+
from: "tailwind-variants",
|
|
910
|
+
imports: ["tv"]
|
|
911
|
+
},
|
|
912
|
+
soubiranComposablesImports
|
|
913
|
+
]
|
|
914
|
+
},
|
|
915
|
+
components: {
|
|
916
|
+
include: componentIncludePatterns,
|
|
917
|
+
dts: "src/components.d.ts",
|
|
918
|
+
resolvers: [soubiranResolver()]
|
|
919
|
+
},
|
|
920
|
+
ui: { colors: { neutral: "neutral" } }
|
|
921
|
+
}),
|
|
922
|
+
markdown({
|
|
923
|
+
headEnabled: true,
|
|
924
|
+
wrapperClasses: soubiranWrapperClasses,
|
|
925
|
+
transforms: options.markdown?.transforms ?? {},
|
|
926
|
+
wrapperComponent: options.markdown?.wrapperComponent,
|
|
927
|
+
markdownItSetup: markdownRulesFactory(hostname),
|
|
928
|
+
frontmatterPreprocess: markdownFrontmatterFactory({
|
|
929
|
+
title,
|
|
930
|
+
hostname,
|
|
931
|
+
extractPage: options.extractPage,
|
|
932
|
+
assertRules: options.seo?.assert?.rules,
|
|
933
|
+
getPageConfig: options.seo?.structuredData?.pageConfig
|
|
934
|
+
})
|
|
935
|
+
}),
|
|
936
|
+
fonts({ google: { families: [
|
|
912
937
|
{
|
|
913
|
-
name: "
|
|
914
|
-
|
|
915
|
-
await resolveAll();
|
|
916
|
-
}
|
|
938
|
+
name: "DM Sans",
|
|
939
|
+
styles: "ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000"
|
|
917
940
|
},
|
|
918
941
|
{
|
|
919
|
-
name: "
|
|
920
|
-
|
|
921
|
-
Object.assign(config, resolvedConfig);
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
],
|
|
925
|
-
optimizeDeps: { exclude: ["@soubiran/ui"] },
|
|
926
|
-
resolve: { alias: { "@": resolve("./src") } },
|
|
927
|
-
ssgOptions: {
|
|
928
|
-
formatting: "minify",
|
|
929
|
-
onPageRendered(route, renderedHTML) {
|
|
930
|
-
routes.add(route);
|
|
931
|
-
return renderedHTML;
|
|
942
|
+
name: "DM Mono",
|
|
943
|
+
styles: "ital,wght@0,300;0,400;0,500;1,300;1,400;1,500"
|
|
932
944
|
},
|
|
933
|
-
|
|
934
|
-
|
|
945
|
+
{
|
|
946
|
+
name: "Sofia Sans",
|
|
947
|
+
styles: "ital,wght@0,1..1000;1,1..1000"
|
|
935
948
|
}
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
949
|
+
] } }),
|
|
950
|
+
icons({ autoInstall: true }),
|
|
951
|
+
config_default(),
|
|
952
|
+
ssg_default(),
|
|
953
|
+
meta_default(hostname),
|
|
954
|
+
api_default(options.api?.categories),
|
|
955
|
+
raw_markdown_default(),
|
|
956
|
+
sitemap_default(hostname),
|
|
957
|
+
promise_default()
|
|
958
|
+
];
|
|
959
|
+
}
|
|
940
960
|
//#endregion
|
|
941
|
-
export {
|
|
961
|
+
export { soubiran as default };
|