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,
message: `Use ${alternative} instead.`,
})),
...[
'readdirSync',
'readFileSync',
'statSync',
'lstatSync',
'existsSync',
'pathExistsSync',
'realpathSync',
'mkdirSync',
'mkdirpSync',
'mkdirsSync',
'writeFileSync',
'writeJsonSync',
'outputFileSync',
'outputJsonSync',
'moveSync',
'copySync',
'copyFileSync',
'ensureFileSync',
'ensureDirSync',
'ensureLinkSync',
'ensureSymlinkSync',
'unlinkSync',
'removeSync',
'emptyDirSync',
].map((property) => ({
object: 'fs',
property,
message: 'Do not use sync fs methods.',
})),
],
'no-restricted-syntax': [
WARNING,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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 content = fs.readFileSync(filepath, 'utf-8');
const content = await fs.readFile(filepath, 'utf-8');
const transformedContent = linkify(content, filepath, markdownOptions);
return [content, transformedContent];
};
test('transform nothing', () => {
test('transform nothing', async () => {
const doc1 = path.join(versionCurrent.contentPath, 'doc1.md');
const [content, transformedContent] = transform(doc1);
const [content, transformedContent] = await transform(doc1);
expect(transformedContent).toMatchSnapshot();
expect(content).toEqual(transformedContent);
});
test('transform to correct links', () => {
test('transform to correct links', async () => {
const doc2 = path.join(versionCurrent.contentPath, 'doc2.md');
const [content, transformedContent] = transform(doc2);
const [content, transformedContent] = await transform(doc2);
expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/doc1');
expect(transformedContent).toContain('](/docs/doc2');
@ -118,19 +121,19 @@ test('transform to correct links', () => {
expect(content).not.toEqual(transformedContent);
});
test('transform relative links', () => {
test('transform relative links', async () => {
const doc3 = path.join(versionCurrent.contentPath, 'subdir', 'doc3.md');
const [content, transformedContent] = transform(doc3);
const [content, transformedContent] = await transform(doc3);
expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/doc2');
expect(transformedContent).not.toContain('](../doc2.md)');
expect(content).not.toEqual(transformedContent);
});
test('transforms reference links', () => {
test('transforms reference links', async () => {
const doc4 = path.join(versionCurrent.contentPath, 'doc4.md');
const [content, transformedContent] = transform(doc4);
const [content, transformedContent] = await transform(doc4);
expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('[doc1]: /docs/doc1');
expect(transformedContent).toContain('[doc2]: /docs/doc2');
@ -139,10 +142,10 @@ test('transforms reference links', () => {
expect(content).not.toEqual(transformedContent);
});
test('report broken markdown links', () => {
test('report broken markdown links', async () => {
const doc5 = path.join(versionCurrent.contentPath, 'doc5.md');
const onBrokenMarkdownLink = jest.fn();
const [content, transformedContent] = transform(doc5, {
const [content, transformedContent] = await transform(doc5, {
onBrokenMarkdownLink,
});
expect(transformedContent).toEqual(content);
@ -169,9 +172,9 @@ test('report broken markdown links', () => {
} as BrokenMarkdownLink);
});
test('transforms absolute links in versioned docs', () => {
test('transforms absolute links in versioned docs', async () => {
const doc2 = path.join(version100.contentPath, 'doc2.md');
const [content, transformedContent] = transform(doc2);
const [content, transformedContent] = await transform(doc2);
expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/1.0.0/subdir/doc1');
expect(transformedContent).toContain('](/docs/1.0.0/doc2#existing-docs');
@ -180,9 +183,9 @@ test('transforms absolute links in versioned docs', () => {
expect(content).not.toEqual(transformedContent);
});
test('transforms relative links in versioned docs', () => {
test('transforms relative links in versioned docs', async () => {
const doc1 = path.join(version100.contentPath, 'subdir', 'doc1.md');
const [content, transformedContent] = transform(doc1);
const [content, transformedContent] = await transform(doc1);
expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/1.0.0/doc2');
expect(transformedContent).not.toContain('](../doc2.md)');

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,8 @@
*/
// @ts-check
/* eslint-disable import/no-extraneous-dependencies */
// TODO convert this to ESM, which would also allow TLA
/* eslint-disable import/no-extraneous-dependencies, no-restricted-properties */
const logger = require('@docusaurus/logger').default;
const path = require('path');

View file

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

View file

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

View file

@ -36,7 +36,7 @@ const {
*
* cache data is stored in `~/.config/configstore/update-notifier-@docusaurus`
*/
function beforeCli() {
async function beforeCli() {
const notifier = updateNotifier({
pkg: {
name,
@ -98,7 +98,9 @@ function beforeCli() {
.filter((p) => p.startsWith('@docusaurus'))
.map((p) => p.concat('@latest'))
.join(' ');
const isYarnUsed = fs.existsSync(path.resolve(process.cwd(), 'yarn.lock'));
const isYarnUsed = await fs.pathExists(
path.resolve(process.cwd(), 'yarn.lock'),
);
const upgradeCommand = isYarnUsed
? `yarn upgrade ${siteDocusaurusPackagesForUpdate}`
: `npm i ${siteDocusaurusPackagesForUpdate}`;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,12 +9,12 @@ import path from 'path';
import {loadThemeAliases} from '../index';
describe('loadThemeAliases', () => {
test('next alias can override the previous alias', () => {
test('next alias can override the previous alias', async () => {
const fixtures = path.join(__dirname, '__fixtures__');
const theme1Path = path.join(fixtures, 'theme-1');
const theme2Path = path.join(fixtures, 'theme-2');
const alias = loadThemeAliases([theme1Path, theme2Path], []);
const alias = await loadThemeAliases([theme1Path, theme2Path], []);
// Testing entries, because order matters!
expect(Object.entries(alias)).toEqual(

View file

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

View file

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

View file

@ -6,7 +6,7 @@
*/
import type {RouteConfig} from '@docusaurus/types';
import nodePath from 'path';
import path from 'path';
import {posixPath, Globby} from '@docusaurus/utils';
// Recursively get the final routes (routes with no subroutes)
@ -26,7 +26,7 @@ export async function safeGlobby(
// Required for Windows support, as paths using \ should not be used by globby
// (also using the windows hard drive prefix like c: is not a good idea)
const globPaths = patterns.map((dirPath) =>
posixPath(nodePath.relative(process.cwd(), dirPath)),
posixPath(path.relative(process.cwd(), dirPath)),
);
return Globby(globPaths, options);

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

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
import StaticSiteGeneratorPlugin from '@slorber/static-site-generator-webpack-plugin';
export default function createServerConfig({
export default async function createServerConfig({
props,
onLinksCollected = () => {},
}: {
props: Props;
onLinksCollected?: (staticPagePath: string, links: string[]) => void;
}): Configuration {
}): Promise<Configuration> {
const {
baseUrl,
routesPaths,
@ -35,7 +35,7 @@ export default function createServerConfig({
ssrTemplate,
siteConfig: {noIndex, trailingSlash},
} = props;
const config = createBaseConfig(props, true);
const config = await createBaseConfig(props, true);
const routesLocation: Record<string, string> = {};
// 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,
): string | undefined {
): Promise<string | undefined> {
const customBabelConfigurationPath = path.join(
siteDir,
BABEL_CONFIG_FILE_NAME,
);
return fs.existsSync(customBabelConfigurationPath)
return (await fs.pathExists(customBabelConfigurationPath))
? customBabelConfigurationPath
: undefined;
}
@ -333,19 +333,21 @@ ${err}`,
}
// Read file and throw an error if it doesn't exist
function readEnvFile(file: string, type: string) {
if (!fs.existsSync(file)) {
async function readEnvFile(file: string, type: string) {
if (!(await fs.pathExists(file))) {
throw new Error(
`You specified ${type} in your env, but the file "${file}" can't be found.`,
);
}
return fs.readFileSync(file);
return fs.readFile(file);
}
const appDirectory = fs.realpathSync(process.cwd());
// Get the https config
// Return cert files if provided in env, otherwise just true or false
export function getHttpsConfig(): boolean | {cert: Buffer; key: Buffer} {
export async function getHttpsConfig(): Promise<
boolean | {cert: Buffer; key: Buffer}
> {
const appDirectory = await fs.realpath(process.cwd());
const {SSL_CRT_FILE, SSL_KEY_FILE, HTTPS} = process.env;
const isHttps = HTTPS === 'true';
@ -353,8 +355,8 @@ export function getHttpsConfig(): boolean | {cert: Buffer; key: Buffer} {
const crtFile = path.resolve(appDirectory, SSL_CRT_FILE);
const keyFile = path.resolve(appDirectory, SSL_KEY_FILE);
const config = {
cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
key: readEnvFile(keyFile, 'SSL_KEY_FILE'),
cert: await readEnvFile(crtFile, 'SSL_CRT_FILE'),
key: await readEnvFile(keyFile, 'SSL_KEY_FILE'),
};
validateKeyAndCerts({...config, keyFile, crtFile});

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
// The target folder uses a _ prefix to test against an edge case regarding MDX partials: https://github.com/facebook/docusaurus/discussions/5181#discussioncomment-1018079
// eslint-disable-next-line no-restricted-properties
path: fs.realpathSync('_dogfooding/docs-tests-symlink'),
showLastUpdateTime: true,
sidebarItemsGenerator(args) {

View file

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