cli: add initial role types and detection

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-01-21 01:52:05 +01:00
parent bbd59ed355
commit 0a2719a5ab
5 changed files with 470 additions and 1 deletions
+2 -1
View File
@@ -112,7 +112,8 @@
"webpack-node-externals": "^3.0.0",
"yaml": "^1.10.0",
"yml-loader": "^2.1.0",
"yn": "^4.0.0"
"yn": "^4.0.0",
"zod": "^3.11.6"
},
"devDependencies": {
"@backstage/backend-common": "^0.10.6",
@@ -0,0 +1,295 @@
/*
* Copyright 2022 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 { detectPackageRole } from './detectPackageRole';
describe('detectPackageRole', () => {
it('detects explicit package roles', () => {
expect(
detectPackageRole({
backstage: {
role: 'web-library',
},
}),
).toEqual({
role: 'web-library',
platform: 'web',
});
expect(
detectPackageRole({
backstage: {
role: 'app',
},
}),
).toEqual({
role: 'app',
platform: 'web',
});
expect(() =>
detectPackageRole({
name: 'test',
backstage: {},
}),
).toThrow('Package test must specify a role in the "backstage" field');
expect(() =>
detectPackageRole({
name: 'test',
backstage: { role: 'invalid' },
}),
).toThrow(`Unknown role 'invalid' in package test`);
});
it('detects the role of example-app', () => {
expect(
detectPackageRole({
name: 'example-app',
private: true,
bundled: true,
scripts: {
start: 'backstage-cli app:serve',
build: 'backstage-cli app:build',
clean: 'backstage-cli clean',
test: 'backstage-cli test',
'test:e2e':
'start-server-and-test start http://localhost:3000 cy:dev',
'test:e2e:ci':
'start-server-and-test start http://localhost:3000 cy:run',
lint: 'backstage-cli lint',
'cy:dev': 'cypress open',
'cy:run': 'cypress run',
},
}),
).toEqual({
role: 'app',
platform: 'web',
});
});
it('detects the role of example-backend', () => {
expect(
detectPackageRole({
name: 'example-backend',
main: 'dist/index.cjs.js',
types: 'src/index.ts',
scripts: {
build: 'backstage-cli backend:bundle',
'build-image':
'docker build ../.. -f Dockerfile --tag example-backend',
start: 'backstage-cli backend:dev',
lint: 'backstage-cli lint',
test: 'backstage-cli test',
clean: 'backstage-cli clean',
'migrate:create': 'knex migrate:make -x ts',
},
}),
).toEqual({
role: 'backend',
platform: 'node',
});
});
it('detects the role of @backstage/plugin-catalog', () => {
expect(
detectPackageRole({
name: '@backstage/plugin-catalog',
main: 'src/index.ts',
types: 'src/index.ts',
publishConfig: {
access: 'public',
main: 'dist/index.esm.js',
types: 'dist/index.d.ts',
},
scripts: {
build: 'backstage-cli plugin:build',
start: 'backstage-cli plugin:serve',
lint: 'backstage-cli lint',
test: 'backstage-cli test',
diff: 'backstage-cli plugin:diff',
prepack: 'backstage-cli prepack',
postpack: 'backstage-cli postpack',
clean: 'backstage-cli clean',
},
}),
).toEqual({
role: 'plugin-frontend',
platform: 'web',
});
});
it('detects the role of @backstage/plugin-catalog-backend', () => {
expect(
detectPackageRole({
name: '@backstage/plugin-catalog-backend',
main: 'src/index.ts',
types: 'src/index.ts',
publishConfig: {
access: 'public',
main: 'dist/index.cjs.js',
types: 'dist/index.d.ts',
},
scripts: {
start: 'backstage-cli backend:dev',
build: 'backstage-cli backend:build',
lint: 'backstage-cli lint',
test: 'backstage-cli test',
prepack: 'backstage-cli prepack',
postpack: 'backstage-cli postpack',
clean: 'backstage-cli clean',
},
}),
).toEqual({
role: 'plugin-backend',
platform: 'node',
});
});
it('detects the role of @backstage/plugin-catalog-react', () => {
expect(
detectPackageRole({
name: '@backstage/plugin-catalog-react',
main: 'src/index.ts',
types: 'src/index.ts',
publishConfig: {
access: 'public',
main: 'dist/index.esm.js',
types: 'dist/index.d.ts',
},
scripts: {
build: 'backstage-cli build',
lint: 'backstage-cli lint',
test: 'backstage-cli test',
prepack: 'backstage-cli prepack',
postpack: 'backstage-cli postpack',
clean: 'backstage-cli clean',
},
}),
).toEqual({
role: 'web-library',
platform: 'web',
});
});
it('detects the role of @backstage/plugin-catalog-common', () => {
expect(
detectPackageRole({
name: '@backstage/plugin-catalog-common',
main: 'src/index.ts',
types: 'src/index.ts',
publishConfig: {
access: 'public',
main: 'dist/index.cjs.js',
module: 'dist/index.esm.js',
types: 'dist/index.d.ts',
},
scripts: {
build: 'backstage-cli build',
lint: 'backstage-cli lint',
test: 'backstage-cli test --passWithNoTests',
prepack: 'backstage-cli prepack',
postpack: 'backstage-cli postpack',
clean: 'backstage-cli clean',
},
}),
).toEqual({
role: 'common-library',
platform: 'common',
});
});
it('detects the role of @backstage/plugin-catalog-backend-module-ldap', () => {
expect(
detectPackageRole({
name: '@backstage/plugin-catalog-backend-module-ldap',
main: 'src/index.ts',
types: 'src/index.ts',
publishConfig: {
access: 'public',
main: 'dist/index.cjs.js',
types: 'dist/index.d.ts',
},
scripts: {
build: 'backstage-cli backend:build',
lint: 'backstage-cli lint',
test: 'backstage-cli test',
prepack: 'backstage-cli prepack',
postpack: 'backstage-cli postpack',
clean: 'backstage-cli clean',
},
}),
).toEqual({
role: 'plugin-backend-module',
platform: 'node',
});
});
it('detects the role of @backstage/plugin-permission-node', () => {
expect(
detectPackageRole({
name: '@backstage/plugin-permission-node',
main: 'src/index.ts',
types: 'src/index.ts',
homepage: 'https://backstage.io',
publishConfig: {
access: 'public',
main: 'dist/index.cjs.js',
types: 'dist/index.d.ts',
},
scripts: {
build: 'backstage-cli backend:build',
lint: 'backstage-cli lint',
test: 'backstage-cli test',
prepack: 'backstage-cli prepack',
postpack: 'backstage-cli postpack',
clean: 'backstage-cli clean',
},
}),
).toEqual({
role: 'node-library',
platform: 'node',
});
});
it('detects the role of @backstage/plugin-analytics-module-ga', () => {
expect(
detectPackageRole({
name: '@backstage/plugin-analytics-module-ga',
main: 'src/index.ts',
types: 'src/index.ts',
publishConfig: {
access: 'public',
main: 'dist/index.esm.js',
types: 'dist/index.d.ts',
},
scripts: {
build: 'backstage-cli plugin:build',
start: 'backstage-cli plugin:serve',
lint: 'backstage-cli lint',
test: 'backstage-cli test',
diff: 'backstage-cli plugin:diff',
prepack: 'backstage-cli prepack',
postpack: 'backstage-cli postpack',
clean: 'backstage-cli clean',
},
}),
).toEqual({
role: 'plugin-frontend-module',
platform: 'web',
});
});
});
@@ -0,0 +1,117 @@
/*
* Copyright 2022 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 { z } from 'zod';
import { PackageRoleInfo } from './types';
const packageRoles: PackageRoleInfo[] = [
{ role: 'app', platform: 'web' },
{ role: 'backend', platform: 'node' },
{ role: 'cli', platform: 'node' },
{ role: 'web-library', platform: 'web' },
{ role: 'node-library', platform: 'node' },
{ role: 'common-library', platform: 'common' },
{ role: 'plugin-frontend', platform: 'web' },
{ role: 'plugin-frontend-module', platform: 'web' },
{ role: 'plugin-backend', platform: 'node' },
{ role: 'plugin-backend-module', platform: 'node' },
];
const roleMap = Object.fromEntries(packageRoles.map(i => [i.role, i]));
const backstagePackageSchema = z.object({
name: z.string().optional(),
scripts: z
.object({
start: z.string().optional(),
build: z.string().optional(),
})
.optional(),
backstage: z
.object({
role: z.string().optional(),
})
.optional(),
publishConfig: z
.object({
main: z.string().optional(),
types: z.string().optional(),
module: z.string().optional(),
})
.optional(),
main: z.string().optional(),
types: z.string().optional(),
module: z.string().optional(),
});
export function detectPackageRole(
pkgJson: unknown,
): PackageRoleInfo | undefined {
const pkg = backstagePackageSchema.parse(pkgJson);
// If there's an explicit role, use that.
if (pkg.backstage) {
const { role } = pkg.backstage;
if (!role) {
throw new Error(
`Package ${pkg.name} must specify a role in the "backstage" field`,
);
}
const roleInfo = packageRoles.find(r => r.role === role);
if (!roleInfo) {
throw new Error(`Unknown role '${role}' in package ${pkg.name}`);
}
return roleInfo;
}
if (pkg.scripts?.start?.includes('app:serve')) {
return roleMap.app;
}
if (pkg.scripts?.build?.includes('backend:bundle')) {
return roleMap.backend;
}
if (pkg.name?.includes('plugin') && pkg.name?.includes('backend-module')) {
return roleMap['plugin-backend-module'];
}
if (pkg.name?.includes('plugin') && pkg.name?.includes('module')) {
return roleMap['plugin-frontend-module'];
}
if (pkg.scripts?.start?.includes('plugin:serve')) {
return roleMap['plugin-frontend'];
}
if (pkg.scripts?.start?.includes('backend:dev')) {
return roleMap['plugin-backend'];
}
const mainEntry = pkg.publishConfig?.main || pkg.main;
const moduleEntry = pkg.publishConfig?.module || pkg.module;
const typesEntry = pkg.publishConfig?.types || pkg.types;
if (typesEntry) {
if (mainEntry && moduleEntry) {
return roleMap['common-library'];
}
if (moduleEntry || mainEntry?.endsWith('.esm.js')) {
return roleMap['web-library'];
}
if (mainEntry) {
return roleMap['node-library'];
}
} else if (mainEntry) {
return roleMap.cli;
}
return undefined;
}
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright 2022 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.
*/
export type {
PackageRoleInfo,
PackagePlatform,
PackageRoleName,
} from './types';
export { detectPackageRole } from './detectPackageRole';
+34
View File
@@ -0,0 +1,34 @@
/*
* Copyright 2022 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.
*/
export type PackageRoleName =
| 'app'
| 'backend'
| 'cli'
| 'web-library'
| 'node-library'
| 'common-library'
| 'plugin-frontend'
| 'plugin-frontend-module'
| 'plugin-backend'
| 'plugin-backend-module';
export type PackagePlatform = 'node' | 'web' | 'common';
export interface PackageRoleInfo {
role: PackageRoleName;
platform: PackagePlatform;
}