@astrojs/markdoc 0.0.0-head-prop-20230323183456
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/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +27 -0
- package/LICENSE +61 -0
- package/README.md +348 -0
- package/components/RenderNode.astro +30 -0
- package/components/Renderer.astro +21 -0
- package/components/astroNode.ts +51 -0
- package/components/index.ts +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +104 -0
- package/dist/utils.d.ts +50 -0
- package/dist/utils.js +92 -0
- package/package.json +54 -0
- package/src/index.ts +122 -0
- package/src/utils.ts +147 -0
- package/template/content-module-types.d.ts +9 -0
- package/test/content-collections.test.js +258 -0
- package/test/fixtures/content-collections/astro.config.mjs +7 -0
- package/test/fixtures/content-collections/node_modules/.bin/astro +17 -0
- package/test/fixtures/content-collections/package.json +13 -0
- package/test/fixtures/content-collections/src/components/Code.astro +12 -0
- package/test/fixtures/content-collections/src/components/CustomMarquee.astro +1 -0
- package/test/fixtures/content-collections/src/content/blog/simple.mdoc +7 -0
- package/test/fixtures/content-collections/src/content/blog/with-components.mdoc +17 -0
- package/test/fixtures/content-collections/src/content/blog/with-config.mdoc +9 -0
- package/test/fixtures/content-collections/src/content/config.ts +12 -0
- package/test/fixtures/content-collections/src/pages/collection.json.js +10 -0
- package/test/fixtures/content-collections/src/pages/content-simple.astro +18 -0
- package/test/fixtures/content-collections/src/pages/content-with-components.astro +23 -0
- package/test/fixtures/content-collections/src/pages/content-with-config.astro +19 -0
- package/test/fixtures/content-collections/src/pages/entry.json.js +10 -0
- package/test/fixtures/content-collections/utils.js +8 -0
- package/tsconfig.json +10 -0
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { AstroInstance } from 'astro';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import type fsMod from 'node:fs';
|
|
5
|
+
/**
|
|
6
|
+
* Match YAML exception handling from Astro core errors
|
|
7
|
+
* @see 'astro/src/core/errors.ts'
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseFrontmatter(fileContents: string, filePath: string): matter.GrayMatterFile<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Matches AstroError object with types like error codes stubbed out
|
|
12
|
+
* @see 'astro/src/core/errors/errors.ts'
|
|
13
|
+
*/
|
|
14
|
+
export declare class MarkdocError extends Error {
|
|
15
|
+
errorCode: number;
|
|
16
|
+
loc: ErrorLocation | undefined;
|
|
17
|
+
title: string | undefined;
|
|
18
|
+
hint: string | undefined;
|
|
19
|
+
frame: string | undefined;
|
|
20
|
+
type: string;
|
|
21
|
+
constructor(props: ErrorProperties, ...params: any);
|
|
22
|
+
}
|
|
23
|
+
interface ErrorLocation {
|
|
24
|
+
file?: string;
|
|
25
|
+
line?: number;
|
|
26
|
+
column?: number;
|
|
27
|
+
}
|
|
28
|
+
interface ErrorProperties {
|
|
29
|
+
code?: number;
|
|
30
|
+
title?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
message?: string;
|
|
33
|
+
location?: ErrorLocation;
|
|
34
|
+
hint?: string;
|
|
35
|
+
stack?: string;
|
|
36
|
+
frame?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Matches `search` function used for resolving `astro.config` files.
|
|
40
|
+
* Used by Markdoc for error handling.
|
|
41
|
+
* @see 'astro/src/core/config/config.ts'
|
|
42
|
+
*/
|
|
43
|
+
export declare function getAstroConfigPath(fs: typeof fsMod, root: string): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* @see 'astro/src/core/path.ts'
|
|
46
|
+
*/
|
|
47
|
+
export declare function prependForwardSlash(str: string): string;
|
|
48
|
+
export declare function validateComponentsProp(components: Record<string, AstroInstance['default']>): void;
|
|
49
|
+
export declare function isCapitalized(str: string): boolean;
|
|
50
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import z from "astro/zod";
|
|
2
|
+
import matter from "gray-matter";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
function parseFrontmatter(fileContents, filePath) {
|
|
5
|
+
try {
|
|
6
|
+
matter.clearCache();
|
|
7
|
+
return matter(fileContents);
|
|
8
|
+
} catch (e) {
|
|
9
|
+
if (e.name === "YAMLException") {
|
|
10
|
+
const err = e;
|
|
11
|
+
err.id = filePath;
|
|
12
|
+
err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
|
|
13
|
+
err.message = e.reason;
|
|
14
|
+
throw err;
|
|
15
|
+
} else {
|
|
16
|
+
throw e;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
class MarkdocError extends Error {
|
|
21
|
+
constructor(props, ...params) {
|
|
22
|
+
super(...params);
|
|
23
|
+
this.type = "MarkdocError";
|
|
24
|
+
const {
|
|
25
|
+
code = 99999,
|
|
26
|
+
name,
|
|
27
|
+
title = "MarkdocError",
|
|
28
|
+
message,
|
|
29
|
+
stack,
|
|
30
|
+
location,
|
|
31
|
+
hint,
|
|
32
|
+
frame
|
|
33
|
+
} = props;
|
|
34
|
+
this.errorCode = code;
|
|
35
|
+
this.title = title;
|
|
36
|
+
if (message)
|
|
37
|
+
this.message = message;
|
|
38
|
+
this.stack = stack ? stack : this.stack;
|
|
39
|
+
this.loc = location;
|
|
40
|
+
this.hint = hint;
|
|
41
|
+
this.frame = frame;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function getAstroConfigPath(fs, root) {
|
|
45
|
+
const paths = [
|
|
46
|
+
"astro.config.mjs",
|
|
47
|
+
"astro.config.js",
|
|
48
|
+
"astro.config.ts",
|
|
49
|
+
"astro.config.mts",
|
|
50
|
+
"astro.config.cjs",
|
|
51
|
+
"astro.config.cts"
|
|
52
|
+
].map((p) => path.join(root, p));
|
|
53
|
+
for (const file of paths) {
|
|
54
|
+
if (fs.existsSync(file)) {
|
|
55
|
+
return file;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function prependForwardSlash(str) {
|
|
60
|
+
return str[0] === "/" ? str : "/" + str;
|
|
61
|
+
}
|
|
62
|
+
function validateComponentsProp(components) {
|
|
63
|
+
try {
|
|
64
|
+
componentsPropValidator.parse(components);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw new MarkdocError({
|
|
67
|
+
message: e instanceof z.ZodError ? e.issues[0].message : "Invalid `components` prop. Ensure you are passing an object of components to <Content />"
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const componentsPropValidator = z.record(
|
|
72
|
+
z.string().min(1, "Invalid `components` prop. Component names cannot be empty!").refine(
|
|
73
|
+
(value) => isCapitalized(value),
|
|
74
|
+
(value) => ({
|
|
75
|
+
message: `Invalid \`components\` prop: ${JSON.stringify(
|
|
76
|
+
value
|
|
77
|
+
)}. Component name must be capitalized. If you want to render HTML elements as components, try using a Markdoc node (https://docs.astro.build/en/guides/integrations-guide/markdoc/#render-markdoc-nodes--html-elements-as-astro-components)`
|
|
78
|
+
})
|
|
79
|
+
),
|
|
80
|
+
z.any()
|
|
81
|
+
);
|
|
82
|
+
function isCapitalized(str) {
|
|
83
|
+
return str.length > 0 && str[0] === str[0].toUpperCase();
|
|
84
|
+
}
|
|
85
|
+
export {
|
|
86
|
+
MarkdocError,
|
|
87
|
+
getAstroConfigPath,
|
|
88
|
+
isCapitalized,
|
|
89
|
+
parseFrontmatter,
|
|
90
|
+
prependForwardSlash,
|
|
91
|
+
validateComponentsProp
|
|
92
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@astrojs/markdoc",
|
|
3
|
+
"description": "Add support for Markdoc pages in your Astro site",
|
|
4
|
+
"version": "0.0.0-head-prop-20230323183456",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"author": "withastro",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/withastro/astro.git",
|
|
12
|
+
"directory": "packages/integrations/markdoc"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"astro-integration",
|
|
16
|
+
"astro-component",
|
|
17
|
+
"markdoc"
|
|
18
|
+
],
|
|
19
|
+
"bugs": "https://github.com/withastro/astro/issues",
|
|
20
|
+
"homepage": "https://docs.astro.build/en/guides/integrations-guide/markdoc/",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./dist/index.js",
|
|
23
|
+
"./components": "./components/index.ts",
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@markdoc/markdoc": "^0.2.2",
|
|
28
|
+
"gray-matter": "^4.0.3",
|
|
29
|
+
"stringify-attributes": "^3.0.0",
|
|
30
|
+
"zod": "^3.17.3"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/chai": "^4.3.1",
|
|
34
|
+
"@types/html-escaper": "^3.0.0",
|
|
35
|
+
"@types/mocha": "^9.1.1",
|
|
36
|
+
"astro": "0.0.0-head-prop-20230323183456",
|
|
37
|
+
"astro-scripts": "0.0.14",
|
|
38
|
+
"chai": "^4.3.6",
|
|
39
|
+
"devalue": "^4.2.0",
|
|
40
|
+
"linkedom": "^0.14.12",
|
|
41
|
+
"mocha": "^9.2.2",
|
|
42
|
+
"vite": "^4.0.3"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=16.12.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
|
49
|
+
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
|
50
|
+
"dev": "astro-scripts dev \"src/**/*.ts\"",
|
|
51
|
+
"test": "mocha --exit --timeout 20000",
|
|
52
|
+
"test:match": "mocha --timeout 20000 -g"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Config } from '@markdoc/markdoc';
|
|
2
|
+
import Markdoc from '@markdoc/markdoc';
|
|
3
|
+
import type { AstroConfig, AstroIntegration, ContentEntryType, HookParameters } from 'astro';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import type { InlineConfig } from 'vite';
|
|
7
|
+
import {
|
|
8
|
+
getAstroConfigPath,
|
|
9
|
+
MarkdocError,
|
|
10
|
+
parseFrontmatter,
|
|
11
|
+
prependForwardSlash,
|
|
12
|
+
} from './utils.js';
|
|
13
|
+
|
|
14
|
+
type SetupHookParams = HookParameters<'astro:config:setup'> & {
|
|
15
|
+
// `contentEntryType` is not a public API
|
|
16
|
+
// Add type defs here
|
|
17
|
+
addContentEntryType: (contentEntryType: ContentEntryType) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function markdoc(markdocConfig: Config = {}): AstroIntegration {
|
|
21
|
+
return {
|
|
22
|
+
name: '@astrojs/markdoc',
|
|
23
|
+
hooks: {
|
|
24
|
+
'astro:config:setup': async (params) => {
|
|
25
|
+
const { updateConfig, config, addContentEntryType } = params as SetupHookParams;
|
|
26
|
+
|
|
27
|
+
function getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
|
|
28
|
+
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
|
|
29
|
+
return {
|
|
30
|
+
data: parsed.data,
|
|
31
|
+
body: parsed.content,
|
|
32
|
+
slug: parsed.data.slug,
|
|
33
|
+
rawData: parsed.matter,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
addContentEntryType({
|
|
37
|
+
extensions: ['.mdoc'],
|
|
38
|
+
getEntryInfo,
|
|
39
|
+
contentModuleTypes: await fs.promises.readFile(
|
|
40
|
+
new URL('../template/content-module-types.d.ts', import.meta.url),
|
|
41
|
+
'utf-8'
|
|
42
|
+
),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const viteConfig: InlineConfig = {
|
|
46
|
+
plugins: [
|
|
47
|
+
{
|
|
48
|
+
name: '@astrojs/markdoc',
|
|
49
|
+
async transform(code, id) {
|
|
50
|
+
if (!id.endsWith('.mdoc')) return;
|
|
51
|
+
|
|
52
|
+
validateRenderProperties(markdocConfig, config);
|
|
53
|
+
const body = getEntryInfo({
|
|
54
|
+
// Can't use `pathToFileUrl` - Vite IDs are not plain file paths
|
|
55
|
+
fileUrl: new URL(prependForwardSlash(id), 'file://'),
|
|
56
|
+
contents: code,
|
|
57
|
+
}).body;
|
|
58
|
+
const ast = Markdoc.parse(body);
|
|
59
|
+
const content = Markdoc.transform(ast, markdocConfig);
|
|
60
|
+
|
|
61
|
+
return `import { jsx as h } from 'astro/jsx-runtime';\nimport { Renderer } from '@astrojs/markdoc/components';\nconst transformedContent = ${JSON.stringify(
|
|
62
|
+
content
|
|
63
|
+
)};\nexport async function Content ({ components }) { return h(Renderer, { content: transformedContent, components }); }\nContent[Symbol.for('astro.needsHeadRendering')] = true;`;
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
updateConfig({ vite: viteConfig });
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function validateRenderProperties(markdocConfig: Config, astroConfig: AstroConfig) {
|
|
75
|
+
const tags = markdocConfig.tags ?? {};
|
|
76
|
+
const nodes = markdocConfig.nodes ?? {};
|
|
77
|
+
|
|
78
|
+
for (const [name, config] of Object.entries(tags)) {
|
|
79
|
+
validateRenderProperty({ type: 'tag', name, config, astroConfig });
|
|
80
|
+
}
|
|
81
|
+
for (const [name, config] of Object.entries(nodes)) {
|
|
82
|
+
validateRenderProperty({ type: 'node', name, config, astroConfig });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateRenderProperty({
|
|
87
|
+
name,
|
|
88
|
+
config,
|
|
89
|
+
type,
|
|
90
|
+
astroConfig,
|
|
91
|
+
}: {
|
|
92
|
+
name: string;
|
|
93
|
+
config: { render?: string };
|
|
94
|
+
type: 'node' | 'tag';
|
|
95
|
+
astroConfig: Pick<AstroConfig, 'root'>;
|
|
96
|
+
}) {
|
|
97
|
+
if (typeof config.render === 'string' && config.render.length === 0) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Invalid ${type} configuration: ${JSON.stringify(
|
|
100
|
+
name
|
|
101
|
+
)}. The "render" property cannot be an empty string.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (typeof config.render === 'string' && !isCapitalized(config.render)) {
|
|
105
|
+
const astroConfigPath = getAstroConfigPath(fs, fileURLToPath(astroConfig.root));
|
|
106
|
+
throw new MarkdocError({
|
|
107
|
+
message: `Invalid ${type} configuration: ${JSON.stringify(
|
|
108
|
+
name
|
|
109
|
+
)}. The "render" property must reference a capitalized component name.`,
|
|
110
|
+
hint: 'If you want to render to an HTML element, see our docs on rendering Markdoc manually: https://docs.astro.build/en/guides/integrations-guide/markdoc/#render-markdoc-nodes--html-elements-as-astro-components',
|
|
111
|
+
location: astroConfigPath
|
|
112
|
+
? {
|
|
113
|
+
file: astroConfigPath,
|
|
114
|
+
}
|
|
115
|
+
: undefined,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isCapitalized(str: string) {
|
|
121
|
+
return str.length > 0 && str[0] === str[0].toUpperCase();
|
|
122
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { AstroInstance } from 'astro';
|
|
2
|
+
import z from 'astro/zod';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import type fsMod from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import type { ErrorPayload as ViteErrorPayload } from 'vite';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Match YAML exception handling from Astro core errors
|
|
10
|
+
* @see 'astro/src/core/errors.ts'
|
|
11
|
+
*/
|
|
12
|
+
export function parseFrontmatter(fileContents: string, filePath: string) {
|
|
13
|
+
try {
|
|
14
|
+
// `matter` is empty string on cache results
|
|
15
|
+
// clear cache to prevent this
|
|
16
|
+
(matter as any).clearCache();
|
|
17
|
+
return matter(fileContents);
|
|
18
|
+
} catch (e: any) {
|
|
19
|
+
if (e.name === 'YAMLException') {
|
|
20
|
+
const err: Error & ViteErrorPayload['err'] = e;
|
|
21
|
+
err.id = filePath;
|
|
22
|
+
err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
|
|
23
|
+
err.message = e.reason;
|
|
24
|
+
throw err;
|
|
25
|
+
} else {
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Matches AstroError object with types like error codes stubbed out
|
|
33
|
+
* @see 'astro/src/core/errors/errors.ts'
|
|
34
|
+
*/
|
|
35
|
+
export class MarkdocError extends Error {
|
|
36
|
+
public errorCode: number;
|
|
37
|
+
public loc: ErrorLocation | undefined;
|
|
38
|
+
public title: string | undefined;
|
|
39
|
+
public hint: string | undefined;
|
|
40
|
+
public frame: string | undefined;
|
|
41
|
+
|
|
42
|
+
type = 'MarkdocError';
|
|
43
|
+
|
|
44
|
+
constructor(props: ErrorProperties, ...params: any) {
|
|
45
|
+
super(...params);
|
|
46
|
+
|
|
47
|
+
const {
|
|
48
|
+
// Use default code for unknown errors in Astro core
|
|
49
|
+
// We don't have a best practice for integration error codes yet
|
|
50
|
+
code = 99999,
|
|
51
|
+
name,
|
|
52
|
+
title = 'MarkdocError',
|
|
53
|
+
message,
|
|
54
|
+
stack,
|
|
55
|
+
location,
|
|
56
|
+
hint,
|
|
57
|
+
frame,
|
|
58
|
+
} = props;
|
|
59
|
+
|
|
60
|
+
this.errorCode = code;
|
|
61
|
+
this.title = title;
|
|
62
|
+
if (message) this.message = message;
|
|
63
|
+
// Only set this if we actually have a stack passed, otherwise uses Error's
|
|
64
|
+
this.stack = stack ? stack : this.stack;
|
|
65
|
+
this.loc = location;
|
|
66
|
+
this.hint = hint;
|
|
67
|
+
this.frame = frame;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface ErrorLocation {
|
|
72
|
+
file?: string;
|
|
73
|
+
line?: number;
|
|
74
|
+
column?: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface ErrorProperties {
|
|
78
|
+
code?: number;
|
|
79
|
+
title?: string;
|
|
80
|
+
name?: string;
|
|
81
|
+
message?: string;
|
|
82
|
+
location?: ErrorLocation;
|
|
83
|
+
hint?: string;
|
|
84
|
+
stack?: string;
|
|
85
|
+
frame?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Matches `search` function used for resolving `astro.config` files.
|
|
90
|
+
* Used by Markdoc for error handling.
|
|
91
|
+
* @see 'astro/src/core/config/config.ts'
|
|
92
|
+
*/
|
|
93
|
+
export function getAstroConfigPath(fs: typeof fsMod, root: string): string | undefined {
|
|
94
|
+
const paths = [
|
|
95
|
+
'astro.config.mjs',
|
|
96
|
+
'astro.config.js',
|
|
97
|
+
'astro.config.ts',
|
|
98
|
+
'astro.config.mts',
|
|
99
|
+
'astro.config.cjs',
|
|
100
|
+
'astro.config.cts',
|
|
101
|
+
].map((p) => path.join(root, p));
|
|
102
|
+
|
|
103
|
+
for (const file of paths) {
|
|
104
|
+
if (fs.existsSync(file)) {
|
|
105
|
+
return file;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @see 'astro/src/core/path.ts'
|
|
112
|
+
*/
|
|
113
|
+
export function prependForwardSlash(str: string) {
|
|
114
|
+
return str[0] === '/' ? str : '/' + str;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function validateComponentsProp(components: Record<string, AstroInstance['default']>) {
|
|
118
|
+
try {
|
|
119
|
+
componentsPropValidator.parse(components);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
throw new MarkdocError({
|
|
122
|
+
message:
|
|
123
|
+
e instanceof z.ZodError
|
|
124
|
+
? e.issues[0].message
|
|
125
|
+
: 'Invalid `components` prop. Ensure you are passing an object of components to <Content />',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const componentsPropValidator = z.record(
|
|
131
|
+
z
|
|
132
|
+
.string()
|
|
133
|
+
.min(1, 'Invalid `components` prop. Component names cannot be empty!')
|
|
134
|
+
.refine(
|
|
135
|
+
(value) => isCapitalized(value),
|
|
136
|
+
(value) => ({
|
|
137
|
+
message: `Invalid \`components\` prop: ${JSON.stringify(
|
|
138
|
+
value
|
|
139
|
+
)}. Component name must be capitalized. If you want to render HTML elements as components, try using a Markdoc node (https://docs.astro.build/en/guides/integrations-guide/markdoc/#render-markdoc-nodes--html-elements-as-astro-components)`,
|
|
140
|
+
})
|
|
141
|
+
),
|
|
142
|
+
z.any()
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
export function isCapitalized(str: string) {
|
|
146
|
+
return str.length > 0 && str[0] === str[0].toUpperCase();
|
|
147
|
+
}
|