refactor: convert all fs methods to async (#6725)

* refactor: convert all fs methods to async

* fix snap
This commit is contained in:
Joshua Chen 2022-02-20 10:21:33 +08:00 committed by GitHub
parent c0b3c9af65
commit c6d0d812eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 518 additions and 429 deletions

View file

@ -107,6 +107,36 @@ module.exports = {
property, property,
message: `Use ${alternative} instead.`, 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': [ 'no-restricted-syntax': [
WARNING, WARNING,

View file

@ -37,16 +37,14 @@ async function updatePkg(pkgPath: string, obj: Record<string, unknown>) {
await fs.outputFile(pkgPath, `${JSON.stringify(newPkg, null, 2)}\n`); await fs.outputFile(pkgPath, `${JSON.stringify(newPkg, null, 2)}\n`);
} }
function readTemplates(templatesDir: string) { async function readTemplates(templatesDir: string) {
const templates = fs const templates = (await fs.readdir(templatesDir)).filter(
.readdirSync(templatesDir) (d) =>
.filter( !d.startsWith('.') &&
(d) => !d.startsWith('README') &&
!d.startsWith('.') && !d.endsWith(TypeScriptTemplateSuffix) &&
!d.startsWith('README') && d !== 'shared',
!d.endsWith(TypeScriptTemplateSuffix) && );
d !== 'shared',
);
// Classic should be first in list! // Classic should be first in list!
return _.sortBy(templates, (t) => t !== RecommendedTemplate); return _.sortBy(templates, (t) => t !== RecommendedTemplate);
@ -86,8 +84,8 @@ async function copyTemplate(
if (tsBaseTemplate) { if (tsBaseTemplate) {
const tsBaseTemplatePath = path.resolve(templatesDir, tsBaseTemplate); const tsBaseTemplatePath = path.resolve(templatesDir, tsBaseTemplate);
await fs.copy(tsBaseTemplatePath, dest, { await fs.copy(tsBaseTemplatePath, dest, {
filter: (filePath) => filter: async (filePath) =>
fs.statSync(filePath).isDirectory() || (await fs.stat(filePath)).isDirectory() ||
path.extname(filePath) === '.css' || path.extname(filePath) === '.css' ||
path.basename(filePath) === 'docusaurus.config.js', path.basename(filePath) === 'docusaurus.config.js',
}); });
@ -96,7 +94,7 @@ async function copyTemplate(
await fs.copy(path.resolve(templatesDir, template), dest, { await fs.copy(path.resolve(templatesDir, template), dest, {
// Symlinks don't exist in published NPM packages anymore, so this is only // Symlinks don't exist in published NPM packages anymore, so this is only
// to prevent errors during local testing // 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<void> { ): Promise<void> {
const useYarn = cliOptions.useNpm ? false : hasYarn(); const useYarn = cliOptions.useNpm ? false : hasYarn();
const templatesDir = fileURLToPath(new URL('../templates', import.meta.url)); const templatesDir = fileURLToPath(new URL('../templates', import.meta.url));
const templates = readTemplates(templatesDir); const templates = await readTemplates(templatesDir);
const hasTS = (templateName: string) => const hasTS = (templateName: string) =>
fs.pathExistsSync( fs.pathExists(
path.resolve(templatesDir, `${templateName}${TypeScriptTemplateSuffix}`), path.resolve(templatesDir, `${templateName}${TypeScriptTemplateSuffix}`),
); );
@ -160,7 +158,7 @@ export default async function init(
} }
const dest = path.resolve(rootDir, name); const dest = path.resolve(rootDir, name);
if (fs.existsSync(dest)) { if (await fs.pathExists(dest)) {
logger.error`Directory already exists at path=${dest}!`; logger.error`Directory already exists at path=${dest}!`;
process.exit(1); process.exit(1);
} }
@ -176,7 +174,7 @@ export default async function init(
choices: createTemplateChoices(templates), choices: createTemplateChoices(templates),
}); });
template = templatePrompt.template; template = templatePrompt.template;
if (template && !useTS && hasTS(template)) { if (template && !useTS && (await hasTS(template))) {
const tsPrompt = await prompts({ const tsPrompt = await prompts({
type: 'confirm', type: 'confirm',
name: 'useTS', name: 'useTS',
@ -223,10 +221,10 @@ export default async function init(
const dirPrompt = await prompts({ const dirPrompt = await prompts({
type: 'text', type: 'text',
name: 'templateDir', name: 'templateDir',
validate: (dir?: string) => { validate: async (dir?: string) => {
if (dir) { if (dir) {
const fullDir = path.resolve(process.cwd(), dir); const fullDir = path.resolve(process.cwd(), dir);
if (fs.existsSync(fullDir)) { if (await fs.pathExists(fullDir)) {
return true; return true;
} }
return logger.red( return logger.red(
@ -267,7 +265,7 @@ export default async function init(
} else if (templates.includes(template)) { } else if (templates.includes(template)) {
// Docusaurus templates. // Docusaurus templates.
if (useTS) { if (useTS) {
if (!hasTS(template)) { if (!(await hasTS(template))) {
logger.error`Template name=${template} doesn't provide the Typescript variant.`; logger.error`Template name=${template} doesn't provide the Typescript variant.`;
process.exit(1); process.exit(1);
} }
@ -279,7 +277,7 @@ export default async function init(
logger.error`Copying Docusaurus template name=${template} failed!`; logger.error`Copying Docusaurus template name=${template} failed!`;
throw err; 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); const templateDir = path.resolve(process.cwd(), template);
try { try {
await fs.copy(templateDir, dest); await fs.copy(templateDir, dest);
@ -306,13 +304,13 @@ export default async function init(
// We need to rename the gitignore file to .gitignore // We need to rename the gitignore file to .gitignore
if ( if (
!fs.pathExistsSync(path.join(dest, '.gitignore')) && !(await fs.pathExists(path.join(dest, '.gitignore'))) &&
fs.pathExistsSync(path.join(dest, 'gitignore')) (await fs.pathExists(path.join(dest, 'gitignore')))
) { ) {
await fs.move(path.join(dest, 'gitignore'), path.join(dest, '.gitignore')); await fs.move(path.join(dest, 'gitignore'), path.join(dest, '.gitignore'));
} }
if (fs.pathExistsSync(path.join(dest, 'gitignore'))) { if (await fs.pathExists(path.join(dest, 'gitignore'))) {
fs.removeSync(path.join(dest, 'gitignore')); await fs.remove(path.join(dest, 'gitignore'));
} }
const pkgManager = useYarn ? 'yarn' : 'npm'; const pkgManager = useYarn ? 'yarn' : 'npm';

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * 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 mdx from '@mdx-js/mdx';
import logger from '@docusaurus/logger'; import logger from '@docusaurus/logger';
import emoji from 'remark-emoji'; import emoji from 'remark-emoji';
@ -57,7 +57,7 @@ type Options = RemarkAndRehypePluginOptions & {
*/ */
async function readMetadataPath(metadataPath: string) { async function readMetadataPath(metadataPath: string) {
try { try {
return await readFile(metadataPath, 'utf8'); return await fs.readFile(metadataPath, 'utf8');
} catch (e) { } catch (e) {
throw new Error( throw new Error(
`MDX loader can't read MDX metadata file for path ${metadataPath}. Maybe the isMDXPartial option function was not provided?`, `MDX loader can't read MDX metadata file for path ${metadataPath}. Maybe the isMDXPartial option function was not provided?`,

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {join} from 'path'; import path from 'path';
import remark from 'remark'; import remark from 'remark';
import mdx from 'remark-mdx'; import mdx from 'remark-mdx';
import vfile from 'to-vfile'; import vfile from 'to-vfile';
@ -13,8 +13,8 @@ import plugin from '../index';
import headings from '../../headings/index'; import headings from '../../headings/index';
const processFixture = async (name, options?) => { const processFixture = async (name, options?) => {
const path = join(__dirname, '__fixtures__', `${name}.md`); const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
const file = await vfile.read(path); const file = await vfile.read(filePath);
const result = await remark() const result = await remark()
.use(headings) .use(headings)
.use(mdx) .use(mdx)

View file

@ -21,7 +21,7 @@ describe('migration test', () => {
await expect( await expect(
migrateDocusaurusProject(siteDir, newDir), migrateDocusaurusProject(siteDir, newDir),
).resolves.toBeUndefined(); ).resolves.toBeUndefined();
fs.removeSync(newDir); await fs.remove(newDir);
}); });
test('complex website', async () => { test('complex website', async () => {
const siteDir = path.join( const siteDir = path.join(
@ -38,7 +38,7 @@ describe('migration test', () => {
await expect( await expect(
migrateDocusaurusProject(siteDir, newDir), migrateDocusaurusProject(siteDir, newDir),
).resolves.toBeUndefined(); ).resolves.toBeUndefined();
fs.removeSync(newDir); await fs.remove(newDir);
}); });
test('missing versions', async () => { test('missing versions', async () => {
@ -56,6 +56,6 @@ describe('migration test', () => {
await expect( await expect(
migrateDocusaurusProject(siteDir, newDir), migrateDocusaurusProject(siteDir, newDir),
).resolves.toBeUndefined(); ).resolves.toBeUndefined();
fs.removeSync(newDir); await fs.remove(newDir);
}); });
}); });

View file

@ -25,18 +25,18 @@ import path from 'path';
const DOCUSAURUS_VERSION = (importFresh('../package.json') as {version: string}) const DOCUSAURUS_VERSION = (importFresh('../package.json') as {version: string})
.version; .version;
export function walk(dir: string): Array<string> { async function walk(dir: string): Promise<string[]> {
let results: Array<string> = []; const results: string[] = [];
const list = fs.readdirSync(dir); const list = await fs.readdir(dir);
list.forEach((file: string) => { for (const file of list) {
const fullPath = `${dir}/${file}`; const fullPath = `${dir}/${file}`;
const stat = fs.statSync(fullPath); const stat = await fs.stat(fullPath);
if (stat && stat.isDirectory()) { if (stat && stat.isDirectory()) {
results = results.concat(walk(fullPath)); results.push(...(await walk(fullPath)));
} else { } else {
results.push(fullPath); results.push(fullPath);
} }
}); }
return results; return results;
} }
@ -112,7 +112,7 @@ export async function migrateDocusaurusProject(
} }
if (shouldMigratePages) { if (shouldMigratePages) {
try { try {
createPages(migrationContext); await createPages(migrationContext);
logger.success( logger.success(
'Created new doc pages (check migration page for more details)', 'Created new doc pages (check migration page for more details)',
); );
@ -122,7 +122,7 @@ export async function migrateDocusaurusProject(
} }
} else { } else {
try { try {
createDefaultLandingPage(migrationContext); await createDefaultLandingPage(migrationContext);
logger.success( logger.success(
'Created landing page (check migration page for more details)', 'Created landing page (check migration page for more details)',
); );
@ -133,41 +133,41 @@ export async function migrateDocusaurusProject(
} }
try { try {
migrateStaticFiles(migrationContext); await migrateStaticFiles(migrationContext);
logger.success('Migrated static folder'); logger.success('Migrated static folder');
} catch (e) { } catch (e) {
logger.error(`Failed to copy static folder: ${e}`); logger.error(`Failed to copy static folder: ${e}`);
errorCount += 1; errorCount += 1;
} }
try { try {
migrateBlogFiles(migrationContext); await migrateBlogFiles(migrationContext);
} catch (e) { } catch (e) {
logger.error(`Failed to migrate blogs: ${e}`); logger.error(`Failed to migrate blogs: ${e}`);
errorCount += 1; errorCount += 1;
} }
try { try {
handleVersioning(migrationContext); await handleVersioning(migrationContext);
} catch (e) { } catch (e) {
logger.error(`Failed to migrate versioned docs: ${e}`); logger.error(`Failed to migrate versioned docs: ${e}`);
errorCount += 1; errorCount += 1;
} }
try { try {
migrateLatestDocs(migrationContext); await migrateLatestDocs(migrationContext);
} catch (e) { } catch (e) {
logger.error(`Failed to migrate docs: ${e}`); logger.error(`Failed to migrate docs: ${e}`);
errorCount += 1; errorCount += 1;
} }
try { try {
migrateLatestSidebar(migrationContext); await migrateLatestSidebar(migrationContext);
} catch (e) { } catch (e) {
logger.error(`Failed to migrate sidebar: ${e}`); logger.error(`Failed to migrate sidebar: ${e}`);
errorCount += 1; errorCount += 1;
} }
try { try {
fs.writeFileSync( await fs.writeFile(
path.join(newDir, 'docusaurus.config.js'), path.join(newDir, 'docusaurus.config.js'),
`module.exports=${JSON.stringify(migrationContext.v2Config, null, 2)}`, `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; const {newDir, siteDir} = context;
fs.mkdirpSync(path.join(newDir, 'src', 'pages')); await fs.mkdirp(path.join(newDir, 'src', 'pages'));
if (fs.existsSync(path.join(siteDir, 'pages', 'en'))) { if (await fs.pathExists(path.join(siteDir, 'pages', 'en'))) {
try { try {
fs.copySync( await fs.copy(
path.join(siteDir, 'pages', 'en'), path.join(siteDir, 'pages', 'en'),
path.join(newDir, 'src', 'pages'), path.join(newDir, 'src', 'pages'),
); );
const files = Globby.sync('**/*.js', { const files = await Globby('**/*.js', {
cwd: path.join(newDir, 'src', 'pages'), cwd: path.join(newDir, 'src', 'pages'),
}); });
files.forEach((file) => { await Promise.all(
const filePath = path.join(newDir, 'src', 'pages', file); files.map(async (file) => {
const content = String(fs.readFileSync(filePath)); const filePath = path.join(newDir, 'src', 'pages', file);
fs.writeFileSync(filePath, migratePage(content)); const content = await fs.readFile(filePath, 'utf-8');
}); await fs.writeFile(filePath, migratePage(content));
}),
);
} catch (e) { } catch (e) {
logger.error(`Unable to migrate Pages: ${e}`); logger.error(`Unable to migrate Pages: ${e}`);
createDefaultLandingPage(context); await createDefaultLandingPage(context);
} }
} else { } else {
logger.info('Ignoring Pages'); logger.info('Ignoring Pages');
} }
} }
function createDefaultLandingPage({newDir}: MigrationContext) { async function createDefaultLandingPage({newDir}: MigrationContext) {
const indexPage = `import Layout from "@theme/Layout"; const indexPage = `import Layout from "@theme/Layout";
import React from "react"; import React from "react";
@ -402,30 +404,32 @@ function createDefaultLandingPage({newDir}: MigrationContext) {
return <Layout />; return <Layout />;
}; };
`; `;
fs.mkdirpSync(`${newDir}/src/pages/`); await fs.mkdirp(`${newDir}/src/pages/`);
fs.writeFileSync(`${newDir}/src/pages/index.js`, indexPage); await fs.writeFile(`${newDir}/src/pages/index.js`, indexPage);
} }
function migrateStaticFiles({siteDir, newDir}: MigrationContext): void { async function migrateStaticFiles({siteDir, newDir}: MigrationContext) {
if (fs.existsSync(path.join(siteDir, 'static'))) { if (await fs.pathExists(path.join(siteDir, 'static'))) {
fs.copySync(path.join(siteDir, 'static'), path.join(newDir, 'static')); await fs.copy(path.join(siteDir, 'static'), path.join(newDir, 'static'));
} else { } 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; const {siteDir, newDir, shouldMigrateMdFiles} = context;
if (fs.existsSync(path.join(siteDir, 'blog'))) { if (await fs.pathExists(path.join(siteDir, 'blog'))) {
fs.copySync(path.join(siteDir, 'blog'), path.join(newDir, 'blog')); await fs.copy(path.join(siteDir, 'blog'), path.join(newDir, 'blog'));
const files = walk(path.join(newDir, 'blog')); const files = await walk(path.join(newDir, 'blog'));
files.forEach((file) => { await Promise.all(
const content = String(fs.readFileSync(file)); files.map(async (file) => {
fs.writeFileSync( const content = await fs.readFile(file, 'utf-8');
file, await fs.writeFile(
sanitizedFileContent(content, shouldMigrateMdFiles), file,
); sanitizedFileContent(content, shouldMigrateMdFiles),
}); );
}),
);
context.v2Config.presets[0][1].blog.path = 'blog'; context.v2Config.presets[0][1].blog.path = 'blog';
logger.success('Migrated blogs to version 2 with change in front matter'); logger.success('Migrated blogs to version 2 with change in front matter');
} else { } else {
@ -433,21 +437,21 @@ function migrateBlogFiles(context: MigrationContext): void {
} }
} }
function handleVersioning(context: MigrationContext): void { async function handleVersioning(context: MigrationContext) {
const {siteDir, newDir} = context; const {siteDir, newDir} = context;
if (fs.existsSync(path.join(siteDir, 'versions.json'))) { if (await fs.pathExists(path.join(siteDir, 'versions.json'))) {
const loadedVersions: Array<string> = JSON.parse( const loadedVersions: string[] = JSON.parse(
String(fs.readFileSync(path.join(siteDir, 'versions.json'))), await fs.readFile(path.join(siteDir, 'versions.json'), 'utf-8'),
); );
fs.copyFileSync( await fs.copyFile(
path.join(siteDir, 'versions.json'), path.join(siteDir, 'versions.json'),
path.join(newDir, 'versions.json'), path.join(newDir, 'versions.json'),
); );
const versions = loadedVersions.reverse(); const versions = loadedVersions.reverse();
const versionRegex = new RegExp(`version-(${versions.join('|')})-`, 'mgi'); const versionRegex = new RegExp(`version-(${versions.join('|')})-`, 'mgi');
migrateVersionedSidebar(context, versions, versionRegex); await migrateVersionedSidebar(context, versions, versionRegex);
fs.mkdirpSync(path.join(newDir, 'versioned_docs')); await fs.mkdirp(path.join(newDir, 'versioned_docs'));
migrateVersionedDocs(context, versions, versionRegex); await migrateVersionedDocs(context, versions, versionRegex);
logger.success`Migrated version docs and sidebar. The following doc versions have been created:name=${loadedVersions}`; logger.success`Migrated version docs and sidebar. The following doc versions have been created:name=${loadedVersions}`;
} else { } else {
logger.warn( logger.warn(
@ -456,69 +460,78 @@ function handleVersioning(context: MigrationContext): void {
} }
} }
function migrateVersionedDocs( async function migrateVersionedDocs(
context: MigrationContext, context: MigrationContext,
versions: string[], versions: string[],
versionRegex: RegExp, versionRegex: RegExp,
): void { ) {
const {siteDir, newDir, shouldMigrateMdFiles} = context; const {siteDir, newDir, shouldMigrateMdFiles} = context;
versions.reverse().forEach((version, index) => { await Promise.all(
if (index === 0) { versions.reverse().map(async (version, index) => {
fs.copySync( if (index === 0) {
path.join(siteDir, '..', context.v1Config.customDocsPath || 'docs'), await fs.copy(
path.join(newDir, 'versioned_docs', `version-${version}`), path.join(siteDir, '..', context.v1Config.customDocsPath || 'docs'),
); path.join(newDir, 'versioned_docs', `version-${version}`),
fs.copySync( );
path.join(siteDir, 'versioned_docs', `version-${version}`), await fs.copy(
path.join(newDir, 'versioned_docs', `version-${version}`), path.join(siteDir, 'versioned_docs', `version-${version}`),
); path.join(newDir, 'versioned_docs', `version-${version}`),
return; );
} return;
try { }
fs.mkdirsSync(path.join(newDir, 'versioned_docs', `version-${version}`)); try {
fs.copySync( await fs.mkdirs(
path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`), path.join(newDir, 'versioned_docs', `version-${version}`),
path.join(newDir, 'versioned_docs', `version-${version}`), );
); await fs.copy(
fs.copySync( path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`),
path.join(siteDir, 'versioned_docs', `version-${version}`), path.join(newDir, 'versioned_docs', `version-${version}`),
path.join(newDir, 'versioned_docs', `version-${version}`), );
); await fs.copy(
} catch { path.join(siteDir, 'versioned_docs', `version-${version}`),
fs.copySync( path.join(newDir, 'versioned_docs', `version-${version}`),
path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`), );
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 = walk(path.join(newDir, 'versioned_docs')); );
files.forEach((pathToFile) => { }
if (path.extname(pathToFile) === '.md') { }),
const content = fs.readFileSync(pathToFile).toString(); );
fs.writeFileSync( const files = await walk(path.join(newDir, 'versioned_docs'));
pathToFile, await Promise.all(
sanitizedFileContent( files.map(async (pathToFile) => {
content.replace(versionRegex, ''), if (path.extname(pathToFile) === '.md') {
shouldMigrateMdFiles, const content = await fs.readFile(pathToFile, 'utf-8');
), await fs.writeFile(
); pathToFile,
} sanitizedFileContent(
}); content.replace(versionRegex, ''),
shouldMigrateMdFiles,
),
);
}
}),
);
} }
function migrateVersionedSidebar( async function migrateVersionedSidebar(
context: MigrationContext, context: MigrationContext,
versions: string[], versions: string[],
versionRegex: RegExp, versionRegex: RegExp,
): void { ) {
const {siteDir, newDir} = context; const {siteDir, newDir} = context;
if (fs.existsSync(path.join(siteDir, 'versioned_sidebars'))) { if (await fs.pathExists(path.join(siteDir, 'versioned_sidebars'))) {
fs.mkdirpSync(path.join(newDir, 'versioned_sidebars')); await fs.mkdirp(path.join(newDir, 'versioned_sidebars'));
const sidebars: { const sidebars: {
entries: SidebarEntries; entries: SidebarEntries;
version: string; 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; let sidebarEntries: SidebarEntries;
const sidebarPath = path.join( const sidebarPath = path.join(
siteDir, siteDir,
@ -526,79 +539,74 @@ function migrateVersionedSidebar(
`version-${version}-sidebars.json`, `version-${version}-sidebars.json`,
); );
try { try {
fs.statSync(sidebarPath); sidebarEntries = JSON.parse(await fs.readFile(sidebarPath, 'utf-8'));
sidebarEntries = JSON.parse(String(fs.readFileSync(sidebarPath)));
} catch { } catch {
sidebars.push({version, entries: sidebars[index - 1].entries}); sidebars.push({version, entries: sidebars[i - 1].entries});
return; return;
} }
const newSidebar = Object.entries(sidebarEntries).reduce( const newSidebar = Object.entries(sidebarEntries).reduce(
(topLevel: SidebarEntries, value) => { (topLevel: SidebarEntries, value) => {
const key = value[0].replace(versionRegex, ''); const key = value[0].replace(versionRegex, '');
topLevel[key] = Object.entries(value[1]).reduce( topLevel[key] = Object.entries(value[1]).reduce((acc, val) => {
( acc[val[0].replace(versionRegex, '')] = (
acc: {[key: string]: Array<Record<string, unknown> | string>}, val[1] as SidebarEntry[]
val, ).map((item) => {
) => { if (typeof item === 'string') {
acc[val[0].replace(versionRegex, '')] = ( return item.replace(versionRegex, '');
val[1] as Array<SidebarEntry> }
).map((item) => { return {
if (typeof item === 'string') { type: 'category',
return item.replace(versionRegex, ''); label: item.label,
} ids: item.ids.map((id) => id.replace(versionRegex, '')),
return { };
type: 'category', });
label: item.label, return acc;
ids: item.ids.map((id) => id.replace(versionRegex, '')), }, {} as Record<string, Array<string | Record<string, unknown>>>);
};
});
return acc;
},
{},
);
return topLevel; return topLevel;
}, },
{}, {},
); );
sidebars.push({version, entries: newSidebar}); sidebars.push({version, entries: newSidebar});
}); }
sidebars.forEach((sidebar) => { await Promise.all(
const newSidebar = Object.entries(sidebar.entries).reduce( sidebars.map(async (sidebar) => {
(acc: SidebarEntries, val) => { const newSidebar = Object.entries(sidebar.entries).reduce(
const key = `version-${sidebar.version}/${val[0]}`; (acc, val) => {
acc[key] = Object.entries(val[1]).map((value) => ({ const key = `version-${sidebar.version}/${val[0]}`;
type: 'category', acc[key] = Object.entries(val[1]).map((value) => ({
label: value[0], type: 'category',
items: (value[1] as Array<SidebarEntry>).map((sidebarItem) => { label: value[0],
if (typeof sidebarItem === 'string') { items: (value[1] as SidebarEntry[]).map((sidebarItem) => {
if (typeof sidebarItem === 'string') {
return {
type: 'doc',
id: `version-${sidebar.version}/${sidebarItem}`,
};
}
return { return {
type: 'doc', type: 'category',
id: `version-${sidebar.version}/${sidebarItem}`, label: sidebarItem.label,
items: sidebarItem.ids.map((id) => ({
type: 'doc',
id: `version-${sidebar.version}/${id}`,
})),
}; };
} }),
return { }));
type: 'category', return acc;
label: sidebarItem.label, },
items: sidebarItem.ids.map((id: string) => ({ {} as SidebarEntries,
type: 'doc', );
id: `version-${sidebar.version}/${id}`, await fs.writeFile(
})), path.join(
}; newDir,
}), 'versioned_sidebars',
})); `version-${sidebar.version}-sidebars.json`,
return acc; ),
}, JSON.stringify(newSidebar, null, 2),
{}, );
); }),
fs.writeFileSync( );
path.join(
newDir,
'versioned_sidebars',
`version-${sidebar.version}-sidebars.json`,
),
JSON.stringify(newSidebar, null, 2),
);
});
context.v2Config.themeConfig.navbar.items.push({ context.v2Config.themeConfig.navbar.items.push({
label: 'Version', label: 'Version',
to: 'docs', to: 'docs',
@ -626,10 +634,10 @@ function migrateVersionedSidebar(
} }
} }
function migrateLatestSidebar(context: MigrationContext): void { async function migrateLatestSidebar(context: MigrationContext) {
const {siteDir, newDir} = context; const {siteDir, newDir} = context;
try { try {
fs.copyFileSync( await fs.copyFile(
path.join(siteDir, 'sidebars.json'), path.join(siteDir, 'sidebars.json'),
path.join(newDir, '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()}; --ifm-color-primary-darkest: ${primaryColor.darken(0.3).hex()};
} }
`; `;
fs.mkdirpSync(path.join(newDir, 'src', 'css')); await fs.mkdirp(path.join(newDir, 'src', 'css'));
fs.writeFileSync(path.join(newDir, 'src', 'css', 'customTheme.css'), css); await fs.writeFile(path.join(newDir, 'src', 'css', 'customTheme.css'), css);
context.v2Config.presets[0][1].theme.customCss = path.join( context.v2Config.presets[0][1].theme.customCss = path.join(
path.relative(newDir, path.join(siteDir, '..')), path.relative(newDir, path.join(siteDir, '..')),
'src', '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; 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( context.v2Config.presets[0][1].docs.path = path.join(
path.relative(newDir, path.join(siteDir, '..')), path.relative(newDir, path.join(siteDir, '..')),
'docs', 'docs',
); );
const files = walk(path.join(siteDir, '..', 'docs')); const files = await walk(path.join(siteDir, '..', 'docs'));
files.forEach((file) => { await Promise.all(
if (path.extname(file) === '.md') { files.map(async (file) => {
const content = fs.readFileSync(file).toString(); if (path.extname(file) === '.md') {
fs.writeFileSync( const content = await fs.readFile(file, 'utf-8');
file, await fs.writeFile(
sanitizedFileContent(content, shouldMigrateMdFiles), file,
); sanitizedFileContent(content, shouldMigrateMdFiles),
} );
}); }
}),
);
logger.success('Migrated docs to version 2'); logger.success('Migrated docs to version 2');
} else { } else {
logger.warn('Docs folder not found. Skipping migration for docs'); logger.warn('Docs folder not found. Skipping migration for docs');
@ -713,7 +723,7 @@ async function migratePackageFile(context: MigrationContext): Promise<void> {
...packageFile.dependencies, ...packageFile.dependencies,
...deps, ...deps,
}; };
fs.writeFileSync( await fs.writeFile(
path.join(newDir, 'package.json'), path.join(newDir, 'package.json'),
JSON.stringify(packageFile, null, 2), JSON.stringify(packageFile, null, 2),
); );
@ -724,14 +734,16 @@ export async function migrateMDToMDX(
siteDir: string, siteDir: string,
newDir: string, newDir: string,
): Promise<void> { ): Promise<void> {
fs.mkdirpSync(newDir); await fs.mkdirp(newDir);
fs.copySync(siteDir, newDir); await fs.copy(siteDir, newDir);
const files = walk(newDir); const files = await walk(newDir);
files.forEach((filePath) => { await Promise.all(
if (path.extname(filePath) === '.md') { files.map(async (filePath) => {
const content = fs.readFileSync(filePath).toString(); if (path.extname(filePath) === '.md') {
fs.writeFileSync(filePath, sanitizedFileContent(content, true)); const content = await fs.readFile(filePath, 'utf-8');
} await fs.writeFile(filePath, sanitizedFileContent(content, true));
}); }
}),
);
logger.success`Successfully migrated path=${siteDir} to path=${newDir}`; logger.success`Successfully migrated path=${siteDir} to path=${newDir}`;
} }

View file

@ -43,8 +43,11 @@ const blogPosts: BlogPost[] = [
}, },
]; ];
const transform = (filePath: string, options?: Partial<LinkifyParams>) => { const transform = async (
const fileContent = fs.readFileSync(filePath, 'utf-8'); filePath: string,
options?: Partial<LinkifyParams>,
) => {
const fileContent = await fs.readFile(filePath, 'utf-8');
const transformedContent = linkify({ const transformedContent = linkify({
filePath, filePath,
fileString: fileContent, fileString: fileContent,
@ -61,9 +64,9 @@ const transform = (filePath: string, options?: Partial<LinkifyParams>) => {
return [fileContent, transformedContent]; return [fileContent, transformedContent];
}; };
test('transform to correct link', () => { test('transform to correct link', async () => {
const post = path.join(contentPaths.contentPath, 'post.md'); const post = path.join(contentPaths.contentPath, 'post.md');
const [content, transformedContent] = transform(post); const [content, transformedContent] = await transform(post);
expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain( expect(transformedContent).toContain(
'](/blog/2018/12/14/Happy-First-Birthday-Slash', '](/blog/2018/12/14/Happy-First-Birthday-Slash',
@ -74,12 +77,12 @@ test('transform to correct link', () => {
expect(content).not.toEqual(transformedContent); expect(content).not.toEqual(transformedContent);
}); });
test('report broken markdown links', () => { test('report broken markdown links', async () => {
const filePath = 'post-with-broken-links.md'; const filePath = 'post-with-broken-links.md';
const folderPath = contentPaths.contentPath; const folderPath = contentPaths.contentPath;
const postWithBrokenLinks = path.join(folderPath, filePath); const postWithBrokenLinks = path.join(folderPath, filePath);
const onBrokenMarkdownLink = jest.fn(); const onBrokenMarkdownLink = jest.fn();
const [, transformedContent] = transform(postWithBrokenLinks, { const [, transformedContent] = await transform(postWithBrokenLinks, {
onBrokenMarkdownLink, onBrokenMarkdownLink,
}); });
expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toMatchSnapshot();

View file

@ -332,7 +332,7 @@ export async function generateBlogPosts(
): Promise<BlogPost[]> { ): Promise<BlogPost[]> {
const {include, exclude} = options; const {include, exclude} = options;
if (!fs.existsSync(contentPaths.contentPath)) { if (!(await fs.pathExists(contentPaths.contentPath))) {
return []; return [];
} }

View file

@ -185,9 +185,9 @@ describe('docsVersion', () => {
}); });
test('first time versioning', async () => { test('first time versioning', async () => {
const copyMock = jest.spyOn(fs, 'copySync').mockImplementation(); const copyMock = jest.spyOn(fs, 'copy').mockImplementation();
const ensureMock = jest.spyOn(fs, 'ensureDirSync').mockImplementation(); const ensureMock = jest.spyOn(fs, 'ensureDir').mockImplementation();
const writeMock = jest.spyOn(fs, 'writeFileSync'); const writeMock = jest.spyOn(fs, 'writeFile');
let versionedSidebar; let versionedSidebar;
let versionedSidebarPath; let versionedSidebarPath;
writeMock.mockImplementationOnce((filepath, content) => { writeMock.mockImplementationOnce((filepath, content) => {
@ -242,9 +242,9 @@ describe('docsVersion', () => {
}); });
test('not the first time versioning', async () => { test('not the first time versioning', async () => {
const copyMock = jest.spyOn(fs, 'copySync').mockImplementation(); const copyMock = jest.spyOn(fs, 'copy').mockImplementation();
const ensureMock = jest.spyOn(fs, 'ensureDirSync').mockImplementation(); const ensureMock = jest.spyOn(fs, 'ensureDir').mockImplementation();
const writeMock = jest.spyOn(fs, 'writeFileSync'); const writeMock = jest.spyOn(fs, 'writeFile');
let versionedSidebar; let versionedSidebar;
let versionedSidebarPath; let versionedSidebarPath;
writeMock.mockImplementationOnce((filepath, content) => { writeMock.mockImplementationOnce((filepath, content) => {
@ -301,9 +301,9 @@ describe('docsVersion', () => {
test('second docs instance versioning', async () => { test('second docs instance versioning', async () => {
const pluginId = 'community'; const pluginId = 'community';
const copyMock = jest.spyOn(fs, 'copySync').mockImplementation(); const copyMock = jest.spyOn(fs, 'copy').mockImplementation();
const ensureMock = jest.spyOn(fs, 'ensureDirSync').mockImplementation(); const ensureMock = jest.spyOn(fs, 'ensureDir').mockImplementation();
const writeMock = jest.spyOn(fs, 'writeFileSync'); const writeMock = jest.spyOn(fs, 'writeFile');
let versionedSidebar; let versionedSidebar;
let versionedSidebarPath; let versionedSidebarPath;
writeMock.mockImplementationOnce((filepath, content) => { writeMock.mockImplementationOnce((filepath, content) => {

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import fs from 'fs'; import fs from 'fs-extra';
import path from 'path'; import path from 'path';
import shell from 'shelljs'; import shell from 'shelljs';
@ -64,9 +64,9 @@ describe('lastUpdate', () => {
test('temporary created file that has no git timestamp', async () => { test('temporary created file that has no git timestamp', async () => {
const tempFilePath = path.join(__dirname, '__fixtures__', '.temp'); const tempFilePath = path.join(__dirname, '__fixtures__', '.temp');
fs.writeFileSync(tempFilePath, 'Lorem ipsum :)'); await fs.writeFile(tempFilePath, 'Lorem ipsum :)');
await expect(getFileLastUpdate(tempFilePath)).resolves.toBeNull(); await expect(getFileLastUpdate(tempFilePath)).resolves.toBeNull();
fs.unlinkSync(tempFilePath); await fs.unlink(tempFilePath);
}); });
test('Git does not exist', async () => { test('Git does not exist', async () => {

View file

@ -47,8 +47,8 @@ async function createVersionedSidebarFile({
versionedSidebarsDir, versionedSidebarsDir,
`version-${version}-sidebars.json`, `version-${version}-sidebars.json`,
); );
fs.ensureDirSync(path.dirname(newSidebarFile)); await fs.ensureDir(path.dirname(newSidebarFile));
fs.writeFileSync( await fs.writeFile(
newSidebarFile, newSidebarFile,
`${JSON.stringify(sidebars, null, 2)}\n`, `${JSON.stringify(sidebars, null, 2)}\n`,
'utf8', 'utf8',
@ -104,8 +104,8 @@ export async function cliDocsVersionCommand(
// Load existing versions. // Load existing versions.
let versions = []; let versions = [];
const versionsJSONFile = getVersionsFilePath(siteDir, pluginId); const versionsJSONFile = getVersionsFilePath(siteDir, pluginId);
if (fs.existsSync(versionsJSONFile)) { if (await fs.pathExists(versionsJSONFile)) {
versions = JSON.parse(fs.readFileSync(versionsJSONFile, 'utf8')); versions = JSON.parse(await fs.readFile(versionsJSONFile, 'utf8'));
} }
// Check if version already exists. // Check if version already exists.
@ -120,10 +120,13 @@ export async function cliDocsVersionCommand(
// Copy docs files. // Copy docs files.
const docsDir = path.join(siteDir, docsPath); 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 versionedDir = getVersionedDocsDirPath(siteDir, pluginId);
const newVersionDir = path.join(versionedDir, `version-${version}`); const newVersionDir = path.join(versionedDir, `version-${version}`);
fs.copySync(docsDir, newVersionDir); await fs.copy(docsDir, newVersionDir);
} else { } else {
throw new Error(`${pluginIdLogPrefix}: there is no docs to version!`); throw new Error(`${pluginIdLogPrefix}: there is no docs to version!`);
} }
@ -137,8 +140,11 @@ export async function cliDocsVersionCommand(
// Update versions.json file. // Update versions.json file.
versions.unshift(version); versions.unshift(version);
fs.ensureDirSync(path.dirname(versionsJSONFile)); await fs.ensureDir(path.dirname(versionsJSONFile));
fs.writeFileSync(versionsJSONFile, `${JSON.stringify(versions, null, 2)}\n`); await fs.writeFile(
versionsJSONFile,
`${JSON.stringify(versions, null, 2)}\n`,
);
logger.success`name=${pluginIdLogPrefix}: version name=${version} created!`; logger.success`name=${pluginIdLogPrefix}: version name=${version} created!`;
} }

View file

@ -89,23 +89,26 @@ function createMarkdownOptions(
}; };
} }
const transform = (filepath: string, options?: Partial<DocsMarkdownOption>) => { const transform = async (
filepath: string,
options?: Partial<DocsMarkdownOption>,
) => {
const markdownOptions = createMarkdownOptions(options); 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); const transformedContent = linkify(content, filepath, markdownOptions);
return [content, transformedContent]; return [content, transformedContent];
}; };
test('transform nothing', () => { test('transform nothing', async () => {
const doc1 = path.join(versionCurrent.contentPath, 'doc1.md'); const doc1 = path.join(versionCurrent.contentPath, 'doc1.md');
const [content, transformedContent] = transform(doc1); const [content, transformedContent] = await transform(doc1);
expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toMatchSnapshot();
expect(content).toEqual(transformedContent); expect(content).toEqual(transformedContent);
}); });
test('transform to correct links', () => { test('transform to correct links', async () => {
const doc2 = path.join(versionCurrent.contentPath, 'doc2.md'); const doc2 = path.join(versionCurrent.contentPath, 'doc2.md');
const [content, transformedContent] = transform(doc2); const [content, transformedContent] = await transform(doc2);
expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/doc1'); expect(transformedContent).toContain('](/docs/doc1');
expect(transformedContent).toContain('](/docs/doc2'); expect(transformedContent).toContain('](/docs/doc2');
@ -118,19 +121,19 @@ test('transform to correct links', () => {
expect(content).not.toEqual(transformedContent); expect(content).not.toEqual(transformedContent);
}); });
test('transform relative links', () => { test('transform relative links', async () => {
const doc3 = path.join(versionCurrent.contentPath, 'subdir', 'doc3.md'); 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).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/doc2'); expect(transformedContent).toContain('](/docs/doc2');
expect(transformedContent).not.toContain('](../doc2.md)'); expect(transformedContent).not.toContain('](../doc2.md)');
expect(content).not.toEqual(transformedContent); expect(content).not.toEqual(transformedContent);
}); });
test('transforms reference links', () => { test('transforms reference links', async () => {
const doc4 = path.join(versionCurrent.contentPath, 'doc4.md'); const doc4 = path.join(versionCurrent.contentPath, 'doc4.md');
const [content, transformedContent] = transform(doc4); const [content, transformedContent] = await transform(doc4);
expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('[doc1]: /docs/doc1'); expect(transformedContent).toContain('[doc1]: /docs/doc1');
expect(transformedContent).toContain('[doc2]: /docs/doc2'); expect(transformedContent).toContain('[doc2]: /docs/doc2');
@ -139,10 +142,10 @@ test('transforms reference links', () => {
expect(content).not.toEqual(transformedContent); 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 doc5 = path.join(versionCurrent.contentPath, 'doc5.md');
const onBrokenMarkdownLink = jest.fn(); const onBrokenMarkdownLink = jest.fn();
const [content, transformedContent] = transform(doc5, { const [content, transformedContent] = await transform(doc5, {
onBrokenMarkdownLink, onBrokenMarkdownLink,
}); });
expect(transformedContent).toEqual(content); expect(transformedContent).toEqual(content);
@ -169,9 +172,9 @@ test('report broken markdown links', () => {
} as BrokenMarkdownLink); } 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 doc2 = path.join(version100.contentPath, 'doc2.md');
const [content, transformedContent] = transform(doc2); const [content, transformedContent] = await transform(doc2);
expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/1.0.0/subdir/doc1'); expect(transformedContent).toContain('](/docs/1.0.0/subdir/doc1');
expect(transformedContent).toContain('](/docs/1.0.0/doc2#existing-docs'); 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); 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 doc1 = path.join(version100.contentPath, 'subdir', 'doc1.md');
const [content, transformedContent] = transform(doc1); const [content, transformedContent] = await transform(doc1);
expect(transformedContent).toMatchSnapshot(); expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/1.0.0/doc2'); expect(transformedContent).toContain('](/docs/1.0.0/doc2');
expect(transformedContent).not.toContain('](../doc2.md)'); expect(transformedContent).not.toContain('](../doc2.md)');

View file

@ -85,7 +85,7 @@ export async function loadSidebarsFileUnsafe(
// Non-existent sidebars file: no sidebars // Non-existent sidebars file: no sidebars
// Note: this edge case can happen on versioned docs, not current version // Note: this edge case can happen on versioned docs, not current version
// We avoid creating empty versioned sidebars file with the CLI // We avoid creating empty versioned sidebars file with the CLI
if (!fs.existsSync(sidebarFilePath)) { if (!(await fs.pathExists(sidebarFilePath))) {
return DisabledSidebars; return DisabledSidebars;
} }

View file

@ -418,7 +418,7 @@ function createVersionMetadata({
}; };
} }
function checkVersionMetadataPaths({ async function checkVersionMetadataPaths({
versionMetadata, versionMetadata,
context, context,
}: { }: {
@ -429,7 +429,7 @@ function checkVersionMetadataPaths({
const {siteDir} = context; const {siteDir} = context;
const isCurrentVersion = versionName === CURRENT_VERSION_NAME; const isCurrentVersion = versionName === CURRENT_VERSION_NAME;
if (!fs.existsSync(contentPath)) { if (!(await fs.pathExists(contentPath))) {
throw new Error( throw new Error(
`The docs folder does not exist for version "${versionName}". A docs folder is expected to be found at ${path.relative( `The docs folder does not exist for version "${versionName}". A docs folder is expected to be found at ${path.relative(
siteDir, siteDir,
@ -446,7 +446,7 @@ function checkVersionMetadataPaths({
if ( if (
isCurrentVersion && isCurrentVersion &&
typeof sidebarFilePath === 'string' && 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( throw new Error(`The path to the sidebar file does not exist at "${path.relative(
siteDir, siteDir,
@ -585,8 +585,10 @@ export async function readVersionsMetadata({
options, options,
}), }),
); );
versionsMetadata.forEach((versionMetadata) => await Promise.all(
checkVersionMetadataPaths({versionMetadata, context}), versionsMetadata.map((versionMetadata) =>
checkVersionMetadataPaths({versionMetadata, context}),
),
); );
return versionsMetadata; return versionsMetadata;
} }

View file

@ -36,13 +36,13 @@
"algoliasearch-helper": "^3.7.0", "algoliasearch-helper": "^3.7.0",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"eta": "^1.12.3", "eta": "^1.12.3",
"fs-extra": "^10.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"tslib": "^2.3.1", "tslib": "^2.3.1",
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.15", "@docusaurus/module-type-aliases": "2.0.0-beta.15"
"fs-extra": "^10.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8.4 || ^17.0.0", "react": "^16.8.4 || ^17.0.0",

View file

@ -6,7 +6,7 @@
*/ */
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs-extra';
import {defaultConfig, compile} from 'eta'; import {defaultConfig, compile} from 'eta';
import {normalizeUrl} from '@docusaurus/utils'; import {normalizeUrl} from '@docusaurus/utils';
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations'; import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
@ -76,7 +76,7 @@ export default function themeSearchAlgolia(context: LoadContext): Plugin<void> {
const siteUrl = normalizeUrl([url, baseUrl]); const siteUrl = normalizeUrl([url, baseUrl]);
try { try {
fs.writeFileSync( await fs.writeFile(
path.join(outDir, OPEN_SEARCH_FILENAME), path.join(outDir, OPEN_SEARCH_FILENAME),
renderOpenSearchTemplate({ renderOpenSearchTemplate({
title, title,

View file

@ -6,7 +6,8 @@
*/ */
// @ts-check // @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 logger = require('@docusaurus/logger').default;
const path = require('path'); const path = require('path');

View file

@ -143,7 +143,7 @@ describe('load utils', () => {
describe('generate', () => { describe('generate', () => {
test('behaves correctly', async () => { test('behaves correctly', async () => {
const writeMock = jest.spyOn(fs, 'writeFile').mockImplementation(() => {}); 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'); const readMock = jest.spyOn(fs, 'readFile');
// First call: no file, no cache // First call: no file, no cache

View file

@ -106,7 +106,7 @@ export async function generate(
// If file already exists but its not in runtime cache yet, // If file already exists but its not in runtime cache yet,
// we try to calculate the content hash and then compare // we try to calculate the content hash and then compare
// This is to avoid unnecessary overwriting and we can reuse old file. // 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'); const lastContent = await fs.readFile(filepath, 'utf8');
lastHash = createHash('md5').update(lastContent).digest('hex'); lastHash = createHash('md5').update(lastContent).digest('hex');
fileHash.set(filepath, lastHash); fileHash.set(filepath, lastHash);

View file

@ -36,7 +36,7 @@ const {
* *
* cache data is stored in `~/.config/configstore/update-notifier-@docusaurus` * cache data is stored in `~/.config/configstore/update-notifier-@docusaurus`
*/ */
function beforeCli() { async function beforeCli() {
const notifier = updateNotifier({ const notifier = updateNotifier({
pkg: { pkg: {
name, name,
@ -98,7 +98,9 @@ function beforeCli() {
.filter((p) => p.startsWith('@docusaurus')) .filter((p) => p.startsWith('@docusaurus'))
.map((p) => p.concat('@latest')) .map((p) => p.concat('@latest'))
.join(' '); .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 const upgradeCommand = isYarnUsed
? `yarn upgrade ${siteDocusaurusPackagesForUpdate}` ? `yarn upgrade ${siteDocusaurusPackagesForUpdate}`
: `npm i ${siteDocusaurusPackagesForUpdate}`; : `npm i ${siteDocusaurusPackagesForUpdate}`;

View file

@ -9,7 +9,7 @@
// @ts-check // @ts-check
import logger from '@docusaurus/logger'; import logger from '@docusaurus/logger';
import fs from 'fs'; import fs from 'fs-extra';
import cli from 'commander'; import cli from 'commander';
import {createRequire} from 'module'; import {createRequire} from 'module';
import { import {
@ -25,9 +25,9 @@ import {
} from '../lib/index.js'; } from '../lib/index.js';
import beforeCli from './beforeCli.mjs'; import beforeCli from './beforeCli.mjs';
beforeCli(); await beforeCli();
const resolveDir = (dir = '.') => fs.realpathSync(dir); const resolveDir = (dir = '.') => fs.realpath(dir);
cli cli
.version(createRequire(import.meta.url)('../package.json').version) .version(createRequire(import.meta.url)('../package.json').version)
@ -56,8 +56,8 @@ cli
'--no-minify', '--no-minify',
'build website without minimizing JS bundles (default: false)', 'build website without minimizing JS bundles (default: false)',
) )
.action((siteDir, {bundleAnalyzer, config, outDir, locale, minify}) => { .action(async (siteDir, {bundleAnalyzer, config, outDir, locale, minify}) => {
build(resolveDir(siteDir), { build(await resolveDir(siteDir), {
bundleAnalyzer, bundleAnalyzer,
outDir, outDir,
config, config,
@ -74,8 +74,14 @@ cli
'copy TypeScript theme files when possible (default: false)', 'copy TypeScript theme files when possible (default: false)',
) )
.option('--danger', 'enable swizzle for internal component of themes') .option('--danger', 'enable swizzle for internal component of themes')
.action((themeName, componentName, siteDir, {typescript, danger}) => { .action(async (themeName, componentName, siteDir, {typescript, danger}) => {
swizzle(resolveDir(siteDir), themeName, componentName, typescript, danger); swizzle(
await resolveDir(siteDir),
themeName,
componentName,
typescript,
danger,
);
}); });
cli cli
@ -97,8 +103,8 @@ cli
'--skip-build', '--skip-build',
'skip building website before deploy it (default: false)', 'skip building website before deploy it (default: false)',
) )
.action((siteDir, {outDir, skipBuild, config}) => { .action(async (siteDir, {outDir, skipBuild, config}) => {
deploy(resolveDir(siteDir), { deploy(await resolveDir(siteDir), {
outDir, outDir,
config, config,
skipBuild, skipBuild,
@ -124,17 +130,19 @@ cli
'--poll [interval]', '--poll [interval]',
'use polling rather than watching for reload (default: false). Can specify a poll interval in milliseconds', '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}) => { .action(
start(resolveDir(siteDir), { async (siteDir, {port, host, locale, config, hotOnly, open, poll}) => {
port, start(await resolveDir(siteDir), {
host, port,
locale, host,
config, locale,
hotOnly, config,
open, hotOnly,
poll, open,
}); poll,
}); });
},
);
cli cli
.command('serve [siteDir]') .command('serve [siteDir]')
@ -151,7 +159,7 @@ cli
.option('--build', 'build website before serving (default: false)') .option('--build', 'build website before serving (default: false)')
.option('-h, --host <host>', 'use specified host (default: localhost)') .option('-h, --host <host>', 'use specified host (default: localhost)')
.action( .action(
( async (
siteDir, siteDir,
{ {
dir = 'build', dir = 'build',
@ -161,7 +169,7 @@ cli
config, config,
}, },
) => { ) => {
serve(resolveDir(siteDir), { serve(await resolveDir(siteDir), {
dir, dir,
port, port,
build: buildSite, build: buildSite,
@ -174,8 +182,8 @@ cli
cli cli
.command('clear [siteDir]') .command('clear [siteDir]')
.description('Remove build artifacts.') .description('Remove build artifacts.')
.action((siteDir) => { .action(async (siteDir) => {
clear(resolveDir(siteDir)); clear(await resolveDir(siteDir));
}); });
cli 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', '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( .action(
( async (
siteDir, siteDir,
{locale = undefined, override = false, messagePrefix = '', config}, {locale = undefined, override = false, messagePrefix = '', config},
) => { ) => {
writeTranslations(resolveDir(siteDir), { writeTranslations(await resolveDir(siteDir), {
locale, locale,
override, override,
config, config,
@ -219,8 +227,8 @@ cli
"keep the headings' casing, otherwise make all lowercase (default: false)", "keep the headings' casing, otherwise make all lowercase (default: false)",
) )
.option('--overwrite', 'overwrite existing heading IDs (default: false)') .option('--overwrite', 'overwrite existing heading IDs (default: false)')
.action((siteDir, files, options) => .action(async (siteDir, files, options) =>
writeHeadingIds(resolveDir(siteDir), files, options), writeHeadingIds(await resolveDir(siteDir), files, options),
); );
cli.arguments('<command>').action((cmd) => { cli.arguments('<command>').action((cmd) => {
@ -246,7 +254,7 @@ function isInternalCommand(command) {
async function run() { async function run() {
if (!isInternalCommand(process.argv.slice(2)[0])) { if (!isInternalCommand(process.argv.slice(2)[0])) {
await externalCommand(cli, resolveDir('.')); await externalCommand(cli, await resolveDir('.'));
} }
cli.parse(process.argv); cli.parse(process.argv);

View file

@ -56,6 +56,7 @@
"boxen": "^5.1.2", "boxen": "^5.1.2",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"clean-css": "^5.2.4", "clean-css": "^5.2.4",
"combine-promises": "^1.1.0",
"commander": "^5.1.0", "commander": "^5.1.0",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^10.2.4",
"core-js": "^3.21.1", "core-js": "^3.21.1",

View file

@ -130,7 +130,7 @@ async function buildLocale({
'client-manifest.json', 'client-manifest.json',
); );
let clientConfig: Configuration = merge( let clientConfig: Configuration = merge(
createClientConfig(props, cliOptions.minify), await createClientConfig(props, cliOptions.minify),
{ {
plugins: [ plugins: [
// Remove/clean build folders before building bundles. // Remove/clean build folders before building bundles.
@ -148,23 +148,26 @@ async function buildLocale({
const allCollectedLinks: Record<string, string[]> = {}; const allCollectedLinks: Record<string, string[]> = {};
let serverConfig: Configuration = createServerConfig({ let serverConfig: Configuration = await createServerConfig({
props, props,
onLinksCollected: (staticPagePath, links) => { onLinksCollected: (staticPagePath, links) => {
allCollectedLinks[staticPagePath] = links; allCollectedLinks[staticPagePath] = links;
}, },
}); });
serverConfig = merge(serverConfig, { if (staticDirectories.length > 0) {
plugins: [ await Promise.all(staticDirectories.map((dir) => fs.ensureDir(dir)));
new CopyWebpackPlugin({
patterns: staticDirectories serverConfig = merge(serverConfig, {
.map((dir) => path.resolve(siteDir, dir)) plugins: [
.filter(fs.existsSync) new CopyWebpackPlugin({
.map((dir) => ({from: dir, to: outDir})), patterns: staticDirectories
}), .map((dir) => path.resolve(siteDir, dir))
], .map((dir) => ({from: dir, to: outDir})),
}); }),
],
});
}
// Plugin Lifecycle - configureWebpack and configurePostCss. // Plugin Lifecycle - configureWebpack and configurePostCss.
plugins.forEach((plugin) => { plugins.forEach((plugin) => {

View file

@ -107,7 +107,7 @@ export default async function start(
? (cliOptions.poll as number) ? (cliOptions.poll as number)
: undefined, : undefined,
}; };
const httpsConfig = getHttpsConfig(); const httpsConfig = await getHttpsConfig();
const fsWatcher = chokidar.watch(pathsToWatch, { const fsWatcher = chokidar.watch(pathsToWatch, {
cwd: siteDir, cwd: siteDir,
ignoreInitial: true, ignoreInitial: true,
@ -118,7 +118,7 @@ export default async function start(
fsWatcher.on(event, reload), fsWatcher.on(event, reload),
); );
let config: webpack.Configuration = merge(createClientConfig(props), { let config: webpack.Configuration = merge(await createClientConfig(props), {
infrastructureLogging: { infrastructureLogging: {
// Reduce log verbosity, see https://github.com/facebook/docusaurus/pull/5420#issuecomment-906613105 // Reduce log verbosity, see https://github.com/facebook/docusaurus/pull/5420#issuecomment-906613105
level: 'warn', level: 'warn',

View file

@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
/* eslint-disable no-restricted-properties */
import logger from '@docusaurus/logger'; import logger from '@docusaurus/logger';
import fs from 'fs-extra'; import fs from 'fs-extra';
import importFresh from 'import-fresh'; import importFresh from 'import-fresh';

View file

@ -97,7 +97,7 @@ Available locales are: ${context.i18n.locales.join(',')}.`,
const babelOptions = getBabelOptions({ const babelOptions = getBabelOptions({
isServer: true, isServer: true,
babelOptions: getCustomBabelConfigFilePath(siteDir), babelOptions: await getCustomBabelConfigFilePath(siteDir),
}); });
const extractedCodeTranslations = await extractSiteSourceCodeTranslations( const extractedCodeTranslations = await extractSiteSourceCodeTranslations(
siteDir, siteDir,

View file

@ -20,6 +20,7 @@ import {
} from '@docusaurus/utils'; } from '@docusaurus/utils';
import {getAllFinalRoutes} from './utils'; import {getAllFinalRoutes} from './utils';
import path from 'path'; import path from 'path';
import combinePromises from 'combine-promises';
function toReactRouterRoutes(routes: RouteConfig[]): RRRouteConfig[] { function toReactRouterRoutes(routes: RouteConfig[]): RRRouteConfig[] {
// @ts-expect-error: types incompatible??? // @ts-expect-error: types incompatible???
@ -158,9 +159,9 @@ export function getBrokenLinksErrorMessage(
); );
} }
function isExistingFile(filePath: string) { async function isExistingFile(filePath: string) {
try { try {
return fs.statSync(filePath).isFile(); return (await fs.stat(filePath)).isFile();
} catch (e) { } catch (e) {
return false; return false;
} }
@ -177,8 +178,7 @@ export async function filterExistingFileLinks({
outDir: string; outDir: string;
allCollectedLinks: Record<string, string[]>; allCollectedLinks: Record<string, string[]>;
}): Promise<Record<string, string[]>> { }): Promise<Record<string, string[]>> {
// not easy to make this async :'( async function linkFileExists(link: string) {
function linkFileExists(link: string): boolean {
// /baseUrl/javadoc/ -> /outDir/javadoc // /baseUrl/javadoc/ -> /outDir/javadoc
const baseFilePath = removeSuffix( const baseFilePath = removeSuffix(
`${outDir}/${removePrefix(link, baseUrl)}`, `${outDir}/${removePrefix(link, baseUrl)}`,
@ -194,11 +194,22 @@ export async function filterExistingFileLinks({
filePathsToTry.push(path.join(baseFilePath, 'index.html')); 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) => return combinePromises(
links.filter((link) => !linkFileExists(link)), _.mapValues(allCollectedLinks, async (links) =>
(
await Promise.all(
links.map(async (link) => ((await linkFileExists(link)) ? '' : link)),
)
).filter(Boolean),
),
); );
} }

View file

@ -13,7 +13,7 @@ import {validateConfig} from './configValidation';
export default async function loadConfig( export default async function loadConfig(
configPath: string, configPath: string,
): Promise<DocusaurusConfig> { ): Promise<DocusaurusConfig> {
if (!fs.existsSync(configPath)) { if (!(await fs.pathExists(configPath))) {
throw new Error(`Config file at "${configPath}" not found.`); throw new Error(`Config file at "${configPath}" not found.`);
} }

View file

@ -403,10 +403,12 @@ ${Object.keys(registry)
// Version metadata. // Version metadata.
const siteMetadata: DocusaurusSiteMetadata = { const siteMetadata: DocusaurusSiteMetadata = {
docusaurusVersion: getPackageJsonVersion( docusaurusVersion: (await getPackageJsonVersion(
path.join(__dirname, '../../package.json'), path.join(__dirname, '../../package.json'),
)!, ))!,
siteVersion: getPackageJsonVersion(path.join(siteDir, 'package.json')), siteVersion: await getPackageJsonVersion(
path.join(siteDir, 'package.json'),
),
pluginVersions: {}, pluginVersions: {},
}; };
plugins plugins

View file

@ -133,9 +133,9 @@ export default async function initPlugins({
// siteDir's package.json declares the dependency on these plugins. // siteDir's package.json declares the dependency on these plugins.
const pluginRequire = createRequire(context.siteConfigPath); const pluginRequire = createRequire(context.siteConfigPath);
function doGetPluginVersion( async function doGetPluginVersion(
normalizedPluginConfig: NormalizedPluginConfig, normalizedPluginConfig: NormalizedPluginConfig,
): DocusaurusPluginVersionInformation { ): Promise<DocusaurusPluginVersionInformation> {
// get plugin version // get plugin version
if (normalizedPluginConfig.pluginModule?.path) { if (normalizedPluginConfig.pluginModule?.path) {
const pluginPath = pluginRequire.resolve( const pluginPath = pluginRequire.resolve(
@ -187,7 +187,7 @@ export default async function initPlugins({
pluginRequire, pluginRequire,
); );
const pluginVersion: DocusaurusPluginVersionInformation = const pluginVersion: DocusaurusPluginVersionInformation =
doGetPluginVersion(normalizedPluginConfig); await doGetPluginVersion(normalizedPluginConfig);
const pluginOptions = doValidatePluginOptions(normalizedPluginConfig); const pluginOptions = doValidatePluginOptions(normalizedPluginConfig);
// Side-effect: merge the normalized theme config in the original one // Side-effect: merge the normalized theme config in the original one

View file

@ -10,10 +10,10 @@ import fs from 'fs-extra';
import themeAlias from '../alias'; import themeAlias from '../alias';
describe('themeAlias', () => { describe('themeAlias', () => {
test('valid themePath 1 with components', () => { test('valid themePath 1 with components', async () => {
const fixtures = path.join(__dirname, '__fixtures__'); const fixtures = path.join(__dirname, '__fixtures__');
const themePath = path.join(fixtures, 'theme-1'); const themePath = path.join(fixtures, 'theme-1');
const alias = themeAlias(themePath, true); const alias = await themeAlias(themePath, true);
// Testing entries, because order matters! // Testing entries, because order matters!
expect(Object.entries(alias)).toEqual( expect(Object.entries(alias)).toEqual(
Object.entries({ Object.entries({
@ -26,10 +26,10 @@ describe('themeAlias', () => {
expect(alias).not.toEqual({}); 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 fixtures = path.join(__dirname, '__fixtures__');
const themePath = path.join(fixtures, 'theme-1'); const themePath = path.join(fixtures, 'theme-1');
const alias = themeAlias(themePath, false); const alias = await themeAlias(themePath, false);
// Testing entries, because order matters! // Testing entries, because order matters!
expect(Object.entries(alias)).toEqual( expect(Object.entries(alias)).toEqual(
Object.entries({ Object.entries({
@ -40,10 +40,10 @@ describe('themeAlias', () => {
expect(alias).not.toEqual({}); expect(alias).not.toEqual({});
}); });
test('valid themePath 2 with components', () => { test('valid themePath 2 with components', async () => {
const fixtures = path.join(__dirname, '__fixtures__'); const fixtures = path.join(__dirname, '__fixtures__');
const themePath = path.join(fixtures, 'theme-2'); const themePath = path.join(fixtures, 'theme-2');
const alias = themeAlias(themePath, true); const alias = await themeAlias(themePath, true);
// Testing entries, because order matters! // Testing entries, because order matters!
expect(Object.entries(alias)).toEqual( expect(Object.entries(alias)).toEqual(
Object.entries({ Object.entries({
@ -83,10 +83,10 @@ describe('themeAlias', () => {
expect(alias).not.toEqual({}); 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 fixtures = path.join(__dirname, '__fixtures__');
const themePath = path.join(fixtures, 'theme-2'); const themePath = path.join(fixtures, 'theme-2');
const alias = themeAlias(themePath, false); const alias = await themeAlias(themePath, false);
// Testing entries, because order matters! // Testing entries, because order matters!
expect(Object.entries(alias)).toEqual( expect(Object.entries(alias)).toEqual(
Object.entries({ Object.entries({
@ -107,26 +107,26 @@ describe('themeAlias', () => {
expect(alias).not.toEqual({}); expect(alias).not.toEqual({});
}); });
test('valid themePath with no components', () => { test('valid themePath with no components', async () => {
const fixtures = path.join(__dirname, '__fixtures__'); const fixtures = path.join(__dirname, '__fixtures__');
const themePath = path.join(fixtures, 'empty-theme'); const themePath = path.join(fixtures, 'empty-theme');
fs.ensureDirSync(themePath); await fs.ensureDir(themePath);
const alias = themeAlias(themePath, true); const alias = await themeAlias(themePath, true);
expect(alias).toEqual({}); 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 fixtures = path.join(__dirname, '__fixtures__');
const themePath = path.join(fixtures, 'empty-theme'); const themePath = path.join(fixtures, 'empty-theme');
fs.ensureDirSync(themePath); await fs.ensureDir(themePath);
const alias = themeAlias(themePath, false); const alias = await themeAlias(themePath, false);
expect(alias).toEqual({}); 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 fixtures = path.join(__dirname, '__fixtures__');
const themePath = path.join(fixtures, '__noExist__'); const themePath = path.join(fixtures, '__noExist__');
const alias = themeAlias(themePath, true); const alias = await themeAlias(themePath, true);
expect(alias).toEqual({}); expect(alias).toEqual({});
}); });
}); });

View file

@ -9,12 +9,12 @@ import path from 'path';
import {loadThemeAliases} from '../index'; import {loadThemeAliases} from '../index';
describe('loadThemeAliases', () => { 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 fixtures = path.join(__dirname, '__fixtures__');
const theme1Path = path.join(fixtures, 'theme-1'); const theme1Path = path.join(fixtures, 'theme-1');
const theme2Path = path.join(fixtures, 'theme-2'); const theme2Path = path.join(fixtures, 'theme-2');
const alias = loadThemeAliases([theme1Path, theme2Path], []); const alias = await loadThemeAliases([theme1Path, theme2Path], []);
// Testing entries, because order matters! // Testing entries, because order matters!
expect(Object.entries(alias)).toEqual( expect(Object.entries(alias)).toEqual(

View file

@ -25,16 +25,15 @@ export function sortAliases(aliases: ThemeAliases): ThemeAliases {
return Object.fromEntries(entries); return Object.fromEntries(entries);
} }
// TODO make async export default async function themeAlias(
export default function themeAlias(
themePath: string, themePath: string,
addOriginalAlias: boolean, addOriginalAlias: boolean,
): ThemeAliases { ): Promise<ThemeAliases> {
if (!fs.pathExistsSync(themePath)) { if (!(await fs.pathExists(themePath))) {
return {}; return {};
} }
const themeComponentFiles = Globby.sync(['**/*.{js,jsx,ts,tsx}'], { const themeComponentFiles = await Globby(['**/*.{js,jsx,ts,tsx}'], {
cwd: themePath, cwd: themePath,
}); });

View file

@ -12,14 +12,14 @@ import themeAlias, {sortAliases} from './alias';
const ThemeFallbackDir = path.resolve(__dirname, '../../client/theme-fallback'); const ThemeFallbackDir = path.resolve(__dirname, '../../client/theme-fallback');
export function loadThemeAliases( export async function loadThemeAliases(
themePaths: string[], themePaths: string[],
userThemePaths: string[], userThemePaths: string[],
): ThemeAliases { ): Promise<ThemeAliases> {
const aliases: ThemeAliases = {}; const aliases: ThemeAliases = {};
themePaths.forEach((themePath) => { for (const themePath of themePaths) {
const themeAliases = themeAlias(themePath, true); const themeAliases = await themeAlias(themePath, true);
Object.keys(themeAliases).forEach((aliasKey) => { Object.keys(themeAliases).forEach((aliasKey) => {
// If this alias shadows a previous one, use @theme-init to preserve the // 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 // initial one. @theme-init is only applied once: to the initial theme
@ -33,12 +33,12 @@ export function loadThemeAliases(
} }
aliases[aliasKey] = themeAliases[aliasKey]; aliases[aliasKey] = themeAliases[aliasKey];
}); });
}); }
userThemePaths.forEach((themePath) => { for (const themePath of userThemePaths) {
const userThemeAliases = themeAlias(themePath, false); const userThemeAliases = await themeAlias(themePath, false);
Object.assign(aliases, userThemeAliases); Object.assign(aliases, userThemeAliases);
}); }
return sortAliases(aliases); return sortAliases(aliases);
} }
@ -49,7 +49,7 @@ export function loadPluginsThemeAliases({
}: { }: {
siteDir: string; siteDir: string;
plugins: LoadedPlugin[]; plugins: LoadedPlugin[];
}): ThemeAliases { }): Promise<ThemeAliases> {
const pluginThemes: string[] = plugins const pluginThemes: string[] = plugins
.map((plugin) => (plugin.getThemePath ? plugin.getThemePath() : undefined)) .map((plugin) => (plugin.getThemePath ? plugin.getThemePath() : undefined))
.filter((x): x is string => Boolean(x)); .filter((x): x is string => Boolean(x));

View file

@ -6,7 +6,7 @@
*/ */
import type {RouteConfig} from '@docusaurus/types'; import type {RouteConfig} from '@docusaurus/types';
import nodePath from 'path'; import path from 'path';
import {posixPath, Globby} from '@docusaurus/utils'; import {posixPath, Globby} from '@docusaurus/utils';
// Recursively get the final routes (routes with no subroutes) // 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 // 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) // (also using the windows hard drive prefix like c: is not a good idea)
const globPaths = patterns.map((dirPath) => const globPaths = patterns.map((dirPath) =>
posixPath(nodePath.relative(process.cwd(), dirPath)), posixPath(path.relative(process.cwd(), dirPath)),
); );
return Globby(globPaths, options); return Globby(globPaths, options);

View file

@ -9,27 +9,27 @@ import {getPluginVersion} from '..';
import path from 'path'; import path from 'path';
describe('getPluginVersion', () => { describe('getPluginVersion', () => {
it('Can detect external packages plugins versions of correctly.', () => { it('Can detect external packages plugins versions of correctly.', async () => {
expect( await expect(
getPluginVersion( getPluginVersion(
path.join(__dirname, '..', '__fixtures__', 'dummy-plugin.js'), path.join(__dirname, '..', '__fixtures__', 'dummy-plugin.js'),
// Make the plugin appear external. // Make the plugin appear external.
path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'), path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'),
), ),
).toEqual({type: 'package', version: 'random-version'}); ).resolves.toEqual({type: 'package', version: 'random-version'});
}); });
it('Can detect project plugins versions correctly.', () => { it('Can detect project plugins versions correctly.', async () => {
expect( await expect(
getPluginVersion( getPluginVersion(
path.join(__dirname, '..', '__fixtures__', 'dummy-plugin.js'), path.join(__dirname, '..', '__fixtures__', 'dummy-plugin.js'),
// Make the plugin appear project local. // Make the plugin appear project local.
path.join(__dirname, '..', '__fixtures__'), path.join(__dirname, '..', '__fixtures__'),
), ),
).toEqual({type: 'project'}); ).resolves.toEqual({type: 'project'});
}); });
it('Can detect local packages versions correctly.', () => { it('Can detect local packages versions correctly.', async () => {
expect(getPluginVersion('/', '/')).toEqual({type: 'local'}); await expect(getPluginVersion('/', '/')).resolves.toEqual({type: 'local'});
}); });
}); });

View file

@ -6,13 +6,13 @@
*/ */
import type {DocusaurusPluginVersionInformation} from '@docusaurus/types'; import type {DocusaurusPluginVersionInformation} from '@docusaurus/types';
import {existsSync, lstatSync} from 'fs-extra'; import fs from 'fs-extra';
import {dirname, join} from 'path'; import path from 'path';
export function getPackageJsonVersion( export async function getPackageJsonVersion(
packageJsonPath: string, packageJsonPath: string,
): string | undefined { ): Promise<string | undefined> {
if (existsSync(packageJsonPath)) { if (await fs.pathExists(packageJsonPath)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
const {version} = require(packageJsonPath); const {version} = require(packageJsonPath);
return typeof version === 'string' ? version : undefined; return typeof version === 'string' ? version : undefined;
@ -20,10 +20,10 @@ export function getPackageJsonVersion(
return undefined; return undefined;
} }
export function getPackageJsonName( export async function getPackageJsonName(
packageJsonPath: string, packageJsonPath: string,
): string | undefined { ): Promise<string | undefined> {
if (existsSync(packageJsonPath)) { if (await fs.pathExists(packageJsonPath)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
const {name} = require(packageJsonPath); const {name} = require(packageJsonPath);
return typeof name === 'string' ? name : undefined; return typeof name === 'string' ? name : undefined;
@ -31,17 +31,20 @@ export function getPackageJsonName(
return undefined; return undefined;
} }
export function getPluginVersion( export async function getPluginVersion(
pluginPath: string, pluginPath: string,
siteDir: string, siteDir: string,
): DocusaurusPluginVersionInformation { ): Promise<DocusaurusPluginVersionInformation> {
let potentialPluginPackageJsonDirectory = dirname(pluginPath); let potentialPluginPackageJsonDirectory = path.dirname(pluginPath);
while (potentialPluginPackageJsonDirectory !== '/') { while (potentialPluginPackageJsonDirectory !== '/') {
const packageJsonPath = join( const packageJsonPath = path.join(
potentialPluginPackageJsonDirectory, potentialPluginPackageJsonDirectory,
'package.json', 'package.json',
); );
if (existsSync(packageJsonPath) && lstatSync(packageJsonPath).isFile()) { if (
(await fs.pathExists(packageJsonPath)) &&
(await fs.lstat(packageJsonPath)).isFile()
) {
if (potentialPluginPackageJsonDirectory === siteDir) { if (potentialPluginPackageJsonDirectory === siteDir) {
// If the plugin belongs to the same docusaurus project, we classify it // If the plugin belongs to the same docusaurus project, we classify it
// as local plugin. // as local plugin.
@ -49,11 +52,11 @@ export function getPluginVersion(
} }
return { return {
type: 'package', type: 'package',
name: getPackageJsonName(packageJsonPath), name: await getPackageJsonName(packageJsonPath),
version: getPackageJsonVersion(packageJsonPath), version: await getPackageJsonVersion(packageJsonPath),
}; };
} }
potentialPluginPackageJsonDirectory = dirname( potentialPluginPackageJsonDirectory = path.dirname(
potentialPluginPackageJsonDirectory, potentialPluginPackageJsonDirectory,
); );
} }

View file

@ -68,10 +68,10 @@ describe('babel transpilation exclude logic', () => {
}); });
describe('getDocusaurusAliases()', () => { describe('getDocusaurusAliases()', () => {
test('return appropriate webpack aliases', () => { test('return appropriate webpack aliases', async () => {
// using relative paths makes tests work everywhere // using relative paths makes tests work everywhere
const relativeDocusaurusAliases = _.mapValues( const relativeDocusaurusAliases = _.mapValues(
getDocusaurusAliases(), await getDocusaurusAliases(),
(aliasValue) => posixPath(path.relative(__dirname, aliasValue)), (aliasValue) => posixPath(path.relative(__dirname, aliasValue)),
); );
expect(relativeDocusaurusAliases).toMatchSnapshot(); expect(relativeDocusaurusAliases).toMatchSnapshot();
@ -120,10 +120,10 @@ describe('base webpack config', () => {
jest.restoreAllMocks(); jest.restoreAllMocks();
}); });
test('should create webpack aliases', () => { test('should create webpack aliases', async () => {
// @ts-expect-error: Docusaurus webpack alias is always an object // @ts-expect-error: Docusaurus webpack alias is always an object
const aliases: ThemeAliases = const aliases: ThemeAliases =
createBaseConfig(props, true).resolve?.alias ?? {}; (await createBaseConfig(props, true)).resolve?.alias ?? {};
// Make aliases relative so that test work on all computers // Make aliases relative so that test work on all computers
const relativeAliases = _.mapValues(aliases, (a) => const relativeAliases = _.mapValues(aliases, (a) =>
posixPath(path.relative(props.siteDir, a)), posixPath(path.relative(props.siteDir, a)),
@ -131,14 +131,14 @@ describe('base webpack config', () => {
expect(relativeAliases).toMatchSnapshot(); expect(relativeAliases).toMatchSnapshot();
}); });
test('should use svg rule', () => { test('should use svg rule', async () => {
const fileLoaderUtils = utils.getFileLoaderUtils(); const fileLoaderUtils = utils.getFileLoaderUtils();
const mockSvg = jest.spyOn(fileLoaderUtils.rules, 'svg'); const mockSvg = jest.spyOn(fileLoaderUtils.rules, 'svg');
jest jest
.spyOn(utils, 'getFileLoaderUtils') .spyOn(utils, 'getFileLoaderUtils')
.mockImplementation(() => fileLoaderUtils); .mockImplementation(() => fileLoaderUtils);
createBaseConfig(props, false, false); await createBaseConfig(props, false, false);
expect(mockSvg).toBeCalled(); expect(mockSvg).toBeCalled();
}); });
}); });

View file

@ -14,7 +14,7 @@ describe('webpack dev config', () => {
test('simple', async () => { test('simple', async () => {
console.log = jest.fn(); console.log = jest.fn();
const props = await loadSetup('simple'); const props = await loadSetup('simple');
const config = createClientConfig(props); const config = await createClientConfig(props);
const errors = validate(config); const errors = validate(config);
expect(errors).toBeUndefined(); expect(errors).toBeUndefined();
}); });
@ -22,7 +22,7 @@ describe('webpack dev config', () => {
test('custom', async () => { test('custom', async () => {
console.log = jest.fn(); console.log = jest.fn();
const props = await loadSetup('custom'); const props = await loadSetup('custom');
const config = createClientConfig(props); const config = await createClientConfig(props);
const errors = validate(config); const errors = validate(config);
expect(errors).toBeUndefined(); expect(errors).toBeUndefined();
}); });

View file

@ -14,7 +14,7 @@ describe('webpack production config', () => {
test('simple', async () => { test('simple', async () => {
console.log = jest.fn(); console.log = jest.fn();
const props = await loadSetup('simple'); const props = await loadSetup('simple');
const config = createServerConfig({props}); const config = await createServerConfig({props});
const errors = validate(config); const errors = validate(config);
expect(errors).toBeUndefined(); expect(errors).toBeUndefined();
}); });
@ -22,7 +22,7 @@ describe('webpack production config', () => {
test('custom', async () => { test('custom', async () => {
console.log = jest.fn(); console.log = jest.fn();
const props = await loadSetup('custom'); const props = await loadSetup('custom');
const config = createServerConfig({props}); const config = await createServerConfig({props});
const errors = validate(config); const errors = validate(config);
expect(errors).toBeUndefined(); expect(errors).toBeUndefined();
}); });

View file

@ -44,13 +44,13 @@ export function excludeJS(modulePath: string): boolean {
); );
} }
export function getDocusaurusAliases(): Record<string, string> { export async function getDocusaurusAliases(): Promise<Record<string, string>> {
const dirPath = path.resolve(__dirname, '../client/exports'); const dirPath = path.resolve(__dirname, '../client/exports');
const extensions = ['.js', '.ts', '.tsx']; const extensions = ['.js', '.ts', '.tsx'];
const aliases: Record<string, string> = {}; const aliases: Record<string, string> = {};
fs.readdirSync(dirPath) (await fs.readdir(dirPath))
.filter((fileName) => extensions.includes(path.extname(fileName))) .filter((fileName) => extensions.includes(path.extname(fileName)))
.forEach((fileName) => { .forEach((fileName) => {
const fileNameWithoutExtension = path.basename( const fileNameWithoutExtension = path.basename(
@ -64,11 +64,11 @@ export function getDocusaurusAliases(): Record<string, string> {
return aliases; return aliases;
} }
export function createBaseConfig( export async function createBaseConfig(
props: Props, props: Props,
isServer: boolean, isServer: boolean,
minify: boolean = true, minify: boolean = true,
): Configuration { ): Promise<Configuration> {
const { const {
outDir, outDir,
siteDir, siteDir,
@ -90,7 +90,7 @@ export function createBaseConfig(
const name = isServer ? 'server' : 'client'; const name = isServer ? 'server' : 'client';
const mode = isProd ? 'production' : 'development'; const mode = isProd ? 'production' : 'development';
const themeAliases = loadPluginsThemeAliases({siteDir, plugins}); const themeAliases = await loadPluginsThemeAliases({siteDir, plugins});
return { return {
mode, mode,
@ -158,7 +158,7 @@ export function createBaseConfig(
// Note: a @docusaurus alias would also catch @docusaurus/theme-common, // Note: a @docusaurus alias would also catch @docusaurus/theme-common,
// so we use fine-grained aliases instead // so we use fine-grained aliases instead
// '@docusaurus': path.resolve(__dirname, '../client/exports'), // '@docusaurus': path.resolve(__dirname, '../client/exports'),
...getDocusaurusAliases(), ...(await getDocusaurusAliases()),
...themeAliases, ...themeAliases,
}, },
// This allows you to set a fallback for where Webpack should look for // This allows you to set a fallback for where Webpack should look for
@ -169,7 +169,7 @@ export function createBaseConfig(
modules: [ modules: [
path.resolve(__dirname, '..', '..', 'node_modules'), path.resolve(__dirname, '..', '..', 'node_modules'),
'node_modules', 'node_modules',
path.resolve(fs.realpathSync(process.cwd()), 'node_modules'), path.resolve(await fs.realpath(process.cwd()), 'node_modules'),
], ],
}, },
resolveLoader: { resolveLoader: {
@ -225,7 +225,7 @@ export function createBaseConfig(
use: [ use: [
getCustomizableJSLoader(siteConfig.webpack?.jsLoader)({ getCustomizableJSLoader(siteConfig.webpack?.jsLoader)({
isServer, isServer,
babelOptions: getCustomBabelConfigFilePath(siteDir), babelOptions: await getCustomBabelConfigFilePath(siteDir),
}), }),
], ],
}, },

View file

@ -15,12 +15,12 @@ import {createBaseConfig} from './base';
import ChunkAssetPlugin from './plugins/ChunkAssetPlugin'; import ChunkAssetPlugin from './plugins/ChunkAssetPlugin';
import LogPlugin from './plugins/LogPlugin'; import LogPlugin from './plugins/LogPlugin';
export default function createClientConfig( export default async function createClientConfig(
props: Props, props: Props,
minify: boolean = true, minify: boolean = true,
): Configuration { ): Promise<Configuration> {
const isBuilding = process.argv[2] === 'build'; const isBuilding = process.argv[2] === 'build';
const config = createBaseConfig(props, false, minify); const config = await createBaseConfig(props, false, minify);
const clientConfig = merge(config, { const clientConfig = merge(config, {
// useless, disabled on purpose (errors on existing sites with no // useless, disabled on purpose (errors on existing sites with no

View file

@ -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 // Forked for Docusaurus: https://github.com/slorber/static-site-generator-webpack-plugin
import StaticSiteGeneratorPlugin from '@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, props,
onLinksCollected = () => {}, onLinksCollected = () => {},
}: { }: {
props: Props; props: Props;
onLinksCollected?: (staticPagePath: string, links: string[]) => void; onLinksCollected?: (staticPagePath: string, links: string[]) => void;
}): Configuration { }): Promise<Configuration> {
const { const {
baseUrl, baseUrl,
routesPaths, routesPaths,
@ -35,7 +35,7 @@ export default function createServerConfig({
ssrTemplate, ssrTemplate,
siteConfig: {noIndex, trailingSlash}, siteConfig: {noIndex, trailingSlash},
} = props; } = props;
const config = createBaseConfig(props, true); const config = await createBaseConfig(props, true);
const routesLocation: Record<string, string> = {}; const routesLocation: Record<string, string> = {};
// Array of paths to be rendered. Relative to output directory // Array of paths to be rendered. Relative to output directory

View file

@ -101,14 +101,14 @@ export function getStyleLoaders(
]; ];
} }
export function getCustomBabelConfigFilePath( export async function getCustomBabelConfigFilePath(
siteDir: string, siteDir: string,
): string | undefined { ): Promise<string | undefined> {
const customBabelConfigurationPath = path.join( const customBabelConfigurationPath = path.join(
siteDir, siteDir,
BABEL_CONFIG_FILE_NAME, BABEL_CONFIG_FILE_NAME,
); );
return fs.existsSync(customBabelConfigurationPath) return (await fs.pathExists(customBabelConfigurationPath))
? customBabelConfigurationPath ? customBabelConfigurationPath
: undefined; : undefined;
} }
@ -333,19 +333,21 @@ ${err}`,
} }
// Read file and throw an error if it doesn't exist // Read file and throw an error if it doesn't exist
function readEnvFile(file: string, type: string) { async function readEnvFile(file: string, type: string) {
if (!fs.existsSync(file)) { if (!(await fs.pathExists(file))) {
throw new Error( throw new Error(
`You specified ${type} in your env, but the file "${file}" can't be found.`, `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 // Get the https config
// Return cert files if provided in env, otherwise just true or false // 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 {SSL_CRT_FILE, SSL_KEY_FILE, HTTPS} = process.env;
const isHttps = HTTPS === 'true'; 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 crtFile = path.resolve(appDirectory, SSL_CRT_FILE);
const keyFile = path.resolve(appDirectory, SSL_KEY_FILE); const keyFile = path.resolve(appDirectory, SSL_KEY_FILE);
const config = { const config = {
cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), cert: await readEnvFile(crtFile, 'SSL_CRT_FILE'),
key: readEnvFile(keyFile, 'SSL_KEY_FILE'), key: await readEnvFile(keyFile, 'SSL_KEY_FILE'),
}; };
validateKeyAndCerts({...config, keyFile, crtFile}); validateKeyAndCerts({...config, keyFile, crtFile});

View file

@ -19,6 +19,7 @@ const dogfoodingPluginInstances = [
// Using a symlinked folder as source, test for use-case https://github.com/facebook/docusaurus/issues/3272 // 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 // 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'), path: fs.realpathSync('_dogfooding/docs-tests-symlink'),
showLastUpdateTime: true, showLastUpdateTime: true,
sidebarItemsGenerator(args) { sidebarItemsGenerator(args) {

View file

@ -49,7 +49,7 @@ expect.extend({
describe('users', () => { describe('users', () => {
sortedUsers.forEach((user) => { sortedUsers.forEach((user) => {
test(user.title, () => { test(user.title, async () => {
Joi.attempt( Joi.attempt(
user, user,
Joi.object<User>({ Joi.object<User>({
@ -86,6 +86,7 @@ describe('users', () => {
}); });
const imageDir = path.join(__dirname, '../showcase'); const imageDir = path.join(__dirname, '../showcase');
// eslint-disable-next-line no-restricted-properties
const files = fs const files = fs
.readdirSync(imageDir) .readdirSync(imageDir)
.filter((file) => ['.png', 'jpg', '.jpeg'].includes(path.extname(file))); .filter((file) => ['.png', 'jpg', '.jpeg'].includes(path.extname(file)));