@docusaurus/utils 2.0.0-beta.15d451942 → 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/README.md +1 -1
- package/lib/constants.d.ts +20 -0
- package/lib/constants.d.ts.map +1 -0
- package/lib/constants.js +27 -0
- package/lib/constants.js.map +1 -0
- package/lib/dataFileUtils.d.ts +24 -0
- package/lib/dataFileUtils.d.ts.map +1 -0
- package/lib/dataFileUtils.js +65 -0
- package/lib/dataFileUtils.js.map +1 -0
- 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/globUtils.d.ts +12 -0
- package/lib/globUtils.d.ts.map +1 -0
- package/lib/globUtils.js +48 -0
- package/lib/globUtils.js.map +1 -0
- package/lib/hashUtils.d.ts +16 -0
- package/lib/hashUtils.d.ts.map +1 -0
- package/lib/hashUtils.js +41 -0
- package/lib/hashUtils.js.map +1 -0
- package/lib/index.d.ts +30 -52
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +126 -247
- package/lib/index.js.map +1 -0
- package/lib/markdownLinks.d.ts +1 -0
- package/lib/markdownLinks.d.ts.map +1 -0
- package/lib/markdownLinks.js +36 -11
- package/lib/markdownLinks.js.map +1 -0
- package/lib/markdownParser.d.ts +7 -3
- package/lib/markdownParser.d.ts.map +1 -0
- package/lib/markdownParser.js +77 -48
- package/lib/markdownParser.js.map +1 -0
- package/lib/pathUtils.d.ts +51 -0
- package/lib/pathUtils.d.ts.map +1 -0
- package/lib/pathUtils.js +106 -0
- package/lib/pathUtils.js.map +1 -0
- package/lib/slugger.d.ts +14 -0
- package/lib/slugger.d.ts.map +1 -0
- package/lib/slugger.js +19 -0
- package/lib/slugger.js.map +1 -0
- package/lib/tags.d.ts +27 -0
- package/lib/tags.d.ts.map +1 -0
- package/lib/tags.js +77 -0
- package/lib/tags.js.map +1 -0
- package/lib/urlUtils.d.ts +9 -0
- package/lib/urlUtils.d.ts.map +1 -0
- package/lib/urlUtils.js +81 -0
- package/lib/urlUtils.js.map +1 -0
- package/lib/webpackUtils.d.ts +30 -0
- package/lib/webpackUtils.d.ts.map +1 -0
- package/lib/webpackUtils.js +112 -0
- package/lib/webpackUtils.js.map +1 -0
- package/package.json +20 -10
- package/src/constants.ts +38 -0
- package/src/dataFileUtils.ts +90 -0
- package/src/deps.d.ts +10 -0
- package/src/gitUtils.ts +93 -0
- package/src/globUtils.ts +64 -0
- package/src/hashUtils.ts +37 -0
- package/src/index.ts +135 -294
- package/src/markdownLinks.ts +35 -13
- package/src/markdownParser.ts +86 -62
- package/src/pathUtils.ts +115 -0
- package/src/slugger.ts +24 -0
- package/src/tags.ts +105 -0
- package/src/urlUtils.ts +96 -0
- package/src/webpackUtils.ts +146 -0
- package/lib/.tsbuildinfo +0 -3972
- package/lib/codeTranslationsUtils.d.ts +0 -11
- package/lib/codeTranslationsUtils.js +0 -50
- package/lib/escapePath.d.ts +0 -17
- package/lib/escapePath.js +0 -25
- package/lib/posixPath.d.ts +0 -14
- package/lib/posixPath.js +0 -28
- package/src/__tests__/__fixtures__/defaultCodeTranslations/en.json +0 -4
- package/src/__tests__/__fixtures__/defaultCodeTranslations/fr-FR.json +0 -5
- package/src/__tests__/__fixtures__/defaultCodeTranslations/fr.json +0 -4
- package/src/__tests__/__snapshots__/index.test.ts.snap +0 -8
- package/src/__tests__/codeTranslationsUtils.test.ts +0 -112
- package/src/__tests__/escapePath.test.ts +0 -25
- package/src/__tests__/index.test.ts +0 -681
- package/src/__tests__/markdownParser.test.ts +0 -772
- package/src/__tests__/posixPath.test.ts +0 -25
- package/src/codeTranslationsUtils.ts +0 -56
- package/src/escapePath.ts +0 -23
- package/src/posixPath.ts +0 -27
- package/tsconfig.json +0 -9
package/src/index.ts
CHANGED
|
@@ -5,32 +5,88 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import logger from '@docusaurus/logger';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import {createHash} from 'crypto';
|
|
11
|
-
import
|
|
12
|
-
import escapeStringRegexp from 'escape-string-regexp';
|
|
11
|
+
import _ from 'lodash';
|
|
13
12
|
import fs from 'fs-extra';
|
|
14
13
|
import {URL} from 'url';
|
|
15
|
-
import {
|
|
14
|
+
import type {
|
|
16
15
|
ReportingSeverity,
|
|
17
16
|
TranslationFileContent,
|
|
18
17
|
TranslationFile,
|
|
19
18
|
} from '@docusaurus/types';
|
|
20
19
|
|
|
21
|
-
// @ts-expect-error: no typedefs :s
|
|
22
20
|
import resolvePathnameUnsafe from 'resolve-pathname';
|
|
23
21
|
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
import {simpleHash, docuHash} from './hashUtils';
|
|
23
|
+
import {DEFAULT_PLUGIN_ID} from './constants';
|
|
24
|
+
|
|
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';
|
|
88
|
+
|
|
89
|
+
const fileHash = new Map<string, string>();
|
|
34
90
|
export async function generate(
|
|
35
91
|
generatedFilesDir: string,
|
|
36
92
|
file: string,
|
|
@@ -50,7 +106,7 @@ export async function generate(
|
|
|
50
106
|
// If file already exists but its not in runtime cache yet,
|
|
51
107
|
// we try to calculate the content hash and then compare
|
|
52
108
|
// This is to avoid unnecessary overwriting and we can reuse old file.
|
|
53
|
-
if (!lastHash && fs.
|
|
109
|
+
if (!lastHash && (await fs.pathExists(filepath))) {
|
|
54
110
|
const lastContent = await fs.readFile(filepath, 'utf8');
|
|
55
111
|
lastHash = createHash('md5').update(lastContent).digest('hex');
|
|
56
112
|
fileHash.set(filepath, lastHash);
|
|
@@ -65,20 +121,8 @@ export async function generate(
|
|
|
65
121
|
}
|
|
66
122
|
}
|
|
67
123
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
): Record<string, T> {
|
|
71
|
-
// https://github.com/lodash/lodash/issues/1459#issuecomment-460941233
|
|
72
|
-
return Object.keys(obj)
|
|
73
|
-
.sort()
|
|
74
|
-
.reduce((acc: Record<string, T>, key: string) => {
|
|
75
|
-
acc[key] = obj[key];
|
|
76
|
-
return acc;
|
|
77
|
-
}, {});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const indexRE = /(^|.*\/)index\.(md|mdx|js|jsx|ts|tsx)$/i;
|
|
81
|
-
const extRE = /\.(md|mdx|js|jsx|ts|tsx)$/;
|
|
124
|
+
const indexRE = /(?<dirname>^|.*\/)index\.(?:mdx?|jsx?|tsx?)$/i;
|
|
125
|
+
const extRE = /\.(?:mdx?|jsx?|tsx?)$/;
|
|
82
126
|
|
|
83
127
|
/**
|
|
84
128
|
* Convert filepath to url path.
|
|
@@ -91,60 +135,13 @@ export function fileToPath(file: string): string {
|
|
|
91
135
|
return `/${file.replace(extRE, '').replace(/\\/g, '/')}`;
|
|
92
136
|
}
|
|
93
137
|
|
|
94
|
-
export function encodePath(
|
|
95
|
-
return
|
|
138
|
+
export function encodePath(userPath: string): string {
|
|
139
|
+
return userPath
|
|
96
140
|
.split('/')
|
|
97
141
|
.map((item) => encodeURIComponent(item))
|
|
98
142
|
.join('/');
|
|
99
143
|
}
|
|
100
144
|
|
|
101
|
-
export function simpleHash(str: string, length: number): string {
|
|
102
|
-
return createHash('md5').update(str).digest('hex').substr(0, length);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Given an input string, convert to kebab-case and append a hash.
|
|
107
|
-
* Avoid str collision.
|
|
108
|
-
*/
|
|
109
|
-
export function docuHash(str: string): string {
|
|
110
|
-
if (str === '/') {
|
|
111
|
-
return 'index';
|
|
112
|
-
}
|
|
113
|
-
const shortHash = simpleHash(str, 3);
|
|
114
|
-
return `${kebabCase(str)}-${shortHash}`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Convert first string character to the upper case.
|
|
119
|
-
* E.g: docusaurus -> Docusaurus
|
|
120
|
-
*/
|
|
121
|
-
export function upperFirst(str: string): string {
|
|
122
|
-
return str ? str.charAt(0).toUpperCase() + str.slice(1) : '';
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Generate unique React Component Name.
|
|
127
|
-
* E.g: /foo-bar -> FooBar096
|
|
128
|
-
*/
|
|
129
|
-
export function genComponentName(pagePath: string): string {
|
|
130
|
-
if (pagePath === '/') {
|
|
131
|
-
return 'index';
|
|
132
|
-
}
|
|
133
|
-
const pageHash = docuHash(pagePath);
|
|
134
|
-
return upperFirst(camelCase(pageHash));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// When you want to display a path in a message/warning/error,
|
|
138
|
-
// it's more convenient to:
|
|
139
|
-
// - make it relative to cwd()
|
|
140
|
-
// - convert to posix (ie not using windows \ path separator)
|
|
141
|
-
// This way, Jest tests can run more reliably on any computer/CI
|
|
142
|
-
// on both Unix/Windows
|
|
143
|
-
// For Windows users this is not perfect (as they see / instead of \) but it's probably good enough
|
|
144
|
-
export function toMessageRelativeFilePath(filePath: string): string {
|
|
145
|
-
return posixPath(path.relative(process.cwd(), filePath));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
145
|
const chunkNameCache = new Map();
|
|
149
146
|
/**
|
|
150
147
|
* Generate unique chunk name given a module path.
|
|
@@ -173,126 +170,6 @@ export function genChunkName(
|
|
|
173
170
|
return chunkName;
|
|
174
171
|
}
|
|
175
172
|
|
|
176
|
-
// Too dynamic
|
|
177
|
-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
|
178
|
-
export function idx(target: any, keyPaths?: string | (string | number)[]): any {
|
|
179
|
-
return (
|
|
180
|
-
target &&
|
|
181
|
-
keyPaths &&
|
|
182
|
-
(Array.isArray(keyPaths)
|
|
183
|
-
? keyPaths.reduce((obj, key) => obj && obj[key], target)
|
|
184
|
-
: target[keyPaths])
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Given a filepath and dirpath, get the first directory.
|
|
190
|
-
*/
|
|
191
|
-
export function getSubFolder(file: string, refDir: string): string | null {
|
|
192
|
-
const separator = escapeStringRegexp(path.sep);
|
|
193
|
-
const baseDir = escapeStringRegexp(path.basename(refDir));
|
|
194
|
-
const regexSubFolder = new RegExp(
|
|
195
|
-
`${baseDir}${separator}(.*?)${separator}.*`,
|
|
196
|
-
);
|
|
197
|
-
const match = regexSubFolder.exec(file);
|
|
198
|
-
return match && match[1];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export function normalizeUrl(rawUrls: string[]): string {
|
|
202
|
-
const urls = [...rawUrls];
|
|
203
|
-
const resultArray = [];
|
|
204
|
-
|
|
205
|
-
let hasStartingSlash = false;
|
|
206
|
-
let hasEndingSlash = false;
|
|
207
|
-
|
|
208
|
-
// If the first part is a plain protocol, we combine it with the next part.
|
|
209
|
-
if (urls[0].match(/^[^/:]+:\/*$/) && urls.length > 1) {
|
|
210
|
-
const first = urls.shift();
|
|
211
|
-
urls[0] = first + urls[0];
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// There must be two or three slashes in the file protocol,
|
|
215
|
-
// two slashes in anything else.
|
|
216
|
-
const replacement = urls[0].match(/^file:\/\/\//) ? '$1:///' : '$1://';
|
|
217
|
-
urls[0] = urls[0].replace(/^([^/:]+):\/*/, replacement);
|
|
218
|
-
|
|
219
|
-
// eslint-disable-next-line
|
|
220
|
-
for (let i = 0; i < urls.length; i++) {
|
|
221
|
-
let component = urls[i];
|
|
222
|
-
|
|
223
|
-
if (typeof component !== 'string') {
|
|
224
|
-
throw new TypeError(`Url must be a string. Received ${typeof component}`);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (component === '') {
|
|
228
|
-
if (i === urls.length - 1 && hasEndingSlash) {
|
|
229
|
-
resultArray.push('/');
|
|
230
|
-
}
|
|
231
|
-
// eslint-disable-next-line
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (component !== '/') {
|
|
236
|
-
if (i > 0) {
|
|
237
|
-
// Removing the starting slashes for each component but the first.
|
|
238
|
-
component = component.replace(
|
|
239
|
-
/^[/]+/,
|
|
240
|
-
// Special case where the first element of rawUrls is empty ["", "/hello"] => /hello
|
|
241
|
-
component[0] === '/' && !hasStartingSlash ? '/' : '',
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
hasEndingSlash = component[component.length - 1] === '/';
|
|
246
|
-
// Removing the ending slashes for each component but the last.
|
|
247
|
-
// For the last component we will combine multiple slashes to a single one.
|
|
248
|
-
component = component.replace(/[/]+$/, i < urls.length - 1 ? '' : '/');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
hasStartingSlash = true;
|
|
252
|
-
resultArray.push(component);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
let str = resultArray.join('/');
|
|
256
|
-
// Each input component is now separated by a single slash
|
|
257
|
-
// except the possible first plain protocol part.
|
|
258
|
-
|
|
259
|
-
// Remove trailing slash before parameters or hash.
|
|
260
|
-
str = str.replace(/\/(\?|&|#[^!])/g, '$1');
|
|
261
|
-
|
|
262
|
-
// Replace ? in parameters with &.
|
|
263
|
-
const parts = str.split('?');
|
|
264
|
-
str = parts.shift() + (parts.length > 0 ? '?' : '') + parts.join('&');
|
|
265
|
-
|
|
266
|
-
// Dedupe forward slashes in the entire path, avoiding protocol slashes.
|
|
267
|
-
str = str.replace(/([^:]\/)\/+/g, '$1');
|
|
268
|
-
|
|
269
|
-
// Dedupe forward slashes at the beginning of the path.
|
|
270
|
-
str = str.replace(/^\/+/g, '/');
|
|
271
|
-
|
|
272
|
-
return str;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Alias filepath relative to site directory, very useful so that we
|
|
277
|
-
* don't expose user's site structure.
|
|
278
|
-
* Example: some/path/to/website/docs/foo.md -> @site/docs/foo.md
|
|
279
|
-
*/
|
|
280
|
-
export function aliasedSitePath(filePath: string, siteDir: string): string {
|
|
281
|
-
const relativePath = posixPath(path.relative(siteDir, filePath));
|
|
282
|
-
// Cannot use path.join() as it resolves '../' and removes
|
|
283
|
-
// the '@site'. Let webpack loader resolve it.
|
|
284
|
-
return `@site/${relativePath}`;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export function getEditUrl(
|
|
288
|
-
fileRelativePath: string,
|
|
289
|
-
editUrl?: string,
|
|
290
|
-
): string | undefined {
|
|
291
|
-
return editUrl
|
|
292
|
-
? normalizeUrl([editUrl, posixPath(fileRelativePath)])
|
|
293
|
-
: undefined;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
173
|
export function isValidPathname(str: string): boolean {
|
|
297
174
|
if (!str.startsWith('/')) {
|
|
298
175
|
return false;
|
|
@@ -301,7 +178,7 @@ export function isValidPathname(str: string): boolean {
|
|
|
301
178
|
// weird, but is there a better way?
|
|
302
179
|
const parsedPathname = new URL(str, 'https://domain.com').pathname;
|
|
303
180
|
return parsedPathname === str || parsedPathname === encodeURI(str);
|
|
304
|
-
} catch
|
|
181
|
+
} catch {
|
|
305
182
|
return false;
|
|
306
183
|
}
|
|
307
184
|
}
|
|
@@ -313,13 +190,18 @@ export function resolvePathname(to: string, from?: string): string {
|
|
|
313
190
|
export function addLeadingSlash(str: string): string {
|
|
314
191
|
return str.startsWith('/') ? str : `/${str}`;
|
|
315
192
|
}
|
|
316
|
-
|
|
317
|
-
return str.endsWith('/') ? str : `${str}/`;
|
|
318
|
-
}
|
|
193
|
+
|
|
319
194
|
export function addTrailingPathSeparator(str: string): string {
|
|
320
|
-
return str.endsWith(path.sep)
|
|
195
|
+
return str.endsWith(path.sep)
|
|
196
|
+
? str
|
|
197
|
+
: // If this is Windows, we need to change the forward slash to backward
|
|
198
|
+
`${str.replace(/\/$/, '')}${path.sep}`;
|
|
321
199
|
}
|
|
322
200
|
|
|
201
|
+
// TODO deduplicate: also present in @docusaurus/utils-common
|
|
202
|
+
export function addTrailingSlash(str: string): string {
|
|
203
|
+
return str.endsWith('/') ? str : `${str}/`;
|
|
204
|
+
}
|
|
323
205
|
export function removeTrailingSlash(str: string): string {
|
|
324
206
|
return removeSuffix(str, '/');
|
|
325
207
|
}
|
|
@@ -335,13 +217,7 @@ export function removePrefix(str: string, prefix: string): string {
|
|
|
335
217
|
return str.startsWith(prefix) ? str.slice(prefix.length) : str;
|
|
336
218
|
}
|
|
337
219
|
|
|
338
|
-
export function
|
|
339
|
-
const fileName = path.basename(routePath);
|
|
340
|
-
const filePath = path.dirname(routePath);
|
|
341
|
-
return path.join(filePath, `${fileName}/index.html`);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
export function getElementsAround<T extends unknown>(
|
|
220
|
+
export function getElementsAround<T>(
|
|
345
221
|
array: T[],
|
|
346
222
|
aroundIndex: number,
|
|
347
223
|
): {
|
|
@@ -352,7 +228,7 @@ export function getElementsAround<T extends unknown>(
|
|
|
352
228
|
const max = array.length - 1;
|
|
353
229
|
if (aroundIndex < min || aroundIndex > max) {
|
|
354
230
|
throw new Error(
|
|
355
|
-
`Valid aroundIndex for array (of size ${array.length}) are between ${min} and ${max}, but you provided
|
|
231
|
+
`Valid "aroundIndex" for array (of size ${array.length}) are between ${min} and ${max}, but you provided ${aroundIndex}.`,
|
|
356
232
|
);
|
|
357
233
|
}
|
|
358
234
|
const previous = aroundIndex === min ? undefined : array[aroundIndex - 1];
|
|
@@ -364,7 +240,7 @@ export function getPluginI18nPath({
|
|
|
364
240
|
siteDir,
|
|
365
241
|
locale,
|
|
366
242
|
pluginName,
|
|
367
|
-
pluginId =
|
|
243
|
+
pluginId = DEFAULT_PLUGIN_ID,
|
|
368
244
|
subPaths = [],
|
|
369
245
|
}: {
|
|
370
246
|
siteDir: string;
|
|
@@ -376,26 +252,58 @@ export function getPluginI18nPath({
|
|
|
376
252
|
return path.join(
|
|
377
253
|
siteDir,
|
|
378
254
|
'i18n',
|
|
379
|
-
// 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
|
|
380
257
|
locale,
|
|
381
258
|
// Make it convenient to use for single-instance
|
|
382
259
|
// ie: return "docs", not "docs-default" nor "docs/default"
|
|
383
|
-
`${pluginName}${
|
|
384
|
-
// TODO duplicate constant :(
|
|
385
|
-
pluginId === 'default' ? '' : `-${pluginId}`
|
|
386
|
-
}`,
|
|
260
|
+
`${pluginName}${pluginId === DEFAULT_PLUGIN_ID ? '' : `-${pluginId}`}`,
|
|
387
261
|
...subPaths,
|
|
388
262
|
);
|
|
389
263
|
}
|
|
390
264
|
|
|
391
|
-
|
|
265
|
+
/**
|
|
266
|
+
* @param permalink The URL that the HTML file corresponds to, without base URL
|
|
267
|
+
* @param outDir Full path to the output directory
|
|
268
|
+
* @param trailingSlash The site config option. If provided, only one path will
|
|
269
|
+
* be read.
|
|
270
|
+
* @returns This returns a buffer, which you have to decode string yourself if
|
|
271
|
+
* needed. (Not always necessary since the output isn't for human consumption
|
|
272
|
+
* anyways, and most HTML manipulation libs accept buffers)
|
|
273
|
+
*/
|
|
274
|
+
export async function readOutputHTMLFile(
|
|
275
|
+
permalink: string,
|
|
276
|
+
outDir: string,
|
|
277
|
+
trailingSlash: boolean | undefined,
|
|
278
|
+
): Promise<Buffer> {
|
|
279
|
+
const withTrailingSlashPath = path.join(outDir, permalink, 'index.html');
|
|
280
|
+
const withoutTrailingSlashPath = path.join(
|
|
281
|
+
outDir,
|
|
282
|
+
`${permalink.replace(/\/$/, '')}.html`,
|
|
283
|
+
);
|
|
284
|
+
if (trailingSlash) {
|
|
285
|
+
return fs.readFile(withTrailingSlashPath);
|
|
286
|
+
} else if (trailingSlash === false) {
|
|
287
|
+
return fs.readFile(withoutTrailingSlashPath);
|
|
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}`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return fs.readFile(HTMLPath);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export async function mapAsyncSequential<T, R>(
|
|
392
302
|
array: T[],
|
|
393
303
|
action: (t: T) => Promise<R>,
|
|
394
304
|
): Promise<R[]> {
|
|
395
305
|
const results: R[] = [];
|
|
396
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
397
306
|
for (const t of array) {
|
|
398
|
-
// eslint-disable-next-line no-await-in-loop
|
|
399
307
|
const result = await action(t);
|
|
400
308
|
results.push(result);
|
|
401
309
|
}
|
|
@@ -406,9 +314,7 @@ export async function findAsyncSequential<T>(
|
|
|
406
314
|
array: T[],
|
|
407
315
|
predicate: (t: T) => Promise<boolean>,
|
|
408
316
|
): Promise<T | undefined> {
|
|
409
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
410
317
|
for (const t of array) {
|
|
411
|
-
// eslint-disable-next-line no-await-in-loop
|
|
412
318
|
if (await predicate(t)) {
|
|
413
319
|
return t;
|
|
414
320
|
}
|
|
@@ -416,35 +322,6 @@ export async function findAsyncSequential<T>(
|
|
|
416
322
|
return undefined;
|
|
417
323
|
}
|
|
418
324
|
|
|
419
|
-
// return the first folder path in which the file exists in
|
|
420
|
-
export async function findFolderContainingFile(
|
|
421
|
-
folderPaths: string[],
|
|
422
|
-
relativeFilePath: string,
|
|
423
|
-
): Promise<string | undefined> {
|
|
424
|
-
return findAsyncSequential(folderPaths, (folderPath) =>
|
|
425
|
-
fs.pathExists(path.join(folderPath, relativeFilePath)),
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
export async function getFolderContainingFile(
|
|
430
|
-
folderPaths: string[],
|
|
431
|
-
relativeFilePath: string,
|
|
432
|
-
): Promise<string> {
|
|
433
|
-
const maybeFolderPath = await findFolderContainingFile(
|
|
434
|
-
folderPaths,
|
|
435
|
-
relativeFilePath,
|
|
436
|
-
);
|
|
437
|
-
// should never happen, as the source was read from the FS anyway...
|
|
438
|
-
if (!maybeFolderPath) {
|
|
439
|
-
throw new Error(
|
|
440
|
-
`relativeFilePath=[${relativeFilePath}] does not exist in any of these folders: \n- ${folderPaths.join(
|
|
441
|
-
'\n- ',
|
|
442
|
-
)}]`,
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
return maybeFolderPath;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
325
|
export function reportMessage(
|
|
449
326
|
message: string,
|
|
450
327
|
reportingSeverity: ReportingSeverity,
|
|
@@ -453,19 +330,19 @@ export function reportMessage(
|
|
|
453
330
|
case 'ignore':
|
|
454
331
|
break;
|
|
455
332
|
case 'log':
|
|
456
|
-
|
|
333
|
+
logger.info(message);
|
|
457
334
|
break;
|
|
458
335
|
case 'warn':
|
|
459
|
-
|
|
336
|
+
logger.warn(message);
|
|
460
337
|
break;
|
|
461
338
|
case 'error':
|
|
462
|
-
|
|
339
|
+
logger.error(message);
|
|
463
340
|
break;
|
|
464
341
|
case 'throw':
|
|
465
342
|
throw new Error(message);
|
|
466
343
|
default:
|
|
467
344
|
throw new Error(
|
|
468
|
-
`
|
|
345
|
+
`Unexpected "reportingSeverity" value: ${reportingSeverity}.`,
|
|
469
346
|
);
|
|
470
347
|
}
|
|
471
348
|
}
|
|
@@ -473,23 +350,7 @@ export function reportMessage(
|
|
|
473
350
|
export function mergeTranslations(
|
|
474
351
|
contents: TranslationFileContent[],
|
|
475
352
|
): TranslationFileContent {
|
|
476
|
-
return contents.reduce((acc, content) => {
|
|
477
|
-
return {...acc, ...content};
|
|
478
|
-
}, {});
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
export function getSwizzledComponent(
|
|
482
|
-
componentPath: string,
|
|
483
|
-
): string | undefined {
|
|
484
|
-
const swizzledComponentPath = path.resolve(
|
|
485
|
-
process.cwd(),
|
|
486
|
-
'src',
|
|
487
|
-
componentPath,
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
return fs.existsSync(swizzledComponentPath)
|
|
491
|
-
? swizzledComponentPath
|
|
492
|
-
: undefined;
|
|
353
|
+
return contents.reduce((acc, content) => ({...acc, ...content}), {});
|
|
493
354
|
}
|
|
494
355
|
|
|
495
356
|
// Useful to update all the messages of a translation file
|
|
@@ -500,29 +361,9 @@ export function updateTranslationFileMessages(
|
|
|
500
361
|
): TranslationFile {
|
|
501
362
|
return {
|
|
502
363
|
...translationFile,
|
|
503
|
-
content: mapValues(translationFile.content, (translation) => ({
|
|
364
|
+
content: _.mapValues(translationFile.content, (translation) => ({
|
|
504
365
|
...translation,
|
|
505
366
|
message: updateMessage(translation.message),
|
|
506
367
|
})),
|
|
507
368
|
};
|
|
508
369
|
}
|
|
509
|
-
|
|
510
|
-
// Input: ## Some heading {#some-heading}
|
|
511
|
-
// Output: {text: "## Some heading", id: "some-heading"}
|
|
512
|
-
export function parseMarkdownHeadingId(
|
|
513
|
-
heading: string,
|
|
514
|
-
): {
|
|
515
|
-
text: string;
|
|
516
|
-
id?: string;
|
|
517
|
-
} {
|
|
518
|
-
const customHeadingIdRegex = /^(.*?)\s*\{#([\w-]+)\}$/;
|
|
519
|
-
const matches = customHeadingIdRegex.exec(heading);
|
|
520
|
-
if (matches) {
|
|
521
|
-
return {
|
|
522
|
-
text: matches[1],
|
|
523
|
-
id: matches[2],
|
|
524
|
-
};
|
|
525
|
-
} else {
|
|
526
|
-
return {text: heading, id: undefined};
|
|
527
|
-
}
|
|
528
|
-
}
|
package/src/markdownLinks.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import {aliasedSitePath} from './
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import {aliasedSitePath} from './pathUtils';
|
|
10
10
|
|
|
11
11
|
export type ContentPaths = {
|
|
12
12
|
contentPath: string;
|
|
@@ -45,9 +45,17 @@ export function replaceMarkdownLinks<T extends ContentPaths>({
|
|
|
45
45
|
|
|
46
46
|
// Replace internal markdown linking (except in fenced blocks).
|
|
47
47
|
let fencedBlock = false;
|
|
48
|
+
let lastCodeFence = '';
|
|
48
49
|
const lines = fileString.split('\n').map((line) => {
|
|
49
50
|
if (line.trim().startsWith('```')) {
|
|
50
|
-
|
|
51
|
+
if (!fencedBlock) {
|
|
52
|
+
fencedBlock = true;
|
|
53
|
+
[lastCodeFence] = line.trim().match(/^`+/)!;
|
|
54
|
+
// If we are in a ````-fenced block, all ``` would be plain text instead
|
|
55
|
+
// of fences
|
|
56
|
+
} else if (line.trim().match(/^`+/)![0].length >= lastCodeFence.length) {
|
|
57
|
+
fencedBlock = false;
|
|
58
|
+
}
|
|
51
59
|
}
|
|
52
60
|
if (fencedBlock) {
|
|
53
61
|
return line;
|
|
@@ -55,24 +63,38 @@ export function replaceMarkdownLinks<T extends ContentPaths>({
|
|
|
55
63
|
|
|
56
64
|
let modifiedLine = line;
|
|
57
65
|
// Replace inline-style links or reference-style links e.g:
|
|
58
|
-
// 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
|
|
59
68
|
// [doc1]: doc1.md -> we replace this doc1.md with correct link
|
|
60
|
-
const mdRegex =
|
|
69
|
+
const mdRegex =
|
|
70
|
+
/(?:(?:\]\()|(?:\]:\s*))(?!https?:\/\/|@site\/)(?<filename>[^'")\]\s>]+\.mdx?)/g;
|
|
61
71
|
let mdMatch = mdRegex.exec(modifiedLine);
|
|
62
72
|
while (mdMatch !== null) {
|
|
63
73
|
// Replace it to correct html link.
|
|
64
|
-
const mdLink = mdMatch
|
|
74
|
+
const mdLink = mdMatch.groups!.filename;
|
|
75
|
+
|
|
76
|
+
const sourcesToTry = [
|
|
77
|
+
path.resolve(path.dirname(filePath), decodeURIComponent(mdLink)),
|
|
78
|
+
`${contentPathLocalized}/${decodeURIComponent(mdLink)}`,
|
|
79
|
+
`${contentPath}/${decodeURIComponent(mdLink)}`,
|
|
80
|
+
];
|
|
65
81
|
|
|
66
|
-
const
|
|
67
|
-
aliasedSitePath(source, siteDir)
|
|
82
|
+
const aliasedSourceMatch = sourcesToTry
|
|
83
|
+
.map((source) => aliasedSitePath(source, siteDir))
|
|
84
|
+
.find((source) => sourceToPermalink[source]);
|
|
68
85
|
|
|
69
|
-
const permalink: string | undefined =
|
|
70
|
-
sourceToPermalink[
|
|
71
|
-
|
|
72
|
-
sourceToPermalink[aliasedSource(`${contentPath}/${mdLink}`)];
|
|
86
|
+
const permalink: string | undefined = aliasedSourceMatch
|
|
87
|
+
? sourceToPermalink[aliasedSourceMatch]
|
|
88
|
+
: undefined;
|
|
73
89
|
|
|
74
90
|
if (permalink) {
|
|
75
|
-
|
|
91
|
+
// MDX won't be happy if the permalink contains a space, we need to
|
|
92
|
+
// convert it to %20
|
|
93
|
+
const encodedPermalink = permalink
|
|
94
|
+
.split('/')
|
|
95
|
+
.map((part) => part.replace(/\s/g, '%20'))
|
|
96
|
+
.join('/');
|
|
97
|
+
modifiedLine = modifiedLine.replace(mdLink, encodedPermalink);
|
|
76
98
|
} else {
|
|
77
99
|
const brokenMarkdownLink: BrokenMarkdownLink<T> = {
|
|
78
100
|
contentPaths,
|