diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/i18n/zh-Hans/docusaurus-plugin-content-docs/current/ipsum.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/i18n/zh-Hans/docusaurus-plugin-content-docs/current/ipsum.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/i18n/zh-Hans/docusaurus-plugin-content-docs/current/lorem.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/i18n/zh-Hans/docusaurus-plugin-content-docs/current/lorem.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/versioned-site/i18n/fr/docusaurus-plugin-content-docs-community/current/hello.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/versioned-site/i18n/fr/docusaurus-plugin-content-docs-community/current/hello.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts index 22aeed790c..27d56ecee1 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts @@ -8,10 +8,7 @@ import {jest} from '@jest/globals'; import path from 'path'; import {cliDocsVersionCommand} from '../cli'; -import type { - PathOptions, - SidebarOptions, -} from '@docusaurus/plugin-content-docs'; +import type {PluginOptions} from '@docusaurus/plugin-content-docs'; import fs from 'fs-extra'; import { getVersionedDocsDirPath, @@ -26,7 +23,8 @@ describe('docsVersion', () => { const simpleSiteDir = path.join(fixtureDir, 'simple-site'); const versionedSiteDir = path.join(fixtureDir, 'versioned-site'); - const DEFAULT_OPTIONS: PathOptions & SidebarOptions = { + const DEFAULT_OPTIONS: PluginOptions = { + id: 'default', path: 'docs', sidebarPath: '', sidebarCollapsed: true, @@ -35,32 +33,19 @@ describe('docsVersion', () => { it('no version tag provided', async () => { await expect(() => - cliDocsVersionCommand( - null, - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand(null, DEFAULT_OPTIONS, {siteDir: simpleSiteDir}), ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: no version tag specified! Pass the version you wish to create as an argument, for example: 1.0.0."`, ); await expect(() => - cliDocsVersionCommand( - undefined, - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand(undefined, DEFAULT_OPTIONS, { + siteDir: simpleSiteDir, + }), ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: no version tag specified! Pass the version you wish to create as an argument, for example: 1.0.0."`, ); await expect(() => - cliDocsVersionCommand( - '', - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('', DEFAULT_OPTIONS, {siteDir: simpleSiteDir}), ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: no version tag specified! Pass the version you wish to create as an argument, for example: 1.0.0."`, ); @@ -68,22 +53,16 @@ describe('docsVersion', () => { it('version tag should not have slash', async () => { await expect(() => - cliDocsVersionCommand( - 'foo/bar', - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('foo/bar', DEFAULT_OPTIONS, { + siteDir: simpleSiteDir, + }), ).rejects.toThrowError( '[docs]: invalid version tag specified! Do not include slash (/) or backslash (\\). Try something like: 1.0.0.', ); await expect(() => - cliDocsVersionCommand( - 'foo\\bar', - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('foo\\bar', DEFAULT_OPTIONS, { + siteDir: simpleSiteDir, + }), ).rejects.toThrowError( '[docs]: invalid version tag specified! Do not include slash (/) or backslash (\\). Try something like: 1.0.0.', ); @@ -91,12 +70,9 @@ describe('docsVersion', () => { it('version tag should not be too long', async () => { await expect(() => - cliDocsVersionCommand( - 'a'.repeat(255), - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('a'.repeat(255), DEFAULT_OPTIONS, { + siteDir: simpleSiteDir, + }), ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Length cannot exceed 32 characters. Try something like: 1.0.0."`, ); @@ -104,22 +80,12 @@ describe('docsVersion', () => { it('version tag should not be a dot or two dots', async () => { await expect(() => - cliDocsVersionCommand( - '..', - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('..', DEFAULT_OPTIONS, {siteDir: simpleSiteDir}), ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Do not name your version \\".\\" or \\"..\\". Try something like: 1.0.0."`, ); await expect(() => - cliDocsVersionCommand( - '.', - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('.', DEFAULT_OPTIONS, {siteDir: simpleSiteDir}), ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Do not name your version \\".\\" or \\"..\\". Try something like: 1.0.0."`, ); @@ -127,32 +93,23 @@ describe('docsVersion', () => { it('version tag should be a valid pathname', async () => { await expect(() => - cliDocsVersionCommand( - '', - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('', DEFAULT_OPTIONS, { + siteDir: simpleSiteDir, + }), ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Please ensure its a valid pathname too. Try something like: 1.0.0."`, ); await expect(() => - cliDocsVersionCommand( - 'foo\x00bar', - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('foo\x00bar', DEFAULT_OPTIONS, { + siteDir: simpleSiteDir, + }), ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Please ensure its a valid pathname too. Try something like: 1.0.0."`, ); await expect(() => - cliDocsVersionCommand( - 'foo:bar', - simpleSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('foo:bar', DEFAULT_OPTIONS, { + siteDir: simpleSiteDir, + }), ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Please ensure its a valid pathname too. Try something like: 1.0.0."`, ); @@ -160,12 +117,9 @@ describe('docsVersion', () => { it('version tag already exist', async () => { await expect(() => - cliDocsVersionCommand( - '1.0.0', - versionedSiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('1.0.0', DEFAULT_OPTIONS, { + siteDir: versionedSiteDir, + }), ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: this version already exists! Use a version tag that does not already exist."`, ); @@ -174,14 +128,12 @@ describe('docsVersion', () => { it('no docs file to version', async () => { const emptySiteDir = path.join(fixtureDir, 'empty-site'); await expect(() => - cliDocsVersionCommand( - '1.0.0', - emptySiteDir, - DEFAULT_PLUGIN_ID, - DEFAULT_OPTIONS, - ), + cliDocsVersionCommand('1.0.0', DEFAULT_OPTIONS, { + siteDir: emptySiteDir, + i18n: {locales: ['en', 'zh-Hans'], defaultLocale: 'en'}, + }), ).rejects.toThrowErrorMatchingInlineSnapshot( - `"[docs]: no docs found in /packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/empty-site/docs."`, + `"[docs]: no docs found in \\"/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/empty-site/docs\\"."`, ); }); @@ -205,12 +157,10 @@ describe('docsVersion', () => { ...DEFAULT_OPTIONS, sidebarPath: path.join(simpleSiteDir, 'sidebars.json'), }; - await cliDocsVersionCommand( - '1.0.0', - simpleSiteDir, - DEFAULT_PLUGIN_ID, - options, - ); + await cliDocsVersionCommand('1.0.0', options, { + siteDir: simpleSiteDir, + i18n: {locales: ['en', 'zh-Hans'], defaultLocale: 'en'}, + }); expect(copyMock).toHaveBeenCalledWith( path.join(simpleSiteDir, options.path), path.join( @@ -218,6 +168,16 @@ describe('docsVersion', () => { 'version-1.0.0', ), ); + expect(copyMock).toHaveBeenCalledWith( + path.join( + simpleSiteDir, + 'i18n/zh-Hans/docusaurus-plugin-content-docs/current', + ), + path.join( + simpleSiteDir, + 'i18n/zh-Hans/docusaurus-plugin-content-docs/version-1.0.0', + ), + ); expect(versionedSidebar).toMatchSnapshot(); expect(versionedSidebarPath).toEqual( path.join( @@ -256,16 +216,15 @@ describe('docsVersion', () => { versions = JSON.parse(content as string); }); const consoleMock = jest.spyOn(console, 'log').mockImplementation(() => {}); + const warnMock = jest.spyOn(console, 'warn').mockImplementation(() => {}); const options = { ...DEFAULT_OPTIONS, sidebarPath: path.join(versionedSiteDir, 'sidebars.json'), }; - await cliDocsVersionCommand( - '2.0.0', - versionedSiteDir, - DEFAULT_PLUGIN_ID, - options, - ); + await cliDocsVersionCommand('2.0.0', options, { + siteDir: versionedSiteDir, + i18n: {locales: ['en', 'zh-Hans'], defaultLocale: 'en'}, + }); expect(copyMock).toHaveBeenCalledWith( path.join(versionedSiteDir, options.path), path.join( @@ -289,7 +248,11 @@ describe('docsVersion', () => { /.*\[SUCCESS\].*\[docs\].*: version .*2\.0\.0.* created!.*/, ), ); + expect(warnMock.mock.calls[0][0]).toMatchInlineSnapshot( + `"[WARNING] [docs]: no docs found in \\"/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/versioned-site/i18n/zh-Hans/docusaurus-plugin-content-docs/current\\". Skipping."`, + ); + warnMock.mockRestore(); copyMock.mockRestore(); writeMock.mockRestore(); consoleMock.mockRestore(); @@ -315,10 +278,14 @@ describe('docsVersion', () => { const consoleMock = jest.spyOn(console, 'log').mockImplementation(() => {}); const options = { ...DEFAULT_OPTIONS, + id: pluginId, path: 'community', sidebarPath: path.join(versionedSiteDir, 'community_sidebars.json'), }; - await cliDocsVersionCommand('2.0.0', versionedSiteDir, pluginId, options); + await cliDocsVersionCommand('2.0.0', options, { + siteDir: versionedSiteDir, + i18n: {locales: ['en', 'fr'], defaultLocale: 'en'}, + }); expect(copyMock).toHaveBeenCalledWith( path.join(versionedSiteDir, options.path), path.join( @@ -326,6 +293,16 @@ describe('docsVersion', () => { 'version-2.0.0', ), ); + expect(copyMock).toHaveBeenCalledWith( + path.join( + versionedSiteDir, + 'i18n/fr/docusaurus-plugin-content-docs-community/current', + ), + path.join( + versionedSiteDir, + 'i18n/fr/docusaurus-plugin-content-docs-community/version-2.0.0', + ), + ); expect(versionedSidebar).toMatchSnapshot(); expect(versionedSidebarPath).toEqual( path.join( diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts index 926bcfb8f9..1ad236a886 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts @@ -16,7 +16,7 @@ import pluginContentDocs from '../index'; import {loadContext} from '@docusaurus/core/src/server/index'; import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils'; import type {RouteConfig} from '@docusaurus/types'; -import {posixPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; +import {posixPath} from '@docusaurus/utils'; import {sortConfig} from '@docusaurus/core/src/server/plugins/routeConfig'; import * as cliDocs from '../cli'; @@ -230,23 +230,21 @@ describe('simple website', () => { const siteDir = path.join(__dirname, '__fixtures__', 'simple-site'); const context = await loadContext({siteDir}); const sidebarPath = path.join(siteDir, 'sidebars.json'); - const plugin = await pluginContentDocs( - context, - validateOptions({ - validate: normalizePluginOptions, - options: { - path: 'docs', - sidebarPath, - }, - }), - ); + const options = validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarPath, + }, + }); + const plugin = await pluginContentDocs(context, options); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); - return {siteDir, context, sidebarPath, plugin, pluginContentDir}; + return {siteDir, context, sidebarPath, plugin, options, pluginContentDir}; } it('extendCli - docsVersion', async () => { - const {siteDir, sidebarPath, plugin} = await loadSite(); + const {plugin, options, context} = await loadSite(); const mock = jest .spyOn(cliDocs, 'cliDocsVersionCommand') .mockImplementation(async () => {}); @@ -256,12 +254,7 @@ describe('simple website', () => { plugin.extendCli!(cli); cli.parse(['node', 'test', 'docs:version', '1.0.0']); expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenCalledWith('1.0.0', siteDir, DEFAULT_PLUGIN_ID, { - path: 'docs', - sidebarPath, - sidebarCollapsed: true, - sidebarCollapsible: true, - }); + expect(mock).toHaveBeenCalledWith('1.0.0', options, context); mock.mockRestore(); }); @@ -344,29 +337,28 @@ describe('versioned website', () => { const context = await loadContext({siteDir}); const sidebarPath = path.join(siteDir, 'sidebars.json'); const routeBasePath = 'docs'; - const plugin = await pluginContentDocs( - context, - validateOptions({ - validate: normalizePluginOptions, - options: { - routeBasePath, - sidebarPath, - }, - }), - ); + const options = validateOptions({ + validate: normalizePluginOptions, + options: { + routeBasePath, + sidebarPath, + }, + }); + const plugin = await pluginContentDocs(context, options); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); return { siteDir, context, routeBasePath, sidebarPath, + options, plugin, pluginContentDir, }; } it('extendCli - docsVersion', async () => { - const {siteDir, routeBasePath, sidebarPath, plugin} = await loadSite(); + const {plugin, context, options} = await loadSite(); const mock = jest .spyOn(cliDocs, 'cliDocsVersionCommand') .mockImplementation(async () => {}); @@ -376,12 +368,7 @@ describe('versioned website', () => { plugin.extendCli!(cli); cli.parse(['node', 'test', 'docs:version', '2.0.0']); expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenCalledWith('2.0.0', siteDir, DEFAULT_PLUGIN_ID, { - path: routeBasePath, - sidebarPath, - sidebarCollapsed: true, - sidebarCollapsible: true, - }); + expect(mock).toHaveBeenCalledWith('2.0.0', options, context); mock.mockRestore(); }); @@ -474,18 +461,16 @@ describe('versioned website (community)', () => { const sidebarPath = path.join(siteDir, 'community_sidebars.json'); const routeBasePath = 'community'; const pluginId = 'community'; - const plugin = await pluginContentDocs( - context, - validateOptions({ - validate: normalizePluginOptions, - options: { - id: 'community', - path: 'community', - routeBasePath, - sidebarPath, - }, - }), - ); + const options = validateOptions({ + validate: normalizePluginOptions, + options: { + id: 'community', + path: 'community', + routeBasePath, + sidebarPath, + }, + }); + const plugin = await pluginContentDocs(context, options); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); return { siteDir, @@ -493,14 +478,14 @@ describe('versioned website (community)', () => { routeBasePath, sidebarPath, pluginId, + options, plugin, pluginContentDir, }; } it('extendCli - docsVersion', async () => { - const {siteDir, routeBasePath, sidebarPath, pluginId, plugin} = - await loadSite(); + const {pluginId, plugin, options, context} = await loadSite(); const mock = jest .spyOn(cliDocs, 'cliDocsVersionCommand') .mockImplementation(async () => {}); @@ -510,12 +495,7 @@ describe('versioned website (community)', () => { plugin.extendCli!(cli); cli.parse(['node', 'test', `docs:version:${pluginId}`, '2.0.0']); expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenCalledWith('2.0.0', siteDir, pluginId, { - path: routeBasePath, - sidebarPath, - sidebarCollapsed: true, - sidebarCollapsible: true, - }); + expect(mock).toHaveBeenCalledWith('2.0.0', options, context); mock.mockRestore(); }); diff --git a/packages/docusaurus-plugin-content-docs/src/cli.ts b/packages/docusaurus-plugin-content-docs/src/cli.ts index 710a5c3d7c..e405d11560 100644 --- a/packages/docusaurus-plugin-content-docs/src/cli.ts +++ b/packages/docusaurus-plugin-content-docs/src/cli.ts @@ -9,16 +9,16 @@ import { getVersionsFilePath, getVersionedDocsDirPath, getVersionedSidebarsDirPath, + getDocsDirPathLocalized, } from './versions'; import fs from 'fs-extra'; import path from 'path'; -import type { - PathOptions, - SidebarOptions, -} from '@docusaurus/plugin-content-docs'; +import type {PluginOptions} from '@docusaurus/plugin-content-docs'; import {loadSidebarsFileUnsafe, resolveSidebarPathOption} from './sidebars'; +import {CURRENT_VERSION_NAME} from './constants'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; import logger from '@docusaurus/logger'; +import type {LoadContext} from '@docusaurus/types'; async function createVersionedSidebarFile({ siteDir, @@ -58,9 +58,8 @@ async function createVersionedSidebarFile({ // Tests depend on non-default export for mocking. export async function cliDocsVersionCommand( version: string | null | undefined, - siteDir: string, - pluginId: string, - options: PathOptions & SidebarOptions, + {id: pluginId, path: docsPath, sidebarPath}: PluginOptions, + {siteDir, i18n}: LoadContext, ): Promise { // It wouldn't be very user-friendly to show a [default] log prefix, // so we use [docs] instead of [default] @@ -114,22 +113,53 @@ export async function cliDocsVersionCommand( ); } - const {path: docsPath, sidebarPath} = options; - - // Copy docs files. - const docsDir = path.resolve(siteDir, docsPath); - - if ( - (await fs.pathExists(docsDir)) && - (await fs.readdir(docsDir)).length > 0 - ) { - const versionedDir = getVersionedDocsDirPath(siteDir, pluginId); - const newVersionDir = path.join(versionedDir, `version-${version}`); - await fs.copy(docsDir, newVersionDir); - } else { - throw new Error(`${pluginIdLogPrefix}: no docs found in ${docsDir}.`); + if (i18n.locales.length > 1) { + logger.info`Versioned docs will be created for the following locales: name=${i18n.locales}`; } + await Promise.all( + i18n.locales.map(async (locale) => { + // Copy docs files. + const docsDir = + locale === i18n.defaultLocale + ? path.resolve(siteDir, docsPath) + : getDocsDirPathLocalized({ + siteDir, + locale, + pluginId, + versionName: CURRENT_VERSION_NAME, + }); + + if ( + !(await fs.pathExists(docsDir)) || + (await fs.readdir(docsDir)).length === 0 + ) { + if (locale === i18n.defaultLocale) { + throw new Error( + logger.interpolate`${pluginIdLogPrefix}: no docs found in path=${docsDir}.`, + ); + } else { + logger.warn`${pluginIdLogPrefix}: no docs found in path=${docsDir}. Skipping.`; + return; + } + } + + const newVersionDir = + locale === i18n.defaultLocale + ? path.join( + getVersionedDocsDirPath(siteDir, pluginId), + `version-${version}`, + ) + : getDocsDirPathLocalized({ + siteDir, + locale, + pluginId, + versionName: version, + }); + await fs.copy(docsDir, newVersionDir); + }), + ); + await createVersionedSidebarFile({ siteDir, pluginId, diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts index b8e7c12114..7fbc9595b4 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/index.ts @@ -67,7 +67,7 @@ export default async function pluginContentDocs( const versionsMetadata = await readVersionsMetadata({context, options}); - const pluginId = options.id ?? DEFAULT_PLUGIN_ID; + const pluginId = options.id; const pluginDataDirRoot = path.join( generatedFilesDir, @@ -97,12 +97,7 @@ export default async function pluginContentDocs( .arguments('') .description(commandDescription) .action((version) => { - cliDocsVersionCommand(version, siteDir, pluginId, { - path: options.path, - sidebarPath: options.sidebarPath, - sidebarCollapsed: options.sidebarCollapsed, - sidebarCollapsible: options.sidebarCollapsible, - }); + cliDocsVersionCommand(version, options, context); }); }, diff --git a/packages/docusaurus-plugin-content-docs/src/versions.ts b/packages/docusaurus-plugin-content-docs/src/versions.ts index 7b06aa0e08..a7f216f532 100644 --- a/packages/docusaurus-plugin-content-docs/src/versions.ts +++ b/packages/docusaurus-plugin-content-docs/src/versions.ts @@ -133,7 +133,7 @@ export async function readVersionNames( return versions; } -function getDocsDirPathLocalized({ +export function getDocsDirPathLocalized({ siteDir, locale, pluginId, @@ -143,7 +143,7 @@ function getDocsDirPathLocalized({ locale: string; pluginId: string; versionName: string; -}) { +}): string { return getPluginI18nPath({ siteDir, locale,