Merge branch 'main' into lex111/prism-styles-fouc

This commit is contained in:
Joshua Chen 2022-05-14 23:49:10 +08:00
commit 15fb0dacde
No known key found for this signature in database
GPG key ID: C37145B818BDB68F
350 changed files with 1693 additions and 1301 deletions

View file

@ -220,7 +220,35 @@ module.exports = {
],
},
],
'import/order': OFF,
'import/order': [
WARNING,
{
groups: [
'builtin',
'external',
'internal',
['parent', 'sibling', 'index'],
'type',
],
pathGroups: [
{pattern: '@jest/globals', group: 'builtin', position: 'before'},
{pattern: 'react', group: 'builtin', position: 'before'},
{pattern: 'fs-extra', group: 'builtin'},
{pattern: 'lodash', group: 'external', position: 'before'},
{pattern: 'clsx', group: 'external', position: 'before'},
// 'Bit weird to not use the `import/internal-regex` option, but this
// way, we can make `import type { Props } from "@theme/*"` appear
// before `import styles from "styles.module.css"`, which is what we
// always did. This should be removable once we stop using ambient
// module declarations for theme aliases.
{pattern: '@theme/**', group: 'internal'},
{pattern: '@site/**', group: 'internal'},
{pattern: '@theme-init/**', group: 'internal'},
{pattern: '@theme-original/**', group: 'internal'},
],
pathGroupsExcludedImportTypes: [],
},
],
'import/prefer-default-export': OFF,
'jest/consistent-test-it': WARNING,

View file

@ -6,4 +6,4 @@ updates:
interval: weekly
open-pull-requests-limit: 99
labels:
- 'tag: dependencies'
- 'pr: dependencies'

View file

@ -34,9 +34,9 @@ jobs:
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3
- name: Initialize CodeQL
uses: github/codeql-action/init@7502d6e991ca767d2db617bfd823a1ed925a0d59 # v2
uses: github/codeql-action/init@75b4f1c4669133dc294b06c2794e969efa2e5316 # v2
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@7502d6e991ca767d2db617bfd823a1ed925a0d59 # v2
uses: github/codeql-action/analyze@75b4f1c4669133dc294b06c2794e969efa2e5316 # v2

5
.husky/pre-commit vendored
View file

@ -1,4 +1,7 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint-staged --allow-empty
# Workaround of a mysterious bug in either lint-staged or husky.
# https://github.com/typicode/husky/issues/1134
# https://github.com/okonet/lint-staged/issues/693#issuecomment-1079759224
FORCE_COLOR=1 yarn lint-staged --allow-empty

View file

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import {Globby} from '@docusaurus/utils';
import fs from 'fs-extra';
import {Globby} from '@docusaurus/utils';
type PackageJsonFile = {
file: string;

View file

@ -5,9 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import type {Handler} from '@netlify/functions';
import {createPlaygroundResponse} from '../functionUtils/playgroundUtils';
import type {Handler} from '@netlify/functions';
export const handler: Handler = async function handler() {
return createPlaygroundResponse('codesandbox');

View file

@ -5,13 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import type {Handler} from '@netlify/functions';
import {
readPlaygroundName,
createPlaygroundResponse,
createPlaygroundDocumentationResponse,
} from '../functionUtils/playgroundUtils';
import type {Handler} from '@netlify/functions';
export const handler: Handler = async (event) => {
const playgroundName = readPlaygroundName(event);

View file

@ -5,9 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import type {Handler} from '@netlify/functions';
import {createPlaygroundResponse} from '../functionUtils/playgroundUtils';
import type {Handler} from '@netlify/functions';
export const handler: Handler = async function handler() {
return createPlaygroundResponse('stackblitz');

View file

@ -70,11 +70,22 @@ This local test step is optional because it will be run by the CI on your releas
### 3. Update the v2 changelog
The changelog uses GitHub labels to classify each pull request. Use the GitHub interface to assign each newly merged pull request to a GitHub label starting with `tag:`, otherwise the PR won't appear in the changelog.
The changelog uses GitHub labels to classify each pull request. Use the GitHub interface to assign each newly merged pull request to a GitHub label starting with `pr:`, otherwise the PR won't appear in the changelog.
[Check tags of all recently merged Pull-Requests](https://github.com/facebook/docusaurus/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+)
Not all labels will appear in the changelog—some are designed not to. However, you should **always** label each PR, so that before release, we can do a quick scan and confirm no PR is accidentally left out. Here's a search query (pity that GH doesn't have wildcard queries yet):
The `tag:` label prefix is for PRs only. Other labels are not used by the changelog tool, and it's not necessary to assign such labels to issues, only PRs.
```
is:pr is:merged sort:updated-desc -label:"pr: breaking change","pr: new feature","pr: bug fix","pr: performance","pr: polish","pr: documentation","pr: maintenance","pr: internal","pr: dependencies","pr: showcase"
```
[Check tags of all recently merged Pull-Requests](https://github.com/facebook/docusaurus/pulls?q=is%3Apr+is%3Amerged+sort%3Aupdated-desc+-label%3A%22pr%3A+breaking+change%22%2C%22pr%3A+new+feature%22%2C%22pr%3A+bug+fix%22%2C%22pr%3A+performance%22%2C%22pr%3A+polish%22%2C%22pr%3A+documentation%22%2C%22pr%3A+maintenance%22%2C%22pr%3A+internal%22%2C%22pr%3A+dependencies%22%2C%22pr%3A+showcase%22)
Some general principles about the labeling process:
- "Will an average user be interested in this entry?" Items like "improve test coverage", "upgrade dependencies" can probably be left out (we have `pr: internal` and `pr: dependencies` for this). However, "pin GitHub actions to an SHA", "add visual testing infrastructure", etc., albeit internal, could be interesting to the user, and can be included in the "maintenance" section.
- "Will this change have tangible impact on the user?" A common case is when a PR implements a feature X, then there are immediately follow-up PRs that fix bugs or polish feature X. These follow-up PRs don't necessarily have to be in the changelog, and even if they alter the API, they are not breaking changes, because to an average user bumping their version, they will only see the new feature X as a whole. Make the entries atomic.
The `pr:` label prefix is for PRs only. Other labels are not used by the changelog tool, and it's not necessary to assign such labels to issues, only PRs.
Generate a GitHub auth token by going to https://github.com/settings/tokens (the only permission needed is `public_repo`). Save the token somewhere for future reference.

View file

@ -5,12 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import sharp from 'sharp';
import fs from 'fs-extra';
import path from 'path';
import imageSize from 'image-size';
import {fileURLToPath} from 'url';
import logger from '@docusaurus/logger';
import sharp from 'sharp';
import imageSize from 'image-size';
const allImages = (
await fs.readdir(new URL('../../website/src/data/showcase', import.meta.url))

View file

@ -8,13 +8,13 @@
// Forked from https://github.com/tribou/jest-serializer-path/blob/master/lib/index.js
// Added some project-specific handlers
import _ from 'lodash';
import {escapePath} from '@docusaurus/utils';
import stripAnsi from 'strip-ansi';
import {version} from '@docusaurus/core/package.json';
import os from 'os';
import path from 'path';
import fs from 'fs';
import _ from 'lodash';
import {escapePath} from '@docusaurus/utils';
import {version} from '@docusaurus/core/package.json';
import stripAnsi from 'strip-ansi';
export function print(
val: unknown,

View file

@ -5,13 +5,13 @@
"changelog": {
"repo": "facebook/docusaurus",
"labels": {
"tag: new feature": ":rocket: New Feature",
"tag: breaking change": ":boom: Breaking Change",
"tag: bug fix": ":bug: Bug Fix",
"tag: polish": ":nail_care: Polish",
"tag: documentation": ":memo: Documentation",
"tag: maintenance": ":wrench: Maintenance",
"tag: performance": ":running_woman: Performance"
"pr: breaking change": ":boom: Breaking Change",
"pr: new feature": ":rocket: New Feature",
"pr: bug fix": ":bug: Bug Fix",
"pr: performance": ":running_woman: Performance",
"pr: polish": ":nail_care: Polish",
"pr: documentation": ":memo: Documentation",
"pr: maintenance": ":wrench: Maintenance"
},
"cacheDir": ".changelog"
}

View file

@ -63,8 +63,8 @@
},
"devDependencies": {
"@crowdin/cli": "^3.7.8",
"@swc/core": "^1.2.178",
"@swc/jest": "^0.2.20",
"@swc/core": "^1.2.181",
"@swc/jest": "^0.2.21",
"@testing-library/react-hooks": "^8.0.0",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^27.5.0",
@ -76,8 +76,8 @@
"@types/react-test-renderer": "^18.0.0",
"@types/semver": "^7.3.9",
"@types/shelljs": "^0.8.11",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"@typescript-eslint/eslint-plugin": "^5.23.0",
"@typescript-eslint/parser": "^5.23.0",
"concurrently": "^7.1.0",
"cross-env": "^7.0.3",
"cspell": "^5.20.0",
@ -91,7 +91,7 @@
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-regexp": "^1.7.0",
"husky": "^7.0.4",
"husky": "^8.0.1",
"image-size": "^1.0.1",
"jest": "^28.1.0",
"jest-environment-jsdom": "^28.1.0",

View file

@ -8,11 +8,11 @@
// @ts-check
import path from 'path';
import {createRequire} from 'module';
import logger from '@docusaurus/logger';
import semver from 'semver';
import path from 'path';
import {program} from 'commander';
import {createRequire} from 'module';
const packageJson = createRequire(import.meta.url)('../package.json');
const requiredVersion = packageJson.engines.node;

View file

@ -5,39 +5,41 @@
* LICENSE file in the root directory of this source tree.
*/
import logger from '@docusaurus/logger';
import fs from 'fs-extra';
import prompts, {type Choice} from 'prompts';
import path from 'path';
import shell from 'shelljs';
import _ from 'lodash';
import supportsColor from 'supports-color';
import {fileURLToPath} from 'url';
import path from 'path';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import shell from 'shelljs';
import prompts, {type Choice} from 'prompts';
import supportsColor from 'supports-color';
const RecommendedTemplate = 'classic';
const TypeScriptTemplateSuffix = '-typescript';
type CLIOptions = {
packageManager?: PackageManager;
skipInstall?: boolean;
typescript?: boolean;
gitStrategy?: GitStrategy;
};
// Only used in the rare, rare case of running globally installed create +
// using --skip-install. We need a default name to show the tip text
const DefaultPackageManager = 'npm';
const defaultPackageManager = 'npm';
const SupportedPackageManagers = {
const lockfileNames = {
npm: 'package-lock.json',
yarn: 'yarn.lock',
pnpm: 'pnpm-lock.yaml',
};
type SupportedPackageManager = keyof typeof SupportedPackageManagers;
type PackageManager = keyof typeof lockfileNames;
const PackageManagersList = Object.keys(
SupportedPackageManagers,
) as SupportedPackageManager[];
const packageManagers = Object.keys(lockfileNames) as PackageManager[];
async function findPackageManagerFromLockFile(): Promise<
SupportedPackageManager | undefined
> {
for (const packageManager of PackageManagersList) {
const lockFilePath = path.resolve(SupportedPackageManagers[packageManager]);
async function findPackageManagerFromLockFile(
rootDir: string,
): Promise<PackageManager | undefined> {
for (const packageManager of packageManagers) {
const lockFilePath = path.join(rootDir, lockfileNames[packageManager]);
if (await fs.pathExists(lockFilePath)) {
return packageManager;
}
@ -45,15 +47,13 @@ async function findPackageManagerFromLockFile(): Promise<
return undefined;
}
function findPackageManagerFromUserAgent():
| SupportedPackageManager
| undefined {
return PackageManagersList.find((packageManager) =>
function findPackageManagerFromUserAgent(): PackageManager | undefined {
return packageManagers.find((packageManager) =>
process.env.npm_config_user_agent?.startsWith(packageManager),
);
}
async function askForPackageManagerChoice(): Promise<SupportedPackageManager> {
async function askForPackageManagerChoice(): Promise<PackageManager> {
const hasYarn = shell.exec('yarn --version', {silent: true}).code === 0;
const hasPnpm = shell.exec('pnpm --version', {silent: true}).code === 0;
@ -65,67 +65,121 @@ async function askForPackageManagerChoice(): Promise<SupportedPackageManager> {
.map((p) => ({title: p, value: p}));
return (
await prompts({
type: 'select',
name: 'packageManager',
message: 'Select a package manager...',
choices,
})
await prompts(
{
type: 'select',
name: 'packageManager',
message: 'Select a package manager...',
choices,
},
{
onCancel() {
logger.info`Falling back to name=${defaultPackageManager}`;
},
},
)
).packageManager;
}
async function getPackageManager(
packageManagerChoice: SupportedPackageManager | undefined,
skipInstall: boolean = false,
): Promise<SupportedPackageManager> {
if (
packageManagerChoice &&
!PackageManagersList.includes(packageManagerChoice)
) {
dest: string,
{packageManager, skipInstall}: CLIOptions,
): Promise<PackageManager> {
if (packageManager && !packageManagers.includes(packageManager)) {
throw new Error(
`Invalid package manager choice ${packageManagerChoice}. Must be one of ${PackageManagersList.join(
`Invalid package manager choice ${packageManager}. Must be one of ${packageManagers.join(
', ',
)}`,
);
}
return (
packageManagerChoice ??
(await findPackageManagerFromLockFile()) ??
// If dest already contains a lockfile (e.g. if using a local template), we
// always use that instead
(await findPackageManagerFromLockFile(dest)) ??
packageManager ??
(await findPackageManagerFromLockFile('.')) ??
findPackageManagerFromUserAgent() ??
// This only happens if the user has a global installation in PATH
(skipInstall ? DefaultPackageManager : askForPackageManagerChoice())
(skipInstall ? defaultPackageManager : askForPackageManagerChoice()) ??
defaultPackageManager
);
}
function isValidGitRepoUrl(gitRepoUrl: string) {
return ['https://', 'git@'].some((item) => gitRepoUrl.startsWith(item));
}
const recommendedTemplate = 'classic';
const typeScriptTemplateSuffix = '-typescript';
const templatesDir = fileURLToPath(new URL('../templates', import.meta.url));
async function updatePkg(pkgPath: string, obj: {[key: string]: unknown}) {
const pkg = await fs.readJSON(pkgPath);
const newPkg = Object.assign(pkg, obj);
type Template = {
name: string;
path: string;
tsVariantPath: string | undefined;
};
await fs.outputFile(pkgPath, `${JSON.stringify(newPkg, null, 2)}\n`);
}
async function readTemplates(templatesDir: string) {
const templates = (await fs.readdir(templatesDir)).filter(
(d) =>
!d.startsWith('.') &&
!d.startsWith('README') &&
!d.endsWith(TypeScriptTemplateSuffix) &&
d !== 'shared',
async function readTemplates(): Promise<Template[]> {
const dirContents = await fs.readdir(templatesDir);
const templates = await Promise.all(
dirContents
.filter(
(d) =>
!d.startsWith('.') &&
!d.startsWith('README') &&
!d.endsWith(typeScriptTemplateSuffix) &&
d !== 'shared',
)
.map(async (name) => {
const tsVariantPath = path.join(
templatesDir,
`${name}${typeScriptTemplateSuffix}`,
);
return {
name,
path: path.join(templatesDir, name),
tsVariantPath: (await fs.pathExists(tsVariantPath))
? tsVariantPath
: undefined,
};
}),
);
// Classic should be first in list!
return _.sortBy(templates, (t) => t !== RecommendedTemplate);
return _.sortBy(templates, (t) => t.name !== recommendedTemplate);
}
function createTemplateChoices(templates: string[]) {
function makeNameAndValueChoice(value: string): Choice {
async function copyTemplate(
template: Template,
dest: string,
typescript: boolean,
): Promise<void> {
await fs.copy(path.join(templatesDir, 'shared'), dest);
// TypeScript variants will copy duplicate resources like CSS & config from
// base template
if (typescript) {
await fs.copy(template.path, dest, {
filter: async (filePath) =>
(await fs.stat(filePath)).isDirectory() ||
path.extname(filePath) === '.css' ||
path.basename(filePath) === 'docusaurus.config.js',
});
}
await fs.copy(typescript ? template.tsVariantPath! : template.path, dest, {
// Symlinks don't exist in published npm packages anymore, so this is only
// to prevent errors during local testing
filter: async (filePath) => !(await fs.lstat(filePath)).isSymbolicLink(),
});
}
function createTemplateChoices(templates: Template[]): Choice[] {
function makeNameAndValueChoice(value: string | Template): Choice {
if (typeof value === 'string') {
return {title: value, value};
}
const title =
value === RecommendedTemplate ? `${value} (recommended)` : value;
value.name === recommendedTemplate
? `${value.name} (recommended)`
: value.name;
return {title, value};
}
@ -136,55 +190,33 @@ function createTemplateChoices(templates: string[]) {
];
}
function getTypeScriptBaseTemplate(template: string): string | undefined {
if (template.endsWith(TypeScriptTemplateSuffix)) {
return template.replace(TypeScriptTemplateSuffix, '');
}
return undefined;
}
async function copyTemplate(
templatesDir: string,
template: string,
dest: string,
) {
await fs.copy(path.join(templatesDir, 'shared'), dest);
// TypeScript variants will copy duplicate resources like CSS & config from
// base template
const tsBaseTemplate = getTypeScriptBaseTemplate(template);
if (tsBaseTemplate) {
const tsBaseTemplatePath = path.resolve(templatesDir, tsBaseTemplate);
await fs.copy(tsBaseTemplatePath, dest, {
filter: async (filePath) =>
(await fs.stat(filePath)).isDirectory() ||
path.extname(filePath) === '.css' ||
path.basename(filePath) === 'docusaurus.config.js',
});
}
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: async (filePath) => !(await fs.lstat(filePath)).isSymbolicLink(),
});
function isValidGitRepoUrl(gitRepoUrl: string): boolean {
return ['https://', 'git@'].some((item) => gitRepoUrl.startsWith(item));
}
const gitStrategies = ['deep', 'shallow', 'copy', 'custom'] as const;
type GitStrategy = typeof gitStrategies[number];
async function getGitCommand(gitStrategy: typeof gitStrategies[number]) {
async function getGitCommand(gitStrategy: GitStrategy): Promise<string> {
switch (gitStrategy) {
case 'shallow':
case 'copy':
return 'git clone --recursive --depth 1';
case 'custom': {
const {command} = await prompts({
type: 'text',
name: 'command',
message:
'Write your own git clone command. The repository URL and destination directory will be supplied. E.g. "git clone --depth 10"',
});
return command;
const {command} = await prompts(
{
type: 'text',
name: 'command',
message:
'Write your own git clone command. The repository URL and destination directory will be supplied. E.g. "git clone --depth 10"',
},
{
onCancel() {
logger.info`Falling back to code=${'git clone'}`;
},
},
);
return command ?? 'git clone';
}
case 'deep':
default:
@ -192,178 +224,273 @@ async function getGitCommand(gitStrategy: typeof gitStrategies[number]) {
}
}
export default async function init(
async function getSiteName(
reqName: string | undefined,
rootDir: string,
siteName?: string,
reqTemplate?: string,
cliOptions: Partial<{
packageManager: SupportedPackageManager;
skipInstall: boolean;
typescript: boolean;
gitStrategy: typeof gitStrategies[number];
}> = {},
): Promise<void> {
const templatesDir = fileURLToPath(new URL('../templates', import.meta.url));
const templates = await readTemplates(templatesDir);
const hasTS = (templateName: string) =>
fs.pathExists(
path.join(templatesDir, `${templateName}${TypeScriptTemplateSuffix}`),
);
let name = siteName;
// Prompt if siteName is not passed from CLI.
if (!name) {
const prompt = await prompts({
): Promise<string> {
async function validateSiteName(siteName: string) {
if (!siteName) {
return 'A website name is required.';
}
const dest = path.resolve(rootDir, siteName);
if (await fs.pathExists(dest)) {
return logger.interpolate`Directory already exists at path=${dest}!`;
}
return true;
}
if (reqName) {
const res = validateSiteName(reqName);
if (typeof res === 'string') {
throw new Error(res);
}
return reqName;
}
const {siteName} = await prompts(
{
type: 'text',
name: 'name',
name: 'siteName',
message: 'What should we name this site?',
initial: 'website',
});
name = prompt.name;
}
if (!name) {
logger.error('A website name is required.');
process.exit(1);
}
const dest = path.resolve(rootDir, name);
if (await fs.pathExists(dest)) {
logger.error`Directory already exists at path=${dest}!`;
process.exit(1);
}
let template = reqTemplate;
let useTS = cliOptions.typescript;
// Prompt if template is not provided from CLI.
if (!template) {
const templatePrompt = await prompts({
type: 'select',
name: 'template',
message: 'Select a template below...',
choices: createTemplateChoices(templates),
});
template = templatePrompt.template;
if (template && !useTS && (await hasTS(template))) {
const tsPrompt = await prompts({
type: 'confirm',
name: 'useTS',
message:
'This template is available in TypeScript. Do you want to use the TS variant?',
initial: false,
});
useTS = tsPrompt.useTS;
}
}
let gitStrategy = cliOptions.gitStrategy ?? 'deep';
// If user choose Git repository, we'll prompt for the url.
if (template === 'Git repository') {
const repoPrompt = await prompts({
type: 'text',
name: 'gitRepoUrl',
validate: (url?: string) => {
if (url && isValidGitRepoUrl(url)) {
return true;
}
return logger.red('Invalid repository URL');
validate: validateSiteName,
},
{
onCancel() {
logger.error('A website name is required.');
process.exit(1);
},
message: logger.interpolate`Enter a repository URL from GitHub, Bitbucket, GitLab, or any other public repo.
(e.g: url=${'https://github.com/ownerName/repoName.git'})`,
});
({gitStrategy} = await prompts({
type: 'select',
name: 'gitStrategy',
message: 'How should we clone this repo?',
choices: [
{title: 'Deep clone: preserve full history', value: 'deep'},
{title: 'Shallow clone: clone with --depth=1', value: 'shallow'},
{
title: 'Copy: do a shallow clone, but do not create a git repo',
value: 'copy',
},
{title: 'Custom: enter your custom git clone command', value: 'custom'},
],
}));
template = repoPrompt.gitRepoUrl;
} else if (template === 'Local template') {
const dirPrompt = await prompts({
type: 'text',
name: 'templateDir',
validate: async (dir?: string) => {
if (dir) {
const fullDir = path.resolve(dir);
if (await fs.pathExists(fullDir)) {
},
);
return siteName;
}
type Source =
| {
type: 'template';
template: Template;
typescript: boolean;
}
| {
type: 'git';
url: string;
strategy: GitStrategy;
}
| {
type: 'local';
path: string;
};
async function getSource(
reqTemplate: string | undefined,
templates: Template[],
cliOptions: CLIOptions,
): Promise<Source> {
if (reqTemplate) {
if (isValidGitRepoUrl(reqTemplate)) {
if (
cliOptions.gitStrategy &&
!gitStrategies.includes(cliOptions.gitStrategy)
) {
logger.error`Invalid git strategy: name=${
cliOptions.gitStrategy
}. Value must be one of ${gitStrategies.join(', ')}.`;
process.exit(1);
}
return {
type: 'git',
url: reqTemplate,
strategy: cliOptions.gitStrategy ?? 'deep',
};
} else if (await fs.pathExists(path.resolve(reqTemplate))) {
return {
type: 'local',
path: path.resolve(reqTemplate),
};
}
const template = templates.find((t) => t.name === reqTemplate);
if (!template) {
logger.error('Invalid template.');
process.exit(1);
}
if (cliOptions.typescript && !template.tsVariantPath) {
logger.error`Template name=${reqTemplate} doesn't provide the TypeScript variant.`;
process.exit(1);
}
return {
type: 'template',
template,
typescript: cliOptions.typescript ?? false,
};
}
const template = cliOptions.gitStrategy
? 'Git repository'
: (
await prompts(
{
type: 'select',
name: 'template',
message: 'Select a template below...',
choices: createTemplateChoices(templates),
},
{
onCancel() {
logger.error('A choice is required.');
process.exit(1);
},
},
)
).template;
if (template === 'Git repository') {
const {gitRepoUrl} = await prompts(
{
type: 'text',
name: 'gitRepoUrl',
validate: (url?: string) => {
if (url && isValidGitRepoUrl(url)) {
return true;
}
return logger.red(
logger.interpolate`path=${fullDir} does not exist.`,
);
}
return logger.red('Please enter a valid path.');
return logger.red('Invalid repository URL');
},
message: logger.interpolate`Enter a repository URL from GitHub, Bitbucket, GitLab, or any other public repo.
(e.g: url=${'https://github.com/ownerName/repoName.git'})`,
},
{
onCancel() {
logger.error('A git repo URL is required.');
process.exit(1);
},
},
);
let strategy = cliOptions.gitStrategy;
if (!strategy) {
({strategy} = await prompts(
{
type: 'select',
name: 'strategy',
message: 'How should we clone this repo?',
choices: [
{title: 'Deep clone: preserve full history', value: 'deep'},
{title: 'Shallow clone: clone with --depth=1', value: 'shallow'},
{
title: 'Copy: do a shallow clone, but do not create a git repo',
value: 'copy',
},
{
title: 'Custom: enter your custom git clone command',
value: 'custom',
},
],
},
{
onCancel() {
logger.info`Falling back to name=${'deep'}`;
},
},
));
}
return {
type: 'git',
url: gitRepoUrl,
strategy: strategy ?? 'deep',
};
} else if (template === 'Local template') {
const {templateDir} = await prompts(
{
type: 'text',
name: 'templateDir',
validate: async (dir?: string) => {
if (dir) {
const fullDir = path.resolve(dir);
if (await fs.pathExists(fullDir)) {
return true;
}
return logger.red(
logger.interpolate`path=${fullDir} does not exist.`,
);
}
return logger.red('Please enter a valid path.');
},
message:
'Enter a local folder path, relative to the current working directory.',
},
{
onCancel() {
logger.error('A file path is required.');
process.exit(1);
},
},
);
return {
type: 'local',
path: templateDir,
};
}
let useTS = cliOptions.typescript;
if (!useTS && template.tsVariantPath) {
({useTS} = await prompts({
type: 'confirm',
name: 'useTS',
message:
'Enter a local folder path, relative to the current working directory.',
});
template = dirPrompt.templateDir;
'This template is available in TypeScript. Do you want to use the TS variant?',
initial: false,
}));
}
return {
type: 'template',
template,
typescript: useTS ?? false,
};
}
if (!template) {
logger.error('Template should not be empty');
process.exit(1);
}
async function updatePkg(pkgPath: string, obj: {[key: string]: unknown}) {
const pkg = await fs.readJSON(pkgPath);
const newPkg = Object.assign(pkg, obj);
await fs.outputFile(pkgPath, `${JSON.stringify(newPkg, null, 2)}\n`);
}
export default async function init(
rootDir: string,
reqName?: string,
reqTemplate?: string,
cliOptions: CLIOptions = {},
): Promise<void> {
const templates = await readTemplates();
const siteName = await getSiteName(reqName, rootDir);
const dest = path.resolve(rootDir, siteName);
const source = await getSource(reqTemplate, templates, cliOptions);
logger.info('Creating new Docusaurus project...');
if (isValidGitRepoUrl(template)) {
logger.info`Cloning Git template url=${template}...`;
if (!gitStrategies.includes(gitStrategy)) {
logger.error`Invalid git strategy: name=${gitStrategy}. Value must be one of ${gitStrategies.join(
', ',
)}.`;
if (source.type === 'git') {
logger.info`Cloning Git template url=${source.url}...`;
const command = await getGitCommand(source.strategy);
if (shell.exec(`${command} ${source.url} ${dest}`).code !== 0) {
logger.error`Cloning Git template failed!`;
process.exit(1);
}
const command = await getGitCommand(gitStrategy);
if (shell.exec(`${command} ${template} ${dest}`).code !== 0) {
logger.error`Cloning Git template name=${template} failed!`;
process.exit(1);
}
if (gitStrategy === 'copy') {
if (source.strategy === 'copy') {
await fs.remove(path.join(dest, '.git'));
}
} else if (templates.includes(template)) {
// Docusaurus templates.
if (useTS) {
if (!(await hasTS(template))) {
logger.error`Template name=${template} doesn't provide the TypeScript variant.`;
process.exit(1);
}
template = `${template}${TypeScriptTemplateSuffix}`;
}
} else if (source.type === 'template') {
try {
await copyTemplate(templatesDir, template, dest);
await copyTemplate(source.template, dest, source.typescript);
} catch (err) {
logger.error`Copying Docusaurus template name=${template} failed!`;
throw err;
}
} else if (await fs.pathExists(path.resolve(template))) {
const templateDir = path.resolve(template);
try {
await fs.copy(templateDir, dest);
} catch (err) {
logger.error`Copying local template path=${templateDir} failed!`;
logger.error`Copying Docusaurus template name=${source.template.name} failed!`;
throw err;
}
} else {
logger.error('Invalid template.');
process.exit(1);
try {
await fs.copy(source.path, dest);
} catch (err) {
logger.error`Copying local template path=${source.path} failed!`;
throw err;
}
}
// Update package.json info.
try {
await updatePkg(path.join(dest, 'package.json'), {
name: _.kebabCase(name),
name: _.kebabCase(siteName),
version: '0.0.0',
private: true,
});
@ -385,10 +512,7 @@ export default async function init(
// Display the most elegant way to cd.
const cdpath = path.relative('.', dest);
const pkgManager = await getPackageManager(
cliOptions.packageManager,
cliOptions.skipInstall,
);
const pkgManager = await getPackageManager(dest, cliOptions);
if (!cliOptions.skipInstall) {
shell.cd(dest);
logger.info`Installing dependencies with name=${pkgManager}...`;
@ -398,8 +522,8 @@ export default async function init(
{
env: {
...process.env,
// Force coloring the output, since the command is invoked,
// by shelljs which is not the interactive shell
// Force coloring the output, since the command is invoked by
// shelljs, which is not an interactive shell
...(supportsColor.stdout ? {FORCE_COLOR: '1'} : {}),
},
},

View file

@ -1,11 +1,12 @@
import React from 'react';
import clsx from 'clsx';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import styles from './index.module.css';
import Layout from '@theme/Layout';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import styles from './index.module.css';
function HomepageHeader() {
const {siteConfig} = useDocusaurusContext();
return (

View file

@ -1,11 +1,12 @@
import React from 'react';
import clsx from 'clsx';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import styles from './index.module.css';
import Layout from '@theme/Layout';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import styles from './index.module.css';
function HomepageHeader() {
const {siteConfig} = useDocusaurusContext();
return (

View file

@ -33,7 +33,7 @@
"tslib": "^2.4.0",
"unist-util-visit": "^2.0.3",
"url-loader": "^4.1.1",
"webpack": "^5.72.0"
"webpack": "^5.72.1"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.20",

View file

@ -6,21 +6,23 @@
*/
import fs from 'fs-extra';
import {createCompiler} from '@mdx-js/mdx';
import logger from '@docusaurus/logger';
import emoji from 'remark-emoji';
import {
parseFrontMatter,
parseMarkdownContentTitle,
escapePath,
getFileLoaderUtils,
} from '@docusaurus/utils';
import {createCompiler} from '@mdx-js/mdx';
import emoji from 'remark-emoji';
import stringifyObject from 'stringify-object';
import headings from './remark/headings';
import toc from './remark/toc';
import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks';
import transformImage from './remark/transformImage';
import transformLinks from './remark/transformLinks';
import type {LoaderContext} from 'webpack';
import type {Processor, Plugin} from 'unified';
@ -31,7 +33,7 @@ const {
const pragma = `
/* @jsxRuntime classic */
/* @jsx mdx */
/* @jsxFrag mdx.Fragment */
/* @jsxFrag React.Fragment */
`;
const DEFAULT_OPTIONS: MDXOptions = {

View file

@ -6,13 +6,13 @@
*/
import {parse, type ParserOptions} from '@babel/parser';
import type {Identifier} from '@babel/types';
import traverse from '@babel/traverse';
import stringifyObject from 'stringify-object';
import toString from 'mdast-util-to-string';
import visit from 'unist-util-visit';
import {toValue} from '../utils';
import type {Identifier} from '@babel/types';
import type {TOCItem} from '../..';
import type {Node, Parent} from 'unist';
import type {Heading, Literal} from 'mdast';

View file

@ -5,6 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import url from 'url';
import fs from 'fs-extra';
import {promisify} from 'util';
import logger from '@docusaurus/logger';
import {
toMessageRelativeFilePath,
posixPath,
@ -13,15 +18,10 @@ import {
findAsyncSequential,
} from '@docusaurus/utils';
import visit from 'unist-util-visit';
import path from 'path';
import url from 'url';
import fs from 'fs-extra';
import escapeHtml from 'escape-html';
import sizeOf from 'image-size';
import {promisify} from 'util';
import type {Transformer} from 'unified';
import type {Image, Literal} from 'mdast';
import logger from '@docusaurus/logger';
const {
loaders: {inlineMarkdownImageFileLoader},

View file

@ -5,6 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import url from 'url';
import fs from 'fs-extra';
import {
toMessageRelativeFilePath,
posixPath,
@ -13,9 +16,6 @@ import {
findAsyncSequential,
} from '@docusaurus/utils';
import visit from 'unist-util-visit';
import path from 'path';
import url from 'url';
import fs from 'fs-extra';
import escapeHtml from 'escape-html';
import {stringifyContent} from '../utils';
import type {Transformer} from 'unified';

View file

@ -8,11 +8,11 @@
// @ts-check
import path from 'path';
import {createRequire} from 'module';
import logger from '@docusaurus/logger';
import semver from 'semver';
import cli from 'commander';
import path from 'path';
import {createRequire} from 'module';
const moduleRequire = createRequire(import.meta.url);
const requiredVersion = moduleRequire('../package.json').engines.node;

View file

@ -6,10 +6,10 @@
*/
import {jest} from '@jest/globals';
import {migrateDocusaurusProject} from '../index';
import path from 'path';
import fs from 'fs-extra';
import {posixPath} from '@docusaurus/utils';
import {migrateDocusaurusProject} from '../index';
async function testMigration(siteDir: string, newDir: string) {
const writeMock = jest.spyOn(fs, 'outputFile').mockImplementation(() => {});

View file

@ -5,22 +5,23 @@
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import fs from 'fs-extra';
import importFresh from 'import-fresh';
import logger from '@docusaurus/logger';
import {Globby} from '@docusaurus/utils';
import importFresh from 'import-fresh';
import Color from 'color';
import extractMetadata, {shouldQuotifyFrontMatter} from './frontMatter';
import migratePage from './transform';
import sanitizeMD from './sanitizeMD';
import type {
SidebarEntry,
SidebarEntries,
VersionOneConfig,
VersionTwoConfig,
} from './types';
import extractMetadata, {shouldQuotifyFrontMatter} from './frontMatter';
import migratePage from './transform';
import sanitizeMD from './sanitizeMD';
import path from 'path';
const DOCUSAURUS_VERSION = (importFresh('../package.json') as {version: string})
.version;

View file

@ -136,8 +136,8 @@ declare module '@docusaurus/ErrorBoundary' {
}
declare module '@docusaurus/Head' {
import type {HelmetProps} from 'react-helmet-async';
import type {ReactNode} from 'react';
import type {HelmetProps} from 'react-helmet-async';
export type Props = HelmetProps & {children: ReactNode};

View file

@ -5,12 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import type {PluginContext} from '../types';
import collectRedirects from '../collectRedirects';
import {validateOptions} from '../options';
import {removeTrailingSlash} from '@docusaurus/utils';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import collectRedirects from '../collectRedirects';
import {validateOptions} from '../options';
import type {Options} from '../options';
import type {PluginContext} from '../types';
function createTestPluginContext(
options?: Options,

View file

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import {validateOptions, DEFAULT_OPTIONS} from '../options';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {validateOptions, DEFAULT_OPTIONS} from '../options';
import type {Options} from '../options';
function testValidate(options: Options) {

View file

@ -6,19 +6,18 @@
*/
import _ from 'lodash';
import type {PluginOptions, RedirectOption} from './options';
import type {PluginContext, RedirectMetadata} from './types';
import logger from '@docusaurus/logger';
import {
applyTrailingSlash,
type ApplyTrailingSlashParams,
} from '@docusaurus/utils-common';
import {
createFromExtensionsRedirects,
createToExtensionsRedirects,
} from './extensionRedirects';
import {validateRedirect} from './redirectValidation';
import {
applyTrailingSlash,
type ApplyTrailingSlashParams,
} from '@docusaurus/utils-common';
import logger from '@docusaurus/logger';
import type {PluginOptions, RedirectOption} from './options';
import type {PluginContext, RedirectMetadata} from './types';
export default function collectRedirects(
pluginContext: PluginContext,

View file

@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import * as eta from 'eta';
import redirectPageTemplate from './templates/redirectPage.template.html';
import _ from 'lodash';
const getCompiledRedirectPageTemplate = _.memoize(() =>
eta.compile(redirectPageTemplate.trim()),

View file

@ -5,16 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {PluginContext, RedirectMetadata} from './types';
import type {PluginOptions, Options} from './options';
import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
import collectRedirects from './collectRedirects';
import writeRedirectFiles, {
toRedirectFilesMetadata,
type RedirectFileMetadata,
} from './writeRedirectFiles';
import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {PluginContext, RedirectMetadata} from './types';
import type {PluginOptions, Options} from './options';
export default function pluginClientRedirectsPages(
context: LoadContext,

View file

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import type {OptionValidationContext} from '@docusaurus/types';
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
import type {OptionValidationContext} from '@docusaurus/types';
export type RedirectOption = {
to: string;

View file

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import type {RedirectMetadata} from './types';
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
import type {RedirectMetadata} from './types';
const RedirectSchema = Joi.object<RedirectMetadata>({
from: PathnameSchema.required(),

View file

@ -8,11 +8,12 @@
import fs from 'fs-extra';
import path from 'path';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {normalizeUrl} from '@docusaurus/utils';
import createRedirectPageContent from './createRedirectPageContent';
import type {PluginContext, RedirectMetadata} from './types';
import createRedirectPageContent from './createRedirectPageContent';
import {normalizeUrl} from '@docusaurus/utils';
import logger from '@docusaurus/logger';
export type WriteFilesPluginContext = Pick<PluginContext, 'baseUrl' | 'outDir'>;

View file

@ -33,7 +33,7 @@
"tslib": "^2.4.0",
"unist-util-visit": "^2.0.3",
"utility-types": "^3.10.0",
"webpack": "^5.72.0"
"webpack": "^5.72.1"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.20",

View file

@ -5,13 +5,13 @@
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import {
type AuthorsMap,
getAuthorsMap,
getBlogPostAuthors,
validateAuthorsMap,
} from '../authors';
import path from 'path';
describe('getBlogPostAuthors', () => {
it('can read no authors', () => {

View file

@ -6,6 +6,8 @@
*/
import {jest} from '@jest/globals';
import fs from 'fs-extra';
import path from 'path';
import {
truncate,
parseBlogFileName,
@ -14,13 +16,8 @@ import {
paginateBlogPosts,
type LinkifyParams,
} from '../blogUtils';
import fs from 'fs-extra';
import path from 'path';
import type {
BlogBrokenMarkdownLink,
BlogContentPaths,
BlogPost,
} from '../types';
import type {BlogBrokenMarkdownLink, BlogContentPaths} from '../types';
import type {BlogPost} from '@docusaurus/plugin-content-blog';
describe('truncate', () => {
it('truncates texts', () => {

View file

@ -8,11 +8,11 @@
import {jest} from '@jest/globals';
import path from 'path';
import fs from 'fs-extra';
import {DEFAULT_OPTIONS} from '../options';
import {generateBlogPosts} from '../blogUtils';
import {createBlogFeedFiles} from '../feed';
import type {LoadContext, I18n} from '@docusaurus/types';
import type {BlogContentPaths} from '../types';
import {DEFAULT_OPTIONS} from '../options';
import {generateBlogPosts} from '../blogUtils';
import type {PluginOptions} from '@docusaurus/plugin-content-blog';
const DefaultI18N: I18n = {

View file

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import {validateBlogPostFrontMatter} from '../frontMatter';
import escapeStringRegexp from 'escape-string-regexp';
import {validateBlogPostFrontMatter} from '../frontMatter';
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
// TODO this abstraction reduce verbosity but it makes it harder to debug

View file

@ -7,13 +7,13 @@
import {jest} from '@jest/globals';
import path from 'path';
import pluginContentBlog from '../index';
import type {DocusaurusConfig, LoadContext, I18n} from '@docusaurus/types';
import {validateOptions} from '../options';
import type {BlogPost} from '../types';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {posixPath, getFileCommitDate} from '@docusaurus/utils';
import pluginContentBlog from '../index';
import {validateOptions} from '../options';
import type {DocusaurusConfig, LoadContext, I18n} from '@docusaurus/types';
import type {
BlogPost,
PluginOptions,
EditUrlFunction,
} from '@docusaurus/plugin-content-blog';

View file

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import {validateOptions, DEFAULT_OPTIONS} from '../options';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {validateOptions, DEFAULT_OPTIONS} from '../options';
import type {Options} from '@docusaurus/plugin-content-blog';
function testValidate(options: Options) {

View file

@ -5,11 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
import type {BlogPost, BlogContent} from '../types';
import {updateTranslationFileMessages} from '@docusaurus/utils';
import {getTranslationFiles, translateContent} from '../translations';
import {DEFAULT_OPTIONS} from '../options';
import {updateTranslationFileMessages} from '@docusaurus/utils';
import type {PluginOptions} from '@docusaurus/plugin-content-blog';
import type {
PluginOptions,
BlogPost,
BlogContent,
} from '@docusaurus/plugin-content-blog';
const sampleBlogOptions: PluginOptions = {
...DEFAULT_OPTIONS,

View file

@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import type {BlogContentPaths} from './types';
import {getDataFileData} from '@docusaurus/utils';
import {Joi, URISchema} from '@docusaurus/utils-validation';
import type {BlogContentPaths} from './types';
import type {
Author,
BlogPostFrontMatter,

View file

@ -7,9 +7,9 @@
import fs from 'fs-extra';
import path from 'path';
import readingTime from 'reading-time';
import _ from 'lodash';
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
import logger from '@docusaurus/logger';
import readingTime from 'reading-time';
import {
parseMarkdownString,
normalizeUrl,
@ -24,10 +24,9 @@ import {
getFileCommitDate,
getContentPathList,
} from '@docusaurus/utils';
import type {LoadContext} from '@docusaurus/types';
import {validateBlogPostFrontMatter} from './frontMatter';
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
import logger from '@docusaurus/logger';
import type {LoadContext} from '@docusaurus/types';
import type {
PluginOptions,
ReadingTimeFunction,
@ -35,6 +34,7 @@ import type {
BlogTags,
BlogPaginated,
} from '@docusaurus/plugin-content-blog';
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
export function truncate(fileString: string, truncateMarker: RegExp): string {
return fileString.split(truncateMarker, 1).shift()!;

View file

@ -5,20 +5,20 @@
* LICENSE file in the root directory of this source tree.
*/
import {Feed, type Author as FeedAuthor, type Item as FeedItem} from 'feed';
import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
import {load as cheerioLoad} from 'cheerio';
import type {DocusaurusConfig} from '@docusaurus/types';
import path from 'path';
import fs from 'fs-extra';
import logger from '@docusaurus/logger';
import {Feed, type Author as FeedAuthor, type Item as FeedItem} from 'feed';
import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
import {blogPostContainerID} from '@docusaurus/utils-common';
import {load as cheerioLoad} from 'cheerio';
import type {DocusaurusConfig} from '@docusaurus/types';
import type {
FeedType,
PluginOptions,
Author,
BlogPost,
} from '@docusaurus/plugin-content-blog';
import {blogPostContainerID} from '@docusaurus/utils-common';
import logger from '@docusaurus/logger';
async function generateBlogFeed({
blogPosts,

View file

@ -6,8 +6,6 @@
*/
import path from 'path';
import admonitions from 'remark-admonitions';
import footnoteIDFixer from './remark/footnoteIDFixer';
import {
normalizeUrl,
docuHash,
@ -23,17 +21,19 @@ import {
type TagsListItem,
type TagModule,
} from '@docusaurus/utils';
import {translateContent, getTranslationFiles} from './translations';
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
import admonitions from 'remark-admonitions';
import {
generateBlogPosts,
getSourceToPermalink,
getBlogTags,
paginateBlogPosts,
} from './blogUtils';
import footnoteIDFixer from './remark/footnoteIDFixer';
import {translateContent, getTranslationFiles} from './translations';
import {createBlogFeedFiles} from './feed';
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
import type {
PluginOptions,
BlogPostFrontMatter,

View file

@ -538,11 +538,11 @@ declare module '@theme/BlogTagsListPage' {
}
declare module '@theme/BlogTagsPostsPage' {
import type {Content} from '@theme/BlogPostPage';
import type {
BlogSidebar,
BlogPaginatedMetadata,
} from '@docusaurus/plugin-content-blog';
import type {Content} from '@theme/BlogPostPage';
import type {TagModule} from '@docusaurus/utils';
export interface Props {

View file

@ -38,7 +38,7 @@
"remark-admonitions": "^1.2.1",
"tslib": "^2.4.0",
"utility-types": "^3.10.0",
"webpack": "^5.72.0"
"webpack": "^5.72.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.20",

View file

@ -8,6 +8,8 @@
import {jest} from '@jest/globals';
import path from 'path';
import {loadContext} from '@docusaurus/core/src/server/index';
import {createSlugger, posixPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import {createSidebarsUtils} from '../sidebars/utils';
import {
processDocMetadata,
readVersionDocs,
@ -17,8 +19,9 @@ import {
type DocEnv,
} from '../docs';
import {loadSidebars} from '../sidebars';
import type {Sidebars} from '../sidebars/types';
import {readVersionsMetadata} from '../versions';
import {DEFAULT_OPTIONS} from '../options';
import type {Sidebars} from '../sidebars/types';
import type {DocFile} from '../types';
import type {
MetadataOptions,
@ -29,10 +32,7 @@ import type {
PropNavigationLink,
} from '@docusaurus/plugin-content-docs';
import type {LoadContext} from '@docusaurus/types';
import {DEFAULT_OPTIONS} from '../options';
import type {Optional} from 'utility-types';
import {createSlugger, posixPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import {createSidebarsUtils} from '../sidebars/utils';
jest.setTimeout(15000);

View file

@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import escapeStringRegexp from 'escape-string-regexp';
import {validateDocFrontMatter} from '../frontMatter';
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
import escapeStringRegexp from 'escape-string-regexp';
function testField(params: {
prefix: string;

View file

@ -7,32 +7,27 @@
import {jest} from '@jest/globals';
import path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import {isMatch} from 'picomatch';
import commander from 'commander';
import _ from 'lodash';
import fs from 'fs-extra';
import pluginContentDocs from '../index';
import webpack from 'webpack';
import {loadContext} from '@docusaurus/core/src/server/index';
import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils';
import type {RouteConfig} from '@docusaurus/types';
import {posixPath} from '@docusaurus/utils';
import {sortConfig} from '@docusaurus/core/src/server/plugins/routeConfig';
import * as cliDocs from '../cli';
import {validateOptions} from '../options';
import {posixPath} from '@docusaurus/utils';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import type {LoadedVersion} from '../types';
import type {
SidebarItem,
SidebarItemsGeneratorOption,
SidebarItemsGeneratorOptionArgs,
} from '../sidebars/types';
import {toSidebarsProp} from '../props';
import webpack from 'webpack';
import pluginContentDocs from '../index';
import {toSidebarsProp} from '../props';
import {DefaultSidebarItemsGenerator} from '../sidebars/generator';
import {DisabledSidebars} from '../sidebars';
import * as cliDocs from '../cli';
import {validateOptions} from '../options';
import type {RouteConfig} from '@docusaurus/types';
import type {LoadedVersion} from '@docusaurus/plugin-content-docs';
import type {SidebarItem, SidebarItemsGeneratorOption} from '../sidebars/types';
function findDocById(version: LoadedVersion, unversionedId: string) {
return version.docs.find((item) => item.unversionedId === unversionedId);
@ -768,14 +763,14 @@ describe('site with custom sidebar items generator', () => {
const customSidebarItemsGeneratorMock = jest.fn(async () => []);
const {siteDir} = await loadSite(customSidebarItemsGeneratorMock);
const generatorArg: SidebarItemsGeneratorOptionArgs =
const generatorArg: Parameters<SidebarItemsGeneratorOption>[0] =
customSidebarItemsGeneratorMock.mock.calls[0][0];
// Make test pass even if docs are in different order and paths are
// absolutes
function makeDeterministic(
arg: SidebarItemsGeneratorOptionArgs,
): SidebarItemsGeneratorOptionArgs {
arg: Parameters<SidebarItemsGeneratorOption>[0],
): Parameters<SidebarItemsGeneratorOption>[0] {
return {
...arg,
docs: _.orderBy(arg.docs, 'id'),

View file

@ -5,11 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import {createTempRepo} from '@testing-utils/git';
import {jest} from '@jest/globals';
import fs from 'fs-extra';
import path from 'path';
import shell from 'shelljs';
import {createTempRepo} from '@testing-utils/git';
import {getFileLastUpdate} from '../lastUpdate';

View file

@ -5,14 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
import {validateOptions, DEFAULT_OPTIONS} from '../options';
import {GlobExcludeDefault} from '@docusaurus/utils';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {validateOptions, DEFAULT_OPTIONS} from '../options';
import {DefaultSidebarItemsGenerator} from '../sidebars/generator';
import {
DefaultNumberPrefixParser,
DisabledNumberPrefixParser,
} from '../numberPrefix';
import {GlobExcludeDefault} from '@docusaurus/utils';
import type {Options} from '@docusaurus/plugin-content-docs';
// The type of remark/rehype plugins can be function/object

View file

@ -5,14 +5,17 @@
* LICENSE file in the root directory of this source tree.
*/
import type {LoadedContent, LoadedVersion} from '../types';
import {updateTranslationFileMessages} from '@docusaurus/utils';
import {CURRENT_VERSION_NAME} from '../constants';
import {
getLoadedContentTranslationFiles,
translateLoadedContent,
} from '../translations';
import type {DocMetadata} from '@docusaurus/plugin-content-docs';
import {updateTranslationFileMessages} from '@docusaurus/utils';
import type {
DocMetadata,
LoadedContent,
LoadedVersion,
} from '@docusaurus/plugin-content-docs';
function createSampleDoc(doc: Pick<DocMetadata, 'id'>): DocMetadata {
return {

View file

@ -5,13 +5,13 @@
* LICENSE file in the root directory of this source tree.
*/
import {type SidebarsUtils, toNavigationLink} from './sidebars/utils';
import {createDocsByIdIndex} from './docs';
import type {
CategoryGeneratedIndexMetadata,
DocMetadataBase,
} from '@docusaurus/plugin-content-docs';
import type {SidebarItemCategoryWithGeneratedIndex} from './sidebars/types';
import {type SidebarsUtils, toNavigationLink} from './sidebars/utils';
import {createDocsByIdIndex} from './docs';
function getCategoryGeneratedIndexMetadata({
category,

View file

@ -5,6 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import fs from 'fs-extra';
import path from 'path';
import logger from '@docusaurus/logger';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import {
getVersionsFilePath,
getVersionDocsDirPath,
@ -12,13 +16,9 @@ import {
getDocsDirPathLocalized,
} from './versions/files';
import {validateVersionName} from './versions/validation';
import fs from 'fs-extra';
import path from 'path';
import type {PluginOptions} from '@docusaurus/plugin-content-docs';
import {loadSidebarsFileUnsafe} from './sidebars';
import {CURRENT_VERSION_NAME} from './constants';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import logger from '@docusaurus/logger';
import type {PluginOptions} from '@docusaurus/plugin-content-docs';
import type {LoadContext} from '@docusaurus/types';
async function createVersionedSidebarFile({

View file

@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import {
getActivePlugin,
getLatestVersion,
@ -17,7 +18,6 @@ import type {
GlobalVersion,
ActivePlugin,
} from '@docusaurus/plugin-content-docs/client';
import _ from 'lodash';
describe('docsClientUtils', () => {
it('getActivePlugin', () => {

View file

@ -19,15 +19,12 @@ import {
Globby,
normalizeFrontMatterTags,
} from '@docusaurus/utils';
import type {LoadContext} from '@docusaurus/types';
import {getFileLastUpdate} from './lastUpdate';
import type {DocFile} from './types';
import getSlug from './slug';
import {CURRENT_VERSION_NAME} from './constants';
import {stripPathNumberPrefixes} from './numberPrefix';
import {validateDocFrontMatter} from './frontMatter';
import type {SidebarsUtils} from './sidebars/utils';
import {toDocNavigationLink, toNavigationLink} from './sidebars/utils';
import type {
MetadataOptions,
@ -41,6 +38,9 @@ import type {
DocFrontMatter,
LoadedVersion,
} from '@docusaurus/plugin-content-docs';
import type {LoadContext} from '@docusaurus/types';
import type {SidebarsUtils} from './sidebars/utils';
import type {DocFile} from './types';
type LastUpdateOptions = Pick<
PluginOptions,

View file

@ -6,7 +6,6 @@
*/
import _ from 'lodash';
import type {Sidebars} from './sidebars/types';
import {getMainDocId} from './docs';
import type {FullVersion} from './types';
import type {
@ -18,6 +17,7 @@ import type {
GlobalSidebar,
GlobalDoc,
} from '@docusaurus/plugin-content-docs/client';
import type {Sidebars} from './sidebars/types';
function toGlobalDataDoc(doc: DocMetadata): GlobalDoc {
return {

View file

@ -6,7 +6,8 @@
*/
import path from 'path';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {
normalizeUrl,
docuHash,
@ -19,20 +20,15 @@ import {
createSlugger,
DEFAULT_PLUGIN_ID,
} from '@docusaurus/utils';
import type {LoadContext, Plugin} from '@docusaurus/types';
import {loadSidebars, resolveSidebarPathOption} from './sidebars';
import {CategoryMetadataFilenamePattern} from './sidebars/generator';
import type {DocEnv} from './docs';
import {readVersionDocs, processDocMetadata, addDocNavigation} from './docs';
import {
readVersionDocs,
processDocMetadata,
addDocNavigation,
type DocEnv,
} from './docs';
import {readVersionsMetadata} from './versions';
import type {
SourceToPermalink,
DocFile,
DocsMarkdownOption,
VersionTag,
FullVersion,
} from './types';
import type {RuleSetRule} from 'webpack';
import {cliDocsVersionCommand} from './cli';
import {VERSIONS_JSON_FILE} from './constants';
import {toGlobalDataVersion} from './globalData';
@ -42,9 +38,10 @@ import {
translateLoadedContent,
getLoadedContentTranslationFiles,
} from './translations';
import logger from '@docusaurus/logger';
import {getVersionTags} from './tags';
import {createVersionRoutes} from './routes';
import {createSidebarsUtils} from './sidebars/utils';
import type {
PropTagsListPage,
PluginOptions,
@ -54,8 +51,15 @@ import type {
LoadedContent,
LoadedVersion,
} from '@docusaurus/plugin-content-docs';
import {createSidebarsUtils} from './sidebars/utils';
import _ from 'lodash';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {
SourceToPermalink,
DocFile,
DocsMarkdownOption,
VersionTag,
FullVersion,
} from './types';
import type {RuleSetRule} from 'webpack';
export default async function pluginContentDocs(
context: LoadContext,

View file

@ -9,13 +9,13 @@ import {jest} from '@jest/globals';
import fs from 'fs-extra';
import path from 'path';
import {linkify} from '../linkify';
import {VERSIONED_DOCS_DIR, CURRENT_VERSION_NAME} from '../../constants';
import type {
DocsMarkdownOption,
SourceToPermalink,
DocBrokenMarkdownLink,
} from '../../types';
import type {VersionMetadata} from '@docusaurus/plugin-content-docs';
import {VERSIONED_DOCS_DIR, CURRENT_VERSION_NAME} from '../../constants';
function createFakeVersion({
versionName,

View file

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import type {DocsMarkdownOption} from '../types';
import {replaceMarkdownLinks, getContentPathList} from '@docusaurus/utils';
import type {DocsMarkdownOption} from '../types';
function getVersion(filePath: string, options: DocsMarkdownOption) {
const versionFound = options.versionsMetadata.find((version) =>

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import type {PluginOptions, Options} from '@docusaurus/plugin-content-docs';
import logger from '@docusaurus/logger';
import {
Joi,
RemarkPluginsSchema,
@ -14,15 +14,14 @@ import {
URISchema,
} from '@docusaurus/utils-validation';
import {GlobExcludeDefault} from '@docusaurus/utils';
import type {OptionValidationContext} from '@docusaurus/types';
import logger from '@docusaurus/logger';
import admonitions from 'remark-admonitions';
import {DefaultSidebarItemsGenerator} from './sidebars/generator';
import {
DefaultNumberPrefixParser,
DisabledNumberPrefixParser,
} from './numberPrefix';
import type {OptionValidationContext} from '@docusaurus/types';
import type {PluginOptions, Options} from '@docusaurus/plugin-content-docs';
export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id' | 'sidebarPath'> = {
path: 'docs', // Path to data on filesystem, relative to site dir.

View file

@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import {createDocsByIdIndex} from './docs';
import type {VersionTag} from './types';
import type {
SidebarItemDoc,
@ -24,8 +26,6 @@ import type {
DocMetadata,
LoadedVersion,
} from '@docusaurus/plugin-content-docs';
import _ from 'lodash';
import {createDocsByIdIndex} from './docs';
export function toSidebarsProp(loadedVersion: LoadedVersion): PropSidebars {
const docsById = createDocsByIdIndex(loadedVersion.docs);

View file

@ -5,15 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/
import type {PluginContentLoadedActions, RouteConfig} from '@docusaurus/types';
import logger from '@docusaurus/logger';
import {docuHash, createSlugger} from '@docusaurus/utils';
import {toVersionMetadataProp} from './props';
import type {PluginContentLoadedActions, RouteConfig} from '@docusaurus/types';
import type {FullVersion} from './types';
import type {
CategoryGeneratedIndexMetadata,
DocMetadata,
} from '@docusaurus/plugin-content-docs';
import {toVersionMetadataProp} from './props';
import logger from '@docusaurus/logger';
export async function createCategoryGeneratedIndexRoutes({
version,

View file

@ -6,4 +6,5 @@ This part is very complicated and hard to navigate. Sidebars are loaded through
2. **Normalization**. The shorthands are expanded. This step is very lenient about the sidebars' shapes. Returns `NormalizedSidebars`.
3. **Validation**. The normalized sidebars are validated. This step happens after normalization, because the normalized sidebars are easier to validate, and allows us to repeatedly validate & generate in the future.
4. **Generation**. This step is done through the "processor" (naming is hard). The `autogenerated` items are unwrapped. In the future, steps 3 and 4 may be repeatedly done until all autogenerated items are unwrapped. Returns `ProcessedSidebars`.
- **Important**: this step should only care about unwrapping autogenerated items, not filtering them, writing additional metadata, applying defaults, etc.—everything will be handled in the post-processor. Important because the generator is exposed to the end-user and we want it to be easy to be reasoned about.
5. **Post-processing**. Defaults are applied (collapsed states), category links are resolved, empty categories are flattened. Returns `Sidebars`.

View file

@ -0,0 +1,23 @@
{
"docs": [
{
"label": "Tutorials",
"type": "category",
"items": [
{
"type": "autogenerated",
"dirName": "tutorials"
}
]
},
{
"label": "index-only",
"type": "category",
"link": {
"type": "doc",
"id": "tutorials/tutorial-basics"
},
"items": []
}
]
}

View file

@ -0,0 +1,60 @@
{
"sidebar": [
"draft1",
{
"type": "category",
"label": "all drafts",
"items": [
"draft2",
"draft3"
]
},
{
"type": "category",
"label": "all drafts",
"link": {
"type": "generated-index"
},
"items": [
"draft2",
"draft3"
]
},
{
"type": "category",
"label": "all drafts",
"link": {
"type": "doc",
"id": "draft1"
},
"items": [
"draft2",
"draft3"
]
},
{
"type": "category",
"label": "index not draft",
"link": {
"type": "doc",
"id": "not-draft"
},
"items": [
"draft2",
"draft3"
]
},
{
"type": "category",
"label": "subitem not draft",
"link": {
"type": "doc",
"id": "draft1"
},
"items": [
"not-draft",
"draft3"
]
}
]
}

View file

@ -1,5 +1,56 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`loadSidebars loads sidebars with index-only categories 1`] = `
{
"docs": [
{
"collapsed": true,
"collapsible": true,
"items": [
{
"id": "tutorials/tutorial-basics",
"label": "tutorial-basics",
"type": "doc",
},
],
"label": "Tutorials",
"link": undefined,
"type": "category",
},
{
"id": "tutorials/tutorial-basics",
"label": "index-only",
"type": "doc",
},
],
}
`;
exports[`loadSidebars loads sidebars with interspersed draft items 1`] = `
{
"sidebar": [
{
"id": "not-draft",
"label": "index not draft",
"type": "doc",
},
{
"collapsed": true,
"collapsible": true,
"items": [
{
"id": "not-draft",
"type": "doc",
},
],
"label": "subitem not draft",
"link": undefined,
"type": "category",
},
],
}
`;
exports[`loadSidebars sidebars link 1`] = `
{
"docs": [

View file

@ -60,14 +60,21 @@ exports[`postProcess corrects collapsed state inconsistencies 3`] = `
}
`;
exports[`postProcess transforms category without subitems 1`] = `
exports[`postProcess filters draft items 1`] = `
{
"sidebar": [
{
"href": "version/generated/permalink",
"id": "another",
"label": "Category",
"type": "link",
"type": "doc",
},
],
}
`;
exports[`postProcess transforms category without subitems 1`] = `
{
"sidebar": [
{
"id": "doc ID",
"label": "Category 2",

View file

@ -7,9 +7,9 @@
import {jest} from '@jest/globals';
import {DefaultSidebarItemsGenerator} from '../generator';
import type {SidebarItemsGenerator} from '../types';
import {DefaultNumberPrefixParser} from '../../numberPrefix';
import {isCategoryIndex} from '../../docs';
import type {SidebarItemsGenerator} from '../types';
describe('DefaultSidebarItemsGenerator', () => {
function testDefaultSidebarItemsGenerator(

View file

@ -7,9 +7,10 @@
import {jest} from '@jest/globals';
import path from 'path';
import {createSlugger} from '@docusaurus/utils';
import {loadSidebars, DisabledSidebars} from '../index';
import type {SidebarProcessorParams} from '../types';
import {DefaultSidebarItemsGenerator} from '../generator';
import type {SidebarProcessorParams} from '../types';
import type {DocMetadata} from '@docusaurus/plugin-content-docs';
describe('loadSidebars', () => {
@ -27,6 +28,7 @@ describe('loadSidebars', () => {
],
drafts: [],
version: {
path: 'version',
contentPath: path.join(fixtureDir, 'docs'),
contentPathLocalized: path.join(fixtureDir, 'docs'),
},
@ -124,6 +126,32 @@ describe('loadSidebars', () => {
expect(result).toMatchSnapshot();
});
it('loads sidebars with index-only categories', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-category-index.json');
const result = await loadSidebars(sidebarPath, {
...params,
docs: [
{
id: 'tutorials/tutorial-basics',
source: '@site/docs/tutorials/tutorial-basics/index.md',
sourceDirName: 'tutorials/tutorial-basics',
frontMatter: {},
},
],
});
expect(result).toMatchSnapshot();
});
it('loads sidebars with interspersed draft items', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-drafts.json');
const result = await loadSidebars(sidebarPath, {
...params,
drafts: [{id: 'draft1'}, {id: 'draft2'}, {id: 'draft3'}],
categoryLabelSlugger: createSlugger(),
});
expect(result).toMatchSnapshot();
});
it('duplicate category metadata files', async () => {
const sidebarPath = path.join(
fixtureDir,

View file

@ -35,6 +35,7 @@ describe('postProcess', () => {
{
sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true},
version: {path: 'version'},
drafts: [],
},
);
@ -54,6 +55,7 @@ describe('postProcess', () => {
{
sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true},
version: {path: 'version'},
drafts: [],
},
);
}).toThrowErrorMatchingInlineSnapshot(
@ -79,6 +81,7 @@ describe('postProcess', () => {
{
sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true},
version: {path: 'version'},
drafts: [],
},
),
).toMatchSnapshot();
@ -99,6 +102,7 @@ describe('postProcess', () => {
{
sidebarOptions: {sidebarCollapsed: false, sidebarCollapsible: false},
version: {path: 'version'},
drafts: [],
},
),
).toMatchSnapshot();
@ -118,6 +122,37 @@ describe('postProcess', () => {
{
sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: false},
version: {path: 'version'},
drafts: [],
},
),
).toMatchSnapshot();
});
it('filters draft items', () => {
expect(
postProcessSidebars(
{
sidebar: [
{
type: 'category',
label: 'Category',
items: [{type: 'doc', id: 'foo'}],
},
{
type: 'category',
label: 'Category',
link: {
type: 'doc',
id: 'another',
},
items: [{type: 'doc', id: 'foo'}],
},
],
},
{
sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true},
version: {path: 'version'},
drafts: [{id: 'foo', unversionedId: 'foo'}],
},
),
).toMatchSnapshot();

View file

@ -6,7 +6,11 @@
*/
import {jest} from '@jest/globals';
import {createSlugger} from '@docusaurus/utils';
import {processSidebars} from '../processor';
import {DefaultSidebarItemsGenerator} from '../generator';
import {DefaultNumberPrefixParser} from '../../numberPrefix';
import {isCategoryIndex} from '../../docs';
import type {
SidebarItem,
SidebarItemsGenerator,
@ -16,11 +20,7 @@ import type {
CategoryMetadataFile,
ProcessedSidebars,
} from '../types';
import {DefaultSidebarItemsGenerator} from '../generator';
import {createSlugger} from '@docusaurus/utils';
import type {VersionMetadata} from '@docusaurus/plugin-content-docs';
import {DefaultNumberPrefixParser} from '../../numberPrefix';
import {isCategoryIndex} from '../../docs';
describe('processSidebars', () => {
function createStaticSidebarItemGenerator(

View file

@ -5,6 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {addTrailingSlash} from '@docusaurus/utils';
import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
import type {
SidebarItemDoc,
SidebarItemsGenerator,
@ -13,11 +18,6 @@ import type {
NormalizedSidebarItem,
SidebarItemCategoryLinkConfig,
} from './types';
import _ from 'lodash';
import {addTrailingSlash} from '@docusaurus/utils';
import logger from '@docusaurus/logger';
import path from 'path';
import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
const BreadcrumbSeparator = '/';

View file

@ -6,19 +6,19 @@
*/
import fs from 'fs-extra';
import path from 'path';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {Globby} from '@docusaurus/utils';
import Yaml from 'js-yaml';
import combinePromises from 'combine-promises';
import importFresh from 'import-fresh';
import type {SidebarsConfig, Sidebars, SidebarProcessorParams} from './types';
import {validateSidebars, validateCategoryMetadataFile} from './validation';
import {normalizeSidebars} from './normalization';
import {processSidebars} from './processor';
import {postProcessSidebars} from './postProcessor';
import path from 'path';
import {Globby} from '@docusaurus/utils';
import logger from '@docusaurus/logger';
import type {PluginOptions} from '@docusaurus/plugin-content-docs';
import Yaml from 'js-yaml';
import _ from 'lodash';
import combinePromises from 'combine-promises';
import type {SidebarsConfig, Sidebars, SidebarProcessorParams} from './types';
export const DefaultSidebars: SidebarsConfig = {
defaultSidebar: [

View file

@ -5,6 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {isCategoriesShorthand} from './utils';
import type {
NormalizedSidebarItem,
NormalizedSidebar,
@ -16,9 +19,6 @@ import type {
SidebarsConfig,
NormalizedSidebarItemCategory,
} from './types';
import {isCategoriesShorthand} from './utils';
import _ from 'lodash';
import logger from '@docusaurus/logger';
function normalizeCategoriesShorthand(
sidebar: SidebarCategoriesShorthand,

View file

@ -5,7 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import {normalizeUrl} from '@docusaurus/utils';
import {getDocIds} from '../docs';
import type {
SidebarItem,
Sidebars,
@ -15,12 +17,18 @@ import type {
ProcessedSidebars,
SidebarItemCategoryLink,
} from './types';
import _ from 'lodash';
type SidebarPostProcessorParams = SidebarProcessorParams & {
draftIds: Set<string>;
};
function normalizeCategoryLink(
category: ProcessedSidebarItemCategory,
params: SidebarProcessorParams,
params: SidebarPostProcessorParams,
): SidebarItemCategoryLink | undefined {
if (category.link?.type === 'doc' && params.draftIds.has(category.link.id)) {
return undefined;
}
if (category.link?.type === 'generated-index') {
// Default slug logic can be improved
const getDefaultSlug = () =>
@ -38,37 +46,42 @@ function normalizeCategoryLink(
function postProcessSidebarItem(
item: ProcessedSidebarItem,
params: SidebarProcessorParams,
): SidebarItem {
params: SidebarPostProcessorParams,
): SidebarItem | null {
if (item.type === 'category') {
// Fail-fast if there's actually no subitems, no because all subitems are
// drafts. This is likely a configuration mistake.
if (item.items.length === 0 && !item.link) {
throw new Error(
`Sidebar category ${item.label} has neither any subitem nor a link. This makes this item not able to link to anything.`,
);
}
const category = {
...item,
collapsed: item.collapsed ?? params.sidebarOptions.sidebarCollapsed,
collapsible: item.collapsible ?? params.sidebarOptions.sidebarCollapsible,
link: normalizeCategoryLink(item, params),
items: item.items.map((subItem) =>
postProcessSidebarItem(subItem, params),
),
items: item.items
.map((subItem) => postProcessSidebarItem(subItem, params))
.filter((v): v is SidebarItem => Boolean(v)),
};
// If the current category doesn't have subitems, we render a normal link
// instead.
if (category.items.length === 0) {
if (!category.link) {
throw new Error(
`Sidebar category ${item.label} has neither any subitem nor a link. This makes this item not able to link to anything.`,
);
// Doesn't make sense to render an empty generated index page, so we
// filter the entire category out as well.
if (
!category.link ||
category.link.type === 'generated-index' ||
params.draftIds.has(category.link.id)
) {
return null;
}
return category.link.type === 'doc'
? {
type: 'doc',
label: category.label,
id: category.link.id,
}
: {
type: 'link',
label: category.label,
href: category.link.permalink,
};
return {
type: 'doc',
label: category.label,
id: category.link.id,
};
}
// A non-collapsible category can't be collapsed!
if (category.collapsible === false) {
@ -76,6 +89,12 @@ function postProcessSidebarItem(
}
return category;
}
if (
(item.type === 'doc' || item.type === 'ref') &&
params.draftIds.has(item.id)
) {
return null;
}
return item;
}
@ -83,7 +102,11 @@ export function postProcessSidebars(
sidebars: ProcessedSidebars,
params: SidebarProcessorParams,
): Sidebars {
const draftIds = new Set(params.drafts.flatMap(getDocIds));
return _.mapValues(sidebars, (sidebar) =>
sidebar.map((item) => postProcessSidebarItem(item, params)),
sidebar
.map((item) => postProcessSidebarItem(item, {...params, draftIds}))
.filter((v): v is SidebarItem => Boolean(v)),
);
}

View file

@ -5,6 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import combinePromises from 'combine-promises';
import {DefaultSidebarItemsGenerator} from './generator';
import {validateSidebars} from './validation';
import {isCategoryIndex} from '../docs';
import type {
DocMetadataBase,
VersionMetadata,
@ -22,11 +27,6 @@ import type {
SidebarProcessorParams,
CategoryMetadataFile,
} from './types';
import {DefaultSidebarItemsGenerator} from './generator';
import {validateSidebars} from './validation';
import _ from 'lodash';
import combinePromises from 'combine-promises';
import {getDocIds, isCategoryIndex} from '../docs';
function toSidebarItemsGeneratorDoc(
doc: DocMetadataBase,
@ -55,8 +55,7 @@ async function processSidebar(
categoriesMetadata: {[filePath: string]: CategoryMetadataFile},
params: SidebarProcessorParams,
): Promise<ProcessedSidebar> {
const {sidebarItemsGenerator, numberPrefixParser, docs, drafts, version} =
params;
const {sidebarItemsGenerator, numberPrefixParser, docs, version} = params;
// Just a minor lazy transformation optimization
const getSidebarItemsGeneratorDocsAndVersion = _.memoize(() => ({
@ -82,19 +81,6 @@ async function processSidebar(
return processItems(generatedItems);
}
const draftIds = new Set(drafts.flatMap(getDocIds));
const isDraftItem = (item: NormalizedSidebarItem): boolean => {
if (item.type === 'doc' || item.type === 'ref') {
return draftIds.has(item.id);
}
// If a category only contains draft items, it should be filtered entirely.
if (item.type === 'category') {
return item.items.every(isDraftItem);
}
return false;
};
async function processItem(
item: NormalizedSidebarItem,
): Promise<ProcessedSidebarItem[]> {
@ -102,7 +88,7 @@ async function processSidebar(
return [
{
...item,
items: await processItems(item.items),
items: (await Promise.all(item.items.map(processItem))).flat(),
},
];
}
@ -115,9 +101,7 @@ async function processSidebar(
async function processItems(
items: NormalizedSidebarItem[],
): Promise<ProcessedSidebarItem[]> {
return (
await Promise.all(items.filter((i) => !isDraftItem(i)).map(processItem))
).flat();
return (await Promise.all(items.map(processItem))).flat();
}
const processedSidebar = await processItems(unprocessedSidebar);

View file

@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import {toMessageRelativeFilePath} from '@docusaurus/utils';
import type {
Sidebars,
Sidebar,
@ -18,9 +20,6 @@ import type {
SidebarItemCategoryWithGeneratedIndex,
SidebarNavigationItem,
} from './types';
import _ from 'lodash';
import {toMessageRelativeFilePath} from '@docusaurus/utils';
import type {
DocMetadataBase,
PropNavigationLink,

View file

@ -5,10 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import {groupTaggedItems} from '@docusaurus/utils';
import type {VersionTags} from './types';
import type {DocMetadata} from '@docusaurus/plugin-content-docs';
import _ from 'lodash';
export function getVersionTags(docs: DocMetadata[]): VersionTags {
const groups = groupTaggedItems(docs, (doc) => doc.tags);

View file

@ -5,6 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import {mergeTranslations} from '@docusaurus/utils';
import {CURRENT_VERSION_NAME} from './constants';
import {
collectSidebarCategories,
transformSidebarItems,
collectSidebarLinks,
} from './sidebars/utils';
import type {
LoadedVersion,
LoadedContent,
@ -15,20 +23,11 @@ import type {
SidebarItemCategoryLink,
Sidebars,
} from './sidebars/types';
import _ from 'lodash';
import {
collectSidebarCategories,
transformSidebarItems,
collectSidebarLinks,
} from './sidebars/utils';
import type {
TranslationFileContent,
TranslationFile,
TranslationMessage,
} from '@docusaurus/types';
import {mergeTranslations} from '@docusaurus/utils';
import {CURRENT_VERSION_NAME} from './constants';
function getVersionFileName(versionName: string): string {
if (versionName === CURRENT_VERSION_NAME) {

View file

@ -7,9 +7,9 @@
import {jest} from '@jest/globals';
import path from 'path';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import {readVersionsMetadata} from '../index';
import {DEFAULT_OPTIONS} from '../../options';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import type {I18n} from '@docusaurus/types';
import type {
PluginOptions,

View file

@ -7,6 +7,7 @@
import path from 'path';
import fs from 'fs-extra';
import {getPluginI18nPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import {
VERSIONS_JSON_FILE,
VERSIONED_DOCS_DIR,
@ -14,7 +15,6 @@ import {
CURRENT_VERSION_NAME,
} from '../constants';
import {validateVersionNames} from './validation';
import {getPluginI18nPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import type {
PluginOptions,
VersionMetadata,

View file

@ -6,8 +6,8 @@
*/
import path from 'path';
import {CURRENT_VERSION_NAME} from '../constants';
import {normalizeUrl, posixPath} from '@docusaurus/utils';
import {CURRENT_VERSION_NAME} from '../constants';
import {validateVersionsOptions} from './validation';
import {
getDocsDirPathLocalized,

View file

@ -25,7 +25,7 @@
"fs-extra": "^10.1.0",
"remark-admonitions": "^1.2.1",
"tslib": "^2.4.0",
"webpack": "^5.72.0"
"webpack": "^5.72.1"
},
"devDependencies": {
"@docusaurus/types": "2.0.0-beta.20"

View file

@ -7,10 +7,10 @@
import path from 'path';
import {loadContext} from '@docusaurus/core/lib/server';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import pluginContentPages from '../index';
import {validateOptions} from '../options';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
describe('docusaurus-plugin-content-pages', () => {
it('loads simple pages', async () => {

View file

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import {validateOptions, DEFAULT_OPTIONS} from '../options';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {validateOptions, DEFAULT_OPTIONS} from '../options';
import type {Options} from '@docusaurus/plugin-content-pages';
function testValidate(options: Options) {

View file

@ -21,10 +21,10 @@ import {
DEFAULT_PLUGIN_ID,
parseMarkdownString,
} from '@docusaurus/utils';
import type {LoadContext, Plugin} from '@docusaurus/types';
import admonitions from 'remark-admonitions';
import {validatePageFrontMatter} from './frontMatter';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {PagesContentPaths} from './types';
import type {
PluginOptions,

View file

@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import type {PluginOptions, Options} from '@docusaurus/plugin-content-pages';
import {
Joi,
RemarkPluginsSchema,
@ -14,6 +13,7 @@ import {
} from '@docusaurus/utils-validation';
import {GlobExcludeDefault} from '@docusaurus/utils';
import type {OptionValidationContext} from '@docusaurus/types';
import type {PluginOptions, Options} from '@docusaurus/plugin-content-pages';
export const DEFAULT_OPTIONS: PluginOptions = {
path: 'src/pages', // Path to data on filesystem, relative to site dir.

View file

@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import type {LoadContext, Plugin} from '@docusaurus/types';
import {docuHash, normalizeUrl, posixPath} from '@docusaurus/utils';
import path from 'path';
import {docuHash, normalizeUrl, posixPath} from '@docusaurus/utils';
import type {LoadContext, Plugin} from '@docusaurus/types';
export default function pluginDebug({
siteConfig: {baseUrl},

View file

@ -6,12 +6,10 @@
*/
import React from 'react';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import DebugLayout from '@theme/DebugLayout';
import DebugJsonView from '@theme/DebugJsonView';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
export default function DebugMetadata(): JSX.Element {
const {siteConfig} = useDocusaurusContext();
return (

View file

@ -6,10 +6,9 @@
*/
import React from 'react';
import useGlobalData from '@docusaurus/useGlobalData';
import DebugLayout from '@theme/DebugLayout';
import DebugJsonView from '@theme/DebugJsonView';
import useGlobalData from '@docusaurus/useGlobalData';
export default function DebugMetadata(): JSX.Element {
const globalData = useGlobalData();

View file

@ -6,9 +6,8 @@
*/
import React from 'react';
import DebugLayout from '@theme/DebugLayout';
import registry from '@generated/registry';
import DebugLayout from '@theme/DebugLayout';
import styles from './styles.module.css';
export default function DebugRegistry(): JSX.Element {

View file

@ -6,10 +6,9 @@
*/
import React from 'react';
import routes from '@generated/routes';
import DebugLayout from '@theme/DebugLayout';
import DebugJsonView from '@theme/DebugJsonView';
import routes from '@generated/routes';
import styles from './styles.module.css';
export default function DebugRoutes(): JSX.Element {

View file

@ -6,9 +6,8 @@
*/
import React from 'react';
import DebugLayout from '@theme/DebugLayout';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import DebugLayout from '@theme/DebugLayout';
import styles from './styles.module.css';
export default function DebugMetadata(): JSX.Element {

View file

@ -30,7 +30,7 @@
"react-waypoint": "^10.1.0",
"sharp": "^0.30.4",
"tslib": "^2.4.0",
"webpack": "^5.72.0"
"webpack": "^5.72.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.20",

View file

@ -5,14 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
import {Joi} from '@docusaurus/utils-validation';
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
import type {
LoadContext,
Plugin,
OptionValidationContext,
} from '@docusaurus/types';
import type {PluginOptions} from '@docusaurus/plugin-ideal-image';
import {Joi} from '@docusaurus/utils-validation';
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
export default function pluginIdealImage(
context: LoadContext,

View file

@ -29,10 +29,10 @@
"@docusaurus/utils-validation": "2.0.0-beta.20",
"babel-loader": "^8.2.5",
"clsx": "^1.1.1",
"core-js": "^3.22.4",
"core-js": "^3.22.5",
"terser-webpack-plugin": "^5.3.1",
"tslib": "^2.4.0",
"webpack": "^5.72.0",
"webpack": "^5.72.1",
"webpack-merge": "^5.8.0",
"workbox-build": "^6.5.3",
"workbox-precaching": "^6.5.3",

Some files were not shown because too many files have changed in this diff Show more