@azerion/bunosaurus-mdx-loader 0.0.0-stage → 4.0.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.
Files changed (130) hide show
  1. package/README.md +57 -2
  2. package/lib/createMDXLoader.d.ts +14 -0
  3. package/lib/createMDXLoader.d.ts.map +1 -0
  4. package/lib/createMDXLoader.js +52 -0
  5. package/lib/createMDXLoader.js.map +1 -0
  6. package/lib/format.d.ts +14 -0
  7. package/lib/format.d.ts.map +1 -0
  8. package/lib/format.js +41 -0
  9. package/lib/format.js.map +1 -0
  10. package/lib/frontMatter.d.ts +13 -0
  11. package/lib/frontMatter.d.ts.map +1 -0
  12. package/lib/frontMatter.js +23 -0
  13. package/lib/frontMatter.js.map +1 -0
  14. package/lib/index.d.ts +35 -0
  15. package/lib/index.d.ts.map +1 -0
  16. package/lib/index.js +15 -0
  17. package/lib/index.js.map +1 -0
  18. package/lib/loader.d.ts +11 -0
  19. package/lib/loader.d.ts.map +1 -0
  20. package/lib/loader.js +170 -0
  21. package/lib/loader.js.map +1 -0
  22. package/lib/options.d.ts +35 -0
  23. package/lib/options.d.ts.map +1 -0
  24. package/lib/options.js +9 -0
  25. package/lib/options.js.map +1 -0
  26. package/lib/preprocessor.d.ts +18 -0
  27. package/lib/preprocessor.d.ts.map +1 -0
  28. package/lib/preprocessor.js +34 -0
  29. package/lib/preprocessor.js.map +1 -0
  30. package/lib/processor.d.ts +49 -0
  31. package/lib/processor.d.ts.map +1 -0
  32. package/lib/processor.js +209 -0
  33. package/lib/processor.js.map +1 -0
  34. package/lib/remark/admonitions/index.d.ts +11 -0
  35. package/lib/remark/admonitions/index.d.ts.map +1 -0
  36. package/lib/remark/admonitions/index.js +107 -0
  37. package/lib/remark/admonitions/index.js.map +1 -0
  38. package/lib/remark/contentTitle/index.d.ts +13 -0
  39. package/lib/remark/contentTitle/index.d.ts.map +1 -0
  40. package/lib/remark/contentTitle/index.js +62 -0
  41. package/lib/remark/contentTitle/index.js.map +1 -0
  42. package/lib/remark/details/index.d.ts +10 -0
  43. package/lib/remark/details/index.d.ts.map +1 -0
  44. package/lib/remark/details/index.js +22 -0
  45. package/lib/remark/details/index.js.map +1 -0
  46. package/lib/remark/head/index.d.ts +10 -0
  47. package/lib/remark/head/index.d.ts.map +1 -0
  48. package/lib/remark/head/index.js +22 -0
  49. package/lib/remark/head/index.js.map +1 -0
  50. package/lib/remark/headings/index.d.ts +14 -0
  51. package/lib/remark/headings/index.d.ts.map +1 -0
  52. package/lib/remark/headings/index.js +126 -0
  53. package/lib/remark/headings/index.js.map +1 -0
  54. package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts +11 -0
  55. package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts.map +1 -0
  56. package/lib/remark/mdx1Compat/codeCompatPlugin.js +30 -0
  57. package/lib/remark/mdx1Compat/codeCompatPlugin.js.map +1 -0
  58. package/lib/remark/mermaid/index.d.ts +10 -0
  59. package/lib/remark/mermaid/index.d.ts.map +1 -0
  60. package/lib/remark/mermaid/index.js +34 -0
  61. package/lib/remark/mermaid/index.js.map +1 -0
  62. package/lib/remark/resolveMarkdownLinks/index.d.ts +33 -0
  63. package/lib/remark/resolveMarkdownLinks/index.d.ts.map +1 -0
  64. package/lib/remark/resolveMarkdownLinks/index.js +87 -0
  65. package/lib/remark/resolveMarkdownLinks/index.js.map +1 -0
  66. package/lib/remark/toc/index.d.ts +14 -0
  67. package/lib/remark/toc/index.d.ts.map +1 -0
  68. package/lib/remark/toc/index.js +115 -0
  69. package/lib/remark/toc/index.js.map +1 -0
  70. package/lib/remark/toc/types.d.ts +22 -0
  71. package/lib/remark/toc/types.d.ts.map +1 -0
  72. package/lib/remark/toc/types.js +9 -0
  73. package/lib/remark/toc/types.js.map +1 -0
  74. package/lib/remark/toc/utils.d.ts +26 -0
  75. package/lib/remark/toc/utils.d.ts.map +1 -0
  76. package/lib/remark/toc/utils.js +184 -0
  77. package/lib/remark/toc/utils.js.map +1 -0
  78. package/lib/remark/transformImage/index.d.ts +17 -0
  79. package/lib/remark/transformImage/index.d.ts.map +1 -0
  80. package/lib/remark/transformImage/index.js +187 -0
  81. package/lib/remark/transformImage/index.js.map +1 -0
  82. package/lib/remark/transformLinks/index.d.ts +17 -0
  83. package/lib/remark/transformLinks/index.d.ts.map +1 -0
  84. package/lib/remark/transformLinks/index.js +205 -0
  85. package/lib/remark/transformLinks/index.js.map +1 -0
  86. package/lib/remark/unusedDirectives/index.d.ts +9 -0
  87. package/lib/remark/unusedDirectives/index.d.ts.map +1 -0
  88. package/lib/remark/unusedDirectives/index.js +125 -0
  89. package/lib/remark/unusedDirectives/index.js.map +1 -0
  90. package/lib/remark/utils/index.d.ts +18 -0
  91. package/lib/remark/utils/index.d.ts.map +1 -0
  92. package/lib/remark/utils/index.js +93 -0
  93. package/lib/remark/utils/index.js.map +1 -0
  94. package/lib/serializeJS.d.ts +17 -0
  95. package/lib/serializeJS.d.ts.map +1 -0
  96. package/lib/serializeJS.js +24 -0
  97. package/lib/serializeJS.js.map +1 -0
  98. package/lib/utils.d.ts +35 -0
  99. package/lib/utils.d.ts.map +1 -0
  100. package/lib/utils.js +97 -0
  101. package/lib/utils.js.map +1 -0
  102. package/package.json +65 -4
  103. package/src/createMDXLoader.ts +65 -0
  104. package/src/format.ts +50 -0
  105. package/src/frontMatter.ts +31 -0
  106. package/src/index.ts +44 -0
  107. package/src/loader.ts +227 -0
  108. package/src/options.ts +37 -0
  109. package/src/preprocessor.ts +47 -0
  110. package/src/processor.ts +306 -0
  111. package/src/remark/admonitions/LICENSE +45 -0
  112. package/src/remark/admonitions/README.md +3 -0
  113. package/src/remark/admonitions/index.ts +138 -0
  114. package/src/remark/contentTitle/index.ts +76 -0
  115. package/src/remark/details/index.ts +23 -0
  116. package/src/remark/head/index.ts +23 -0
  117. package/src/remark/headings/index.ts +158 -0
  118. package/src/remark/mdx1Compat/codeCompatPlugin.ts +35 -0
  119. package/src/remark/mermaid/index.ts +35 -0
  120. package/src/remark/resolveMarkdownLinks/index.ts +137 -0
  121. package/src/remark/toc/index.ts +183 -0
  122. package/src/remark/toc/types.ts +29 -0
  123. package/src/remark/toc/utils.ts +243 -0
  124. package/src/remark/transformImage/index.ts +268 -0
  125. package/src/remark/transformLinks/index.ts +291 -0
  126. package/src/remark/unusedDirectives/index.ts +171 -0
  127. package/src/remark/utils/index.ts +99 -0
  128. package/src/serializeJS.ts +22 -0
  129. package/src/types.d.mts +40 -0
  130. package/src/utils.ts +134 -0
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import path from 'node:path';
9
+ import {visit} from 'unist-util-visit';
10
+ import {
11
+ toMessageRelativeFilePath,
12
+ posixPath,
13
+ escapePath,
14
+ findAsyncSequential,
15
+ getFileLoaderUtils,
16
+ parseURLOrPath,
17
+ parseLocalURLPath,
18
+ } from '@docusaurus/utils';
19
+ import {imageSizeFromFile} from 'image-size/fromFile';
20
+ import logger from '@docusaurus/logger';
21
+ import {pathExists} from '@docusaurus/fs';
22
+ import {
23
+ assetRequireAttributeValue,
24
+ formatNodePositionExtraMessage,
25
+ transformNode,
26
+ } from '../utils';
27
+ import type {Plugin, Transformer} from 'unified';
28
+ import type {MdxJsxTextElement} from 'mdast-util-mdx';
29
+ import type {Image, Root} from 'mdast';
30
+ import type {Parent} from 'unist';
31
+ import type {
32
+ MarkdownConfig,
33
+ OnBrokenMarkdownImagesFunction,
34
+ } from '@docusaurus/types';
35
+
36
+ export type PluginOptions = {
37
+ staticDirs: string[];
38
+ siteDir: string;
39
+ onBrokenMarkdownImages: MarkdownConfig['hooks']['onBrokenMarkdownImages'];
40
+ };
41
+
42
+ type Context = {
43
+ staticDirs: PluginOptions['staticDirs'];
44
+ siteDir: PluginOptions['siteDir'];
45
+ onBrokenMarkdownImages: OnBrokenMarkdownImagesFunction;
46
+ filePath: string;
47
+ inlineMarkdownImageFileLoader: string;
48
+ };
49
+
50
+ function asFunction(
51
+ onBrokenMarkdownImages: PluginOptions['onBrokenMarkdownImages'],
52
+ ): OnBrokenMarkdownImagesFunction {
53
+ if (typeof onBrokenMarkdownImages === 'string') {
54
+ const extraHelp =
55
+ onBrokenMarkdownImages === 'throw'
56
+ ? logger.interpolate`\nTo ignore this error, use the code=${'siteConfig.markdown.hooks.onBrokenMarkdownImages'} option, or apply the code=${'pathname://'} protocol to the broken image URLs.`
57
+ : '';
58
+ return ({sourceFilePath, url: imageUrl, node}) => {
59
+ const relativePath = toMessageRelativeFilePath(sourceFilePath);
60
+ if (imageUrl) {
61
+ logger.report(
62
+ onBrokenMarkdownImages,
63
+ )`Markdown image with URL code=${imageUrl} in source file path=${relativePath}${formatNodePositionExtraMessage(
64
+ node,
65
+ )} couldn't be resolved to an existing local image file.${extraHelp}`;
66
+ } else {
67
+ logger.report(
68
+ onBrokenMarkdownImages,
69
+ )`Markdown image with empty URL found in source file path=${relativePath}${formatNodePositionExtraMessage(
70
+ node,
71
+ )}.${extraHelp}`;
72
+ }
73
+ };
74
+ } else {
75
+ return (params) =>
76
+ onBrokenMarkdownImages({
77
+ ...params,
78
+ sourceFilePath: toMessageRelativeFilePath(params.sourceFilePath),
79
+ });
80
+ }
81
+ }
82
+
83
+ type Target = [node: Image, index: number, parent: Parent];
84
+
85
+ async function toImageRequireNode(
86
+ [node]: Target,
87
+ imagePath: string,
88
+ context: Context,
89
+ ) {
90
+ // MdxJsxTextElement => see https://github.com/facebook/docusaurus/pull/8288#discussion_r1125871405
91
+ const jsxNode = node as unknown as MdxJsxTextElement;
92
+ const attributes: MdxJsxTextElement['attributes'] = [];
93
+
94
+ let relativeImagePath = posixPath(
95
+ path.relative(path.dirname(context.filePath), imagePath),
96
+ );
97
+ relativeImagePath = `./${relativeImagePath}`;
98
+
99
+ const parsedUrl = parseURLOrPath(node.url);
100
+ const hash = parsedUrl.hash ?? '';
101
+ const search = parsedUrl.search ?? '';
102
+ const requireString = `${context.inlineMarkdownImageFileLoader}${
103
+ escapePath(relativeImagePath) + search
104
+ }`;
105
+ if (node.alt) {
106
+ attributes.push({
107
+ type: 'mdxJsxAttribute',
108
+ name: 'alt',
109
+ value: node.alt,
110
+ });
111
+ }
112
+
113
+ attributes.push({
114
+ type: 'mdxJsxAttribute',
115
+ name: 'src',
116
+ value: assetRequireAttributeValue(requireString, hash),
117
+ });
118
+
119
+ if (node.title) {
120
+ attributes.push({
121
+ type: 'mdxJsxAttribute',
122
+ name: 'title',
123
+ value: node.title,
124
+ });
125
+ }
126
+
127
+ try {
128
+ const size = (await imageSizeFromFile(imagePath))!;
129
+ if (size.width) {
130
+ attributes.push({
131
+ type: 'mdxJsxAttribute',
132
+ name: 'width',
133
+ value: String(size.width),
134
+ });
135
+ }
136
+ if (size.height) {
137
+ attributes.push({
138
+ type: 'mdxJsxAttribute',
139
+ name: 'height',
140
+ value: String(size.height),
141
+ });
142
+ }
143
+ } catch (err) {
144
+ console.error(err);
145
+ // Workaround for https://github.com/yarnpkg/berry/pull/3889#issuecomment-1034469784
146
+ // TODO remove this check once fixed in Yarn PnP
147
+ if (!process.versions.pnp) {
148
+ logger.warn`The image at path=${imagePath} can't be read correctly. Please ensure it's a valid image.
149
+ ${(err as Error).message}`;
150
+ }
151
+ }
152
+
153
+ transformNode(jsxNode, {
154
+ type: 'mdxJsxTextElement',
155
+ name: 'img',
156
+ attributes,
157
+ children: [],
158
+ });
159
+ }
160
+
161
+ async function getLocalImageAbsolutePath(
162
+ originalImagePath: string,
163
+ {siteDir, filePath, staticDirs}: Context,
164
+ ) {
165
+ if (originalImagePath.startsWith('@site/')) {
166
+ const imageFilePath = path.join(
167
+ siteDir,
168
+ originalImagePath.replace('@site/', ''),
169
+ );
170
+ if (!(await pathExists(imageFilePath))) {
171
+ return null;
172
+ }
173
+ return imageFilePath;
174
+ } else if (path.isAbsolute(originalImagePath)) {
175
+ // Absolute paths are expected to exist in the static folder.
176
+ const possiblePaths = staticDirs.map((dir) =>
177
+ path.join(dir, originalImagePath),
178
+ );
179
+ const imageFilePath = await findAsyncSequential(possiblePaths, pathExists);
180
+ if (!imageFilePath) {
181
+ return null;
182
+ }
183
+ return imageFilePath;
184
+ } else {
185
+ // relative paths are resolved against the source file's folder
186
+ const imageFilePath = path.join(path.dirname(filePath), originalImagePath);
187
+ if (!(await pathExists(imageFilePath))) {
188
+ return null;
189
+ }
190
+ return imageFilePath;
191
+ }
192
+ }
193
+
194
+ async function processImageNode(target: Target, context: Context) {
195
+ const [node] = target;
196
+
197
+ if (!node.url) {
198
+ node.url =
199
+ context.onBrokenMarkdownImages({
200
+ url: node.url,
201
+ sourceFilePath: context.filePath,
202
+ node,
203
+ }) ?? node.url;
204
+ return;
205
+ }
206
+
207
+ const localUrlPath = parseLocalURLPath(node.url);
208
+ if (!localUrlPath) {
209
+ // pathname:// is an escape hatch, in case the user does not want images to
210
+ // be converted to require calls going through webpack loader
211
+ if (parseURLOrPath(node.url).protocol === 'pathname:') {
212
+ node.url = node.url.replace('pathname://', '');
213
+ }
214
+ return;
215
+ }
216
+
217
+ // We decode it first because Node Url.pathname is always encoded
218
+ // while the image file-system path are not.
219
+ // See https://github.com/facebook/docusaurus/discussions/10720
220
+ const decodedPathname = decodeURIComponent(localUrlPath.pathname);
221
+
222
+ // We try to convert image urls without protocol to images with require calls
223
+ // going through webpack ensures that image assets exist at build time
224
+ const localImagePath = await getLocalImageAbsolutePath(
225
+ decodedPathname,
226
+ context,
227
+ );
228
+ if (localImagePath === null) {
229
+ node.url =
230
+ context.onBrokenMarkdownImages({
231
+ url: node.url,
232
+ sourceFilePath: context.filePath,
233
+ node,
234
+ }) ?? node.url;
235
+ } else {
236
+ await toImageRequireNode(target, localImagePath, context);
237
+ }
238
+ }
239
+
240
+ const plugin: Plugin<PluginOptions[], Root> = function plugin(
241
+ options,
242
+ ): Transformer<Root> {
243
+ const onBrokenMarkdownImages = asFunction(options.onBrokenMarkdownImages);
244
+
245
+ return async (root, vfile) => {
246
+ const fileLoaderUtils = getFileLoaderUtils(
247
+ vfile.data.compilerName === 'server',
248
+ );
249
+ const context: Context = {
250
+ ...options,
251
+ filePath: vfile.path!,
252
+ inlineMarkdownImageFileLoader:
253
+ fileLoaderUtils.loaders.inlineMarkdownImageFileLoader,
254
+ onBrokenMarkdownImages,
255
+ };
256
+
257
+ const promises: Promise<void>[] = [];
258
+ visit(root, 'image', (node, index, parent) => {
259
+ if (!parent || index === undefined) {
260
+ return;
261
+ }
262
+ promises.push(processImageNode([node, index, parent!], context));
263
+ });
264
+ await Promise.all(promises);
265
+ };
266
+ };
267
+
268
+ export default plugin;
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import path from 'node:path';
9
+ import {visit} from 'unist-util-visit';
10
+ import {
11
+ toMessageRelativeFilePath,
12
+ posixPath,
13
+ escapePath,
14
+ findAsyncSequential,
15
+ getFileLoaderUtils,
16
+ parseURLOrPath,
17
+ parseLocalURLPath,
18
+ } from '@docusaurus/utils';
19
+ import logger from '@docusaurus/logger';
20
+ import {fs} from '@docusaurus/fs';
21
+ import {
22
+ assetRequireAttributeValue,
23
+ formatNodePositionExtraMessage,
24
+ transformNode,
25
+ } from '../utils';
26
+ import type {Plugin, Transformer} from 'unified';
27
+ import type {MdxJsxTextElement} from 'mdast-util-mdx';
28
+ import type {Parent} from 'unist';
29
+ import type {Link, Root} from 'mdast';
30
+ import type {
31
+ MarkdownConfig,
32
+ OnBrokenMarkdownLinksFunction,
33
+ } from '@docusaurus/types';
34
+
35
+ export type PluginOptions = {
36
+ staticDirs: string[];
37
+ siteDir: string;
38
+ onBrokenMarkdownLinks: MarkdownConfig['hooks']['onBrokenMarkdownLinks'];
39
+ };
40
+
41
+ type Context = PluginOptions & {
42
+ staticDirs: string[];
43
+ siteDir: string;
44
+ onBrokenMarkdownLinks: OnBrokenMarkdownLinksFunction;
45
+ filePath: string;
46
+ inlineMarkdownLinkFileLoader: string;
47
+ };
48
+
49
+ type Target = [node: Link, index: number, parent: Parent];
50
+
51
+ function asFunction(
52
+ onBrokenMarkdownLinks: PluginOptions['onBrokenMarkdownLinks'],
53
+ ): OnBrokenMarkdownLinksFunction {
54
+ if (typeof onBrokenMarkdownLinks === 'string') {
55
+ const extraHelp =
56
+ onBrokenMarkdownLinks === 'throw'
57
+ ? logger.interpolate`\nTo ignore this error, use the code=${'siteConfig.markdown.hooks.onBrokenMarkdownLinks'} option, or apply the code=${'pathname://'} protocol to the broken link URLs.`
58
+ : '';
59
+
60
+ return ({sourceFilePath, url: linkUrl, node}) => {
61
+ const relativePath = toMessageRelativeFilePath(sourceFilePath);
62
+ if (linkUrl) {
63
+ logger.report(
64
+ onBrokenMarkdownLinks,
65
+ )`Markdown link with URL code=${linkUrl} in source file path=${relativePath}${formatNodePositionExtraMessage(
66
+ node,
67
+ )} couldn't be resolved.
68
+ Make sure it references a local Markdown file that exists within the current plugin.${extraHelp}`;
69
+ } else {
70
+ logger.report(
71
+ onBrokenMarkdownLinks,
72
+ )`Markdown link with empty URL found in source file path=${relativePath}${formatNodePositionExtraMessage(
73
+ node,
74
+ )}.${extraHelp}`;
75
+ }
76
+ };
77
+ } else {
78
+ return (params) =>
79
+ onBrokenMarkdownLinks({
80
+ ...params,
81
+ sourceFilePath: toMessageRelativeFilePath(params.sourceFilePath),
82
+ });
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Transforms the link node to a JSX `<a>` element with a `require()` call.
88
+ */
89
+ function toAssetRequireNode(
90
+ [node]: Target,
91
+ assetPath: string,
92
+ context: Context,
93
+ ) {
94
+ // MdxJsxTextElement => see https://github.com/facebook/docusaurus/pull/8288#discussion_r1125871405
95
+ const jsxNode = node as unknown as MdxJsxTextElement;
96
+ const attributes: MdxJsxTextElement['attributes'] = [];
97
+
98
+ // require("assets/file.pdf") means requiring from a package called assets
99
+ const relativeAssetPath = `./${posixPath(
100
+ path.relative(path.dirname(context.filePath), assetPath),
101
+ )}`;
102
+
103
+ const parsedUrl = parseURLOrPath(node.url);
104
+ const hash = parsedUrl.hash ?? '';
105
+ const search = parsedUrl.search ?? '';
106
+
107
+ const requireString = `${
108
+ // A hack to stop Webpack from using its built-in loader to parse JSON
109
+ path.extname(relativeAssetPath) === '.json'
110
+ ? `${relativeAssetPath.replace('.json', '.raw')}!=`
111
+ : ''
112
+ }${context.inlineMarkdownLinkFileLoader}${
113
+ escapePath(relativeAssetPath) + search
114
+ }`;
115
+
116
+ attributes.push({
117
+ type: 'mdxJsxAttribute',
118
+ name: 'target',
119
+ value: '_blank',
120
+ });
121
+
122
+ // Assets are not routes, and are required by Webpack already
123
+ // They should not trigger the broken link checker
124
+ attributes.push({
125
+ type: 'mdxJsxAttribute',
126
+ name: 'data-noBrokenLinkCheck',
127
+ value: {
128
+ type: 'mdxJsxAttributeValueExpression',
129
+ value: 'true',
130
+ data: {
131
+ estree: {
132
+ type: 'Program',
133
+ body: [
134
+ {
135
+ type: 'ExpressionStatement',
136
+ expression: {
137
+ type: 'Literal',
138
+ value: true,
139
+ raw: 'true',
140
+ },
141
+ },
142
+ ],
143
+ sourceType: 'module',
144
+ comments: [],
145
+ },
146
+ },
147
+ },
148
+ });
149
+
150
+ attributes.push({
151
+ type: 'mdxJsxAttribute',
152
+ name: 'href',
153
+ value: assetRequireAttributeValue(requireString, hash),
154
+ });
155
+
156
+ if (node.title) {
157
+ attributes.push({
158
+ type: 'mdxJsxAttribute',
159
+ name: 'title',
160
+ value: node.title,
161
+ });
162
+ }
163
+
164
+ const {children} = node;
165
+
166
+ transformNode(jsxNode, {
167
+ type: 'mdxJsxTextElement',
168
+ name: 'a',
169
+ attributes,
170
+ children,
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Checks if the given file path exists and is a file.
176
+ * Returns true if it exists, false otherwise.
177
+ */
178
+ async function isExistingFile(filePath: string): Promise<boolean> {
179
+ try {
180
+ return (await fs.stat(filePath)).isFile();
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
185
+
186
+ async function getLocalFileAbsolutePath(
187
+ assetPath: string,
188
+ {siteDir, filePath, staticDirs}: Context,
189
+ ) {
190
+ if (assetPath.startsWith('@site/')) {
191
+ const assetFilePath = path.join(siteDir, assetPath.replace('@site/', ''));
192
+ if (await isExistingFile(assetFilePath)) {
193
+ return assetFilePath;
194
+ }
195
+ } else if (path.isAbsolute(assetPath)) {
196
+ const assetFilePath = await findAsyncSequential(
197
+ staticDirs.map((dir) => path.join(dir, assetPath)),
198
+ isExistingFile,
199
+ );
200
+ if (assetFilePath) {
201
+ return assetFilePath;
202
+ }
203
+ } else {
204
+ const assetFilePath = path.join(path.dirname(filePath), assetPath);
205
+ if (await isExistingFile(assetFilePath)) {
206
+ return assetFilePath;
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+
212
+ async function processLinkNode(target: Target, context: Context) {
213
+ const [node] = target;
214
+ if (!node.url) {
215
+ node.url =
216
+ context.onBrokenMarkdownLinks({
217
+ url: node.url,
218
+ sourceFilePath: context.filePath,
219
+ node,
220
+ }) ?? node.url;
221
+ return;
222
+ }
223
+
224
+ const localUrlPath = parseLocalURLPath(node.url);
225
+ if (!localUrlPath) {
226
+ // Don't process pathname:// here, it's used by the <Link> component
227
+ return;
228
+ }
229
+
230
+ const hasSiteAlias = localUrlPath.pathname.startsWith('@site/');
231
+ const hasAssetLikeExtension =
232
+ path.extname(localUrlPath.pathname) &&
233
+ !localUrlPath.pathname.match(/\.(?:mdx?|html)(?:#|$)/);
234
+ if (!hasSiteAlias && !hasAssetLikeExtension) {
235
+ return;
236
+ }
237
+
238
+ const localFilePath = await getLocalFileAbsolutePath(
239
+ decodeURIComponent(localUrlPath.pathname),
240
+ context,
241
+ );
242
+
243
+ if (localFilePath) {
244
+ toAssetRequireNode(target, localFilePath, context);
245
+ } else {
246
+ // The @site alias is the only way to believe that the user wants an asset.
247
+ if (hasSiteAlias) {
248
+ node.url =
249
+ context.onBrokenMarkdownLinks({
250
+ url: node.url,
251
+ sourceFilePath: context.filePath,
252
+ node,
253
+ }) ?? node.url;
254
+ } else {
255
+ // Even if the url has a dot, and it looks like a file extension
256
+ // it can be risky to throw and fail fast by default
257
+ // It's perfectly valid for a route path segment to look like a filename
258
+ }
259
+ }
260
+ }
261
+
262
+ const plugin: Plugin<PluginOptions[], Root> = function plugin(
263
+ options,
264
+ ): Transformer<Root> {
265
+ const onBrokenMarkdownLinks = asFunction(options.onBrokenMarkdownLinks);
266
+
267
+ return async (root, vfile) => {
268
+ const fileLoaderUtils = getFileLoaderUtils(
269
+ vfile.data.compilerName === 'server',
270
+ );
271
+
272
+ const context: Context = {
273
+ ...options,
274
+ filePath: vfile.path!,
275
+ inlineMarkdownLinkFileLoader:
276
+ fileLoaderUtils.loaders.inlineMarkdownLinkFileLoader,
277
+ onBrokenMarkdownLinks,
278
+ };
279
+
280
+ const promises: Promise<void>[] = [];
281
+ visit(root, 'link', (node, index, parent) => {
282
+ if (!parent || index === undefined) {
283
+ return;
284
+ }
285
+ promises.push(processLinkNode([node, index, parent], context));
286
+ });
287
+ await Promise.all(promises);
288
+ };
289
+ };
290
+
291
+ export default plugin;