@docusaurus/utils 2.0.0-beta.15 → 2.0.0-beta.16
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/lib/dataFileUtils.js +2 -2
- package/lib/dataFileUtils.js.map +1 -1
- package/lib/gitUtils.d.ts +17 -0
- package/lib/gitUtils.d.ts.map +1 -0
- package/lib/gitUtils.js +63 -0
- package/lib/gitUtils.js.map +1 -0
- package/lib/hashUtils.js +4 -3
- package/lib/hashUtils.js.map +1 -1
- package/lib/index.d.ts +14 -12
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +75 -29
- package/lib/index.js.map +1 -1
- package/lib/markdownLinks.d.ts.map +1 -1
- package/lib/markdownLinks.js +8 -5
- package/lib/markdownLinks.js.map +1 -1
- package/lib/markdownParser.d.ts.map +1 -1
- package/lib/markdownParser.js +35 -35
- package/lib/markdownParser.js.map +1 -1
- package/lib/pathUtils.d.ts +14 -1
- package/lib/pathUtils.d.ts.map +1 -1
- package/lib/pathUtils.js +19 -11
- package/lib/pathUtils.js.map +1 -1
- package/lib/tags.d.ts +8 -0
- package/lib/tags.d.ts.map +1 -1
- package/lib/tags.js +18 -13
- package/lib/tags.js.map +1 -1
- package/lib/urlUtils.d.ts.map +1 -1
- package/lib/urlUtils.js +11 -10
- package/lib/urlUtils.js.map +1 -1
- package/lib/webpackUtils.d.ts.map +1 -1
- package/lib/webpackUtils.js +11 -9
- package/lib/webpackUtils.js.map +1 -1
- package/package.json +12 -20
- package/src/dataFileUtils.ts +2 -2
- package/src/deps.d.ts +0 -4
- package/src/gitUtils.ts +93 -0
- package/src/hashUtils.ts +3 -3
- package/src/index.ts +86 -32
- package/src/markdownLinks.ts +9 -5
- package/src/markdownParser.ts +35 -33
- package/src/pathUtils.ts +19 -11
- package/src/tags.ts +17 -13
- package/src/urlUtils.ts +11 -10
- package/src/webpackUtils.ts +11 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/utils",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.16",
|
|
4
4
|
"description": "Node utility functions for Docusaurus packages.",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -18,40 +18,32 @@
|
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@docusaurus/logger": "2.0.0-beta.
|
|
22
|
-
"@mdx-js/runtime": "^1.6.22",
|
|
21
|
+
"@docusaurus/logger": "2.0.0-beta.16",
|
|
23
22
|
"@svgr/webpack": "^6.0.0",
|
|
24
23
|
"file-loader": "^6.2.0",
|
|
25
|
-
"fs-extra": "^10.0.
|
|
24
|
+
"fs-extra": "^10.0.1",
|
|
26
25
|
"github-slugger": "^1.4.0",
|
|
27
26
|
"globby": "^11.0.4",
|
|
28
27
|
"gray-matter": "^4.0.3",
|
|
29
|
-
"js-yaml": "^4.
|
|
30
|
-
"lodash": "^4.17.
|
|
28
|
+
"js-yaml": "^4.1.0",
|
|
29
|
+
"lodash": "^4.17.21",
|
|
31
30
|
"micromatch": "^4.0.4",
|
|
32
|
-
"remark-mdx-remove-exports": "^1.6.22",
|
|
33
|
-
"remark-mdx-remove-imports": "^1.6.22",
|
|
34
31
|
"resolve-pathname": "^3.0.0",
|
|
32
|
+
"shelljs": "^0.8.5",
|
|
35
33
|
"tslib": "^2.3.1",
|
|
36
|
-
"url-loader": "^4.1.1"
|
|
34
|
+
"url-loader": "^4.1.1",
|
|
35
|
+
"webpack": "^5.69.1"
|
|
37
36
|
},
|
|
38
37
|
"engines": {
|
|
39
38
|
"node": ">=14"
|
|
40
39
|
},
|
|
41
40
|
"devDependencies": {
|
|
42
|
-
"@docusaurus/types": "2.0.0-beta.
|
|
41
|
+
"@docusaurus/types": "2.0.0-beta.16",
|
|
43
42
|
"@types/dedent": "^0.7.0",
|
|
44
43
|
"@types/github-slugger": "^1.3.0",
|
|
45
44
|
"@types/micromatch": "^4.0.2",
|
|
46
|
-
"@types/react-dom": "^17.0.
|
|
47
|
-
"dedent": "^0.7.0"
|
|
48
|
-
"tslib": "^2.3.1"
|
|
45
|
+
"@types/react-dom": "^17.0.11",
|
|
46
|
+
"dedent": "^0.7.0"
|
|
49
47
|
},
|
|
50
|
-
"
|
|
51
|
-
"@babel/core": "^7.0.0",
|
|
52
|
-
"react": "*",
|
|
53
|
-
"react-dom": "*",
|
|
54
|
-
"webpack": "5.x"
|
|
55
|
-
},
|
|
56
|
-
"gitHead": "32ec84ef3c0a238436e913b2026ab809e5750fa8"
|
|
48
|
+
"gitHead": "eb43c4d4f95a4fb97dc9bb9dc615413e0dc2e1e7"
|
|
57
49
|
}
|
package/src/dataFileUtils.ts
CHANGED
|
@@ -48,10 +48,10 @@ export async function getDataFileData<T>(
|
|
|
48
48
|
const contentString = await fs.readFile(filePath, {encoding: 'utf8'});
|
|
49
49
|
const unsafeContent = Yaml.load(contentString);
|
|
50
50
|
return validate(unsafeContent);
|
|
51
|
-
} catch (
|
|
51
|
+
} catch (err) {
|
|
52
52
|
// TODO replace later by error cause, see https://v8.dev/features/error-cause
|
|
53
53
|
logger.error`The ${params.fileType} file at path=${filePath} looks invalid.`;
|
|
54
|
-
throw
|
|
54
|
+
throw err;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
package/src/deps.d.ts
CHANGED
package/src/gitUtils.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
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 'path';
|
|
9
|
+
import shell from 'shelljs';
|
|
10
|
+
|
|
11
|
+
export class GitNotFoundError extends Error {}
|
|
12
|
+
|
|
13
|
+
export const getFileCommitDate = (
|
|
14
|
+
file: string,
|
|
15
|
+
{
|
|
16
|
+
age = 'oldest',
|
|
17
|
+
includeAuthor = false,
|
|
18
|
+
}: {
|
|
19
|
+
age?: 'oldest' | 'newest';
|
|
20
|
+
includeAuthor?: boolean;
|
|
21
|
+
},
|
|
22
|
+
): {
|
|
23
|
+
date: Date;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
author?: string;
|
|
26
|
+
} => {
|
|
27
|
+
if (!shell.which('git')) {
|
|
28
|
+
throw new GitNotFoundError(
|
|
29
|
+
`Failed to retrieve git history for "${file}" because git is not installed.`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!shell.test('-f', file)) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Failed to retrieve git history for "${file}" because the file does not exist.`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const fileBasename = path.basename(file);
|
|
40
|
+
const fileDirname = path.dirname(file);
|
|
41
|
+
|
|
42
|
+
let formatArg = '--format=%ct';
|
|
43
|
+
if (includeAuthor) {
|
|
44
|
+
formatArg += ',%an';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let extraArgs = '--max-count=1';
|
|
48
|
+
if (age === 'oldest') {
|
|
49
|
+
// --follow is necessary to follow file renames
|
|
50
|
+
// --diff-filter=A ensures we only get the commit which (A)dded the file
|
|
51
|
+
extraArgs += ' --follow --diff-filter=A';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = shell.exec(
|
|
55
|
+
`git log ${extraArgs} ${formatArg} -- "${fileBasename}"`,
|
|
56
|
+
{
|
|
57
|
+
// cwd is important, see: https://github.com/facebook/docusaurus/pull/5048
|
|
58
|
+
cwd: fileDirname,
|
|
59
|
+
silent: true,
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
if (result.code !== 0) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Failed to retrieve the git history for file "${file}" with exit code ${result.code}: ${result.stderr}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
let regex = /^(?<timestamp>\d+)$/;
|
|
68
|
+
if (includeAuthor) {
|
|
69
|
+
regex = /^(?<timestamp>\d+),(?<author>.+)$/;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const output = result.stdout.trim();
|
|
73
|
+
const match = output.match(regex);
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
!match ||
|
|
77
|
+
!match.groups ||
|
|
78
|
+
!match.groups.timestamp ||
|
|
79
|
+
(includeAuthor && !match.groups.author)
|
|
80
|
+
) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Failed to retrieve the git history for file "${file}" with unexpected output: ${output}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const timestamp = Number(match.groups.timestamp);
|
|
87
|
+
const date = new Date(timestamp * 1000);
|
|
88
|
+
|
|
89
|
+
if (includeAuthor) {
|
|
90
|
+
return {date, timestamp, author: match.groups.author};
|
|
91
|
+
}
|
|
92
|
+
return {date, timestamp};
|
|
93
|
+
};
|
package/src/hashUtils.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {createHash} from 'crypto';
|
|
9
|
-
import
|
|
9
|
+
import _ from 'lodash';
|
|
10
10
|
import {shortName, isNameTooLong} from './pathUtils';
|
|
11
11
|
|
|
12
12
|
export function md5Hash(str: string): string {
|
|
@@ -29,9 +29,9 @@ export function docuHash(str: string): string {
|
|
|
29
29
|
return 'index';
|
|
30
30
|
}
|
|
31
31
|
const shortHash = simpleHash(str, 3);
|
|
32
|
-
const parsedPath = `${kebabCase(str)}-${shortHash}`;
|
|
32
|
+
const parsedPath = `${_.kebabCase(str)}-${shortHash}`;
|
|
33
33
|
if (isNameTooLong(parsedPath)) {
|
|
34
|
-
return `${shortName(kebabCase(str))}-${shortHash}`;
|
|
34
|
+
return `${shortName(_.kebabCase(str))}-${shortHash}`;
|
|
35
35
|
}
|
|
36
36
|
return parsedPath;
|
|
37
37
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import logger from '@docusaurus/logger';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import {createHash} from 'crypto';
|
|
11
|
-
import
|
|
11
|
+
import _ from 'lodash';
|
|
12
12
|
import fs from 'fs-extra';
|
|
13
13
|
import {URL} from 'url';
|
|
14
14
|
import type {
|
|
@@ -22,17 +22,69 @@ import resolvePathnameUnsafe from 'resolve-pathname';
|
|
|
22
22
|
import {simpleHash, docuHash} from './hashUtils';
|
|
23
23
|
import {DEFAULT_PLUGIN_ID} from './constants';
|
|
24
24
|
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
export {
|
|
26
|
+
NODE_MAJOR_VERSION,
|
|
27
|
+
NODE_MINOR_VERSION,
|
|
28
|
+
DEFAULT_BUILD_DIR_NAME,
|
|
29
|
+
DEFAULT_CONFIG_FILE_NAME,
|
|
30
|
+
BABEL_CONFIG_FILE_NAME,
|
|
31
|
+
GENERATED_FILES_DIR_NAME,
|
|
32
|
+
SRC_DIR_NAME,
|
|
33
|
+
STATIC_DIR_NAME,
|
|
34
|
+
OUTPUT_STATIC_ASSETS_DIR_NAME,
|
|
35
|
+
THEME_PATH,
|
|
36
|
+
DEFAULT_PORT,
|
|
37
|
+
DEFAULT_PLUGIN_ID,
|
|
38
|
+
WEBPACK_URL_LOADER_LIMIT,
|
|
39
|
+
} from './constants';
|
|
40
|
+
export {getFileCommitDate, GitNotFoundError} from './gitUtils';
|
|
41
|
+
export {normalizeUrl, getEditUrl} from './urlUtils';
|
|
42
|
+
export {
|
|
43
|
+
type Tag,
|
|
44
|
+
type FrontMatterTag,
|
|
45
|
+
type TaggedItemGroup,
|
|
46
|
+
normalizeFrontMatterTag,
|
|
47
|
+
normalizeFrontMatterTags,
|
|
48
|
+
groupTaggedItems,
|
|
49
|
+
} from './tags';
|
|
50
|
+
export {
|
|
51
|
+
parseMarkdownHeadingId,
|
|
52
|
+
createExcerpt,
|
|
53
|
+
parseFrontMatter,
|
|
54
|
+
parseMarkdownContentTitle,
|
|
55
|
+
parseMarkdownString,
|
|
56
|
+
} from './markdownParser';
|
|
57
|
+
export {
|
|
58
|
+
type ContentPaths,
|
|
59
|
+
type BrokenMarkdownLink,
|
|
60
|
+
type ReplaceMarkdownLinksParams,
|
|
61
|
+
type ReplaceMarkdownLinksReturn,
|
|
62
|
+
replaceMarkdownLinks,
|
|
63
|
+
} from './markdownLinks';
|
|
64
|
+
export {type SluggerOptions, type Slugger, createSlugger} from './slugger';
|
|
65
|
+
export {
|
|
66
|
+
isNameTooLong,
|
|
67
|
+
shortName,
|
|
68
|
+
posixPath,
|
|
69
|
+
toMessageRelativeFilePath,
|
|
70
|
+
aliasedSitePath,
|
|
71
|
+
escapePath,
|
|
72
|
+
} from './pathUtils';
|
|
73
|
+
export {md5Hash, simpleHash, docuHash} from './hashUtils';
|
|
74
|
+
export {
|
|
75
|
+
Globby,
|
|
76
|
+
GlobExcludeDefault,
|
|
77
|
+
createMatcher,
|
|
78
|
+
createAbsoluteFilePathMatcher,
|
|
79
|
+
} from './globUtils';
|
|
80
|
+
export {getFileLoaderUtils} from './webpackUtils';
|
|
81
|
+
export {
|
|
82
|
+
getDataFilePath,
|
|
83
|
+
getDataFileData,
|
|
84
|
+
getContentPathList,
|
|
85
|
+
findFolderContainingFile,
|
|
86
|
+
getFolderContainingFile,
|
|
87
|
+
} from './dataFileUtils';
|
|
36
88
|
|
|
37
89
|
const fileHash = new Map<string, string>();
|
|
38
90
|
export async function generate(
|
|
@@ -54,7 +106,7 @@ export async function generate(
|
|
|
54
106
|
// If file already exists but its not in runtime cache yet,
|
|
55
107
|
// we try to calculate the content hash and then compare
|
|
56
108
|
// This is to avoid unnecessary overwriting and we can reuse old file.
|
|
57
|
-
if (!lastHash && fs.
|
|
109
|
+
if (!lastHash && (await fs.pathExists(filepath))) {
|
|
58
110
|
const lastContent = await fs.readFile(filepath, 'utf8');
|
|
59
111
|
lastHash = createHash('md5').update(lastContent).digest('hex');
|
|
60
112
|
fileHash.set(filepath, lastHash);
|
|
@@ -69,8 +121,8 @@ export async function generate(
|
|
|
69
121
|
}
|
|
70
122
|
}
|
|
71
123
|
|
|
72
|
-
const indexRE = /(
|
|
73
|
-
const extRE = /\.(
|
|
124
|
+
const indexRE = /(?<dirname>^|.*\/)index\.(?:mdx?|jsx?|tsx?)$/i;
|
|
125
|
+
const extRE = /\.(?:mdx?|jsx?|tsx?)$/;
|
|
74
126
|
|
|
75
127
|
/**
|
|
76
128
|
* Convert filepath to url path.
|
|
@@ -126,7 +178,7 @@ export function isValidPathname(str: string): boolean {
|
|
|
126
178
|
// weird, but is there a better way?
|
|
127
179
|
const parsedPathname = new URL(str, 'https://domain.com').pathname;
|
|
128
180
|
return parsedPathname === str || parsedPathname === encodeURI(str);
|
|
129
|
-
} catch
|
|
181
|
+
} catch {
|
|
130
182
|
return false;
|
|
131
183
|
}
|
|
132
184
|
}
|
|
@@ -200,7 +252,8 @@ export function getPluginI18nPath({
|
|
|
200
252
|
return path.join(
|
|
201
253
|
siteDir,
|
|
202
254
|
'i18n',
|
|
203
|
-
// namespace first by locale: convenient to work in a single folder for a
|
|
255
|
+
// namespace first by locale: convenient to work in a single folder for a
|
|
256
|
+
// translator
|
|
204
257
|
locale,
|
|
205
258
|
// Make it convenient to use for single-instance
|
|
206
259
|
// ie: return "docs", not "docs-default" nor "docs/default"
|
|
@@ -212,7 +265,8 @@ export function getPluginI18nPath({
|
|
|
212
265
|
/**
|
|
213
266
|
* @param permalink The URL that the HTML file corresponds to, without base URL
|
|
214
267
|
* @param outDir Full path to the output directory
|
|
215
|
-
* @param trailingSlash The site config option. If provided, only one path will
|
|
268
|
+
* @param trailingSlash The site config option. If provided, only one path will
|
|
269
|
+
* be read.
|
|
216
270
|
* @returns This returns a buffer, which you have to decode string yourself if
|
|
217
271
|
* needed. (Not always necessary since the output isn't for human consumption
|
|
218
272
|
* anyways, and most HTML manipulation libs accept buffers)
|
|
@@ -223,23 +277,25 @@ export async function readOutputHTMLFile(
|
|
|
223
277
|
trailingSlash: boolean | undefined,
|
|
224
278
|
): Promise<Buffer> {
|
|
225
279
|
const withTrailingSlashPath = path.join(outDir, permalink, 'index.html');
|
|
226
|
-
const withoutTrailingSlashPath = path.join(
|
|
280
|
+
const withoutTrailingSlashPath = path.join(
|
|
281
|
+
outDir,
|
|
282
|
+
`${permalink.replace(/\/$/, '')}.html`,
|
|
283
|
+
);
|
|
227
284
|
if (trailingSlash) {
|
|
228
285
|
return fs.readFile(withTrailingSlashPath);
|
|
229
286
|
} else if (trailingSlash === false) {
|
|
230
287
|
return fs.readFile(withoutTrailingSlashPath);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
288
|
+
}
|
|
289
|
+
const HTMLPath = await findAsyncSequential(
|
|
290
|
+
[withTrailingSlashPath, withoutTrailingSlashPath],
|
|
291
|
+
fs.pathExists,
|
|
292
|
+
);
|
|
293
|
+
if (!HTMLPath) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Expected output HTML file to be found at ${withTrailingSlashPath}`,
|
|
235
296
|
);
|
|
236
|
-
if (!HTMLPath) {
|
|
237
|
-
throw new Error(
|
|
238
|
-
`Expected output HTML file to be found at ${withTrailingSlashPath}`,
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
return fs.readFile(HTMLPath);
|
|
242
297
|
}
|
|
298
|
+
return fs.readFile(HTMLPath);
|
|
243
299
|
}
|
|
244
300
|
|
|
245
301
|
export async function mapAsyncSequential<T, R>(
|
|
@@ -247,7 +303,6 @@ export async function mapAsyncSequential<T, R>(
|
|
|
247
303
|
action: (t: T) => Promise<R>,
|
|
248
304
|
): Promise<R[]> {
|
|
249
305
|
const results: R[] = [];
|
|
250
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
251
306
|
for (const t of array) {
|
|
252
307
|
const result = await action(t);
|
|
253
308
|
results.push(result);
|
|
@@ -259,7 +314,6 @@ export async function findAsyncSequential<T>(
|
|
|
259
314
|
array: T[],
|
|
260
315
|
predicate: (t: T) => Promise<boolean>,
|
|
261
316
|
): Promise<T | undefined> {
|
|
262
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
263
317
|
for (const t of array) {
|
|
264
318
|
if (await predicate(t)) {
|
|
265
319
|
return t;
|
|
@@ -307,7 +361,7 @@ export function updateTranslationFileMessages(
|
|
|
307
361
|
): TranslationFile {
|
|
308
362
|
return {
|
|
309
363
|
...translationFile,
|
|
310
|
-
content: mapValues(translationFile.content, (translation) => ({
|
|
364
|
+
content: _.mapValues(translationFile.content, (translation) => ({
|
|
311
365
|
...translation,
|
|
312
366
|
message: updateMessage(translation.message),
|
|
313
367
|
})),
|
package/src/markdownLinks.ts
CHANGED
|
@@ -51,7 +51,8 @@ export function replaceMarkdownLinks<T extends ContentPaths>({
|
|
|
51
51
|
if (!fencedBlock) {
|
|
52
52
|
fencedBlock = true;
|
|
53
53
|
[lastCodeFence] = line.trim().match(/^`+/)!;
|
|
54
|
-
// If we are in a ````-fenced block, all ``` would be plain text instead
|
|
54
|
+
// If we are in a ````-fenced block, all ``` would be plain text instead
|
|
55
|
+
// of fences
|
|
55
56
|
} else if (line.trim().match(/^`+/)![0].length >= lastCodeFence.length) {
|
|
56
57
|
fencedBlock = false;
|
|
57
58
|
}
|
|
@@ -62,13 +63,15 @@ export function replaceMarkdownLinks<T extends ContentPaths>({
|
|
|
62
63
|
|
|
63
64
|
let modifiedLine = line;
|
|
64
65
|
// Replace inline-style links or reference-style links e.g:
|
|
65
|
-
// This is [Document 1](doc1.md) -> we replace this doc1.md with correct
|
|
66
|
+
// This is [Document 1](doc1.md) -> we replace this doc1.md with correct
|
|
67
|
+
// ink
|
|
66
68
|
// [doc1]: doc1.md -> we replace this doc1.md with correct link
|
|
67
|
-
const mdRegex =
|
|
69
|
+
const mdRegex =
|
|
70
|
+
/(?:(?:\]\()|(?:\]:\s*))(?!https?:\/\/|@site\/)(?<filename>[^'")\]\s>]+\.mdx?)/g;
|
|
68
71
|
let mdMatch = mdRegex.exec(modifiedLine);
|
|
69
72
|
while (mdMatch !== null) {
|
|
70
73
|
// Replace it to correct html link.
|
|
71
|
-
const mdLink = mdMatch
|
|
74
|
+
const mdLink = mdMatch.groups!.filename;
|
|
72
75
|
|
|
73
76
|
const sourcesToTry = [
|
|
74
77
|
path.resolve(path.dirname(filePath), decodeURIComponent(mdLink)),
|
|
@@ -85,7 +88,8 @@ export function replaceMarkdownLinks<T extends ContentPaths>({
|
|
|
85
88
|
: undefined;
|
|
86
89
|
|
|
87
90
|
if (permalink) {
|
|
88
|
-
// MDX won't be happy if the permalink contains a space, we need to
|
|
91
|
+
// MDX won't be happy if the permalink contains a space, we need to
|
|
92
|
+
// convert it to %20
|
|
89
93
|
const encodedPermalink = permalink
|
|
90
94
|
.split('/')
|
|
91
95
|
.map((part) => part.replace(/\s/g, '%20'))
|
package/src/markdownParser.ts
CHANGED
|
@@ -14,16 +14,15 @@ export function parseMarkdownHeadingId(heading: string): {
|
|
|
14
14
|
text: string;
|
|
15
15
|
id?: string;
|
|
16
16
|
} {
|
|
17
|
-
const customHeadingIdRegex = /^(
|
|
17
|
+
const customHeadingIdRegex = /^(?<text>.*?)\s*\{#(?<id>[\w-]+)\}$/;
|
|
18
18
|
const matches = customHeadingIdRegex.exec(heading);
|
|
19
19
|
if (matches) {
|
|
20
20
|
return {
|
|
21
|
-
text: matches
|
|
22
|
-
id: matches
|
|
21
|
+
text: matches.groups!.text,
|
|
22
|
+
id: matches.groups!.id,
|
|
23
23
|
};
|
|
24
|
-
} else {
|
|
25
|
-
return {text: heading, id: undefined};
|
|
26
24
|
}
|
|
25
|
+
return {text: heading, id: undefined};
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
// Hacky way of stripping out import statements from the excerpt
|
|
@@ -39,7 +38,6 @@ export function createExcerpt(fileString: string): string | undefined {
|
|
|
39
38
|
let lastCodeFence = '';
|
|
40
39
|
|
|
41
40
|
/* eslint-disable no-continue */
|
|
42
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
43
41
|
for (const fileLine of fileLines) {
|
|
44
42
|
// Skip empty line.
|
|
45
43
|
if (!fileLine.trim()) {
|
|
@@ -47,7 +45,7 @@ export function createExcerpt(fileString: string): string | undefined {
|
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
// Skip import/export declaration.
|
|
50
|
-
if (
|
|
48
|
+
if (/^(?:import|export)\s.*/.test(fileLine)) {
|
|
51
49
|
continue;
|
|
52
50
|
}
|
|
53
51
|
|
|
@@ -56,7 +54,8 @@ export function createExcerpt(fileString: string): string | undefined {
|
|
|
56
54
|
if (!inCode) {
|
|
57
55
|
inCode = true;
|
|
58
56
|
[lastCodeFence] = fileLine.trim().match(/^`+/)!;
|
|
59
|
-
// If we are in a ````-fenced block, all ``` would be plain text instead
|
|
57
|
+
// If we are in a ````-fenced block, all ``` would be plain text instead
|
|
58
|
+
// of fences
|
|
60
59
|
} else if (
|
|
61
60
|
fileLine.trim().match(/^`+/)![0].length >= lastCodeFence.length
|
|
62
61
|
) {
|
|
@@ -71,25 +70,27 @@ export function createExcerpt(fileString: string): string | undefined {
|
|
|
71
70
|
// Remove HTML tags.
|
|
72
71
|
.replace(/<[^>]*>/g, '')
|
|
73
72
|
// Remove Title headers
|
|
74
|
-
.replace(/^#\s*
|
|
73
|
+
.replace(/^#\s*[^#]*\s*#?/gm, '')
|
|
75
74
|
// Remove Markdown + ATX-style headers
|
|
76
|
-
.replace(/^#{1,6}\s*([^#]*)\s*(
|
|
77
|
-
// Remove emphasis
|
|
78
|
-
.replace(/([*_
|
|
75
|
+
.replace(/^#{1,6}\s*(?<text>[^#]*)\s*(?:#{1,6})?/gm, '$1')
|
|
76
|
+
// Remove emphasis.
|
|
77
|
+
.replace(/(?<opening>[*_]{1,3})(?<text>.*?)\1/g, '$2')
|
|
78
|
+
// Remove strikethroughs.
|
|
79
|
+
.replace(/~~(?<text>\S.*\S)~~/g, '$1')
|
|
79
80
|
// Remove images.
|
|
80
|
-
.replace(/!\[(
|
|
81
|
+
.replace(/!\[(?<alt>.*?)\][[(].*?[\])]/g, '$1')
|
|
81
82
|
// Remove footnotes.
|
|
82
|
-
.replace(/\[\^.+?\](
|
|
83
|
+
.replace(/\[\^.+?\](?:: .*?$)?/g, '')
|
|
83
84
|
// Remove inline links.
|
|
84
|
-
.replace(/\[(
|
|
85
|
+
.replace(/\[(?<alt>.*?)\][[(].*?[\])]/g, '$1')
|
|
85
86
|
// Remove inline code.
|
|
86
|
-
.replace(/`(
|
|
87
|
+
.replace(/`(?<text>.+?)`/g, '$1')
|
|
87
88
|
// Remove blockquotes.
|
|
88
89
|
.replace(/^\s{0,3}>\s?/g, '')
|
|
89
90
|
// Remove admonition definition.
|
|
90
|
-
.replace(
|
|
91
|
+
.replace(/:::.*/, '')
|
|
91
92
|
// Remove Emoji names within colons include preceding whitespace.
|
|
92
|
-
.replace(/\s
|
|
93
|
+
.replace(/\s?:(?:::|[^:\n])+:/g, '')
|
|
93
94
|
// Remove custom Markdown heading id.
|
|
94
95
|
.replace(/{#*[\w-]+}/, '')
|
|
95
96
|
.trim();
|
|
@@ -113,9 +114,11 @@ export function parseFrontMatter(markdownFileContent: string): {
|
|
|
113
114
|
};
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Try to convert markdown heading to text. Does not need to be perfect, it is
|
|
119
|
+
* only used as a fallback when frontMatter.title is not provided. For now, we
|
|
120
|
+
* just unwrap possible inline code blocks (# `config.js`)
|
|
121
|
+
*/
|
|
119
122
|
function toTextContentTitle(contentTitle: string): string {
|
|
120
123
|
if (contentTitle.startsWith('`') && contentTitle.endsWith('`')) {
|
|
121
124
|
return contentTitle.substring(1, contentTitle.length - 1);
|
|
@@ -132,9 +135,9 @@ export function parseMarkdownContentTitle(
|
|
|
132
135
|
const content = contentUntrimmed.trim();
|
|
133
136
|
|
|
134
137
|
const IMPORT_STATEMENT =
|
|
135
|
-
/import\s+(
|
|
138
|
+
/import\s+(?:[\w*{}\s\n,]+from\s+)?["'\s][@\w/_.-]+["'\s];?|\n/.source;
|
|
136
139
|
const REGULAR_TITLE =
|
|
137
|
-
/(?<pattern>#\s*(?<title>[^#\n{]*)+[ \t]*(?<suffix>({#*[\w-]+})|#)?\n*?)/
|
|
140
|
+
/(?<pattern>#\s*(?<title>[^#\n{]*)+[ \t]*(?<suffix>(?:{#*[\w-]+})|#)?\n*?)/
|
|
138
141
|
.source;
|
|
139
142
|
const ALTERNATE_TITLE = /(?<pattern>\s*(?<title>[^\n]*)\s*\n[=]+)/.source;
|
|
140
143
|
|
|
@@ -152,15 +155,14 @@ export function parseMarkdownContentTitle(
|
|
|
152
155
|
|
|
153
156
|
if (!pattern || !title) {
|
|
154
157
|
return {content, contentTitle: undefined};
|
|
155
|
-
} else {
|
|
156
|
-
const newContent = removeContentTitleOption
|
|
157
|
-
? content.replace(pattern, '')
|
|
158
|
-
: content;
|
|
159
|
-
return {
|
|
160
|
-
content: newContent.trim(),
|
|
161
|
-
contentTitle: toTextContentTitle(title.trim()).trim(),
|
|
162
|
-
};
|
|
163
158
|
}
|
|
159
|
+
const newContent = removeContentTitleOption
|
|
160
|
+
? content.replace(pattern, '')
|
|
161
|
+
: content;
|
|
162
|
+
return {
|
|
163
|
+
content: newContent.trim(),
|
|
164
|
+
contentTitle: toTextContentTitle(title.trim()).trim(),
|
|
165
|
+
};
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
type ParsedMarkdown = {
|
|
@@ -191,9 +193,9 @@ export function parseMarkdownString(
|
|
|
191
193
|
contentTitle,
|
|
192
194
|
excerpt,
|
|
193
195
|
};
|
|
194
|
-
} catch (
|
|
196
|
+
} catch (err) {
|
|
195
197
|
logger.error(`Error while parsing Markdown front matter.
|
|
196
198
|
This can happen if you use special characters in front matter values (try using double quotes around that value).`);
|
|
197
|
-
throw
|
|
199
|
+
throw err;
|
|
198
200
|
}
|
|
199
201
|
}
|
package/src/pathUtils.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import path from 'path';
|
|
11
11
|
|
|
12
|
-
// MacOS (APFS) and Windows (NTFS) filename length limit = 255 chars,
|
|
12
|
+
// MacOS (APFS) and Windows (NTFS) filename length limit = 255 chars,
|
|
13
|
+
// Others = 255 bytes
|
|
13
14
|
const MAX_PATH_SEGMENT_CHARS = 255;
|
|
14
15
|
const MAX_PATH_SEGMENT_BYTES = 255;
|
|
15
16
|
// Space for appending things to the string like file extensions and so on
|
|
@@ -19,7 +20,7 @@ const isMacOs = () => process.platform === 'darwin';
|
|
|
19
20
|
const isWindows = () => process.platform === 'win32';
|
|
20
21
|
|
|
21
22
|
export const isNameTooLong = (str: string): boolean =>
|
|
22
|
-
//
|
|
23
|
+
// Not entirely correct: we can't assume FS from OS. But good enough?
|
|
23
24
|
isMacOs() || isWindows()
|
|
24
25
|
? str.length + SPACE_FOR_APPENDING > MAX_PATH_SEGMENT_CHARS // MacOS (APFS) and Windows (NTFS) filename length limit (255 chars)
|
|
25
26
|
: Buffer.from(str).length + SPACE_FOR_APPENDING > MAX_PATH_SEGMENT_BYTES; // Other (255 bytes)
|
|
@@ -56,7 +57,8 @@ export const shortName = (str: string): string => {
|
|
|
56
57
|
export function posixPath(str: string): string {
|
|
57
58
|
const isExtendedLengthPath = /^\\\\\?\\/.test(str);
|
|
58
59
|
|
|
59
|
-
// Forward slashes are only valid Windows paths when they don't contain non-
|
|
60
|
+
// Forward slashes are only valid Windows paths when they don't contain non-
|
|
61
|
+
// ascii characters.
|
|
60
62
|
// eslint-disable-next-line no-control-regex
|
|
61
63
|
const hasNonAscii = /[^\u0000-\u0080]+/.test(str);
|
|
62
64
|
|
|
@@ -66,13 +68,18 @@ export function posixPath(str: string): string {
|
|
|
66
68
|
return str.replace(/\\/g, '/');
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
/**
|
|
72
|
+
* When you want to display a path in a message/warning/error, it's more
|
|
73
|
+
* convenient to:
|
|
74
|
+
*
|
|
75
|
+
* - make it relative to `cwd()`
|
|
76
|
+
* - convert to posix (ie not using windows \ path separator)
|
|
77
|
+
*
|
|
78
|
+
* This way, Jest tests can run more reliably on any computer/CI on both
|
|
79
|
+
* Unix/Windows
|
|
80
|
+
* For Windows users this is not perfect (as they see / instead of \) but it's
|
|
81
|
+
* probably good enough
|
|
82
|
+
*/
|
|
76
83
|
export function toMessageRelativeFilePath(filePath: string): string {
|
|
77
84
|
return posixPath(path.relative(process.cwd(), filePath));
|
|
78
85
|
}
|
|
@@ -92,7 +99,8 @@ export function aliasedSitePath(filePath: string, siteDir: string): string {
|
|
|
92
99
|
/**
|
|
93
100
|
* When you have a path like C:\X\Y
|
|
94
101
|
* It is not safe to use directly when generating code
|
|
95
|
-
* For example, this would fail due to unescaped \:
|
|
102
|
+
* For example, this would fail due to unescaped \:
|
|
103
|
+
* `<img src={require('${filePath}')} />`
|
|
96
104
|
* But this would work: `<img src={require('${escapePath(filePath)}')} />`
|
|
97
105
|
*
|
|
98
106
|
* posixPath can't be used in all cases, because forward slashes are only valid
|