Add changeset

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Add new package.json templates to `templatePackagePaths`

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app/.yarn/* files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app/examples/* files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app/packages/backend/* files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app/packages files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app/plugins/* files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Combine templates/default-app/app-config.yaml.hbs & packages/app-next/app-config.yaml

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Combine templates/default-app/packages/app/* & packages/app-next/*

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Align App.tsx with template's backend package

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Misc. changes that align templates/default-app & templates/default-app-next

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Add default-app-next's frontend packages to versions file and mocks

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Add `--experimental-frontend` CLI flag

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Fix default-app-next's frontend package name

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Fix spacing around `scripts` block

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

PR review: Clarify changeset message

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

PR review: Reduce to one default template with two frontend packages (1/2)

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

PR review: Reduce to one default template with two frontend packages (2/2)

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

PR review: Trim down packages/app-next

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Bug: Normalize Windows paths while parsing them out

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Update packages/create-app/templates/default-app/packages/app-next/README.md

Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com>
Signed-off-by: Lynette Lopez <lynettelopez12@gmail.com>

Small follow up

Signed-off-by: Andre Wanlin <awanlin@spotify.com>

Fixed test

Signed-off-by: Andre Wanlin <awanlin@spotify.com>
This commit is contained in:
Lynette Lopez
2024-12-10 19:29:16 -05:00
committed by benjdlambert
parent 85b6f96f64
commit d7a3d04e82
30 changed files with 551 additions and 61 deletions
+32 -6
View File
@@ -57,7 +57,7 @@ describe('command entrypoint', () => {
jest.resetAllMocks();
});
test('should call expected tasks with no path option', async () => {
test('should call expected tasks with no `--path` option', async () => {
const cmd = {} as unknown as Command;
await createApp(cmd);
expect(checkAppExistsMock).toHaveBeenCalled();
@@ -74,11 +74,12 @@ describe('command entrypoint', () => {
expect(templatingMock.mock.lastCall?.[1]).toContain(
path.join(tmpdir(), 'MyApp'),
);
expect(templatingMock.mock.lastCall?.[3]).toEqual(['packages/app-next/']);
expect(moveAppMock).toHaveBeenCalled();
expect(buildAppMock).toHaveBeenCalled();
});
it('should call expected tasks with path option', async () => {
it('should call expected tasks with `--path` option', async () => {
const cmd = { path: 'myDirectory' } as unknown as Command;
await createApp(cmd);
expect(checkPathExistsMock).toHaveBeenCalled();
@@ -93,10 +94,33 @@ describe('command entrypoint', () => {
),
);
expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory');
expect(templatingMock.mock.lastCall?.[3]).toEqual(['packages/app-next/']);
expect(buildAppMock).toHaveBeenCalled();
});
it('should call expected tasks with relative --template-path option', async () => {
it('should call expected tasks when `--experimental-frontend` is supplied', async () => {
const cmd = { experimentalFrontend: true } as unknown as Command;
await createApp(cmd);
expect(checkAppExistsMock).toHaveBeenCalled();
expect(tryInitGitRepositoryMock).toHaveBeenCalled();
expect(templatingMock).toHaveBeenCalled();
expect(templatingMock.mock.lastCall?.[0]).toEqual(
findPaths(__dirname).resolveTarget(
'packages',
'create-app',
'templates',
'default-app',
),
);
expect(templatingMock.mock.lastCall?.[1]).toContain(
path.join(tmpdir(), 'MyApp'),
);
expect(templatingMock.mock.lastCall?.[3]).toEqual(['packages/app/']);
expect(moveAppMock).toHaveBeenCalled();
expect(buildAppMock).toHaveBeenCalled();
});
it('should call expected tasks with relative `--template-path` option', async () => {
const cmd = {
path: 'myDirectory',
templatePath: 'templateDirectory',
@@ -109,10 +133,11 @@ describe('command entrypoint', () => {
findPaths(__dirname).resolveTarget('templateDirectory'),
);
expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory');
expect(templatingMock.mock.lastCall?.[3]).toEqual([]);
expect(buildAppMock).toHaveBeenCalled();
});
it('should call expected tasks with absolute --template-path option', async () => {
it('should call expected tasks with absolute `--template-path` option', async () => {
const cmd = {
path: 'myDirectory',
templatePath: path.resolve('somewhere', 'templateDirectory'),
@@ -125,16 +150,17 @@ describe('command entrypoint', () => {
path.resolve('somewhere', 'templateDirectory'),
);
expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory');
expect(templatingMock.mock.lastCall?.[3]).toEqual([]);
expect(buildAppMock).toHaveBeenCalled();
});
it('should not call `buildAppTask` when `skipInstall` is supplied', async () => {
it('should not call `buildAppTask()` when `--skip-install` is supplied', async () => {
const cmd = { skipInstall: true } as unknown as Command;
await createApp(cmd);
expect(buildAppMock).not.toHaveBeenCalled();
});
it('should not call `initGitRepository` when `gitConfig` is undefined', async () => {
it('should not call `initGitRepository()` when `gitConfig` is undefined', async () => {
const cmd = {} as unknown as Command;
readGitConfig.mockResolvedValue(undefined);
await createApp(cmd);
+25 -8
View File
@@ -65,10 +65,17 @@ export default async (opts: OptionValues): Promise<void> => {
},
]);
// Use `--template-path` argument as template when specified. Otherwise, use the default template.
const templateDir = opts.templatePath
? paths.resolveTarget(opts.templatePath)
: paths.resolveOwn('templates/default-app');
// If using the default template, filter out `packages/app` when the `--experimental-frontend` flag
// is used or `packages/app-next` when its not used.
const excludedDirs = !opts.templatePath
? [opts.experimentalFrontend ? 'packages/app/' : 'packages/app-next/']
: [];
// Use `--path` argument as application directory when specified, otherwise
// create a directory using `answers.name`
const appDir = opts.path
@@ -88,10 +95,15 @@ export default async (opts: OptionValues): Promise<void> => {
await checkPathExistsTask(appDir);
Task.section('Preparing files');
await templatingTask(templateDir, opts.path, {
...answers,
defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH,
});
await templatingTask(
templateDir,
opts.path,
{
...answers,
defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH,
},
excludedDirs,
);
} else {
// Template to temporary location, and then move files
@@ -102,10 +114,15 @@ export default async (opts: OptionValues): Promise<void> => {
const tempDir = await fs.mkdtemp(resolvePath(os.tmpdir(), answers.name));
Task.section('Preparing files');
await templatingTask(templateDir, tempDir, {
...answers,
defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH,
});
await templatingTask(
templateDir,
tempDir,
{
...answers,
defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH,
},
excludedDirs,
);
Task.section('Moving to final location');
await moveAppTask(tempDir, appDir, answers.name);
+4
View File
@@ -31,6 +31,10 @@ const main = (argv: string[]) => {
.name('backstage-create-app')
.version(version)
.description('Creates a new app in a new directory or specified path')
.option(
'--experimental-frontend',
'Use the default template with the experimental frontend',
)
.option(
'--path [directory]',
'Location to store the app defaulting to a new folder with the app name',
+47 -27
View File
@@ -49,59 +49,65 @@ jest.mock('child_process');
jest.mock('./versions', () => ({
packageVersions: {
root: '1.2.3',
'@backstage/cli': '1.0.0',
'@backstage/app-defaults': '1.0.0',
'@backstage/backend-defaults': '1.0.0',
'@backstage/backend-tasks': '1.0.0',
'@backstage/canon': '1.0.0',
'@backstage/catalog-model': '1.0.0',
'@backstage/catalog-client': '1.0.0',
'@backstage/catalog-model': '1.0.0',
'@backstage/cli': '1.0.0',
'@backstage/config': '1.0.0',
'@backstage/core-app-api': '1.0.0',
'@backstage/core-components': '1.0.0',
'@backstage/core-plugin-api': '1.0.0',
'@backstage/e2e-test-utils': '1.0.0',
'@backstage/frontend-defaults': '1.0.0',
'@backstage/frontend-plugin-api': '1.0.0',
'@backstage/integration-react': '1.0.0',
'@backstage/plugin-api-docs': '1.0.0',
'@backstage/plugin-app-backend': '1.0.0',
'@backstage/plugin-app-visualizer': '1.0.0',
'@backstage/plugin-auth-backend': '1.0.0',
'@backstage/plugin-auth-node': '1.0.0',
'@backstage/plugin-auth-backend-module-github-provider': '1.0.0',
'@backstage/plugin-auth-backend-module-guest-provider': '1.0.0',
'@backstage/plugin-auth-node': '1.0.0',
'@backstage/plugin-catalog': '1.0.0',
'@backstage/plugin-catalog-backend': '1.0.0',
'@backstage/plugin-catalog-backend-module-logs': '1.0.0',
'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': '1.0.0',
'@backstage/plugin-permission-common': '1.0.0',
'@backstage/plugin-permission-node': '1.0.0',
'@backstage/plugin-catalog-common': '1.0.0',
'@backstage/plugin-catalog-graph': '1.0.0',
'@backstage/plugin-catalog-import': '1.0.0',
'@backstage/plugin-catalog-react': '1.0.0',
'@backstage/plugin-home': '1.0.0',
'@backstage/plugin-kubernetes': '1.0.0',
'@backstage/plugin-kubernetes-backend': '1.0.0',
'@backstage/plugin-org': '1.0.0',
'@backstage/plugin-permission-backend': '1.0.0',
'@backstage/plugin-permission-backend-module-allow-all-policy': '1.0.0',
'@backstage/plugin-permission-common': '1.0.0',
'@backstage/plugin-permission-node': '1.0.0',
'@backstage/plugin-permission-react': '1.0.0',
'@backstage/plugin-proxy-backend': '1.0.0',
'@backstage/plugin-scaffolder': '1.0.0',
'@backstage/plugin-scaffolder-backend': '1.0.0',
'@backstage/plugin-scaffolder-backend-module-github': '1.0.0',
'@backstage/plugin-search': '1.0.0',
'@backstage/plugin-search-backend': '1.0.0',
'@backstage/plugin-search-backend-module-catalog': '1.0.0',
'@backstage/plugin-search-backend-module-pg': '1.0.0',
'@backstage/plugin-search-backend-module-techdocs': '1.0.0',
'@backstage/plugin-search-backend-node': '1.0.0',
'@backstage/plugin-techdocs-backend': '1.0.0',
'@backstage/app-defaults': '1.0.0',
'@backstage/core-app-api': '1.0.0',
'@backstage/core-components': '1.0.0',
'@backstage/core-plugin-api': '1.0.0',
'@backstage/e2e-test-utils': '1.0.0',
'@backstage/integration-react': '1.0.0',
'@backstage/plugin-api-docs': '1.0.0',
'@backstage/plugin-catalog': '1.0.0',
'@backstage/plugin-catalog-common': '1.0.0',
'@backstage/plugin-catalog-graph': '1.0.0',
'@backstage/plugin-catalog-import': '1.0.0',
'@backstage/plugin-catalog-react': '1.0.0',
'@backstage/plugin-kubernetes': '1.0.0',
'@backstage/plugin-kubernetes-backend': '1.0.0',
'@backstage/plugin-org': '1.0.0',
'@backstage/plugin-scaffolder': '1.0.0',
'@backstage/plugin-scaffolder-backend-module-github': '1.0.0',
'@backstage/plugin-permission-react': '1.0.0',
'@backstage/plugin-search': '1.0.0',
'@backstage/plugin-search-react': '1.0.0',
'@backstage/plugin-techdocs': '1.0.0',
'@backstage/plugin-techdocs-react': '1.0.0',
'@backstage/plugin-techdocs-backend': '1.0.0',
'@backstage/plugin-techdocs-module-addons-contrib': '1.0.0',
'@backstage/plugin-techdocs-react': '1.0.0',
'@backstage/plugin-user-settings': '1.0.0',
'@backstage/theme': '1.0.0',
'@backstage/test-utils': '1.0.0',
'@backstage/ui': '1.0.0',
'@backstage/theme': '1.0.0',
},
}));
@@ -267,7 +273,7 @@ describe('tasks', () => {
});
describe('templatingTask', () => {
it('should generate a project populating context parameters', async () => {
it('should generate a project and populate context parameters', async () => {
const templateDir = resolvePath(__dirname, '../../templates/default-app');
const destinationDir = 'templatedApp';
const context = {
@@ -291,6 +297,20 @@ describe('tasks', () => {
fs.readFile('templatedApp/packages/backend/package.json', 'utf-8'),
).resolves.toContain('sqlite3"');
});
it('should generate a project and skip excluded directories', async () => {
const templateDir = resolvePath(__dirname, '../../templates/default-app');
const destinationDir = 'templatedApp';
const context = { name: 'Backstage', dbTypeSqlite: true };
const excludedDirs = ['packages/app-next'];
await templatingTask(templateDir, destinationDir, context, excludedDirs);
expect(fs.existsSync('templatedApp/packages/app/package.json')).toBe(
true,
);
expect(fs.existsSync('templatedApp/packages/app-next/package.json')).toBe(
false,
);
});
});
describe('readGitConfig', () => {
+15 -4
View File
@@ -85,21 +85,32 @@ export class Task {
* @param templateDir - location containing template files
* @param destinationDir - location to save templated project
* @param context - template parameters
* @param excludedDirs - template files to exclude
*/
export async function templatingTask(
templateDir: string,
destinationDir: string,
context: any,
excludedDirs?: string[],
) {
const files = await recursive(templateDir).catch(error => {
throw new Error(`Failed to read template directory: ${error.message}`);
});
for (const file of files) {
const destinationFile = resolvePath(
destinationDir,
relativePath(templateDir, file),
);
const filePath = relativePath(templateDir, file);
if (
excludedDirs?.some(excludedDir => {
const normalizedFilePath = filePath.replace(/\\/g, '/');
const normalizedExcludedDir = excludedDir.replace(/\\/g, '/');
return normalizedFilePath.startsWith(normalizedExcludedDir);
})
) {
continue;
}
const destinationFile = resolvePath(destinationDir, filePath);
await fs.ensureDir(dirname(destinationFile));
if (file.endsWith('.hbs')) {
+21 -16
View File
@@ -33,6 +33,7 @@ import { version as root } from '../../../../package.json';
import { version as appDefaults } from '../../../app-defaults/package.json';
import { version as backendDefaults } from '../../../backend-defaults/package.json';
import { version as canon } from '../../../canon/package.json';
import { version as catalogClient } from '../../../catalog-client/package.json';
import { version as catalogModel } from '../../../catalog-model/package.json';
import { version as cli } from '../../../cli/package.json';
@@ -41,56 +42,58 @@ import { version as coreAppApi } from '../../../core-app-api/package.json';
import { version as coreComponents } from '../../../core-components/package.json';
import { version as corePluginApi } from '../../../core-plugin-api/package.json';
import { version as e2eTestUtils } from '../../../e2e-test-utils/package.json';
import { version as errors } from '../../../errors/package.json';
import { version as frontendDefaults } from '../../../frontend-defaults/package.json';
import { version as frontendPluginApi } from '../../../frontend-plugin-api/package.json';
import { version as integrationReact } from '../../../integration-react/package.json';
import { version as testUtils } from '../../../test-utils/package.json';
import { version as theme } from '../../../theme/package.json';
import { version as repoTools } from '../../../repo-tools/package.json';
import { version as ui } from '../../../ui/package.json';
import { version as pluginApiDocs } from '../../../../plugins/api-docs/package.json';
import { version as pluginAppBackend } from '../../../../plugins/app-backend/package.json';
import { version as pluginAppVisualizer } from '../../../../plugins/app-visualizer/package.json';
import { version as pluginAuthBackend } from '../../../../plugins/auth-backend/package.json';
import { version as pluginAuthBackendModuleGithubProvider } from '../../../../plugins/auth-backend-module-github-provider/package.json';
import { version as pluginAuthBackendModuleGuestProvider } from '../../../../plugins/auth-backend-module-guest-provider/package.json';
import { version as pluginAuthNode } from '../../../../plugins/auth-node/package.json';
import { version as pluginCatalog } from '../../../../plugins/catalog/package.json';
import { version as pluginCatalogCommon } from '../../../../plugins/catalog-common/package.json';
import { version as pluginCatalogReact } from '../../../../plugins/catalog-react/package.json';
import { version as pluginCatalogBackend } from '../../../../plugins/catalog-backend/package.json';
import { version as pluginCatalogBackendModuleLogs } from '../../../../plugins/catalog-backend-module-logs/package.json';
import { version as pluginCatalogBackendModuleScaffolderEntityModel } from '../../../../plugins/catalog-backend-module-scaffolder-entity-model/package.json';
import { version as pluginCatalogCommon } from '../../../../plugins/catalog-common/package.json';
import { version as pluginCatalogGraph } from '../../../../plugins/catalog-graph/package.json';
import { version as pluginCatalogImport } from '../../../../plugins/catalog-import/package.json';
import { version as pluginCatalogReact } from '../../../../plugins/catalog-react/package.json';
import { version as pluginHome } from '../../../../plugins/home/package.json';
import { version as pluginKubernetes } from '../../../../plugins/kubernetes/package.json';
import { version as pluginKubernetesBackend } from '../../../../plugins/kubernetes-backend/package.json';
import { version as pluginOrg } from '../../../../plugins/org/package.json';
import { version as pluginPermissionBackend } from '../../../../plugins/permission-backend/package.json';
import { version as pluginPermissionBackendModulePolicyAllowAll } from '../../../../plugins/permission-backend-module-policy-allow-all/package.json';
import { version as pluginPermissionCommon } from '../../../../plugins/permission-common/package.json';
import { version as pluginPermissionReact } from '../../../../plugins/permission-react/package.json';
import { version as pluginPermissionNode } from '../../../../plugins/permission-node/package.json';
import { version as pluginPermissionReact } from '../../../../plugins/permission-react/package.json';
import { version as pluginProxyBackend } from '../../../../plugins/proxy-backend/package.json';
import { version as pluginScaffolder } from '../../../../plugins/scaffolder/package.json';
import { version as pluginScaffolderBackend } from '../../../../plugins/scaffolder-backend/package.json';
import { version as pluginScaffolderBackendModuleGithub } from '../../../../plugins/scaffolder-backend-module-github/package.json';
import { version as pluginSearch } from '../../../../plugins/search/package.json';
import { version as pluginSearchReact } from '../../../../plugins/search-react/package.json';
import { version as pluginSearchBackend } from '../../../../plugins/search-backend/package.json';
import { version as pluginSearchBackendModuleCatalog } from '../../../../plugins/search-backend-module-catalog/package.json';
import { version as pluginSearchBackendModulePg } from '../../../../plugins/search-backend-module-pg/package.json';
import { version as pluginSearchBackendModuleTechdocs } from '../../../../plugins/search-backend-module-techdocs/package.json';
import { version as pluginSearchBackendNode } from '../../../../plugins/search-backend-node/package.json';
import { version as pluginSearchReact } from '../../../../plugins/search-react/package.json';
import { version as pluginTechdocs } from '../../../../plugins/techdocs/package.json';
import { version as pluginTechdocsReact } from '../../../../plugins/techdocs-react/package.json';
import { version as pluginTechdocsModuleAddonsContrib } from '../../../../plugins/techdocs-module-addons-contrib/package.json';
import { version as pluginTechdocsBackend } from '../../../../plugins/techdocs-backend/package.json';
import { version as pluginTechdocsModuleAddonsContrib } from '../../../../plugins/techdocs-module-addons-contrib/package.json';
import { version as pluginTechdocsReact } from '../../../../plugins/techdocs-react/package.json';
import { version as pluginUserSettings } from '../../../../plugins/user-settings/package.json';
export const packageVersions = {
root,
'@backstage/app-defaults': appDefaults,
'@backstage/backend-defaults': backendDefaults,
'@backstage/canon': canon,
'@backstage/catalog-client': catalogClient,
'@backstage/catalog-model': catalogModel,
'@backstage/cli': cli,
@@ -99,11 +102,12 @@ export const packageVersions = {
'@backstage/core-components': coreComponents,
'@backstage/core-plugin-api': corePluginApi,
'@backstage/e2e-test-utils': e2eTestUtils,
'@backstage/errors': errors,
'@backstage/frontend-defaults': frontendDefaults,
'@backstage/frontend-plugin-api': frontendPluginApi,
'@backstage/integration-react': integrationReact,
'@backstage/repo-tools': repoTools,
'@backstage/plugin-api-docs': pluginApiDocs,
'@backstage/plugin-app-backend': pluginAppBackend,
'@backstage/plugin-app-visualizer': pluginAppVisualizer,
'@backstage/plugin-auth-backend': pluginAuthBackend,
'@backstage/plugin-auth-backend-module-github-provider':
pluginAuthBackendModuleGithubProvider,
@@ -111,15 +115,16 @@ export const packageVersions = {
pluginAuthBackendModuleGuestProvider,
'@backstage/plugin-auth-node': pluginAuthNode,
'@backstage/plugin-catalog': pluginCatalog,
'@backstage/plugin-catalog-common': pluginCatalogCommon,
'@backstage/plugin-catalog-react': pluginCatalogReact,
'@backstage/plugin-catalog-backend': pluginCatalogBackend,
'@backstage/plugin-catalog-backend-module-logs':
pluginCatalogBackendModuleLogs,
'@backstage/plugin-catalog-backend-module-scaffolder-entity-model':
pluginCatalogBackendModuleScaffolderEntityModel,
'@backstage/plugin-catalog-common': pluginCatalogCommon,
'@backstage/plugin-catalog-graph': pluginCatalogGraph,
'@backstage/plugin-catalog-import': pluginCatalogImport,
'@backstage/plugin-catalog-react': pluginCatalogReact,
'@backstage/plugin-home': pluginHome,
'@backstage/plugin-kubernetes': pluginKubernetes,
'@backstage/plugin-kubernetes-backend': pluginKubernetesBackend,
'@backstage/plugin-org': pluginOrg,
@@ -135,7 +140,6 @@ export const packageVersions = {
'@backstage/plugin-scaffolder-backend-module-github':
pluginScaffolderBackendModuleGithub,
'@backstage/plugin-search': pluginSearch,
'@backstage/plugin-search-react': pluginSearchReact,
'@backstage/plugin-search-backend': pluginSearchBackend,
'@backstage/plugin-search-backend-module-catalog':
pluginSearchBackendModuleCatalog,
@@ -143,11 +147,12 @@ export const packageVersions = {
'@backstage/plugin-search-backend-module-techdocs':
pluginSearchBackendModuleTechdocs,
'@backstage/plugin-search-backend-node': pluginSearchBackendNode,
'@backstage/plugin-search-react': pluginSearchReact,
'@backstage/plugin-techdocs': pluginTechdocs,
'@backstage/plugin-techdocs-react': pluginTechdocsReact,
'@backstage/plugin-techdocs-backend': pluginTechdocsBackend,
'@backstage/plugin-techdocs-module-addons-contrib':
pluginTechdocsModuleAddonsContrib,
'@backstage/plugin-techdocs-backend': pluginTechdocsBackend,
'@backstage/plugin-techdocs-react': pluginTechdocsReact,
'@backstage/plugin-user-settings': pluginUserSettings,
'@backstage/test-utils': testUtils,
'@backstage/theme': theme,