diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index 021201e495ee..b1f3a424342e 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -131,19 +131,25 @@ export type HostPortCLIOptions = { port?: string; }; -export type StartCLIOptions = HostPortCLIOptions & { - hotOnly: boolean; - open: boolean; - poll: boolean | number; - locale?: string; +export type ConfigOptions = { + config: string; }; -export type ServeCLIOptions = HostPortCLIOptions & { - build: boolean; - dir: string; -}; +export type StartCLIOptions = HostPortCLIOptions & + ConfigOptions & { + hotOnly: boolean; + open: boolean; + poll: boolean | number; + locale?: string; + }; + +export type ServeCLIOptions = HostPortCLIOptions & + ConfigOptions & { + dir: string; + build: boolean; + }; -export type BuildOptions = { +export type BuildOptions = ConfigOptions & { bundleAnalyzer: boolean; outDir: string; minify: boolean; @@ -158,6 +164,7 @@ export interface LoadContext { siteDir: string; generatedFilesDir: string; siteConfig: DocusaurusConfig; + siteConfigPath: string; outDir: string; baseUrl: string; i18n: I18n; diff --git a/packages/docusaurus/bin/docusaurus.js b/packages/docusaurus/bin/docusaurus.js index ff0969cb35a9..d77327de6e86 100755 --- a/packages/docusaurus/bin/docusaurus.js +++ b/packages/docusaurus/bin/docusaurus.js @@ -109,6 +109,10 @@ cli '--out-dir ', 'The full path for the new output directory, relative to the current workspace (default: build).', ) + .option( + '--config ', + 'Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js`', + ) .option( '-l, --locale ', 'Build the site in a specified locale. Build all known locales otherwise.', @@ -117,10 +121,11 @@ cli '--no-minify', 'Build website without minimizing JS bundles (default: false)', ) - .action((siteDir = '.', {bundleAnalyzer, outDir, locale, minify}) => { + .action((siteDir = '.', {bundleAnalyzer, config, outDir, locale, minify}) => { wrapCommand(build)(path.resolve(siteDir), { bundleAnalyzer, outDir, + config, locale, minify, }); @@ -155,12 +160,20 @@ cli '--out-dir ', 'The full path for the new output directory, relative to the current workspace (default: build).', ) + .option( + '--config ', + 'Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js`', + ) .option( '--skip-build', 'Skip building website before deploy it (default: false)', ) - .action((siteDir = '.', {outDir, skipBuild}) => { - wrapCommand(deploy)(path.resolve(siteDir), {outDir, skipBuild}); + .action((siteDir = '.', {outDir, skipBuild, config}) => { + wrapCommand(deploy)(path.resolve(siteDir), { + outDir, + config, + skipBuild, + }); }); cli @@ -173,21 +186,28 @@ cli '--hot-only', 'Do not fallback to page refresh if hot reload fails (default: false)', ) + .option( + '--config ', + 'Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js`', + ) .option('--no-open', 'Do not open page in the browser (default: false)') .option( '--poll [interval]', 'Use polling rather than watching for reload (default: false). Can specify a poll interval in milliseconds.', ) - .action((siteDir = '.', {port, host, locale, hotOnly, open, poll}) => { - wrapCommand(start)(path.resolve(siteDir), { - port, - host, - locale, - hotOnly, - open, - poll, - }); - }); + .action( + (siteDir = '.', {port, host, locale, config, hotOnly, open, poll}) => { + wrapCommand(start)(path.resolve(siteDir), { + port, + host, + locale, + config, + hotOnly, + open, + poll, + }); + }, + ); cli .command('serve [siteDir]') @@ -196,6 +216,10 @@ cli '--dir ', 'The full path for the new output directory, relative to the current workspace (default: build).', ) + .option( + '--config ', + 'Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js`', + ) .option('-p, --port ', 'use specified port (default: 3000)') .option('--build', 'Build website before serving (default: false)') .option('-h, --host ', 'use specified host (default: localhost') @@ -207,12 +231,14 @@ cli port = 3000, host = 'localhost', build: buildSite = false, + config, }, ) => { wrapCommand(serve)(path.resolve(siteDir), { dir, port, build: buildSite, + config, host, }); }, @@ -236,6 +262,10 @@ cli '--override', 'By default, we only append missing translation messages to existing translation files. This option allows to override existing translation messages. Make sure to commit or backup your existing translations, as they may be overridden.', ) + .option( + '--config ', + 'Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js`', + ) .option( '--messagePrefix ', '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.', @@ -243,11 +273,12 @@ cli .action( ( siteDir = '.', - {locale = undefined, override = false, messagePrefix = ''}, + {locale = undefined, override = false, messagePrefix = '', config}, ) => { wrapCommand(writeTranslations)(path.resolve(siteDir), { locale, override, + config, messagePrefix, }); }, diff --git a/packages/docusaurus/src/commands/build.ts b/packages/docusaurus/src/commands/build.ts index 0ec5f13cfa2d..1e28ab4d995f 100644 --- a/packages/docusaurus/src/commands/build.ts +++ b/packages/docusaurus/src/commands/build.ts @@ -14,7 +14,7 @@ import {Configuration, Plugin} from 'webpack'; import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'; import merge from 'webpack-merge'; import {STATIC_DIR_NAME} from '../constants'; -import {load} from '../server'; +import {load, loadContext} from '../server'; import {handleBrokenLinks} from '../server/brokenLinks'; import {BuildCLIOptions, Props} from '@docusaurus/types'; @@ -28,7 +28,6 @@ import { import CleanWebpackPlugin from '../webpack/plugins/CleanWebpackPlugin'; import {loadI18n} from '../server/i18n'; import {mapAsyncSequencial} from '@docusaurus/utils'; -import loadConfig from '../server/config'; export default async function build( siteDir: string, @@ -59,8 +58,13 @@ export default async function build( throw e; } } - - const i18n = await loadI18n(loadConfig(siteDir), { + const context = await loadContext(siteDir, { + customOutDir: cliOptions.outDir, + customConfigFilePath: cliOptions.config, + locale: cliOptions.locale, + localizePath: cliOptions.locale ? false : undefined, + }); + const i18n = await loadI18n(context.siteConfig, { locale: cliOptions.locale, }); if (cliOptions.locale) { @@ -112,6 +116,7 @@ async function buildLocale({ const props: Props = await load(siteDir, { customOutDir: cliOptions.outDir, + customConfigFilePath: cliOptions.config, locale, localizePath: cliOptions.locale ? false : undefined, }); diff --git a/packages/docusaurus/src/commands/clear.ts b/packages/docusaurus/src/commands/clear.ts index c221e86f9ea7..84634f3031fe 100644 --- a/packages/docusaurus/src/commands/clear.ts +++ b/packages/docusaurus/src/commands/clear.ts @@ -7,7 +7,7 @@ import fs from 'fs-extra'; import path from 'path'; import chalk = require('chalk'); -import {BUILD_DIR_NAME, GENERATED_FILES_DIR_NAME} from '../constants'; +import {DEFAULT_BUILD_DIR_NAME, GENERATED_FILES_DIR_NAME} from '../constants'; function removePath(fsPath: string) { return fs @@ -24,7 +24,7 @@ function removePath(fsPath: string) { export default async function clear(siteDir: string): Promise { return Promise.all([ removePath(path.join(siteDir, GENERATED_FILES_DIR_NAME)), - removePath(path.join(siteDir, BUILD_DIR_NAME)), + removePath(path.join(siteDir, DEFAULT_BUILD_DIR_NAME)), removePath(path.join(siteDir, 'node_modules/.cache/cache-loader')), ]); } diff --git a/packages/docusaurus/src/commands/deploy.ts b/packages/docusaurus/src/commands/deploy.ts index cd2451a195b3..748f268cd959 100644 --- a/packages/docusaurus/src/commands/deploy.ts +++ b/packages/docusaurus/src/commands/deploy.ts @@ -6,14 +6,13 @@ */ import fs from 'fs-extra'; -import path from 'path'; import shell from 'shelljs'; import chalk from 'chalk'; -import {CONFIG_FILE_NAME, GENERATED_FILES_DIR_NAME} from '../constants'; import {loadContext} from '../server'; -import loadConfig from '../server/config'; import build from './build'; import {BuildCLIOptions} from '@docusaurus/types'; +import path from 'path'; +import os from 'os'; // GIT_PASS env variable should not appear in logs function obfuscateGitPass(str) { @@ -42,10 +41,10 @@ export default async function deploy( siteDir: string, cliOptions: Partial = {}, ): Promise { - const {outDir} = await loadContext(siteDir, { + const {outDir, siteConfig, siteConfigPath} = await loadContext(siteDir, { + customConfigFilePath: cliOptions.config, customOutDir: cliOptions.outDir, }); - const tempDir = path.join(siteDir, GENERATED_FILES_DIR_NAME); console.log('Deploy command invoked ...'); if (!shell.which('git')) { @@ -62,14 +61,13 @@ export default async function deploy( process.env.CURRENT_BRANCH || shell.exec('git rev-parse --abbrev-ref HEAD').stdout.trim(); - const siteConfig = loadConfig(siteDir); const organizationName = process.env.ORGANIZATION_NAME || process.env.CIRCLE_PROJECT_USERNAME || siteConfig.organizationName; if (!organizationName) { throw new Error( - `Missing project organization name. Did you forget to define 'organizationName' in ${CONFIG_FILE_NAME}? You may also export it via the ORGANIZATION_NAME environment variable.`, + `Missing project organization name. Did you forget to define 'organizationName' in ${siteConfigPath}? You may also export it via the ORGANIZATION_NAME environment variable.`, ); } console.log(`${chalk.cyan('organizationName:')} ${organizationName}`); @@ -80,7 +78,7 @@ export default async function deploy( siteConfig.projectName; if (!projectName) { throw new Error( - `Missing project name. Did you forget to define 'projectName' in ${CONFIG_FILE_NAME}? You may also export it via the PROJECT_NAME environment variable.`, + `Missing project name. Did you forget to define 'projectName' in ${siteConfigPath}? You may also export it via the PROJECT_NAME environment variable.`, ); } console.log(`${chalk.cyan('projectName:')} ${projectName}`); @@ -141,22 +139,16 @@ export default async function deploy( // out to deployment branch. const currentCommit = shellExecLog('git rev-parse HEAD').stdout.trim(); - const runDeploy = (outputDirectory) => { - if (shell.cd(tempDir).code !== 0) { - throw new Error( - `Temp dir ${GENERATED_FILES_DIR_NAME} does not exists. Run build website first.`, - ); - } - - if ( - shellExecLog( - `git clone ${remoteBranch} ${projectName}-${deploymentBranch}`, - ).code !== 0 - ) { - throw new Error('Error: git clone failed'); + const runDeploy = async (outputDirectory) => { + const fromPath = outputDirectory; + const toPath = await fs.mkdtemp( + path.join(os.tmpdir(), `${projectName}-${deploymentBranch}`), + ); + if (shellExecLog(`git clone ${remoteBranch} ${toPath}`).code !== 0) { + throw new Error(`Error: git clone failed in ${toPath}`); } - shell.cd(`${projectName}-${deploymentBranch}`); + shell.cd(toPath); // If the default branch is the one we're deploying to, then we'll fail // to create it. This is the case of a cross-repo publish, where we clone @@ -183,63 +175,50 @@ export default async function deploy( } shellExecLog('git rm -rf .'); + try { + await fs.copy(fromPath, toPath); + } catch (error) { + throw new Error( + `Error: Copying build assets from "${fromPath}" to "${toPath}" failed with error '${error}'`, + ); + } + shell.cd(toPath); + shellExecLog('git add --all'); - shell.cd('../..'); - - const fromPath = outputDirectory; - const toPath = path.join( - GENERATED_FILES_DIR_NAME, - `${projectName}-${deploymentBranch}`, - ); - - fs.copy(fromPath, toPath, (error) => { - if (error) { - throw new Error( - `Error: Copying build assets failed with error '${error}'`, - ); - } - - shell.cd(toPath); - shellExecLog('git add --all'); - - const commitMessage = - process.env.CUSTOM_COMMIT_MESSAGE || - `Deploy website - based on ${currentCommit}`; - const commitResults = shellExecLog(`git commit -m "${commitMessage}"`); - if ( - shellExecLog(`git push --force origin ${deploymentBranch}`).code !== 0 - ) { - throw new Error('Error: Git push failed'); - } else if (commitResults.code === 0) { - // The commit might return a non-zero value when site is up to date. - let websiteURL = ''; - if (githubHost === 'github.com') { - websiteURL = projectName.includes('.github.io') - ? `https://${organizationName}.github.io/` - : `https://${organizationName}.github.io/${projectName}/`; - } else { - // GitHub enterprise hosting. - websiteURL = `https://${githubHost}/pages/${organizationName}/${projectName}/`; - } - shell.echo(`Website is live at ${websiteURL}`); - shell.exit(0); + const commitMessage = + process.env.CUSTOM_COMMIT_MESSAGE || + `Deploy website - based on ${currentCommit}`; + const commitResults = shellExecLog(`git commit -m "${commitMessage}"`); + if ( + shellExecLog(`git push --force origin ${deploymentBranch}`).code !== 0 + ) { + throw new Error('Error: Git push failed'); + } else if (commitResults.code === 0) { + // The commit might return a non-zero value when site is up to date. + let websiteURL = ''; + if (githubHost === 'github.com') { + websiteURL = projectName.includes('.github.io') + ? `https://${organizationName}.github.io/` + : `https://${organizationName}.github.io/${projectName}/`; + } else { + // GitHub enterprise hosting. + websiteURL = `https://${githubHost}/pages/${organizationName}/${projectName}/`; } - }); + shell.echo(`Website is live at ${websiteURL}`); + shell.exit(0); + } }; if (!cliOptions.skipBuild) { - // Clear Docusaurus 2 cache dir for deploy consistency. - fs.removeSync(tempDir); - // Build static html files, then push to deploymentBranch branch of specified repo. - build(siteDir, cliOptions, false) - .then(runDeploy) - .catch((buildError) => { - console.error(buildError); - process.exit(1); - }); + try { + await runDeploy(await build(siteDir, cliOptions, false)); + } catch (buildError) { + console.error(buildError); + process.exit(1); + } } else { // Push current build to deploymentBranch branch of specified repo. - runDeploy(outDir); + await runDeploy(outDir); } } diff --git a/packages/docusaurus/src/commands/serve.ts b/packages/docusaurus/src/commands/serve.ts index 8b0952d2628c..09184389e530 100644 --- a/packages/docusaurus/src/commands/serve.ts +++ b/packages/docusaurus/src/commands/serve.ts @@ -19,11 +19,14 @@ export default async function serve( siteDir: string, cliOptions: ServeCLIOptions, ): Promise { - let dir = path.join(siteDir, cliOptions.dir); + let dir = path.isAbsolute(cliOptions.dir) + ? cliOptions.dir + : path.join(siteDir, cliOptions.dir); if (cliOptions.build) { dir = await build( siteDir, { + config: cliOptions.config, outDir: dir, }, false, diff --git a/packages/docusaurus/src/commands/start.ts b/packages/docusaurus/src/commands/start.ts index 40a023e0cc57..b50ba7a346ee 100644 --- a/packages/docusaurus/src/commands/start.ts +++ b/packages/docusaurus/src/commands/start.ts @@ -22,7 +22,7 @@ import merge from 'webpack-merge'; import HotModuleReplacementPlugin from 'webpack/lib/HotModuleReplacementPlugin'; import {load} from '../server'; import {StartCLIOptions} from '@docusaurus/types'; -import {CONFIG_FILE_NAME, STATIC_DIR_NAME} from '../constants'; +import {STATIC_DIR_NAME} from '../constants'; import createClientConfig from '../webpack/client'; import { applyConfigureWebpack, @@ -42,6 +42,7 @@ export default async function start( function loadSite() { return load(siteDir, { + customConfigFilePath: cliOptions.config, locale: cliOptions.locale, localizePath: undefined, // should this be configurable? }); @@ -97,7 +98,7 @@ export default async function start( const pathsToWatch: string[] = [ ...pluginPaths, - CONFIG_FILE_NAME, + props.siteConfigPath, getTranslationsLocaleDirPath({ siteDir, locale: props.i18n.currentLocale, diff --git a/packages/docusaurus/src/commands/writeTranslations.ts b/packages/docusaurus/src/commands/writeTranslations.ts index 749eaeb0bd09..ee26e7fff6e8 100644 --- a/packages/docusaurus/src/commands/writeTranslations.ts +++ b/packages/docusaurus/src/commands/writeTranslations.ts @@ -4,6 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +import {ConfigOptions} from '@docusaurus/types'; import {loadContext, loadPluginConfigs} from '../server'; import initPlugins, {InitPlugin} from '../server/plugins/init'; @@ -47,9 +48,12 @@ async function writePluginTranslationFiles({ export default async function writeTranslations( siteDir: string, - options: WriteTranslationsOptions & {locale?: string}, + options: WriteTranslationsOptions & ConfigOptions & {locale?: string}, ): Promise { - const context = await loadContext(siteDir, {locale: options.locale}); + const context = await loadContext(siteDir, { + customConfigFilePath: options.config, + locale: options.locale, + }); const pluginConfigs = loadPluginConfigs(context); const plugins = initPlugins({ pluginConfigs, diff --git a/packages/docusaurus/src/constants.ts b/packages/docusaurus/src/constants.ts index 765dbae40eac..924afaa67090 100644 --- a/packages/docusaurus/src/constants.ts +++ b/packages/docusaurus/src/constants.ts @@ -5,10 +5,18 @@ * LICENSE file in the root directory of this source tree. */ -export const BABEL_CONFIG_FILE_NAME = 'babel.config.js'; -export const BUILD_DIR_NAME = 'build'; -export const CONFIG_FILE_NAME = 'docusaurus.config.js'; -export const GENERATED_FILES_DIR_NAME = '.docusaurus'; +// Can be overridden with cli option --out-dir +export const DEFAULT_BUILD_DIR_NAME = 'build'; + +// Can be overridden with cli option --config +export const DEFAULT_CONFIG_FILE_NAME = 'docusaurus.config.js'; + +export const BABEL_CONFIG_FILE_NAME = + process.env.DOCUSAURUS_BABEL_CONFIG_FILE_NAME || 'babel.config.js'; + +export const GENERATED_FILES_DIR_NAME = + process.env.DOCUSAURUS_GENERATED_FILES_DIR_NAME || '.docusaurus'; + export const SRC_DIR_NAME = 'src'; export const STATIC_DIR_NAME = 'static'; export const OUTPUT_STATIC_ASSETS_DIR_NAME = 'assets'; // files handled by webpack, hashed (can be cached aggressively) diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap index d0d71639874e..9600828797d6 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap @@ -6,8 +6,6 @@ exports[`loadConfig website with incomplete siteConfig 1`] = ` " `; -exports[`loadConfig website with no siteConfig 1`] = `"docusaurus.config.js not found at packages/docusaurus/src/server/__tests__/__fixtures__/nonExisting/docusaurus.config.js"`; - exports[`loadConfig website with useless field (wrong field) in siteConfig 1`] = ` "\\"favicon\\" is required These field(s) [\\"useLessField\\",] are not recognized in docusaurus.config.js. diff --git a/packages/docusaurus/src/server/__tests__/config.test.ts b/packages/docusaurus/src/server/__tests__/config.test.ts index ede66200d0c8..7c2f0ff276b0 100644 --- a/packages/docusaurus/src/server/__tests__/config.test.ts +++ b/packages/docusaurus/src/server/__tests__/config.test.ts @@ -10,31 +10,52 @@ import loadConfig from '../config'; describe('loadConfig', () => { test('website with valid siteConfig', async () => { - const fixtures = path.join(__dirname, '__fixtures__'); - const siteDir = path.join(fixtures, 'simple-site'); + const siteDir = path.join( + __dirname, + '__fixtures__', + 'simple-site', + 'docusaurus.config.js', + ); const config = loadConfig(siteDir); expect(config).toMatchSnapshot(); expect(config).not.toEqual({}); }); test('website with incomplete siteConfig', () => { - const siteDir = path.join(__dirname, '__fixtures__', 'bad-site'); + const siteDir = path.join( + __dirname, + '__fixtures__', + 'bad-site', + 'docusaurus.config.js', + ); expect(() => { loadConfig(siteDir); }).toThrowErrorMatchingSnapshot(); }); test('website with useless field (wrong field) in siteConfig', () => { - const siteDir = path.join(__dirname, '__fixtures__', 'wrong-site'); + const siteDir = path.join( + __dirname, + '__fixtures__', + 'wrong-site', + 'docusaurus.config.js', + ); expect(() => { loadConfig(siteDir); }).toThrowErrorMatchingSnapshot(); }); test('website with no siteConfig', () => { - const siteDir = path.join(__dirname, '__fixtures__', 'nonExisting'); + const siteDir = path.join( + __dirname, + '__fixtures__', + 'nonExisting', + 'docusaurus.config.js', + ); expect(() => { loadConfig(siteDir); - }).toThrowErrorMatchingSnapshot(); + }).toThrowError( + /Config file "(.*?)__fixtures__[/\\]nonExisting[/\\]docusaurus.config.js" not found$/, + ); }); }); diff --git a/packages/docusaurus/src/server/config.ts b/packages/docusaurus/src/server/config.ts index 78ee8ebf302f..2f08307c889a 100644 --- a/packages/docusaurus/src/server/config.ts +++ b/packages/docusaurus/src/server/config.ts @@ -7,25 +7,12 @@ import fs from 'fs-extra'; import importFresh from 'import-fresh'; -import path from 'path'; import {DocusaurusConfig} from '@docusaurus/types'; -import {CONFIG_FILE_NAME} from '../constants'; import {validateConfig} from './configValidation'; -import {toMessageRelativeFilePath} from '@docusaurus/utils'; - -export default function loadConfig(siteDir: string): DocusaurusConfig { - // TODO temporary undocumented env variable: we should be able to use a cli option instead! - const loadedConfigFileName = - process.env.DOCUSAURUS_CONFIG || CONFIG_FILE_NAME; - - const configPath = path.resolve(siteDir, loadedConfigFileName); +export default function loadConfig(configPath: string): DocusaurusConfig { if (!fs.existsSync(configPath)) { - throw new Error( - `${CONFIG_FILE_NAME} not found at ${toMessageRelativeFilePath( - configPath, - )}`, - ); + throw new Error(`Config file "${configPath}" not found`); } const loadedConfig = importFresh(configPath) as Partial; diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index 47bd3c83f5da..137d7eaaaf5f 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -6,7 +6,7 @@ */ import {DocusaurusConfig, I18nConfig} from '@docusaurus/types'; -import {CONFIG_FILE_NAME} from '../constants'; +import {DEFAULT_CONFIG_FILE_NAME} from '../constants'; import Joi from 'joi'; import { logValidationBugReportHint, @@ -164,7 +164,7 @@ export function validateConfig( '', ); formattedError = unknownFields - ? `${formattedError}These field(s) [${unknownFields}] are not recognized in ${CONFIG_FILE_NAME}.\nIf you still want these fields to be in your configuration, put them in the 'customFields' attribute.\nSee https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields` + ? `${formattedError}These field(s) [${unknownFields}] are not recognized in ${DEFAULT_CONFIG_FILE_NAME}.\nIf you still want these fields to be in your configuration, put them in the 'customFields' attribute.\nSee https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields` : formattedError; throw new Error(formattedError); } else { diff --git a/packages/docusaurus/src/server/index.ts b/packages/docusaurus/src/server/index.ts index 9a5d90b56b86..57ec879a1cdc 100644 --- a/packages/docusaurus/src/server/index.ts +++ b/packages/docusaurus/src/server/index.ts @@ -10,8 +10,8 @@ import path, {join} from 'path'; import chalk from 'chalk'; import ssrDefaultTemplate from '../client/templates/ssr.html.template'; import { - BUILD_DIR_NAME, - CONFIG_FILE_NAME, + DEFAULT_BUILD_DIR_NAME, + DEFAULT_CONFIG_FILE_NAME, GENERATED_FILES_DIR_NAME, THEME_PATH, } from '../constants'; @@ -40,6 +40,7 @@ import {mapValues} from 'lodash'; type LoadContextOptions = { customOutDir?: string; + customConfigFilePath?: string; locale?: string; localizePath?: boolean; // undefined = only non-default locales paths are localized }; @@ -48,17 +49,23 @@ export async function loadContext( siteDir: string, options: LoadContextOptions = {}, ): Promise { - const {customOutDir, locale} = options; - const generatedFilesDir: string = path.resolve( - siteDir, - GENERATED_FILES_DIR_NAME, - ); - const initialSiteConfig: DocusaurusConfig = loadConfig(siteDir); + const {customOutDir, locale, customConfigFilePath} = options; + const generatedFilesDir = path.isAbsolute(GENERATED_FILES_DIR_NAME) + ? GENERATED_FILES_DIR_NAME + : path.resolve(siteDir, GENERATED_FILES_DIR_NAME); + + const siteConfigPathUnresolved = + customConfigFilePath ?? DEFAULT_CONFIG_FILE_NAME; + const siteConfigPath = path.isAbsolute(siteConfigPathUnresolved) + ? siteConfigPathUnresolved + : path.resolve(siteDir, siteConfigPathUnresolved); + + const initialSiteConfig: DocusaurusConfig = loadConfig(siteConfigPath); const {ssrTemplate} = initialSiteConfig; const baseOutDir = customOutDir ? path.resolve(customOutDir) - : path.resolve(siteDir, BUILD_DIR_NAME); + : path.resolve(siteDir, DEFAULT_BUILD_DIR_NAME); const i18n = await loadI18n(initialSiteConfig, {locale}); @@ -93,6 +100,7 @@ export async function loadContext( siteDir, generatedFilesDir, siteConfig, + siteConfigPath, outDir, baseUrl, i18n, @@ -122,6 +130,7 @@ export async function load( const { generatedFilesDir, siteConfig, + siteConfigPath, outDir, baseUrl, i18n, @@ -149,7 +158,7 @@ export async function load( // We want the generated config to have been normalized by the plugins! const genSiteConfig = generate( generatedFilesDir, - CONFIG_FILE_NAME, + DEFAULT_CONFIG_FILE_NAME, `export default ${JSON.stringify(siteConfig, null, 2)};`, ); @@ -315,6 +324,7 @@ ${Object.keys(registry) const props: Props = { siteConfig, + siteConfigPath, siteDir, outDir, baseUrl, diff --git a/packages/docusaurus/src/server/plugins/init.ts b/packages/docusaurus/src/server/plugins/init.ts index 08966af5389c..7fe92bbfb091 100644 --- a/packages/docusaurus/src/server/plugins/init.ts +++ b/packages/docusaurus/src/server/plugins/init.ts @@ -6,7 +6,6 @@ */ import Module from 'module'; -import {join} from 'path'; import importFresh from 'import-fresh'; import { DocusaurusPluginVersionInformation, @@ -15,7 +14,7 @@ import { PluginConfig, PluginOptions, } from '@docusaurus/types'; -import {CONFIG_FILE_NAME, DEFAULT_PLUGIN_ID} from '../../constants'; +import {DEFAULT_PLUGIN_ID} from '../../constants'; import {getPluginVersion} from '../versions'; import {ensureUniquePluginInstanceIds} from './pluginIds'; import { @@ -40,7 +39,7 @@ export default function initPlugins({ // We need to fallback to createRequireFromPath since createRequire is only available in node v12. // See: https://nodejs.org/api/modules.html#modules_module_createrequire_filename const createRequire = Module.createRequire || Module.createRequireFromPath; - const pluginRequire = createRequire(join(context.siteDir, CONFIG_FILE_NAME)); + const pluginRequire = createRequire(context.siteConfigPath); const plugins: InitPlugin[] = pluginConfigs .map((pluginItem) => { diff --git a/packages/docusaurus/src/server/presets/__tests__/index.test.ts b/packages/docusaurus/src/server/presets/__tests__/index.test.ts index 05c12bcde5ca..a7cdd682e976 100644 --- a/packages/docusaurus/src/server/presets/__tests__/index.test.ts +++ b/packages/docusaurus/src/server/presets/__tests__/index.test.ts @@ -13,7 +13,7 @@ import {LoadContext} from '@docusaurus/types'; describe('loadPresets', () => { test('no presets', () => { const context = { - siteDir: __dirname, + siteConfigPath: __dirname, siteConfig: {}, } as LoadContext; const presets = loadPresets(context); @@ -27,7 +27,7 @@ describe('loadPresets', () => { test('string form', () => { const context = { - siteDir: __dirname, + siteConfigPath: __dirname, siteConfig: { presets: [path.join(__dirname, '__fixtures__/preset-bar.js')], }, @@ -52,7 +52,7 @@ describe('loadPresets', () => { test('string form composite', () => { const context = { - siteDir: __dirname, + siteConfigPath: __dirname, siteConfig: { presets: [ path.join(__dirname, '__fixtures__/preset-bar.js'), @@ -88,7 +88,7 @@ describe('loadPresets', () => { test('array form', () => { const context = { - siteDir: __dirname, + siteConfigPath: __dirname, siteConfig: { presets: [[path.join(__dirname, '__fixtures__/preset-bar.js')]], }, @@ -113,7 +113,7 @@ describe('loadPresets', () => { test('array form with options', () => { const context = { - siteDir: __dirname, + siteConfigPath: __dirname, siteConfig: { presets: [ [ @@ -145,7 +145,7 @@ describe('loadPresets', () => { test('array form composite', () => { const context = { - siteDir: __dirname, + siteConfigPath: __dirname, siteConfig: { presets: [ [ @@ -191,7 +191,7 @@ describe('loadPresets', () => { test('mixed form', () => { const context = { - siteDir: __dirname, + siteConfigPath: __dirname, siteConfig: { presets: [ [ @@ -232,7 +232,7 @@ describe('loadPresets', () => { test('mixed form with themes', () => { const context = { - siteDir: __dirname, + siteConfigPath: __dirname, siteConfig: { presets: [ [ diff --git a/packages/docusaurus/src/server/presets/index.ts b/packages/docusaurus/src/server/presets/index.ts index d1e8797e79e8..f970f6880d65 100644 --- a/packages/docusaurus/src/server/presets/index.ts +++ b/packages/docusaurus/src/server/presets/index.ts @@ -6,9 +6,7 @@ */ import Module from 'module'; -import {join} from 'path'; import importFresh from 'import-fresh'; -import {CONFIG_FILE_NAME} from '../../constants'; import { LoadContext, PluginConfig, @@ -27,7 +25,7 @@ export default function loadPresets( // We need to fallback to createRequireFromPath since createRequire is only available in node v12. // See: https://nodejs.org/api/modules.html#modules_module_createrequire_filename const createRequire = Module.createRequire || Module.createRequireFromPath; - const pluginRequire = createRequire(join(context.siteDir, CONFIG_FILE_NAME)); + const pluginRequire = createRequire(context.siteConfigPath); const presets: PresetConfig[] = (context.siteConfig || {}).presets || []; const unflatPlugins: PluginConfig[][] = []; diff --git a/website/docs/cli.md b/website/docs/cli.md index 8d136b212d3f..1821b53aa069 100644 --- a/website/docs/cli.md +++ b/website/docs/cli.md @@ -30,7 +30,7 @@ import TOCInline from "@theme/TOCInline" Below is a list of Docusaurus CLI commands and their usages: -### `docusaurus start` +### `docusaurus start [siteDir]` Builds and serves a preview of your site locally with [Webpack Dev Server](https://webpack.js.org/configuration/dev-server). @@ -42,6 +42,7 @@ Builds and serves a preview of your site locally with [Webpack Dev Server](https | `--host` | `localhost` | Specify a host to use. For example, if you want your server to be accessible externally, you can use `--host 0.0.0.0`. | | `--hot-only` | `false` | Enables Hot Module Replacement without page refresh as fallback in case of build failures. More information [here](https://webpack.js.org/configuration/dev-server/#devserverhotonly). | | `--no-open` | `false` | Do not open automatically the page in the browser. | +| `--config` | `undefined` | Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js` | | `--poll [optionalIntervalMs]` | `false` | Use polling of files rather than watching for live reload as a fallback in environments where watching doesn't work. More information [here](https://webpack.js.org/configuration/watch/#watchoptionspoll). | :::important @@ -66,7 +67,7 @@ HTTPS=true SSL_CRT_FILE=localhost.pem SSL_KEY_FILE=localhost-key.pem yarn start 4. Open `https://localhost:3000/` -### `docusaurus build` +### `docusaurus build [siteDir]` Compiles your site for production. @@ -76,6 +77,7 @@ Compiles your site for production. | --- | --- | --- | | `--bundle-analyzer` | `false` | Analyze your bundle with the [webpack bundle analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer). | | `--out-dir` | `build` | The full path for the new output directory, relative to the current workspace. | +| `--config` | `undefined` | Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js` | | `--no-minify` | `false` | Build website without minimizing JS/CSS bundles. | :::info @@ -84,7 +86,7 @@ For advanced minification of CSS bundle, we use the [advanced cssnano preset](ht ::: -### `docusaurus swizzle` +### `docusaurus swizzle [siteDir]` :::caution @@ -133,7 +135,7 @@ TODO a separate section for swizzle tutorial. To learn more about swizzling, check [here](#). --> -### `docusaurus deploy` +### `docusaurus deploy [siteDir]` Deploys your site with [GitHub Pages](https://pages.github.com/). Check out the docs on [deployment](deployment.mdx#deploying-to-github-pages) for more details. @@ -143,8 +145,9 @@ Deploys your site with [GitHub Pages](https://pages.github.com/). Check out the | --- | --- | --- | | `--out-dir` | `build` | The full path for the new output directory, relative to the current workspace. | | `--skip-build` | `false` | Deploy website without building it. This may be useful when using custom deploy script. | +| `--config` | `undefined` | Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js` | -### `docusaurus serve` +### `docusaurus serve [siteDir]` Serve your built website locally. @@ -153,15 +156,16 @@ Serve your built website locally. | `--port` | `3000` | Use specified port | | `--dir` | `build` | The full path for the output directory, relative to the current workspace | | `--build` | `false` | Build website before serving | +| `--config` | `undefined` | Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js` | | `--host` | `localhost` | Specify a host to use. For example, if you want your server to be accessible externally, you can use `--host 0.0.0.0`. | -### `docusaurus clear` +### `docusaurus clear [siteDir]` Clear a Docusaurus site's generated assets, caches, build artifacts. We recommend running this command before reporting bugs, after upgrading versions, or anytime you have issues with your Docusaurus site. -### `docusaurus write-translations` +### `docusaurus write-translations [siteDir]` Write the JSON translation files that you will have to translate. @@ -171,4 +175,5 @@ By default, the files are written in `website/i18n//...`. | --- | --- | --- | | `--locale` | `` | Define which locale folder you want to write translations the JSON files in | | `--override` | `false` | Override existing translation messages | +| `--config` | `undefined` | Path to docusaurus config file, default to `[siteDir]/docusaurus.config.js` | | `--messagePrefix` | `''` | Allows to add a prefix to each translation message, to help you highlight untranslated strings | diff --git a/website/package.json b/website/package.json index 049aaf5fd722..b7a883af3984 100644 --- a/website/package.json +++ b/website/package.json @@ -15,8 +15,8 @@ "build:baseUrl": "cross-env BASE_URL='/build/' yarn build", "start:bootstrap": "cross-env DOCUSAURUS_PRESET=bootstrap yarn start", "build:bootstrap": "cross-env DOCUSAURUS_PRESET=bootstrap yarn build", - "start:blogOnly": "cross-env DOCUSAURUS_CONFIG='docusaurus.config-blog-only.js' yarn start", - "build:blogOnly": "cross-env DOCUSAURUS_CONFIG='docusaurus.config-blog-only.js' yarn build", + "start:blogOnly": "cross-env yarn start --config=docusaurus.config-blog-only.js", + "build:blogOnly": "cross-env yarn build --config=docusaurus.config-blog-only.js", "netlify:build:production": "yarn docusaurus write-translations && yarn netlify:crowdin:uploadSources && yarn netlify:crowdin:downloadTranslations && yarn build", "netlify:build:deployPreview": "yarn docusaurus write-translations --locale fr --messagePrefix '(fr) ' && yarn netlify:build:deployPreview:v1:all && yarn netlify:build:deployPreview:classic && yarn netlify:build:deployPreview:bootstrap && yarn netlify:build:deployPreview:blogOnly", "netlify:build:deployPreview:classic": "cross-env BASE_URL='/classic/' yarn build --out-dir netlifyDeployPreview/classic",