mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 15:47:23 +02:00
refactor: convert all fs methods to async (#6725)
* refactor: convert all fs methods to async * fix snap
This commit is contained in:
parent
c0b3c9af65
commit
c6d0d812eb
46 changed files with 518 additions and 429 deletions
30
.eslintrc.js
30
.eslintrc.js
|
@ -107,6 +107,36 @@ module.exports = {
|
|||
property,
|
||||
message: `Use ${alternative} instead.`,
|
||||
})),
|
||||
...[
|
||||
'readdirSync',
|
||||
'readFileSync',
|
||||
'statSync',
|
||||
'lstatSync',
|
||||
'existsSync',
|
||||
'pathExistsSync',
|
||||
'realpathSync',
|
||||
'mkdirSync',
|
||||
'mkdirpSync',
|
||||
'mkdirsSync',
|
||||
'writeFileSync',
|
||||
'writeJsonSync',
|
||||
'outputFileSync',
|
||||
'outputJsonSync',
|
||||
'moveSync',
|
||||
'copySync',
|
||||
'copyFileSync',
|
||||
'ensureFileSync',
|
||||
'ensureDirSync',
|
||||
'ensureLinkSync',
|
||||
'ensureSymlinkSync',
|
||||
'unlinkSync',
|
||||
'removeSync',
|
||||
'emptyDirSync',
|
||||
].map((property) => ({
|
||||
object: 'fs',
|
||||
property,
|
||||
message: 'Do not use sync fs methods.',
|
||||
})),
|
||||
],
|
||||
'no-restricted-syntax': [
|
||||
WARNING,
|
||||
|
|
|
@ -37,16 +37,14 @@ 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(
|
||||
(d) =>
|
||||
!d.startsWith('.') &&
|
||||
!d.startsWith('README') &&
|
||||
!d.endsWith(TypeScriptTemplateSuffix) &&
|
||||
d !== 'shared',
|
||||
);
|
||||
async function readTemplates(templatesDir: string) {
|
||||
const templates = (await fs.readdir(templatesDir)).filter(
|
||||
(d) =>
|
||||
!d.startsWith('.') &&
|
||||
!d.startsWith('README') &&
|
||||
!d.endsWith(TypeScriptTemplateSuffix) &&
|
||||
d !== 'shared',
|
||||
);
|
||||
|
||||
// Classic should be first in list!
|
||||
return _.sortBy(templates, (t) => t !== RecommendedTemplate);
|
||||
|
@ -86,8 +84,8 @@ async function copyTemplate(
|
|||
if (tsBaseTemplate) {
|
||||
const tsBaseTemplatePath = path.resolve(templatesDir, tsBaseTemplate);
|
||||
await fs.copy(tsBaseTemplatePath, dest, {
|
||||
filter: (filePath) =>
|
||||
fs.statSync(filePath).isDirectory() ||
|
||||
filter: async (filePath) =>
|
||||
(await fs.stat(filePath)).isDirectory() ||
|
||||
path.extname(filePath) === '.css' ||
|
||||
path.basename(filePath) === 'docusaurus.config.js',
|
||||
});
|
||||
|
@ -96,7 +94,7 @@ async function copyTemplate(
|
|||
await fs.copy(path.resolve(templatesDir, template), dest, {
|
||||
// Symlinks don't exist in published NPM packages anymore, so this is only
|
||||
// to prevent errors during local testing
|
||||
filter: (filePath) => !fs.lstatSync(filePath).isSymbolicLink(),
|
||||
filter: async (filePath) => !(await fs.lstat(filePath)).isSymbolicLink(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -135,9 +133,9 @@ export default async function init(
|
|||
): Promise<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';
|
||||
|
|
|
@ -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?`,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) => {
|
||||
const filePath = path.join(newDir, 'src', 'pages', file);
|
||||
const content = String(fs.readFileSync(filePath));
|
||||
fs.writeFileSync(filePath, migratePage(content));
|
||||
});
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const filePath = path.join(newDir, 'src', 'pages', file);
|
||||
const content = await fs.readFile(filePath, 'utf-8');
|
||||
await fs.writeFile(filePath, migratePage(content));
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(`Unable to migrate Pages: ${e}`);
|
||||
createDefaultLandingPage(context);
|
||||
await createDefaultLandingPage(context);
|
||||
}
|
||||
} else {
|
||||
logger.info('Ignoring Pages');
|
||||
}
|
||||
}
|
||||
|
||||
function createDefaultLandingPage({newDir}: MigrationContext) {
|
||||
async function createDefaultLandingPage({newDir}: MigrationContext) {
|
||||
const indexPage = `import Layout from "@theme/Layout";
|
||||
import React from "react";
|
||||
|
||||
|
@ -402,30 +404,32 @@ function createDefaultLandingPage({newDir}: MigrationContext) {
|
|||
return <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(
|
||||
file,
|
||||
sanitizedFileContent(content, shouldMigrateMdFiles),
|
||||
);
|
||||
});
|
||||
if (await fs.pathExists(path.join(siteDir, 'blog'))) {
|
||||
await fs.copy(path.join(siteDir, 'blog'), path.join(newDir, 'blog'));
|
||||
const files = await walk(path.join(newDir, 'blog'));
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const content = await fs.readFile(file, 'utf-8');
|
||||
await fs.writeFile(
|
||||
file,
|
||||
sanitizedFileContent(content, shouldMigrateMdFiles),
|
||||
);
|
||||
}),
|
||||
);
|
||||
context.v2Config.presets[0][1].blog.path = 'blog';
|
||||
logger.success('Migrated blogs to version 2 with change in front matter');
|
||||
} else {
|
||||
|
@ -433,21 +437,21 @@ function migrateBlogFiles(context: MigrationContext): void {
|
|||
}
|
||||
}
|
||||
|
||||
function handleVersioning(context: MigrationContext): void {
|
||||
async function handleVersioning(context: MigrationContext) {
|
||||
const {siteDir, newDir} = context;
|
||||
if (fs.existsSync(path.join(siteDir, 'versions.json'))) {
|
||||
const loadedVersions: Array<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,69 +460,78 @@ function handleVersioning(context: MigrationContext): void {
|
|||
}
|
||||
}
|
||||
|
||||
function migrateVersionedDocs(
|
||||
async function migrateVersionedDocs(
|
||||
context: MigrationContext,
|
||||
versions: string[],
|
||||
versionRegex: RegExp,
|
||||
): void {
|
||||
) {
|
||||
const {siteDir, newDir, shouldMigrateMdFiles} = context;
|
||||
versions.reverse().forEach((version, index) => {
|
||||
if (index === 0) {
|
||||
fs.copySync(
|
||||
path.join(siteDir, '..', context.v1Config.customDocsPath || 'docs'),
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
fs.copySync(
|
||||
path.join(siteDir, 'versioned_docs', `version-${version}`),
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
fs.mkdirsSync(path.join(newDir, 'versioned_docs', `version-${version}`));
|
||||
fs.copySync(
|
||||
path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`),
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
fs.copySync(
|
||||
path.join(siteDir, 'versioned_docs', `version-${version}`),
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
} catch {
|
||||
fs.copySync(
|
||||
path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`),
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
}
|
||||
});
|
||||
const files = walk(path.join(newDir, 'versioned_docs'));
|
||||
files.forEach((pathToFile) => {
|
||||
if (path.extname(pathToFile) === '.md') {
|
||||
const content = fs.readFileSync(pathToFile).toString();
|
||||
fs.writeFileSync(
|
||||
pathToFile,
|
||||
sanitizedFileContent(
|
||||
content.replace(versionRegex, ''),
|
||||
shouldMigrateMdFiles,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
await Promise.all(
|
||||
versions.reverse().map(async (version, index) => {
|
||||
if (index === 0) {
|
||||
await fs.copy(
|
||||
path.join(siteDir, '..', context.v1Config.customDocsPath || 'docs'),
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
await fs.copy(
|
||||
path.join(siteDir, 'versioned_docs', `version-${version}`),
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await fs.mkdirs(
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
await fs.copy(
|
||||
path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`),
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
await fs.copy(
|
||||
path.join(siteDir, 'versioned_docs', `version-${version}`),
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
} catch {
|
||||
await fs.copy(
|
||||
path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`),
|
||||
path.join(newDir, 'versioned_docs', `version-${version}`),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
const files = await walk(path.join(newDir, 'versioned_docs'));
|
||||
await Promise.all(
|
||||
files.map(async (pathToFile) => {
|
||||
if (path.extname(pathToFile) === '.md') {
|
||||
const content = await fs.readFile(pathToFile, 'utf-8');
|
||||
await fs.writeFile(
|
||||
pathToFile,
|
||||
sanitizedFileContent(
|
||||
content.replace(versionRegex, ''),
|
||||
shouldMigrateMdFiles,
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function migrateVersionedSidebar(
|
||||
async function migrateVersionedSidebar(
|
||||
context: MigrationContext,
|
||||
versions: string[],
|
||||
versionRegex: RegExp,
|
||||
): void {
|
||||
) {
|
||||
const {siteDir, newDir} = context;
|
||||
if (fs.existsSync(path.join(siteDir, 'versioned_sidebars'))) {
|
||||
fs.mkdirpSync(path.join(newDir, 'versioned_sidebars'));
|
||||
if (await fs.pathExists(path.join(siteDir, 'versioned_sidebars'))) {
|
||||
await fs.mkdirp(path.join(newDir, 'versioned_sidebars'));
|
||||
const sidebars: {
|
||||
entries: SidebarEntries;
|
||||
version: string;
|
||||
}[] = [];
|
||||
versions.forEach((version, index) => {
|
||||
// Order matters: if a sidebar file doesn't exist, we have to use the
|
||||
// previous version's
|
||||
for (let i = 0; i < versions.length; i += 1) {
|
||||
const version = versions[i];
|
||||
let sidebarEntries: SidebarEntries;
|
||||
const sidebarPath = path.join(
|
||||
siteDir,
|
||||
|
@ -526,79 +539,74 @@ function migrateVersionedSidebar(
|
|||
`version-${version}-sidebars.json`,
|
||||
);
|
||||
try {
|
||||
fs.statSync(sidebarPath);
|
||||
sidebarEntries = JSON.parse(String(fs.readFileSync(sidebarPath)));
|
||||
sidebarEntries = JSON.parse(await fs.readFile(sidebarPath, 'utf-8'));
|
||||
} catch {
|
||||
sidebars.push({version, entries: sidebars[index - 1].entries});
|
||||
sidebars.push({version, entries: sidebars[i - 1].entries});
|
||||
return;
|
||||
}
|
||||
const newSidebar = Object.entries(sidebarEntries).reduce(
|
||||
(topLevel: SidebarEntries, value) => {
|
||||
const key = value[0].replace(versionRegex, '');
|
||||
topLevel[key] = Object.entries(value[1]).reduce(
|
||||
(
|
||||
acc: {[key: string]: Array<Record<string, unknown> | string>},
|
||||
val,
|
||||
) => {
|
||||
acc[val[0].replace(versionRegex, '')] = (
|
||||
val[1] as Array<SidebarEntry>
|
||||
).map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
return item.replace(versionRegex, '');
|
||||
}
|
||||
return {
|
||||
type: 'category',
|
||||
label: item.label,
|
||||
ids: item.ids.map((id) => id.replace(versionRegex, '')),
|
||||
};
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
topLevel[key] = Object.entries(value[1]).reduce((acc, val) => {
|
||||
acc[val[0].replace(versionRegex, '')] = (
|
||||
val[1] as SidebarEntry[]
|
||||
).map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
return item.replace(versionRegex, '');
|
||||
}
|
||||
return {
|
||||
type: 'category',
|
||||
label: item.label,
|
||||
ids: item.ids.map((id) => id.replace(versionRegex, '')),
|
||||
};
|
||||
});
|
||||
return acc;
|
||||
}, {} as Record<string, Array<string | Record<string, unknown>>>);
|
||||
return topLevel;
|
||||
},
|
||||
{},
|
||||
);
|
||||
sidebars.push({version, entries: newSidebar});
|
||||
});
|
||||
sidebars.forEach((sidebar) => {
|
||||
const newSidebar = Object.entries(sidebar.entries).reduce(
|
||||
(acc: SidebarEntries, val) => {
|
||||
const key = `version-${sidebar.version}/${val[0]}`;
|
||||
acc[key] = Object.entries(val[1]).map((value) => ({
|
||||
type: 'category',
|
||||
label: value[0],
|
||||
items: (value[1] as Array<SidebarEntry>).map((sidebarItem) => {
|
||||
if (typeof sidebarItem === 'string') {
|
||||
}
|
||||
await Promise.all(
|
||||
sidebars.map(async (sidebar) => {
|
||||
const newSidebar = Object.entries(sidebar.entries).reduce(
|
||||
(acc, val) => {
|
||||
const key = `version-${sidebar.version}/${val[0]}`;
|
||||
acc[key] = Object.entries(val[1]).map((value) => ({
|
||||
type: 'category',
|
||||
label: value[0],
|
||||
items: (value[1] as SidebarEntry[]).map((sidebarItem) => {
|
||||
if (typeof sidebarItem === 'string') {
|
||||
return {
|
||||
type: 'doc',
|
||||
id: `version-${sidebar.version}/${sidebarItem}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'doc',
|
||||
id: `version-${sidebar.version}/${sidebarItem}`,
|
||||
type: 'category',
|
||||
label: sidebarItem.label,
|
||||
items: sidebarItem.ids.map((id) => ({
|
||||
type: 'doc',
|
||||
id: `version-${sidebar.version}/${id}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'category',
|
||||
label: sidebarItem.label,
|
||||
items: sidebarItem.ids.map((id: string) => ({
|
||||
type: 'doc',
|
||||
id: `version-${sidebar.version}/${id}`,
|
||||
})),
|
||||
};
|
||||
}),
|
||||
}));
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(
|
||||
newDir,
|
||||
'versioned_sidebars',
|
||||
`version-${sidebar.version}-sidebars.json`,
|
||||
),
|
||||
JSON.stringify(newSidebar, null, 2),
|
||||
);
|
||||
});
|
||||
}),
|
||||
}));
|
||||
return acc;
|
||||
},
|
||||
{} as SidebarEntries,
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(
|
||||
newDir,
|
||||
'versioned_sidebars',
|
||||
`version-${sidebar.version}-sidebars.json`,
|
||||
),
|
||||
JSON.stringify(newSidebar, null, 2),
|
||||
);
|
||||
}),
|
||||
);
|
||||
context.v2Config.themeConfig.navbar.items.push({
|
||||
label: 'Version',
|
||||
to: 'docs',
|
||||
|
@ -626,10 +634,10 @@ function migrateVersionedSidebar(
|
|||
}
|
||||
}
|
||||
|
||||
function migrateLatestSidebar(context: MigrationContext): void {
|
||||
async function migrateLatestSidebar(context: MigrationContext) {
|
||||
const {siteDir, newDir} = context;
|
||||
try {
|
||||
fs.copyFileSync(
|
||||
await fs.copyFile(
|
||||
path.join(siteDir, 'sidebars.json'),
|
||||
path.join(newDir, 'sidebars.json'),
|
||||
);
|
||||
|
@ -652,8 +660,8 @@ function migrateLatestSidebar(context: MigrationContext): void {
|
|||
--ifm-color-primary-darkest: ${primaryColor.darken(0.3).hex()};
|
||||
}
|
||||
`;
|
||||
fs.mkdirpSync(path.join(newDir, 'src', 'css'));
|
||||
fs.writeFileSync(path.join(newDir, 'src', 'css', 'customTheme.css'), css);
|
||||
await fs.mkdirp(path.join(newDir, 'src', 'css'));
|
||||
await fs.writeFile(path.join(newDir, 'src', 'css', 'customTheme.css'), css);
|
||||
context.v2Config.presets[0][1].theme.customCss = path.join(
|
||||
path.relative(newDir, path.join(siteDir, '..')),
|
||||
'src',
|
||||
|
@ -663,23 +671,25 @@ function migrateLatestSidebar(context: MigrationContext): void {
|
|||
}
|
||||
}
|
||||
|
||||
function migrateLatestDocs(context: MigrationContext): void {
|
||||
async function migrateLatestDocs(context: MigrationContext) {
|
||||
const {siteDir, newDir, shouldMigrateMdFiles} = context;
|
||||
if (fs.existsSync(path.join(siteDir, '..', 'docs'))) {
|
||||
if (await fs.pathExists(path.join(siteDir, '..', 'docs'))) {
|
||||
context.v2Config.presets[0][1].docs.path = path.join(
|
||||
path.relative(newDir, path.join(siteDir, '..')),
|
||||
'docs',
|
||||
);
|
||||
const files = walk(path.join(siteDir, '..', 'docs'));
|
||||
files.forEach((file) => {
|
||||
if (path.extname(file) === '.md') {
|
||||
const content = fs.readFileSync(file).toString();
|
||||
fs.writeFileSync(
|
||||
file,
|
||||
sanitizedFileContent(content, shouldMigrateMdFiles),
|
||||
);
|
||||
}
|
||||
});
|
||||
const files = await walk(path.join(siteDir, '..', 'docs'));
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
if (path.extname(file) === '.md') {
|
||||
const content = await fs.readFile(file, 'utf-8');
|
||||
await fs.writeFile(
|
||||
file,
|
||||
sanitizedFileContent(content, shouldMigrateMdFiles),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
logger.success('Migrated docs to version 2');
|
||||
} else {
|
||||
logger.warn('Docs folder not found. Skipping migration for docs');
|
||||
|
@ -713,7 +723,7 @@ async function migratePackageFile(context: MigrationContext): Promise<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) => {
|
||||
if (path.extname(filePath) === '.md') {
|
||||
const content = fs.readFileSync(filePath).toString();
|
||||
fs.writeFileSync(filePath, sanitizedFileContent(content, true));
|
||||
}
|
||||
});
|
||||
await fs.mkdirp(newDir);
|
||||
await fs.copy(siteDir, newDir);
|
||||
const files = await walk(newDir);
|
||||
await Promise.all(
|
||||
files.map(async (filePath) => {
|
||||
if (path.extname(filePath) === '.md') {
|
||||
const content = await fs.readFile(filePath, 'utf-8');
|
||||
await fs.writeFile(filePath, sanitizedFileContent(content, true));
|
||||
}
|
||||
}),
|
||||
);
|
||||
logger.success`Successfully migrated path=${siteDir} to path=${newDir}`;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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!`;
|
||||
}
|
||||
|
|
|
@ -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)');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -418,7 +418,7 @@ function createVersionMetadata({
|
|||
};
|
||||
}
|
||||
|
||||
function checkVersionMetadataPaths({
|
||||
async function checkVersionMetadataPaths({
|
||||
versionMetadata,
|
||||
context,
|
||||
}: {
|
||||
|
@ -429,7 +429,7 @@ function checkVersionMetadataPaths({
|
|||
const {siteDir} = context;
|
||||
const isCurrentVersion = versionName === CURRENT_VERSION_NAME;
|
||||
|
||||
if (!fs.existsSync(contentPath)) {
|
||||
if (!(await fs.pathExists(contentPath))) {
|
||||
throw new Error(
|
||||
`The docs folder does not exist for version "${versionName}". A docs folder is expected to be found at ${path.relative(
|
||||
siteDir,
|
||||
|
@ -446,7 +446,7 @@ function checkVersionMetadataPaths({
|
|||
if (
|
||||
isCurrentVersion &&
|
||||
typeof sidebarFilePath === 'string' &&
|
||||
!fs.existsSync(sidebarFilePath)
|
||||
!(await fs.pathExists(sidebarFilePath))
|
||||
) {
|
||||
throw new Error(`The path to the sidebar file does not exist at "${path.relative(
|
||||
siteDir,
|
||||
|
@ -585,8 +585,10 @@ export async function readVersionsMetadata({
|
|||
options,
|
||||
}),
|
||||
);
|
||||
versionsMetadata.forEach((versionMetadata) =>
|
||||
checkVersionMetadataPaths({versionMetadata, context}),
|
||||
await Promise.all(
|
||||
versionsMetadata.map((versionMetadata) =>
|
||||
checkVersionMetadataPaths({versionMetadata, context}),
|
||||
),
|
||||
);
|
||||
return versionsMetadata;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}`;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
// @ts-check
|
||||
|
||||
import logger from '@docusaurus/logger';
|
||||
import fs from 'fs';
|
||||
import fs from 'fs-extra';
|
||||
import cli from 'commander';
|
||||
import {createRequire} from 'module';
|
||||
import {
|
||||
|
@ -25,9 +25,9 @@ import {
|
|||
} from '../lib/index.js';
|
||||
import beforeCli from './beforeCli.mjs';
|
||||
|
||||
beforeCli();
|
||||
await beforeCli();
|
||||
|
||||
const resolveDir = (dir = '.') => fs.realpathSync(dir);
|
||||
const resolveDir = (dir = '.') => fs.realpath(dir);
|
||||
|
||||
cli
|
||||
.version(createRequire(import.meta.url)('../package.json').version)
|
||||
|
@ -56,8 +56,8 @@ cli
|
|||
'--no-minify',
|
||||
'build website without minimizing JS bundles (default: false)',
|
||||
)
|
||||
.action((siteDir, {bundleAnalyzer, config, outDir, locale, minify}) => {
|
||||
build(resolveDir(siteDir), {
|
||||
.action(async (siteDir, {bundleAnalyzer, config, outDir, locale, minify}) => {
|
||||
build(await resolveDir(siteDir), {
|
||||
bundleAnalyzer,
|
||||
outDir,
|
||||
config,
|
||||
|
@ -74,8 +74,14 @@ cli
|
|||
'copy TypeScript theme files when possible (default: false)',
|
||||
)
|
||||
.option('--danger', 'enable swizzle for internal component of themes')
|
||||
.action((themeName, componentName, siteDir, {typescript, danger}) => {
|
||||
swizzle(resolveDir(siteDir), themeName, componentName, typescript, danger);
|
||||
.action(async (themeName, componentName, siteDir, {typescript, danger}) => {
|
||||
swizzle(
|
||||
await resolveDir(siteDir),
|
||||
themeName,
|
||||
componentName,
|
||||
typescript,
|
||||
danger,
|
||||
);
|
||||
});
|
||||
|
||||
cli
|
||||
|
@ -97,8 +103,8 @@ cli
|
|||
'--skip-build',
|
||||
'skip building website before deploy it (default: false)',
|
||||
)
|
||||
.action((siteDir, {outDir, skipBuild, config}) => {
|
||||
deploy(resolveDir(siteDir), {
|
||||
.action(async (siteDir, {outDir, skipBuild, config}) => {
|
||||
deploy(await resolveDir(siteDir), {
|
||||
outDir,
|
||||
config,
|
||||
skipBuild,
|
||||
|
@ -124,17 +130,19 @@ cli
|
|||
'--poll [interval]',
|
||||
'use polling rather than watching for reload (default: false). Can specify a poll interval in milliseconds',
|
||||
)
|
||||
.action((siteDir, {port, host, locale, config, hotOnly, open, poll}) => {
|
||||
start(resolveDir(siteDir), {
|
||||
port,
|
||||
host,
|
||||
locale,
|
||||
config,
|
||||
hotOnly,
|
||||
open,
|
||||
poll,
|
||||
});
|
||||
});
|
||||
.action(
|
||||
async (siteDir, {port, host, locale, config, hotOnly, open, poll}) => {
|
||||
start(await resolveDir(siteDir), {
|
||||
port,
|
||||
host,
|
||||
locale,
|
||||
config,
|
||||
hotOnly,
|
||||
open,
|
||||
poll,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
cli
|
||||
.command('serve [siteDir]')
|
||||
|
@ -151,7 +159,7 @@ cli
|
|||
.option('--build', 'build website before serving (default: false)')
|
||||
.option('-h, --host <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);
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
serverConfig = merge(serverConfig, {
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: staticDirectories
|
||||
.map((dir) => path.resolve(siteDir, dir))
|
||||
.filter(fs.existsSync)
|
||||
.map((dir) => ({from: dir, to: outDir})),
|
||||
}),
|
||||
],
|
||||
});
|
||||
if (staticDirectories.length > 0) {
|
||||
await Promise.all(staticDirectories.map((dir) => fs.ensureDir(dir)));
|
||||
|
||||
serverConfig = merge(serverConfig, {
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: staticDirectories
|
||||
.map((dir) => path.resolve(siteDir, dir))
|
||||
.map((dir) => ({from: dir, to: outDir})),
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Plugin Lifecycle - configureWebpack and configurePostCss.
|
||||
plugins.forEach((plugin) => {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.`);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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({});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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),
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue