@docusaurus/utils 3.9.2 → 3.10.1

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 (74) hide show
  1. package/lib/index.d.ts +5 -4
  2. package/lib/index.d.ts.map +1 -1
  3. package/lib/index.js +13 -11
  4. package/lib/index.js.map +1 -1
  5. package/lib/lastUpdateUtils.d.ts +2 -6
  6. package/lib/lastUpdateUtils.d.ts.map +1 -1
  7. package/lib/lastUpdateUtils.js +13 -60
  8. package/lib/lastUpdateUtils.js.map +1 -1
  9. package/lib/markdownHeadingIdUtils.d.ts +49 -0
  10. package/lib/markdownHeadingIdUtils.d.ts.map +1 -0
  11. package/lib/markdownHeadingIdUtils.js +148 -0
  12. package/lib/markdownHeadingIdUtils.js.map +1 -0
  13. package/lib/markdownUtils.d.ts +0 -31
  14. package/lib/markdownUtils.d.ts.map +1 -1
  15. package/lib/markdownUtils.js +0 -89
  16. package/lib/markdownUtils.js.map +1 -1
  17. package/lib/moduleUtils.d.ts.map +1 -1
  18. package/lib/moduleUtils.js +4 -4
  19. package/lib/moduleUtils.js.map +1 -1
  20. package/lib/{gitUtils.d.ts → vcs/gitUtils.d.ts} +18 -0
  21. package/lib/vcs/gitUtils.d.ts.map +1 -0
  22. package/lib/vcs/gitUtils.js +343 -0
  23. package/lib/vcs/gitUtils.js.map +1 -0
  24. package/lib/vcs/vcs.d.ts +19 -0
  25. package/lib/vcs/vcs.d.ts.map +1 -0
  26. package/lib/vcs/vcs.js +46 -0
  27. package/lib/vcs/vcs.js.map +1 -0
  28. package/lib/vcs/vcsDefaultV1.d.ts +13 -0
  29. package/lib/vcs/vcsDefaultV1.d.ts.map +1 -0
  30. package/lib/vcs/vcsDefaultV1.js +33 -0
  31. package/lib/vcs/vcsDefaultV1.js.map +1 -0
  32. package/lib/vcs/vcsDefaultV2.d.ts +13 -0
  33. package/lib/vcs/vcsDefaultV2.d.ts.map +1 -0
  34. package/lib/vcs/vcsDefaultV2.js +33 -0
  35. package/lib/vcs/vcsDefaultV2.js.map +1 -0
  36. package/lib/vcs/vcsDisabled.d.ts +12 -0
  37. package/lib/vcs/vcsDisabled.d.ts.map +1 -0
  38. package/lib/vcs/vcsDisabled.js +24 -0
  39. package/lib/vcs/vcsDisabled.js.map +1 -0
  40. package/lib/vcs/vcsGitAdHoc.d.ts +16 -0
  41. package/lib/vcs/vcsGitAdHoc.d.ts.map +1 -0
  42. package/lib/vcs/vcsGitAdHoc.js +29 -0
  43. package/lib/vcs/vcsGitAdHoc.js.map +1 -0
  44. package/lib/vcs/vcsGitEager.d.ts +10 -0
  45. package/lib/vcs/vcsGitEager.d.ts.map +1 -0
  46. package/lib/vcs/vcsGitEager.js +89 -0
  47. package/lib/vcs/vcsGitEager.js.map +1 -0
  48. package/lib/vcs/vcsHardcoded.d.ts +17 -0
  49. package/lib/vcs/vcsHardcoded.d.ts.map +1 -0
  50. package/lib/vcs/vcsHardcoded.js +41 -0
  51. package/lib/vcs/vcsHardcoded.js.map +1 -0
  52. package/package.json +7 -7
  53. package/src/index.ts +11 -8
  54. package/src/lastUpdateUtils.ts +18 -76
  55. package/src/markdownHeadingIdUtils.ts +209 -0
  56. package/src/markdownUtils.ts +0 -119
  57. package/src/moduleUtils.ts +6 -8
  58. package/src/vcs/gitUtils.ts +541 -0
  59. package/src/vcs/vcs.ts +54 -0
  60. package/src/vcs/vcsDefaultV1.ts +33 -0
  61. package/src/vcs/vcsDefaultV2.ts +33 -0
  62. package/src/vcs/vcsDisabled.ts +25 -0
  63. package/src/vcs/vcsGitAdHoc.ts +30 -0
  64. package/src/vcs/vcsGitEager.ts +135 -0
  65. package/src/vcs/vcsHardcoded.ts +45 -0
  66. package/lib/cliUtils.d.ts +0 -14
  67. package/lib/cliUtils.d.ts.map +0 -1
  68. package/lib/cliUtils.js +0 -49
  69. package/lib/cliUtils.js.map +0 -1
  70. package/lib/gitUtils.d.ts.map +0 -1
  71. package/lib/gitUtils.js +0 -103
  72. package/lib/gitUtils.js.map +0 -1
  73. package/src/cliUtils.ts +0 -65
  74. package/src/gitUtils.ts +0 -200
@@ -0,0 +1,209 @@
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 {createSlugger, type Slugger, type SluggerOptions} from './slugger';
9
+
10
+ /**
11
+ * The syntax to use for heading IDs.
12
+ * - `classic` => `{#id}` (invalid MDX, but commonly supported)
13
+ * - `mdx-comment` => `{/* #id * /}` (valid MDX)
14
+ */
15
+ export type HeadingIdSyntax = 'classic' | 'mdx-comment';
16
+
17
+ /**
18
+ * Parses custom ID from a heading. The ID can contain any characters except
19
+ * `{#` and `}`.
20
+ *
21
+ * @param heading e.g. `## Some heading {#some-heading}` where the last
22
+ * character must be `}` for the ID to be recognized
23
+ * @param syntax which heading ID syntax to recognize
24
+ */
25
+ export function parseMarkdownHeadingId(
26
+ heading: string,
27
+ syntax: HeadingIdSyntax = 'classic',
28
+ ): {
29
+ /**
30
+ * The heading content sans the ID part, right-trimmed. e.g. `## Some heading`
31
+ */
32
+ text: string;
33
+ /** The heading ID. e.g. `some-heading` */
34
+ id: string | undefined;
35
+ } {
36
+ // Classic syntax: {#my-id}
37
+ if (syntax === 'classic') {
38
+ const customHeadingIdRegex = /\s*\{#(?<id>(?:.(?!\{#|\}))*.)\}$/;
39
+ const matches = customHeadingIdRegex.exec(heading);
40
+ if (matches) {
41
+ return {
42
+ text: heading.replace(matches[0]!, ''),
43
+ id: matches.groups!.id!.trim(),
44
+ };
45
+ }
46
+ }
47
+ // MDX comment syntax: {/* #my-id */}
48
+ // Note: this is only used for the "write-heading-ids" CLI
49
+ // The mdx loader is using a real MDX parser to find these comments
50
+ else if (syntax === 'mdx-comment') {
51
+ const mdxCommentHeadingIdRegex = /\s*\{\/\*\s*#(?<id>\S+)\s*\*\/\}$/;
52
+ const mdxMatches = mdxCommentHeadingIdRegex.exec(heading);
53
+ if (mdxMatches) {
54
+ return {
55
+ text: heading.replace(mdxMatches[0]!, ''),
56
+ id: mdxMatches.groups!.id!.trim(),
57
+ };
58
+ }
59
+ }
60
+ // Unhandled cases, shouldn't happen
61
+ else {
62
+ throw new Error(`unknown heading id syntax '${syntax}'`);
63
+ }
64
+ return {text: heading, id: undefined};
65
+ }
66
+
67
+ /**
68
+ * For our classic syntax, MDX v2+ now requires escaping { to compile: \{#id}.
69
+ * See https://mdxjs.com/docs/troubleshooting-mdx/#could-not-parse-expression-with-acorn-error
70
+ */
71
+ export function escapeMarkdownHeadingIds(content: string): string {
72
+ const markdownHeadingRegexp = /(?:^|\n)#{1,6}(?!#).*/g;
73
+ return content.replaceAll(markdownHeadingRegexp, (substring) =>
74
+ // TODO probably not the most efficient impl...
75
+ substring
76
+ .replace('{#', '\\{#')
77
+ // prevent duplicate escaping
78
+ .replace('\\\\{#', '\\{#'),
79
+ );
80
+ }
81
+
82
+ function addHeadingId(
83
+ line: string,
84
+ slugger: Slugger,
85
+ maintainCase: boolean,
86
+ syntax: HeadingIdSyntax,
87
+ headingId: string | undefined,
88
+ ): string {
89
+ let headingLevel = 0;
90
+ while (line.charAt(headingLevel) === '#') {
91
+ headingLevel += 1;
92
+ }
93
+
94
+ const headingHashes = line.slice(0, headingLevel);
95
+
96
+ const headingContent = line.slice(headingLevel).trimEnd();
97
+
98
+ function getHeadingId() {
99
+ if (headingId) {
100
+ return headingId;
101
+ }
102
+ // Unwrap links
103
+ // "[ Hello](https://example.com) World " => "Hello world"
104
+ const headingText = headingContent
105
+ .replace(/\[(?<alt>[^\]]+)\]\([^)]+\)/g, (_match, p1: string) => p1)
106
+ .trim();
107
+
108
+ return slugger.slug(headingText, {
109
+ maintainCase,
110
+ });
111
+ }
112
+
113
+ const headingIdSuffix =
114
+ syntax === 'mdx-comment'
115
+ ? `{/* #${getHeadingId()} */}`
116
+ : `{#${getHeadingId()}}`;
117
+
118
+ return `${headingHashes}${headingContent} ${headingIdSuffix}`;
119
+ }
120
+
121
+ export type WriteHeadingIDOptions = SluggerOptions & {
122
+ /** The target syntax to use for heading IDs. */
123
+ syntax?: HeadingIdSyntax;
124
+ /** Migrate the existing heading IDs to the target syntax */
125
+ migrate?: boolean;
126
+ /** Overwrite existing heading IDs by re-generating them from the text. */
127
+ overwrite?: boolean;
128
+ };
129
+
130
+ /**
131
+ * Takes Markdown content, returns new content with heading IDs written.
132
+ * Respects existing IDs (unless `overwrite=true`) and never generates colliding
133
+ * IDs (through the slugger).
134
+ */
135
+ export function writeMarkdownHeadingId(
136
+ content: string,
137
+ options: WriteHeadingIDOptions = {},
138
+ ): string {
139
+ const {
140
+ syntax = 'classic', // Maybe we'll want to change this default later?
141
+ overwrite = false,
142
+ migrate = false,
143
+ maintainCase = false,
144
+ } = options;
145
+
146
+ // For now, we have 2 booleans (retro compatible)
147
+ // but it could be useful to have a "mode" enum instead?
148
+ if (overwrite && migrate) {
149
+ throw new Error(
150
+ 'Heading ids can either be overwritten or migrated, not both at the same time',
151
+ );
152
+ }
153
+
154
+ const lines = content.split('\n');
155
+ const slugger = createSlugger();
156
+
157
+ // Parse heading ID trying both syntaxes (classic first, then mdx-comment)
158
+ function parseHeadingIdAnySyntax(heading: string) {
159
+ const classic = parseMarkdownHeadingId(heading, 'classic');
160
+ if (classic.id) {
161
+ return classic;
162
+ }
163
+ return parseMarkdownHeadingId(heading, 'mdx-comment');
164
+ }
165
+
166
+ // If we can't overwrite existing slugs, make sure other headings don't
167
+ // generate colliding slugs by first marking these slugs as occupied
168
+ if (!overwrite) {
169
+ lines.forEach((line) => {
170
+ const parsedHeading = parseHeadingIdAnySyntax(line);
171
+ if (parsedHeading.id) {
172
+ slugger.slug(parsedHeading.id);
173
+ }
174
+ });
175
+ }
176
+
177
+ let inCode = false;
178
+ return lines
179
+ .map((line) => {
180
+ if (line.startsWith('```')) {
181
+ inCode = !inCode;
182
+ return line;
183
+ }
184
+ // Ignore h1 headings, as we don't create anchor links for those
185
+ if (inCode || !line.startsWith('##')) {
186
+ return line;
187
+ }
188
+ const parsedHeading = parseHeadingIdAnySyntax(line);
189
+
190
+ // Preserve the line if id is already there, unless we migrate/overwrite
191
+ if (parsedHeading.id && !overwrite && !migrate) {
192
+ return line;
193
+ }
194
+ const headingId = overwrite
195
+ ? undefined
196
+ : migrate
197
+ ? parsedHeading.id
198
+ : undefined;
199
+
200
+ return addHeadingId(
201
+ parsedHeading.text,
202
+ slugger,
203
+ maintainCase,
204
+ syntax,
205
+ headingId,
206
+ );
207
+ })
208
+ .join('\n');
209
+ }
@@ -7,7 +7,6 @@
7
7
 
8
8
  import logger from '@docusaurus/logger';
9
9
  import matter from 'gray-matter';
10
- import {createSlugger, type Slugger, type SluggerOptions} from './slugger';
11
10
  import type {
12
11
  ParseFrontMatter,
13
12
  DefaultParseFrontMatter,
@@ -17,47 +16,6 @@ import type {
17
16
  // server-side when we infer metadata like `title` and `description` from the
18
17
  // content. Most parsing is still done in MDX through the mdx-loader.
19
18
 
20
- /**
21
- * Parses custom ID from a heading. The ID can contain any characters except
22
- * `{#` and `}`.
23
- *
24
- * @param heading e.g. `## Some heading {#some-heading}` where the last
25
- * character must be `}` for the ID to be recognized
26
- */
27
- export function parseMarkdownHeadingId(heading: string): {
28
- /**
29
- * The heading content sans the ID part, right-trimmed. e.g. `## Some heading`
30
- */
31
- text: string;
32
- /** The heading ID. e.g. `some-heading` */
33
- id: string | undefined;
34
- } {
35
- const customHeadingIdRegex = /\s*\{#(?<id>(?:.(?!\{#|\}))*.)\}$/;
36
- const matches = customHeadingIdRegex.exec(heading);
37
- if (matches) {
38
- return {
39
- text: heading.replace(matches[0]!, ''),
40
- id: matches.groups!.id!,
41
- };
42
- }
43
- return {text: heading, id: undefined};
44
- }
45
-
46
- /**
47
- * MDX 2 requires escaping { with a \ so our anchor syntax need that now.
48
- * See https://mdxjs.com/docs/troubleshooting-mdx/#could-not-parse-expression-with-acorn-error
49
- */
50
- export function escapeMarkdownHeadingIds(content: string): string {
51
- const markdownHeadingRegexp = /(?:^|\n)#{1,6}(?!#).*/g;
52
- return content.replaceAll(markdownHeadingRegexp, (substring) =>
53
- // TODO probably not the most efficient impl...
54
- substring
55
- .replace('{#', '\\{#')
56
- // prevent duplicate escaping
57
- .replace('\\\\{#', '\\{#'),
58
- );
59
- }
60
-
61
19
  /**
62
20
  * Hacky temporary escape hatch for Crowdin bad MDX support
63
21
  * See https://docusaurus.io/docs/i18n/crowdin#mdx
@@ -383,80 +341,3 @@ This can happen if you use special characters in front matter values (try using
383
341
  throw err;
384
342
  }
385
343
  }
386
-
387
- function unwrapMarkdownLinks(line: string): string {
388
- return line.replace(
389
- /\[(?<alt>[^\]]+)\]\([^)]+\)/g,
390
- (match, p1: string) => p1,
391
- );
392
- }
393
-
394
- function addHeadingId(
395
- line: string,
396
- slugger: Slugger,
397
- maintainCase: boolean,
398
- ): string {
399
- let headingLevel = 0;
400
- while (line.charAt(headingLevel) === '#') {
401
- headingLevel += 1;
402
- }
403
-
404
- const headingText = line.slice(headingLevel).trimEnd();
405
- const headingHashes = line.slice(0, headingLevel);
406
- const slug = slugger.slug(unwrapMarkdownLinks(headingText).trim(), {
407
- maintainCase,
408
- });
409
-
410
- return `${headingHashes}${headingText} {#${slug}}`;
411
- }
412
-
413
- export type WriteHeadingIDOptions = SluggerOptions & {
414
- /** Overwrite existing heading IDs. */
415
- overwrite?: boolean;
416
- };
417
-
418
- /**
419
- * Takes Markdown content, returns new content with heading IDs written.
420
- * Respects existing IDs (unless `overwrite=true`) and never generates colliding
421
- * IDs (through the slugger).
422
- */
423
- export function writeMarkdownHeadingId(
424
- content: string,
425
- options: WriteHeadingIDOptions = {maintainCase: false, overwrite: false},
426
- ): string {
427
- const {maintainCase = false, overwrite = false} = options;
428
- const lines = content.split('\n');
429
- const slugger = createSlugger();
430
-
431
- // If we can't overwrite existing slugs, make sure other headings don't
432
- // generate colliding slugs by first marking these slugs as occupied
433
- if (!overwrite) {
434
- lines.forEach((line) => {
435
- const parsedHeading = parseMarkdownHeadingId(line);
436
- if (parsedHeading.id) {
437
- slugger.slug(parsedHeading.id);
438
- }
439
- });
440
- }
441
-
442
- let inCode = false;
443
- return lines
444
- .map((line) => {
445
- if (line.startsWith('```')) {
446
- inCode = !inCode;
447
- return line;
448
- }
449
- // Ignore h1 headings, as we don't create anchor links for those
450
- if (inCode || !line.startsWith('##')) {
451
- return line;
452
- }
453
- const parsedHeading = parseMarkdownHeadingId(line);
454
-
455
- // Do not process if id is already there
456
- if (parsedHeading.id && !overwrite) {
457
- return line;
458
- }
459
- return addHeadingId(parsedHeading.text, slugger, maintainCase);
460
- })
461
- .join('\n');
462
- }
@@ -12,12 +12,12 @@ import logger from '@docusaurus/logger';
12
12
  jiti is able to load ESM, CJS, JSON, TS modules
13
13
  */
14
14
  export async function loadFreshModule(modulePath: string): Promise<unknown> {
15
+ if (typeof modulePath !== 'string') {
16
+ throw new Error(
17
+ logger.interpolate`Invalid module path of type "name=${typeof modulePath}" with value "name=${modulePath}"`,
18
+ );
19
+ }
15
20
  try {
16
- if (typeof modulePath !== 'string') {
17
- throw new Error(
18
- logger.interpolate`Invalid module path of type name=${modulePath}`,
19
- );
20
- }
21
21
  const load = jiti(__filename, {
22
22
  // Transpilation cache, can be safely enabled
23
23
  cache: true,
@@ -34,9 +34,7 @@ export async function loadFreshModule(modulePath: string): Promise<unknown> {
34
34
  return load(modulePath);
35
35
  } catch (error) {
36
36
  throw new Error(
37
- logger.interpolate`Docusaurus could not load module at path path=${modulePath}\nCause: ${
38
- (error as Error).message
39
- }`,
37
+ logger.interpolate`Docusaurus could not load module at path path=${modulePath}`,
40
38
  {cause: error},
41
39
  );
42
40
  }