diff --git a/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexComp1.css b/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexComp1.css
new file mode 100644
index 0000000000..7aa192f3b3
--- /dev/null
+++ b/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexComp1.css
@@ -0,0 +1,3 @@
+.testClass {
+ background: black;
+}
diff --git a/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexComp1.tsx b/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexComp1.tsx
new file mode 100644
index 0000000000..dcf3086a30
--- /dev/null
+++ b/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexComp1.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export default function NoIndexComp1() {
+ return
NoIndexComp1
;
+}
diff --git a/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexComp2.tsx b/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexComp2.tsx
new file mode 100644
index 0000000000..f070a38c03
--- /dev/null
+++ b/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexComp2.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export default function NoIndexComp2() {
+ return NoIndexComp2
;
+}
diff --git a/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexSub/NoIndexSubComp.tsx b/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexSub/NoIndexSubComp.tsx
new file mode 100644
index 0000000000..fdb3570a32
--- /dev/null
+++ b/packages/docusaurus/src/commands/swizzle/__tests__/__fixtures__/theme/NoIndex/NoIndexSub/NoIndexSubComp.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export default function NoIndexSubComp() {
+ return NoIndexSubComp
;
+}
diff --git a/packages/docusaurus/src/commands/swizzle/__tests__/components.test.ts b/packages/docusaurus/src/commands/swizzle/__tests__/components.test.ts
index d278e727aa..eb0b284457 100644
--- a/packages/docusaurus/src/commands/swizzle/__tests__/components.test.ts
+++ b/packages/docusaurus/src/commands/swizzle/__tests__/components.test.ts
@@ -19,6 +19,9 @@ describe('readComponentNames', () => {
Components.ComponentInSubFolder,
Components.Sibling,
Components.FirstLevelComponent,
+ Components.NoIndexComp1,
+ Components.NoIndexComp2,
+ Components.NoIndexSubComp,
]);
});
});
@@ -66,6 +69,11 @@ describe('getThemeComponents', () => {
Components.ComponentInSubFolder,
Components.Sibling,
Components.FirstLevelComponent,
+ Components.NoIndex,
+ Components.NoIndexComp1,
+ Components.NoIndexComp2,
+ Components.NoIndexSub,
+ Components.NoIndexSubComp,
]);
});
@@ -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 () => {
diff --git a/packages/docusaurus/src/commands/swizzle/__tests__/testUtils.ts b/packages/docusaurus/src/commands/swizzle/__tests__/testUtils.ts
index 97e0aba8a7..e0e9acf795 100644
--- a/packages/docusaurus/src/commands/swizzle/__tests__/testUtils.ts
+++ b/packages/docusaurus/src/commands/swizzle/__tests__/testUtils.ts
@@ -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 {
diff --git a/packages/docusaurus/src/commands/swizzle/components.ts b/packages/docusaurus/src/commands/swizzle/components.ts
index 33968ad0fa..f9dc0d7beb 100644
--- a/packages/docusaurus/src/commands/swizzle/components.ts
+++ b/packages/docusaurus/src/commands/swizzle/components.ts
@@ -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 {
@@ -88,13 +117,9 @@ export async function readComponentNames(themePath: string): Promise {
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 {
@@ -125,8 +150,25 @@ 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 allComponents = await readComponentNames(themePath);
+ const allInitialComponents = await readComponentNames(themePath);
+
+ const missingIntermediateComponentFolderNames =
+ getMissingIntermediateComponentFolderNames(allInitialComponents);
+
+ const allComponents = sortComponentNames(
+ allInitialComponents.concat(missingIntermediateComponentFolderNames),
+ );
function getConfig(component: string): SwizzleComponentConfig {
if (!allComponents.includes(component)) {
@@ -134,6 +176,15 @@ export async function getThemeComponents({
`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
);
diff --git a/packages/docusaurus/src/commands/swizzle/config.ts b/packages/docusaurus/src/commands/swizzle/config.ts
index d7e4ffb7fb..a16c7dfd4a 100644
--- a/packages/docusaurus/src/commands/swizzle/config.ts
+++ b/packages/docusaurus/src/commands/swizzle/config.ts
@@ -48,33 +48,35 @@ function getModuleSwizzleConfig(
return undefined;
}
-export function normalizeSwizzleConfig(
- unsafeSwizzleConfig: unknown,
-): SwizzleConfig {
- const schema = Joi.object({
- 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({
+ 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) => {