create-docusaurus 2.0.0-beta.18 → 2.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/index.js CHANGED
@@ -8,13 +8,15 @@
8
8
 
9
9
  // @ts-check
10
10
 
11
+ import path from 'path';
12
+ import {createRequire} from 'module';
11
13
  import logger from '@docusaurus/logger';
12
14
  import semver from 'semver';
13
- import path from 'path';
14
15
  import {program} from 'commander';
15
- import {createRequire} from 'module';
16
16
 
17
- const packageJson = createRequire(import.meta.url)('../package.json');
17
+ const packageJson = /** @type {import("../package.json")} */ (
18
+ createRequire(import.meta.url)('../package.json')
19
+ );
18
20
  const requiredVersion = packageJson.engines.node;
19
21
 
20
22
  if (!semver.satisfies(process.version, requiredVersion)) {
@@ -45,24 +47,12 @@ program
45
47
  \`custom\`: enter your custom git clone command. We will prompt you for it.`,
46
48
  )
47
49
  .description('Initialize website.')
48
- .action(
49
- (
50
- siteName,
51
- template,
52
- rootDir = '.',
53
- {packageManager, skipInstall, typescript, gitStrategy} = {},
54
- ) => {
55
- // See https://github.com/facebook/docusaurus/pull/6860
56
- import('../lib/index.js').then(({default: init}) => {
57
- init(path.resolve(rootDir), siteName, template, {
58
- packageManager,
59
- skipInstall,
60
- typescript,
61
- gitStrategy,
62
- });
63
- });
64
- },
65
- );
50
+ .action((siteName, template, rootDir, options) => {
51
+ // See https://github.com/facebook/docusaurus/pull/6860
52
+ import('../lib/index.js').then(({default: init}) => {
53
+ init(path.resolve(rootDir ?? '.'), siteName, template, options);
54
+ });
55
+ });
66
56
 
67
57
  program.parse(process.argv);
68
58
 
package/lib/index.d.ts CHANGED
@@ -4,17 +4,19 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- declare const SupportedPackageManagers: {
7
+ declare type CLIOptions = {
8
+ packageManager?: PackageManager;
9
+ skipInstall?: boolean;
10
+ typescript?: boolean;
11
+ gitStrategy?: GitStrategy;
12
+ };
13
+ declare const lockfileNames: {
8
14
  npm: string;
9
15
  yarn: string;
10
16
  pnpm: string;
11
17
  };
12
- declare type SupportedPackageManager = keyof typeof SupportedPackageManagers;
18
+ declare type PackageManager = keyof typeof lockfileNames;
13
19
  declare const gitStrategies: readonly ["deep", "shallow", "copy", "custom"];
14
- export default function init(rootDir: string, siteName?: string, reqTemplate?: string, cliOptions?: Partial<{
15
- packageManager: SupportedPackageManager;
16
- skipInstall: boolean;
17
- typescript: boolean;
18
- gitStrategy: typeof gitStrategies[number];
19
- }>): Promise<void>;
20
+ declare type GitStrategy = typeof gitStrategies[number];
21
+ export default function init(rootDir: string, reqName?: string, reqTemplate?: string, cliOptions?: CLIOptions): Promise<void>;
20
22
  export {};
package/lib/index.js CHANGED
@@ -4,28 +4,27 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import logger from '@docusaurus/logger';
8
7
  import fs from 'fs-extra';
9
- import prompts from 'prompts';
8
+ import { fileURLToPath } from 'url';
10
9
  import path from 'path';
11
- import shell from 'shelljs';
12
10
  import _ from 'lodash';
11
+ import logger from '@docusaurus/logger';
12
+ import shell from 'shelljs';
13
+ import prompts from 'prompts';
13
14
  import supportsColor from 'supports-color';
14
- import { fileURLToPath } from 'url';
15
- const RecommendedTemplate = 'classic';
16
- const TypeScriptTemplateSuffix = '-typescript';
15
+ import { escapeShellArg } from '@docusaurus/utils';
17
16
  // Only used in the rare, rare case of running globally installed create +
18
17
  // using --skip-install. We need a default name to show the tip text
19
- const DefaultPackageManager = 'npm';
20
- const SupportedPackageManagers = {
18
+ const defaultPackageManager = 'npm';
19
+ const lockfileNames = {
21
20
  npm: 'package-lock.json',
22
21
  yarn: 'yarn.lock',
23
22
  pnpm: 'pnpm-lock.yaml',
24
23
  };
25
- const PackageManagersList = Object.keys(SupportedPackageManagers);
26
- async function findPackageManagerFromLockFile() {
27
- for (const packageManager of PackageManagersList) {
28
- const lockFilePath = path.resolve(SupportedPackageManagers[packageManager]);
24
+ const packageManagers = Object.keys(lockfileNames);
25
+ async function findPackageManagerFromLockFile(rootDir) {
26
+ for (const packageManager of packageManagers) {
27
+ const lockFilePath = path.join(rootDir, lockfileNames[packageManager]);
29
28
  if (await fs.pathExists(lockFilePath)) {
30
29
  return packageManager;
31
30
  }
@@ -33,88 +32,101 @@ async function findPackageManagerFromLockFile() {
33
32
  return undefined;
34
33
  }
35
34
  function findPackageManagerFromUserAgent() {
36
- return PackageManagersList.find((packageManager) => process.env.npm_config_user_agent?.startsWith(packageManager));
35
+ return packageManagers.find((packageManager) => process.env.npm_config_user_agent?.startsWith(packageManager));
37
36
  }
38
37
  async function askForPackageManagerChoice() {
39
38
  const hasYarn = shell.exec('yarn --version', { silent: true }).code === 0;
40
- const hasPNPM = shell.exec('pnpm --version', { silent: true }).code === 0;
41
- if (!hasYarn && !hasPNPM) {
39
+ const hasPnpm = shell.exec('pnpm --version', { silent: true }).code === 0;
40
+ if (!hasYarn && !hasPnpm) {
42
41
  return 'npm';
43
42
  }
44
- const choices = ['npm', hasYarn && 'yarn', hasPNPM && 'pnpm']
43
+ const choices = ['npm', hasYarn && 'yarn', hasPnpm && 'pnpm']
45
44
  .filter((p) => Boolean(p))
46
45
  .map((p) => ({ title: p, value: p }));
47
- return (await prompts({
46
+ return ((await prompts({
48
47
  type: 'select',
49
48
  name: 'packageManager',
50
49
  message: 'Select a package manager...',
51
50
  choices,
52
- })).packageManager;
51
+ }, {
52
+ onCancel() {
53
+ logger.info `Falling back to name=${defaultPackageManager}`;
54
+ },
55
+ })).packageManager ?? defaultPackageManager);
53
56
  }
54
- async function getPackageManager(packageManagerChoice, skipInstall = false) {
55
- if (packageManagerChoice &&
56
- !PackageManagersList.includes(packageManagerChoice)) {
57
- throw new Error(`Invalid package manager choice ${packageManagerChoice}. Must be one of ${PackageManagersList.join(', ')}`);
57
+ async function getPackageManager(dest, { packageManager, skipInstall }) {
58
+ if (packageManager && !packageManagers.includes(packageManager)) {
59
+ throw new Error(`Invalid package manager choice ${packageManager}. Must be one of ${packageManagers.join(', ')}`);
58
60
  }
59
- return (packageManagerChoice ??
60
- (await findPackageManagerFromLockFile()) ??
61
+ return (
62
+ // If dest already contains a lockfile (e.g. if using a local template), we
63
+ // always use that instead
64
+ (await findPackageManagerFromLockFile(dest)) ??
65
+ packageManager ??
66
+ (await findPackageManagerFromLockFile('.')) ??
61
67
  findPackageManagerFromUserAgent() ??
62
68
  // This only happens if the user has a global installation in PATH
63
- (skipInstall ? DefaultPackageManager : askForPackageManagerChoice()));
64
- }
65
- function isValidGitRepoUrl(gitRepoUrl) {
66
- return ['https://', 'git@'].some((item) => gitRepoUrl.startsWith(item));
69
+ (skipInstall ? defaultPackageManager : askForPackageManagerChoice()));
67
70
  }
68
- async function updatePkg(pkgPath, obj) {
69
- const content = await fs.readFile(pkgPath, 'utf-8');
70
- const pkg = JSON.parse(content);
71
- const newPkg = Object.assign(pkg, obj);
72
- await fs.outputFile(pkgPath, `${JSON.stringify(newPkg, null, 2)}\n`);
73
- }
74
- async function readTemplates(templatesDir) {
75
- const templates = (await fs.readdir(templatesDir)).filter((d) => !d.startsWith('.') &&
71
+ const recommendedTemplate = 'classic';
72
+ const typeScriptTemplateSuffix = '-typescript';
73
+ const templatesDir = fileURLToPath(new URL('../templates', import.meta.url));
74
+ async function readTemplates() {
75
+ const dirContents = await fs.readdir(templatesDir);
76
+ const templates = await Promise.all(dirContents
77
+ .filter((d) => !d.startsWith('.') &&
76
78
  !d.startsWith('README') &&
77
- !d.endsWith(TypeScriptTemplateSuffix) &&
78
- d !== 'shared');
79
+ !d.endsWith(typeScriptTemplateSuffix) &&
80
+ d !== 'shared')
81
+ .map(async (name) => {
82
+ const tsVariantPath = path.join(templatesDir, `${name}${typeScriptTemplateSuffix}`);
83
+ return {
84
+ name,
85
+ path: path.join(templatesDir, name),
86
+ tsVariantPath: (await fs.pathExists(tsVariantPath))
87
+ ? tsVariantPath
88
+ : undefined,
89
+ };
90
+ }));
79
91
  // Classic should be first in list!
80
- return _.sortBy(templates, (t) => t !== RecommendedTemplate);
92
+ return _.sortBy(templates, (t) => t.name !== recommendedTemplate);
81
93
  }
82
- function createTemplateChoices(templates) {
83
- function makeNameAndValueChoice(value) {
84
- const title = value === RecommendedTemplate ? `${value} (recommended)` : value;
85
- return { title, value };
86
- }
87
- return [
88
- ...templates.map((template) => makeNameAndValueChoice(template)),
89
- makeNameAndValueChoice('Git repository'),
90
- makeNameAndValueChoice('Local template'),
91
- ];
92
- }
93
- function getTypeScriptBaseTemplate(template) {
94
- if (template.endsWith(TypeScriptTemplateSuffix)) {
95
- return template.replace(TypeScriptTemplateSuffix, '');
96
- }
97
- return undefined;
98
- }
99
- async function copyTemplate(templatesDir, template, dest) {
94
+ async function copyTemplate(template, dest, typescript) {
100
95
  await fs.copy(path.join(templatesDir, 'shared'), dest);
101
96
  // TypeScript variants will copy duplicate resources like CSS & config from
102
97
  // base template
103
- const tsBaseTemplate = getTypeScriptBaseTemplate(template);
104
- if (tsBaseTemplate) {
105
- const tsBaseTemplatePath = path.resolve(templatesDir, tsBaseTemplate);
106
- await fs.copy(tsBaseTemplatePath, dest, {
98
+ if (typescript) {
99
+ await fs.copy(template.path, dest, {
107
100
  filter: async (filePath) => (await fs.stat(filePath)).isDirectory() ||
108
101
  path.extname(filePath) === '.css' ||
109
102
  path.basename(filePath) === 'docusaurus.config.js',
110
103
  });
111
104
  }
112
- await fs.copy(path.resolve(templatesDir, template), dest, {
113
- // Symlinks don't exist in published NPM packages anymore, so this is only
105
+ await fs.copy(typescript ? template.tsVariantPath : template.path, dest, {
106
+ // Symlinks don't exist in published npm packages anymore, so this is only
114
107
  // to prevent errors during local testing
115
108
  filter: async (filePath) => !(await fs.lstat(filePath)).isSymbolicLink(),
116
109
  });
117
110
  }
111
+ function createTemplateChoices(templates) {
112
+ function makeNameAndValueChoice(value) {
113
+ if (typeof value === 'string') {
114
+ return { title: value, value };
115
+ }
116
+ const title = value.name === recommendedTemplate
117
+ ? `${value.name} (recommended)`
118
+ : value.name;
119
+ return { title, value };
120
+ }
121
+ return [
122
+ ...templates.map((template) => makeNameAndValueChoice(template)),
123
+ makeNameAndValueChoice('Git repository'),
124
+ makeNameAndValueChoice('Local template'),
125
+ ];
126
+ }
127
+ function isValidGitRepoUrl(gitRepoUrl) {
128
+ return ['https://', 'git@'].some((item) => gitRepoUrl.startsWith(item));
129
+ }
118
130
  const gitStrategies = ['deep', 'shallow', 'copy', 'custom'];
119
131
  async function getGitCommand(gitStrategy) {
120
132
  switch (gitStrategy) {
@@ -122,67 +134,104 @@ async function getGitCommand(gitStrategy) {
122
134
  case 'copy':
123
135
  return 'git clone --recursive --depth 1';
124
136
  case 'custom': {
125
- const { command } = await prompts({
137
+ const { command } = (await prompts({
126
138
  type: 'text',
127
139
  name: 'command',
128
140
  message: 'Write your own git clone command. The repository URL and destination directory will be supplied. E.g. "git clone --depth 10"',
129
- });
130
- return command;
141
+ }, {
142
+ onCancel() {
143
+ logger.info `Falling back to code=${'git clone'}`;
144
+ },
145
+ }));
146
+ return command ?? 'git clone';
131
147
  }
132
148
  case 'deep':
133
149
  default:
134
150
  return 'git clone';
135
151
  }
136
152
  }
137
- export default async function init(rootDir, siteName, reqTemplate, cliOptions = {}) {
138
- const templatesDir = fileURLToPath(new URL('../templates', import.meta.url));
139
- const templates = await readTemplates(templatesDir);
140
- const hasTS = (templateName) => fs.pathExists(path.join(templatesDir, `${templateName}${TypeScriptTemplateSuffix}`));
141
- let name = siteName;
142
- // Prompt if siteName is not passed from CLI.
143
- if (!name) {
144
- const prompt = await prompts({
145
- type: 'text',
146
- name: 'name',
147
- message: 'What should we name this site?',
148
- initial: 'website',
149
- });
150
- name = prompt.name;
153
+ async function getSiteName(reqName, rootDir) {
154
+ async function validateSiteName(siteName) {
155
+ if (!siteName) {
156
+ return 'A website name is required.';
157
+ }
158
+ const dest = path.resolve(rootDir, siteName);
159
+ if (await fs.pathExists(dest)) {
160
+ return logger.interpolate `Directory already exists at path=${dest}!`;
161
+ }
162
+ return true;
151
163
  }
152
- if (!name) {
153
- logger.error('A website name is required.');
154
- process.exit(1);
164
+ if (reqName) {
165
+ const res = validateSiteName(reqName);
166
+ if (typeof res === 'string') {
167
+ throw new Error(res);
168
+ }
169
+ return reqName;
155
170
  }
156
- const dest = path.resolve(rootDir, name);
157
- if (await fs.pathExists(dest)) {
158
- logger.error `Directory already exists at path=${dest}!`;
159
- process.exit(1);
171
+ const { siteName } = (await prompts({
172
+ type: 'text',
173
+ name: 'siteName',
174
+ message: 'What should we name this site?',
175
+ initial: 'website',
176
+ validate: validateSiteName,
177
+ }, {
178
+ onCancel() {
179
+ logger.error('A website name is required.');
180
+ process.exit(1);
181
+ },
182
+ }));
183
+ return siteName;
184
+ }
185
+ async function getSource(reqTemplate, templates, cliOptions) {
186
+ if (reqTemplate) {
187
+ if (isValidGitRepoUrl(reqTemplate)) {
188
+ if (cliOptions.gitStrategy &&
189
+ !gitStrategies.includes(cliOptions.gitStrategy)) {
190
+ logger.error `Invalid git strategy: name=${cliOptions.gitStrategy}. Value must be one of ${gitStrategies.join(', ')}.`;
191
+ process.exit(1);
192
+ }
193
+ return {
194
+ type: 'git',
195
+ url: reqTemplate,
196
+ strategy: cliOptions.gitStrategy ?? 'deep',
197
+ };
198
+ }
199
+ else if (await fs.pathExists(path.resolve(reqTemplate))) {
200
+ return {
201
+ type: 'local',
202
+ path: path.resolve(reqTemplate),
203
+ };
204
+ }
205
+ const template = templates.find((t) => t.name === reqTemplate);
206
+ if (!template) {
207
+ logger.error('Invalid template.');
208
+ process.exit(1);
209
+ }
210
+ if (cliOptions.typescript && !template.tsVariantPath) {
211
+ logger.error `Template name=${reqTemplate} doesn't provide the TypeScript variant.`;
212
+ process.exit(1);
213
+ }
214
+ return {
215
+ type: 'template',
216
+ template,
217
+ typescript: cliOptions.typescript ?? false,
218
+ };
160
219
  }
161
- let template = reqTemplate;
162
- let useTS = cliOptions.typescript;
163
- // Prompt if template is not provided from CLI.
164
- if (!template) {
165
- const templatePrompt = await prompts({
220
+ const template = cliOptions.gitStrategy
221
+ ? 'Git repository'
222
+ : (await prompts({
166
223
  type: 'select',
167
224
  name: 'template',
168
225
  message: 'Select a template below...',
169
226
  choices: createTemplateChoices(templates),
170
- });
171
- template = templatePrompt.template;
172
- if (template && !useTS && (await hasTS(template))) {
173
- const tsPrompt = await prompts({
174
- type: 'confirm',
175
- name: 'useTS',
176
- message: 'This template is available in TypeScript. Do you want to use the TS variant?',
177
- initial: false,
178
- });
179
- useTS = tsPrompt.useTS;
180
- }
181
- }
182
- let gitStrategy = cliOptions.gitStrategy ?? 'deep';
183
- // If user choose Git repository, we'll prompt for the url.
227
+ }, {
228
+ onCancel() {
229
+ logger.error('A choice is required.');
230
+ process.exit(1);
231
+ },
232
+ })).template;
184
233
  if (template === 'Git repository') {
185
- const repoPrompt = await prompts({
234
+ const { gitRepoUrl } = (await prompts({
186
235
  type: 'text',
187
236
  name: 'gitRepoUrl',
188
237
  validate: (url) => {
@@ -192,26 +241,45 @@ export default async function init(rootDir, siteName, reqTemplate, cliOptions =
192
241
  return logger.red('Invalid repository URL');
193
242
  },
194
243
  message: logger.interpolate `Enter a repository URL from GitHub, Bitbucket, GitLab, or any other public repo.
195
- (e.g: path=${'https://github.com/ownerName/repoName.git'})`,
196
- });
197
- ({ gitStrategy } = await prompts({
198
- type: 'select',
199
- name: 'gitStrategy',
200
- message: 'How should we clone this repo?',
201
- choices: [
202
- { title: 'Deep clone: preserve full history', value: 'deep' },
203
- { title: 'Shallow clone: clone with --depth=1', value: 'shallow' },
204
- {
205
- title: 'Copy: do a shallow clone, but do not create a git repo',
206
- value: 'copy',
207
- },
208
- { title: 'Custom: enter your custom git clone command', value: 'custom' },
209
- ],
244
+ (e.g: url=${'https://github.com/ownerName/repoName.git'})`,
245
+ }, {
246
+ onCancel() {
247
+ logger.error('A git repo URL is required.');
248
+ process.exit(1);
249
+ },
210
250
  }));
211
- template = repoPrompt.gitRepoUrl;
251
+ let strategy = cliOptions.gitStrategy;
252
+ if (!strategy) {
253
+ ({ strategy } = (await prompts({
254
+ type: 'select',
255
+ name: 'strategy',
256
+ message: 'How should we clone this repo?',
257
+ choices: [
258
+ { title: 'Deep clone: preserve full history', value: 'deep' },
259
+ { title: 'Shallow clone: clone with --depth=1', value: 'shallow' },
260
+ {
261
+ title: 'Copy: do a shallow clone, but do not create a git repo',
262
+ value: 'copy',
263
+ },
264
+ {
265
+ title: 'Custom: enter your custom git clone command',
266
+ value: 'custom',
267
+ },
268
+ ],
269
+ }, {
270
+ onCancel() {
271
+ logger.info `Falling back to name=${'deep'}`;
272
+ },
273
+ })));
274
+ }
275
+ return {
276
+ type: 'git',
277
+ url: gitRepoUrl,
278
+ strategy: strategy ?? 'deep',
279
+ };
212
280
  }
213
281
  else if (template === 'Local template') {
214
- const dirPrompt = await prompts({
282
+ const { templateDir } = (await prompts({
215
283
  type: 'text',
216
284
  name: 'templateDir',
217
285
  validate: async (dir) => {
@@ -225,64 +293,76 @@ export default async function init(rootDir, siteName, reqTemplate, cliOptions =
225
293
  return logger.red('Please enter a valid path.');
226
294
  },
227
295
  message: 'Enter a local folder path, relative to the current working directory.',
228
- });
229
- template = dirPrompt.templateDir;
296
+ }, {
297
+ onCancel() {
298
+ logger.error('A file path is required.');
299
+ process.exit(1);
300
+ },
301
+ }));
302
+ return {
303
+ type: 'local',
304
+ path: templateDir,
305
+ };
230
306
  }
231
- if (!template) {
232
- logger.error('Template should not be empty');
233
- process.exit(1);
307
+ let useTS = cliOptions.typescript;
308
+ if (!useTS && template.tsVariantPath) {
309
+ ({ useTS } = (await prompts({
310
+ type: 'confirm',
311
+ name: 'useTS',
312
+ message: 'This template is available in TypeScript. Do you want to use the TS variant?',
313
+ initial: false,
314
+ })));
234
315
  }
316
+ return {
317
+ type: 'template',
318
+ template,
319
+ typescript: useTS ?? false,
320
+ };
321
+ }
322
+ async function updatePkg(pkgPath, obj) {
323
+ const pkg = (await fs.readJSON(pkgPath));
324
+ const newPkg = Object.assign(pkg, obj);
325
+ await fs.outputFile(pkgPath, `${JSON.stringify(newPkg, null, 2)}\n`);
326
+ }
327
+ export default async function init(rootDir, reqName, reqTemplate, cliOptions = {}) {
328
+ const templates = await readTemplates();
329
+ const siteName = await getSiteName(reqName, rootDir);
330
+ const dest = path.resolve(rootDir, siteName);
331
+ const source = await getSource(reqTemplate, templates, cliOptions);
235
332
  logger.info('Creating new Docusaurus project...');
236
- if (isValidGitRepoUrl(template)) {
237
- logger.info `Cloning Git template path=${template}...`;
238
- if (!gitStrategies.includes(gitStrategy)) {
239
- logger.error `Invalid git strategy: name=${gitStrategy}. Value must be one of ${gitStrategies.join(', ')}.`;
333
+ if (source.type === 'git') {
334
+ const gitCommand = await getGitCommand(source.strategy);
335
+ const gitCloneCommand = `${gitCommand} ${escapeShellArg(source.url)} ${escapeShellArg(dest)}`;
336
+ if (shell.exec(gitCloneCommand).code !== 0) {
337
+ logger.error `Cloning Git template failed!`;
240
338
  process.exit(1);
241
339
  }
242
- const command = await getGitCommand(gitStrategy);
243
- if (shell.exec(`${command} ${template} ${dest}`).code !== 0) {
244
- logger.error `Cloning Git template name=${template} failed!`;
245
- process.exit(1);
246
- }
247
- if (gitStrategy === 'copy') {
340
+ if (source.strategy === 'copy') {
248
341
  await fs.remove(path.join(dest, '.git'));
249
342
  }
250
343
  }
251
- else if (templates.includes(template)) {
252
- // Docusaurus templates.
253
- if (useTS) {
254
- if (!(await hasTS(template))) {
255
- logger.error `Template name=${template} doesn't provide the Typescript variant.`;
256
- process.exit(1);
257
- }
258
- template = `${template}${TypeScriptTemplateSuffix}`;
259
- }
344
+ else if (source.type === 'template') {
260
345
  try {
261
- await copyTemplate(templatesDir, template, dest);
346
+ await copyTemplate(source.template, dest, source.typescript);
262
347
  }
263
348
  catch (err) {
264
- logger.error `Copying Docusaurus template name=${template} failed!`;
349
+ logger.error `Copying Docusaurus template name=${source.template.name} failed!`;
265
350
  throw err;
266
351
  }
267
352
  }
268
- else if (await fs.pathExists(path.resolve(template))) {
269
- const templateDir = path.resolve(template);
353
+ else {
270
354
  try {
271
- await fs.copy(templateDir, dest);
355
+ await fs.copy(source.path, dest);
272
356
  }
273
357
  catch (err) {
274
- logger.error `Copying local template path=${templateDir} failed!`;
358
+ logger.error `Copying local template path=${source.path} failed!`;
275
359
  throw err;
276
360
  }
277
361
  }
278
- else {
279
- logger.error('Invalid template.');
280
- process.exit(1);
281
- }
282
362
  // Update package.json info.
283
363
  try {
284
364
  await updatePkg(path.join(dest, 'package.json'), {
285
- name: _.kebabCase(name),
365
+ name: _.kebabCase(siteName),
286
366
  version: '0.0.0',
287
367
  private: true,
288
368
  });
@@ -301,15 +381,15 @@ export default async function init(rootDir, siteName, reqTemplate, cliOptions =
301
381
  }
302
382
  // Display the most elegant way to cd.
303
383
  const cdpath = path.relative('.', dest);
304
- const pkgManager = await getPackageManager(cliOptions.packageManager, cliOptions.skipInstall);
384
+ const pkgManager = await getPackageManager(dest, cliOptions);
305
385
  if (!cliOptions.skipInstall) {
306
386
  shell.cd(dest);
307
387
  logger.info `Installing dependencies with name=${pkgManager}...`;
308
388
  if (shell.exec(pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install --color always`, {
309
389
  env: {
310
390
  ...process.env,
311
- // Force coloring the output, since the command is invoked,
312
- // by shelljs which is not the interactive shell
391
+ // Force coloring the output, since the command is invoked by
392
+ // shelljs, which is not an interactive shell
313
393
  ...(supportsColor.stdout ? { FORCE_COLOR: '1' } : {}),
314
394
  },
315
395
  }).code !== 0) {
@@ -322,7 +402,7 @@ export default async function init(rootDir, siteName, reqTemplate, cliOptions =
322
402
  }
323
403
  }
324
404
  const useNpm = pkgManager === 'npm';
325
- logger.success `Created path=${cdpath}.`;
405
+ logger.success `Created name=${cdpath}.`;
326
406
  logger.info `Inside that directory, you can run several commands:
327
407
 
328
408
  code=${`${pkgManager} start`}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-docusaurus",
3
- "version": "2.0.0-beta.18",
3
+ "version": "2.0.0-beta.21",
4
4
  "description": "Create Docusaurus apps easily.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -13,8 +13,8 @@
13
13
  },
14
14
  "scripts": {
15
15
  "create-docusaurus": "create-docusaurus",
16
- "build": "tsc -p tsconfig.build.json",
17
- "watch": "tsc -p tsconfig.build.json --watch"
16
+ "build": "tsc --build",
17
+ "watch": "tsc --build --watch"
18
18
  },
19
19
  "bin": "bin/index.js",
20
20
  "publishConfig": {
@@ -22,21 +22,22 @@
22
22
  },
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "@docusaurus/logger": "2.0.0-beta.18",
25
+ "@docusaurus/logger": "2.0.0-beta.21",
26
+ "@docusaurus/utils": "2.0.0-beta.21",
26
27
  "commander": "^5.1.0",
27
- "fs-extra": "^10.0.1",
28
+ "fs-extra": "^10.1.0",
28
29
  "lodash": "^4.17.21",
29
30
  "prompts": "^2.4.2",
30
- "semver": "^7.3.5",
31
+ "semver": "^7.3.7",
31
32
  "shelljs": "^0.8.5",
32
- "supports-color": "^9.2.1",
33
- "tslib": "^2.3.1"
33
+ "supports-color": "^9.2.2",
34
+ "tslib": "^2.4.0"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/supports-color": "^8.1.1"
37
38
  },
38
39
  "engines": {
39
- "node": ">=14"
40
+ "node": ">=16.14"
40
41
  },
41
- "gitHead": "1a945d06993d53376e61bed2c942799fe07dc336"
42
+ "gitHead": "69ac49fc6909517f13615ee40290c4bd00c39df4"
42
43
  }
@@ -13,9 +13,20 @@ const config = {
13
13
  onBrokenLinks: 'throw',
14
14
  onBrokenMarkdownLinks: 'warn',
15
15
  favicon: 'img/favicon.ico',
16
+
17
+ // GitHub pages deployment config.
18
+ // If you aren't using GitHub pages, you don't need these.
16
19
  organizationName: 'facebook', // Usually your GitHub org/user name.
17
20
  projectName: 'docusaurus', // Usually your repo name.
18
21
 
22
+ // Even if you don't use internalization, you can use this field to set useful
23
+ // metadata like html lang. For example, if your site is Chinese, you may want
24
+ // to replace "en" with "zh-Hans".
25
+ i18n: {
26
+ defaultLocale: 'en',
27
+ locales: ['en'],
28
+ },
29
+
19
30
  presets: [
20
31
  [
21
32
  'classic',
@@ -24,11 +35,14 @@ const config = {
24
35
  docs: {
25
36
  sidebarPath: require.resolve('./sidebars.js'),
26
37
  // Please change this to your repo.
27
- editUrl: 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
38
+ // Remove this to remove the "edit this page" links.
39
+ editUrl:
40
+ 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
28
41
  },
29
42
  blog: {
30
43
  showReadingTime: true,
31
44
  // Please change this to your repo.
45
+ // Remove this to remove the "edit this page" links.
32
46
  editUrl:
33
47
  'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
34
48
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docusaurus-2-classic-template",
3
- "version": "2.0.0-beta.18",
3
+ "version": "2.0.0-beta.21",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "docusaurus": "docusaurus",
@@ -14,14 +14,17 @@
14
14
  "write-heading-ids": "docusaurus write-heading-ids"
15
15
  },
16
16
  "dependencies": {
17
- "@docusaurus/core": "2.0.0-beta.18",
18
- "@docusaurus/preset-classic": "2.0.0-beta.18",
17
+ "@docusaurus/core": "2.0.0-beta.21",
18
+ "@docusaurus/preset-classic": "2.0.0-beta.21",
19
19
  "@mdx-js/react": "^1.6.22",
20
20
  "clsx": "^1.1.1",
21
- "prism-react-renderer": "^1.3.1",
21
+ "prism-react-renderer": "^1.3.3",
22
22
  "react": "^17.0.2",
23
23
  "react-dom": "^17.0.2"
24
24
  },
25
+ "devDependencies": {
26
+ "@docusaurus/module-type-aliases": "2.0.0-beta.21"
27
+ },
25
28
  "browserslist": {
26
29
  "production": [
27
30
  ">0.5%",
@@ -14,6 +14,7 @@
14
14
  --ifm-color-primary-lighter: #359962;
15
15
  --ifm-color-primary-lightest: #3cad6e;
16
16
  --ifm-code-font-size: 95%;
17
+ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
17
18
  }
18
19
 
19
20
  /* For readability concerns, you should choose a lighter palette in dark mode. */
@@ -25,15 +26,5 @@
25
26
  --ifm-color-primary-light: #29d5b0;
26
27
  --ifm-color-primary-lighter: #32d8b4;
27
28
  --ifm-color-primary-lightest: #4fddbf;
28
- }
29
-
30
- .docusaurus-highlight-code-line {
31
- background-color: rgba(0, 0, 0, 0.1);
32
- display: block;
33
- margin: 0 calc(-1 * var(--ifm-pre-padding));
34
- padding: 0 var(--ifm-pre-padding);
35
- }
36
-
37
- [data-theme='dark'] .docusaurus-highlight-code-line {
38
- background-color: rgba(0, 0, 0, 0.3);
29
+ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
39
30
  }
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
2
  import clsx from 'clsx';
3
- import Layout from '@theme/Layout';
4
3
  import Link from '@docusaurus/Link';
5
4
  import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
6
- import styles from './index.module.css';
5
+ import Layout from '@theme/Layout';
7
6
  import HomepageFeatures from '@site/src/components/HomepageFeatures';
8
7
 
8
+ import styles from './index.module.css';
9
+
9
10
  function HomepageHeader() {
10
11
  const {siteConfig} = useDocusaurusContext();
11
12
  return (
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docusaurus-2-classic-typescript-template",
3
- "version": "2.0.0-beta.18",
3
+ "version": "2.0.0-beta.21",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "docusaurus": "docusaurus",
@@ -15,18 +15,18 @@
15
15
  "typecheck": "tsc"
16
16
  },
17
17
  "dependencies": {
18
- "@docusaurus/core": "2.0.0-beta.18",
19
- "@docusaurus/preset-classic": "2.0.0-beta.18",
18
+ "@docusaurus/core": "2.0.0-beta.21",
19
+ "@docusaurus/preset-classic": "2.0.0-beta.21",
20
20
  "@mdx-js/react": "^1.6.22",
21
21
  "clsx": "^1.1.1",
22
- "prism-react-renderer": "^1.3.1",
22
+ "prism-react-renderer": "^1.3.3",
23
23
  "react": "^17.0.2",
24
24
  "react-dom": "^17.0.2"
25
25
  },
26
26
  "devDependencies": {
27
- "@docusaurus/module-type-aliases": "2.0.0-beta.18",
27
+ "@docusaurus/module-type-aliases": "2.0.0-beta.21",
28
28
  "@tsconfig/docusaurus": "^1.0.5",
29
- "typescript": "^4.6.3"
29
+ "typescript": "^4.6.4"
30
30
  },
31
31
  "browserslist": {
32
32
  "production": [
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
2
  import clsx from 'clsx';
3
- import Layout from '@theme/Layout';
4
3
  import Link from '@docusaurus/Link';
5
4
  import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
6
- import styles from './index.module.css';
5
+ import Layout from '@theme/Layout';
7
6
  import HomepageFeatures from '@site/src/components/HomepageFeatures';
8
7
 
8
+ import styles from './index.module.css';
9
+
9
10
  function HomepageHeader() {
10
11
  const {siteConfig} = useDocusaurusContext();
11
12
  return (
@@ -18,6 +18,9 @@ const config = {
18
18
  onBrokenLinks: 'throw',
19
19
  onBrokenMarkdownLinks: 'warn',
20
20
  favicon: 'img/favicon.ico',
21
+
22
+ // GitHub pages deployment config.
23
+ // If you aren't using GitHub pages, you don't need these.
21
24
  organizationName: 'facebook', // Usually your GitHub org/user name.
22
25
  projectName: 'docusaurus', // Usually your repo name.
23
26
 
@@ -29,12 +32,14 @@ const config = {
29
32
  docs: {
30
33
  sidebarPath: require.resolve('./sidebars.js'),
31
34
  // Please change this to your repo.
35
+ // Remove this to remove the "edit this page" links.
32
36
  editUrl:
33
37
  'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
34
38
  },
35
39
  blog: {
36
40
  showReadingTime: true,
37
41
  // Please change this to your repo.
42
+ // Remove this to remove the "edit this page" links.
38
43
  editUrl:
39
44
  'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
40
45
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docusaurus-2-facebook-template",
3
- "version": "2.0.0-beta.18",
3
+ "version": "2.0.0-beta.21",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "docusaurus": "docusaurus",
@@ -18,25 +18,25 @@
18
18
  "format:diff": "prettier --config .prettierrc --list-different \"**/*.{js,jsx,ts,tsx,md,mdx}\""
19
19
  },
20
20
  "dependencies": {
21
- "@docusaurus/core": "2.0.0-beta.18",
22
- "@docusaurus/preset-classic": "2.0.0-beta.18",
21
+ "@docusaurus/core": "2.0.0-beta.21",
22
+ "@docusaurus/preset-classic": "2.0.0-beta.21",
23
23
  "@mdx-js/react": "^1.6.22",
24
24
  "clsx": "^1.1.1",
25
25
  "react": "^17.0.2",
26
26
  "react-dom": "^17.0.2"
27
27
  },
28
28
  "devDependencies": {
29
- "@babel/eslint-parser": "^7.17.0",
30
- "eslint": "^8.11.0",
29
+ "@babel/eslint-parser": "^7.18.2",
30
+ "eslint": "^8.16.0",
31
31
  "eslint-config-airbnb": "^19.0.4",
32
32
  "eslint-config-prettier": "^8.5.0",
33
33
  "eslint-plugin-header": "^3.1.1",
34
- "eslint-plugin-import": "^2.25.4",
34
+ "eslint-plugin-import": "^2.26.0",
35
35
  "eslint-plugin-jsx-a11y": "^6.5.1",
36
- "eslint-plugin-react": "^7.29.4",
37
- "eslint-plugin-react-hooks": "^4.3.0",
38
- "prettier": "^2.6.0",
39
- "stylelint": "^14.6.0"
36
+ "eslint-plugin-react": "^7.30.0",
37
+ "eslint-plugin-react-hooks": "^4.5.0",
38
+ "prettier": "^2.6.2",
39
+ "stylelint": "^14.8.5"
40
40
  },
41
41
  "browserslist": {
42
42
  "production": [
@@ -35,10 +35,3 @@
35
35
  --ifm-color-primary-lighter: #32d8b4;
36
36
  --ifm-color-primary-lightest: #4fddbf;
37
37
  }
38
-
39
- .docusaurus-highlight-code-line {
40
- background-color: rgb(72, 77, 91);
41
- display: block;
42
- margin: 0 calc(-1 * var(--ifm-pre-padding));
43
- padding: 0 var(--ifm-pre-padding);
44
- }
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "label": "Tutorial - Basics",
3
- "position": 2
3
+ "position": 2,
4
+ "link": {
5
+ "type": "generated-index",
6
+ "description": "5 minutes to learn the most important Docusaurus concepts."
7
+ }
4
8
  }
@@ -43,7 +43,7 @@ Let's see how to [Create a page](./create-a-page.md).
43
43
 
44
44
  Regular Markdown images are supported.
45
45
 
46
- Add an image at `static/img/docusaurus.png` and display it in Markdown:
46
+ You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
47
47
 
48
48
  ```md
49
49
  ![Docusaurus logo](/img/docusaurus.png)
@@ -51,6 +51,8 @@ Add an image at `static/img/docusaurus.png` and display it in Markdown:
51
51
 
52
52
  ![Docusaurus logo](/img/docusaurus.png)
53
53
 
54
+ You can reference images relative to the current file as well, as shown in [the extra guides](../tutorial-extras/manage-docs-versions.md).
55
+
54
56
  ## Code Blocks
55
57
 
56
58
  Markdown code blocks are supported with Syntax highlighting.
@@ -1,4 +1,7 @@
1
1
  {
2
2
  "label": "Tutorial - Extras",
3
- "position": 3
3
+ "position": 3,
4
+ "link": {
5
+ "type": "generated-index"
6
+ }
4
7
  }
@@ -45,7 +45,7 @@ module.exports = {
45
45
 
46
46
  The docs version dropdown appears in your navbar:
47
47
 
48
- ![Docs Version Dropdown](/img/tutorial/docsVersionDropdown.png)
48
+ ![Docs Version Dropdown](./img/docsVersionDropdown.png)
49
49
 
50
50
  ## Update an existing version
51
51
 
@@ -71,7 +71,7 @@ module.exports = {
71
71
 
72
72
  The locale dropdown now appears in your navbar:
73
73
 
74
- ![Locale Dropdown](/img/tutorial/localeDropdown.png)
74
+ ![Locale Dropdown](./img/localeDropdown.png)
75
75
 
76
76
  ## Build your localized site
77
77