@docusaurus/core 3.10.1 → 4.0.0-canary-6808

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 (46) hide show
  1. package/bin/beforeCli.mjs +9 -11
  2. package/lib/client/BaseUrlIssueBanner/index.js +1 -1
  3. package/lib/client/exports/ComponentCreator.js +0 -2
  4. package/lib/client/exports/Link.d.ts +3 -3
  5. package/lib/client/exports/Link.js +17 -17
  6. package/lib/client/exports/isInternalUrl.js +1 -1
  7. package/lib/client/preload.js +1 -3
  8. package/lib/client/serverEntry.js +1 -6
  9. package/lib/client/serverHelmetUtils.js +0 -1
  10. package/lib/commands/build/buildLocale.d.ts +1 -1
  11. package/lib/commands/build/buildLocale.js +0 -4
  12. package/lib/commands/build/buildUtils.d.ts +1 -1
  13. package/lib/commands/cli.js +3 -0
  14. package/lib/commands/deploy.js +39 -56
  15. package/lib/commands/serve.js +3 -2
  16. package/lib/commands/start/start.d.ts +3 -0
  17. package/lib/commands/start/webpack.js +20 -2
  18. package/lib/commands/swizzle/actions.js +5 -0
  19. package/lib/commands/utils/listenToServer.d.ts +5 -0
  20. package/lib/commands/utils/listenToServer.js +24 -0
  21. package/lib/commands/utils/openBrowser/openBrowser.js +3 -3
  22. package/lib/commands/writeHeadingIds.js +3 -3
  23. package/lib/server/codegen/codegenRoutes.js +0 -1
  24. package/lib/server/config.js +1 -1
  25. package/lib/server/configValidation.js +11 -17
  26. package/lib/server/getHostPort.js +1 -2
  27. package/lib/server/htmlTags.js +2 -3
  28. package/lib/server/i18n.js +2 -6
  29. package/lib/server/plugins/plugins.js +1 -9
  30. package/lib/server/site.js +2 -1
  31. package/lib/server/siteMetadata.d.ts +6 -1
  32. package/lib/server/siteMetadata.js +15 -12
  33. package/lib/server/translations/translations.js +1 -1
  34. package/lib/ssg/ssgEnv.js +2 -3
  35. package/lib/ssg/ssgExecutor.js +22 -5
  36. package/lib/ssg/ssgGlobalResult.js +1 -10
  37. package/lib/ssg/ssgParams.d.ts +0 -1
  38. package/lib/ssg/ssgParams.js +0 -1
  39. package/lib/ssg/ssgRenderer.js +1 -5
  40. package/lib/ssg/ssgTemplate.js +4 -6
  41. package/lib/ssg/ssgWorkerThread.js +0 -1
  42. package/lib/webpack/base.js +12 -22
  43. package/lib/webpack/server.js +1 -1
  44. package/lib/webpack/utils/getHttpsConfig.d.ts +7 -1
  45. package/lib/webpack/utils/getHttpsConfig.js +93 -32
  46. package/package.json +41 -41
@@ -16,15 +16,14 @@ const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
16
16
  const DEFAULT_I18N_LOCALE = 'en';
17
17
  const SiteUrlSchema = utils_validation_1.Joi.string()
18
18
  .custom((value, helpers) => {
19
- try {
20
- const { pathname } = new URL(value);
21
- if (pathname !== '/') {
22
- return helpers.error('docusaurus.subPathError', { pathname });
23
- }
24
- }
25
- catch {
19
+ const url = URL.parse(value);
20
+ if (url === null) {
26
21
  return helpers.error('any.invalid');
27
22
  }
23
+ const { pathname } = url;
24
+ if (pathname !== '/') {
25
+ return helpers.error('docusaurus.subPathError', { pathname });
26
+ }
28
27
  return (0, utils_common_1.removeTrailingSlash)(value);
29
28
  })
30
29
  .messages({
@@ -70,7 +69,6 @@ exports.DEFAULT_FASTER_CONFIG_TRUE = {
70
69
  gitEagerVcs: true,
71
70
  };
72
71
  exports.DEFAULT_FUTURE_V4_CONFIG = {
73
- removeLegacyPostBuildHeadAttribute: false,
74
72
  useCssCascadeLayers: false,
75
73
  siteStorageNamespacing: false,
76
74
  fasterByDefault: false,
@@ -78,7 +76,6 @@ exports.DEFAULT_FUTURE_V4_CONFIG = {
78
76
  };
79
77
  // When using the "v4: true" shortcut
80
78
  exports.DEFAULT_FUTURE_V4_CONFIG_TRUE = {
81
- removeLegacyPostBuildHeadAttribute: true,
82
79
  useCssCascadeLayers: true,
83
80
  siteStorageNamespacing: true,
84
81
  fasterByDefault: true,
@@ -93,6 +90,7 @@ exports.DEFAULT_FUTURE_CONFIG = {
93
90
  exports.DEFAULT_MARKDOWN_HOOKS = {
94
91
  onBrokenMarkdownLinks: 'warn',
95
92
  onBrokenMarkdownImages: 'throw',
93
+ onUnusedMarkdownDirectives: 'warn',
96
94
  };
97
95
  exports.DEFAULT_MARKDOWN_MDX1COMPAT = {
98
96
  comments: true,
@@ -227,7 +225,6 @@ const FASTER_CONFIG_SCHEMA = utils_validation_1.Joi.alternatives()
227
225
  .optional();
228
226
  const FUTURE_V4_SCHEMA = utils_validation_1.Joi.alternatives()
229
227
  .try(utils_validation_1.Joi.object({
230
- removeLegacyPostBuildHeadAttribute: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.removeLegacyPostBuildHeadAttribute),
231
228
  useCssCascadeLayers: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.useCssCascadeLayers),
232
229
  siteStorageNamespacing: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.siteStorageNamespacing),
233
230
  fasterByDefault: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.fasterByDefault),
@@ -352,7 +349,7 @@ exports.ConfigSchema = utils_validation_1.Joi.object({
352
349
  is: utils_validation_1.Joi.valid(true),
353
350
  then: utils_validation_1.Joi.optional(),
354
351
  otherwise: utils_validation_1.Joi.object()
355
- .pattern(/[\w-]+/, utils_validation_1.Joi.string())
352
+ .pattern(/[\w-]+/, utils_validation_1.Joi.alternatives().try(utils_validation_1.Joi.string(), utils_validation_1.Joi.boolean()))
356
353
  .required(),
357
354
  }),
358
355
  customElement: utils_validation_1.Joi.bool().default(false),
@@ -415,6 +412,9 @@ exports.ConfigSchema = utils_validation_1.Joi.object({
415
412
  onBrokenMarkdownImages: utils_validation_1.Joi.alternatives()
416
413
  .try(utils_validation_1.Joi.string().equal('ignore', 'log', 'warn', 'throw'), utils_validation_1.Joi.function())
417
414
  .default(exports.DEFAULT_CONFIG.markdown.hooks.onBrokenMarkdownImages),
415
+ onUnusedMarkdownDirectives: utils_validation_1.Joi.alternatives()
416
+ .try(utils_validation_1.Joi.string().equal('ignore', 'log', 'warn', 'throw'), utils_validation_1.Joi.function())
417
+ .default(exports.DEFAULT_CONFIG.markdown.hooks.onUnusedMarkdownDirectives),
418
418
  }).default(exports.DEFAULT_CONFIG.markdown.hooks),
419
419
  }).default({
420
420
  ...exports.DEFAULT_CONFIG.markdown,
@@ -470,12 +470,6 @@ Please migrate and move this option to code=${'siteConfig.markdown.hooks.onBroke
470
470
  : (0, utils_1.getVcsPreset)('disabled');
471
471
  config.future.experimental_vcs = vcsConfig;
472
472
  }
473
- if (config.future.faster.ssgWorkerThreads &&
474
- !config.future.v4.removeLegacyPostBuildHeadAttribute) {
475
- throw new Error(`Docusaurus config ${logger_1.default.code('future.faster.ssgWorkerThreads')} requires the future flag ${logger_1.default.code('future.v4.removeLegacyPostBuildHeadAttribute')} to be turned on.
476
- If you use Docusaurus Faster, we recommend that you also activate Docusaurus v4 future flags: ${logger_1.default.code('{future: {v4: true}}')}
477
- All the v4 future flags are documented here: https://docusaurus.io/docs/api/docusaurus-config#future`);
478
- }
479
473
  if (config.future.faster.rspackPersistentCache &&
480
474
  !config.future.faster.rspackBundler) {
481
475
  throw new Error(`Docusaurus config flag ${logger_1.default.code('future.faster.rspackPersistentCache')} requires the flag ${logger_1.default.code('future.faster.rspackBundler')} to be turned on.`);
@@ -69,8 +69,7 @@ Would you like to run the app on another port instead?`),
69
69
  return shouldChangePort ? port : null;
70
70
  }
71
71
  catch (err) {
72
- logger_1.default.error `Could not find an open port at ${host}.`;
73
- throw err;
72
+ throw new Error(logger_1.default.interpolate `Could not find an open port at ${host}.`, { cause: err });
74
73
  }
75
74
  }
76
75
  async function getHostPort(options) {
@@ -9,8 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.loadHtmlTags = loadHtmlTags;
10
10
  const tslib_1 = require("tslib");
11
11
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
12
- const html_tags_1 = tslib_1.__importDefault(require("html-tags"));
13
- const void_1 = tslib_1.__importDefault(require("html-tags/void"));
12
+ const html_tags_1 = tslib_1.__importStar(require("html-tags"));
14
13
  const escape_html_1 = tslib_1.__importDefault(require("escape-html"));
15
14
  // TODO this should be done at config validation time, not here
16
15
  function assertIsHtmlTagObject(val) {
@@ -34,7 +33,7 @@ function hashRouterAbsoluteToRelativeTagAttribute(name, value) {
34
33
  }
35
34
  function htmlTagObjectToString({ tag, router, }) {
36
35
  assertIsHtmlTagObject(tag);
37
- const isVoidTag = void_1.default.includes(tag.tagName);
36
+ const isVoidTag = html_tags_1.voidHtmlTags.includes(tag.tagName);
38
37
  const tagAttributes = tag.attributes ?? {};
39
38
  const attributes = Object.keys(tagAttributes)
40
39
  .map((attr) => {
@@ -23,7 +23,7 @@ function inferLanguageDisplayName(locale) {
23
23
  fallback: 'code',
24
24
  }).of(l);
25
25
  }
26
- catch (e) {
26
+ catch {
27
27
  // This is to compensate "of()" that is a bit strict
28
28
  // Looks like starting Node 22, this locale throws: "en-US-u-ca-buddhist"
29
29
  // RangeError: invalid_argument
@@ -64,11 +64,7 @@ function getDefaultDirection(localeStr) {
64
64
  const locale = new Intl.Locale(localeStr);
65
65
  // see https://github.com/tc39/proposal-intl-locale-info
66
66
  // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getTextInfo
67
- // Node 18.0 implements a former version of the getTextInfo() proposal
68
- // TODO Docusaurus v4: remove the fallback to locale.textInfo
69
- // @ts-expect-error: The TC39 proposal was updated
70
- const textInto = locale.getTextInfo?.() ?? locale.textInfo;
71
- return textInto.direction ?? 'ltr';
67
+ return locale.getTextInfo().direction ?? 'ltr';
72
68
  }
73
69
  function getDefaultLocaleConfig(
74
70
  // Locale "key/identifier"
@@ -169,15 +169,7 @@ async function reloadPlugin({ pluginIdentifier, plugins: previousPlugins, contex
169
169
  plugin: previousPlugin,
170
170
  context,
171
171
  });
172
- /*
173
- // TODO Docusaurus v4 - upgrade to Node 20, use array.with()
174
- const plugins = previousPlugins.with(
175
- previousPlugins.indexOf(previousPlugin),
176
- plugin,
177
- );
178
- */
179
- const plugins = [...previousPlugins];
180
- plugins[previousPlugins.indexOf(previousPlugin)] = plugin;
172
+ const plugins = previousPlugins.with(previousPlugins.indexOf(previousPlugin), plugin);
181
173
  const allContentLoadedResult = await executeAllPluginsAllContentLoaded({
182
174
  plugins,
183
175
  context,
@@ -37,7 +37,7 @@ async function loadContext(params) {
37
37
  const { siteDir, outDir: baseOutDir = utils_1.DEFAULT_BUILD_DIR_NAME, locale, config: customConfigFilePath, automaticBaseUrlLocalizationDisabled, } = params;
38
38
  const generatedFilesDir = path_1.default.resolve(siteDir, utils_1.GENERATED_FILES_DIR_NAME);
39
39
  const { siteVersion, loadSiteConfig: { siteConfig: initialSiteConfig, siteConfigPath }, } = await (0, combine_promises_1.default)({
40
- siteVersion: (0, siteMetadata_1.loadSiteVersion)(siteDir),
40
+ siteVersion: (0, siteMetadata_1.tryLoadSitePackageJson)(siteDir).then((pkg) => pkg?.version),
41
41
  loadSiteConfig: (0, config_1.loadSiteConfig)({
42
42
  siteDir,
43
43
  customConfigFilePath,
@@ -78,6 +78,7 @@ async function loadContext(params) {
78
78
  const localizationDir = path_1.default.resolve(siteDir, i18n.path, (0, utils_1.getLocaleConfig)(i18n).path);
79
79
  const siteConfig = {
80
80
  ...initialSiteConfig,
81
+ url: localeConfig.url,
81
82
  baseUrl,
82
83
  };
83
84
  const codeTranslations = await (0, translations_1.loadSiteCodeTranslations)({ localizationDir });
@@ -5,9 +5,14 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import type { LoadedPlugin, PluginVersionInformation, SiteMetadata } from '@docusaurus/types';
8
- export declare function loadSiteVersion(siteDir: string): Promise<string | undefined>;
8
+ type PackageJson = {
9
+ name?: string;
10
+ version?: string;
11
+ };
12
+ export declare function tryLoadSitePackageJson(siteDir: string): Promise<PackageJson | undefined>;
9
13
  export declare function loadPluginVersion(pluginPath: string, siteDir: string): Promise<PluginVersionInformation>;
10
14
  export declare function createSiteMetadata({ siteVersion, plugins, }: {
11
15
  siteVersion: string | undefined;
12
16
  plugins: LoadedPlugin[];
13
17
  }): SiteMetadata;
18
+ export {};
@@ -6,26 +6,28 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.loadSiteVersion = loadSiteVersion;
9
+ exports.tryLoadSitePackageJson = tryLoadSitePackageJson;
10
10
  exports.loadPluginVersion = loadPluginVersion;
11
11
  exports.createSiteMetadata = createSiteMetadata;
12
12
  const tslib_1 = require("tslib");
13
13
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
14
14
  const path_1 = tslib_1.__importDefault(require("path"));
15
15
  const utils_1 = require("@docusaurus/utils");
16
- async function loadPackageJsonVersion(packageJsonPath) {
16
+ async function tryLoadPackageJson(packageJsonPath) {
17
17
  if (await fs_extra_1.default.pathExists(packageJsonPath)) {
18
- // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
19
- return require(packageJsonPath).version;
18
+ try {
19
+ return (await fs_extra_1.default.readJSON(packageJsonPath));
20
+ }
21
+ catch (error) {
22
+ throw new Error(`Couldn't load package.json file at ${packageJsonPath}`, {
23
+ cause: error,
24
+ });
25
+ }
20
26
  }
21
27
  return undefined;
22
28
  }
23
- async function loadPackageJsonName(packageJsonPath) {
24
- // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
25
- return require(packageJsonPath).name;
26
- }
27
- async function loadSiteVersion(siteDir) {
28
- return loadPackageJsonVersion(path_1.default.join(siteDir, 'package.json'));
29
+ async function tryLoadSitePackageJson(siteDir) {
30
+ return tryLoadPackageJson(path_1.default.join(siteDir, 'package.json'));
29
31
  }
30
32
  async function loadPluginVersion(pluginPath, siteDir) {
31
33
  let potentialPluginPackageJsonDirectory = path_1.default.dirname(pluginPath);
@@ -38,10 +40,11 @@ async function loadPluginVersion(pluginPath, siteDir) {
38
40
  // as local plugin.
39
41
  return { type: 'project' };
40
42
  }
43
+ const packageJson = await tryLoadPackageJson(packageJsonPath);
41
44
  return {
42
45
  type: 'package',
43
- name: await loadPackageJsonName(packageJsonPath),
44
- version: await loadPackageJsonVersion(packageJsonPath),
46
+ name: packageJson?.name,
47
+ version: packageJson?.version,
45
48
  };
46
49
  }
47
50
  potentialPluginPackageJsonDirectory = path_1.default.dirname(potentialPluginPackageJsonDirectory);
@@ -62,7 +62,7 @@ function mergeTranslationFileContent({ existingContent = {}, newContent, options
62
62
  // If messages already exist, we don't override them (unless requested)
63
63
  message: options.override
64
64
  ? message
65
- : existingContent[key]?.message ?? message,
65
+ : (existingContent[key]?.message ?? message),
66
66
  description,
67
67
  };
68
68
  });
package/lib/ssg/ssgEnv.js CHANGED
@@ -9,9 +9,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.SSGWorkerThreadRecyclerMaxMemory = exports.SSGWorkerThreadTaskSize = exports.SSGWorkerThreadCount = exports.SSGConcurrency = void 0;
10
10
  // Secret way to set SSR plugin async concurrency option
11
11
  // Waiting for feedback before documenting this officially?
12
- // TODO Docusaurus v4, rename SSR => SSG
13
- exports.SSGConcurrency = process.env.DOCUSAURUS_SSR_CONCURRENCY
14
- ? parseInt(process.env.DOCUSAURUS_SSR_CONCURRENCY, 10)
12
+ exports.SSGConcurrency = process.env.DOCUSAURUS_SSG_CONCURRENCY
13
+ ? parseInt(process.env.DOCUSAURUS_SSG_CONCURRENCY, 10)
15
14
  : // Not easy to define a reasonable option default
16
15
  // Will still be better than Infinity
17
16
  // See also https://github.com/sindresorhus/p-map/issues/24
@@ -51,11 +51,7 @@ function getNumberOfThreads(pathnames) {
51
51
  return ssgEnv_1.SSGWorkerThreadCount;
52
52
  }
53
53
  // See also https://github.com/tinylibs/tinypool/pull/108
54
- const cpuCount =
55
- // TODO Docusaurus v4: bump node, availableParallelism() now always exists
56
- typeof os_1.default.availableParallelism === 'function'
57
- ? os_1.default.availableParallelism()
58
- : os_1.default.cpus().length;
54
+ const cpuCount = os_1.default.availableParallelism();
59
55
  return inferNumberOfThreads({
60
56
  pageCount: pathnames.length,
61
57
  cpuCount,
@@ -64,6 +60,21 @@ function getNumberOfThreads(pathnames) {
64
60
  minPagesPerCpu: 100,
65
61
  });
66
62
  }
63
+ // Workaround for Node styleText() limitation
64
+ // See https://github.com/nodejs/node/issues/65766
65
+ function getWorkerColorEnv() {
66
+ // Preserve an explicit user choice
67
+ if (process.env.FORCE_COLOR !== undefined) {
68
+ return {};
69
+ }
70
+ const depth = process.stdout.isTTY
71
+ ? (process.stdout.getColorDepth?.() ?? 0)
72
+ : 0;
73
+ if (depth > 2) {
74
+ return { FORCE_COLOR: depth >= 24 ? '3' : depth >= 8 ? '2' : '1' };
75
+ }
76
+ return {};
77
+ }
67
78
  const createPooledSSGExecutor = async ({ params, pathnames, }) => {
68
79
  const numberOfThreads = getNumberOfThreads(pathnames);
69
80
  // When the inferred or provided number of threads is just 1
@@ -84,6 +95,12 @@ const createPooledSSGExecutor = async ({ params, pathnames, }) => {
84
95
  runtime: 'worker_threads',
85
96
  isolateWorkers: false,
86
97
  workerData: { params },
98
+ env: {
99
+ // Cast is safe
100
+ // See https://github.com/tinylibs/tinypool/issues/136
101
+ ...process.env,
102
+ ...getWorkerColorEnv(),
103
+ },
87
104
  // WORKER MEMORY MANAGEMENT
88
105
  // Allows containing SSG memory leaks with a thread recycling workaround
89
106
  // See https://github.com/facebook/docusaurus/pull/11166
@@ -17,20 +17,11 @@ function printSSGWarnings(results) {
17
17
  if (process.env.DOCUSAURUS_IGNORE_SSG_WARNINGS === 'true') {
18
18
  return;
19
19
  }
20
- const ignoredWarnings = [
21
- // TODO Docusaurus v4: remove with React 19 upgrade
22
- // React 18 emit NULL chars, and minifier detects it
23
- // see https://github.com/facebook/docusaurus/issues/9985
24
- 'Unexpected null character',
25
- ];
26
- const keepWarning = (warning) => {
27
- return !ignoredWarnings.some((iw) => warning.includes(iw));
28
- };
29
20
  const resultsWithWarnings = results
30
21
  .map((success) => {
31
22
  return {
32
23
  ...success,
33
- warnings: success.result.warnings.filter(keepWarning),
24
+ warnings: success.result.warnings,
34
25
  };
35
26
  })
36
27
  .filter((result) => result.warnings.length > 0);
@@ -20,7 +20,6 @@ export type SSGParams = {
20
20
  htmlMinifierType: HtmlMinifierType;
21
21
  serverBundlePath: string;
22
22
  ssgTemplateContent: string;
23
- v4RemoveLegacyPostBuildHeadAttribute: boolean;
24
23
  };
25
24
  export declare function createSSGParams({ props, serverBundlePath, clientManifestPath, }: {
26
25
  props: Props;
@@ -29,7 +29,6 @@ async function createSSGParams({ props, serverBundlePath, clientManifestPath, })
29
29
  htmlMinifierType: props.siteConfig.future.faster.swcHtmlMinimizer
30
30
  ? 'swc'
31
31
  : 'terser',
32
- v4RemoveLegacyPostBuildHeadAttribute: props.siteConfig.future.v4.removeLegacyPostBuildHeadAttribute,
33
32
  };
34
33
  // Useless but ensures that SSG params remain serializable
35
34
  return structuredClone(params);
@@ -86,7 +86,6 @@ function reduceCollectedData(pageCollectedData) {
86
86
  anchors: pageCollectedData.anchors,
87
87
  metadata: {
88
88
  public: pageCollectedData.metadata.public,
89
- helmet: pageCollectedData.metadata.helmet,
90
89
  },
91
90
  links: pageCollectedData.links,
92
91
  };
@@ -94,10 +93,7 @@ function reduceCollectedData(pageCollectedData) {
94
93
  async function generateStaticFile({ pathname, appRenderer, params, htmlMinifier, ssgTemplate, }) {
95
94
  try {
96
95
  // This only renders the app HTML
97
- const appRenderResult = await appRenderer.render({
98
- pathname,
99
- v4RemoveLegacyPostBuildHeadAttribute: params.v4RemoveLegacyPostBuildHeadAttribute,
100
- });
96
+ const appRenderResult = await appRenderer.render({ pathname });
101
97
  // This renders the full page HTML, including head tags...
102
98
  const fullPageHtml = (0, ssgTemplate_1.renderSSGTemplate)({
103
99
  params,
@@ -9,15 +9,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.compileSSGTemplate = compileSSGTemplate;
10
10
  exports.renderSSGTemplate = renderSSGTemplate;
11
11
  exports.renderHashRouterTemplate = renderHashRouterTemplate;
12
- const tslib_1 = require("tslib");
13
- const eta = tslib_1.__importStar(require("eta"));
12
+ const eta_1 = require("eta");
14
13
  const react_loadable_ssr_addon_v5_slorber_1 = require("react-loadable-ssr-addon-v5-slorber");
15
14
  const logger_1 = require("@docusaurus/logger");
16
15
  async function compileSSGTemplate(template) {
17
- const compiledTemplate = eta.compile(template.trim(), {
18
- rmWhitespace: true,
19
- });
20
- return (data) => compiledTemplate(data, eta.defaultConfig);
16
+ const eta = new eta_1.Eta({ rmWhitespace: true });
17
+ const compiledTemplate = eta.compile(template.trim());
18
+ return (data) => eta.render(compiledTemplate, data);
21
19
  }
22
20
  /**
23
21
  * Given a list of modules that were SSR an d
@@ -11,7 +11,6 @@ const tslib_1 = require("tslib");
11
11
  const node_worker_threads_1 = require("node:worker_threads");
12
12
  const logger_1 = tslib_1.__importStar(require("@docusaurus/logger"));
13
13
  const ssgRenderer_js_1 = require("./ssgRenderer.js");
14
- // eslint-disable-next-line no-underscore-dangle
15
14
  const workerId = process?.__tinypool_state__?.workerId;
16
15
  if (!workerId) {
17
16
  throw new Error('SSG Worker Thread not executing in Tinypool context?');
@@ -87,9 +87,14 @@ async function createBaseConfig({ props, isServer, minify, faster, configureWebp
87
87
  }
88
88
  if (props.currentBundler.name === 'rspack') {
89
89
  if (props.siteConfig.future.faster.rspackPersistentCache) {
90
- // Use cache: true + experiments.cache.type: "persistent"
91
- // See https://rspack.dev/config/experiments#persistent-cache
92
- return true;
90
+ return {
91
+ type: 'persistent',
92
+ // Rspack doesn't have "cache.name" like Webpack
93
+ // This is not ideal but work around is to merge name/version
94
+ // See https://github.com/web-infra-dev/rspack/pull/8920#issuecomment-2658938695
95
+ version: `${getCacheName()}-${getCacheVersion()}`,
96
+ buildDependencies: getCacheBuildDependencies(),
97
+ };
93
98
  }
94
99
  else {
95
100
  return disabledPersistentCacheValue;
@@ -104,29 +109,10 @@ async function createBaseConfig({ props, isServer, minify, faster, configureWebp
104
109
  },
105
110
  };
106
111
  }
107
- function getExperiments() {
108
- if (props.currentBundler.name === 'rspack') {
109
- // TODO find a way to type this
110
- const experiments = {};
111
- if (!process.env.DOCUSAURUS_NO_PERSISTENT_CACHE) {
112
- experiments.cache = {
113
- type: 'persistent',
114
- // Rspack doesn't have "cache.name" like Webpack
115
- // This is not ideal but work around is to merge name/version
116
- // See https://github.com/web-infra-dev/rspack/pull/8920#issuecomment-2658938695
117
- version: `${getCacheName()}-${getCacheVersion()}`,
118
- buildDependencies: getCacheBuildDependencies(),
119
- };
120
- }
121
- return experiments;
122
- }
123
- return undefined;
124
- }
125
112
  return {
126
113
  mode,
127
114
  name,
128
115
  cache: getCache(),
129
- experiments: getExperiments(),
130
116
  output: {
131
117
  pathinfo: false,
132
118
  path: outDir,
@@ -251,6 +237,10 @@ async function createBaseConfig({ props, isServer, minify, faster, configureWebp
251
237
  // See https://github.com/facebook/docusaurus/pull/10423
252
238
  localIdentName: `[local]_[contenthash:base64:4]`,
253
239
  exportOnlyLocals: isServer,
240
+ // Export CSS module class names compatible with css-loader v6
241
+ // export ".themedComponent--dark" instead of .themedComponentDark
242
+ // See https://github.com/webpack/css-loader/releases/tag/v7.0.0
243
+ exportLocalsConvention: 'as-is',
254
244
  },
255
245
  importLoaders: 1,
256
246
  sourceMap: !isProd,
@@ -35,7 +35,7 @@ async function createServerConfig({ props, configureWebpackUtils, }) {
35
35
  output: {
36
36
  path: outputDir,
37
37
  filename: outputFilename,
38
- libraryTarget: 'commonjs2',
38
+ library: { type: 'commonjs2' },
39
39
  },
40
40
  plugins: [
41
41
  new ProgressBarPlugin({
@@ -4,7 +4,13 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- export default function getHttpsConfig(): Promise<boolean | {
7
+ type HttpsConfigOptions = {
8
+ https: boolean;
9
+ sslCert: string;
10
+ sslKey: string;
11
+ };
12
+ export default function getHttpsConfig(options?: Partial<HttpsConfigOptions>): Promise<boolean | {
8
13
  cert: Buffer;
9
14
  key: Buffer;
10
15
  }>;
16
+ export {};
@@ -13,48 +13,109 @@ const path_1 = tslib_1.__importDefault(require("path"));
13
13
  const crypto_1 = tslib_1.__importDefault(require("crypto"));
14
14
  const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
15
15
  // Ensure the certificate and key provided are valid and if not
16
- // throw an easy to debug error
17
- function validateKeyAndCerts({ cert, key, keyFile, crtFile, }) {
18
- let encrypted;
16
+ // throw an easy to debug error.
17
+ //
18
+ // Works for any key type (RSA, ECDSA, EdDSA, ...) — parses both PEMs and
19
+ // checks that the public key embedded in the cert matches the public key
20
+ // derived from the private key.
21
+ function validateKeyAndCerts({ cert, key }) {
22
+ let certPublicKey;
19
23
  try {
20
- // publicEncrypt will throw an error with an invalid cert
21
- encrypted = crypto_1.default.publicEncrypt(cert, Buffer.from('test'));
24
+ certPublicKey = new crypto_1.default.X509Certificate(cert.content).publicKey;
22
25
  }
23
- catch (err) {
24
- logger_1.default.error `The certificate path=${crtFile} is invalid.`;
25
- throw err;
26
+ catch (error) {
27
+ throw new Error(logger_1.default.interpolate `The certificate path=${cert.path} is invalid.`, { cause: error });
26
28
  }
29
+ let keyPublicKey;
27
30
  try {
28
- // privateDecrypt will throw an error with an invalid key
29
- crypto_1.default.privateDecrypt(key, encrypted);
31
+ keyPublicKey = crypto_1.default.createPublicKey(crypto_1.default.createPrivateKey(key.content));
30
32
  }
31
- catch (err) {
32
- logger_1.default.error `The certificate key path=${keyFile} is invalid.`;
33
- throw err;
33
+ catch (error) {
34
+ throw new Error(logger_1.default.interpolate `The certificate key path=${key.path} is invalid.`, { cause: error });
34
35
  }
36
+ if (!certPublicKey.equals(keyPublicKey)) {
37
+ throw new Error(logger_1.default.interpolate `The certificate path=${cert.path} and key path=${key.path} do not match.`);
38
+ }
39
+ }
40
+ function getExplicitHttps(options) {
41
+ return (options.https ??
42
+ (typeof process.env.DOCUSAURUS_HTTPS !== 'undefined'
43
+ ? process.env.DOCUSAURUS_HTTPS == 'true'
44
+ : undefined) ??
45
+ (typeof process.env.HTTPS !== 'undefined'
46
+ ? process.env.HTTPS == 'true'
47
+ : undefined));
48
+ }
49
+ async function readCryptoFile(filepath, source) {
50
+ if (!(await fs_extra_1.default.pathExists(filepath))) {
51
+ throw new Error(logger_1.default.interpolate `You specified ${source}, but file at path path=${filepath} can't be found.`);
52
+ }
53
+ try {
54
+ return {
55
+ path: filepath,
56
+ source,
57
+ content: await fs_extra_1.default.readFile(filepath),
58
+ };
59
+ }
60
+ catch (error) {
61
+ throw new Error(logger_1.default.interpolate `You specified ${source}, but file at path path=${filepath} can't be read.`, { cause: error });
62
+ }
63
+ }
64
+ function getCert(options, cwd) {
65
+ if (options.sslCert) {
66
+ return readCryptoFile(path_1.default.resolve(cwd, options.sslCert), 'CLI arg --ssl-cert');
67
+ }
68
+ if (process.env.DOCUSAURUS_SSL_CRT_FILE) {
69
+ return readCryptoFile(path_1.default.resolve(cwd, process.env.DOCUSAURUS_SSL_CRT_FILE), 'env DOCUSAURUS_SSL_CRT_FILE');
70
+ }
71
+ if (process.env.SSL_CRT_FILE) {
72
+ return readCryptoFile(path_1.default.resolve(cwd, process.env.SSL_CRT_FILE), 'env SSL_CRT_FILE');
73
+ }
74
+ return null;
75
+ }
76
+ function getKeyFile(options, cwd) {
77
+ if (options.sslKey) {
78
+ return readCryptoFile(path_1.default.resolve(cwd, options.sslKey), 'CLI arg --ssl-key');
79
+ }
80
+ if (process.env.DOCUSAURUS_SSL_KEY_FILE) {
81
+ return readCryptoFile(path_1.default.resolve(cwd, process.env.DOCUSAURUS_SSL_KEY_FILE), 'env DOCUSAURUS_SSL_KEY_FILE');
82
+ }
83
+ if (process.env.SSL_KEY_FILE) {
84
+ return readCryptoFile(path_1.default.resolve(cwd, process.env.SSL_KEY_FILE), 'env SSL_KEY_FILE');
85
+ }
86
+ return null;
35
87
  }
36
- // Read file and throw an error if it doesn't exist
37
- async function readEnvFile(file, type) {
38
- if (!(await fs_extra_1.default.pathExists(file))) {
39
- throw new Error(`You specified ${type} in your env, but the file "${file}" can't be found.`);
88
+ function ensureCertKeyBothProvided(cert, key) {
89
+ if ((cert || key) && !(cert && key)) {
90
+ const fileProvided = (cert ?? key);
91
+ throw new Error(logger_1.default.interpolate `HTTPS support require proving a certificate and key at the same time.
92
+ You only provided a ${cert ? 'certificate' : 'key'} (with ${fileProvided.source}) at path path=${fileProvided.path}.`);
40
93
  }
41
- return fs_extra_1.default.readFile(file);
42
94
  }
43
95
  // Get the https config
44
- // Return cert files if provided in env, otherwise just true or false
45
- async function getHttpsConfig() {
46
- const appDirectory = await fs_extra_1.default.realpath(process.cwd());
47
- const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
48
- const isHttps = HTTPS === 'true';
49
- if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
50
- const crtFile = path_1.default.resolve(appDirectory, SSL_CRT_FILE);
51
- const keyFile = path_1.default.resolve(appDirectory, SSL_KEY_FILE);
52
- const config = {
53
- cert: await readEnvFile(crtFile, 'SSL_CRT_FILE'),
54
- key: await readEnvFile(keyFile, 'SSL_KEY_FILE'),
96
+ // Return cert files if provided via CLI or env, otherwise just true or false.
97
+ // CLI options take precedence over env vars.
98
+ async function getHttpsConfig(options = {}) {
99
+ const cwd = await fs_extra_1.default.realpath(process.cwd());
100
+ const [cert, key] = await Promise.all([
101
+ getCert(options, cwd),
102
+ getKeyFile(options, cwd),
103
+ ]);
104
+ // Providing both cert/key implies HTTPS
105
+ const inferredHttps = !!(cert && key);
106
+ const https = getExplicitHttps(options) ?? inferredHttps;
107
+ if (https && cert && key) {
108
+ validateKeyAndCerts({
109
+ cert,
110
+ key,
111
+ });
112
+ return {
113
+ cert: cert.content,
114
+ key: key.content,
55
115
  };
56
- validateKeyAndCerts({ ...config, keyFile, crtFile });
57
- return config;
58
116
  }
59
- return isHttps;
117
+ ensureCertKeyBothProvided(cert, key);
118
+ // Apparently we can have https without cert/key (historical)
119
+ // although I don't know how this works 🤷‍♂️
120
+ return https;
60
121
  }