From 6196cd530a71d8aa583796ee8fe13fc3563336d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Thu, 21 May 2026 12:39:18 +0200 Subject: [PATCH 1/6] chore(ci): Opt out of Yarn 4 security for our internal packages on e2e CI tests (#12046) --- .github/workflows/tests-e2e.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index c0f085e65477..e58ceb5eadaf 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -144,6 +144,7 @@ jobs: yarn config set npmRegistryServer http://localhost:4873 yarn config set unsafeHttpWhitelist --json '["localhost"]' yarn config set enableGlobalCache true + yarn config set npmPreapprovedPackages --json '["@docusaurus/*"]' # Make PnP as strict as possible # https://yarnpkg.com/features/pnp#fallback-mode From c94af61d07dcb43877d2ce92202a5c492b902a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Thu, 21 May 2026 13:19:13 +0200 Subject: [PATCH 2/6] chore(ci): Fix Node `ERR_INTERNAL_ASSERTION` error under Yarn PnP (#12048) --- packages/docusaurus/src/server/site.ts | 4 +-- .../docusaurus/src/server/siteMetadata.ts | 36 ++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/docusaurus/src/server/site.ts b/packages/docusaurus/src/server/site.ts index df511c264549..8ab24425684e 100644 --- a/packages/docusaurus/src/server/site.ts +++ b/packages/docusaurus/src/server/site.ts @@ -18,7 +18,7 @@ import {loadSiteConfig} from './config'; import {getAllClientModules} from './clientModules'; import {loadPlugins, reloadPlugin} from './plugins/plugins'; import {loadHtmlTags} from './htmlTags'; -import {createSiteMetadata, loadSiteVersion} from './siteMetadata'; +import {createSiteMetadata, tryLoadSitePackageJson} from './siteMetadata'; import {loadI18n} from './i18n'; import { loadSiteCodeTranslations, @@ -94,7 +94,7 @@ export async function loadContext( siteVersion, loadSiteConfig: {siteConfig: initialSiteConfig, siteConfigPath}, } = await combinePromises({ - siteVersion: loadSiteVersion(siteDir), + siteVersion: tryLoadSitePackageJson(siteDir).then((pkg) => pkg?.version), loadSiteConfig: loadSiteConfig({ siteDir, customConfigFilePath, diff --git a/packages/docusaurus/src/server/siteMetadata.ts b/packages/docusaurus/src/server/siteMetadata.ts index 18c96da3be52..ed50ab4c2eac 100644 --- a/packages/docusaurus/src/server/siteMetadata.ts +++ b/packages/docusaurus/src/server/siteMetadata.ts @@ -14,27 +14,30 @@ import type { SiteMetadata, } from '@docusaurus/types'; -async function loadPackageJsonVersion( +type PackageJson = { + name?: string; + version?: string; +}; + +async function tryLoadPackageJson( packageJsonPath: string, -): Promise { +): Promise { if (await fs.pathExists(packageJsonPath)) { - // eslint-disable-next-line @typescript-eslint/no-require-imports - return (require(packageJsonPath) as {version?: string}).version; + try { + return (await fs.readJSON(packageJsonPath)) as PackageJson; + } catch (error) { + throw new Error(`Couldn't load package.json file at ${packageJsonPath}`, { + cause: error, + }); + } } return undefined; } -async function loadPackageJsonName( - packageJsonPath: string, -): Promise { - // eslint-disable-next-line @typescript-eslint/no-require-imports - return (require(packageJsonPath) as {name?: string}).name; -} - -export async function loadSiteVersion( +export async function tryLoadSitePackageJson( siteDir: string, -): Promise { - return loadPackageJsonVersion(path.join(siteDir, 'package.json')); +): Promise { + return tryLoadPackageJson(path.join(siteDir, 'package.json')); } export async function loadPluginVersion( @@ -56,10 +59,11 @@ export async function loadPluginVersion( // as local plugin. return {type: 'project'}; } + const packageJson = await tryLoadPackageJson(packageJsonPath); return { type: 'package', - name: await loadPackageJsonName(packageJsonPath), - version: await loadPackageJsonVersion(packageJsonPath), + name: packageJson?.name, + version: packageJson?.version, }; } potentialPluginPackageJsonDirectory = path.dirname( From 35c81a1979b76487d04a0780ad942f09c89c3437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Thu, 21 May 2026 13:41:40 +0200 Subject: [PATCH 3/6] feat(core): upgrade Jiti to v2 (#12045) --- .syncpackrc.ts | 6 - .../src/sidebars/index.ts | 2 +- .../src/index.ts | 5 +- packages/docusaurus-utils/package.json | 2 +- .../__fixtures__/moduleUtils/user/user.cjs | 2 + .../src/__tests__/moduleUtils.test.ts | 252 +++++++++++++++--- packages/docusaurus-utils/src/moduleUtils.ts | 49 +++- packages/docusaurus/src/server/config.ts | 2 +- yarn.lock | 5 - 9 files changed, 253 insertions(+), 72 deletions(-) diff --git a/.syncpackrc.ts b/.syncpackrc.ts index a945b1b4db3d..c80893e549cd 100644 --- a/.syncpackrc.ts +++ b/.syncpackrc.ts @@ -30,12 +30,6 @@ export default { ], versionGroups: [ - // TODO temporary, need to upgrade jiti deps - { - dependencies: ['jiti'], - isIgnored: true, - }, - { label: 'Ignore * deps in type-alias packages', packages: [ diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts index 1566ec82175a..6af73d22ad0d 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts @@ -88,7 +88,7 @@ async function loadSidebarsFileUnsafe( } // We don't want sidebars to be cached because of hot reloading. - const module = await loadFreshModule(sidebarFilePath); + const module = await loadFreshModule(sidebarFilePath, {default: true}); // TODO unsafe, need to refactor and improve validation return module as SidebarsConfig; diff --git a/packages/docusaurus-remark-plugin-npm2yarn/src/index.ts b/packages/docusaurus-remark-plugin-npm2yarn/src/index.ts index c1c8863f8ed9..1a40f206916c 100644 --- a/packages/docusaurus-remark-plugin-npm2yarn/src/index.ts +++ b/packages/docusaurus-remark-plugin-npm2yarn/src/index.ts @@ -206,7 +206,4 @@ const plugin: Plugin<[PluginOptions?]> = (options = {}): Transformer => { }; }; -// To continue supporting `require('npm2yarn')` without the `.default` ㄟ(▔,▔)ㄏ -// TODO change to export default after migrating to ESM -// @ts-expect-error: Docusaurus v4: remove -export = plugin; +export default plugin; diff --git a/packages/docusaurus-utils/package.json b/packages/docusaurus-utils/package.json index 26c892c34d90..459c1bde3de3 100644 --- a/packages/docusaurus-utils/package.json +++ b/packages/docusaurus-utils/package.json @@ -28,7 +28,7 @@ "github-slugger": "^1.5.0", "globby": "^11.1.0", "gray-matter": "^4.0.3", - "jiti": "^1.20.0", + "jiti": "^2.7.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "micromatch": "^4.0.5", diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs index 394d1bb4ce56..46b387d95592 100644 --- a/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/moduleUtils/user/user.cjs @@ -1,7 +1,9 @@ exports.someNamedExport = 42; + module.exports = { firstName: 'Sebastien', lastName: 'Lorber', birthYear: 1986, }; + diff --git a/packages/docusaurus-utils/src/__tests__/moduleUtils.test.ts b/packages/docusaurus-utils/src/__tests__/moduleUtils.test.ts index 6d65346ac13e..901cc0510d88 100644 --- a/packages/docusaurus-utils/src/__tests__/moduleUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/moduleUtils.test.ts @@ -31,16 +31,33 @@ async function moduleGraphHelpers() { return { filePath, write: (content: string) => fs.outputFile(filePath, content), - load: () => loadFreshModule(filePath), + load: (withDefault: boolean) => loadModule(filePath, withDefault), }; } return {fileHelper}; } -async function loadFixtureModule(fixtureName: string) { - return loadFreshModule( +async function loadModule(modulePath: string, withDefault: boolean) { + const result = (await loadFreshModule( + modulePath, + withDefault ? {default: true} : undefined, + )) as object; + + // Because Jiti uses an internal proxy for interopDefault + // Spreading converts the proxy to an object compatible with test matchers + const obj = {...result}; + // console.log(modulePath, obj); + return obj; +} + +async function loadFixtureModule( + fixtureName: string, + withDefault: boolean, +): Promise<{default?: unknown; [key: string]: unknown}> { + return loadModule( path.resolve(__dirname, '__fixtures__/moduleUtils', fixtureName), + withDefault, ); } @@ -48,12 +65,27 @@ describe('loadFreshModule', () => { describe('can load CJS user module', () => { async function testUserFixture(fixtureName: string) { const userFixturePath = `user/${fixtureName}`; - const userModule = await loadFixtureModule(userFixturePath); - expect(userModule).toEqual({ + + await expect(loadFixtureModule(userFixturePath, true)).resolves.toEqual({ birthYear: 1986, firstName: 'Sebastien', lastName: 'Lorber', }); + + // Note: Jiti 2+ doesn't seem to behave exactly the same as Jiti 1 on our + // fancy CJS module fixtures mixing "module.exports" + exports.named + // I doubt this is a problem in practice, but we'll see + // Maybe we'll drop support for CJS config modules in the future? + // See https://github.com/facebook/docusaurus/pull/12045 + /* + await expect(loadFixtureModule(userFixturePath, false)).resolves.toEqual({ + default: { + birthYear: 1986, + firstName: 'Sebastien', + lastName: 'Lorber', + }, + }); + */ } it('for .cjs.js', async () => { @@ -72,11 +104,19 @@ describe('loadFreshModule', () => { describe('can load ESM user module', () => { async function testUserFixture(fixtureName: string) { const userFixturePath = `user/${fixtureName}`; - const userModule = await loadFixtureModule(userFixturePath); - expect(userModule).toEqual({ + + await expect(loadFixtureModule(userFixturePath, true)).resolves.toEqual({ birthYear: 1986, firstName: 'Sebastien', lastName: 'Lorber', + }); + + await expect(loadFixtureModule(userFixturePath, false)).resolves.toEqual({ + default: { + birthYear: 1986, + firstName: 'Sebastien', + lastName: 'Lorber', + }, someNamedExport: 42, }); } @@ -112,7 +152,9 @@ describe('loadFreshModule', () => { 'dependency2.ts', /* language=ts */ dedent` - export default {dep2Val: "dep2 val"} satisfies {dep2Val: string} + export const dep2Export = "dep2 val1"; + + export default {dep2Val: "dep2 val2"} satisfies {dep2Val: string} `, ); @@ -121,7 +163,7 @@ describe('loadFreshModule', () => { /* language=js */ dedent` import dependency1 from "./dependency1"; - import dependency2 from "./dependency2"; + import * as dependency2 from "./dependency2"; export default { someEntryValue: "entryVal", @@ -132,22 +174,54 @@ describe('loadFreshModule', () => { ); // Should be able to read the initial module graph - await expect(entryFile.load()).resolves.toEqual({ + await expect(entryFile.load(true)).resolves.toEqual({ someEntryValue: 'entryVal', dependency1: { - dep1Export: 'dep1 val1', dep1Val: 'dep1 val2', }, dependency2: { - dep2Val: 'dep2 val', + dep2Export: 'dep2 val1', + default: { + dep2Val: 'dep2 val2', + }, }, }); - await expect(dependency1.load()).resolves.toEqual({ - dep1Export: 'dep1 val1', + await expect(entryFile.load(false)).resolves.toEqual({ + default: { + someEntryValue: 'entryVal', + dependency1: { + // dep1Export: 'dep1 val1', // Expected: not using "* as" + dep1Val: 'dep1 val2', + }, + dependency2: { + dep2Export: 'dep2 val1', + + default: { + dep2Val: 'dep2 val2', + }, + }, + }, + }); + + await expect(dependency1.load(true)).resolves.toEqual({ dep1Val: 'dep1 val2', }); - await expect(dependency2.load()).resolves.toEqual({ - dep2Val: 'dep2 val', + + await expect(dependency1.load(false)).resolves.toEqual({ + dep1Export: 'dep1 val1', + default: { + dep1Val: 'dep1 val2', + }, + }); + + await expect(dependency2.load(true)).resolves.toEqual({ + dep2Val: 'dep2 val2', + }); + await expect(dependency2.load(false)).resolves.toEqual({ + dep2Export: 'dep2 val1', + default: { + dep2Val: 'dep2 val2', + }, }); // Should be able to read the module graph again after updates @@ -159,54 +233,121 @@ describe('loadFreshModule', () => { export default {dep1Val: "dep1 val2 updated"} `, ); - await expect(entryFile.load()).resolves.toEqual({ + + await expect(entryFile.load(true)).resolves.toEqual({ someEntryValue: 'entryVal', dependency1: { - dep1Export: 'dep1 val1 updated', + // dep1Export: 'dep1 val1 updated', // Expected: not using "* as" dep1Val: 'dep1 val2 updated', }, dependency2: { - dep2Val: 'dep2 val', + dep2Export: 'dep2 val1', + default: { + dep2Val: 'dep2 val2', + }, }, }); - await expect(dependency1.load()).resolves.toEqual({ - dep1Export: 'dep1 val1 updated', + await expect(entryFile.load(false)).resolves.toEqual({ + default: { + someEntryValue: 'entryVal', + dependency1: { + // dep1Export: 'dep1 val1 updated', // Expected: not using "* as" + dep1Val: 'dep1 val2 updated', + }, + dependency2: { + dep2Export: 'dep2 val1', + default: { + dep2Val: 'dep2 val2', + }, + }, + }, + }); + + await expect(dependency1.load(true)).resolves.toEqual({ dep1Val: 'dep1 val2 updated', }); - await expect(dependency2.load()).resolves.toEqual({ - dep2Val: 'dep2 val', + await expect(dependency1.load(false)).resolves.toEqual({ + dep1Export: 'dep1 val1 updated', + default: { + dep1Val: 'dep1 val2 updated', + }, + }); + + await expect(dependency2.load(true)).resolves.toEqual({ + dep2Val: 'dep2 val2', + }); + await expect(dependency2.load(false)).resolves.toEqual({ + dep2Export: 'dep2 val1', + default: { + dep2Val: 'dep2 val2', + }, }); // Should be able to read the module graph again after updates await dependency2.write( /* language=ts */ dedent` - export default {dep2Val: "dep2 val updated"} satisfies {dep2Val: string} + export const dep2Export = "dep2 val1 updated"; + + export default {dep2Val: "dep2 val2 updated"} satisfies {dep2Val: string} `, ); - await expect(entryFile.load()).resolves.toEqual({ + + await expect(entryFile.load(true)).resolves.toEqual({ someEntryValue: 'entryVal', dependency1: { - dep1Export: 'dep1 val1 updated', + // dep1Export: 'dep1 val1 updated', // Expected: not using "* as" dep1Val: 'dep1 val2 updated', }, dependency2: { - dep2Val: 'dep2 val updated', + dep2Export: 'dep2 val1 updated', + default: { + dep2Val: 'dep2 val2 updated', + }, }, }); - await expect(dependency1.load()).resolves.toEqual({ - dep1Export: 'dep1 val1 updated', + await expect(entryFile.load(false)).resolves.toEqual({ + default: { + someEntryValue: 'entryVal', + dependency1: { + // dep1Export: 'dep1 val1 updated', // Expected: not using "* as" + dep1Val: 'dep1 val2 updated', + }, + dependency2: { + dep2Export: 'dep2 val1 updated', + default: { + dep2Val: 'dep2 val2 updated', + }, + }, + }, + }); + + await expect(dependency1.load(true)).resolves.toEqual({ + // dep1Export: 'dep1 val1 updated', // Expected: not using "* as" dep1Val: 'dep1 val2 updated', }); - await expect(dependency2.load()).resolves.toEqual({ - dep2Val: 'dep2 val updated', + await expect(dependency1.load(false)).resolves.toEqual({ + dep1Export: 'dep1 val1 updated', + default: { + dep1Val: 'dep1 val2 updated', + }, }); - // Should be able to read the module graph again after updates + await expect(dependency2.load(true)).resolves.toEqual({ + dep2Val: 'dep2 val2 updated', + }); + await expect(dependency2.load(false)).resolves.toEqual({ + dep2Export: 'dep2 val1 updated', + default: { + dep2Val: 'dep2 val2 updated', + }, + }); + + // Should be able to read the module graph again after entry updates await entryFile.write( /* language=js */ dedent` - import dependency1 from "./dependency1"; + import * as dependency1 from "./dependency1"; import dependency2 from "./dependency2"; export default { @@ -217,23 +358,54 @@ describe('loadFreshModule', () => { } `, ); - await expect(entryFile.load()).resolves.toEqual({ + + await expect(entryFile.load(true)).resolves.toEqual({ someEntryValue: 'entryVal updated', newAttribute: 'is there', dependency1: { dep1Export: 'dep1 val1 updated', - dep1Val: 'dep1 val2 updated', + default: { + dep1Val: 'dep1 val2 updated', + }, }, dependency2: { - dep2Val: 'dep2 val updated', + dep2Val: 'dep2 val2 updated', }, }); - await expect(dependency1.load()).resolves.toEqual({ - dep1Export: 'dep1 val1 updated', + await expect(entryFile.load(false)).resolves.toEqual({ + default: { + someEntryValue: 'entryVal updated', + newAttribute: 'is there', + dependency1: { + dep1Export: 'dep1 val1 updated', + default: { + dep1Val: 'dep1 val2 updated', + }, + }, + dependency2: { + dep2Val: 'dep2 val2 updated', + }, + }, + }); + + await expect(dependency1.load(true)).resolves.toEqual({ dep1Val: 'dep1 val2 updated', }); - await expect(dependency2.load()).resolves.toEqual({ - dep2Val: 'dep2 val updated', + await expect(dependency1.load(false)).resolves.toEqual({ + dep1Export: 'dep1 val1 updated', + default: { + dep1Val: 'dep1 val2 updated', + }, + }); + + await expect(dependency2.load(true)).resolves.toEqual({ + dep2Val: 'dep2 val2 updated', + }); + await expect(dependency2.load(false)).resolves.toEqual({ + dep2Export: 'dep2 val1 updated', + default: { + dep2Val: 'dep2 val2 updated', + }, }); }); }); diff --git a/packages/docusaurus-utils/src/moduleUtils.ts b/packages/docusaurus-utils/src/moduleUtils.ts index bfd5d86c9ea5..f6266f55d42e 100644 --- a/packages/docusaurus-utils/src/moduleUtils.ts +++ b/packages/docusaurus-utils/src/moduleUtils.ts @@ -5,33 +5,54 @@ * LICENSE file in the root directory of this source tree. */ -import jiti from 'jiti'; import logger from '@docusaurus/logger'; +import {createJiti} from 'jiti'; + +const DEBUG = false; + +const jiti = createJiti(__filename, { + // Transpilation cache, can be safely enabled + fsCache: true, + // Bypass Node.js runtime require cache for hot reloads + moduleCache: false, + + interopDefault: true, + debug: DEBUG, +}); /* jiti is able to load ESM, CJS, JSON, TS modules */ -export async function loadFreshModule(modulePath: string): Promise { +export async function loadFreshModule( + modulePath: string, + options?: { + default?: true; // Use this when only the default export matters + }, +): Promise { if (typeof modulePath !== 'string') { throw new Error( logger.interpolate`Invalid module path of type "name=${typeof modulePath}" with value "name=${modulePath}"`, ); } try { - const load = jiti(__filename, { - // Transpilation cache, can be safely enabled - cache: true, - // Bypass Node.js runtime require cache - // Same as "import-fresh" package we used previously - requireCache: false, - // Only take into consideration the default export - // For now we don't need named exports - // This also helps normalize return value for both CJS/ESM/TS modules - interopDefault: true, - // debug: true, + const module = await jiti.import(modulePath, { + default: options?.default, }); - return load(modulePath); + if (DEBUG) { + console.log('Jiti module loaded', { + modulePath, + options, + type: typeof module, + keys: + module && typeof module === 'object' + ? Object.keys(module) + : undefined, + module, + }); + } + + return module; } catch (error) { throw new Error( logger.interpolate`Docusaurus could not load module at path path=${modulePath}`, diff --git a/packages/docusaurus/src/server/config.ts b/packages/docusaurus/src/server/config.ts index 0cc238879039..15945e4f69dd 100644 --- a/packages/docusaurus/src/server/config.ts +++ b/packages/docusaurus/src/server/config.ts @@ -50,7 +50,7 @@ export async function loadSiteConfig({ throw new Error(`Config file at "${siteConfigPath}" not found.`); } - const importedConfig = await loadFreshModule(siteConfigPath); + const importedConfig = await loadFreshModule(siteConfigPath, {default: true}); const loadedConfig: unknown = typeof importedConfig === 'function' diff --git a/yarn.lock b/yarn.lock index 385b8999fe84..29b8e8fd2c88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11498,11 +11498,6 @@ jest-worker@^30.0.5: merge-stream "^2.0.0" supports-color "^8.1.1" -jiti@^1.20.0: - version "1.21.6" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" - integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== - jiti@^2.5.1, jiti@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.7.0.tgz#974228f2f4ca2bc21885a1797b45fea68e950c64" From f63ef9452e4057c7ad5fcc5b08eb95a5e429eabb Mon Sep 17 00:00:00 2001 From: Atharv Mohite <154729565+atharv96k@users.noreply.github.com> Date: Thu, 21 May 2026 17:31:46 +0530 Subject: [PATCH 4/6] docs: clarify where to add custom CSS files (#12032) --- website/docs/styling-layout.mdx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/website/docs/styling-layout.mdx b/website/docs/styling-layout.mdx index 7bbc94017c4f..19add973031b 100644 --- a/website/docs/styling-layout.mdx +++ b/website/docs/styling-layout.mdx @@ -23,6 +23,17 @@ This is the most traditional way of styling that most developers (including non- If you're using `@docusaurus/preset-classic`, you can create your own CSS files (e.g. `/src/css/custom.css`) and import them globally by passing them as an option of the classic theme. +Your project structure should look like this: + +``` +my-website/ +├── src/ +│ └── css/ +│ └── custom.css ← add your custom styles here +├── docusaurus.config.js +└── package.json +``` + ```js title="docusaurus.config.js" export default { // ... From db31c5b9b3035667be548bd82e4c667451aec86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20O=E2=80=99Shannessy?= Date: Thu, 21 May 2026 05:15:23 -0700 Subject: [PATCH 5/6] fix(utils): Make execa usage consistent in git utils (#12028) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: sebastien --- packages/docusaurus-utils/src/vcs/gitUtils.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/docusaurus-utils/src/vcs/gitUtils.ts b/packages/docusaurus-utils/src/vcs/gitUtils.ts index 7ca4919fd673..a1c13997401a 100644 --- a/packages/docusaurus-utils/src/vcs/gitUtils.ts +++ b/packages/docusaurus-utils/src/vcs/gitUtils.ts @@ -147,25 +147,25 @@ export async function getFileCommitDate( // See why: https://github.com/facebook/docusaurus/pull/10022 const resultFormat = includeAuthor ? 'RESULT:%ct,%an' : 'RESULT:%ct'; - const args = [ - `--format=${resultFormat}`, - '--max-count=1', - age === 'oldest' ? '--follow --diff-filter=A' : undefined, - ] - .filter(Boolean) - .join(' '); - - // Do not include GPG signature in the log output - // See https://github.com/facebook/docusaurus/pull/10022 - const command = `git -c log.showSignature=false log ${args} -- "${path.basename( - file, - )}"`; - const result = (await GitCommandQueue.add(() => { - return execa(command, { - cwd: path.dirname(file), - shell: true, - }); + return execa( + 'git', + [ + // Do not include GPG signature in the log output + // See https://github.com/facebook/docusaurus/pull/10022 + '-c', + 'log.showSignature=false', + 'log', + `--format=${resultFormat}`, + '--max-count=1', + ...(age === 'oldest' ? ['--follow', '--diff-filter=A'] : []), + '--', + path.basename(file), + ], + { + cwd: path.dirname(file), + }, + ); }))!; if (result.exitCode !== 0) { From 698a15910feb0a3568a4c6618ca42489dfd6a366 Mon Sep 17 00:00:00 2001 From: Kiwi Date: Thu, 21 May 2026 08:41:10 -0400 Subject: [PATCH 6/6] docs(cli): document missing `--javascript` option for `docusaurus swizzle` (#12038) --- website/docs/cli.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/cli.mdx b/website/docs/cli.mdx index 54d7c87b5f00..1541e41f983d 100644 --- a/website/docs/cli.mdx +++ b/website/docs/cli.mdx @@ -125,6 +125,7 @@ The swizzle CLI is interactive and will guide you through the whole [swizzle pro | `--wrap` | [Wrap](./swizzling.mdx#wrapping) the theme component | | `--danger` | Allow immediate swizzling of unsafe components | | `--typescript` | Swizzle the TypeScript variant component | +| `--javascript` | Swizzle the JavaScript variant component | | `--config` | Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js` | :::warning