@astrojs/markdoc 0.1.2 → 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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @astrojs/markdoc@0.1.2 build /home/runner/work/astro/astro/packages/integrations/markdoc
2
+ > @astrojs/markdoc@0.1.3 build /home/runner/work/astro/astro/packages/integrations/markdoc
3
3
  > astro-scripts build "src/**/*.ts" && tsc
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @astrojs/markdoc
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#7045](https://github.com/withastro/astro/pull/7045) [`3a9f72c7f`](https://github.com/withastro/astro/commit/3a9f72c7f30ed173438fd0a222a094e5997b917d) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Improve Markdoc validation errors with full message and file preview.
8
+
9
+ - Updated dependencies [[`48395c815`](https://github.com/withastro/astro/commit/48395c81522f7527126699c4f185f7b4488a4b9a), [`630f8c8ef`](https://github.com/withastro/astro/commit/630f8c8ef68fedfa393899c13a072e50145895e8)]:
10
+ - astro@2.4.4
11
+
3
12
  ## 0.1.2
4
13
 
5
14
  ### Patch Changes
package/README.md CHANGED
@@ -143,28 +143,26 @@ Use tags like this fancy "aside" to add some *flair* to your docs.
143
143
 
144
144
  #### Render Markdoc nodes / HTML elements as Astro components
145
145
 
146
- You may also want to map standard HTML elements like headings and paragraphs to components. For this, you can configure a custom [Markdoc node][markdoc-nodes]. This example overrides Markdoc's `heading` node to render a `Heading` component, passing the built-in `level` attribute as a prop:
146
+ You may also want to map standard HTML elements like headings and paragraphs to components. For this, you can configure a custom [Markdoc node][markdoc-nodes]. This example overrides Markdoc's `heading` node to render a `Heading` component, and passes through [Markdoc's default attributes for headings](https://markdoc.dev/docs/nodes#built-in-nodes).
147
147
 
148
148
  ```js
149
149
  // markdoc.config.mjs
150
- import { defineMarkdocConfig } from '@astrojs/markdoc/config';
150
+ import { defineMarkdocConfig, Markdoc } from '@astrojs/markdoc/config';
151
151
  import Heading from './src/components/Heading.astro';
152
152
 
153
153
  export default defineMarkdocConfig({
154
154
  nodes: {
155
155
  heading: {
156
156
  render: Heading,
157
- attributes: {
158
- // Pass the attributes from Markdoc's default heading node
159
- // as component props.
160
- level: { type: String },
161
- }
157
+ attributes: Markdoc.nodes.heading.attributes,
162
158
  },
163
159
  },
164
160
  })
165
161
  ```
166
162
 
167
- Now, all Markdown headings will render with the `Heading.astro` component. This example uses a level 3 heading, automatically passing `level: 3` as the component prop:
163
+ Now, all Markdown headings will render with the `Heading.astro` component, and pass these `attributes` as component props. For headings, Markdoc provides a `level` attribute containing the numeric heading level.
164
+
165
+ This example uses a level 3 heading, automatically passing `level: 3` as the component prop:
168
166
 
169
167
  ```md
170
168
  ### I'm a level 3 heading!
package/dist/config.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import type { ConfigType as MarkdocConfig } from '@markdoc/markdoc';
2
+ export { default as Markdoc } from '@markdoc/markdoc';
2
3
  export declare function defineMarkdocConfig(config: MarkdocConfig): MarkdocConfig;
package/dist/config.js CHANGED
@@ -1,6 +1,8 @@
1
+ import { default as default2 } from "@markdoc/markdoc";
1
2
  function defineMarkdocConfig(config) {
2
3
  return config;
3
4
  }
4
5
  export {
6
+ default2 as Markdoc,
5
7
  defineMarkdocConfig
6
8
  };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import type { AstroIntegration } from 'astro';
2
- export default function markdocIntegration(legacyConfig: any): AstroIntegration;
2
+ export default function markdocIntegration(legacyConfig?: any): AstroIntegration;
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import Markdoc from "@markdoc/markdoc";
2
2
  import fs from "node:fs";
3
- import { fileURLToPath } from "node:url";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from "./utils.js";
5
5
  import { emitESMImage } from "astro/assets";
6
- import { bold, red } from "kleur/colors";
6
+ import { bold, red, yellow } from "kleur/colors";
7
7
  import { applyDefaultConfig } from "./default-config.js";
8
8
  import { loadMarkdocConfig } from "./load-config.js";
9
9
  function markdocIntegration(legacyConfig) {
@@ -15,13 +15,14 @@ function markdocIntegration(legacyConfig) {
15
15
  );
16
16
  process.exit(0);
17
17
  }
18
+ let markdocConfigResult;
18
19
  return {
19
20
  name: "@astrojs/markdoc",
20
21
  hooks: {
21
22
  "astro:config:setup": async (params) => {
22
23
  const { config: astroConfig, addContentEntryType } = params;
23
- const configLoadResult = await loadMarkdocConfig(astroConfig);
24
- const userMarkdocConfig = (configLoadResult == null ? void 0 : configLoadResult.config) ?? {};
24
+ markdocConfigResult = await loadMarkdocConfig(astroConfig);
25
+ const userMarkdocConfig = (markdocConfigResult == null ? void 0 : markdocConfigResult.config) ?? {};
25
26
  function getEntryInfo({ fileUrl, contents }) {
26
27
  const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
27
28
  return {
@@ -39,14 +40,26 @@ function markdocIntegration(legacyConfig) {
39
40
  const pluginContext = this;
40
41
  const markdocConfig = applyDefaultConfig(userMarkdocConfig, { entry });
41
42
  const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
42
- return e.error.id !== "variable-undefined";
43
+ return (
44
+ // Ignore `variable-undefined` errors.
45
+ // Variables can be configured at runtime,
46
+ // so we cannot validate them at build time.
47
+ e.error.id !== "variable-undefined" && (e.error.level === "error" || e.error.level === "critical")
48
+ );
43
49
  });
44
50
  if (validationErrors.length) {
51
+ const frontmatterBlockOffset = entry._internal.rawData.split("\n").length + 2;
45
52
  throw new MarkdocError({
46
53
  message: [
47
- `**${String(entry.collection)} \u2192 ${String(entry.id)}** failed to validate:`,
48
- ...validationErrors.map((e) => e.error.id)
49
- ].join("\n")
54
+ `**${String(entry.collection)} \u2192 ${String(entry.id)}** contains invalid content:`,
55
+ ...validationErrors.map((e) => `- ${e.error.message}`)
56
+ ].join("\n"),
57
+ location: {
58
+ // Error overlay does not support multi-line or ranges.
59
+ // Just point to the first line.
60
+ line: frontmatterBlockOffset + validationErrors[0].lines[0],
61
+ file: viteId
62
+ }
50
63
  });
51
64
  }
52
65
  if (astroConfig.experimental.assets) {
@@ -56,29 +69,41 @@ function markdocIntegration(legacyConfig) {
56
69
  filePath: entry._internal.filePath
57
70
  });
58
71
  }
59
- const code = {
72
+ return {
60
73
  code: `import { jsx as h } from 'astro/jsx-runtime';
61
74
  import { applyDefaultConfig } from '@astrojs/markdoc/default-config';
62
75
  import { Renderer } from '@astrojs/markdoc/components';
63
- import * as entry from ${JSON.stringify(viteId + "?astroContent")};${configLoadResult ? `
64
- import userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};` : ""}${astroConfig.experimental.assets ? `
76
+ import * as entry from ${JSON.stringify(viteId + "?astroContent")};${markdocConfigResult ? `
77
+ import userConfig from ${JSON.stringify(
78
+ markdocConfigResult.fileUrl.pathname
79
+ )};` : ""}${astroConfig.experimental.assets ? `
65
80
  import { experimentalAssetsConfig } from '@astrojs/markdoc/experimental-assets-config';` : ""}
66
81
  const stringifiedAst = ${JSON.stringify(
67
82
  /* Double stringify to encode *as* stringified JSON */
68
83
  JSON.stringify(ast)
69
84
  )};
70
85
  export async function Content (props) {
71
- const config = applyDefaultConfig(${configLoadResult ? "{ ...userConfig, variables: { ...userConfig.variables, ...props } }" : "{ variables: props }"}, { entry });${astroConfig.experimental.assets ? `
86
+ const config = applyDefaultConfig(${markdocConfigResult ? "{ ...userConfig, variables: { ...userConfig.variables, ...props } }" : "{ variables: props }"}, { entry });${astroConfig.experimental.assets ? `
72
87
  config.nodes = { ...experimentalAssetsConfig.nodes, ...config.nodes };` : ""}
73
88
  return h(Renderer, { stringifiedAst, config }); };`
74
89
  };
75
- return code;
76
90
  },
77
91
  contentModuleTypes: await fs.promises.readFile(
78
92
  new URL("../template/content-module-types.d.ts", import.meta.url),
79
93
  "utf-8"
80
94
  )
81
95
  });
96
+ },
97
+ "astro:server:setup": async ({ server }) => {
98
+ server.watcher.on("all", (event, entry) => {
99
+ if (pathToFileURL(entry).pathname === (markdocConfigResult == null ? void 0 : markdocConfigResult.fileUrl.pathname)) {
100
+ console.log(
101
+ yellow(
102
+ `${bold("[Markdoc]")} Restart the dev server for config changes to take effect.`
103
+ )
104
+ );
105
+ }
106
+ });
82
107
  }
83
108
  }
84
109
  };
@@ -1,14 +1,7 @@
1
+ import type { Config as MarkdocConfig } from '@markdoc/markdoc';
1
2
  import type { AstroConfig } from 'astro';
2
- export declare function loadMarkdocConfig(astroConfig: Pick<AstroConfig, 'root'>): Promise<{
3
- config: Readonly<Partial<{
4
- nodes: Partial<Record<import("@markdoc/markdoc").NodeType, import("@markdoc/markdoc").Schema<Readonly<Partial<any>>, string>>>;
5
- tags: Record<string, import("@markdoc/markdoc").Schema<Readonly<Partial<any>>, string>>;
6
- variables: Record<string, any>;
7
- functions: Record<string, import("@markdoc/markdoc").ConfigFunction>;
8
- partials: Record<string, any>;
9
- validation?: {
10
- validateFunctions?: boolean | undefined;
11
- } | undefined;
12
- }>>;
3
+ export type MarkdocConfigResult = {
4
+ config: MarkdocConfig;
13
5
  fileUrl: URL;
14
- } | undefined>;
6
+ };
7
+ export declare function loadMarkdocConfig(astroConfig: Pick<AstroConfig, 'root'>): Promise<MarkdocConfigResult | undefined>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@astrojs/markdoc",
3
3
  "description": "Add support for Markdoc pages in your Astro site",
4
- "version": "0.1.2",
4
+ "version": "0.1.3",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -34,7 +34,7 @@
34
34
  "zod": "^3.17.3"
35
35
  },
36
36
  "peerDependencies": {
37
- "astro": "^2.4.0"
37
+ "astro": "^2.4.4"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/chai": "^4.3.1",
@@ -46,7 +46,7 @@
46
46
  "mocha": "^9.2.2",
47
47
  "rollup": "^3.20.1",
48
48
  "vite": "^4.3.1",
49
- "astro": "2.4.0",
49
+ "astro": "2.4.4",
50
50
  "astro-scripts": "0.0.14"
51
51
  },
52
52
  "engines": {
package/src/config.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ConfigType as MarkdocConfig } from '@markdoc/markdoc';
2
+ export { default as Markdoc } from '@markdoc/markdoc';
2
3
 
3
4
  export function defineMarkdocConfig(config: MarkdocConfig): MarkdocConfig {
4
5
  return config;
package/src/index.ts CHANGED
@@ -1,15 +1,16 @@
1
+ /* eslint-disable no-console */
1
2
  import type { Node } from '@markdoc/markdoc';
2
3
  import Markdoc from '@markdoc/markdoc';
3
4
  import type { AstroConfig, AstroIntegration, ContentEntryType, HookParameters } from 'astro';
4
5
  import fs from 'node:fs';
5
- import { fileURLToPath } from 'node:url';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
6
7
  import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from './utils.js';
7
8
  // @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations.
8
9
  import { emitESMImage } from 'astro/assets';
9
- import { bold, red } from 'kleur/colors';
10
+ import { bold, red, yellow } from 'kleur/colors';
10
11
  import type * as rollup from 'rollup';
11
12
  import { applyDefaultConfig } from './default-config.js';
12
- import { loadMarkdocConfig } from './load-config.js';
13
+ import { loadMarkdocConfig, type MarkdocConfigResult } from './load-config.js';
13
14
 
14
15
  type SetupHookParams = HookParameters<'astro:config:setup'> & {
15
16
  // `contentEntryType` is not a public API
@@ -17,9 +18,8 @@ type SetupHookParams = HookParameters<'astro:config:setup'> & {
17
18
  addContentEntryType: (contentEntryType: ContentEntryType) => void;
18
19
  };
19
20
 
20
- export default function markdocIntegration(legacyConfig: any): AstroIntegration {
21
+ export default function markdocIntegration(legacyConfig?: any): AstroIntegration {
21
22
  if (legacyConfig) {
22
- // eslint-disable-next-line no-console
23
23
  console.log(
24
24
  `${red(
25
25
  bold('[Markdoc]')
@@ -27,14 +27,15 @@ export default function markdocIntegration(legacyConfig: any): AstroIntegration
27
27
  );
28
28
  process.exit(0);
29
29
  }
30
+ let markdocConfigResult: MarkdocConfigResult | undefined;
30
31
  return {
31
32
  name: '@astrojs/markdoc',
32
33
  hooks: {
33
34
  'astro:config:setup': async (params) => {
34
35
  const { config: astroConfig, addContentEntryType } = params as SetupHookParams;
35
36
 
36
- const configLoadResult = await loadMarkdocConfig(astroConfig);
37
- const userMarkdocConfig = configLoadResult?.config ?? {};
37
+ markdocConfigResult = await loadMarkdocConfig(astroConfig);
38
+ const userMarkdocConfig = markdocConfigResult?.config ?? {};
38
39
 
39
40
  function getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
40
41
  const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
@@ -54,17 +55,28 @@ export default function markdocIntegration(legacyConfig: any): AstroIntegration
54
55
  const markdocConfig = applyDefaultConfig(userMarkdocConfig, { entry });
55
56
 
56
57
  const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
57
- // Ignore `variable-undefined` errors.
58
- // Variables can be configured at runtime,
59
- // so we cannot validate them at build time.
60
- return e.error.id !== 'variable-undefined';
58
+ return (
59
+ // Ignore `variable-undefined` errors.
60
+ // Variables can be configured at runtime,
61
+ // so we cannot validate them at build time.
62
+ e.error.id !== 'variable-undefined' &&
63
+ (e.error.level === 'error' || e.error.level === 'critical')
64
+ );
61
65
  });
62
66
  if (validationErrors.length) {
67
+ // Heuristic: take number of newlines for `rawData` and add 2 for the `---` fences
68
+ const frontmatterBlockOffset = entry._internal.rawData.split('\n').length + 2;
63
69
  throw new MarkdocError({
64
70
  message: [
65
- `**${String(entry.collection)} → ${String(entry.id)}** failed to validate:`,
66
- ...validationErrors.map((e) => e.error.id),
71
+ `**${String(entry.collection)} → ${String(entry.id)}** contains invalid content:`,
72
+ ...validationErrors.map((e) => `- ${e.error.message}`),
67
73
  ].join('\n'),
74
+ location: {
75
+ // Error overlay does not support multi-line or ranges.
76
+ // Just point to the first line.
77
+ line: frontmatterBlockOffset + validationErrors[0].lines[0],
78
+ file: viteId,
79
+ },
68
80
  });
69
81
  }
70
82
 
@@ -76,13 +88,15 @@ export default function markdocIntegration(legacyConfig: any): AstroIntegration
76
88
  });
77
89
  }
78
90
 
79
- const code = {
91
+ return {
80
92
  code: `import { jsx as h } from 'astro/jsx-runtime';
81
93
  import { applyDefaultConfig } from '@astrojs/markdoc/default-config';
82
94
  import { Renderer } from '@astrojs/markdoc/components';
83
95
  import * as entry from ${JSON.stringify(viteId + '?astroContent')};${
84
- configLoadResult
85
- ? `\nimport userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};`
96
+ markdocConfigResult
97
+ ? `\nimport userConfig from ${JSON.stringify(
98
+ markdocConfigResult.fileUrl.pathname
99
+ )};`
86
100
  : ''
87
101
  }${
88
102
  astroConfig.experimental.assets
@@ -94,7 +108,7 @@ const stringifiedAst = ${JSON.stringify(
94
108
  )};
95
109
  export async function Content (props) {
96
110
  const config = applyDefaultConfig(${
97
- configLoadResult
111
+ markdocConfigResult
98
112
  ? '{ ...userConfig, variables: { ...userConfig.variables, ...props } }'
99
113
  : '{ variables: props }'
100
114
  }, { entry });${
@@ -104,7 +118,6 @@ export async function Content (props) {
104
118
  }
105
119
  return h(Renderer, { stringifiedAst, config }); };`,
106
120
  };
107
- return code;
108
121
  },
109
122
  contentModuleTypes: await fs.promises.readFile(
110
123
  new URL('../template/content-module-types.d.ts', import.meta.url),
@@ -112,6 +125,17 @@ export async function Content (props) {
112
125
  ),
113
126
  });
114
127
  },
128
+ 'astro:server:setup': async ({ server }) => {
129
+ server.watcher.on('all', (event, entry) => {
130
+ if (pathToFileURL(entry).pathname === markdocConfigResult?.fileUrl.pathname) {
131
+ console.log(
132
+ yellow(
133
+ `${bold('[Markdoc]')} Restart the dev server for config changes to take effect.`
134
+ )
135
+ );
136
+ }
137
+ });
138
+ },
115
139
  },
116
140
  };
117
141
  }
@@ -11,7 +11,14 @@ const SUPPORTED_MARKDOC_CONFIG_FILES = [
11
11
  'markdoc.config.ts',
12
12
  ];
13
13
 
14
- export async function loadMarkdocConfig(astroConfig: Pick<AstroConfig, 'root'>) {
14
+ export type MarkdocConfigResult = {
15
+ config: MarkdocConfig;
16
+ fileUrl: URL;
17
+ };
18
+
19
+ export async function loadMarkdocConfig(
20
+ astroConfig: Pick<AstroConfig, 'root'>
21
+ ): Promise<MarkdocConfigResult | undefined> {
15
22
  let markdocConfigUrl: URL | undefined;
16
23
  for (const filename of SUPPORTED_MARKDOC_CONFIG_FILES) {
17
24
  const filePath = new URL(filename, astroConfig.root);