Skip to content

Commit

Permalink
fix: allow swizzling a component's parent folder (#7225)
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber authored Apr 22, 2022
1 parent f7c995b commit c3add31
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 27 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ describe('readComponentNames', () => {
Components.ComponentInSubFolder,
Components.Sibling,
Components.FirstLevelComponent,
Components.NoIndexComp1,
Components.NoIndexComp2,
Components.NoIndexSubComp,
]);
});
});
Expand Down Expand Up @@ -66,6 +69,11 @@ describe('getThemeComponents', () => {
Components.ComponentInSubFolder,
Components.Sibling,
Components.FirstLevelComponent,
Components.NoIndex,
Components.NoIndexComp1,
Components.NoIndexComp2,
Components.NoIndexSub,
Components.NoIndexSubComp,
]);
});

Expand Down Expand Up @@ -155,6 +163,39 @@ describe('getThemeComponents', () => {
expect(
themeComponents.getActionStatus(Components.FirstLevelComponent, 'eject'),
).toBe('unsafe');

expect(
themeComponents.getActionStatus(Components.NoIndexComp1, 'wrap'),
).toBe('unsafe');
expect(
themeComponents.getActionStatus(Components.NoIndexComp1, 'eject'),
).toBe('unsafe');
expect(
themeComponents.getActionStatus(Components.NoIndexComp2, 'wrap'),
).toBe('unsafe');
expect(
themeComponents.getActionStatus(Components.NoIndexComp2, 'eject'),
).toBe('unsafe');
expect(
themeComponents.getActionStatus(Components.NoIndexSubComp, 'wrap'),
).toBe('unsafe');
expect(
themeComponents.getActionStatus(Components.NoIndexSubComp, 'eject'),
).toBe('unsafe');

// Intermediate folders are not real components: forbidden to wrap!
expect(themeComponents.getActionStatus(Components.NoIndex, 'wrap')).toBe(
'forbidden',
);
expect(themeComponents.getActionStatus(Components.NoIndex, 'eject')).toBe(
'unsafe',
);
expect(themeComponents.getActionStatus(Components.NoIndexSub, 'wrap')).toBe(
'forbidden',
);
expect(
themeComponents.getActionStatus(Components.NoIndexSub, 'eject'),
).toBe('unsafe');
});

it('isSafeAction', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export const Components = {
Sibling: 'ComponentInFolder/Sibling',
ComponentInFolder: 'ComponentInFolder',
FirstLevelComponent: 'FirstLevelComponent',
NoIndex: 'NoIndex',
NoIndexComp1: 'NoIndex/NoIndexComp1',
NoIndexComp2: 'NoIndex/NoIndexComp2',
NoIndexSub: 'NoIndex/NoIndexSub',
NoIndexSubComp: 'NoIndex/NoIndexSub/NoIndexSubComp',
};

export async function createTempSiteDir(): Promise<string> {
Expand Down
65 changes: 58 additions & 7 deletions packages/docusaurus/src/commands/swizzle/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,35 @@ export type ThemeComponents = {
const formatComponentName = (componentName: string): string =>
componentName.replace(/[/\\]index\.[jt]sx?/, '').replace(/\.[jt]sx?/, '');

function sortComponentNames(componentNames: string[]): string[] {
return componentNames.sort(); // Algo may change?
}

/**
* Expand a list of components to include and return parent folders.
* If a folder is not directly a component (no Folder/index.tsx file),
* we still want to be able to swizzle --eject that folder.
* See https://github.com/facebook/docusaurus/pull/7175#issuecomment-1103757218
*
* @param componentNames the original list of component names
*/
function getMissingIntermediateComponentFolderNames(
componentNames: string[],
): string[] {
function getAllIntermediatePaths(componentName: string): string[] {
const paths = componentName.split('/');
return _.range(1, paths.length + 1).map((i) => paths.slice(0, i).join('/'));
}

const expandedComponentNames = _.uniq(
componentNames.flatMap((componentName) =>
getAllIntermediatePaths(componentName),
),
);

return _.difference(expandedComponentNames, componentNames);
}

const skipReadDirNames = ['__test__', '__tests__', '__mocks__', '__fixtures__'];

export async function readComponentNames(themePath: string): Promise<string[]> {
Expand Down Expand Up @@ -88,13 +117,9 @@ export async function readComponentNames(themePath: string): Promise<string[]> {

const componentFiles = await walk(themePath);

const componentFilesOrdered = _.orderBy(
componentFiles,
[(f) => f.componentName],
['asc'],
);
const componentNames = componentFiles.map((f) => f.componentName);

return componentFilesOrdered.map((f) => f.componentName);
return sortComponentNames(componentNames);
}

export function listComponentNames(themeComponents: ThemeComponents): string {
Expand Down Expand Up @@ -125,15 +150,41 @@ export async function getThemeComponents({
},
description: FallbackSwizzleComponentDescription,
};
const FallbackIntermediateFolderSwizzleComponentConfig: SwizzleComponentConfig =
{
actions: {
// It doesn't make sense to wrap an intermediate folder
// because it has not any index component
wrap: 'forbidden',
eject: FallbackSwizzleActionStatus,
},
description: FallbackSwizzleComponentDescription,
};

const allInitialComponents = await readComponentNames(themePath);

const allComponents = await readComponentNames(themePath);
const missingIntermediateComponentFolderNames =
getMissingIntermediateComponentFolderNames(allInitialComponents);

const allComponents = sortComponentNames(
allInitialComponents.concat(missingIntermediateComponentFolderNames),
);

function getConfig(component: string): SwizzleComponentConfig {
if (!allComponents.includes(component)) {
throw new Error(
`Can't get component config: component doesn't exist: ${component}`,
);
}
const config = swizzleConfig.components[component];
if (config) {
return config;
}
const isIntermediateFolder =
missingIntermediateComponentFolderNames.includes(component);
if (isIntermediateFolder) {
return FallbackIntermediateFolderSwizzleComponentConfig;
}
return (
swizzleConfig.components[component] ?? FallbackSwizzleComponentConfig
);
Expand Down
42 changes: 22 additions & 20 deletions packages/docusaurus/src/commands/swizzle/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,33 +48,35 @@ function getModuleSwizzleConfig(
return undefined;
}

export function normalizeSwizzleConfig(
unsafeSwizzleConfig: unknown,
): SwizzleConfig {
const schema = Joi.object<SwizzleConfig>({
components: Joi.object()
.pattern(
Joi.string(),
Joi.object({
actions: Joi.object().pattern(
Joi.string().valid(...SwizzleActions),
Joi.string().valid(...SwizzleActionsStatuses),
),
description: Joi.string(),
}),
)
.required(),
});

const result = schema.validate(unsafeSwizzleConfig);
const SwizzleConfigSchema = Joi.object<SwizzleConfig>({
components: Joi.object()
.pattern(
Joi.string(),
Joi.object({
actions: Joi.object().pattern(
Joi.string().valid(...SwizzleActions),
Joi.string().valid(...SwizzleActionsStatuses),
),
description: Joi.string(),
}),
)
.required(),
});

function validateSwizzleConfig(unsafeSwizzleConfig: unknown): SwizzleConfig {
const result = SwizzleConfigSchema.validate(unsafeSwizzleConfig);
if (result.error) {
throw new Error(
`Swizzle config does not match expected schema: ${result.error.message}`,
);
}
return result.value;
}

const swizzleConfig: SwizzleConfig = result.value;
export function normalizeSwizzleConfig(
unsafeSwizzleConfig: unknown,
): SwizzleConfig {
const swizzleConfig = validateSwizzleConfig(unsafeSwizzleConfig);

// Ensure all components always declare all actions
Object.values(swizzleConfig.components).forEach((componentConfig) => {
Expand Down

0 comments on commit c3add31

Please sign in to comment.