Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(v2): fix inconsistent error output in swizzle command #3725

Merged
merged 1 commit into from
Nov 12, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 145 additions & 139 deletions packages/docusaurus/src/commands/swizzle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function themeComponents(themePath: string, plugin: Plugin<unknown>): string {
}

return `
${chalk.cyan('Theme Components available for swizzle')}
${chalk.cyan('Theme components available for swizzle')}

${chalk.green('green =>')} recommended: lower breaking change risk
${chalk.red('red =>')} internal: higher breaking change risk
Expand All @@ -99,7 +99,7 @@ ${components.join('\n')}
`;
}

function formatedThemeNames(themeNames: string[]): string {
function formattedThemeNames(themeNames: string[]): string {
return `Themes available for swizzle:\n${themeNames.join('\n')}`;
}

Expand Down Expand Up @@ -142,152 +142,158 @@ export default async function swizzle(
? plugins[index].getTypeScriptThemePath
: plugins[index].getThemePath,
);

if (!themeName) {
console.log(formatedThemeNames(themeNames));
} else {
let pluginModule;
try {
pluginModule = importFresh(themeName) as (
context: LoadContext,
) => Plugin<unknown>;
} catch {
let suggestion;
themeNames.forEach((name) => {
if (leven(name, themeName) < 4) {
suggestion = name;
}
});
throw new Error(
`Theme ${themeName} not found. ${
suggestion
? `Did you mean "${suggestion}" ?`
: formatedThemeNames(themeNames)
}`,
);
}
const plugin = pluginModule.default ?? pluginModule;
const validateOptions =
pluginModule.default?.validateOptions ?? pluginModule.validateOptions;
let pluginOptions;
const resolvedThemeName = require.resolve(themeName);
// find the plugin from list of plugin and get options if specified
pluginConfigs.forEach((pluginConfig) => {
// plugin can be a [string], [string,object] or string.
if (Array.isArray(pluginConfig)) {
if (require.resolve(pluginConfig[0]) === resolvedThemeName) {
if (pluginConfig.length === 2) {
const [, options] = pluginConfig;
pluginOptions = options;
}
}
console.log(formattedThemeNames(themeNames));
process.exit(1);
}

let pluginModule;
try {
pluginModule = importFresh(themeName) as (
context: LoadContext,
) => Plugin<unknown>;
} catch {
let suggestion;
themeNames.forEach((name) => {
if (leven(name, themeName) < 4) {
suggestion = name;
}
});
if (validateOptions) {
// normilize options
const normalizedOptions = validateOptions({
validate: normalizePluginOptions,
options: pluginOptions,
});
pluginOptions = normalizedOptions;
}
const pluginInstance = plugin(context, pluginOptions);
const themePath = typescript
? pluginInstance.getTypeScriptThemePath?.()
: pluginInstance.getThemePath?.();
const components = getComponentName(
themePath,
pluginModule,
Boolean(danger),
chalk.red(
`Theme ${themeName} not found. ${
suggestion
? `Did you mean "${suggestion}" ?`
: formattedThemeNames(themeNames)
}`,
);
if (componentName) {
const formatedComponentName = formatComponentName(componentName);
const isComponentExists = components.find(
(component) => component === formatedComponentName,
);
let mostSuitableComponent = componentName;
if (!isComponentExists) {
let mostSuitableMatch = componentName;
let score = formatedComponentName.length;
components.forEach((component) => {
if (component.toLowerCase() === formatedComponentName.toLowerCase()) {
// may be components with same lowercase key, try to match closest component
const currentScore = leven(formatedComponentName, component);
if (currentScore < score) {
score = currentScore;
mostSuitableMatch = component;
}
}
});
if (mostSuitableMatch) {
mostSuitableComponent = mostSuitableMatch;
console.log(
chalk.red(`Component "${componentName}" doesn't exists.`),
chalk.yellow(
`"${mostSuitableComponent}" is swizzled instead of "${componentName}".`,
),
);
process.exit(1);
}

const plugin = pluginModule.default ?? pluginModule;
const validateOptions =
pluginModule.default?.validateOptions ?? pluginModule.validateOptions;
let pluginOptions;
const resolvedThemeName = require.resolve(themeName);
// find the plugin from list of plugin and get options if specified
pluginConfigs.forEach((pluginConfig) => {
// plugin can be a [string], [string,object] or string.
if (Array.isArray(pluginConfig)) {
if (require.resolve(pluginConfig[0]) === resolvedThemeName) {
if (pluginConfig.length === 2) {
const [, options] = pluginConfig;
pluginOptions = options;
}
}
let fromPath = themePath;
if (fromPath) {
let toPath = path.resolve(siteDir, THEME_PATH);
fromPath = path.join(fromPath, mostSuitableComponent);
toPath = path.join(toPath, mostSuitableComponent);
// Handle single TypeScript/JavaScript file only.
// E.g: if <fromPath> does not exist, we try to swizzle <fromPath>.(ts|tsx|js) instead
if (!fs.existsSync(fromPath)) {
if (fs.existsSync(`${fromPath}.ts`)) {
[fromPath, toPath] = [`${fromPath}.ts`, `${toPath}.ts`];
} else if (fs.existsSync(`${fromPath}.tsx`)) {
[fromPath, toPath] = [`${fromPath}.tsx`, `${toPath}.tsx`];
} else if (fs.existsSync(`${fromPath}.js`)) {
[fromPath, toPath] = [`${fromPath}.js`, `${toPath}.js`];
} else {
let suggestion;
components.forEach((name) => {
if (leven(name, mostSuitableComponent) < 3) {
suggestion = name;
}
});
throw new Error(
`Component ${mostSuitableComponent} not found.${
suggestion
? ` Did you mean "${suggestion}"?`
: `${themeComponents(themePath, pluginModule)}`
}`,
);
}
}
if (!components.includes(mostSuitableComponent) && !danger) {
throw new Error(
`${mostSuitableComponent} is an internal component, and have a higher breaking change probability. If you want to swizzle it, use the "--danger" flag.`,
);
}
});

if (validateOptions) {
pluginOptions = validateOptions({
validate: normalizePluginOptions,
options: pluginOptions,
});
}

const pluginInstance = plugin(context, pluginOptions);
const themePath = typescript
? pluginInstance.getTypeScriptThemePath?.()
: pluginInstance.getThemePath?.();

if (!themePath) {
console.warn(
chalk.yellow(
typescript
? `${themeName} does not provide TypeScript theme code via "getTypeScriptThemePath()".`
: `${themeName} does not provide any theme code.`,
),
);
process.exit(1);
}

if (!componentName) {
console.warn(themeComponents(themePath, pluginModule));
process.exit(1);
}

const components = getComponentName(themePath, pluginModule, Boolean(danger));
const formattedComponentName = formatComponentName(componentName);
const isComponentExists = components.find(
(component) => component === formattedComponentName,
);
let mostSuitableComponent = componentName;

if (!isComponentExists) {
let mostSuitableMatch = componentName;
let score = formattedComponentName.length;
components.forEach((component) => {
if (component.toLowerCase() === formattedComponentName.toLowerCase()) {
// may be components with same lowercase key, try to match closest component
const currentScore = leven(formattedComponentName, component);
if (currentScore < score) {
score = currentScore;
mostSuitableMatch = component;
}
await fs.copy(fromPath, toPath);

const relativeDir = path.relative(process.cwd(), toPath);
const fromMsg = chalk.blue(
mostSuitableComponent
? `${themeName} ${chalk.yellow(mostSuitableComponent)}`
: themeName,
);
const toMsg = chalk.cyan(relativeDir);
console.log(
`\n${chalk.green('Success!')} Copied ${fromMsg} to ${toMsg}.\n`,
);
} else if (typescript) {
console.warn(
chalk.yellow(
`${themeName} does not provide TypeScript theme code via getTypeScriptThemePath().`,
),
);
} else {
console.warn(
chalk.yellow(`${themeName} does not provide any theme code.`),
);
}
});

if (mostSuitableMatch !== componentName) {
mostSuitableComponent = mostSuitableMatch;
console.log(
chalk.red(`Component "${componentName}" doesn't exists.`),
chalk.yellow(
`"${mostSuitableComponent}" is swizzled instead of "${componentName}".`,
),
);
}
}

let fromPath = path.join(themePath, mostSuitableComponent);
let toPath = path.resolve(siteDir, THEME_PATH, mostSuitableComponent);
// Handle single TypeScript/JavaScript file only.
// E.g: if <fromPath> does not exist, we try to swizzle <fromPath>.(ts|tsx|js) instead
if (!fs.existsSync(fromPath)) {
if (fs.existsSync(`${fromPath}.ts`)) {
[fromPath, toPath] = [`${fromPath}.ts`, `${toPath}.ts`];
} else if (fs.existsSync(`${fromPath}.tsx`)) {
[fromPath, toPath] = [`${fromPath}.tsx`, `${toPath}.tsx`];
} else if (fs.existsSync(`${fromPath}.js`)) {
[fromPath, toPath] = [`${fromPath}.js`, `${toPath}.js`];
} else {
console.log(themeComponents(themePath, pluginModule));
let suggestion;
components.forEach((name) => {
if (leven(name, mostSuitableComponent) < 3) {
suggestion = name;
}
});
console.warn(chalk.red(`Component ${mostSuitableComponent} not found.`));
console.warn(
suggestion
? `Did you mean "${suggestion}"?`
: `${themeComponents(themePath, pluginModule)}`,
);
process.exit(1);
}
}

if (!components.includes(mostSuitableComponent) && !danger) {
console.warn(
chalk.red(
`${mostSuitableComponent} is an internal component, and have a higher breaking change probability. If you want to swizzle it, use the "--danger" flag.`,
),
);
process.exit(1);
}

await fs.copy(fromPath, toPath);

const relativeDir = path.relative(process.cwd(), toPath);
const fromMsg = chalk.blue(
mostSuitableComponent
? `${themeName} ${chalk.yellow(mostSuitableComponent)}`
: themeName,
);
const toMsg = chalk.cyan(relativeDir);

console.log(`\n${chalk.green('Success!')} Copied ${fromMsg} to ${toMsg}.\n`);
}