@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.
Files changed (88) hide show
  1. package/README.md +1 -1
  2. package/lib/constants.d.ts +20 -0
  3. package/lib/constants.d.ts.map +1 -0
  4. package/lib/constants.js +27 -0
  5. package/lib/constants.js.map +1 -0
  6. package/lib/dataFileUtils.d.ts +24 -0
  7. package/lib/dataFileUtils.d.ts.map +1 -0
  8. package/lib/dataFileUtils.js +65 -0
  9. package/lib/dataFileUtils.js.map +1 -0
  10. package/lib/gitUtils.d.ts +17 -0
  11. package/lib/gitUtils.d.ts.map +1 -0
  12. package/lib/gitUtils.js +63 -0
  13. package/lib/gitUtils.js.map +1 -0
  14. package/lib/globUtils.d.ts +12 -0
  15. package/lib/globUtils.d.ts.map +1 -0
  16. package/lib/globUtils.js +48 -0
  17. package/lib/globUtils.js.map +1 -0
  18. package/lib/hashUtils.d.ts +16 -0
  19. package/lib/hashUtils.d.ts.map +1 -0
  20. package/lib/hashUtils.js +41 -0
  21. package/lib/hashUtils.js.map +1 -0
  22. package/lib/index.d.ts +30 -52
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +126 -247
  25. package/lib/index.js.map +1 -0
  26. package/lib/markdownLinks.d.ts +1 -0
  27. package/lib/markdownLinks.d.ts.map +1 -0
  28. package/lib/markdownLinks.js +36 -11
  29. package/lib/markdownLinks.js.map +1 -0
  30. package/lib/markdownParser.d.ts +7 -3
  31. package/lib/markdownParser.d.ts.map +1 -0
  32. package/lib/markdownParser.js +77 -48
  33. package/lib/markdownParser.js.map +1 -0
  34. package/lib/pathUtils.d.ts +51 -0
  35. package/lib/pathUtils.d.ts.map +1 -0
  36. package/lib/pathUtils.js +106 -0
  37. package/lib/pathUtils.js.map +1 -0
  38. package/lib/slugger.d.ts +14 -0
  39. package/lib/slugger.d.ts.map +1 -0
  40. package/lib/slugger.js +19 -0
  41. package/lib/slugger.js.map +1 -0
  42. package/lib/tags.d.ts +27 -0
  43. package/lib/tags.d.ts.map +1 -0
  44. package/lib/tags.js +77 -0
  45. package/lib/tags.js.map +1 -0
  46. package/lib/urlUtils.d.ts +9 -0
  47. package/lib/urlUtils.d.ts.map +1 -0
  48. package/lib/urlUtils.js +81 -0
  49. package/lib/urlUtils.js.map +1 -0
  50. package/lib/webpackUtils.d.ts +30 -0
  51. package/lib/webpackUtils.d.ts.map +1 -0
  52. package/lib/webpackUtils.js +112 -0
  53. package/lib/webpackUtils.js.map +1 -0
  54. package/package.json +20 -10
  55. package/src/constants.ts +38 -0
  56. package/src/dataFileUtils.ts +90 -0
  57. package/src/deps.d.ts +10 -0
  58. package/src/gitUtils.ts +93 -0
  59. package/src/globUtils.ts +64 -0
  60. package/src/hashUtils.ts +37 -0
  61. package/src/index.ts +135 -294
  62. package/src/markdownLinks.ts +35 -13
  63. package/src/markdownParser.ts +86 -62
  64. package/src/pathUtils.ts +115 -0
  65. package/src/slugger.ts +24 -0
  66. package/src/tags.ts +105 -0
  67. package/src/urlUtils.ts +96 -0
  68. package/src/webpackUtils.ts +146 -0
  69. package/lib/.tsbuildinfo +0 -3972
  70. package/lib/codeTranslationsUtils.d.ts +0 -11
  71. package/lib/codeTranslationsUtils.js +0 -50
  72. package/lib/escapePath.d.ts +0 -17
  73. package/lib/escapePath.js +0 -25
  74. package/lib/posixPath.d.ts +0 -14
  75. package/lib/posixPath.js +0 -28
  76. package/src/__tests__/__fixtures__/defaultCodeTranslations/en.json +0 -4
  77. package/src/__tests__/__fixtures__/defaultCodeTranslations/fr-FR.json +0 -5
  78. package/src/__tests__/__fixtures__/defaultCodeTranslations/fr.json +0 -4
  79. package/src/__tests__/__snapshots__/index.test.ts.snap +0 -8
  80. package/src/__tests__/codeTranslationsUtils.test.ts +0 -112
  81. package/src/__tests__/escapePath.test.ts +0 -25
  82. package/src/__tests__/index.test.ts +0 -681
  83. package/src/__tests__/markdownParser.test.ts +0 -772
  84. package/src/__tests__/posixPath.test.ts +0 -25
  85. package/src/codeTranslationsUtils.ts +0 -56
  86. package/src/escapePath.ts +0 -23
  87. package/src/posixPath.ts +0 -27
  88. 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 chalk from 'chalk';
8
+ import logger from '@docusaurus/logger';
9
9
  import path from 'path';
10
10
  import {createHash} from 'crypto';
11
- import {camelCase, kebabCase, mapValues} from 'lodash';
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 {posixPath as posixPathImport} from './posixPath';
25
-
26
- export const posixPath = posixPathImport;
27
-
28
- export * from './codeTranslationsUtils';
29
- export * from './markdownParser';
30
- export * from './markdownLinks';
31
- export * from './escapePath';
32
-
33
- const fileHash = new Map();
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.existsSync(filepath)) {
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
- export function objectWithKeySorted<T>(
69
- obj: Record<string, T>,
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(userpath: string): string {
95
- return userpath
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 (e) {
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
- export function addTrailingSlash(str: string): string {
317
- return str.endsWith('/') ? str : `${str}/`;
318
- }
193
+
319
194
  export function addTrailingPathSeparator(str: string): string {
320
- return str.endsWith(path.sep) ? str : `${str}${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 getFilePathForRoutePath(routePath: string): string {
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 aroundIndex=${aroundIndex}`,
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 = 'default', // TODO duplicated constant
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 translator
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
- export async function mapAsyncSequencial<T extends unknown, R extends unknown>(
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
- console.log(chalk.bold.blue('info ') + chalk.blue(message));
333
+ logger.info(message);
457
334
  break;
458
335
  case 'warn':
459
- console.warn(chalk.bold.yellow('warn ') + chalk.yellow(message));
336
+ logger.warn(message);
460
337
  break;
461
338
  case 'error':
462
- console.error(chalk.bold.red('error ') + chalk.red(message));
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
- `unexpected reportingSeverity value: ${reportingSeverity}`,
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
- }
@@ -5,8 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import {resolve} from 'url';
9
- import {aliasedSitePath} from './index';
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
- fencedBlock = !fencedBlock;
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 link
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 = /(?:(?:\]\()|(?:\]:\s?))(?!https)([^'")\]\s>]+\.mdx?)/g;
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[1];
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 aliasedSource = (source: string) =>
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[aliasedSource(resolve(filePath, mdLink))] ||
71
- sourceToPermalink[aliasedSource(`${contentPathLocalized}/${mdLink}`)] ||
72
- sourceToPermalink[aliasedSource(`${contentPath}/${mdLink}`)];
86
+ const permalink: string | undefined = aliasedSourceMatch
87
+ ? sourceToPermalink[aliasedSourceMatch]
88
+ : undefined;
73
89
 
74
90
  if (permalink) {
75
- modifiedLine = modifiedLine.replace(mdLink, permalink);
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,