@slidev-react/node 0.1.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/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/build.d.mts +10 -0
- package/dist/build.mjs +28 -0
- package/dist/cli/buildArgs.mjs +78 -0
- package/dist/cli/devArgs.mjs +74 -0
- package/dist/cli/exportArgs.mjs +87 -0
- package/dist/cli/lintArgs.mjs +32 -0
- package/dist/cli/readOptionValue.mjs +16 -0
- package/dist/context.d.mts +11 -0
- package/dist/context.mjs +37 -0
- package/dist/dev.d.mts +13 -0
- package/dist/dev.mjs +61 -0
- package/dist/export.d.mts +11 -0
- package/dist/export.mjs +67 -0
- package/dist/exportBrowser.d.mts +8 -0
- package/dist/exportBrowser.mjs +123 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +5 -0
- package/dist/lint.d.mts +15 -0
- package/dist/lint.mjs +36 -0
- package/dist/slides/build/createSlidesViteConfig.d.mts +9 -0
- package/dist/slides/build/createSlidesViteConfig.mjs +25 -0
- package/dist/slides/build/generateCompiledSlides.mjs +264 -0
- package/dist/slides/build/slidesSourceFile.mjs +9 -0
- package/dist/slides/compiling/mdx-options.mjs +18 -0
- package/dist/slides/compiling/rehypeShikiVitesse.mjs +93 -0
- package/dist/slides/mdx/remarkDiagramComponents.mjs +38 -0
- package/dist/slides/parsing/frontmatter.mjs +29 -0
- package/dist/slides/parsing/parseSlides.mjs +159 -0
- package/dist/slides/validation/validateSlidesAuthoring.mjs +93 -0
- package/package.json +57 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { parseFrontmatter } from "./frontmatter.mjs";
|
|
2
|
+
import { resolveSlidesViewportMeta } from "@slidev-react/core/slides/viewport";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { transitionNames } from "@slidev-react/core/slides/transition";
|
|
5
|
+
//#region src/slides/parsing/parseSlides.ts
|
|
6
|
+
const layoutSchema = z.string().trim().min(1, "Layout name cannot be empty");
|
|
7
|
+
const transitionSchema = z.enum(transitionNames);
|
|
8
|
+
const addonsSchema = z.union([z.string(), z.array(z.string())]).optional().transform((value) => {
|
|
9
|
+
if (!value) return void 0;
|
|
10
|
+
const list = (Array.isArray(value) ? value : [value]).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
11
|
+
return list.length > 0 ? [...new Set(list)] : void 0;
|
|
12
|
+
});
|
|
13
|
+
const notesSchema = z.string().transform((value) => value.trim()).optional().transform((value) => value && value.length > 0 ? value : void 0);
|
|
14
|
+
const slidesMetaSchema = z.object({
|
|
15
|
+
title: z.string().optional(),
|
|
16
|
+
theme: z.string().optional(),
|
|
17
|
+
addons: addonsSchema,
|
|
18
|
+
layout: layoutSchema.optional(),
|
|
19
|
+
background: z.string().optional(),
|
|
20
|
+
transition: transitionSchema.optional(),
|
|
21
|
+
exportFilename: z.string().optional(),
|
|
22
|
+
ar: z.string().optional()
|
|
23
|
+
});
|
|
24
|
+
const slideMetaSchema = z.object({
|
|
25
|
+
title: z.string().optional(),
|
|
26
|
+
layout: layoutSchema.optional(),
|
|
27
|
+
class: z.string().optional(),
|
|
28
|
+
background: z.string().optional(),
|
|
29
|
+
transition: transitionSchema.optional(),
|
|
30
|
+
clicks: z.number().int().nonnegative().optional(),
|
|
31
|
+
notes: notesSchema,
|
|
32
|
+
src: z.string().optional()
|
|
33
|
+
});
|
|
34
|
+
const codeFenceStartRE = /^(`{3,}|~{3,})/;
|
|
35
|
+
function isLikelyYamlMeta(lines) {
|
|
36
|
+
const candidate = lines.join("\n").trim();
|
|
37
|
+
if (!candidate) return false;
|
|
38
|
+
try {
|
|
39
|
+
const parsed = parseFrontmatter(`---\n${candidate}\n---`).data;
|
|
40
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed);
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function findFrontmatterCloseLine(lines, start) {
|
|
46
|
+
for (let index = start + 1; index < lines.length; index += 1) if (lines[index].trim() === "---") return index;
|
|
47
|
+
return -1;
|
|
48
|
+
}
|
|
49
|
+
function splitSlides(content) {
|
|
50
|
+
const lines = content.split("\n");
|
|
51
|
+
const slides = [];
|
|
52
|
+
let current = [];
|
|
53
|
+
let atSlideStart = true;
|
|
54
|
+
let inCodeFence = false;
|
|
55
|
+
let codeFenceToken = null;
|
|
56
|
+
let inSlideFrontmatter = false;
|
|
57
|
+
const flush = () => {
|
|
58
|
+
const source = current.join("\n").trim();
|
|
59
|
+
if (source) slides.push(source);
|
|
60
|
+
current = [];
|
|
61
|
+
atSlideStart = true;
|
|
62
|
+
inCodeFence = false;
|
|
63
|
+
codeFenceToken = null;
|
|
64
|
+
inSlideFrontmatter = false;
|
|
65
|
+
};
|
|
66
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
67
|
+
const line = lines[index];
|
|
68
|
+
const trimmed = line.trim();
|
|
69
|
+
if (inSlideFrontmatter) {
|
|
70
|
+
current.push(line);
|
|
71
|
+
if (trimmed === "---") {
|
|
72
|
+
inSlideFrontmatter = false;
|
|
73
|
+
atSlideStart = false;
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (!inCodeFence) {
|
|
78
|
+
const match = trimmed.match(codeFenceStartRE);
|
|
79
|
+
if (match) {
|
|
80
|
+
inCodeFence = true;
|
|
81
|
+
codeFenceToken = match[1];
|
|
82
|
+
}
|
|
83
|
+
} else if (codeFenceToken && trimmed.startsWith(codeFenceToken)) {
|
|
84
|
+
inCodeFence = false;
|
|
85
|
+
codeFenceToken = null;
|
|
86
|
+
}
|
|
87
|
+
if (!inCodeFence) {
|
|
88
|
+
if (atSlideStart && trimmed === "---") {
|
|
89
|
+
inSlideFrontmatter = true;
|
|
90
|
+
current.push(line);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (trimmed === "---") {
|
|
94
|
+
flush();
|
|
95
|
+
const closeLine = findFrontmatterCloseLine(lines, index);
|
|
96
|
+
if (closeLine > index + 1 && isLikelyYamlMeta(lines.slice(index + 1, closeLine))) {
|
|
97
|
+
inSlideFrontmatter = true;
|
|
98
|
+
current.push(line);
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
current.push(line);
|
|
104
|
+
if (trimmed !== "") atSlideStart = false;
|
|
105
|
+
}
|
|
106
|
+
flush();
|
|
107
|
+
return slides;
|
|
108
|
+
}
|
|
109
|
+
function createSlideUnits(slideSources) {
|
|
110
|
+
return slideSources.map((slide, index) => {
|
|
111
|
+
const slideMatter = parseFrontmatter(slide);
|
|
112
|
+
const slideMeta = parseSlideMeta(slideMatter.data, index);
|
|
113
|
+
const trimmedSource = slideMatter.content.trim();
|
|
114
|
+
return {
|
|
115
|
+
id: `slide-${index + 1}`,
|
|
116
|
+
index,
|
|
117
|
+
meta: slideMeta,
|
|
118
|
+
source: trimmedSource || "# Empty slide",
|
|
119
|
+
hasInlineSource: trimmedSource.length > 0
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function parseSlidesMeta(data) {
|
|
124
|
+
const parsed = slidesMetaSchema.safeParse(data);
|
|
125
|
+
if (!parsed.success) throw new Error(`Invalid slides frontmatter: ${formatZodIssues(parsed.error)}`);
|
|
126
|
+
return {
|
|
127
|
+
...parsed.data,
|
|
128
|
+
...resolveSlidesViewportMeta(parsed.data.ar)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function formatZodIssues(error) {
|
|
132
|
+
return error.issues.map((issue) => {
|
|
133
|
+
return `${issue.path.length > 0 ? `${issue.path.join(".")}: ` : ""}${issue.message}`;
|
|
134
|
+
}).join("; ");
|
|
135
|
+
}
|
|
136
|
+
function parseSlideMeta(data, slideIndex) {
|
|
137
|
+
const parsed = slideMetaSchema.safeParse(data);
|
|
138
|
+
if (!parsed.success) {
|
|
139
|
+
const slideLabel = typeof slideIndex === "number" ? `slide ${slideIndex + 1}` : String(slideIndex);
|
|
140
|
+
throw new Error(`Invalid frontmatter in ${slideLabel}: ${formatZodIssues(parsed.error)}`);
|
|
141
|
+
}
|
|
142
|
+
return parsed.data;
|
|
143
|
+
}
|
|
144
|
+
function parseSlides(source) {
|
|
145
|
+
const slidesMatter = parseFrontmatter(source.replace(/\r\n/g, "\n").trim());
|
|
146
|
+
const meta = parseSlidesMeta(slidesMatter.data);
|
|
147
|
+
const rawSlides = splitSlides(slidesMatter.content);
|
|
148
|
+
return {
|
|
149
|
+
meta,
|
|
150
|
+
slides: createSlideUnits(rawSlides.length > 0 ? rawSlides : ["# Empty slides"])
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function parseImportedSlides(source) {
|
|
154
|
+
const slideSources = splitSlides(source.replace(/\r\n/g, "\n").trim());
|
|
155
|
+
if (slideSources.length === 0) return createSlideUnits(["# Empty slide"]);
|
|
156
|
+
return createSlideUnits(slideSources);
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
export { parseImportedSlides, parseSlides };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import { layoutNames } from "@slidev-react/core/slides/layout";
|
|
4
|
+
//#region src/slides/validation/validateSlidesAuthoring.ts
|
|
5
|
+
const CLIENT_THEME_THEMES_DIR = "packages/client/src/theme/themes";
|
|
6
|
+
const CLIENT_ADDONS_DIR = "packages/client/src/addons";
|
|
7
|
+
function collectKnownLayouts() {
|
|
8
|
+
return new Set(layoutNames);
|
|
9
|
+
}
|
|
10
|
+
function formatSlideLabel(slide) {
|
|
11
|
+
return slide.meta.title ? `slide ${slide.index + 1} (${slide.meta.title})` : `slide ${slide.index + 1}`;
|
|
12
|
+
}
|
|
13
|
+
async function readLocalIds(rootDir) {
|
|
14
|
+
try {
|
|
15
|
+
return (await readdir(rootDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
16
|
+
} catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function findDefinitionFile(rootDir) {
|
|
21
|
+
for (const candidate of [
|
|
22
|
+
"index.ts",
|
|
23
|
+
"index.tsx",
|
|
24
|
+
"index.js",
|
|
25
|
+
"index.jsx"
|
|
26
|
+
]) {
|
|
27
|
+
const filePath = path.join(rootDir, candidate);
|
|
28
|
+
try {
|
|
29
|
+
await readFile(filePath, "utf8");
|
|
30
|
+
return filePath;
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function extractObjectLiteralKeys(source, propertyName) {
|
|
36
|
+
const propertyIndex = source.indexOf(`${propertyName}:`);
|
|
37
|
+
if (propertyIndex === -1) return [];
|
|
38
|
+
const objectStart = source.indexOf("{", propertyIndex);
|
|
39
|
+
if (objectStart === -1) return [];
|
|
40
|
+
let depth = 0;
|
|
41
|
+
let objectEnd = -1;
|
|
42
|
+
for (let index = objectStart; index < source.length; index += 1) {
|
|
43
|
+
const char = source[index];
|
|
44
|
+
if (char === "{") depth += 1;
|
|
45
|
+
if (char === "}") depth -= 1;
|
|
46
|
+
if (depth === 0) {
|
|
47
|
+
objectEnd = index;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (objectEnd === -1) return [];
|
|
52
|
+
return [...source.slice(objectStart + 1, objectEnd).matchAll(/(?:^|\n|\s)(["']?[\w-]+["']?)\s*:/g)].map((match) => match[1]?.replace(/^["']|["']$/g, "")).filter((key) => Boolean(key));
|
|
53
|
+
}
|
|
54
|
+
async function readCustomLayoutIds(rootDir) {
|
|
55
|
+
const ids = await readLocalIds(rootDir);
|
|
56
|
+
const layouts = /* @__PURE__ */ new Set();
|
|
57
|
+
await Promise.all(ids.map(async (id) => {
|
|
58
|
+
const definitionFile = await findDefinitionFile(path.join(rootDir, id));
|
|
59
|
+
if (!definitionFile) return;
|
|
60
|
+
try {
|
|
61
|
+
const source = await readFile(definitionFile, "utf8");
|
|
62
|
+
for (const layoutName of extractObjectLiteralKeys(source, "layouts")) layouts.add(layoutName);
|
|
63
|
+
} catch {}
|
|
64
|
+
}));
|
|
65
|
+
return layouts;
|
|
66
|
+
}
|
|
67
|
+
async function validateSlidesAuthoring({ appRoot, slides }) {
|
|
68
|
+
const warnings = [];
|
|
69
|
+
const themeRootDir = path.join(appRoot, CLIENT_THEME_THEMES_DIR);
|
|
70
|
+
const addonsRootDir = path.join(appRoot, CLIENT_ADDONS_DIR);
|
|
71
|
+
const [themeIdList, addonIdList, themeLayouts, addonLayouts] = await Promise.all([
|
|
72
|
+
readLocalIds(themeRootDir),
|
|
73
|
+
readLocalIds(addonsRootDir),
|
|
74
|
+
readCustomLayoutIds(themeRootDir),
|
|
75
|
+
readCustomLayoutIds(addonsRootDir)
|
|
76
|
+
]);
|
|
77
|
+
const themeIds = new Set(themeIdList);
|
|
78
|
+
const addonIds = new Set(addonIdList);
|
|
79
|
+
const knownLayouts = collectKnownLayouts();
|
|
80
|
+
for (const layoutName of themeLayouts) knownLayouts.add(layoutName);
|
|
81
|
+
for (const layoutName of addonLayouts) knownLayouts.add(layoutName);
|
|
82
|
+
if (slides.meta.theme && !themeIds.has(slides.meta.theme)) warnings.push(`Unknown theme "${slides.meta.theme}". The runtime will fall back to the default theme.`);
|
|
83
|
+
for (const addonId of slides.meta.addons ?? []) if (!addonIds.has(addonId)) warnings.push(`Unknown addon "${addonId}". It will be ignored until a matching local addon exists.`);
|
|
84
|
+
if (slides.meta.layout && !knownLayouts.has(slides.meta.layout)) warnings.push(`Unknown slides layout "${slides.meta.layout}". The runtime will fall back to the default layout.`);
|
|
85
|
+
for (const slide of slides.slides) {
|
|
86
|
+
const slideLayout = slide.meta.layout;
|
|
87
|
+
if (!slideLayout || knownLayouts.has(slideLayout)) continue;
|
|
88
|
+
warnings.push(`Unknown layout "${slideLayout}" in ${formatSlideLabel(slide)}. The runtime will fall back to the default layout.`);
|
|
89
|
+
}
|
|
90
|
+
return warnings;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { validateSlidesAuthoring };
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@slidev-react/node",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Node-side command APIs for slidev-react authoring and build workflows",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"types": "./dist/index.d.mts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.mts",
|
|
16
|
+
"import": "./dist/index.mjs"
|
|
17
|
+
},
|
|
18
|
+
"./slides/build/createSlidesViteConfig": {
|
|
19
|
+
"types": "./dist/slides/build/createSlidesViteConfig.d.mts",
|
|
20
|
+
"import": "./dist/slides/build/createSlidesViteConfig.mjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@mdx-js/mdx": "3.1.1",
|
|
25
|
+
"@mdx-js/react": "3.1.1",
|
|
26
|
+
"@playwright/test": "^1.58.2",
|
|
27
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
28
|
+
"react": "19.2.3",
|
|
29
|
+
"rehype-katex": "7.0.1",
|
|
30
|
+
"remark-gfm": "4.0.1",
|
|
31
|
+
"remark-math": "6.0.0",
|
|
32
|
+
"shiki": "4.0.1",
|
|
33
|
+
"vite": "^7.3.1",
|
|
34
|
+
"yaml": "2.8.2",
|
|
35
|
+
"zod": "4.3.6",
|
|
36
|
+
"@slidev-react/core": "0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+ssh://git@github.com/hylarucoder/slidev-react.git",
|
|
44
|
+
"directory": "packages/node"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/hylarucoder/slidev-react/tree/main/packages/node",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/hylarucoder/slidev-react/issues"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=22"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build:pkg": "pnpm exec tsdown",
|
|
55
|
+
"dev:pkg": "pnpm exec tsdown --watch"
|
|
56
|
+
}
|
|
57
|
+
}
|