Merge branch 'backstage:master' into master

This commit is contained in:
matteosilv
2022-09-02 11:41:41 +02:00
committed by GitHub
56 changed files with 1406 additions and 350 deletions
+25 -29
View File
@@ -12,8 +12,7 @@ Options:
-h, --help
Commands:
create [options]
create-plugin [options]
new [options]
plugin:diff [options]
test
config:docs [options]
@@ -100,20 +99,6 @@ Options:
-h, --help
```
### `backstage-cli create`
```
Usage: backstage-cli create [options]
Options:
--select <name>
--option <name>=<value>
--scope <scope>
--npm-registry <URL>
--no-private
-h, --help
```
### `backstage-cli create-github-app`
```
@@ -123,19 +108,6 @@ Options:
-h, --help
```
### `backstage-cli create-plugin`
```
Usage: backstage-cli create-plugin [options]
Options:
--backend
--scope <scope>
--npm-registry <URL>
--no-private
-h, --help
```
### `backstage-cli info`
```
@@ -197,6 +169,20 @@ Options:
-h, --help
```
### `backstage-cli new`
```
Usage: backstage-cli new [options]
Options:
--select <name>
--option <name>=<value>
--scope <scope>
--npm-registry <URL>
--no-private
-h, --help
```
### `backstage-cli package`
```
@@ -415,6 +401,7 @@ Options:
Commands:
build [options]
lint [options]
clean
help [command]
```
@@ -429,6 +416,15 @@ Options:
-h, --help
```
### `backstage-cli repo clean`
```
Usage: backstage-cli repo clean [options]
Options:
-h, --help
```
### `backstage-cli repo lint`
```
+26
View File
@@ -248,6 +248,32 @@
"description": "The port that the frontend should be bound to. Only used for local development."
}
}
},
"https": {
"type": "object",
"description": "Only used for local development. The https object is passed to webpack in order to enable using https on localhost.",
"properties": {
"certificate": {
"type": "object",
"description": "Parent object containing certificate and the private key",
"required": [
"key",
"cert"
],
"properties": {
"key": {
"type": "string",
"visibility": "secret",
"description": "Https Certificate private key. Use $file to load in a file"
},
"cert": {
"type": "string",
"visibility": "secret",
"description": "Https Certificate. Use $file to load in a file"
}
}
}
}
}
}
}
+33 -4
View File
@@ -60,6 +60,11 @@ export function registerRepoCommand(program: Command) {
.option('--fix', 'Attempt to automatically fix violations')
.action(lazy(() => import('./repo/lint').then(m => m.command)));
command
.command('clean')
.description('Delete cache and output directories')
.action(lazy(() => import('./repo/clean').then(m => m.command)));
command
.command('list-deprecations', { hidden: true })
.description('List deprecations. [EXPERIMENTAL]')
@@ -193,7 +198,7 @@ export function registerMigrateCommand(program: Command) {
export function registerCommands(program: Command) {
program
.command('create')
.command('new')
.storeOptionsAsProperties(false)
.description(
'Open up an interactive guide to creating new things in your app',
@@ -214,15 +219,39 @@ export function registerCommands(program: Command) {
'The package registry to use for new packages',
)
.option('--no-private', 'Do not mark new packages as private')
.action(lazy(() => import('./create/create').then(m => m.default)));
.action(lazy(() => import('./new/new').then(m => m.default)));
program
.command('create-plugin')
.command('create', { hidden: true })
.storeOptionsAsProperties(false)
.description(
'Open up an interactive guide to creating new things in your app [DEPRECATED]',
)
.option(
'--select <name>',
'Select the thing you want to be creating upfront',
)
.option(
'--option <name>=<value>',
'Pre-fill options for the creation process',
(opt, arr: string[]) => [...arr, opt],
[],
)
.option('--scope <scope>', 'The scope to use for new packages')
.option(
'--npm-registry <URL>',
'The package registry to use for new packages',
)
.option('--no-private', 'Do not mark new packages as private')
.action(lazy(() => import('./new/new').then(m => m.default)));
program
.command('create-plugin', { hidden: true })
.option(
'--backend',
'Create plugin with the backend dependencies as default',
)
.description('Creates a new plugin in the current repository')
.description('Creates a new plugin in the current repository [DEPRECATED]')
.option('--scope <scope>', 'npm scope')
.option('--npm-registry <URL>', 'npm registry URL')
.option('--no-private', 'Public npm package')
@@ -18,7 +18,7 @@ import os from 'os';
import fs from 'fs-extra';
import { join as joinPath } from 'path';
import { OptionValues } from 'commander';
import { FactoryRegistry } from '../../lib/create/FactoryRegistry';
import { FactoryRegistry } from '../../lib/new/FactoryRegistry';
import { paths } from '../../lib/paths';
import { assertError } from '@backstage/errors';
import { Task } from '../../lib/tasks';
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { execFile as execFileCb } from 'child_process';
import fs from 'fs-extra';
import { resolve as resolvePath } from 'path';
import { promisify } from 'util';
import { PackageGraph } from '../../lib/monorepo';
import { paths } from '../../lib/paths';
const execFile = promisify(execFileCb);
export async function command(): Promise<void> {
const packages = await PackageGraph.listTargetPackages();
await fs.remove(paths.resolveTargetRoot('dist'));
await fs.remove(paths.resolveTargetRoot('dist-types'));
await fs.remove(paths.resolveTargetRoot('coverage'));
await Promise.all(
Array.from(Array(10), async () => {
while (packages.length > 0) {
const pkg = packages.pop()!;
const cleanScript = pkg.packageJson.scripts?.clean;
if (
cleanScript === 'backstage-cli clean' ||
cleanScript === 'backstage-cli package clean'
) {
await fs.remove(resolvePath(pkg.dir, 'dist'));
await fs.remove(resolvePath(pkg.dir, 'dist-types'));
await fs.remove(resolvePath(pkg.dir, 'coverage'));
} else if (cleanScript) {
const result = await execFile('yarn', ['run', 'clean'], {
cwd: pkg.dir,
shell: true,
});
process.stdout.write(result.stdout);
process.stderr.write(result.stderr);
}
}
}),
);
}
+20 -10
View File
@@ -174,6 +174,8 @@ describe('bump', () => {
'unlocking @backstage/core@^1.0.3 ~> 1.0.6',
'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
'bumping @backstage/core in a to ^1.0.6',
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage/theme in b to ^2.0.0',
'Running yarn install to install new versions',
'⚠️ The following packages may have breaking changes:',
@@ -210,15 +212,15 @@ describe('bump', () => {
expect(packageA).toEqual({
name: 'a',
dependencies: {
'@backstage/core': '^1.0.5', // not bumped since new version is within range
'@backstage/core': '^1.0.6',
},
});
const packageB = await fs.readJson('/packages/b/package.json');
expect(packageB).toEqual({
name: 'b',
dependencies: {
'@backstage/core': '^1.0.3', // not bumped
'@backstage/theme': '^2.0.0', // bumped since newer
'@backstage/core': '^1.0.6',
'@backstage/theme': '^2.0.0',
},
});
});
@@ -297,6 +299,8 @@ describe('bump', () => {
'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
'bumping @backstage/theme in b to ^5.0.0',
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage/core in a to ^1.0.6',
'Your project is now at version 0.0.1, which has been written to backstage.json',
'Running yarn install to install new versions',
'⚠️ The following packages may have breaking changes:',
@@ -333,15 +337,15 @@ describe('bump', () => {
expect(packageA).toEqual({
name: 'a',
dependencies: {
'@backstage/core': '^1.0.5', // not bumped since new version is within range
'@backstage/core': '^1.0.6',
},
});
const packageB = await fs.readJson('/packages/b/package.json');
expect(packageB).toEqual({
name: 'b',
dependencies: {
'@backstage/core': '^1.0.3', // not bumped
'@backstage/theme': '^5.0.0', // bumped since newer
'@backstage/core': '^1.0.6',
'@backstage/theme': '^5.0.0',
},
});
});
@@ -544,6 +548,8 @@ describe('bump', () => {
'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
'bumping @backstage/theme in b to ^5.0.0',
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage/core in a to ^1.0.6',
'Your project is now at version 1.0.0, which has been written to backstage.json',
'Running yarn install to install new versions',
'⚠️ The following packages may have breaking changes:',
@@ -650,7 +656,11 @@ describe('bump', () => {
'unlocking @backstage-extra/custom@^1.0.1 ~> 1.1.0',
'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
'bumping @backstage/core in a to ^1.0.6',
'bumping @backstage-extra/custom in a to ^1.1.0',
'bumping @backstage-extra/custom-two in a to ^2.0.0',
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage-extra/custom in b to ^1.1.0',
'bumping @backstage-extra/custom-two in b to ^2.0.0',
'bumping @backstage/theme in b to ^2.0.0',
'Skipping backstage.json update as custom pattern is used',
@@ -690,9 +700,9 @@ describe('bump', () => {
expect(packageA).toEqual({
name: 'a',
dependencies: {
'@backstage-extra/custom': '^1.0.1',
'@backstage-extra/custom': '^1.1.0',
'@backstage-extra/custom-two': '^2.0.0',
'@backstage/core': '^1.0.5', // not bumped since new version is within range
'@backstage/core': '^1.0.6',
},
});
const packageB = await fs.readJson('/packages/b/package.json');
@@ -701,8 +711,8 @@ describe('bump', () => {
dependencies: {
'@backstage-extra/custom': '^1.1.0',
'@backstage-extra/custom-two': '^2.0.0',
'@backstage/core': '^1.0.3', // not bumped
'@backstage/theme': '^2.0.0', // bumped since newer
'@backstage/core': '^1.0.6',
'@backstage/theme': '^2.0.0',
},
});
});
@@ -124,13 +124,6 @@ export default async (opts: OptionValues) => {
}
for (const pkg of pkgs) {
if (semver.satisfies(target, pkg.range)) {
if (semver.minVersion(pkg.range)?.version !== target) {
unlocked.push({ name, range: pkg.range, target });
}
continue;
}
versionBumps.set(
pkg.name,
(versionBumps.get(pkg.name) ?? []).concat({
+10 -1
View File
@@ -40,6 +40,7 @@ export async function serveBundle(options: ServeOptions) {
isDev: true,
baseUrl: url,
});
const compiler = webpack(config);
const server = new WebpackDevServer(
@@ -60,7 +61,15 @@ export async function serveBundle(options: ServeOptions) {
// See https://github.com/facebookincubator/create-react-app/issues/387.
disableDotRule: true,
},
https: url.protocol === 'https:',
https:
url.protocol === 'https:'
? {
cert: options.backendConfig.getString(
'app.https.certificate.cert',
),
key: options.backendConfig.getString('app.https.certificate.key'),
}
: false,
host,
port,
proxy: pkg.proxy,
+1
View File
@@ -31,6 +31,7 @@ export type ServeOptions = BundlingPathsOptions & {
checksEnabled: boolean;
frontendConfig: Config;
frontendAppConfigs: AppConfig[];
backendConfig: Config;
};
export type BuildOptions = BundlingPathsOptions & {
+4
View File
@@ -96,11 +96,15 @@ export async function loadCliConfig(options: Options) {
});
const frontendConfig = ConfigReader.fromConfigs(frontendAppConfigs);
const backendAppConfigs = schema.process(appConfigs);
const backendConfig = ConfigReader.fromConfigs(backendAppConfigs);
return {
schema,
appConfigs,
frontendConfig,
frontendAppConfigs,
backendConfig,
};
} catch (error) {
const maybeSchemaError = error as Error & { messages?: string[] };
@@ -453,18 +453,19 @@ describe('discovery', () => {
);
});
it('should throw elements within element prop contains a path', () => {
expect(() => {
traverseElementTree({
root: <Route path="foo" element={<Extension3 path="bar" />} />,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
});
}).toThrow(
'Elements within the element prop tree may not have paths, found "bar"',
);
it('should ignore path props within route elements', () => {
const { routing } = traverseElementTree({
root: <Route path="foo" element={<Extension1 path="bar" />} />,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
});
expect(sortedEntries(routing.paths)).toEqual([[ref1, 'foo']]);
expect(sortedEntries(routing.parents)).toEqual([[ref1, undefined]]);
expect(routing.objects).toEqual([
routeObj('foo', [ref1], [], undefined, plugin),
]);
});
it('should throw when a routable extension does not have a path set', () => {
@@ -53,6 +53,8 @@ interface RoutingV2CollectorContext {
isElementAncestor?: boolean;
}
// This collects all the mount points and their plugins within an element tree.
// Unlike regular traversal this ignores all other things, like path props and mount point gatherers.
function collectSubTree(
node: ReactNode,
entries = new Array<{ routeRef: RouteRef; plugin?: BackstagePlugin }>(),
@@ -62,12 +64,6 @@ function collectSubTree(
return;
}
if (element.props.path) {
throw new Error(
`Elements within the element prop tree may not have paths, found "${element.props.path}"`,
);
}
const routeRef = getComponentData<RouteRef>(element, 'core.mountPoint');
if (routeRef) {
const plugin = getComponentData<BackstagePlugin>(element, 'core.plugin');
@@ -87,6 +83,16 @@ export const routingV2Collector = createCollector(
objects: new Array<BackstageRouteObject>(),
}),
(acc, node, parent, ctx?: RoutingV2CollectorContext) => {
// If we're in an element prop, ignore everything
if (ctx?.isElementAncestor) {
return ctx;
}
// Start ignoring everything if we enter an element prop
if (parent?.props.element === node) {
return { ...ctx, isElementAncestor: true };
}
const pathProp: unknown = node.props?.path;
const mountPoint = getComponentData<RouteRef>(node, 'core.mountPoint');
@@ -98,15 +104,6 @@ export const routingV2Collector = createCollector(
);
}
// If we're in an element prop, ignore everything
if (ctx?.isElementAncestor) {
return ctx;
}
// Start ignoring everything if we enter an element prop
if (parent?.props.element === node) {
return { ...ctx, isElementAncestor: true };
}
const parentChildren = ctx?.obj?.children ?? acc.objects;
if (pathProp !== undefined) {
@@ -13,7 +13,7 @@
"build-image": "yarn workspace backend build-image",
"tsc": "tsc",
"tsc:full": "tsc --skipLibCheck false --incremental false",
"clean": "backstage-cli clean && lerna run clean",
"clean": "backstage-cli repo clean",
"diff": "lerna run diff --",
"test": "backstage-cli test",
"test:all": "lerna run test -- --coverage",
@@ -21,7 +21,7 @@
"lint:all": "backstage-cli repo lint",
"prettier:check": "prettier --check .",
"create-plugin": "backstage-cli create-plugin --scope internal",
"remove-plugin": "backstage-cli remove-plugin"
"new": "backstage-cli new --scope internal"
},
"workspaces": {
"packages": [