@docusaurus/core 3.7.0 → 3.8.0

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 (47) hide show
  1. package/bin/beforeCli.mjs +3 -3
  2. package/lib/client/App.js +7 -4
  3. package/lib/client/serverEntry.js +10 -4
  4. package/lib/client/serverHelmetUtils.d.ts +11 -0
  5. package/lib/client/serverHelmetUtils.js +39 -0
  6. package/lib/client/theme-fallback/ThemeProvider/index.d.ts +9 -0
  7. package/lib/client/theme-fallback/ThemeProvider/index.js +17 -0
  8. package/lib/commands/build/buildLocale.js +33 -10
  9. package/lib/commands/clear.js +4 -3
  10. package/lib/commands/deploy.js +70 -28
  11. package/lib/commands/serve.js +2 -2
  12. package/lib/commands/start/start.js +7 -4
  13. package/lib/commands/start/utils.js +17 -3
  14. package/lib/commands/start/webpack.js +1 -1
  15. package/lib/commands/utils/clearPath.d.ts +10 -0
  16. package/lib/commands/utils/clearPath.js +21 -0
  17. package/lib/commands/utils/legacy/evalSourceMapMiddleware.d.ts +2 -0
  18. package/lib/commands/utils/legacy/evalSourceMapMiddleware.js +57 -0
  19. package/lib/commands/utils/openBrowser/openBrowser.d.ts +10 -0
  20. package/lib/commands/utils/openBrowser/openBrowser.js +124 -0
  21. package/lib/commands/utils/openBrowser/openChrome.applescript +94 -0
  22. package/lib/server/configValidation.d.ts +3 -1
  23. package/lib/server/configValidation.js +45 -4
  24. package/lib/ssg/ssgEnv.d.ts +10 -0
  25. package/lib/ssg/ssgEnv.js +38 -0
  26. package/lib/ssg/ssgExecutor.js +121 -8
  27. package/lib/ssg/ssgGlobalResult.d.ts +12 -0
  28. package/lib/ssg/ssgGlobalResult.js +74 -0
  29. package/lib/ssg/ssgParams.d.ts +1 -0
  30. package/lib/ssg/ssgParams.js +1 -0
  31. package/lib/ssg/ssgRenderer.d.ts +29 -0
  32. package/lib/ssg/{ssg.js → ssgRenderer.js} +55 -90
  33. package/lib/ssg/ssgTemplate.js +6 -7
  34. package/lib/ssg/ssgUtils.d.ts +0 -1
  35. package/lib/ssg/ssgUtils.js +0 -9
  36. package/lib/ssg/ssgWorkerInline.d.ts +12 -0
  37. package/lib/ssg/ssgWorkerInline.js +17 -0
  38. package/lib/ssg/ssgWorkerThread.d.ts +13 -0
  39. package/lib/ssg/ssgWorkerThread.js +36 -0
  40. package/lib/webpack/base.js +76 -28
  41. package/lib/webpack/client.js +0 -3
  42. package/lib/webpack/plugins/BundlerCPUProfilerPlugin.d.ts +12 -0
  43. package/lib/webpack/plugins/BundlerCPUProfilerPlugin.js +41 -0
  44. package/package.json +14 -14
  45. package/lib/ssg/ssg.d.ts +0 -21
  46. package/lib/webpack/plugins/CleanWebpackPlugin.d.ts +0 -59
  47. package/lib/webpack/plugins/CleanWebpackPlugin.js +0 -177
package/bin/beforeCli.mjs CHANGED
@@ -10,7 +10,7 @@
10
10
  import fs from 'fs-extra';
11
11
  import path from 'path';
12
12
  import {createRequire} from 'module';
13
- import shell from 'shelljs';
13
+ import execa from 'execa';
14
14
  import {logger} from '@docusaurus/logger';
15
15
  import semver from 'semver';
16
16
  import updateNotifier from 'update-notifier';
@@ -111,8 +111,8 @@ export default async function beforeCli() {
111
111
  return undefined;
112
112
  }
113
113
 
114
- const yarnVersionResult = shell.exec('yarn --version', {silent: true});
115
- if (yarnVersionResult?.code === 0) {
114
+ const yarnVersionResult = await execa.command('yarn --version');
115
+ if (yarnVersionResult.exitCode === 0) {
116
116
  const majorVersion = parseInt(
117
117
  yarnVersionResult.stdout?.trim().split('.')[0] ?? '',
118
118
  10,
package/lib/client/App.js CHANGED
@@ -10,6 +10,7 @@ import routes from '@generated/routes';
10
10
  import { useLocation } from '@docusaurus/router';
11
11
  import renderRoutes from '@docusaurus/renderRoutes';
12
12
  import Root from '@theme/Root';
13
+ import ThemeProvider from '@theme/ThemeProvider';
13
14
  import SiteMetadata from '@theme/SiteMetadata';
14
15
  import normalizeLocation from './normalizeLocation';
15
16
  import { BrowserContextProvider } from './browserContext';
@@ -34,10 +35,12 @@ export default function App() {
34
35
  <DocusaurusContextProvider>
35
36
  <BrowserContextProvider>
36
37
  <Root>
37
- <SiteMetadataDefaults />
38
- <SiteMetadata />
39
- <BaseUrlIssueBanner />
40
- <AppNavigation />
38
+ <ThemeProvider>
39
+ <SiteMetadataDefaults />
40
+ <SiteMetadata />
41
+ <BaseUrlIssueBanner />
42
+ <AppNavigation />
43
+ </ThemeProvider>
41
44
  </Root>
42
45
  <HasHydratedDataAttribute />
43
46
  </BrowserContextProvider>
@@ -12,7 +12,8 @@ import { renderToHtml } from './renderToHtml';
12
12
  import preload from './preload';
13
13
  import App from './App';
14
14
  import { createStatefulBrokenLinks, BrokenLinksProvider, } from './BrokenLinksContext';
15
- const render = async ({ pathname }) => {
15
+ import { toPageCollectedMetadataInternal } from './serverHelmetUtils';
16
+ const render = async ({ pathname, v4RemoveLegacyPostBuildHeadAttribute, }) => {
16
17
  await preload(pathname);
17
18
  const modules = new Set();
18
19
  const routerContext = {};
@@ -30,10 +31,15 @@ const render = async ({ pathname }) => {
30
31
  </HelmetProvider>
31
32
  </Loadable.Capture>);
32
33
  const html = await renderToHtml(app);
34
+ const { helmet } = helmetContext;
35
+ const metadata = toPageCollectedMetadataInternal({ helmet });
36
+ // TODO Docusaurus v4 remove with deprecated postBuild({head}) API
37
+ // the returned collectedData must be serializable to run in workers
38
+ if (v4RemoveLegacyPostBuildHeadAttribute) {
39
+ metadata.helmet = null;
40
+ }
33
41
  const collectedData = {
34
- // TODO Docusaurus v4 refactor: helmet state is non-serializable
35
- // this makes it impossible to run SSG in a worker thread
36
- helmet: helmetContext.helmet,
42
+ metadata,
37
43
  anchors: statefulBrokenLinks.getCollectedAnchors(),
38
44
  links: statefulBrokenLinks.getCollectedLinks(),
39
45
  modules: Array.from(modules),
@@ -0,0 +1,11 @@
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
+ import type { PageCollectedMetadataInternal } from '../common';
8
+ import type { HelmetServerState } from 'react-helmet-async';
9
+ export declare function toPageCollectedMetadataInternal({ helmet, }: {
10
+ helmet: HelmetServerState;
11
+ }): PageCollectedMetadataInternal;
@@ -0,0 +1,39 @@
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
+ function getBuildMetaTags(helmet) {
8
+ // @ts-expect-error: see https://github.com/staylor/react-helmet-async/pull/167
9
+ const metaElements = helmet.meta.toComponent() ?? [];
10
+ return metaElements.map((el) => el.props);
11
+ }
12
+ function isNoIndexTag(tag) {
13
+ if (!tag.name || !tag.content) {
14
+ return false;
15
+ }
16
+ return (
17
+ // meta name is not case-sensitive
18
+ tag.name.toLowerCase() === 'robots' &&
19
+ // Robots directives are not case-sensitive
20
+ tag.content.toLowerCase().includes('noindex'));
21
+ }
22
+ export function toPageCollectedMetadataInternal({ helmet, }) {
23
+ const tags = getBuildMetaTags(helmet);
24
+ const noIndex = tags.some(isNoIndexTag);
25
+ return {
26
+ helmet, // TODO Docusaurus v4 remove
27
+ public: {
28
+ noIndex,
29
+ },
30
+ internal: {
31
+ htmlAttributes: helmet.htmlAttributes.toString(),
32
+ bodyAttributes: helmet.bodyAttributes.toString(),
33
+ title: helmet.title.toString(),
34
+ meta: helmet.meta.toString(),
35
+ link: helmet.link.toString(),
36
+ script: helmet.script.toString(),
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,9 @@
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
+ import { type ReactNode } from 'react';
8
+ import type { Props } from '@theme/ThemeProvider';
9
+ export default function ThemeProvider({ children }: Props): ReactNode;
@@ -0,0 +1,17 @@
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
+ import React from 'react';
8
+ // Wrapper component expected to be implemented by a theme
9
+ // Unlike <Layout>, it applies to all sites routes and never unmounts
10
+ //
11
+ // Unlike <Root>, the theme is expected to provide an implementation
12
+ // <Root> is empty and the implementation is expected to be provided by the user
13
+ //
14
+ // Tree order: Root > ThemeProvider > Layout
15
+ export default function ThemeProvider({ children }) {
16
+ return <>{children}</>;
17
+ }
@@ -19,6 +19,9 @@ const client_1 = require("../../webpack/client");
19
19
  const server_1 = tslib_1.__importDefault(require("../../webpack/server"));
20
20
  const configure_1 = require("../../webpack/configure");
21
21
  const ssgExecutor_1 = require("../../ssg/ssgExecutor");
22
+ const clearPath_1 = tslib_1.__importDefault(require("../utils/clearPath"));
23
+ const SkipBundling = process.env.DOCUSAURUS_SKIP_BUNDLING === 'true';
24
+ const ExitAfterBundling = process.env.DOCUSAURUS_EXIT_AFTER_BUNDLING === 'true';
22
25
  async function buildLocale({ siteDir, locale, cliOptions, }) {
23
26
  // Temporary workaround to unlock the ability to translate the site config
24
27
  // We'll remove it if a better official API can be designed
@@ -47,16 +50,32 @@ async function buildLocale({ siteDir, locale, cliOptions, }) {
47
50
  props,
48
51
  configureWebpackUtils,
49
52
  }),
53
+ // We also clear website/build dir
54
+ // returns void, no useful result needed before compilation
55
+ // See also https://github.com/facebook/docusaurus/pull/11037
56
+ SkipBundling ? undefined : (0, clearPath_1.default)(outDir),
50
57
  ]));
51
- // Run webpack to build JS bundle (client) and static html files (server).
52
- await logger_1.PerfLogger.async(`Bundling with ${props.currentBundler.name}`, () => {
53
- return (0, bundler_1.compile)({
54
- configs:
55
- // For hash router we don't do SSG and can skip the server bundle
56
- router === 'hash' ? [clientConfig] : [clientConfig, serverConfig],
57
- currentBundler: configureWebpackUtils.currentBundler,
58
+ if (SkipBundling) {
59
+ console.warn(`Skipping the Docusaurus bundling step because DOCUSAURUS_SKIP_BUNDLING='true'`);
60
+ }
61
+ else {
62
+ const cleanupBundlerTracing = await (0, bundler_1.registerBundlerTracing)({
63
+ currentBundler: props.currentBundler,
58
64
  });
59
- });
65
+ // Run webpack to build JS bundle (client) and static html files (server).
66
+ await logger_1.PerfLogger.async(`Bundling with ${props.currentBundler.name}`, () => {
67
+ return (0, bundler_1.compile)({
68
+ configs:
69
+ // For hash router we don't do SSG and can skip the server bundle
70
+ router === 'hash' ? [clientConfig] : [clientConfig, serverConfig],
71
+ currentBundler: configureWebpackUtils.currentBundler,
72
+ });
73
+ });
74
+ await cleanupBundlerTracing();
75
+ }
76
+ if (ExitAfterBundling) {
77
+ return process.exit(0);
78
+ }
60
79
  const { collectedData } = await logger_1.PerfLogger.async('SSG', () => (0, ssgExecutor_1.executeSSG)({
61
80
  props,
62
81
  serverBundlePath,
@@ -71,7 +90,10 @@ async function buildLocale({ siteDir, locale, cliOptions, }) {
71
90
  logger_1.default.success `Generated static files in path=${path_1.default.relative(process.cwd(), outDir)}.`;
72
91
  }
73
92
  async function executePluginsPostBuild({ plugins, props, collectedData, }) {
74
- const head = lodash_1.default.mapValues(collectedData, (d) => d.helmet);
93
+ const head = props.siteConfig.future.v4.removeLegacyPostBuildHeadAttribute
94
+ ? {}
95
+ : lodash_1.default.mapValues(collectedData, (d) => d.metadata.helmet);
96
+ const routesBuildMetadata = lodash_1.default.mapValues(collectedData, (d) => d.metadata.public);
75
97
  await Promise.all(plugins.map(async (plugin) => {
76
98
  if (!plugin.postBuild) {
77
99
  return;
@@ -79,6 +101,7 @@ async function executePluginsPostBuild({ plugins, props, collectedData, }) {
79
101
  await plugin.postBuild({
80
102
  ...props,
81
103
  head,
104
+ routesBuildMetadata,
82
105
  content: plugin.content,
83
106
  });
84
107
  }));
@@ -131,7 +154,7 @@ async function getBuildServerConfig({ props, configureWebpackUtils, }) {
131
154
  // Remove /build/server server.bundle.js because it is not needed.
132
155
  async function cleanupServerBundle(serverBundlePath) {
133
156
  if (process.env.DOCUSAURUS_KEEP_SERVER_BUNDLE === 'true') {
134
- logger_1.default.warn("Will NOT delete server bundle because DOCUSAURUS_KEEP_SERVER_BUNDLE is set to 'true'");
157
+ logger_1.default.warn("Will NOT delete server bundle because DOCUSAURUS_KEEP_SERVER_BUNDLE='true'");
135
158
  }
136
159
  else {
137
160
  await logger_1.PerfLogger.async('Deleting server bundle', async () => {
@@ -12,13 +12,14 @@ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
12
12
  const path_1 = tslib_1.__importDefault(require("path"));
13
13
  const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
14
14
  const utils_1 = require("@docusaurus/utils");
15
+ const clearPath_1 = tslib_1.__importDefault(require("./utils/clearPath"));
15
16
  async function removePath(entry) {
16
17
  if (!(await fs_extra_1.default.pathExists(entry.path))) {
17
18
  return;
18
19
  }
19
20
  try {
20
- await fs_extra_1.default.remove(entry.path);
21
- logger_1.default.success `Removed the ${entry.description} at path=${entry.path}.`;
21
+ await (0, clearPath_1.default)(entry.path);
22
+ logger_1.default.success `Removed the ${entry.description} at path=${path_1.default.relative(process.cwd(), entry.path)}.`;
22
23
  }
23
24
  catch (err) {
24
25
  logger_1.default.error `Could not remove the ${entry.description} at path=${entry.path}.`;
@@ -38,7 +39,7 @@ async function clear(siteDirParam = '.') {
38
39
  // In Yarn PnP, cache is stored in `.yarn/.cache` because n_m doesn't exist
39
40
  const cacheFolders = ['node_modules', '.yarn'].map((p) => ({
40
41
  path: path_1.default.join(siteDir, p, '.cache'),
41
- description: 'Webpack persistent cache folder',
42
+ description: 'bundler persistent cache folder',
42
43
  }));
43
44
  await Promise.all([generatedFolder, buildFolder, ...cacheFolders].map(removePath));
44
45
  }
@@ -12,7 +12,7 @@ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
12
12
  const path_1 = tslib_1.__importDefault(require("path"));
13
13
  const os_1 = tslib_1.__importDefault(require("os"));
14
14
  const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
15
- const shelljs_1 = tslib_1.__importDefault(require("shelljs"));
15
+ const execa_1 = tslib_1.__importDefault(require("execa"));
16
16
  const utils_1 = require("@docusaurus/utils");
17
17
  const site_1 = require("../server/site");
18
18
  const build_1 = require("./build/build");
@@ -21,19 +21,41 @@ function obfuscateGitPass(str) {
21
21
  const gitPass = process.env.GIT_PASS;
22
22
  return gitPass ? str.replace(gitPass, 'GIT_PASS') : str;
23
23
  }
24
+ const debugMode = !!process.env.DOCUSAURUS_DEPLOY_DEBUG;
24
25
  // Log executed commands so that user can figure out mistakes on his own
25
26
  // for example: https://github.com/facebook/docusaurus/issues/3875
26
- function shellExecLog(cmd) {
27
+ function exec(cmd, options) {
28
+ const log = options?.log ?? true;
29
+ const failfast = options?.failfast ?? false;
27
30
  try {
28
- const result = shelljs_1.default.exec(cmd);
29
- logger_1.default.info `code=${obfuscateGitPass(cmd)} subdue=${`code: ${result.code}`}`;
31
+ // TODO migrate to execa(file,[...args]) instead
32
+ // Use async/await everything
33
+ // Avoid execa.command: the args need to be escaped manually
34
+ const result = execa_1.default.commandSync(cmd);
35
+ if (log || debugMode) {
36
+ logger_1.default.info `code=${obfuscateGitPass(cmd)} subdue=${`code: ${result.exitCode}`}`;
37
+ }
38
+ if (debugMode) {
39
+ console.log(result);
40
+ }
41
+ if (failfast && result.exitCode !== 0) {
42
+ throw new Error(`Command returned unexpected exitCode ${result.exitCode}`);
43
+ }
30
44
  return result;
31
45
  }
32
46
  catch (err) {
33
- logger_1.default.error `code=${obfuscateGitPass(cmd)}`;
34
- throw err;
47
+ throw new Error(logger_1.default.interpolate `Error while executing command code=${obfuscateGitPass(cmd)}
48
+ In CWD code=${process.cwd()}`, { cause: err });
35
49
  }
36
50
  }
51
+ // Execa escape args and add necessary quotes automatically
52
+ // When using Execa.command, the args containing spaces must be escaped manually
53
+ function escapeArg(arg) {
54
+ return arg.replaceAll(' ', '\\ ');
55
+ }
56
+ function hasGit() {
57
+ return exec('git --version').exitCode === 0;
58
+ }
37
59
  async function deploy(siteDirParam = '.', cliOptions = {}) {
38
60
  const siteDir = await fs_extra_1.default.realpath(siteDirParam);
39
61
  const { outDir, siteConfig, siteConfigPath } = await (0, site_1.loadContext)({
@@ -48,16 +70,23 @@ This behavior can have SEO impacts and create relative link issues.
48
70
  `);
49
71
  }
50
72
  logger_1.default.info('Deploy command invoked...');
51
- if (!shelljs_1.default.which('git')) {
52
- throw new Error('Git not installed or on the PATH!');
73
+ if (!hasGit()) {
74
+ throw new Error('Git not installed or not added to PATH!');
53
75
  }
54
76
  // Source repo is the repo from where the command is invoked
55
- const sourceRepoUrl = shelljs_1.default
56
- .exec('git remote get-url origin', { silent: true })
57
- .stdout.trim();
77
+ const { stdout } = exec('git remote get-url origin', {
78
+ log: false,
79
+ failfast: true,
80
+ });
81
+ const sourceRepoUrl = stdout.trim();
58
82
  // The source branch; defaults to the currently checked out branch
59
83
  const sourceBranch = process.env.CURRENT_BRANCH ??
60
- shelljs_1.default.exec('git rev-parse --abbrev-ref HEAD', { silent: true }).stdout.trim();
84
+ exec('git rev-parse --abbrev-ref HEAD', {
85
+ log: false,
86
+ failfast: true,
87
+ })
88
+ ?.stdout?.toString()
89
+ .trim();
61
90
  const gitUser = process.env.GIT_USER;
62
91
  let useSSH = process.env.USE_SSH !== undefined &&
63
92
  process.env.USE_SSH.toLowerCase() === 'true';
@@ -87,8 +116,11 @@ This behavior can have SEO impacts and create relative link issues.
87
116
  // We never deploy on pull request.
88
117
  const isPullRequest = process.env.CI_PULL_REQUEST ?? process.env.CIRCLE_PULL_REQUEST;
89
118
  if (isPullRequest) {
90
- shelljs_1.default.echo('Skipping deploy on a pull request.');
91
- shelljs_1.default.exit(0);
119
+ exec('echo "Skipping deploy on a pull request."', {
120
+ log: false,
121
+ failfast: true,
122
+ });
123
+ process.exit(0);
92
124
  }
93
125
  // github.io indicates organization repos that deploy via default branch. All
94
126
  // others use gh-pages (either case can be configured actually, but we can
@@ -126,21 +158,24 @@ You can also set the deploymentBranch property in docusaurus.config.js .`);
126
158
  }
127
159
  // Save the commit hash that triggers publish-gh-pages before checking
128
160
  // out to deployment branch.
129
- const currentCommit = shellExecLog('git rev-parse HEAD').stdout.trim();
161
+ const currentCommit = exec('git rev-parse HEAD')?.stdout?.toString().trim();
130
162
  const runDeploy = async (outputDirectory) => {
131
163
  const targetDirectory = cliOptions.targetDir ?? '.';
132
164
  const fromPath = outputDirectory;
133
165
  const toPath = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), `${projectName}-${deploymentBranch}`));
134
- shelljs_1.default.cd(toPath);
166
+ process.chdir(toPath);
135
167
  // Clones the repo into the temp folder and checks out the target branch.
136
168
  // If the branch doesn't exist, it creates a new one based on the
137
169
  // repository default branch.
138
- if (shellExecLog(`git clone --depth 1 --branch ${deploymentBranch} ${deploymentRepoURL} "${toPath}"`).code !== 0) {
139
- shellExecLog(`git clone --depth 1 ${deploymentRepoURL} "${toPath}"`);
140
- shellExecLog(`git checkout -b ${deploymentBranch}`);
170
+ if (exec(`git clone --depth 1 --branch ${deploymentBranch} ${deploymentRepoURL} ${escapeArg(toPath)}`).exitCode !== 0) {
171
+ exec(`git clone --depth 1 ${deploymentRepoURL} ${escapeArg(toPath)}`);
172
+ exec(`git checkout -b ${deploymentBranch}`);
141
173
  }
142
174
  // Clear out any existing contents in the target directory
143
- shellExecLog(`git rm -rf ${targetDirectory}`);
175
+ exec(`git rm -rf ${escapeArg(targetDirectory)}`, {
176
+ log: false,
177
+ failfast: true,
178
+ });
144
179
  const targetPath = path_1.default.join(toPath, targetDirectory);
145
180
  try {
146
181
  await fs_extra_1.default.copy(fromPath, targetPath);
@@ -149,22 +184,24 @@ You can also set the deploymentBranch property in docusaurus.config.js .`);
149
184
  logger_1.default.error `Copying build assets from path=${fromPath} to path=${targetPath} failed.`;
150
185
  throw err;
151
186
  }
152
- shellExecLog('git add --all');
187
+ exec('git add --all', { failfast: true });
153
188
  const gitUserName = process.env.GIT_USER_NAME;
154
189
  if (gitUserName) {
155
- shellExecLog(`git config user.name "${gitUserName}"`);
190
+ exec(`git config user.name ${escapeArg(gitUserName)}`, { failfast: true });
156
191
  }
157
192
  const gitUserEmail = process.env.GIT_USER_EMAIL;
158
193
  if (gitUserEmail) {
159
- shellExecLog(`git config user.email "${gitUserEmail}"`);
194
+ exec(`git config user.email ${escapeArg(gitUserEmail)}`, {
195
+ failfast: true,
196
+ });
160
197
  }
161
198
  const commitMessage = process.env.CUSTOM_COMMIT_MESSAGE ??
162
199
  `Deploy website - based on ${currentCommit}`;
163
- const commitResults = shellExecLog(`git commit -m "${commitMessage}"`);
164
- if (shellExecLog(`git push --force origin ${deploymentBranch}`).code !== 0) {
200
+ const commitResults = exec(`git commit -m ${escapeArg(commitMessage)} --allow-empty`);
201
+ if (exec(`git push --force origin ${deploymentBranch}`).exitCode !== 0) {
165
202
  throw new Error('Running "git push" command failed. Does the GitHub user account you are using have push access to the repository?');
166
203
  }
167
- else if (commitResults.code === 0) {
204
+ else if (commitResults.exitCode === 0) {
168
205
  // The commit might return a non-zero value when site is up to date.
169
206
  let websiteURL = '';
170
207
  if (githubHost === 'github.com') {
@@ -176,8 +213,13 @@ You can also set the deploymentBranch property in docusaurus.config.js .`);
176
213
  // GitHub enterprise hosting.
177
214
  websiteURL = `https://${githubHost}/pages/${organizationName}/${projectName}/`;
178
215
  }
179
- shelljs_1.default.echo(`Website is live at "${websiteURL}".`);
180
- shelljs_1.default.exit(0);
216
+ try {
217
+ exec(`echo "Website is live at ${websiteURL}."`, { failfast: true });
218
+ process.exit(0);
219
+ }
220
+ catch (err) {
221
+ throw new Error(`Failed to execute command: ${err}`);
222
+ }
181
223
  }
182
224
  };
183
225
  if (!cliOptions.skipBuild) {
@@ -14,8 +14,8 @@ const path_1 = tslib_1.__importDefault(require("path"));
14
14
  const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
15
15
  const utils_1 = require("@docusaurus/utils");
16
16
  const serve_handler_1 = tslib_1.__importDefault(require("serve-handler"));
17
- const openBrowser_1 = tslib_1.__importDefault(require("react-dev-utils/openBrowser"));
18
17
  const utils_common_1 = require("@docusaurus/utils-common");
18
+ const openBrowser_1 = tslib_1.__importDefault(require("./utils/openBrowser/openBrowser"));
19
19
  const config_1 = require("../server/config");
20
20
  const build_1 = require("./build/build");
21
21
  const getHostPort_1 = require("../server/getHostPort");
@@ -86,6 +86,6 @@ async function serve(siteDirParam = '.', cliOptions = {}) {
86
86
  logger_1.default.success `Serving path=${buildDir} directory at: url=${url}`;
87
87
  server.listen(port);
88
88
  if (cliOptions.open && !process.env.CI) {
89
- (0, openBrowser_1.default)(url);
89
+ await (0, openBrowser_1.default)(url);
90
90
  }
91
91
  }
@@ -8,12 +8,12 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.start = start;
10
10
  const tslib_1 = require("tslib");
11
- const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
12
- const openBrowser_1 = tslib_1.__importDefault(require("react-dev-utils/openBrowser"));
11
+ const logger_1 = tslib_1.__importStar(require("@docusaurus/logger"));
12
+ const openBrowser_1 = tslib_1.__importDefault(require("../utils/openBrowser/openBrowser"));
13
13
  const watcher_1 = require("./watcher");
14
14
  const webpack_1 = require("./webpack");
15
15
  const utils_1 = require("./utils");
16
- async function start(siteDirParam = '.', cliOptions = {}) {
16
+ async function doStart(siteDirParam = '.', cliOptions = {}) {
17
17
  logger_1.default.info('Starting the development server...');
18
18
  // Temporary workaround to unlock the ability to translate the site config
19
19
  // We'll remove it if a better official API can be designed
@@ -41,6 +41,9 @@ async function start(siteDirParam = '.', cliOptions = {}) {
41
41
  });
42
42
  await devServer.start();
43
43
  if (cliOptions.open) {
44
- (0, openBrowser_1.default)(reloadableSite.getOpenUrl());
44
+ await (0, openBrowser_1.default)(reloadableSite.getOpenUrl());
45
45
  }
46
46
  }
47
+ async function start(siteDirParam = '.', cliOptions = {}) {
48
+ return logger_1.PerfLogger.async('CLI start', () => doStart(siteDirParam, cliOptions));
49
+ }
@@ -10,13 +10,28 @@ exports.createOpenUrlContext = createOpenUrlContext;
10
10
  exports.createReloadableSite = createReloadableSite;
11
11
  const tslib_1 = require("tslib");
12
12
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
13
+ const url_1 = tslib_1.__importDefault(require("url"));
13
14
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
14
- const WebpackDevServerUtils_1 = require("react-dev-utils/WebpackDevServerUtils");
15
15
  const utils_1 = require("@docusaurus/utils");
16
16
  const logger_1 = tslib_1.__importStar(require("@docusaurus/logger"));
17
17
  const getHostPort_1 = require("../../server/getHostPort");
18
18
  const site_1 = require("../../server/site");
19
19
  const pluginsUtils_1 = require("../../server/plugins/pluginsUtils");
20
+ // This code was historically in CRA/react-dev-utils (deprecated in 2025)
21
+ // We internalized it, refactored and removed useless code paths
22
+ // See https://github.com/facebook/docusaurus/pull/10956
23
+ // See https://github.com/facebook/create-react-app/blob/main/packages/react-dev-utils/WebpackDevServerUtils.js
24
+ function getOpenUrlOrigin(protocol, host, port) {
25
+ const isUnspecifiedHost = host === '0.0.0.0' || host === '::';
26
+ const prettyHost = isUnspecifiedHost ? 'localhost' : host;
27
+ const localUrlForBrowser = url_1.default.format({
28
+ protocol,
29
+ hostname: prettyHost,
30
+ port,
31
+ pathname: '/',
32
+ });
33
+ return localUrlForBrowser;
34
+ }
20
35
  async function createOpenUrlContext({ cliOptions, }) {
21
36
  const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
22
37
  const { host, port } = await (0, getHostPort_1.getHostPort)(cliOptions);
@@ -24,9 +39,8 @@ async function createOpenUrlContext({ cliOptions, }) {
24
39
  return process.exit();
25
40
  }
26
41
  const getOpenUrl = ({ baseUrl, router }) => {
27
- const urls = (0, WebpackDevServerUtils_1.prepareUrls)(protocol, host, port);
28
42
  return (0, utils_1.normalizeUrl)([
29
- urls.localUrlForBrowser,
43
+ getOpenUrlOrigin(protocol, host, port),
30
44
  router === 'hash' ? '/#/' : '',
31
45
  baseUrl,
32
46
  ]);
@@ -13,7 +13,7 @@ const webpack_merge_1 = tslib_1.__importDefault(require("webpack-merge"));
13
13
  const bundler_1 = require("@docusaurus/bundler");
14
14
  const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
15
15
  const webpack_dev_server_1 = tslib_1.__importDefault(require("webpack-dev-server"));
16
- const evalSourceMapMiddleware_1 = tslib_1.__importDefault(require("react-dev-utils/evalSourceMapMiddleware"));
16
+ const evalSourceMapMiddleware_1 = tslib_1.__importDefault(require("../utils/legacy/evalSourceMapMiddleware"));
17
17
  const watcher_1 = require("./watcher");
18
18
  const getHttpsConfig_1 = tslib_1.__importDefault(require("../../webpack/utils/getHttpsConfig"));
19
19
  const configure_1 = require("../../webpack/configure");
@@ -0,0 +1,10 @@
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
+ * @param pathToClear
9
+ */
10
+ export default function clearPath(pathToClear: string): Promise<void>;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.default = clearPath;
10
+ const tslib_1 = require("tslib");
11
+ const path_1 = tslib_1.__importDefault(require("path"));
12
+ const promises_1 = require("fs/promises");
13
+ const logger_1 = require("@docusaurus/logger");
14
+ /**
15
+ * @param pathToClear
16
+ */
17
+ async function clearPath(pathToClear) {
18
+ return logger_1.PerfLogger.async(`clearPath ${path_1.default.relative(process.cwd(), pathToClear)}`, async () => {
19
+ await (0, promises_1.rm)(pathToClear, { recursive: true, force: true });
20
+ });
21
+ }
@@ -0,0 +1,2 @@
1
+ declare function _exports(server: import("webpack-dev-server").default): import("express").Handler;
2
+ export = _exports;
@@ -0,0 +1,57 @@
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
+ // TODO Legacy CRA react-dev-utils package code
9
+ // This code was in CRA/react-dev-utils (deprecated in 2025)
10
+ // We just copied the code as-is to remove a fat/useless dependency subtree
11
+ // See https://github.com/facebook/docusaurus/pull/10956
12
+ // See https://github.com/facebook/create-react-app/blob/main/packages/react-dev-utils/evalSourceMapMiddleware.js
13
+
14
+ /* eslint-disable */
15
+
16
+ function base64SourceMap(source) {
17
+ const base64 = Buffer.from(JSON.stringify(source.map()), 'utf8').toString(
18
+ 'base64',
19
+ );
20
+ return `data:application/json;charset=utf-8;base64,${base64}`;
21
+ }
22
+
23
+ function getSourceById(server, id) {
24
+ const module = Array.from(server._stats.compilation.modules).find(
25
+ (m) => server._stats.compilation.chunkGraph.getModuleId(m) == id,
26
+ );
27
+ return module.originalSource();
28
+ }
29
+
30
+ /**
31
+ * Middleware responsible for retrieving a generated source
32
+ * Receives a webpack internal url: "webpack-internal:///<module-id>"
33
+ * Returns a generated source: "<source-text><sourceMappingURL><sourceURL>"
34
+ *
35
+ * Based on EvalSourceMapDevToolModuleTemplatePlugin.js
36
+ *
37
+ * @param {import("webpack-dev-server").default} server
38
+ * @returns {import("express").Handler}
39
+ */
40
+ module.exports = function createEvalSourceMapMiddleware(server) {
41
+ return function handleWebpackInternalMiddleware(req, res, next) {
42
+ if (req.url.startsWith('/__get-internal-source')) {
43
+ const fileName = req.query.fileName;
44
+ const id = fileName.match(/webpack-internal:\/\/\/(.+)/)[1];
45
+ if (!id || !server._stats) {
46
+ next();
47
+ }
48
+
49
+ const source = getSourceById(server, id);
50
+ const sourceMapURL = `//# sourceMappingURL=${base64SourceMap(source)}`;
51
+ const sourceURL = `//# sourceURL=webpack-internal:///${module.id}`;
52
+ res.end(`${source.source()}\n${sourceMapURL}\n${sourceURL}`);
53
+ } else {
54
+ next();
55
+ }
56
+ };
57
+ };
@@ -0,0 +1,10 @@
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
+ * Returns true if it opened a browser
9
+ */
10
+ export default function openBrowser(url: string): Promise<boolean>;