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:
@@ -0,0 +1,16 @@
|
||||
---
|
||||
'@backstage/cli': minor
|
||||
---
|
||||
|
||||
**BREAKING**: The CLI templates for frontend plugins have been renamed:
|
||||
|
||||
- `new-frontend-plugin` → `frontend-plugin`
|
||||
- `new-frontend-plugin-module` → `frontend-plugin-module`
|
||||
- `frontend-plugin` (legacy) → `legacy-frontend-plugin`
|
||||
|
||||
To smooth out this breaking change, the CLI now auto-detects which frontend system your app uses based on the dependencies in `packages/app/package.json`. When using the default templates (no explicit `templates` configuration):
|
||||
|
||||
- Apps using `@backstage/frontend-defaults` will see the new frontend system templates (`frontend-plugin`, `frontend-plugin-module`)
|
||||
- Apps using `@backstage/app-defaults` will see the legacy template (displayed as `frontend-plugin`)
|
||||
|
||||
This means existing projects that haven't migrated to the new frontend system will continue to create legacy plugins by default, while new projects will get the new frontend system templates. If you have explicit template configuration in your `package.json`, it will be used as-is without any auto-detection.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': minor
|
||||
---
|
||||
|
||||
**BREAKING**: The new frontend system is now the default template when creating a new Backstage app. The previous `--next` flag has been replaced with a `--legacy` flag that can be used to create an app using the old frontend system instead.
|
||||
@@ -18,12 +18,12 @@ The create-app CLI requires Node.js Active LTS Release, see the [prerequisites d
|
||||
:::
|
||||
|
||||
```sh
|
||||
# The command bellow creates a Backstage App inside the current folder.
|
||||
# The command below creates a Backstage App inside the current folder.
|
||||
# The name of the app-folder is the name that was provided when prompted.
|
||||
npx @backstage/create-app@latest --next
|
||||
npx @backstage/create-app@latest
|
||||
```
|
||||
|
||||
Using the `--next` flag will result in a Backstage app using the New Frontend System which will be further explained in the sections below.
|
||||
This will create a Backstage app using the new frontend system which will be further explained in the sections below.
|
||||
|
||||
## The app instance
|
||||
|
||||
|
||||
@@ -899,39 +899,7 @@ It's encouraged that once you switch over to using the new frontend system, that
|
||||
|
||||
This practice is also pretty important early on, as it's going to help you get familiar with the practices of the new frontend system.
|
||||
|
||||
When creating a new Backstage app with `create-app` and using the `--next` flag you'll automatically get these choices in the `yarn new` command, but if you want to bring these templates to an older app, you can add the following to your root `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"scripts": {
|
||||
...
|
||||
"new": "backstage-cli new"
|
||||
},
|
||||
"backstage": {
|
||||
"cli": {
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
The `yarn new` command now defaults to the new frontend system templates for frontend plugins. If you have an older app that was created before this change, you can simply update the `@backstage/cli-module-new` package to get access to the new templates.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -83,6 +83,8 @@ When defining the `templates` array it will override the default set of template
|
||||
"new": {
|
||||
"templates": [
|
||||
"@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",
|
||||
@@ -90,6 +92,7 @@ When defining the `templates` array it will override the default set of template
|
||||
"@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"
|
||||
]
|
||||
|
||||
@@ -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