diff --git a/.eslintrc.js b/.eslintrc.js index 3a755c032e..0dc85ad9a9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -107,6 +107,36 @@ module.exports = { property, message: `Use ${alternative} instead.`, })), + ...[ + 'readdirSync', + 'readFileSync', + 'statSync', + 'lstatSync', + 'existsSync', + 'pathExistsSync', + 'realpathSync', + 'mkdirSync', + 'mkdirpSync', + 'mkdirsSync', + 'writeFileSync', + 'writeJsonSync', + 'outputFileSync', + 'outputJsonSync', + 'moveSync', + 'copySync', + 'copyFileSync', + 'ensureFileSync', + 'ensureDirSync', + 'ensureLinkSync', + 'ensureSymlinkSync', + 'unlinkSync', + 'removeSync', + 'emptyDirSync', + ].map((property) => ({ + object: 'fs', + property, + message: 'Do not use sync fs methods.', + })), ], 'no-restricted-syntax': [ WARNING, diff --git a/packages/create-docusaurus/src/index.ts b/packages/create-docusaurus/src/index.ts index 9d8024fc89..6a7398a2a6 100755 --- a/packages/create-docusaurus/src/index.ts +++ b/packages/create-docusaurus/src/index.ts @@ -37,16 +37,14 @@ async function updatePkg(pkgPath: string, obj: Record) { await fs.outputFile(pkgPath, `${JSON.stringify(newPkg, null, 2)}\n`); } -function readTemplates(templatesDir: string) { - const templates = fs - .readdirSync(templatesDir) - .filter( - (d) => - !d.startsWith('.') && - !d.startsWith('README') && - !d.endsWith(TypeScriptTemplateSuffix) && - d !== 'shared', - ); +async function readTemplates(templatesDir: string) { + const templates = (await fs.readdir(templatesDir)).filter( + (d) => + !d.startsWith('.') && + !d.startsWith('README') && + !d.endsWith(TypeScriptTemplateSuffix) && + d !== 'shared', + ); // Classic should be first in list! return _.sortBy(templates, (t) => t !== RecommendedTemplate); @@ -86,8 +84,8 @@ async function copyTemplate( if (tsBaseTemplate) { const tsBaseTemplatePath = path.resolve(templatesDir, tsBaseTemplate); await fs.copy(tsBaseTemplatePath, dest, { - filter: (filePath) => - fs.statSync(filePath).isDirectory() || + filter: async (filePath) => + (await fs.stat(filePath)).isDirectory() || path.extname(filePath) === '.css' || path.basename(filePath) === 'docusaurus.config.js', }); @@ -96,7 +94,7 @@ async function copyTemplate( await fs.copy(path.resolve(templatesDir, template), dest, { // Symlinks don't exist in published NPM packages anymore, so this is only // to prevent errors during local testing - filter: (filePath) => !fs.lstatSync(filePath).isSymbolicLink(), + filter: async (filePath) => !(await fs.lstat(filePath)).isSymbolicLink(), }); } @@ -135,9 +133,9 @@ export default async function init( ): Promise { const useYarn = cliOptions.useNpm ? false : hasYarn(); const templatesDir = fileURLToPath(new URL('../templates', import.meta.url)); - const templates = readTemplates(templatesDir); + const templates = await readTemplates(templatesDir); const hasTS = (templateName: string) => - fs.pathExistsSync( + fs.pathExists( path.resolve(templatesDir, `${templateName}${TypeScriptTemplateSuffix}`), ); @@ -160,7 +158,7 @@ export default async function init( } const dest = path.resolve(rootDir, name); - if (fs.existsSync(dest)) { + if (await fs.pathExists(dest)) { logger.error`Directory already exists at path=${dest}!`; process.exit(1); } @@ -176,7 +174,7 @@ export default async function init( choices: createTemplateChoices(templates), }); template = templatePrompt.template; - if (template && !useTS && hasTS(template)) { + if (template && !useTS && (await hasTS(template))) { const tsPrompt = await prompts({ type: 'confirm', name: 'useTS', @@ -223,10 +221,10 @@ export default async function init( const dirPrompt = await prompts({ type: 'text', name: 'templateDir', - validate: (dir?: string) => { + validate: async (dir?: string) => { if (dir) { const fullDir = path.resolve(process.cwd(), dir); - if (fs.existsSync(fullDir)) { + if (await fs.pathExists(fullDir)) { return true; } return logger.red( @@ -267,7 +265,7 @@ export default async function init( } else if (templates.includes(template)) { // Docusaurus templates. if (useTS) { - if (!hasTS(template)) { + if (!(await hasTS(template))) { logger.error`Template name=${template} doesn't provide the Typescript variant.`; process.exit(1); } @@ -279,7 +277,7 @@ export default async function init( logger.error`Copying Docusaurus template name=${template} failed!`; throw err; } - } else if (fs.existsSync(path.resolve(process.cwd(), template))) { + } else if (await fs.pathExists(path.resolve(process.cwd(), template))) { const templateDir = path.resolve(process.cwd(), template); try { await fs.copy(templateDir, dest); @@ -306,13 +304,13 @@ export default async function init( // We need to rename the gitignore file to .gitignore if ( - !fs.pathExistsSync(path.join(dest, '.gitignore')) && - fs.pathExistsSync(path.join(dest, 'gitignore')) + !(await fs.pathExists(path.join(dest, '.gitignore'))) && + (await fs.pathExists(path.join(dest, 'gitignore'))) ) { await fs.move(path.join(dest, 'gitignore'), path.join(dest, '.gitignore')); } - if (fs.pathExistsSync(path.join(dest, 'gitignore'))) { - fs.removeSync(path.join(dest, 'gitignore')); + if (await fs.pathExists(path.join(dest, 'gitignore'))) { + await fs.remove(path.join(dest, 'gitignore')); } const pkgManager = useYarn ? 'yarn' : 'npm'; diff --git a/packages/docusaurus-mdx-loader/src/index.ts b/packages/docusaurus-mdx-loader/src/index.ts index c3e15c338d..98690e146a 100644 --- a/packages/docusaurus-mdx-loader/src/index.ts +++ b/packages/docusaurus-mdx-loader/src/index.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {readFile} from 'fs-extra'; +import fs from 'fs-extra'; import mdx from '@mdx-js/mdx'; import logger from '@docusaurus/logger'; import emoji from 'remark-emoji'; @@ -57,7 +57,7 @@ type Options = RemarkAndRehypePluginOptions & { */ async function readMetadataPath(metadataPath: string) { try { - return await readFile(metadataPath, 'utf8'); + return await fs.readFile(metadataPath, 'utf8'); } catch (e) { throw new Error( `MDX loader can't read MDX metadata file for path ${metadataPath}. Maybe the isMDXPartial option function was not provided?`, diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts index 53014375fa..f3134ef5d4 100644 --- a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts +++ b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {join} from 'path'; +import path from 'path'; import remark from 'remark'; import mdx from 'remark-mdx'; import vfile from 'to-vfile'; @@ -13,8 +13,8 @@ import plugin from '../index'; import headings from '../../headings/index'; const processFixture = async (name, options?) => { - const path = join(__dirname, '__fixtures__', `${name}.md`); - const file = await vfile.read(path); + const filePath = path.join(__dirname, '__fixtures__', `${name}.md`); + const file = await vfile.read(filePath); const result = await remark() .use(headings) .use(mdx) diff --git a/packages/docusaurus-migrate/src/__tests__/migration.test.ts b/packages/docusaurus-migrate/src/__tests__/migration.test.ts index 4b0397f9e8..daf21ba47c 100644 --- a/packages/docusaurus-migrate/src/__tests__/migration.test.ts +++ b/packages/docusaurus-migrate/src/__tests__/migration.test.ts @@ -21,7 +21,7 @@ describe('migration test', () => { await expect( migrateDocusaurusProject(siteDir, newDir), ).resolves.toBeUndefined(); - fs.removeSync(newDir); + await fs.remove(newDir); }); test('complex website', async () => { const siteDir = path.join( @@ -38,7 +38,7 @@ describe('migration test', () => { await expect( migrateDocusaurusProject(siteDir, newDir), ).resolves.toBeUndefined(); - fs.removeSync(newDir); + await fs.remove(newDir); }); test('missing versions', async () => { @@ -56,6 +56,6 @@ describe('migration test', () => { await expect( migrateDocusaurusProject(siteDir, newDir), ).resolves.toBeUndefined(); - fs.removeSync(newDir); + await fs.remove(newDir); }); }); diff --git a/packages/docusaurus-migrate/src/index.ts b/packages/docusaurus-migrate/src/index.ts index fc41d79675..dc16556a2c 100644 --- a/packages/docusaurus-migrate/src/index.ts +++ b/packages/docusaurus-migrate/src/index.ts @@ -25,18 +25,18 @@ import path from 'path'; const DOCUSAURUS_VERSION = (importFresh('../package.json') as {version: string}) .version; -export function walk(dir: string): Array { - let results: Array = []; - const list = fs.readdirSync(dir); - list.forEach((file: string) => { +async function walk(dir: string): Promise { + const results: string[] = []; + const list = await fs.readdir(dir); + for (const file of list) { const fullPath = `${dir}/${file}`; - const stat = fs.statSync(fullPath); + const stat = await fs.stat(fullPath); if (stat && stat.isDirectory()) { - results = results.concat(walk(fullPath)); + results.push(...(await walk(fullPath))); } else { results.push(fullPath); } - }); + } return results; } @@ -112,7 +112,7 @@ export async function migrateDocusaurusProject( } if (shouldMigratePages) { try { - createPages(migrationContext); + await createPages(migrationContext); logger.success( 'Created new doc pages (check migration page for more details)', ); @@ -122,7 +122,7 @@ export async function migrateDocusaurusProject( } } else { try { - createDefaultLandingPage(migrationContext); + await createDefaultLandingPage(migrationContext); logger.success( 'Created landing page (check migration page for more details)', ); @@ -133,41 +133,41 @@ export async function migrateDocusaurusProject( } try { - migrateStaticFiles(migrationContext); + await migrateStaticFiles(migrationContext); logger.success('Migrated static folder'); } catch (e) { logger.error(`Failed to copy static folder: ${e}`); errorCount += 1; } try { - migrateBlogFiles(migrationContext); + await migrateBlogFiles(migrationContext); } catch (e) { logger.error(`Failed to migrate blogs: ${e}`); errorCount += 1; } try { - handleVersioning(migrationContext); + await handleVersioning(migrationContext); } catch (e) { logger.error(`Failed to migrate versioned docs: ${e}`); errorCount += 1; } try { - migrateLatestDocs(migrationContext); + await migrateLatestDocs(migrationContext); } catch (e) { logger.error(`Failed to migrate docs: ${e}`); errorCount += 1; } try { - migrateLatestSidebar(migrationContext); + await migrateLatestSidebar(migrationContext); } catch (e) { logger.error(`Failed to migrate sidebar: ${e}`); errorCount += 1; } try { - fs.writeFileSync( + await fs.writeFile( path.join(newDir, 'docusaurus.config.js'), `module.exports=${JSON.stringify(migrationContext.v2Config, null, 2)}`, ); @@ -368,33 +368,35 @@ function createClientRedirects(context: MigrationContext): void { } } -function createPages(context: MigrationContext): void { +async function createPages(context: MigrationContext) { const {newDir, siteDir} = context; - fs.mkdirpSync(path.join(newDir, 'src', 'pages')); - if (fs.existsSync(path.join(siteDir, 'pages', 'en'))) { + await fs.mkdirp(path.join(newDir, 'src', 'pages')); + if (await fs.pathExists(path.join(siteDir, 'pages', 'en'))) { try { - fs.copySync( + await fs.copy( path.join(siteDir, 'pages', 'en'), path.join(newDir, 'src', 'pages'), ); - const files = Globby.sync('**/*.js', { + const files = await Globby('**/*.js', { cwd: path.join(newDir, 'src', 'pages'), }); - files.forEach((file) => { - const filePath = path.join(newDir, 'src', 'pages', file); - const content = String(fs.readFileSync(filePath)); - fs.writeFileSync(filePath, migratePage(content)); - }); + await Promise.all( + files.map(async (file) => { + const filePath = path.join(newDir, 'src', 'pages', file); + const content = await fs.readFile(filePath, 'utf-8'); + await fs.writeFile(filePath, migratePage(content)); + }), + ); } catch (e) { logger.error(`Unable to migrate Pages: ${e}`); - createDefaultLandingPage(context); + await createDefaultLandingPage(context); } } else { logger.info('Ignoring Pages'); } } -function createDefaultLandingPage({newDir}: MigrationContext) { +async function createDefaultLandingPage({newDir}: MigrationContext) { const indexPage = `import Layout from "@theme/Layout"; import React from "react"; @@ -402,30 +404,32 @@ function createDefaultLandingPage({newDir}: MigrationContext) { return ; }; `; - fs.mkdirpSync(`${newDir}/src/pages/`); - fs.writeFileSync(`${newDir}/src/pages/index.js`, indexPage); + await fs.mkdirp(`${newDir}/src/pages/`); + await fs.writeFile(`${newDir}/src/pages/index.js`, indexPage); } -function migrateStaticFiles({siteDir, newDir}: MigrationContext): void { - if (fs.existsSync(path.join(siteDir, 'static'))) { - fs.copySync(path.join(siteDir, 'static'), path.join(newDir, 'static')); +async function migrateStaticFiles({siteDir, newDir}: MigrationContext) { + if (await fs.pathExists(path.join(siteDir, 'static'))) { + await fs.copy(path.join(siteDir, 'static'), path.join(newDir, 'static')); } else { - fs.mkdirSync(path.join(newDir, 'static')); + await fs.mkdir(path.join(newDir, 'static')); } } -function migrateBlogFiles(context: MigrationContext): void { +async function migrateBlogFiles(context: MigrationContext) { const {siteDir, newDir, shouldMigrateMdFiles} = context; - if (fs.existsSync(path.join(siteDir, 'blog'))) { - fs.copySync(path.join(siteDir, 'blog'), path.join(newDir, 'blog')); - const files = walk(path.join(newDir, 'blog')); - files.forEach((file) => { - const content = String(fs.readFileSync(file)); - fs.writeFileSync( - file, - sanitizedFileContent(content, shouldMigrateMdFiles), - ); - }); + if (await fs.pathExists(path.join(siteDir, 'blog'))) { + await fs.copy(path.join(siteDir, 'blog'), path.join(newDir, 'blog')); + const files = await walk(path.join(newDir, 'blog')); + await Promise.all( + files.map(async (file) => { + const content = await fs.readFile(file, 'utf-8'); + await fs.writeFile( + file, + sanitizedFileContent(content, shouldMigrateMdFiles), + ); + }), + ); context.v2Config.presets[0][1].blog.path = 'blog'; logger.success('Migrated blogs to version 2 with change in front matter'); } else { @@ -433,21 +437,21 @@ function migrateBlogFiles(context: MigrationContext): void { } } -function handleVersioning(context: MigrationContext): void { +async function handleVersioning(context: MigrationContext) { const {siteDir, newDir} = context; - if (fs.existsSync(path.join(siteDir, 'versions.json'))) { - const loadedVersions: Array = JSON.parse( - String(fs.readFileSync(path.join(siteDir, 'versions.json'))), + if (await fs.pathExists(path.join(siteDir, 'versions.json'))) { + const loadedVersions: string[] = JSON.parse( + await fs.readFile(path.join(siteDir, 'versions.json'), 'utf-8'), ); - fs.copyFileSync( + await fs.copyFile( path.join(siteDir, 'versions.json'), path.join(newDir, 'versions.json'), ); const versions = loadedVersions.reverse(); const versionRegex = new RegExp(`version-(${versions.join('|')})-`, 'mgi'); - migrateVersionedSidebar(context, versions, versionRegex); - fs.mkdirpSync(path.join(newDir, 'versioned_docs')); - migrateVersionedDocs(context, versions, versionRegex); + await migrateVersionedSidebar(context, versions, versionRegex); + await fs.mkdirp(path.join(newDir, 'versioned_docs')); + await migrateVersionedDocs(context, versions, versionRegex); logger.success`Migrated version docs and sidebar. The following doc versions have been created:name=${loadedVersions}`; } else { logger.warn( @@ -456,69 +460,78 @@ function handleVersioning(context: MigrationContext): void { } } -function migrateVersionedDocs( +async function migrateVersionedDocs( context: MigrationContext, versions: string[], versionRegex: RegExp, -): void { +) { const {siteDir, newDir, shouldMigrateMdFiles} = context; - versions.reverse().forEach((version, index) => { - if (index === 0) { - fs.copySync( - path.join(siteDir, '..', context.v1Config.customDocsPath || 'docs'), - path.join(newDir, 'versioned_docs', `version-${version}`), - ); - fs.copySync( - path.join(siteDir, 'versioned_docs', `version-${version}`), - path.join(newDir, 'versioned_docs', `version-${version}`), - ); - return; - } - try { - fs.mkdirsSync(path.join(newDir, 'versioned_docs', `version-${version}`)); - fs.copySync( - path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`), - path.join(newDir, 'versioned_docs', `version-${version}`), - ); - fs.copySync( - path.join(siteDir, 'versioned_docs', `version-${version}`), - path.join(newDir, 'versioned_docs', `version-${version}`), - ); - } catch { - fs.copySync( - path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`), - path.join(newDir, 'versioned_docs', `version-${version}`), - ); - } - }); - const files = walk(path.join(newDir, 'versioned_docs')); - files.forEach((pathToFile) => { - if (path.extname(pathToFile) === '.md') { - const content = fs.readFileSync(pathToFile).toString(); - fs.writeFileSync( - pathToFile, - sanitizedFileContent( - content.replace(versionRegex, ''), - shouldMigrateMdFiles, - ), - ); - } - }); + await Promise.all( + versions.reverse().map(async (version, index) => { + if (index === 0) { + await fs.copy( + path.join(siteDir, '..', context.v1Config.customDocsPath || 'docs'), + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + await fs.copy( + path.join(siteDir, 'versioned_docs', `version-${version}`), + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + return; + } + try { + await fs.mkdirs( + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + await fs.copy( + path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`), + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + await fs.copy( + path.join(siteDir, 'versioned_docs', `version-${version}`), + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + } catch { + await fs.copy( + path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`), + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + } + }), + ); + const files = await walk(path.join(newDir, 'versioned_docs')); + await Promise.all( + files.map(async (pathToFile) => { + if (path.extname(pathToFile) === '.md') { + const content = await fs.readFile(pathToFile, 'utf-8'); + await fs.writeFile( + pathToFile, + sanitizedFileContent( + content.replace(versionRegex, ''), + shouldMigrateMdFiles, + ), + ); + } + }), + ); } -function migrateVersionedSidebar( +async function migrateVersionedSidebar( context: MigrationContext, versions: string[], versionRegex: RegExp, -): void { +) { const {siteDir, newDir} = context; - if (fs.existsSync(path.join(siteDir, 'versioned_sidebars'))) { - fs.mkdirpSync(path.join(newDir, 'versioned_sidebars')); + if (await fs.pathExists(path.join(siteDir, 'versioned_sidebars'))) { + await fs.mkdirp(path.join(newDir, 'versioned_sidebars')); const sidebars: { entries: SidebarEntries; version: string; }[] = []; - versions.forEach((version, index) => { + // Order matters: if a sidebar file doesn't exist, we have to use the + // previous version's + for (let i = 0; i < versions.length; i += 1) { + const version = versions[i]; let sidebarEntries: SidebarEntries; const sidebarPath = path.join( siteDir, @@ -526,79 +539,74 @@ function migrateVersionedSidebar( `version-${version}-sidebars.json`, ); try { - fs.statSync(sidebarPath); - sidebarEntries = JSON.parse(String(fs.readFileSync(sidebarPath))); + sidebarEntries = JSON.parse(await fs.readFile(sidebarPath, 'utf-8')); } catch { - sidebars.push({version, entries: sidebars[index - 1].entries}); + sidebars.push({version, entries: sidebars[i - 1].entries}); return; } const newSidebar = Object.entries(sidebarEntries).reduce( (topLevel: SidebarEntries, value) => { const key = value[0].replace(versionRegex, ''); - topLevel[key] = Object.entries(value[1]).reduce( - ( - acc: {[key: string]: Array | string>}, - val, - ) => { - acc[val[0].replace(versionRegex, '')] = ( - val[1] as Array - ).map((item) => { - if (typeof item === 'string') { - return item.replace(versionRegex, ''); - } - return { - type: 'category', - label: item.label, - ids: item.ids.map((id) => id.replace(versionRegex, '')), - }; - }); - return acc; - }, - {}, - ); + topLevel[key] = Object.entries(value[1]).reduce((acc, val) => { + acc[val[0].replace(versionRegex, '')] = ( + val[1] as SidebarEntry[] + ).map((item) => { + if (typeof item === 'string') { + return item.replace(versionRegex, ''); + } + return { + type: 'category', + label: item.label, + ids: item.ids.map((id) => id.replace(versionRegex, '')), + }; + }); + return acc; + }, {} as Record>>); return topLevel; }, {}, ); sidebars.push({version, entries: newSidebar}); - }); - sidebars.forEach((sidebar) => { - const newSidebar = Object.entries(sidebar.entries).reduce( - (acc: SidebarEntries, val) => { - const key = `version-${sidebar.version}/${val[0]}`; - acc[key] = Object.entries(val[1]).map((value) => ({ - type: 'category', - label: value[0], - items: (value[1] as Array).map((sidebarItem) => { - if (typeof sidebarItem === 'string') { + } + await Promise.all( + sidebars.map(async (sidebar) => { + const newSidebar = Object.entries(sidebar.entries).reduce( + (acc, val) => { + const key = `version-${sidebar.version}/${val[0]}`; + acc[key] = Object.entries(val[1]).map((value) => ({ + type: 'category', + label: value[0], + items: (value[1] as SidebarEntry[]).map((sidebarItem) => { + if (typeof sidebarItem === 'string') { + return { + type: 'doc', + id: `version-${sidebar.version}/${sidebarItem}`, + }; + } return { - type: 'doc', - id: `version-${sidebar.version}/${sidebarItem}`, + type: 'category', + label: sidebarItem.label, + items: sidebarItem.ids.map((id) => ({ + type: 'doc', + id: `version-${sidebar.version}/${id}`, + })), }; - } - return { - type: 'category', - label: sidebarItem.label, - items: sidebarItem.ids.map((id: string) => ({ - type: 'doc', - id: `version-${sidebar.version}/${id}`, - })), - }; - }), - })); - return acc; - }, - {}, - ); - fs.writeFileSync( - path.join( - newDir, - 'versioned_sidebars', - `version-${sidebar.version}-sidebars.json`, - ), - JSON.stringify(newSidebar, null, 2), - ); - }); + }), + })); + return acc; + }, + {} as SidebarEntries, + ); + await fs.writeFile( + path.join( + newDir, + 'versioned_sidebars', + `version-${sidebar.version}-sidebars.json`, + ), + JSON.stringify(newSidebar, null, 2), + ); + }), + ); context.v2Config.themeConfig.navbar.items.push({ label: 'Version', to: 'docs', @@ -626,10 +634,10 @@ function migrateVersionedSidebar( } } -function migrateLatestSidebar(context: MigrationContext): void { +async function migrateLatestSidebar(context: MigrationContext) { const {siteDir, newDir} = context; try { - fs.copyFileSync( + await fs.copyFile( path.join(siteDir, 'sidebars.json'), path.join(newDir, 'sidebars.json'), ); @@ -652,8 +660,8 @@ function migrateLatestSidebar(context: MigrationContext): void { --ifm-color-primary-darkest: ${primaryColor.darken(0.3).hex()}; } `; - fs.mkdirpSync(path.join(newDir, 'src', 'css')); - fs.writeFileSync(path.join(newDir, 'src', 'css', 'customTheme.css'), css); + await fs.mkdirp(path.join(newDir, 'src', 'css')); + await fs.writeFile(path.join(newDir, 'src', 'css', 'customTheme.css'), css); context.v2Config.presets[0][1].theme.customCss = path.join( path.relative(newDir, path.join(siteDir, '..')), 'src', @@ -663,23 +671,25 @@ function migrateLatestSidebar(context: MigrationContext): void { } } -function migrateLatestDocs(context: MigrationContext): void { +async function migrateLatestDocs(context: MigrationContext) { const {siteDir, newDir, shouldMigrateMdFiles} = context; - if (fs.existsSync(path.join(siteDir, '..', 'docs'))) { + if (await fs.pathExists(path.join(siteDir, '..', 'docs'))) { context.v2Config.presets[0][1].docs.path = path.join( path.relative(newDir, path.join(siteDir, '..')), 'docs', ); - const files = walk(path.join(siteDir, '..', 'docs')); - files.forEach((file) => { - if (path.extname(file) === '.md') { - const content = fs.readFileSync(file).toString(); - fs.writeFileSync( - file, - sanitizedFileContent(content, shouldMigrateMdFiles), - ); - } - }); + const files = await walk(path.join(siteDir, '..', 'docs')); + await Promise.all( + files.map(async (file) => { + if (path.extname(file) === '.md') { + const content = await fs.readFile(file, 'utf-8'); + await fs.writeFile( + file, + sanitizedFileContent(content, shouldMigrateMdFiles), + ); + } + }), + ); logger.success('Migrated docs to version 2'); } else { logger.warn('Docs folder not found. Skipping migration for docs'); @@ -713,7 +723,7 @@ async function migratePackageFile(context: MigrationContext): Promise { ...packageFile.dependencies, ...deps, }; - fs.writeFileSync( + await fs.writeFile( path.join(newDir, 'package.json'), JSON.stringify(packageFile, null, 2), ); @@ -724,14 +734,16 @@ export async function migrateMDToMDX( siteDir: string, newDir: string, ): Promise { - fs.mkdirpSync(newDir); - fs.copySync(siteDir, newDir); - const files = walk(newDir); - files.forEach((filePath) => { - if (path.extname(filePath) === '.md') { - const content = fs.readFileSync(filePath).toString(); - fs.writeFileSync(filePath, sanitizedFileContent(content, true)); - } - }); + await fs.mkdirp(newDir); + await fs.copy(siteDir, newDir); + const files = await walk(newDir); + await Promise.all( + files.map(async (filePath) => { + if (path.extname(filePath) === '.md') { + const content = await fs.readFile(filePath, 'utf-8'); + await fs.writeFile(filePath, sanitizedFileContent(content, true)); + } + }), + ); logger.success`Successfully migrated path=${siteDir} to path=${newDir}`; } diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/linkify.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/linkify.test.ts index 9fe9a51490..d745399331 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/linkify.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/linkify.test.ts @@ -43,8 +43,11 @@ const blogPosts: BlogPost[] = [ }, ]; -const transform = (filePath: string, options?: Partial) => { - const fileContent = fs.readFileSync(filePath, 'utf-8'); +const transform = async ( + filePath: string, + options?: Partial, +) => { + const fileContent = await fs.readFile(filePath, 'utf-8'); const transformedContent = linkify({ filePath, fileString: fileContent, @@ -61,9 +64,9 @@ const transform = (filePath: string, options?: Partial) => { return [fileContent, transformedContent]; }; -test('transform to correct link', () => { +test('transform to correct link', async () => { const post = path.join(contentPaths.contentPath, 'post.md'); - const [content, transformedContent] = transform(post); + const [content, transformedContent] = await transform(post); expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toContain( '](/blog/2018/12/14/Happy-First-Birthday-Slash', @@ -74,12 +77,12 @@ test('transform to correct link', () => { expect(content).not.toEqual(transformedContent); }); -test('report broken markdown links', () => { +test('report broken markdown links', async () => { const filePath = 'post-with-broken-links.md'; const folderPath = contentPaths.contentPath; const postWithBrokenLinks = path.join(folderPath, filePath); const onBrokenMarkdownLink = jest.fn(); - const [, transformedContent] = transform(postWithBrokenLinks, { + const [, transformedContent] = await transform(postWithBrokenLinks, { onBrokenMarkdownLink, }); expect(transformedContent).toMatchSnapshot(); diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index 94898473e8..0cfaf0f146 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -332,7 +332,7 @@ export async function generateBlogPosts( ): Promise { const {include, exclude} = options; - if (!fs.existsSync(contentPaths.contentPath)) { + if (!(await fs.pathExists(contentPaths.contentPath))) { return []; } 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 46fd8f0e92..e866f3099e 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts @@ -185,9 +185,9 @@ describe('docsVersion', () => { }); test('first time versioning', async () => { - const copyMock = jest.spyOn(fs, 'copySync').mockImplementation(); - const ensureMock = jest.spyOn(fs, 'ensureDirSync').mockImplementation(); - const writeMock = jest.spyOn(fs, 'writeFileSync'); + const copyMock = jest.spyOn(fs, 'copy').mockImplementation(); + const ensureMock = jest.spyOn(fs, 'ensureDir').mockImplementation(); + const writeMock = jest.spyOn(fs, 'writeFile'); let versionedSidebar; let versionedSidebarPath; writeMock.mockImplementationOnce((filepath, content) => { @@ -242,9 +242,9 @@ describe('docsVersion', () => { }); test('not the first time versioning', async () => { - const copyMock = jest.spyOn(fs, 'copySync').mockImplementation(); - const ensureMock = jest.spyOn(fs, 'ensureDirSync').mockImplementation(); - const writeMock = jest.spyOn(fs, 'writeFileSync'); + const copyMock = jest.spyOn(fs, 'copy').mockImplementation(); + const ensureMock = jest.spyOn(fs, 'ensureDir').mockImplementation(); + const writeMock = jest.spyOn(fs, 'writeFile'); let versionedSidebar; let versionedSidebarPath; writeMock.mockImplementationOnce((filepath, content) => { @@ -301,9 +301,9 @@ describe('docsVersion', () => { test('second docs instance versioning', async () => { const pluginId = 'community'; - const copyMock = jest.spyOn(fs, 'copySync').mockImplementation(); - const ensureMock = jest.spyOn(fs, 'ensureDirSync').mockImplementation(); - const writeMock = jest.spyOn(fs, 'writeFileSync'); + const copyMock = jest.spyOn(fs, 'copy').mockImplementation(); + const ensureMock = jest.spyOn(fs, 'ensureDir').mockImplementation(); + const writeMock = jest.spyOn(fs, 'writeFile'); let versionedSidebar; let versionedSidebarPath; writeMock.mockImplementationOnce((filepath, content) => { diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts index 6e90dc54d6..01356ccdcb 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import fs from 'fs'; +import fs from 'fs-extra'; import path from 'path'; import shell from 'shelljs'; @@ -64,9 +64,9 @@ describe('lastUpdate', () => { test('temporary created file that has no git timestamp', async () => { const tempFilePath = path.join(__dirname, '__fixtures__', '.temp'); - fs.writeFileSync(tempFilePath, 'Lorem ipsum :)'); + await fs.writeFile(tempFilePath, 'Lorem ipsum :)'); await expect(getFileLastUpdate(tempFilePath)).resolves.toBeNull(); - fs.unlinkSync(tempFilePath); + await fs.unlink(tempFilePath); }); test('Git does not exist', async () => { diff --git a/packages/docusaurus-plugin-content-docs/src/cli.ts b/packages/docusaurus-plugin-content-docs/src/cli.ts index a4cf9ee054..fea26eae58 100644 --- a/packages/docusaurus-plugin-content-docs/src/cli.ts +++ b/packages/docusaurus-plugin-content-docs/src/cli.ts @@ -47,8 +47,8 @@ async function createVersionedSidebarFile({ versionedSidebarsDir, `version-${version}-sidebars.json`, ); - fs.ensureDirSync(path.dirname(newSidebarFile)); - fs.writeFileSync( + await fs.ensureDir(path.dirname(newSidebarFile)); + await fs.writeFile( newSidebarFile, `${JSON.stringify(sidebars, null, 2)}\n`, 'utf8', @@ -104,8 +104,8 @@ export async function cliDocsVersionCommand( // Load existing versions. let versions = []; const versionsJSONFile = getVersionsFilePath(siteDir, pluginId); - if (fs.existsSync(versionsJSONFile)) { - versions = JSON.parse(fs.readFileSync(versionsJSONFile, 'utf8')); + if (await fs.pathExists(versionsJSONFile)) { + versions = JSON.parse(await fs.readFile(versionsJSONFile, 'utf8')); } // Check if version already exists. @@ -120,10 +120,13 @@ export async function cliDocsVersionCommand( // Copy docs files. const docsDir = path.join(siteDir, docsPath); - if (fs.existsSync(docsDir) && fs.readdirSync(docsDir).length > 0) { + if ( + (await fs.pathExists(docsDir)) && + (await fs.readdir(docsDir)).length > 0 + ) { const versionedDir = getVersionedDocsDirPath(siteDir, pluginId); const newVersionDir = path.join(versionedDir, `version-${version}`); - fs.copySync(docsDir, newVersionDir); + await fs.copy(docsDir, newVersionDir); } else { throw new Error(`${pluginIdLogPrefix}: there is no docs to version!`); } @@ -137,8 +140,11 @@ export async function cliDocsVersionCommand( // Update versions.json file. versions.unshift(version); - fs.ensureDirSync(path.dirname(versionsJSONFile)); - fs.writeFileSync(versionsJSONFile, `${JSON.stringify(versions, null, 2)}\n`); + await fs.ensureDir(path.dirname(versionsJSONFile)); + await fs.writeFile( + versionsJSONFile, + `${JSON.stringify(versions, null, 2)}\n`, + ); logger.success`name=${pluginIdLogPrefix}: version name=${version} created!`; } diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts index 56dc2cc9e9..79c2fc91f5 100644 --- a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts @@ -89,23 +89,26 @@ function createMarkdownOptions( }; } -const transform = (filepath: string, options?: Partial) => { +const transform = async ( + filepath: string, + options?: Partial, +) => { const markdownOptions = createMarkdownOptions(options); - const content = fs.readFileSync(filepath, 'utf-8'); + const content = await fs.readFile(filepath, 'utf-8'); const transformedContent = linkify(content, filepath, markdownOptions); return [content, transformedContent]; }; -test('transform nothing', () => { +test('transform nothing', async () => { const doc1 = path.join(versionCurrent.contentPath, 'doc1.md'); - const [content, transformedContent] = transform(doc1); + const [content, transformedContent] = await transform(doc1); expect(transformedContent).toMatchSnapshot(); expect(content).toEqual(transformedContent); }); -test('transform to correct links', () => { +test('transform to correct links', async () => { const doc2 = path.join(versionCurrent.contentPath, 'doc2.md'); - const [content, transformedContent] = transform(doc2); + const [content, transformedContent] = await transform(doc2); expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toContain('](/docs/doc1'); expect(transformedContent).toContain('](/docs/doc2'); @@ -118,19 +121,19 @@ test('transform to correct links', () => { expect(content).not.toEqual(transformedContent); }); -test('transform relative links', () => { +test('transform relative links', async () => { const doc3 = path.join(versionCurrent.contentPath, 'subdir', 'doc3.md'); - const [content, transformedContent] = transform(doc3); + const [content, transformedContent] = await transform(doc3); expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toContain('](/docs/doc2'); expect(transformedContent).not.toContain('](../doc2.md)'); expect(content).not.toEqual(transformedContent); }); -test('transforms reference links', () => { +test('transforms reference links', async () => { const doc4 = path.join(versionCurrent.contentPath, 'doc4.md'); - const [content, transformedContent] = transform(doc4); + const [content, transformedContent] = await transform(doc4); expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toContain('[doc1]: /docs/doc1'); expect(transformedContent).toContain('[doc2]: /docs/doc2'); @@ -139,10 +142,10 @@ test('transforms reference links', () => { expect(content).not.toEqual(transformedContent); }); -test('report broken markdown links', () => { +test('report broken markdown links', async () => { const doc5 = path.join(versionCurrent.contentPath, 'doc5.md'); const onBrokenMarkdownLink = jest.fn(); - const [content, transformedContent] = transform(doc5, { + const [content, transformedContent] = await transform(doc5, { onBrokenMarkdownLink, }); expect(transformedContent).toEqual(content); @@ -169,9 +172,9 @@ test('report broken markdown links', () => { } as BrokenMarkdownLink); }); -test('transforms absolute links in versioned docs', () => { +test('transforms absolute links in versioned docs', async () => { const doc2 = path.join(version100.contentPath, 'doc2.md'); - const [content, transformedContent] = transform(doc2); + const [content, transformedContent] = await transform(doc2); expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toContain('](/docs/1.0.0/subdir/doc1'); expect(transformedContent).toContain('](/docs/1.0.0/doc2#existing-docs'); @@ -180,9 +183,9 @@ test('transforms absolute links in versioned docs', () => { expect(content).not.toEqual(transformedContent); }); -test('transforms relative links in versioned docs', () => { +test('transforms relative links in versioned docs', async () => { const doc1 = path.join(version100.contentPath, 'subdir', 'doc1.md'); - const [content, transformedContent] = transform(doc1); + const [content, transformedContent] = await transform(doc1); expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toContain('](/docs/1.0.0/doc2'); expect(transformedContent).not.toContain('](../doc2.md)'); diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts index bdb65f73f7..b57822b5dd 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts @@ -85,7 +85,7 @@ export async function loadSidebarsFileUnsafe( // Non-existent sidebars file: no sidebars // Note: this edge case can happen on versioned docs, not current version // We avoid creating empty versioned sidebars file with the CLI - if (!fs.existsSync(sidebarFilePath)) { + if (!(await fs.pathExists(sidebarFilePath))) { return DisabledSidebars; } diff --git a/packages/docusaurus-plugin-content-docs/src/versions.ts b/packages/docusaurus-plugin-content-docs/src/versions.ts index f886a93189..52d1c476bf 100644 --- a/packages/docusaurus-plugin-content-docs/src/versions.ts +++ b/packages/docusaurus-plugin-content-docs/src/versions.ts @@ -418,7 +418,7 @@ function createVersionMetadata({ }; } -function checkVersionMetadataPaths({ +async function checkVersionMetadataPaths({ versionMetadata, context, }: { @@ -429,7 +429,7 @@ function checkVersionMetadataPaths({ const {siteDir} = context; const isCurrentVersion = versionName === CURRENT_VERSION_NAME; - if (!fs.existsSync(contentPath)) { + if (!(await fs.pathExists(contentPath))) { throw new Error( `The docs folder does not exist for version "${versionName}". A docs folder is expected to be found at ${path.relative( siteDir, @@ -446,7 +446,7 @@ function checkVersionMetadataPaths({ if ( isCurrentVersion && typeof sidebarFilePath === 'string' && - !fs.existsSync(sidebarFilePath) + !(await fs.pathExists(sidebarFilePath)) ) { throw new Error(`The path to the sidebar file does not exist at "${path.relative( siteDir, @@ -585,8 +585,10 @@ export async function readVersionsMetadata({ options, }), ); - versionsMetadata.forEach((versionMetadata) => - checkVersionMetadataPaths({versionMetadata, context}), + await Promise.all( + versionsMetadata.map((versionMetadata) => + checkVersionMetadataPaths({versionMetadata, context}), + ), ); return versionsMetadata; } diff --git a/packages/docusaurus-theme-search-algolia/package.json b/packages/docusaurus-theme-search-algolia/package.json index a0e2376278..ba52707baa 100644 --- a/packages/docusaurus-theme-search-algolia/package.json +++ b/packages/docusaurus-theme-search-algolia/package.json @@ -36,13 +36,13 @@ "algoliasearch-helper": "^3.7.0", "clsx": "^1.1.1", "eta": "^1.12.3", + "fs-extra": "^10.0.0", "lodash": "^4.17.21", "tslib": "^2.3.1", "utility-types": "^3.10.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.15", - "fs-extra": "^10.0.0" + "@docusaurus/module-type-aliases": "2.0.0-beta.15" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", diff --git a/packages/docusaurus-theme-search-algolia/src/index.ts b/packages/docusaurus-theme-search-algolia/src/index.ts index 1e38a7a898..4cb9597229 100644 --- a/packages/docusaurus-theme-search-algolia/src/index.ts +++ b/packages/docusaurus-theme-search-algolia/src/index.ts @@ -6,7 +6,7 @@ */ import path from 'path'; -import fs from 'fs'; +import fs from 'fs-extra'; import {defaultConfig, compile} from 'eta'; import {normalizeUrl} from '@docusaurus/utils'; import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations'; @@ -76,7 +76,7 @@ export default function themeSearchAlgolia(context: LoadContext): Plugin { const siteUrl = normalizeUrl([url, baseUrl]); try { - fs.writeFileSync( + await fs.writeFile( path.join(outDir, OPEN_SEARCH_FILENAME), renderOpenSearchTemplate({ title, diff --git a/packages/docusaurus-theme-translations/update.js b/packages/docusaurus-theme-translations/update.js index e1570edb9a..36ab30805c 100644 --- a/packages/docusaurus-theme-translations/update.js +++ b/packages/docusaurus-theme-translations/update.js @@ -6,7 +6,8 @@ */ // @ts-check -/* eslint-disable import/no-extraneous-dependencies */ +// TODO convert this to ESM, which would also allow TLA +/* eslint-disable import/no-extraneous-dependencies, no-restricted-properties */ const logger = require('@docusaurus/logger').default; const path = require('path'); diff --git a/packages/docusaurus-utils/src/__tests__/index.test.ts b/packages/docusaurus-utils/src/__tests__/index.test.ts index 6b84a8b5d6..8dbc0bc117 100644 --- a/packages/docusaurus-utils/src/__tests__/index.test.ts +++ b/packages/docusaurus-utils/src/__tests__/index.test.ts @@ -143,7 +143,7 @@ describe('load utils', () => { describe('generate', () => { test('behaves correctly', async () => { const writeMock = jest.spyOn(fs, 'writeFile').mockImplementation(() => {}); - const existsMock = jest.spyOn(fs, 'existsSync'); + const existsMock = jest.spyOn(fs, 'pathExists'); const readMock = jest.spyOn(fs, 'readFile'); // First call: no file, no cache diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index 2ae82d7bbb..7dd103f9d0 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -106,7 +106,7 @@ export async function generate( // If file already exists but its not in runtime cache yet, // we try to calculate the content hash and then compare // This is to avoid unnecessary overwriting and we can reuse old file. - if (!lastHash && fs.existsSync(filepath)) { + if (!lastHash && (await fs.pathExists(filepath))) { const lastContent = await fs.readFile(filepath, 'utf8'); lastHash = createHash('md5').update(lastContent).digest('hex'); fileHash.set(filepath, lastHash); diff --git a/packages/docusaurus/bin/beforeCli.mjs b/packages/docusaurus/bin/beforeCli.mjs index b4ec0dd456..5043f4073d 100644 --- a/packages/docusaurus/bin/beforeCli.mjs +++ b/packages/docusaurus/bin/beforeCli.mjs @@ -36,7 +36,7 @@ const { * * cache data is stored in `~/.config/configstore/update-notifier-@docusaurus` */ -function beforeCli() { +async function beforeCli() { const notifier = updateNotifier({ pkg: { name, @@ -98,7 +98,9 @@ function beforeCli() { .filter((p) => p.startsWith('@docusaurus')) .map((p) => p.concat('@latest')) .join(' '); - const isYarnUsed = fs.existsSync(path.resolve(process.cwd(), 'yarn.lock')); + const isYarnUsed = await fs.pathExists( + path.resolve(process.cwd(), 'yarn.lock'), + ); const upgradeCommand = isYarnUsed ? `yarn upgrade ${siteDocusaurusPackagesForUpdate}` : `npm i ${siteDocusaurusPackagesForUpdate}`; diff --git a/packages/docusaurus/bin/docusaurus.mjs b/packages/docusaurus/bin/docusaurus.mjs index 99798420f6..410f95f249 100755 --- a/packages/docusaurus/bin/docusaurus.mjs +++ b/packages/docusaurus/bin/docusaurus.mjs @@ -9,7 +9,7 @@ // @ts-check import logger from '@docusaurus/logger'; -import fs from 'fs'; +import fs from 'fs-extra'; import cli from 'commander'; import {createRequire} from 'module'; import { @@ -25,9 +25,9 @@ import { } from '../lib/index.js'; import beforeCli from './beforeCli.mjs'; -beforeCli(); +await beforeCli(); -const resolveDir = (dir = '.') => fs.realpathSync(dir); +const resolveDir = (dir = '.') => fs.realpath(dir); cli .version(createRequire(import.meta.url)('../package.json').version) @@ -56,8 +56,8 @@ cli '--no-minify', 'build website without minimizing JS bundles (default: false)', ) - .action((siteDir, {bundleAnalyzer, config, outDir, locale, minify}) => { - build(resolveDir(siteDir), { + .action(async (siteDir, {bundleAnalyzer, config, outDir, locale, minify}) => { + build(await resolveDir(siteDir), { bundleAnalyzer, outDir, config, @@ -74,8 +74,14 @@ cli 'copy TypeScript theme files when possible (default: false)', ) .option('--danger', 'enable swizzle for internal component of themes') - .action((themeName, componentName, siteDir, {typescript, danger}) => { - swizzle(resolveDir(siteDir), themeName, componentName, typescript, danger); + .action(async (themeName, componentName, siteDir, {typescript, danger}) => { + swizzle( + await resolveDir(siteDir), + themeName, + componentName, + typescript, + danger, + ); }); cli @@ -97,8 +103,8 @@ cli '--skip-build', 'skip building website before deploy it (default: false)', ) - .action((siteDir, {outDir, skipBuild, config}) => { - deploy(resolveDir(siteDir), { + .action(async (siteDir, {outDir, skipBuild, config}) => { + deploy(await resolveDir(siteDir), { outDir, config, skipBuild, @@ -124,17 +130,19 @@ cli '--poll [interval]', 'use polling rather than watching for reload (default: false). Can specify a poll interval in milliseconds', ) - .action((siteDir, {port, host, locale, config, hotOnly, open, poll}) => { - start(resolveDir(siteDir), { - port, - host, - locale, - config, - hotOnly, - open, - poll, - }); - }); + .action( + async (siteDir, {port, host, locale, config, hotOnly, open, poll}) => { + start(await resolveDir(siteDir), { + port, + host, + locale, + config, + hotOnly, + open, + poll, + }); + }, + ); cli .command('serve [siteDir]') @@ -151,7 +159,7 @@ cli .option('--build', 'build website before serving (default: false)') .option('-h, --host ', 'use specified host (default: localhost)') .action( - ( + async ( siteDir, { dir = 'build', @@ -161,7 +169,7 @@ cli config, }, ) => { - serve(resolveDir(siteDir), { + serve(await resolveDir(siteDir), { dir, port, build: buildSite, @@ -174,8 +182,8 @@ cli cli .command('clear [siteDir]') .description('Remove build artifacts.') - .action((siteDir) => { - clear(resolveDir(siteDir)); + .action(async (siteDir) => { + clear(await resolveDir(siteDir)); }); cli @@ -198,11 +206,11 @@ cli 'allows to init new written messages with a given prefix. This might help you to highlight untranslated message to make them stand out in the UI', ) .action( - ( + async ( siteDir, {locale = undefined, override = false, messagePrefix = '', config}, ) => { - writeTranslations(resolveDir(siteDir), { + writeTranslations(await resolveDir(siteDir), { locale, override, config, @@ -219,8 +227,8 @@ cli "keep the headings' casing, otherwise make all lowercase (default: false)", ) .option('--overwrite', 'overwrite existing heading IDs (default: false)') - .action((siteDir, files, options) => - writeHeadingIds(resolveDir(siteDir), files, options), + .action(async (siteDir, files, options) => + writeHeadingIds(await resolveDir(siteDir), files, options), ); cli.arguments('').action((cmd) => { @@ -246,7 +254,7 @@ function isInternalCommand(command) { async function run() { if (!isInternalCommand(process.argv.slice(2)[0])) { - await externalCommand(cli, resolveDir('.')); + await externalCommand(cli, await resolveDir('.')); } cli.parse(process.argv); diff --git a/packages/docusaurus/package.json b/packages/docusaurus/package.json index c361d0c5d1..64ac317006 100644 --- a/packages/docusaurus/package.json +++ b/packages/docusaurus/package.json @@ -56,6 +56,7 @@ "boxen": "^5.1.2", "chokidar": "^3.5.3", "clean-css": "^5.2.4", + "combine-promises": "^1.1.0", "commander": "^5.1.0", "copy-webpack-plugin": "^10.2.4", "core-js": "^3.21.1", diff --git a/packages/docusaurus/src/commands/build.ts b/packages/docusaurus/src/commands/build.ts index 916515f8b0..e1ad88e567 100644 --- a/packages/docusaurus/src/commands/build.ts +++ b/packages/docusaurus/src/commands/build.ts @@ -130,7 +130,7 @@ async function buildLocale({ 'client-manifest.json', ); let clientConfig: Configuration = merge( - createClientConfig(props, cliOptions.minify), + await createClientConfig(props, cliOptions.minify), { plugins: [ // Remove/clean build folders before building bundles. @@ -148,23 +148,26 @@ async function buildLocale({ const allCollectedLinks: Record = {}; - let serverConfig: Configuration = createServerConfig({ + let serverConfig: Configuration = await createServerConfig({ props, onLinksCollected: (staticPagePath, links) => { allCollectedLinks[staticPagePath] = links; }, }); - serverConfig = merge(serverConfig, { - plugins: [ - new CopyWebpackPlugin({ - patterns: staticDirectories - .map((dir) => path.resolve(siteDir, dir)) - .filter(fs.existsSync) - .map((dir) => ({from: dir, to: outDir})), - }), - ], - }); + if (staticDirectories.length > 0) { + await Promise.all(staticDirectories.map((dir) => fs.ensureDir(dir))); + + serverConfig = merge(serverConfig, { + plugins: [ + new CopyWebpackPlugin({ + patterns: staticDirectories + .map((dir) => path.resolve(siteDir, dir)) + .map((dir) => ({from: dir, to: outDir})), + }), + ], + }); + } // Plugin Lifecycle - configureWebpack and configurePostCss. plugins.forEach((plugin) => { diff --git a/packages/docusaurus/src/commands/start.ts b/packages/docusaurus/src/commands/start.ts index 706ff325a3..4a02bdf57b 100644 --- a/packages/docusaurus/src/commands/start.ts +++ b/packages/docusaurus/src/commands/start.ts @@ -107,7 +107,7 @@ export default async function start( ? (cliOptions.poll as number) : undefined, }; - const httpsConfig = getHttpsConfig(); + const httpsConfig = await getHttpsConfig(); const fsWatcher = chokidar.watch(pathsToWatch, { cwd: siteDir, ignoreInitial: true, @@ -118,7 +118,7 @@ export default async function start( fsWatcher.on(event, reload), ); - let config: webpack.Configuration = merge(createClientConfig(props), { + let config: webpack.Configuration = merge(await createClientConfig(props), { infrastructureLogging: { // Reduce log verbosity, see https://github.com/facebook/docusaurus/pull/5420#issuecomment-906613105 level: 'warn', diff --git a/packages/docusaurus/src/commands/swizzle.ts b/packages/docusaurus/src/commands/swizzle.ts index bba9dc90ef..95580cdfd4 100644 --- a/packages/docusaurus/src/commands/swizzle.ts +++ b/packages/docusaurus/src/commands/swizzle.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +/* eslint-disable no-restricted-properties */ import logger from '@docusaurus/logger'; import fs from 'fs-extra'; import importFresh from 'import-fresh'; diff --git a/packages/docusaurus/src/commands/writeTranslations.ts b/packages/docusaurus/src/commands/writeTranslations.ts index 9d7cdfe3b6..017225bdf1 100644 --- a/packages/docusaurus/src/commands/writeTranslations.ts +++ b/packages/docusaurus/src/commands/writeTranslations.ts @@ -97,7 +97,7 @@ Available locales are: ${context.i18n.locales.join(',')}.`, const babelOptions = getBabelOptions({ isServer: true, - babelOptions: getCustomBabelConfigFilePath(siteDir), + babelOptions: await getCustomBabelConfigFilePath(siteDir), }); const extractedCodeTranslations = await extractSiteSourceCodeTranslations( siteDir, diff --git a/packages/docusaurus/src/server/brokenLinks.ts b/packages/docusaurus/src/server/brokenLinks.ts index 84bc87c810..0f8a51e122 100644 --- a/packages/docusaurus/src/server/brokenLinks.ts +++ b/packages/docusaurus/src/server/brokenLinks.ts @@ -20,6 +20,7 @@ import { } from '@docusaurus/utils'; import {getAllFinalRoutes} from './utils'; import path from 'path'; +import combinePromises from 'combine-promises'; function toReactRouterRoutes(routes: RouteConfig[]): RRRouteConfig[] { // @ts-expect-error: types incompatible??? @@ -158,9 +159,9 @@ export function getBrokenLinksErrorMessage( ); } -function isExistingFile(filePath: string) { +async function isExistingFile(filePath: string) { try { - return fs.statSync(filePath).isFile(); + return (await fs.stat(filePath)).isFile(); } catch (e) { return false; } @@ -177,8 +178,7 @@ export async function filterExistingFileLinks({ outDir: string; allCollectedLinks: Record; }): Promise> { - // not easy to make this async :'( - function linkFileExists(link: string): boolean { + async function linkFileExists(link: string) { // /baseUrl/javadoc/ -> /outDir/javadoc const baseFilePath = removeSuffix( `${outDir}/${removePrefix(link, baseUrl)}`, @@ -194,11 +194,22 @@ export async function filterExistingFileLinks({ filePathsToTry.push(path.join(baseFilePath, 'index.html')); } - return filePathsToTry.some(isExistingFile); + for (const file of filePathsToTry) { + if (await isExistingFile(file)) { + return true; + } + } + return false; } - return _.mapValues(allCollectedLinks, (links) => - links.filter((link) => !linkFileExists(link)), + return combinePromises( + _.mapValues(allCollectedLinks, async (links) => + ( + await Promise.all( + links.map(async (link) => ((await linkFileExists(link)) ? '' : link)), + ) + ).filter(Boolean), + ), ); } diff --git a/packages/docusaurus/src/server/config.ts b/packages/docusaurus/src/server/config.ts index 53bfec0a95..5f6d0ef01f 100644 --- a/packages/docusaurus/src/server/config.ts +++ b/packages/docusaurus/src/server/config.ts @@ -13,7 +13,7 @@ import {validateConfig} from './configValidation'; export default async function loadConfig( configPath: string, ): Promise { - if (!fs.existsSync(configPath)) { + if (!(await fs.pathExists(configPath))) { throw new Error(`Config file at "${configPath}" not found.`); } diff --git a/packages/docusaurus/src/server/index.ts b/packages/docusaurus/src/server/index.ts index 88a2c022ce..79e38c2549 100644 --- a/packages/docusaurus/src/server/index.ts +++ b/packages/docusaurus/src/server/index.ts @@ -403,10 +403,12 @@ ${Object.keys(registry) // Version metadata. const siteMetadata: DocusaurusSiteMetadata = { - docusaurusVersion: getPackageJsonVersion( + docusaurusVersion: (await getPackageJsonVersion( path.join(__dirname, '../../package.json'), - )!, - siteVersion: getPackageJsonVersion(path.join(siteDir, 'package.json')), + ))!, + siteVersion: await getPackageJsonVersion( + path.join(siteDir, 'package.json'), + ), pluginVersions: {}, }; plugins diff --git a/packages/docusaurus/src/server/plugins/init.ts b/packages/docusaurus/src/server/plugins/init.ts index 2ec1265fb7..bca734d302 100644 --- a/packages/docusaurus/src/server/plugins/init.ts +++ b/packages/docusaurus/src/server/plugins/init.ts @@ -133,9 +133,9 @@ export default async function initPlugins({ // siteDir's package.json declares the dependency on these plugins. const pluginRequire = createRequire(context.siteConfigPath); - function doGetPluginVersion( + async function doGetPluginVersion( normalizedPluginConfig: NormalizedPluginConfig, - ): DocusaurusPluginVersionInformation { + ): Promise { // get plugin version if (normalizedPluginConfig.pluginModule?.path) { const pluginPath = pluginRequire.resolve( @@ -187,7 +187,7 @@ export default async function initPlugins({ pluginRequire, ); const pluginVersion: DocusaurusPluginVersionInformation = - doGetPluginVersion(normalizedPluginConfig); + await doGetPluginVersion(normalizedPluginConfig); const pluginOptions = doValidatePluginOptions(normalizedPluginConfig); // Side-effect: merge the normalized theme config in the original one diff --git a/packages/docusaurus/src/server/themes/__tests__/alias.test.ts b/packages/docusaurus/src/server/themes/__tests__/alias.test.ts index d64e3e5b39..dd140c3ee1 100644 --- a/packages/docusaurus/src/server/themes/__tests__/alias.test.ts +++ b/packages/docusaurus/src/server/themes/__tests__/alias.test.ts @@ -10,10 +10,10 @@ import fs from 'fs-extra'; import themeAlias from '../alias'; describe('themeAlias', () => { - test('valid themePath 1 with components', () => { + test('valid themePath 1 with components', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'theme-1'); - const alias = themeAlias(themePath, true); + const alias = await themeAlias(themePath, true); // Testing entries, because order matters! expect(Object.entries(alias)).toEqual( Object.entries({ @@ -26,10 +26,10 @@ describe('themeAlias', () => { expect(alias).not.toEqual({}); }); - test('valid themePath 1 with components without original', () => { + test('valid themePath 1 with components without original', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'theme-1'); - const alias = themeAlias(themePath, false); + const alias = await themeAlias(themePath, false); // Testing entries, because order matters! expect(Object.entries(alias)).toEqual( Object.entries({ @@ -40,10 +40,10 @@ describe('themeAlias', () => { expect(alias).not.toEqual({}); }); - test('valid themePath 2 with components', () => { + test('valid themePath 2 with components', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'theme-2'); - const alias = themeAlias(themePath, true); + const alias = await themeAlias(themePath, true); // Testing entries, because order matters! expect(Object.entries(alias)).toEqual( Object.entries({ @@ -83,10 +83,10 @@ describe('themeAlias', () => { expect(alias).not.toEqual({}); }); - test('valid themePath 2 with components without original', () => { + test('valid themePath 2 with components without original', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'theme-2'); - const alias = themeAlias(themePath, false); + const alias = await themeAlias(themePath, false); // Testing entries, because order matters! expect(Object.entries(alias)).toEqual( Object.entries({ @@ -107,26 +107,26 @@ describe('themeAlias', () => { expect(alias).not.toEqual({}); }); - test('valid themePath with no components', () => { + test('valid themePath with no components', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'empty-theme'); - fs.ensureDirSync(themePath); - const alias = themeAlias(themePath, true); + await fs.ensureDir(themePath); + const alias = await themeAlias(themePath, true); expect(alias).toEqual({}); }); - test('valid themePath with no components without original', () => { + test('valid themePath with no components without original', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'empty-theme'); - fs.ensureDirSync(themePath); - const alias = themeAlias(themePath, false); + await fs.ensureDir(themePath); + const alias = await themeAlias(themePath, false); expect(alias).toEqual({}); }); - test('invalid themePath that does not exist', () => { + test('invalid themePath that does not exist', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, '__noExist__'); - const alias = themeAlias(themePath, true); + const alias = await themeAlias(themePath, true); expect(alias).toEqual({}); }); }); diff --git a/packages/docusaurus/src/server/themes/__tests__/index.test.ts b/packages/docusaurus/src/server/themes/__tests__/index.test.ts index 157ec169d3..9c42f76074 100644 --- a/packages/docusaurus/src/server/themes/__tests__/index.test.ts +++ b/packages/docusaurus/src/server/themes/__tests__/index.test.ts @@ -9,12 +9,12 @@ import path from 'path'; import {loadThemeAliases} from '../index'; describe('loadThemeAliases', () => { - test('next alias can override the previous alias', () => { + test('next alias can override the previous alias', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const theme1Path = path.join(fixtures, 'theme-1'); const theme2Path = path.join(fixtures, 'theme-2'); - const alias = loadThemeAliases([theme1Path, theme2Path], []); + const alias = await loadThemeAliases([theme1Path, theme2Path], []); // Testing entries, because order matters! expect(Object.entries(alias)).toEqual( diff --git a/packages/docusaurus/src/server/themes/alias.ts b/packages/docusaurus/src/server/themes/alias.ts index 3ced5b26be..f6f3478c94 100644 --- a/packages/docusaurus/src/server/themes/alias.ts +++ b/packages/docusaurus/src/server/themes/alias.ts @@ -25,16 +25,15 @@ export function sortAliases(aliases: ThemeAliases): ThemeAliases { return Object.fromEntries(entries); } -// TODO make async -export default function themeAlias( +export default async function themeAlias( themePath: string, addOriginalAlias: boolean, -): ThemeAliases { - if (!fs.pathExistsSync(themePath)) { +): Promise { + if (!(await fs.pathExists(themePath))) { return {}; } - const themeComponentFiles = Globby.sync(['**/*.{js,jsx,ts,tsx}'], { + const themeComponentFiles = await Globby(['**/*.{js,jsx,ts,tsx}'], { cwd: themePath, }); diff --git a/packages/docusaurus/src/server/themes/index.ts b/packages/docusaurus/src/server/themes/index.ts index cb973e0e2a..9c4940aa8b 100644 --- a/packages/docusaurus/src/server/themes/index.ts +++ b/packages/docusaurus/src/server/themes/index.ts @@ -12,14 +12,14 @@ import themeAlias, {sortAliases} from './alias'; const ThemeFallbackDir = path.resolve(__dirname, '../../client/theme-fallback'); -export function loadThemeAliases( +export async function loadThemeAliases( themePaths: string[], userThemePaths: string[], -): ThemeAliases { +): Promise { const aliases: ThemeAliases = {}; - themePaths.forEach((themePath) => { - const themeAliases = themeAlias(themePath, true); + for (const themePath of themePaths) { + const themeAliases = await themeAlias(themePath, true); Object.keys(themeAliases).forEach((aliasKey) => { // If this alias shadows a previous one, use @theme-init to preserve the // initial one. @theme-init is only applied once: to the initial theme @@ -33,12 +33,12 @@ export function loadThemeAliases( } aliases[aliasKey] = themeAliases[aliasKey]; }); - }); + } - userThemePaths.forEach((themePath) => { - const userThemeAliases = themeAlias(themePath, false); + for (const themePath of userThemePaths) { + const userThemeAliases = await themeAlias(themePath, false); Object.assign(aliases, userThemeAliases); - }); + } return sortAliases(aliases); } @@ -49,7 +49,7 @@ export function loadPluginsThemeAliases({ }: { siteDir: string; plugins: LoadedPlugin[]; -}): ThemeAliases { +}): Promise { const pluginThemes: string[] = plugins .map((plugin) => (plugin.getThemePath ? plugin.getThemePath() : undefined)) .filter((x): x is string => Boolean(x)); diff --git a/packages/docusaurus/src/server/utils.ts b/packages/docusaurus/src/server/utils.ts index 37a3f160d6..e4e22df7d9 100644 --- a/packages/docusaurus/src/server/utils.ts +++ b/packages/docusaurus/src/server/utils.ts @@ -6,7 +6,7 @@ */ import type {RouteConfig} from '@docusaurus/types'; -import nodePath from 'path'; +import path from 'path'; import {posixPath, Globby} from '@docusaurus/utils'; // Recursively get the final routes (routes with no subroutes) @@ -26,7 +26,7 @@ export async function safeGlobby( // Required for Windows support, as paths using \ should not be used by globby // (also using the windows hard drive prefix like c: is not a good idea) const globPaths = patterns.map((dirPath) => - posixPath(nodePath.relative(process.cwd(), dirPath)), + posixPath(path.relative(process.cwd(), dirPath)), ); return Globby(globPaths, options); diff --git a/packages/docusaurus/src/server/versions/__tests__/index.test.ts b/packages/docusaurus/src/server/versions/__tests__/index.test.ts index e7ba75d5f6..377cb6f2ab 100644 --- a/packages/docusaurus/src/server/versions/__tests__/index.test.ts +++ b/packages/docusaurus/src/server/versions/__tests__/index.test.ts @@ -9,27 +9,27 @@ import {getPluginVersion} from '..'; import path from 'path'; describe('getPluginVersion', () => { - it('Can detect external packages plugins versions of correctly.', () => { - expect( + it('Can detect external packages plugins versions of correctly.', async () => { + await expect( getPluginVersion( path.join(__dirname, '..', '__fixtures__', 'dummy-plugin.js'), // Make the plugin appear external. path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'), ), - ).toEqual({type: 'package', version: 'random-version'}); + ).resolves.toEqual({type: 'package', version: 'random-version'}); }); - it('Can detect project plugins versions correctly.', () => { - expect( + it('Can detect project plugins versions correctly.', async () => { + await expect( getPluginVersion( path.join(__dirname, '..', '__fixtures__', 'dummy-plugin.js'), // Make the plugin appear project local. path.join(__dirname, '..', '__fixtures__'), ), - ).toEqual({type: 'project'}); + ).resolves.toEqual({type: 'project'}); }); - it('Can detect local packages versions correctly.', () => { - expect(getPluginVersion('/', '/')).toEqual({type: 'local'}); + it('Can detect local packages versions correctly.', async () => { + await expect(getPluginVersion('/', '/')).resolves.toEqual({type: 'local'}); }); }); diff --git a/packages/docusaurus/src/server/versions/index.ts b/packages/docusaurus/src/server/versions/index.ts index 3df9f84923..32c374463a 100644 --- a/packages/docusaurus/src/server/versions/index.ts +++ b/packages/docusaurus/src/server/versions/index.ts @@ -6,13 +6,13 @@ */ import type {DocusaurusPluginVersionInformation} from '@docusaurus/types'; -import {existsSync, lstatSync} from 'fs-extra'; -import {dirname, join} from 'path'; +import fs from 'fs-extra'; +import path from 'path'; -export function getPackageJsonVersion( +export async function getPackageJsonVersion( packageJsonPath: string, -): string | undefined { - if (existsSync(packageJsonPath)) { +): Promise { + if (await fs.pathExists(packageJsonPath)) { // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require const {version} = require(packageJsonPath); return typeof version === 'string' ? version : undefined; @@ -20,10 +20,10 @@ export function getPackageJsonVersion( return undefined; } -export function getPackageJsonName( +export async function getPackageJsonName( packageJsonPath: string, -): string | undefined { - if (existsSync(packageJsonPath)) { +): Promise { + if (await fs.pathExists(packageJsonPath)) { // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require const {name} = require(packageJsonPath); return typeof name === 'string' ? name : undefined; @@ -31,17 +31,20 @@ export function getPackageJsonName( return undefined; } -export function getPluginVersion( +export async function getPluginVersion( pluginPath: string, siteDir: string, -): DocusaurusPluginVersionInformation { - let potentialPluginPackageJsonDirectory = dirname(pluginPath); +): Promise { + let potentialPluginPackageJsonDirectory = path.dirname(pluginPath); while (potentialPluginPackageJsonDirectory !== '/') { - const packageJsonPath = join( + const packageJsonPath = path.join( potentialPluginPackageJsonDirectory, 'package.json', ); - if (existsSync(packageJsonPath) && lstatSync(packageJsonPath).isFile()) { + if ( + (await fs.pathExists(packageJsonPath)) && + (await fs.lstat(packageJsonPath)).isFile() + ) { if (potentialPluginPackageJsonDirectory === siteDir) { // If the plugin belongs to the same docusaurus project, we classify it // as local plugin. @@ -49,11 +52,11 @@ export function getPluginVersion( } return { type: 'package', - name: getPackageJsonName(packageJsonPath), - version: getPackageJsonVersion(packageJsonPath), + name: await getPackageJsonName(packageJsonPath), + version: await getPackageJsonVersion(packageJsonPath), }; } - potentialPluginPackageJsonDirectory = dirname( + potentialPluginPackageJsonDirectory = path.dirname( potentialPluginPackageJsonDirectory, ); } diff --git a/packages/docusaurus/src/webpack/__tests__/base.test.ts b/packages/docusaurus/src/webpack/__tests__/base.test.ts index 14c41df56e..8e2ccb9703 100644 --- a/packages/docusaurus/src/webpack/__tests__/base.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/base.test.ts @@ -68,10 +68,10 @@ describe('babel transpilation exclude logic', () => { }); describe('getDocusaurusAliases()', () => { - test('return appropriate webpack aliases', () => { + test('return appropriate webpack aliases', async () => { // using relative paths makes tests work everywhere const relativeDocusaurusAliases = _.mapValues( - getDocusaurusAliases(), + await getDocusaurusAliases(), (aliasValue) => posixPath(path.relative(__dirname, aliasValue)), ); expect(relativeDocusaurusAliases).toMatchSnapshot(); @@ -120,10 +120,10 @@ describe('base webpack config', () => { jest.restoreAllMocks(); }); - test('should create webpack aliases', () => { + test('should create webpack aliases', async () => { // @ts-expect-error: Docusaurus webpack alias is always an object const aliases: ThemeAliases = - createBaseConfig(props, true).resolve?.alias ?? {}; + (await createBaseConfig(props, true)).resolve?.alias ?? {}; // Make aliases relative so that test work on all computers const relativeAliases = _.mapValues(aliases, (a) => posixPath(path.relative(props.siteDir, a)), @@ -131,14 +131,14 @@ describe('base webpack config', () => { expect(relativeAliases).toMatchSnapshot(); }); - test('should use svg rule', () => { + test('should use svg rule', async () => { const fileLoaderUtils = utils.getFileLoaderUtils(); const mockSvg = jest.spyOn(fileLoaderUtils.rules, 'svg'); jest .spyOn(utils, 'getFileLoaderUtils') .mockImplementation(() => fileLoaderUtils); - createBaseConfig(props, false, false); + await createBaseConfig(props, false, false); expect(mockSvg).toBeCalled(); }); }); diff --git a/packages/docusaurus/src/webpack/__tests__/client.test.ts b/packages/docusaurus/src/webpack/__tests__/client.test.ts index 5e93c1e739..b9f40387b4 100644 --- a/packages/docusaurus/src/webpack/__tests__/client.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/client.test.ts @@ -14,7 +14,7 @@ describe('webpack dev config', () => { test('simple', async () => { console.log = jest.fn(); const props = await loadSetup('simple'); - const config = createClientConfig(props); + const config = await createClientConfig(props); const errors = validate(config); expect(errors).toBeUndefined(); }); @@ -22,7 +22,7 @@ describe('webpack dev config', () => { test('custom', async () => { console.log = jest.fn(); const props = await loadSetup('custom'); - const config = createClientConfig(props); + const config = await createClientConfig(props); const errors = validate(config); expect(errors).toBeUndefined(); }); diff --git a/packages/docusaurus/src/webpack/__tests__/server.test.ts b/packages/docusaurus/src/webpack/__tests__/server.test.ts index 75f95fd56a..be8f72e08e 100644 --- a/packages/docusaurus/src/webpack/__tests__/server.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/server.test.ts @@ -14,7 +14,7 @@ describe('webpack production config', () => { test('simple', async () => { console.log = jest.fn(); const props = await loadSetup('simple'); - const config = createServerConfig({props}); + const config = await createServerConfig({props}); const errors = validate(config); expect(errors).toBeUndefined(); }); @@ -22,7 +22,7 @@ describe('webpack production config', () => { test('custom', async () => { console.log = jest.fn(); const props = await loadSetup('custom'); - const config = createServerConfig({props}); + const config = await createServerConfig({props}); const errors = validate(config); expect(errors).toBeUndefined(); }); diff --git a/packages/docusaurus/src/webpack/base.ts b/packages/docusaurus/src/webpack/base.ts index 1a2b57f1d8..7f4f963139 100644 --- a/packages/docusaurus/src/webpack/base.ts +++ b/packages/docusaurus/src/webpack/base.ts @@ -44,13 +44,13 @@ export function excludeJS(modulePath: string): boolean { ); } -export function getDocusaurusAliases(): Record { +export async function getDocusaurusAliases(): Promise> { const dirPath = path.resolve(__dirname, '../client/exports'); const extensions = ['.js', '.ts', '.tsx']; const aliases: Record = {}; - fs.readdirSync(dirPath) + (await fs.readdir(dirPath)) .filter((fileName) => extensions.includes(path.extname(fileName))) .forEach((fileName) => { const fileNameWithoutExtension = path.basename( @@ -64,11 +64,11 @@ export function getDocusaurusAliases(): Record { return aliases; } -export function createBaseConfig( +export async function createBaseConfig( props: Props, isServer: boolean, minify: boolean = true, -): Configuration { +): Promise { const { outDir, siteDir, @@ -90,7 +90,7 @@ export function createBaseConfig( const name = isServer ? 'server' : 'client'; const mode = isProd ? 'production' : 'development'; - const themeAliases = loadPluginsThemeAliases({siteDir, plugins}); + const themeAliases = await loadPluginsThemeAliases({siteDir, plugins}); return { mode, @@ -158,7 +158,7 @@ export function createBaseConfig( // Note: a @docusaurus alias would also catch @docusaurus/theme-common, // so we use fine-grained aliases instead // '@docusaurus': path.resolve(__dirname, '../client/exports'), - ...getDocusaurusAliases(), + ...(await getDocusaurusAliases()), ...themeAliases, }, // This allows you to set a fallback for where Webpack should look for @@ -169,7 +169,7 @@ export function createBaseConfig( modules: [ path.resolve(__dirname, '..', '..', 'node_modules'), 'node_modules', - path.resolve(fs.realpathSync(process.cwd()), 'node_modules'), + path.resolve(await fs.realpath(process.cwd()), 'node_modules'), ], }, resolveLoader: { @@ -225,7 +225,7 @@ export function createBaseConfig( use: [ getCustomizableJSLoader(siteConfig.webpack?.jsLoader)({ isServer, - babelOptions: getCustomBabelConfigFilePath(siteDir), + babelOptions: await getCustomBabelConfigFilePath(siteDir), }), ], }, diff --git a/packages/docusaurus/src/webpack/client.ts b/packages/docusaurus/src/webpack/client.ts index 0942e0c078..ffe0a704eb 100644 --- a/packages/docusaurus/src/webpack/client.ts +++ b/packages/docusaurus/src/webpack/client.ts @@ -15,12 +15,12 @@ import {createBaseConfig} from './base'; import ChunkAssetPlugin from './plugins/ChunkAssetPlugin'; import LogPlugin from './plugins/LogPlugin'; -export default function createClientConfig( +export default async function createClientConfig( props: Props, minify: boolean = true, -): Configuration { +): Promise { const isBuilding = process.argv[2] === 'build'; - const config = createBaseConfig(props, false, minify); + const config = await createBaseConfig(props, false, minify); const clientConfig = merge(config, { // useless, disabled on purpose (errors on existing sites with no diff --git a/packages/docusaurus/src/webpack/server.ts b/packages/docusaurus/src/webpack/server.ts index 85aa0a6722..cc3578d058 100644 --- a/packages/docusaurus/src/webpack/server.ts +++ b/packages/docusaurus/src/webpack/server.ts @@ -18,13 +18,13 @@ import {NODE_MAJOR_VERSION, NODE_MINOR_VERSION} from '@docusaurus/utils'; // Forked for Docusaurus: https://github.com/slorber/static-site-generator-webpack-plugin import StaticSiteGeneratorPlugin from '@slorber/static-site-generator-webpack-plugin'; -export default function createServerConfig({ +export default async function createServerConfig({ props, onLinksCollected = () => {}, }: { props: Props; onLinksCollected?: (staticPagePath: string, links: string[]) => void; -}): Configuration { +}): Promise { const { baseUrl, routesPaths, @@ -35,7 +35,7 @@ export default function createServerConfig({ ssrTemplate, siteConfig: {noIndex, trailingSlash}, } = props; - const config = createBaseConfig(props, true); + const config = await createBaseConfig(props, true); const routesLocation: Record = {}; // Array of paths to be rendered. Relative to output directory diff --git a/packages/docusaurus/src/webpack/utils.ts b/packages/docusaurus/src/webpack/utils.ts index 6d7e31b6fa..5c17c05522 100644 --- a/packages/docusaurus/src/webpack/utils.ts +++ b/packages/docusaurus/src/webpack/utils.ts @@ -101,14 +101,14 @@ export function getStyleLoaders( ]; } -export function getCustomBabelConfigFilePath( +export async function getCustomBabelConfigFilePath( siteDir: string, -): string | undefined { +): Promise { const customBabelConfigurationPath = path.join( siteDir, BABEL_CONFIG_FILE_NAME, ); - return fs.existsSync(customBabelConfigurationPath) + return (await fs.pathExists(customBabelConfigurationPath)) ? customBabelConfigurationPath : undefined; } @@ -333,19 +333,21 @@ ${err}`, } // Read file and throw an error if it doesn't exist -function readEnvFile(file: string, type: string) { - if (!fs.existsSync(file)) { +async function readEnvFile(file: string, type: string) { + if (!(await fs.pathExists(file))) { throw new Error( `You specified ${type} in your env, but the file "${file}" can't be found.`, ); } - return fs.readFileSync(file); + return fs.readFile(file); } -const appDirectory = fs.realpathSync(process.cwd()); // Get the https config // Return cert files if provided in env, otherwise just true or false -export function getHttpsConfig(): boolean | {cert: Buffer; key: Buffer} { +export async function getHttpsConfig(): Promise< + boolean | {cert: Buffer; key: Buffer} +> { + const appDirectory = await fs.realpath(process.cwd()); const {SSL_CRT_FILE, SSL_KEY_FILE, HTTPS} = process.env; const isHttps = HTTPS === 'true'; @@ -353,8 +355,8 @@ export function getHttpsConfig(): boolean | {cert: Buffer; key: Buffer} { const crtFile = path.resolve(appDirectory, SSL_CRT_FILE); const keyFile = path.resolve(appDirectory, SSL_KEY_FILE); const config = { - cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), - key: readEnvFile(keyFile, 'SSL_KEY_FILE'), + cert: await readEnvFile(crtFile, 'SSL_CRT_FILE'), + key: await readEnvFile(keyFile, 'SSL_KEY_FILE'), }; validateKeyAndCerts({...config, keyFile, crtFile}); diff --git a/website/_dogfooding/dogfooding.config.js b/website/_dogfooding/dogfooding.config.js index 3d087da411..968d90e385 100644 --- a/website/_dogfooding/dogfooding.config.js +++ b/website/_dogfooding/dogfooding.config.js @@ -19,6 +19,7 @@ const dogfoodingPluginInstances = [ // Using a symlinked folder as source, test for use-case https://github.com/facebook/docusaurus/issues/3272 // The target folder uses a _ prefix to test against an edge case regarding MDX partials: https://github.com/facebook/docusaurus/discussions/5181#discussioncomment-1018079 + // eslint-disable-next-line no-restricted-properties path: fs.realpathSync('_dogfooding/docs-tests-symlink'), showLastUpdateTime: true, sidebarItemsGenerator(args) { diff --git a/website/src/data/__tests__/user.test.ts b/website/src/data/__tests__/user.test.ts index f6e80becea..bb3b83d91f 100644 --- a/website/src/data/__tests__/user.test.ts +++ b/website/src/data/__tests__/user.test.ts @@ -49,7 +49,7 @@ expect.extend({ describe('users', () => { sortedUsers.forEach((user) => { - test(user.title, () => { + test(user.title, async () => { Joi.attempt( user, Joi.object({ @@ -86,6 +86,7 @@ describe('users', () => { }); const imageDir = path.join(__dirname, '../showcase'); + // eslint-disable-next-line no-restricted-properties const files = fs .readdirSync(imageDir) .filter((file) => ['.png', 'jpg', '.jpeg'].includes(path.extname(file)));