summaryrefslogtreecommitdiff
path: root/packages/integrations/markdoc/src
diff options
context:
space:
mode:
authorGravatar Ben Holmes <hey@bholmes.dev> 2023-05-09 17:20:55 -0400
committerGravatar GitHub <noreply@github.com> 2023-05-09 17:20:55 -0400
commit3a9f72c7f30ed173438fd0a222a094e5997b917d (patch)
tree4c099504c829ff55b822edf8505178ad1c474ade /packages/integrations/markdoc/src
parent18d06329116cf0467f9d78fcf09c8a5e9ac97aea (diff)
downloadastro-3a9f72c7f30ed173438fd0a222a094e5997b917d.tar.gz
astro-3a9f72c7f30ed173438fd0a222a094e5997b917d.tar.zst
astro-3a9f72c7f30ed173438fd0a222a094e5997b917d.zip
[Markdoc] Validation and debugging improvements (#7045)
* feat: better validation logs * chore: add warning to restart server on config chnage * feat: expose Markdoc global from markdoc/config * docs: update `nodes` reference * chore: changeset * docs: simplify headings explainer * chore: ignore eslint log errors * fix: make legacyConfig prop optional
Diffstat (limited to 'packages/integrations/markdoc/src')
-rw-r--r--packages/integrations/markdoc/src/config.ts1
-rw-r--r--packages/integrations/markdoc/src/index.ts60
-rw-r--r--packages/integrations/markdoc/src/load-config.ts9
3 files changed, 51 insertions, 19 deletions
diff --git a/packages/integrations/markdoc/src/config.ts b/packages/integrations/markdoc/src/config.ts
index 4c20e311f..09bbead12 100644
--- a/packages/integrations/markdoc/src/config.ts
+++ b/packages/integrations/markdoc/src/config.ts
@@ -1,4 +1,5 @@
import type { ConfigType as MarkdocConfig } from '@markdoc/markdoc';
+export { default as Markdoc } from '@markdoc/markdoc';
export function defineMarkdocConfig(config: MarkdocConfig): MarkdocConfig {
return config;
diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts
index 55d13169b..5b3568992 100644
--- a/packages/integrations/markdoc/src/index.ts
+++ b/packages/integrations/markdoc/src/index.ts
@@ -1,15 +1,16 @@
+/* eslint-disable no-console */
import type { Node } from '@markdoc/markdoc';
import Markdoc from '@markdoc/markdoc';
import type { AstroConfig, AstroIntegration, ContentEntryType, HookParameters } from 'astro';
import fs from 'node:fs';
-import { fileURLToPath } from 'node:url';
+import { fileURLToPath, pathToFileURL } from 'node:url';
import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from './utils.js';
// @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations.
import { emitESMImage } from 'astro/assets';
-import { bold, red } from 'kleur/colors';
+import { bold, red, yellow } from 'kleur/colors';
import type * as rollup from 'rollup';
import { applyDefaultConfig } from './default-config.js';
-import { loadMarkdocConfig } from './load-config.js';
+import { loadMarkdocConfig, type MarkdocConfigResult } from './load-config.js';
type SetupHookParams = HookParameters<'astro:config:setup'> & {
// `contentEntryType` is not a public API
@@ -17,9 +18,8 @@ type SetupHookParams = HookParameters<'astro:config:setup'> & {
addContentEntryType: (contentEntryType: ContentEntryType) => void;
};
-export default function markdocIntegration(legacyConfig: any): AstroIntegration {
+export default function markdocIntegration(legacyConfig?: any): AstroIntegration {
if (legacyConfig) {
- // eslint-disable-next-line no-console
console.log(
`${red(
bold('[Markdoc]')
@@ -27,14 +27,15 @@ export default function markdocIntegration(legacyConfig: any): AstroIntegration
);
process.exit(0);
}
+ let markdocConfigResult: MarkdocConfigResult | undefined;
return {
name: '@astrojs/markdoc',
hooks: {
'astro:config:setup': async (params) => {
const { config: astroConfig, addContentEntryType } = params as SetupHookParams;
- const configLoadResult = await loadMarkdocConfig(astroConfig);
- const userMarkdocConfig = configLoadResult?.config ?? {};
+ markdocConfigResult = await loadMarkdocConfig(astroConfig);
+ const userMarkdocConfig = markdocConfigResult?.config ?? {};
function getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
@@ -54,17 +55,28 @@ export default function markdocIntegration(legacyConfig: any): AstroIntegration
const markdocConfig = applyDefaultConfig(userMarkdocConfig, { entry });
const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
- // Ignore `variable-undefined` errors.
- // Variables can be configured at runtime,
- // so we cannot validate them at build time.
- return e.error.id !== 'variable-undefined';
+ return (
+ // Ignore `variable-undefined` errors.
+ // Variables can be configured at runtime,
+ // so we cannot validate them at build time.
+ e.error.id !== 'variable-undefined' &&
+ (e.error.level === 'error' || e.error.level === 'critical')
+ );
});
if (validationErrors.length) {
+ // Heuristic: take number of newlines for `rawData` and add 2 for the `---` fences
+ const frontmatterBlockOffset = entry._internal.rawData.split('\n').length + 2;
throw new MarkdocError({
message: [
- `**${String(entry.collection)} → ${String(entry.id)}** failed to validate:`,
- ...validationErrors.map((e) => e.error.id),
+ `**${String(entry.collection)} → ${String(entry.id)}** contains invalid content:`,
+ ...validationErrors.map((e) => `- ${e.error.message}`),
].join('\n'),
+ location: {
+ // Error overlay does not support multi-line or ranges.
+ // Just point to the first line.
+ line: frontmatterBlockOffset + validationErrors[0].lines[0],
+ file: viteId,
+ },
});
}
@@ -76,13 +88,15 @@ export default function markdocIntegration(legacyConfig: any): AstroIntegration
});
}
- const code = {
+ return {
code: `import { jsx as h } from 'astro/jsx-runtime';
import { applyDefaultConfig } from '@astrojs/markdoc/default-config';
import { Renderer } from '@astrojs/markdoc/components';
import * as entry from ${JSON.stringify(viteId + '?astroContent')};${
- configLoadResult
- ? `\nimport userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};`
+ markdocConfigResult
+ ? `\nimport userConfig from ${JSON.stringify(
+ markdocConfigResult.fileUrl.pathname
+ )};`
: ''
}${
astroConfig.experimental.assets
@@ -94,7 +108,7 @@ const stringifiedAst = ${JSON.stringify(
)};
export async function Content (props) {
const config = applyDefaultConfig(${
- configLoadResult
+ markdocConfigResult
? '{ ...userConfig, variables: { ...userConfig.variables, ...props } }'
: '{ variables: props }'
}, { entry });${
@@ -104,7 +118,6 @@ export async function Content (props) {
}
return h(Renderer, { stringifiedAst, config }); };`,
};
- return code;
},
contentModuleTypes: await fs.promises.readFile(
new URL('../template/content-module-types.d.ts', import.meta.url),
@@ -112,6 +125,17 @@ export async function Content (props) {
),
});
},
+ 'astro:server:setup': async ({ server }) => {
+ server.watcher.on('all', (event, entry) => {
+ if (pathToFileURL(entry).pathname === markdocConfigResult?.fileUrl.pathname) {
+ console.log(
+ yellow(
+ `${bold('[Markdoc]')} Restart the dev server for config changes to take effect.`
+ )
+ );
+ }
+ });
+ },
},
};
}
diff --git a/packages/integrations/markdoc/src/load-config.ts b/packages/integrations/markdoc/src/load-config.ts
index af4e0e4aa..4a8b2f9cd 100644
--- a/packages/integrations/markdoc/src/load-config.ts
+++ b/packages/integrations/markdoc/src/load-config.ts
@@ -11,7 +11,14 @@ const SUPPORTED_MARKDOC_CONFIG_FILES = [
'markdoc.config.ts',
];
-export async function loadMarkdocConfig(astroConfig: Pick<AstroConfig, 'root'>) {
+export type MarkdocConfigResult = {
+ config: MarkdocConfig;
+ fileUrl: URL;
+};
+
+export async function loadMarkdocConfig(
+ astroConfig: Pick<AstroConfig, 'root'>
+): Promise<MarkdocConfigResult | undefined> {
let markdocConfigUrl: URL | undefined;
for (const filename of SUPPORTED_MARKDOC_CONFIG_FILES) {
const filePath = new URL(filename, astroConfig.root);