Merge pull request #32700 from backstage/rugvip/new-frontend-system-default
Make the new frontend system the default for new apps
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
export const defaultTemplates = [
|
||||
'@backstage/cli-module-new/templates/frontend-plugin',
|
||||
'@backstage/cli-module-new/templates/frontend-plugin-module',
|
||||
'@backstage/cli-module-new/templates/legacy-frontend-plugin',
|
||||
'@backstage/cli-module-new/templates/backend-plugin',
|
||||
'@backstage/cli-module-new/templates/backend-plugin-module',
|
||||
'@backstage/cli-module-new/templates/plugin-web-library',
|
||||
|
||||
@@ -360,6 +360,141 @@ describe('loadPortableTemplateConfig', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should filter out legacy frontend template for new frontend system apps', async () => {
|
||||
mockDir.setContent({
|
||||
'package.json': JSON.stringify({}),
|
||||
packages: {
|
||||
app: {
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'@backstage/frontend-defaults': '^0.1.0',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
node_modules: Object.fromEntries(
|
||||
defaultTemplates.map(t => {
|
||||
// Match the real behavior: both frontend-plugin and legacy-frontend-plugin
|
||||
// have the same template name "frontend-plugin"
|
||||
const name = t.endsWith('/legacy-frontend-plugin')
|
||||
? 'frontend-plugin'
|
||||
: basename(t);
|
||||
return [
|
||||
t,
|
||||
{ [TEMPLATE_FILE_NAME]: `name: ${name}\nrole: web-library\n` },
|
||||
];
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const config = await loadPortableTemplateConfig({
|
||||
packagePath: mockDir.resolve('package.json'),
|
||||
});
|
||||
|
||||
expect(config.isUsingDefaultTemplates).toBe(true);
|
||||
|
||||
const templateNames = config.templatePointers.map(t => t.name);
|
||||
expect(templateNames).toContain('frontend-plugin');
|
||||
expect(templateNames).toContain('frontend-plugin-module');
|
||||
expect(templateNames).toContain('backend-plugin');
|
||||
// Legacy template should be filtered out
|
||||
expect(templateNames).not.toContain('legacy-frontend-plugin');
|
||||
|
||||
// The frontend-plugin in the list should be from the new template, not legacy
|
||||
const frontendPlugin = config.templatePointers.find(
|
||||
t => t.name === 'frontend-plugin',
|
||||
);
|
||||
expect(frontendPlugin?.target).toContain('/frontend-plugin/');
|
||||
expect(frontendPlugin?.target).not.toContain('/legacy-frontend-plugin/');
|
||||
});
|
||||
|
||||
it('should filter out new frontend templates for legacy frontend system apps', async () => {
|
||||
mockDir.setContent({
|
||||
'package.json': JSON.stringify({}),
|
||||
packages: {
|
||||
app: {
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'@backstage/app-defaults': '^0.1.0',
|
||||
'@backstage/core-app-api': '^0.1.0',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
node_modules: Object.fromEntries(
|
||||
defaultTemplates.map(t => {
|
||||
const name = t.endsWith('/legacy-frontend-plugin')
|
||||
? 'frontend-plugin'
|
||||
: basename(t);
|
||||
return [
|
||||
t,
|
||||
{ [TEMPLATE_FILE_NAME]: `name: ${name}\nrole: web-library\n` },
|
||||
];
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const config = await loadPortableTemplateConfig({
|
||||
packagePath: mockDir.resolve('package.json'),
|
||||
});
|
||||
|
||||
expect(config.isUsingDefaultTemplates).toBe(true);
|
||||
|
||||
const templateNames = config.templatePointers.map(t => t.name);
|
||||
// Legacy template should be present (shown as "frontend-plugin")
|
||||
expect(templateNames).toContain('frontend-plugin');
|
||||
expect(templateNames).toContain('backend-plugin');
|
||||
// New frontend templates should be filtered out
|
||||
expect(templateNames).not.toContain('frontend-plugin-module');
|
||||
|
||||
// The frontend-plugin in the list should be from the legacy template
|
||||
const frontendPlugin = config.templatePointers.find(
|
||||
t => t.name === 'frontend-plugin',
|
||||
);
|
||||
expect(frontendPlugin?.target).toContain('/legacy-frontend-plugin/');
|
||||
});
|
||||
|
||||
it('should not filter templates when using explicit configuration', async () => {
|
||||
mockDir.setContent({
|
||||
'package.json': JSON.stringify({
|
||||
backstage: {
|
||||
cli: {
|
||||
new: {
|
||||
templates: ['./my-frontend-plugin', './my-backend-plugin'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Even with a new frontend system app, explicit templates aren't filtered
|
||||
packages: {
|
||||
app: {
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: {
|
||||
'@backstage/frontend-defaults': '^0.1.0',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
'my-frontend-plugin': {
|
||||
[TEMPLATE_FILE_NAME]: 'name: frontend-plugin\nrole: frontend-plugin\n',
|
||||
},
|
||||
'my-backend-plugin': {
|
||||
[TEMPLATE_FILE_NAME]: 'name: backend-plugin\nrole: backend-plugin\n',
|
||||
},
|
||||
});
|
||||
|
||||
const config = await loadPortableTemplateConfig({
|
||||
packagePath: mockDir.resolve('package.json'),
|
||||
});
|
||||
|
||||
expect(config.isUsingDefaultTemplates).toBe(false);
|
||||
expect(config.templatePointers).toHaveLength(2);
|
||||
expect(config.templatePointers.map(t => t.name)).toEqual([
|
||||
'frontend-plugin',
|
||||
'backend-plugin',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle missing backstage.new configuration', async () => {
|
||||
mockDir.setContent({
|
||||
'package.json': JSON.stringify({}),
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { resolve as resolvePath, dirname, isAbsolute } from 'node:path';
|
||||
import { resolve as resolvePath, dirname, isAbsolute, join } from 'node:path';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
import { defaultTemplates } from '../defaultTemplates';
|
||||
import {
|
||||
PortableTemplateConfig,
|
||||
@@ -29,6 +28,60 @@ import { z } from 'zod/v3';
|
||||
import { fromZodError } from 'zod-validation-error/v3';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
|
||||
type FrontendSystem = 'new' | 'legacy' | 'unknown';
|
||||
|
||||
async function detectFrontendSystem(basePath: string): Promise<FrontendSystem> {
|
||||
const appPkgPath = join(basePath, 'packages', 'app', 'package.json');
|
||||
|
||||
try {
|
||||
const appPkgJson = await fs.readJson(appPkgPath);
|
||||
const deps = {
|
||||
...appPkgJson.dependencies,
|
||||
...appPkgJson.devDependencies,
|
||||
};
|
||||
|
||||
if (
|
||||
deps['@backstage/frontend-defaults'] ||
|
||||
deps['@backstage/frontend-app-api']
|
||||
) {
|
||||
return 'new';
|
||||
}
|
||||
if (deps['@backstage/app-defaults'] || deps['@backstage/core-app-api']) {
|
||||
return 'legacy';
|
||||
}
|
||||
} catch {
|
||||
// App package doesn't exist or can't be read
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
// Templates to exclude based on frontend system detection (by path, not name)
|
||||
const newFrontendTemplates = [
|
||||
'@backstage/cli-module-new/templates/frontend-plugin',
|
||||
'@backstage/cli-module-new/templates/frontend-plugin-module',
|
||||
];
|
||||
const legacyFrontendTemplates = [
|
||||
'@backstage/cli-module-new/templates/legacy-frontend-plugin',
|
||||
];
|
||||
|
||||
function filterTemplateEntriesForFrontendSystem(
|
||||
entries: Array<{ pointer: PortableTemplatePointer; rawPointer: string }>,
|
||||
frontendSystem: FrontendSystem,
|
||||
): Array<{ pointer: PortableTemplatePointer; rawPointer: string }> {
|
||||
if (frontendSystem === 'unknown') {
|
||||
return entries;
|
||||
}
|
||||
|
||||
if (frontendSystem === 'new') {
|
||||
// Filter out legacy frontend templates
|
||||
return entries.filter(e => !legacyFrontendTemplates.includes(e.rawPointer));
|
||||
}
|
||||
|
||||
// Legacy system - filter out new frontend templates
|
||||
return entries.filter(e => !newFrontendTemplates.includes(e.rawPointer));
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
license: 'Apache-2.0',
|
||||
version: '0.1.0',
|
||||
@@ -105,7 +158,9 @@ export async function loadPortableTemplateConfig(
|
||||
const config = parsed.data.backstage?.cli?.new;
|
||||
|
||||
const basePath = dirname(pkgPath);
|
||||
const templatePointerEntries = await Promise.all(
|
||||
const isUsingDefaultTemplates = !config?.templates;
|
||||
|
||||
let templatePointerEntries = await Promise.all(
|
||||
(config?.templates ?? defaultTemplates).map(async rawPointer => {
|
||||
try {
|
||||
const templatePath = resolveLocalTemplatePath(rawPointer, basePath);
|
||||
@@ -121,6 +176,17 @@ export async function loadPortableTemplateConfig(
|
||||
}),
|
||||
);
|
||||
|
||||
// Auto-filter frontend templates based on detected frontend system.
|
||||
// This must happen before the conflict check since both the new and legacy
|
||||
// frontend plugin templates have the same name, but only one will be shown.
|
||||
if (isUsingDefaultTemplates) {
|
||||
const frontendSystem = await detectFrontendSystem(basePath);
|
||||
templatePointerEntries = filterTemplateEntriesForFrontendSystem(
|
||||
templatePointerEntries,
|
||||
frontendSystem,
|
||||
);
|
||||
}
|
||||
|
||||
const templateNameConflicts = new Map<string, string>();
|
||||
for (const { pointer, rawPointer } of templatePointerEntries) {
|
||||
const conflict = templateNameConflicts.get(pointer.name);
|
||||
@@ -143,7 +209,7 @@ export async function loadPortableTemplateConfig(
|
||||
);
|
||||
|
||||
return {
|
||||
isUsingDefaultTemplates: !config?.templates,
|
||||
isUsingDefaultTemplates,
|
||||
templatePointers: templatePointerEntries.map(({ pointer }) => pointer),
|
||||
license: overrides.license ?? config?.globals?.license ?? defaults.license,
|
||||
version: overrides.version ?? config?.globals?.version ?? defaults.version,
|
||||
|
||||
@@ -6,7 +6,14 @@ _This plugin was created through the Backstage CLI_
|
||||
|
||||
## Getting started
|
||||
|
||||
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/{{pluginId}}](http://localhost:3000/{{pluginId}}).
|
||||
Your plugin has been added to the app in this repository, meaning you'll be able
|
||||
to access it by running `yarn start` in the root directory, and then navigating
|
||||
to [/{{pluginId}}](http://localhost:3000/{{pluginId}}).
|
||||
|
||||
This plugin is built with Backstage's [new frontend
|
||||
system](https://backstage.io/docs/frontend-system/architecture/index), and you
|
||||
can find more information about building plugins in the [plugin builder
|
||||
documentation](https://backstage.io/docs/frontend-system/building-plugins/index).
|
||||
|
||||
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
|
||||
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "{{versionQuery '@backstage/core-components'}}",
|
||||
"@backstage/core-plugin-api": "{{versionQuery '@backstage/core-plugin-api'}}",
|
||||
"@backstage/frontend-plugin-api": "{{versionQuery '@backstage/frontend-plugin-api'}}",
|
||||
"@backstage/theme": "{{versionQuery '@backstage/theme'}}",
|
||||
"@material-ui/core": "{{versionQuery '@material-ui/core' '4.12.2'}}",
|
||||
"@material-ui/icons": "{{versionQuery '@material-ui/icons' '4.9.1'}}",
|
||||
@@ -31,22 +31,18 @@
|
||||
"react-use": "{{versionQuery 'react-use' '17.2.4'}}"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
|
||||
"react-dom": "{{versionQuery 'react-dom' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
|
||||
"react-router-dom": "{{versionQuery 'react-router-dom' '^6.0.0'}}"
|
||||
"react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "{{versionQuery '@backstage/cli'}}",
|
||||
"@backstage/core-app-api": "{{versionQuery '@backstage/core-app-api'}}",
|
||||
"@backstage/dev-utils": "{{versionQuery '@backstage/dev-utils'}}",
|
||||
"@backstage/test-utils": "{{versionQuery '@backstage/test-utils'}}",
|
||||
"@backstage/frontend-dev-utils": "{{versionQuery '@backstage/frontend-dev-utils'}}",
|
||||
"@backstage/frontend-defaults": "{{versionQuery '@backstage/frontend-defaults'}}",
|
||||
"@backstage/frontend-test-utils": "{{versionQuery '@backstage/frontend-test-utils'}}",
|
||||
"@testing-library/jest-dom": "{{versionQuery '@testing-library/jest-dom' '6.0.0'}}",
|
||||
"@testing-library/react": "{{versionQuery '@testing-library/react' '14.0.0'}}",
|
||||
"@testing-library/user-event": "{{versionQuery '@testing-library/user-event' '14.0.0'}}",
|
||||
"msw": "{{versionQuery 'msw' '1.0.0'}}",
|
||||
"react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
|
||||
"react-dom": "{{versionQuery 'react-dom' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
|
||||
"react-router-dom": "{{versionQuery 'react-router-dom' '^6.0.0'}}"
|
||||
"react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -3,4 +3,3 @@ role: frontend-plugin
|
||||
description: A new frontend plugin
|
||||
values:
|
||||
pluginVar: '{{ camelCase pluginId }}Plugin'
|
||||
extensionName: '{{ upperFirst ( camelCase pluginId ) }}Page'
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { screen } from '@testing-library/react';
|
||||
import {
|
||||
registerMswTestHooks,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
} from '@backstage/frontend-test-utils';
|
||||
|
||||
describe('ExampleComponent', () => {
|
||||
const server = setupServer();
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { renderInTestApp } from '@backstage/frontend-test-utils';
|
||||
import { ExampleFetchComponent } from './ExampleFetchComponent';
|
||||
|
||||
describe('ExampleFetchComponent', () => {
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { {{ pluginVar }}, {{ extensionName }} } from './plugin';
|
||||
export { {{ pluginVar }} as default } from './plugin';
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# {{pluginId}}
|
||||
|
||||
Welcome to the {{pluginId}} plugin!
|
||||
|
||||
_This plugin was created through the Backstage CLI_
|
||||
|
||||
## Getting started
|
||||
|
||||
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/{{pluginId}}](http://localhost:3000/{{pluginId}}).
|
||||
|
||||
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
|
||||
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
|
||||
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
|
||||
+10
-5
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "{{versionQuery '@backstage/core-components'}}",
|
||||
"@backstage/frontend-plugin-api": "{{versionQuery '@backstage/frontend-plugin-api'}}",
|
||||
"@backstage/core-plugin-api": "{{versionQuery '@backstage/core-plugin-api'}}",
|
||||
"@backstage/theme": "{{versionQuery '@backstage/theme'}}",
|
||||
"@material-ui/core": "{{versionQuery '@material-ui/core' '4.12.2'}}",
|
||||
"@material-ui/icons": "{{versionQuery '@material-ui/icons' '4.9.1'}}",
|
||||
@@ -31,17 +31,22 @@
|
||||
"react-use": "{{versionQuery 'react-use' '17.2.4'}}"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}"
|
||||
"react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
|
||||
"react-dom": "{{versionQuery 'react-dom' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
|
||||
"react-router-dom": "{{versionQuery 'react-router-dom' '^6.0.0'}}"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "{{versionQuery '@backstage/cli'}}",
|
||||
"@backstage/frontend-dev-utils": "{{versionQuery '@backstage/frontend-dev-utils'}}",
|
||||
"@backstage/frontend-test-utils": "{{versionQuery '@backstage/frontend-test-utils'}}",
|
||||
"@backstage/core-app-api": "{{versionQuery '@backstage/core-app-api'}}",
|
||||
"@backstage/dev-utils": "{{versionQuery '@backstage/dev-utils'}}",
|
||||
"@backstage/test-utils": "{{versionQuery '@backstage/test-utils'}}",
|
||||
"@testing-library/jest-dom": "{{versionQuery '@testing-library/jest-dom' '6.0.0'}}",
|
||||
"@testing-library/react": "{{versionQuery '@testing-library/react' '14.0.0'}}",
|
||||
"@testing-library/user-event": "{{versionQuery '@testing-library/user-event' '14.0.0'}}",
|
||||
"msw": "{{versionQuery 'msw' '1.0.0'}}",
|
||||
"react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}"
|
||||
"react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
|
||||
"react-dom": "{{versionQuery 'react-dom' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
|
||||
"react-router-dom": "{{versionQuery 'react-router-dom' '^6.0.0'}}"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
@@ -0,0 +1,6 @@
|
||||
name: frontend-plugin
|
||||
role: frontend-plugin
|
||||
description: A new frontend plugin (legacy system)
|
||||
values:
|
||||
pluginVar: '{{ camelCase pluginId }}Plugin'
|
||||
extensionName: '{{ upperFirst ( camelCase pluginId ) }}Page'
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { screen } from '@testing-library/react';
|
||||
import {
|
||||
registerMswTestHooks,
|
||||
renderInTestApp,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
} from '@backstage/test-utils';
|
||||
|
||||
describe('ExampleComponent', () => {
|
||||
const server = setupServer();
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { renderInTestApp } from '@backstage/frontend-test-utils';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { ExampleFetchComponent } from './ExampleFetchComponent';
|
||||
|
||||
describe('ExampleFetchComponent', () => {
|
||||
@@ -0,0 +1 @@
|
||||
export { {{ pluginVar }}, {{ extensionName }} } from './plugin';
|
||||
@@ -1,20 +0,0 @@
|
||||
# {{pluginId}}
|
||||
|
||||
Welcome to the {{pluginId}} plugin!
|
||||
|
||||
_This plugin was created through the Backstage CLI_
|
||||
|
||||
## Getting started
|
||||
|
||||
Your plugin has been added to the app in this repository, meaning you'll be able
|
||||
to access it by running `yarn start` in the root directory, and then navigating
|
||||
to [/{{pluginId}}](http://localhost:3000/{{pluginId}}).
|
||||
|
||||
This plugin is built with Backstage's [new frontend
|
||||
system](https://backstage.io/docs/frontend-system/architecture/index), and you
|
||||
can find more information about building plugins in the [plugin builder
|
||||
documentation](https://backstage.io/docs/frontend-system/building-plugins/index).
|
||||
|
||||
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
|
||||
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
|
||||
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
|
||||
@@ -1,5 +0,0 @@
|
||||
name: frontend-plugin
|
||||
role: frontend-plugin
|
||||
description: A new frontend plugin
|
||||
values:
|
||||
pluginVar: '{{ camelCase pluginId }}Plugin'
|
||||
@@ -1 +0,0 @@
|
||||
export { {{ pluginVar }} as default } from './plugin';
|
||||
@@ -8,7 +8,7 @@
|
||||
Usage: backstage-create-app [options]
|
||||
|
||||
Options:
|
||||
--next
|
||||
--legacy
|
||||
--path [directory]
|
||||
--skip-install
|
||||
--template-path [directory]
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('command entrypoint', () => {
|
||||
expect(tryInitGitRepositoryMock).toHaveBeenCalled();
|
||||
expect(templatingMock).toHaveBeenCalled();
|
||||
expect(templatingMock.mock.lastCall?.[0]).toEqual(
|
||||
findOwnPaths(__dirname).resolve('templates/default-app'),
|
||||
findOwnPaths(__dirname).resolve('templates/next-app'),
|
||||
);
|
||||
expect(templatingMock.mock.lastCall?.[1]).toContain(
|
||||
path.join(tmpdir(), 'MyApp'),
|
||||
@@ -85,20 +85,20 @@ describe('command entrypoint', () => {
|
||||
expect(tryInitGitRepositoryMock).toHaveBeenCalled();
|
||||
expect(templatingMock).toHaveBeenCalled();
|
||||
expect(templatingMock.mock.lastCall?.[0]).toEqual(
|
||||
findOwnPaths(__dirname).resolve('templates/default-app'),
|
||||
findOwnPaths(__dirname).resolve('templates/next-app'),
|
||||
);
|
||||
expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory');
|
||||
expect(buildAppMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call expected tasks when `--next` is supplied', async () => {
|
||||
const cmd = { next: true } as unknown as Command;
|
||||
it('should call expected tasks when `--legacy` is supplied', async () => {
|
||||
const cmd = { legacy: true } as unknown as Command;
|
||||
await createApp(cmd);
|
||||
expect(checkAppExistsMock).toHaveBeenCalled();
|
||||
expect(tryInitGitRepositoryMock).toHaveBeenCalled();
|
||||
expect(templatingMock).toHaveBeenCalled();
|
||||
expect(templatingMock.mock.lastCall?.[0]).toEqual(
|
||||
findOwnPaths(__dirname).resolve('templates/next-app'),
|
||||
findOwnPaths(__dirname).resolve('templates/default-app'),
|
||||
);
|
||||
expect(templatingMock.mock.lastCall?.[1]).toContain(
|
||||
path.join(tmpdir(), 'MyApp'),
|
||||
|
||||
@@ -63,12 +63,12 @@ export default async (opts: OptionValues): Promise<void> => {
|
||||
},
|
||||
]);
|
||||
|
||||
// Pick the built-in template based on the --next flag
|
||||
// Pick the built-in template based on the --legacy flag
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
const ownPaths = findOwnPaths(__dirname);
|
||||
const builtInTemplate = opts.next
|
||||
? ownPaths.resolve('templates/next-app')
|
||||
: ownPaths.resolve('templates/default-app');
|
||||
const builtInTemplate = opts.legacy
|
||||
? ownPaths.resolve('templates/default-app')
|
||||
: ownPaths.resolve('templates/next-app');
|
||||
|
||||
// Use `--template-path` argument as template when specified. Otherwise, use the default template.
|
||||
const templateDir = opts.templatePath
|
||||
|
||||
@@ -31,7 +31,7 @@ const main = (argv: string[]) => {
|
||||
.name('backstage-create-app')
|
||||
.version(version)
|
||||
.description('Creates a new app in a new directory or specified path')
|
||||
.option('--next', 'Use the next generation of the app template')
|
||||
.option('--legacy', 'Use the legacy version of the app template')
|
||||
.option(
|
||||
'--path [directory]',
|
||||
'Location to store the app defaulting to a new folder with the app name',
|
||||
|
||||
@@ -39,6 +39,7 @@ 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';
|
||||
import { version as cliDefaults } from '../../../cli-defaults/package.json';
|
||||
import { version as cliModuleNew } from '../../../cli-module-new/package.json';
|
||||
import { version as config } from '../../../config/package.json';
|
||||
import { version as coreAppApi } from '../../../core-app-api/package.json';
|
||||
import { version as coreCompatApi } from '../../../core-compat-api/package.json';
|
||||
@@ -109,6 +110,7 @@ export const packageVersions = {
|
||||
'@backstage/catalog-model': catalogModel,
|
||||
'@backstage/cli': cli,
|
||||
'@backstage/cli-defaults': cliDefaults,
|
||||
'@backstage/cli-module-new': cliModuleNew,
|
||||
'@backstage/config': config,
|
||||
'@backstage/core-app-api': coreAppApi,
|
||||
'@backstage/core-compat-api': coreCompatApi,
|
||||
|
||||
@@ -22,6 +22,28 @@
|
||||
"prettier:check": "prettier --check .",
|
||||
"new": "backstage-cli new"
|
||||
},
|
||||
"backstage": {
|
||||
"cli": {
|
||||
"new": {
|
||||
"globals": {
|
||||
"license": "UNLICENSED"
|
||||
},
|
||||
"templates": [
|
||||
"@backstage/cli-module-new/templates/legacy-frontend-plugin",
|
||||
"@backstage/cli-module-new/templates/backend-plugin",
|
||||
"@backstage/cli-module-new/templates/backend-plugin-module",
|
||||
"@backstage/cli-module-new/templates/plugin-web-library",
|
||||
"@backstage/cli-module-new/templates/plugin-node-library",
|
||||
"@backstage/cli-module-new/templates/plugin-common-library",
|
||||
"@backstage/cli-module-new/templates/web-library",
|
||||
"@backstage/cli-module-new/templates/node-library",
|
||||
"@backstage/cli-module-new/templates/cli-module",
|
||||
"@backstage/cli-module-new/templates/catalog-provider-module",
|
||||
"@backstage/cli-module-new/templates/scaffolder-backend-module"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"plugins/*"
|
||||
|
||||
@@ -17,7 +17,6 @@ app:
|
||||
config:
|
||||
path: /
|
||||
|
||||
|
||||
organization:
|
||||
name: My Company
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ spec:
|
||||
input:
|
||||
repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
|
||||
catalogInfoPath: '/catalog-info.yaml'
|
||||
|
||||
|
||||
# Let's notify the user that the template has completed using the Notification action
|
||||
- id: notify
|
||||
name: Notify
|
||||
|
||||
@@ -27,20 +27,7 @@
|
||||
"new": {
|
||||
"globals": {
|
||||
"license": "UNLICENSED"
|
||||
},
|
||||
"templates": [
|
||||
"@backstage/cli-module-new/templates/new-frontend-plugin",
|
||||
"@backstage/cli-module-new/templates/new-frontend-plugin-module",
|
||||
"@backstage/cli-module-new/templates/backend-plugin",
|
||||
"@backstage/cli-module-new/templates/backend-plugin-module",
|
||||
"@backstage/cli-module-new/templates/plugin-web-library",
|
||||
"@backstage/cli-module-new/templates/plugin-node-library",
|
||||
"@backstage/cli-module-new/templates/plugin-common-library",
|
||||
"@backstage/cli-module-new/templates/web-library",
|
||||
"@backstage/cli-module-new/templates/node-library",
|
||||
"@backstage/cli-module-new/templates/catalog-provider-module",
|
||||
"@backstage/cli-module-new/templates/scaffolder-backend-module"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -23,5 +23,6 @@ test('App should render the welcome page', async ({ page }) => {
|
||||
await expect(enterButton).toBeVisible();
|
||||
await enterButton.click();
|
||||
|
||||
await expect(page.getByText('My Company Catalog')).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Catalog' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'APIs' })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -12,19 +12,20 @@ import { SidebarLogo } from './SidebarLogo';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
import { SidebarSearchModal } from '@backstage/plugin-search';
|
||||
import { UserSettingsSignInAvatar, Settings as SidebarSettings } from '@backstage/plugin-user-settings';
|
||||
import {
|
||||
UserSettingsSignInAvatar,
|
||||
Settings as SidebarSettings,
|
||||
} from '@backstage/plugin-user-settings';
|
||||
import { NotificationsSidebarItem } from '@backstage/plugin-notifications';
|
||||
|
||||
export const SidebarContent = NavContentBlueprint.make({
|
||||
params: {
|
||||
component: ({ navItems }) => {
|
||||
const nav = navItems.withComponent(item => (
|
||||
<SidebarItem
|
||||
icon={() => item.icon}
|
||||
to={item.href}
|
||||
text={item.title}
|
||||
/>
|
||||
<SidebarItem icon={() => item.icon} to={item.href} text={item.title} />
|
||||
));
|
||||
// Skipped items
|
||||
nav.take('page:search');
|
||||
return compatWrapper(
|
||||
<Sidebar>
|
||||
<SidebarLogo />
|
||||
|
||||
@@ -35,9 +35,9 @@ const ownPaths = findOwnPaths(__dirname);
|
||||
|
||||
const templatePackagePaths = [
|
||||
'packages/cli-module-new/templates/frontend-plugin/package.json.hbs',
|
||||
'packages/create-app/templates/default-app/package.json.hbs',
|
||||
'packages/create-app/templates/default-app/packages/app/package.json.hbs',
|
||||
'packages/create-app/templates/default-app/packages/backend/package.json.hbs',
|
||||
'packages/create-app/templates/next-app/package.json.hbs',
|
||||
'packages/create-app/templates/next-app/packages/app/package.json.hbs',
|
||||
'packages/create-app/templates/next-app/packages/backend/package.json.hbs',
|
||||
];
|
||||
|
||||
export async function runCommand(opts: OptionValues) {
|
||||
@@ -66,20 +66,6 @@ export async function runCommand(opts: OptionValues) {
|
||||
env: { ...process.env, CI: undefined },
|
||||
});
|
||||
|
||||
await switchToReact17(appDir);
|
||||
|
||||
print(`Running 'yarn install' to install React 17`);
|
||||
await runOutput(['yarn', 'install'], { cwd: appDir });
|
||||
|
||||
print(`Running 'yarn tsc' with React 17`);
|
||||
await runOutput(['yarn', 'tsc'], { cwd: appDir });
|
||||
|
||||
print(`Running 'yarn test:e2e' with React 17`);
|
||||
await runOutput(['yarn', 'test:e2e'], {
|
||||
cwd: appDir,
|
||||
env: { ...process.env, CI: undefined },
|
||||
});
|
||||
|
||||
if (
|
||||
Boolean(process.env.POSTGRES_USER) ||
|
||||
Boolean(process.env.MYSQL_CONNECTION)
|
||||
@@ -412,37 +398,6 @@ async function createPlugin(options: {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the entire project to use React 17
|
||||
*/
|
||||
async function switchToReact17(appDir: string) {
|
||||
const rootPkg = await fs.readJson(resolvePath(appDir, 'package.json'));
|
||||
rootPkg.resolutions = {
|
||||
...(rootPkg.resolutions || {}),
|
||||
react: '^17.0.0',
|
||||
'react-dom': '^17.0.0',
|
||||
'@types/react': '^17.0.0',
|
||||
'@types/react-dom': '^17.0.0',
|
||||
'swagger-ui-react/react': '17.0.2',
|
||||
'swagger-ui-react/react-dom': '17.0.2',
|
||||
'swagger-ui-react/react-redux': '^8',
|
||||
};
|
||||
await fs.writeJson(resolvePath(appDir, 'package.json'), rootPkg, {
|
||||
spaces: 2,
|
||||
});
|
||||
|
||||
await fs.writeFile(
|
||||
resolvePath(appDir, 'packages/app/src/index.tsx'),
|
||||
`import '@backstage/cli/asset-types';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
`,
|
||||
'utf8',
|
||||
);
|
||||
}
|
||||
|
||||
/** Drops PG databases */
|
||||
async function dropDB(database: string, client: string) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user