@soubiran/vite 0.0.0 → 0.1.3
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/dist/vite.config.d.mts +82 -0
- package/dist/vite.config.mjs +754 -0
- package/package.json +63 -2
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { UserConfig } from "vite";
|
|
2
|
+
import { Options } from "unplugin-vue-markdown/types";
|
|
3
|
+
|
|
4
|
+
//#region src/assert.d.ts
|
|
5
|
+
type AssertFn = (id: string, frontmatter: Record<string, any>) => void;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/structured-data/breadcrumb.d.ts
|
|
8
|
+
|
|
9
|
+
interface BreadcrumbItem {
|
|
10
|
+
title: string;
|
|
11
|
+
type?: 'WebSite' | 'WebPage';
|
|
12
|
+
url?: string;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/structured-data/person.d.ts
|
|
16
|
+
interface PersonOptions {
|
|
17
|
+
name: string;
|
|
18
|
+
sameAs: string[];
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/structured-data/index.d.ts
|
|
22
|
+
interface StructuredDataPageConfig {
|
|
23
|
+
type: 'article' | 'collection' | 'default';
|
|
24
|
+
breadcrumbItems?: BreadcrumbItem[];
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/utils.d.ts
|
|
28
|
+
declare function getUri(id: string): string;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region vite.config.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* Main configuration interface for the infrastructure status app.
|
|
33
|
+
*/
|
|
34
|
+
interface Options$1 {
|
|
35
|
+
/**
|
|
36
|
+
* Extracts the page identifier from a given file path or id.
|
|
37
|
+
* @param id - The file path or identifier.
|
|
38
|
+
* @returns The extracted page name, or null if not found.
|
|
39
|
+
*/
|
|
40
|
+
extractPage: (id: string) => string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Markdown rendering options for unplugin-vue-markdown.
|
|
43
|
+
*/
|
|
44
|
+
markdown?: Options;
|
|
45
|
+
/**
|
|
46
|
+
* SEO and structured data configuration.
|
|
47
|
+
*/
|
|
48
|
+
seo: {
|
|
49
|
+
/**
|
|
50
|
+
* Person information for Schema.org structured data.
|
|
51
|
+
*/
|
|
52
|
+
person: PersonOptions;
|
|
53
|
+
/**
|
|
54
|
+
* Custom validation rules for frontmatter fields.
|
|
55
|
+
*/
|
|
56
|
+
assert?: {
|
|
57
|
+
/**
|
|
58
|
+
* Validation rules function for frontmatter.
|
|
59
|
+
*/
|
|
60
|
+
rules?: AssertFn;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Structured data generation configuration.
|
|
64
|
+
*/
|
|
65
|
+
structuredData?: {
|
|
66
|
+
/**
|
|
67
|
+
* Callback to determine page type and configuration for structured data generation.
|
|
68
|
+
* @param page - The page name or null.
|
|
69
|
+
* @param frontmatter - The frontmatter data for the page.
|
|
70
|
+
* @returns Structured data configuration for the page.
|
|
71
|
+
*/
|
|
72
|
+
pageConfig?: (page: string | null, frontmatter: Record<string, any>) => StructuredDataPageConfig;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Categories to generate API JSON files for (e.g., ['websites', 'platforms']).
|
|
77
|
+
*/
|
|
78
|
+
apiCategories?: string[];
|
|
79
|
+
}
|
|
80
|
+
declare const _default: (title: string, hostname: string, options: Options$1, config?: UserConfig) => Record<string, any>;
|
|
81
|
+
//#endregion
|
|
82
|
+
export { type BreadcrumbItem, type PersonOptions, type StructuredDataPageConfig, _default as default, getUri };
|
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import ui from "@nuxt/ui/vite";
|
|
4
|
+
import soubiranComposablesImports from "@soubiran/ui/imports";
|
|
5
|
+
import soubiranResolver from "@soubiran/ui/resolver";
|
|
6
|
+
import { unheadVueComposablesImports } from "@unhead/vue";
|
|
7
|
+
import vue from "@vitejs/plugin-vue";
|
|
8
|
+
import matter from "gray-matter";
|
|
9
|
+
import fonts from "unplugin-fonts/vite";
|
|
10
|
+
import icons from "unplugin-icons/vite";
|
|
11
|
+
import markdown from "unplugin-vue-markdown/vite";
|
|
12
|
+
import vueRouter from "unplugin-vue-router/vite";
|
|
13
|
+
import { defineConfig, mergeConfig } from "vite";
|
|
14
|
+
import { joinURL, withoutTrailingSlash } from "ufo";
|
|
15
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16
|
+
import { blurhashToDataUri } from "@unpic/placeholder";
|
|
17
|
+
import MarkdownItGitHubAlerts from "markdown-it-github-alerts";
|
|
18
|
+
import implicitFigures from "markdown-it-image-figures";
|
|
19
|
+
import linkAttributes from "markdown-it-link-attributes";
|
|
20
|
+
import { fromAsyncCodeToHtml } from "@shikijs/markdown-it/async";
|
|
21
|
+
import { codeToHtml } from "shiki";
|
|
22
|
+
import { defaultOptions, findHeadlineElements, flatHeadlineItemsToNestedTree, getTokensText, slugify } from "markdown-it-table-of-contents";
|
|
23
|
+
import { Buffer } from "node:buffer";
|
|
24
|
+
import fs from "fs-extra";
|
|
25
|
+
import sharp from "sharp";
|
|
26
|
+
import { cwd } from "node:process";
|
|
27
|
+
import { cyan, dim, green, yellow } from "ansis";
|
|
28
|
+
import { createHash } from "node:crypto";
|
|
29
|
+
import { SitemapStream } from "sitemap";
|
|
30
|
+
|
|
31
|
+
//#region src/assert.ts
|
|
32
|
+
function createAssert(customAssert) {
|
|
33
|
+
return (id, frontmatter) => {
|
|
34
|
+
if (frontmatter.description) {
|
|
35
|
+
const descLength = frontmatter.description.length;
|
|
36
|
+
if (descLength < 110 || descLength > 160) throw new Error(`Description length must be between 110 and 160 characters. Current length: ${descLength} in file: ${id}`);
|
|
37
|
+
}
|
|
38
|
+
customAssert?.(id, frontmatter);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/utils.ts
|
|
44
|
+
function getUri(id) {
|
|
45
|
+
return withoutTrailingSlash(id.split("/pages/")[1].replace(/\.md$/, "").replace(/\.vue$/, "").replace(/index$/, ""));
|
|
46
|
+
}
|
|
47
|
+
function toUrl(hostname, ...paths) {
|
|
48
|
+
return joinURL(`https://${hostname}`, ...paths);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/canonical.ts
|
|
53
|
+
function getCanonicalUrl(id, hostname) {
|
|
54
|
+
return joinURL(toUrl(hostname), getUri(id));
|
|
55
|
+
}
|
|
56
|
+
function canonical(id, frontmatter, hostname) {
|
|
57
|
+
const url = getCanonicalUrl(id, hostname);
|
|
58
|
+
frontmatter.meta ??= [];
|
|
59
|
+
frontmatter.meta.push({
|
|
60
|
+
property: "og:url",
|
|
61
|
+
content: url
|
|
62
|
+
});
|
|
63
|
+
frontmatter.link ??= [];
|
|
64
|
+
frontmatter.link.push({
|
|
65
|
+
rel: "canonical",
|
|
66
|
+
href: url
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/markdown-it/custom-image.ts
|
|
72
|
+
function customImage(md, hostname) {
|
|
73
|
+
md.use((md$1) => {
|
|
74
|
+
const imageRule = md$1.renderer.rules.image;
|
|
75
|
+
md$1.renderer.rules.image = async (tokens, idx, options, env, self) => {
|
|
76
|
+
const token = tokens[idx];
|
|
77
|
+
const src = token.attrGet("src");
|
|
78
|
+
if (src) {
|
|
79
|
+
if (!src.startsWith("http")) {
|
|
80
|
+
const remoteSrc = `https://assets.${hostname}`;
|
|
81
|
+
const metadataFilename = `${src}.json`;
|
|
82
|
+
const cachedMetadataFilename = join(".cache", metadataFilename);
|
|
83
|
+
let metadata = await readFile(cachedMetadataFilename, "utf-8").then((text) => JSON.parse(text)).catch(() => void 0);
|
|
84
|
+
if (!metadata) {
|
|
85
|
+
metadata = await fetch(joinURL(remoteSrc, metadataFilename)).then((res) => res.json());
|
|
86
|
+
await mkdir(dirname(cachedMetadataFilename), { recursive: true });
|
|
87
|
+
await writeFile(cachedMetadataFilename, JSON.stringify(metadata, null, 2));
|
|
88
|
+
}
|
|
89
|
+
token.attrSet("loading", "lazy");
|
|
90
|
+
token.attrSet("width", metadata.width.toString());
|
|
91
|
+
token.attrSet("height", metadata.height.toString());
|
|
92
|
+
token.attrSet("style", `background-size: cover; background-image: url(${blurhashToDataUri(metadata.blurhash)});`);
|
|
93
|
+
token.attrSet("src", joinURL(`https://${hostname}`, "cdn-cgi/image", "width=1200,quality=80,format=auto", remoteSrc, src));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return imageRule(tokens, idx, options, env, self);
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/markdown-it/custom-link.ts
|
|
103
|
+
function customLink(md, hostname) {
|
|
104
|
+
md.use((md$1) => {
|
|
105
|
+
const linkRule = md$1.renderer.rules.link_open;
|
|
106
|
+
md$1.renderer.rules.link_open = (tokens, idx, options, env, self) => {
|
|
107
|
+
const token = tokens[idx];
|
|
108
|
+
const href = token.attrGet("href");
|
|
109
|
+
if (href && /^https?:\/\/(?:[a-z0-9-]+\.)?soubiran\.dev(?:[/?#]|$)/.test(href)) {
|
|
110
|
+
let linkText = "";
|
|
111
|
+
let nextIdx = idx + 1;
|
|
112
|
+
while (nextIdx < tokens.length && tokens[nextIdx].type !== "link_close") {
|
|
113
|
+
if (tokens[nextIdx].type === "text" || tokens[nextIdx].type === "code_inline") linkText += tokens[nextIdx].content;
|
|
114
|
+
else if (tokens[nextIdx].children) {
|
|
115
|
+
for (const child of tokens[nextIdx].children) if (child.type === "text" || child.type === "code_inline") linkText += child.content;
|
|
116
|
+
}
|
|
117
|
+
nextIdx++;
|
|
118
|
+
}
|
|
119
|
+
const url = new URL(href);
|
|
120
|
+
url.searchParams.set("utm_source", hostname);
|
|
121
|
+
url.searchParams.set("utm_medium", "link");
|
|
122
|
+
url.searchParams.set("utm_content", "textlink");
|
|
123
|
+
if (linkText) url.searchParams.set("utm_term", linkText);
|
|
124
|
+
token.attrSet("href", url.toString());
|
|
125
|
+
}
|
|
126
|
+
return linkRule(tokens, idx, options, env, self);
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/markdown-it/github-alerts.ts
|
|
133
|
+
function githubAlerts(md) {
|
|
134
|
+
md.use(MarkdownItGitHubAlerts);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/markdown-it/implicit-figures.ts
|
|
139
|
+
function implicitFiguresRule(md) {
|
|
140
|
+
md.use(implicitFigures, { figcaption: "alt" });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/markdown-it/link-attributes.ts
|
|
145
|
+
function linkAttributesRule(md) {
|
|
146
|
+
md.use(linkAttributes, [{
|
|
147
|
+
matcher: (link) => /^https?:\/\/(?:[a-z0-9-]+\.)?soubiran\.dev(?:[/?#]|$)/.test(link),
|
|
148
|
+
attrs: { target: "_blank" }
|
|
149
|
+
}, {
|
|
150
|
+
matcher: (link) => /^https?:\/\//.test(link),
|
|
151
|
+
attrs: {
|
|
152
|
+
target: "_blank",
|
|
153
|
+
rel: "noopener"
|
|
154
|
+
}
|
|
155
|
+
}]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/markdown-it/shiki-highlight.ts
|
|
160
|
+
async function shikiHighlight(md) {
|
|
161
|
+
md.use(fromAsyncCodeToHtml(codeToHtml, {
|
|
162
|
+
defaultColor: false,
|
|
163
|
+
themes: {
|
|
164
|
+
light: "github-light",
|
|
165
|
+
dark: "github-dark"
|
|
166
|
+
}
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/markdown-it/table-of-contents.ts
|
|
172
|
+
function tableOfContentsRule(md) {
|
|
173
|
+
md.use((md$1) => {
|
|
174
|
+
md$1.renderer.rules.heading_open = (tokens, idx, options, _env, self) => {
|
|
175
|
+
const token = tokens[idx];
|
|
176
|
+
if (token.tag === "h2" || token.tag === "h3") {
|
|
177
|
+
const inlineToken = tokens[idx + 1];
|
|
178
|
+
const textContent = getTokensText(inlineToken.children);
|
|
179
|
+
token.attrSet("id", slugify(textContent));
|
|
180
|
+
return `<Heading :level="${token.tag.slice(1)}"${self.renderAttrs(token)}>`;
|
|
181
|
+
}
|
|
182
|
+
return self.renderToken(tokens, idx, options);
|
|
183
|
+
};
|
|
184
|
+
md$1.renderer.rules.heading_close = (tokens, idx, options, _env, self) => {
|
|
185
|
+
const token = tokens[idx];
|
|
186
|
+
if (token.tag === "h2" || token.tag === "h3") return "</Heading>";
|
|
187
|
+
return self.renderToken(tokens, idx, options);
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
md.use((md$1) => {
|
|
191
|
+
md$1.core.ruler.push("toc_to_env", (state) => {
|
|
192
|
+
const options = defaultOptions;
|
|
193
|
+
const tocTree = flatHeadlineItemsToNestedTree(findHeadlineElements(options.includeLevel, state.tokens, options));
|
|
194
|
+
state.env.frontmatter = state.env.frontmatter || {};
|
|
195
|
+
state.env.frontmatter.toc = tocTree.children || [];
|
|
196
|
+
return true;
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/promise.ts
|
|
203
|
+
const promises = [];
|
|
204
|
+
async function resolveAll() {
|
|
205
|
+
await Promise.all(promises);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/og.ts
|
|
210
|
+
const ogSVG = fs.readFileSync(new URL("./og-template.svg", import.meta.url), "utf-8");
|
|
211
|
+
async function generate(title, hostname, output) {
|
|
212
|
+
if (fs.existsSync(output)) return;
|
|
213
|
+
await fs.mkdir(dirname(output), { recursive: true });
|
|
214
|
+
const lines = title.trim().split(/(.{0,30})(?:\s|$)/g).filter(Boolean);
|
|
215
|
+
const data = {
|
|
216
|
+
line1: lines[0],
|
|
217
|
+
line2: lines[1],
|
|
218
|
+
line3: lines[2],
|
|
219
|
+
headline: "",
|
|
220
|
+
hostname
|
|
221
|
+
};
|
|
222
|
+
const svg = ogSVG.replace(/\{\{([^}]+)\}\}/g, (_, name) => data[name] || "");
|
|
223
|
+
console.log(`Generating ${output}`);
|
|
224
|
+
try {
|
|
225
|
+
await sharp(Buffer.from(svg)).resize(1200 * 1.1, 630 * 1.1).png().toFile(output);
|
|
226
|
+
} catch (e) {
|
|
227
|
+
console.error("Failed to generate og image", e);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function og(id, frontmatter, hostname) {
|
|
231
|
+
(() => {
|
|
232
|
+
const path = `og/${basename(id, ".md")}.png`;
|
|
233
|
+
promises.push(generate(frontmatter.title.replace(/\s-\s.*$/, "").trim(), hostname, `public/${path}`));
|
|
234
|
+
frontmatter.image = `https://${hostname}/${path}`;
|
|
235
|
+
})();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/plugins/api.ts
|
|
240
|
+
/**
|
|
241
|
+
* Recursively scan a directory for markdown files
|
|
242
|
+
*/
|
|
243
|
+
function scanMarkdownFiles(dir, baseDir) {
|
|
244
|
+
const files = [];
|
|
245
|
+
try {
|
|
246
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
247
|
+
for (const entry of entries) {
|
|
248
|
+
const fullPath = join(dir, entry.name);
|
|
249
|
+
if (entry.isDirectory()) files.push(...scanMarkdownFiles(fullPath, baseDir));
|
|
250
|
+
else if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "index.md") files.push(fullPath);
|
|
251
|
+
}
|
|
252
|
+
} catch {}
|
|
253
|
+
return files;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Process a markdown file and extract its frontmatter
|
|
257
|
+
*/
|
|
258
|
+
function processMarkdownFile(filePath, category) {
|
|
259
|
+
const { data } = matter(readFileSync(filePath, "utf-8"));
|
|
260
|
+
return {
|
|
261
|
+
path: `/${category}/${filePath.split("/").pop()?.replace(/\.md$/, "") || ""}`,
|
|
262
|
+
...data
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Generates the pages API JSON files in dist/api directory
|
|
267
|
+
*/
|
|
268
|
+
async function api(config, categories) {
|
|
269
|
+
const pagesDir = resolve(cwd(), "pages");
|
|
270
|
+
const distDir = resolve(cwd(), config.build.outDir);
|
|
271
|
+
for (const name of categories) {
|
|
272
|
+
const processedFiles = scanMarkdownFiles(join(pagesDir, name)).map((file) => processMarkdownFile(file, name));
|
|
273
|
+
const apiDir = join(distDir, "api");
|
|
274
|
+
const path = join(apiDir, `${name}.json`);
|
|
275
|
+
mkdirSync(apiDir, { recursive: true });
|
|
276
|
+
writeFileSync(path, JSON.stringify(processedFiles, null, 2));
|
|
277
|
+
config.logger.info(`${dim(`${config.build.outDir}/`)}${cyan(path.replace(`${distDir}/`, ""))}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function apiPlugin(categories = []) {
|
|
281
|
+
let config;
|
|
282
|
+
return {
|
|
283
|
+
name: "api",
|
|
284
|
+
configResolved(resolvedConfig) {
|
|
285
|
+
config = resolvedConfig;
|
|
286
|
+
},
|
|
287
|
+
closeBundle() {
|
|
288
|
+
if (this.environment.name !== "client") return;
|
|
289
|
+
if (categories.length === 0) return;
|
|
290
|
+
const time = /* @__PURE__ */ new Date();
|
|
291
|
+
config.logger.info(yellow("Generate API files"));
|
|
292
|
+
api(config, categories);
|
|
293
|
+
config.logger.info(green(`✓ generated in ${(/* @__PURE__ */ new Date()).getTime() - time.getTime()}ms`));
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/plugins/markdown.ts
|
|
300
|
+
/**
|
|
301
|
+
* Sanitize markdown content for LLM consumption
|
|
302
|
+
* - Removes HTML tags while preserving content inside paired tags
|
|
303
|
+
* - Adds title as H1 heading at the top
|
|
304
|
+
* - Safe for build-time processing
|
|
305
|
+
*/
|
|
306
|
+
function sanitizeMarkdown(content, title) {
|
|
307
|
+
let sanitized = content;
|
|
308
|
+
let prevSanitized = "";
|
|
309
|
+
while (sanitized !== prevSanitized) {
|
|
310
|
+
prevSanitized = sanitized;
|
|
311
|
+
sanitized = sanitized.replace(/<[^>]+>([^<]*)<\/[^>]+>/g, "$1");
|
|
312
|
+
sanitized = sanitized.replace(/<[^>]*>/g, "");
|
|
313
|
+
}
|
|
314
|
+
if (title) sanitized = `# ${title}\n\n${sanitized}`;
|
|
315
|
+
return sanitized.trim();
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Recursively copy and sanitize markdown files from source to target directory
|
|
319
|
+
* - Parses frontmatter and removes it
|
|
320
|
+
* - Sanitizes HTML tags
|
|
321
|
+
* - Preserves directory structure
|
|
322
|
+
* - Converts /<something>/index.md to /<something>.md (except for root /index.md)
|
|
323
|
+
*/
|
|
324
|
+
function copyAndSanitizeMarkdownFiles(config, sourceDir, targetDir, isRoot = true) {
|
|
325
|
+
const outDir = join(resolve(cwd()), config.build.outDir);
|
|
326
|
+
const entries = readdirSync(sourceDir);
|
|
327
|
+
for (const entry of entries) {
|
|
328
|
+
const sourcePath = join(sourceDir, entry);
|
|
329
|
+
if (statSync(sourcePath).isDirectory()) {
|
|
330
|
+
const newTargetDir = join(targetDir, entry);
|
|
331
|
+
if (!existsSync(newTargetDir)) mkdirSync(newTargetDir, { recursive: true });
|
|
332
|
+
copyAndSanitizeMarkdownFiles(config, sourcePath, newTargetDir, false);
|
|
333
|
+
} else if (entry.endsWith(".md")) {
|
|
334
|
+
let targetPath;
|
|
335
|
+
if (entry === "index.md" && !isRoot) {
|
|
336
|
+
const parentDirName = basename(sourceDir);
|
|
337
|
+
targetPath = join(dirname(targetDir), `${parentDirName}.md`);
|
|
338
|
+
} else targetPath = join(targetDir, entry);
|
|
339
|
+
const targetDirPath = dirname(targetPath);
|
|
340
|
+
if (!existsSync(targetDirPath)) mkdirSync(targetDirPath, { recursive: true });
|
|
341
|
+
const { data, content } = matter(readFileSync(sourcePath, "utf-8"));
|
|
342
|
+
const sanitizedContent = sanitizeMarkdown(content, data.title);
|
|
343
|
+
writeFileSync(targetPath, sanitizedContent, "utf-8");
|
|
344
|
+
config.logger.info(`${dim(`${config.build.outDir}/`)}${cyan(targetPath.replace(`${outDir}/`, ""))}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function markdownPlugin() {
|
|
349
|
+
let config;
|
|
350
|
+
return {
|
|
351
|
+
name: "markdown",
|
|
352
|
+
configResolved(resolvedConfig) {
|
|
353
|
+
config = resolvedConfig;
|
|
354
|
+
},
|
|
355
|
+
closeBundle() {
|
|
356
|
+
if (this.environment.name !== "client") return;
|
|
357
|
+
const pagesDir = resolve(cwd(), "pages");
|
|
358
|
+
const distDir = resolve(cwd(), config.build.outDir);
|
|
359
|
+
const time = /* @__PURE__ */ new Date();
|
|
360
|
+
config.logger.info(yellow("Copy and Sanitize Markdown"));
|
|
361
|
+
copyAndSanitizeMarkdownFiles(config, pagesDir, distDir);
|
|
362
|
+
config.logger.info(green(`✓ copied in ${(/* @__PURE__ */ new Date()).getTime() - time.getTime()}ms`));
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
//#endregion
|
|
368
|
+
//#region src/plugins/meta.ts
|
|
369
|
+
/**
|
|
370
|
+
* Generate a hash of the content for change detection
|
|
371
|
+
*/
|
|
372
|
+
function generateContentHash(content) {
|
|
373
|
+
return createHash("sha256").update(content).digest("hex");
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Recursively scan all markdown files and extract metadata
|
|
377
|
+
*/
|
|
378
|
+
function scanPagesForMeta(pagesDir, baseUri = "") {
|
|
379
|
+
const pages = [];
|
|
380
|
+
if (!existsSync(pagesDir)) return pages;
|
|
381
|
+
const entries = readdirSync(pagesDir);
|
|
382
|
+
for (const entry of entries) {
|
|
383
|
+
const fullPath = join(pagesDir, entry);
|
|
384
|
+
if (statSync(fullPath).isDirectory()) {
|
|
385
|
+
const subPages = scanPagesForMeta(fullPath, `${baseUri}/${entry}`);
|
|
386
|
+
pages.push(...subPages);
|
|
387
|
+
} else if (entry.endsWith(".md")) {
|
|
388
|
+
const parsed = matter(readFileSync(fullPath, "utf-8"));
|
|
389
|
+
let uri = baseUri;
|
|
390
|
+
if (entry !== "index.md") uri = joinURL(baseUri, entry.replace(/\.md$/, ""));
|
|
391
|
+
uri = withoutTrailingSlash(uri);
|
|
392
|
+
pages.push({
|
|
393
|
+
uri,
|
|
394
|
+
title: parsed.data.title,
|
|
395
|
+
description: parsed.data.description,
|
|
396
|
+
content: parsed.content,
|
|
397
|
+
id: parsed.data.id,
|
|
398
|
+
filePath: fullPath
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return pages;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Generate meta.json file with all pages metadata
|
|
406
|
+
*/
|
|
407
|
+
async function generateMeta(config, hostname) {
|
|
408
|
+
const pagesDir = resolve(cwd(), "pages");
|
|
409
|
+
const distDir = resolve(cwd(), config.build.outDir);
|
|
410
|
+
const pages = scanPagesForMeta(pagesDir).filter((page) => page.title).map((page) => ({
|
|
411
|
+
id: page.id,
|
|
412
|
+
title: page.title,
|
|
413
|
+
description: page.description,
|
|
414
|
+
uri: page.uri,
|
|
415
|
+
url: joinURL(`https://${hostname}`, page.uri),
|
|
416
|
+
hash: generateContentHash(page.content)
|
|
417
|
+
}));
|
|
418
|
+
pages.sort((a, b) => a.uri.localeCompare(b.uri));
|
|
419
|
+
const metaPath = join(distDir, "meta.json");
|
|
420
|
+
writeFileSync(metaPath, JSON.stringify(pages, null, 2));
|
|
421
|
+
config.logger.info(`${dim(`${config.build.outDir}/`)}${cyan(metaPath.replace(`${distDir}/`, ""))}`);
|
|
422
|
+
}
|
|
423
|
+
function metaPlugin(hostname) {
|
|
424
|
+
let config;
|
|
425
|
+
return {
|
|
426
|
+
name: "meta",
|
|
427
|
+
configResolved(resolvedConfig) {
|
|
428
|
+
config = resolvedConfig;
|
|
429
|
+
},
|
|
430
|
+
closeBundle() {
|
|
431
|
+
if (this.environment.name !== "client") return;
|
|
432
|
+
const time = /* @__PURE__ */ new Date();
|
|
433
|
+
config.logger.info(yellow("Generate meta.json"));
|
|
434
|
+
generateMeta(config, hostname);
|
|
435
|
+
config.logger.info(green(`✓ generated in ${(/* @__PURE__ */ new Date()).getTime() - time.getTime()}ms`));
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/sitemap.ts
|
|
442
|
+
const routes = /* @__PURE__ */ new Set();
|
|
443
|
+
function sitemap(config, hostname, routes$1) {
|
|
444
|
+
const sitemapStream = new SitemapStream({ hostname: `https://${hostname}` });
|
|
445
|
+
const writeStream = createWriteStream(join(config.build.outDir, "sitemap.xml"));
|
|
446
|
+
sitemapStream.pipe(writeStream);
|
|
447
|
+
routes$1.forEach((item) => sitemapStream.write(item));
|
|
448
|
+
sitemapStream.end();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region src/structured-data/article.ts
|
|
453
|
+
/**
|
|
454
|
+
* @see https://developer.yoast.com/features/schema/pieces/article/
|
|
455
|
+
*/
|
|
456
|
+
function article(id, structuredData$1, properties, options) {
|
|
457
|
+
const { title, description } = properties;
|
|
458
|
+
return { data: {
|
|
459
|
+
"@type": "Article",
|
|
460
|
+
"@id": joinURL(toUrl(options.hostname), "#", "schema", "Article", getUri(id)),
|
|
461
|
+
"headline": title,
|
|
462
|
+
"description": description,
|
|
463
|
+
"isPartOf": { "@id": structuredData$1.webpage.data["@id"] },
|
|
464
|
+
"mainEntityOfPage": { "@id": structuredData$1.webpage.data["@id"] },
|
|
465
|
+
"datePublished": structuredData$1.webpage.data.datePublished ? structuredData$1.webpage.data.datePublished : void 0,
|
|
466
|
+
"author": { "@id": structuredData$1.person.data["@id"] },
|
|
467
|
+
"publisher": { "@id": structuredData$1.person.data["@id"] },
|
|
468
|
+
"inLanguage": structuredData$1.webpage.data.inLanguage
|
|
469
|
+
} };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
//#endregion
|
|
473
|
+
//#region src/structured-data/breadcrumb.ts
|
|
474
|
+
/**
|
|
475
|
+
* @see https://developer.yoast.com/features/schema/pieces/breadcrumb/
|
|
476
|
+
*/
|
|
477
|
+
function breadcrumb(id, items, options) {
|
|
478
|
+
return { data: {
|
|
479
|
+
"@type": "BreadcrumbList",
|
|
480
|
+
"@id": joinURL(toUrl(options.hostname), "#", "schema", "BreadcrumbList", getUri(id)),
|
|
481
|
+
"itemListElement": items.map((item, index) => ({
|
|
482
|
+
"@type": "ListItem",
|
|
483
|
+
"position": index + 1,
|
|
484
|
+
"name": item.title,
|
|
485
|
+
...item.type && item.url ? { item: {
|
|
486
|
+
"@type": item.type,
|
|
487
|
+
"@id": item.url
|
|
488
|
+
} } : {}
|
|
489
|
+
}))
|
|
490
|
+
} };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region src/structured-data/person.ts
|
|
495
|
+
/**
|
|
496
|
+
* @see https://developer.yoast.com/features/schema/pieces/person/
|
|
497
|
+
*/
|
|
498
|
+
function person(options, personOptions) {
|
|
499
|
+
return { data: {
|
|
500
|
+
"@type": "Person",
|
|
501
|
+
"@id": joinURL(options.url, "#", "schema", "Person", "1"),
|
|
502
|
+
"name": personOptions.name,
|
|
503
|
+
"sameAs": personOptions.sameAs
|
|
504
|
+
} };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region src/structured-data/webpage.ts
|
|
509
|
+
/**
|
|
510
|
+
* @see https://developer.yoast.com/features/schema/pieces/webpage/
|
|
511
|
+
*/
|
|
512
|
+
function webpage(id, structuredData$1, properties, options) {
|
|
513
|
+
const { title, description, datePublished, keywords } = properties;
|
|
514
|
+
const canonicalUrl = getCanonicalUrl(id, options.hostname);
|
|
515
|
+
const data = {
|
|
516
|
+
"@type": "WebPage",
|
|
517
|
+
"@id": canonicalUrl,
|
|
518
|
+
"url": canonicalUrl,
|
|
519
|
+
"name": title,
|
|
520
|
+
"description": description,
|
|
521
|
+
"isPartOf": { "@id": structuredData$1.website.data["@id"] },
|
|
522
|
+
"inLanguage": "en-US",
|
|
523
|
+
"potentialAction": [{
|
|
524
|
+
"@type": "ReadAction",
|
|
525
|
+
"target": [canonicalUrl]
|
|
526
|
+
}],
|
|
527
|
+
...datePublished ? { datePublished: datePublished.toISOString() } : {},
|
|
528
|
+
...keywords ? { keywords } : {}
|
|
529
|
+
};
|
|
530
|
+
return {
|
|
531
|
+
data,
|
|
532
|
+
setBreadcrumb(breadcrumbData) {
|
|
533
|
+
data.breadcrumb = { "@id": breadcrumbData.data["@id"] };
|
|
534
|
+
},
|
|
535
|
+
setCollection() {
|
|
536
|
+
data["@type"] = "CollectionPage";
|
|
537
|
+
delete data.potentialAction;
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
//#endregion
|
|
543
|
+
//#region src/structured-data/website.ts
|
|
544
|
+
/**
|
|
545
|
+
* @see https://developer.yoast.com/features/schema/pieces/website/
|
|
546
|
+
*/
|
|
547
|
+
function website(structuredData$1, options) {
|
|
548
|
+
return { data: {
|
|
549
|
+
"@type": "WebSite",
|
|
550
|
+
"@id": joinURL(options.url, "#", "schema", "WebSite", "1"),
|
|
551
|
+
"url": options.url,
|
|
552
|
+
"name": options.name,
|
|
553
|
+
"inLanguage": ["en-US"],
|
|
554
|
+
"publisher": { "@id": structuredData$1.person.data["@id"] }
|
|
555
|
+
} };
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region src/structured-data/index.ts
|
|
560
|
+
function structuredData(id, frontmatter, options) {
|
|
561
|
+
const { name, hostname, extractPage, getPageConfig } = options;
|
|
562
|
+
const graph = {
|
|
563
|
+
"@context": "https://schema.org",
|
|
564
|
+
"@graph": []
|
|
565
|
+
};
|
|
566
|
+
const structuredDataOptions = {
|
|
567
|
+
name,
|
|
568
|
+
hostname,
|
|
569
|
+
url: toUrl(hostname)
|
|
570
|
+
};
|
|
571
|
+
const personData = person(structuredDataOptions, options.person);
|
|
572
|
+
const websiteData = website({ person: personData }, structuredDataOptions);
|
|
573
|
+
const webpageData = webpage(id, { website: websiteData }, {
|
|
574
|
+
title: frontmatter.title,
|
|
575
|
+
description: frontmatter.description,
|
|
576
|
+
datePublished: frontmatter.date ? new Date(frontmatter.date) : void 0,
|
|
577
|
+
keywords: frontmatter.tags
|
|
578
|
+
}, structuredDataOptions);
|
|
579
|
+
const page = extractPage(id);
|
|
580
|
+
const pageConfig = getPageConfig?.(page, frontmatter);
|
|
581
|
+
if (pageConfig?.type === "article") {
|
|
582
|
+
const articleData = article(id, {
|
|
583
|
+
person: personData,
|
|
584
|
+
webpage: webpageData
|
|
585
|
+
}, {
|
|
586
|
+
title: frontmatter.title,
|
|
587
|
+
description: frontmatter.description
|
|
588
|
+
}, structuredDataOptions);
|
|
589
|
+
graph["@graph"].push(articleData.data);
|
|
590
|
+
if (pageConfig.breadcrumbItems) {
|
|
591
|
+
const breadcrumbData = breadcrumb(id, pageConfig.breadcrumbItems, structuredDataOptions);
|
|
592
|
+
graph["@graph"].push(breadcrumbData.data);
|
|
593
|
+
webpageData.setBreadcrumb(breadcrumbData);
|
|
594
|
+
}
|
|
595
|
+
} else if (pageConfig?.type === "collection") webpageData.setCollection();
|
|
596
|
+
graph["@graph"].push(personData.data, websiteData.data, webpageData.data);
|
|
597
|
+
frontmatter.script ??= [];
|
|
598
|
+
frontmatter.script.push({
|
|
599
|
+
type: "application/ld+json",
|
|
600
|
+
innerHTML: JSON.stringify(graph)
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region vite.config.ts
|
|
606
|
+
var vite_config_default = (title, hostname, options, config = {}) => mergeConfig(defineConfig({
|
|
607
|
+
plugins: [
|
|
608
|
+
vueRouter({
|
|
609
|
+
extensions: [".vue", ".md"],
|
|
610
|
+
routesFolder: "pages",
|
|
611
|
+
dts: "src/typed-router.d.ts",
|
|
612
|
+
extendRoute(route) {
|
|
613
|
+
const path = route.components.get("default");
|
|
614
|
+
if (!path) return;
|
|
615
|
+
if (path.endsWith(".vue")) route.addToMeta({ frontmatter: { page: options.extractPage(path) } });
|
|
616
|
+
if (path.endsWith(".md")) {
|
|
617
|
+
const { data } = matter(readFileSync(path, "utf-8"));
|
|
618
|
+
route.addToMeta({ frontmatter: data });
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}),
|
|
622
|
+
vue({ include: [/\.vue$/, /\.md$/] }),
|
|
623
|
+
ui({
|
|
624
|
+
autoImport: {
|
|
625
|
+
dts: "src/auto-imports.d.ts",
|
|
626
|
+
dirs: ["src/composables"],
|
|
627
|
+
imports: [
|
|
628
|
+
"vue",
|
|
629
|
+
"vue-router",
|
|
630
|
+
"@vueuse/core",
|
|
631
|
+
unheadVueComposablesImports,
|
|
632
|
+
{
|
|
633
|
+
from: "tailwind-variants",
|
|
634
|
+
imports: ["tv"]
|
|
635
|
+
},
|
|
636
|
+
soubiranComposablesImports
|
|
637
|
+
]
|
|
638
|
+
},
|
|
639
|
+
components: {
|
|
640
|
+
include: [
|
|
641
|
+
/\.vue$/,
|
|
642
|
+
/\.vue\?vue/,
|
|
643
|
+
/\.md$/
|
|
644
|
+
],
|
|
645
|
+
dts: "src/components.d.ts",
|
|
646
|
+
resolvers: [soubiranResolver()]
|
|
647
|
+
},
|
|
648
|
+
ui: { colors: { neutral: "neutral" } }
|
|
649
|
+
}),
|
|
650
|
+
markdown({
|
|
651
|
+
headEnabled: true,
|
|
652
|
+
wrapperClasses: [
|
|
653
|
+
"slide-enter-content",
|
|
654
|
+
"max-w-none",
|
|
655
|
+
"prose prose-neutral dark:prose-invert",
|
|
656
|
+
"prose-headings:text-default prose-h2:text-[1.125em] prose-h2:mb-[0.5em] prose-h3:text-[1em]",
|
|
657
|
+
"prose-p:my-[1em] dark:prose-p:text-muted",
|
|
658
|
+
"dark:prose-ul:text-muted dark:prose-ol:text-muted",
|
|
659
|
+
"dark:prose-strong:text-default",
|
|
660
|
+
"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)]",
|
|
661
|
+
"prose-hr:max-w-1/2 prose-hr:mx-auto prose-hr:my-[2em]",
|
|
662
|
+
"prose-figure:bg-neutral-100 dark:prose-figure:bg-neutral-800 prose-figure:rounded-lg",
|
|
663
|
+
"prose-img:rounded-lg prose-img:border prose-img:border-accented prose-img:shadow-md",
|
|
664
|
+
"prose-video:rounded-lg prose-video:border prose-video:border-accented prose-video:shadow-md",
|
|
665
|
+
"prose-figcaption:text-center prose-figcaption:py-1 prose-figcaption:m-0",
|
|
666
|
+
"[&_:first-child]:mt-0 [&_:last-child]:mb-0"
|
|
667
|
+
],
|
|
668
|
+
transforms: options.markdown?.transforms ?? {},
|
|
669
|
+
wrapperComponent: options.markdown?.wrapperComponent,
|
|
670
|
+
async markdownItSetup(md) {
|
|
671
|
+
githubAlerts(md);
|
|
672
|
+
implicitFiguresRule(md);
|
|
673
|
+
linkAttributesRule(md);
|
|
674
|
+
tableOfContentsRule(md);
|
|
675
|
+
customLink(md, hostname);
|
|
676
|
+
customImage(md, hostname);
|
|
677
|
+
await shikiHighlight(md);
|
|
678
|
+
},
|
|
679
|
+
frontmatterPreprocess(frontmatter, frontmatterOptions, id, defaults) {
|
|
680
|
+
createAssert(options.seo.assert?.rules)(id, frontmatter);
|
|
681
|
+
og(id, frontmatter, hostname);
|
|
682
|
+
canonical(id, frontmatter, hostname);
|
|
683
|
+
structuredData(id, frontmatter, {
|
|
684
|
+
name: title,
|
|
685
|
+
hostname,
|
|
686
|
+
person: options.seo.person,
|
|
687
|
+
extractPage: options.extractPage,
|
|
688
|
+
getPageConfig: options.seo.structuredData?.pageConfig
|
|
689
|
+
});
|
|
690
|
+
frontmatter.page = options.extractPage(id);
|
|
691
|
+
return {
|
|
692
|
+
head: defaults(frontmatter, frontmatterOptions),
|
|
693
|
+
frontmatter
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
}),
|
|
697
|
+
fonts({ google: { families: [
|
|
698
|
+
{
|
|
699
|
+
name: "DM Sans",
|
|
700
|
+
styles: "ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000"
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
name: "DM Mono",
|
|
704
|
+
styles: "ital,wght@0,300;0,400;0,500;1,300;1,400;1,500"
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
name: "Sofia Sans",
|
|
708
|
+
styles: "ital,wght@0,1..1000;1,1..1000"
|
|
709
|
+
}
|
|
710
|
+
] } }),
|
|
711
|
+
icons({ autoInstall: true }),
|
|
712
|
+
apiPlugin(options.apiCategories),
|
|
713
|
+
markdownPlugin(),
|
|
714
|
+
metaPlugin(hostname),
|
|
715
|
+
{
|
|
716
|
+
name: "await",
|
|
717
|
+
async closeBundle() {
|
|
718
|
+
await resolveAll();
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
name: "extract-config",
|
|
723
|
+
configResolved(resolvedConfig) {
|
|
724
|
+
Object.assign(config, resolvedConfig);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
],
|
|
728
|
+
optimizeDeps: {
|
|
729
|
+
include: [
|
|
730
|
+
"vue",
|
|
731
|
+
"ofetch",
|
|
732
|
+
"reka-ui",
|
|
733
|
+
"vue-router",
|
|
734
|
+
"@unhead/vue",
|
|
735
|
+
"partysocket",
|
|
736
|
+
"@iconify/vue"
|
|
737
|
+
],
|
|
738
|
+
exclude: ["@soubiran/ui"]
|
|
739
|
+
},
|
|
740
|
+
resolve: { alias: { "@": resolve("./src") } },
|
|
741
|
+
ssgOptions: {
|
|
742
|
+
formatting: "minify",
|
|
743
|
+
onPageRendered(route, renderedHTML) {
|
|
744
|
+
routes.add(route);
|
|
745
|
+
return renderedHTML;
|
|
746
|
+
},
|
|
747
|
+
onFinished() {
|
|
748
|
+
sitemap(config, hostname, Array.from(routes));
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}), config);
|
|
752
|
+
|
|
753
|
+
//#endregion
|
|
754
|
+
export { vite_config_default as default, getUri };
|
package/package.json
CHANGED
|
@@ -1,4 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soubiran/vite",
|
|
3
|
-
"
|
|
4
|
-
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.3",
|
|
5
|
+
"author": "Estéban Soubiran <esteban@soubiran.dev>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"funding": "https://github.com/sponsors/Barbapapazes",
|
|
8
|
+
"homepage": "https://github.com/Barbapapazes/.soubiran.dev",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/Barbapapazes/.soubiran.dev"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/Barbapapazes/.soubiran.dev/issues",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/vite.config.d.ts",
|
|
17
|
+
"import": "./dist/vite.config.mjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"main": "dist/vite.config.mjs",
|
|
21
|
+
"types": "dist/vite.config.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@nuxt/ui": "^4.2.0",
|
|
27
|
+
"@shikijs/markdown-it": "^3.15.0",
|
|
28
|
+
"@types/fs-extra": "^11.0.4",
|
|
29
|
+
"@types/markdown-it": "^14.1.2",
|
|
30
|
+
"@types/markdown-it-link-attributes": "^3.0.5",
|
|
31
|
+
"@types/node": "^24.10.1",
|
|
32
|
+
"@unhead/vue": "^2.0.19",
|
|
33
|
+
"@unpic/placeholder": "^0.1.2",
|
|
34
|
+
"@vitejs/plugin-vue": "^6.0.2",
|
|
35
|
+
"ansis": "^4.2.0",
|
|
36
|
+
"fs-extra": "^11.3.2",
|
|
37
|
+
"gray-matter": "^4.0.3",
|
|
38
|
+
"markdown-it": "^14.1.0",
|
|
39
|
+
"markdown-it-async": "^2.2.0",
|
|
40
|
+
"markdown-it-github-alerts": "^1.0.0",
|
|
41
|
+
"markdown-it-image-figures": "^2.1.1",
|
|
42
|
+
"markdown-it-link-attributes": "^4.0.1",
|
|
43
|
+
"markdown-it-table-of-contents": "^1.1.0",
|
|
44
|
+
"sharp": "^0.34.5",
|
|
45
|
+
"shiki": "^3.15.0",
|
|
46
|
+
"sitemap": "^8.0.2",
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"ufo": "^1.6.1",
|
|
49
|
+
"unhead": "^2.0.19",
|
|
50
|
+
"unplugin-fonts": "^1.4.0",
|
|
51
|
+
"unplugin-icons": "^22.5.0",
|
|
52
|
+
"unplugin-vue-markdown": "^29.2.0",
|
|
53
|
+
"unplugin-vue-router": "^0.16.2",
|
|
54
|
+
"vite": "npm:rolldown-vite@7.1.20",
|
|
55
|
+
"vite-ssg": "^28.2.2",
|
|
56
|
+
"vue-router": "^4.6.3",
|
|
57
|
+
"@soubiran/ui": "0.1.3"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"tsdown": "^0.18.3"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsdown"
|
|
64
|
+
}
|
|
65
|
+
}
|