From 520a383e5e86d4ad3dd85c604dca2e0060ce0f77 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Fri, 16 Aug 2024 16:20:03 +0100 Subject: [PATCH 01/12] Added step to backstage-cli repo fix --publish command Added a fixer step to the backstage-cli fix command that will annotate default export features to the package json backstage metadata Signed-off-by: Harrison Hogg --- .changeset/clever-paws-stare.md | 6 + .changeset/healthy-moose-tease.md | 63 ++++ packages/cli-node/api-report.md | 59 ++++ .../cli-node/src/monorepo/PackageGraph.ts | 63 ++++ packages/cli-node/src/monorepo/index.ts | 4 + packages/cli/package.json | 1 + packages/cli/src/commands/repo/fix.ts | 54 +++- packages/cli/src/lib/features.test.ts | 273 ++++++++++++++++++ packages/cli/src/lib/features.ts | 163 +++++++++++ .../cli/src/tests/createFeatureEnvironment.ts | 196 +++++++++++++ plugins/api-docs/package.json | 6 + plugins/app-backend/package.json | 6 + .../catalog-backend-module-aws/package.json | 8 +- .../catalog-backend-module-azure/package.json | 8 +- .../package.json | 8 +- .../package.json | 8 +- .../catalog-backend-module-gcp/package.json | 7 +- .../package.json | 8 +- .../package.json | 8 +- .../package.json | 8 +- .../package.json | 8 +- .../package.json | 8 +- .../package.json | 8 +- .../package.json | 7 +- plugins/catalog-backend/package.json | 6 + plugins/catalog-graph/package.json | 6 + plugins/catalog-import/package.json | 6 + plugins/catalog/package.json | 6 + plugins/devtools/package.json | 6 + .../package.json | 8 +- .../events-backend-module-azure/package.json | 8 +- .../package.json | 8 +- .../events-backend-module-gerrit/package.json | 8 +- plugins/events-backend/package.json | 6 + plugins/home/package.json | 6 + plugins/kubernetes-backend/package.json | 6 + plugins/kubernetes/package.json | 6 + plugins/org/package.json | 6 + plugins/permission-backend/package.json | 6 + plugins/proxy-backend/package.json | 6 + .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- .../package.json | 7 +- plugins/scaffolder-backend/package.json | 6 + plugins/scaffolder/package.json | 6 + .../package.json | 8 +- .../package.json | 8 +- .../package.json | 8 +- plugins/search-backend-module-pg/package.json | 8 +- .../package.json | 8 +- plugins/search-backend/package.json | 6 + plugins/search/package.json | 6 + plugins/techdocs-backend/package.json | 6 + plugins/techdocs/package.json | 6 + plugins/user-settings-backend/package.json | 6 + plugins/user-settings/package.json | 6 + yarn.lock | 1 + 68 files changed, 1242 insertions(+), 37 deletions(-) create mode 100644 .changeset/clever-paws-stare.md create mode 100644 .changeset/healthy-moose-tease.md create mode 100644 packages/cli/src/lib/features.test.ts create mode 100644 packages/cli/src/lib/features.ts create mode 100644 packages/cli/src/tests/createFeatureEnvironment.ts diff --git a/.changeset/clever-paws-stare.md b/.changeset/clever-paws-stare.md new file mode 100644 index 0000000000..b3cb421723 --- /dev/null +++ b/.changeset/clever-paws-stare.md @@ -0,0 +1,6 @@ +--- +'@backstage/cli-node': patch +'@backstage/cli': patch +--- + +Added a new step to the `backstage-cli repo fix --publish` command that will annotate default export features to the 'backstage' metadata within plugin package.json. This is to help with identifying the declarative integration points for plugins without needing to fetch or run the plugins first. diff --git a/.changeset/healthy-moose-tease.md b/.changeset/healthy-moose-tease.md new file mode 100644 index 0000000000..1f9ba3952f --- /dev/null +++ b/.changeset/healthy-moose-tease.md @@ -0,0 +1,63 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-events-backend-module-bitbucket-cloud': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/plugin-scaffolder-backend-module-sentry': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +'@backstage/plugin-catalog-backend-module-puppetdb': patch +'@backstage/plugin-scaffolder-backend-module-azure': patch +'@backstage/plugin-scaffolder-backend-module-gitea': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-search-backend-module-techdocs': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-events-backend-module-aws-sqs': patch +'@backstage/plugin-scaffolder-backend-module-gcp': patch +'@backstage/plugin-search-backend-module-catalog': patch +'@backstage/plugin-search-backend-module-explore': patch +'@backstage/plugin-catalog-backend-module-azure': patch +'@backstage/plugin-events-backend-module-gerrit': patch +'@backstage/plugin-events-backend-module-github': patch +'@backstage/plugin-events-backend-module-gitlab': patch +'@backstage/plugin-events-backend-module-azure': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-catalog-backend-module-gcp': patch +'@backstage/plugin-search-backend-module-pg': patch +'@backstage/plugin-user-settings-backend': patch +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-search-backend': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-proxy-backend': patch +'@backstage/plugin-user-settings': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-devtools': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-search': patch +'@backstage/plugin-home': patch +'@backstage/plugin-org': patch +--- + +Added `features` metadata to Package JSON diff --git a/packages/cli-node/api-report.md b/packages/cli-node/api-report.md index f22583dd25..519fe12bf9 100644 --- a/packages/cli-node/api-report.md +++ b/packages/cli-node/api-report.md @@ -12,6 +12,15 @@ export type BackstagePackage = { packageJson: BackstagePackageJson; }; +// @public +export type BackstagePackageFeature = { + path?: string; + type: BackstagePackageFeatureType; +}; + +// @public +export type BackstagePackageFeatureType = (typeof packageFeatureTypes)[number]; + // @public export interface BackstagePackageJson { // (undocumented) @@ -22,6 +31,7 @@ export interface BackstagePackageJson { pluginId?: string | null; pluginPackage?: string; pluginPackages?: string[]; + features?: BackstagePackageFeature[]; }; // (undocumented) bundled?: boolean; @@ -88,6 +98,31 @@ export class GitUtils { // @public export function isMonoRepo(): Promise; +// @public +export const isValidPackageFeatureType: ( + value: string, +) => value is + | '@backstage/TranslationResource' + | '@backstage/TranslationMessages' + | '@backstage/TranslationRef' + | '@backstage/RouteRef' + | '@backstage/SubRouteRef' + | '@backstage/ExternalRouteRef' + | '@backstage/ExtensionDataValue' + | '@backstage/ExtensionDataRef' + | '@backstage/ExtensionInput' + | '@backstage/ExtensionDefinition' + | '@backstage/Extension' + | '@backstage/ExtensionOverrides' + | '@backstage/FrontendPlugin' + | '@backstage/BackstagePlugin' + | '@backstage/FrontendModule' + | '@backstage/BackendFeatureFactory' + | '@backstage/BackendFeature' + | '@backstage/ServiceRef' + | '@backstage/BackstageCredentials' + | '@backstage/ExtensionPoint'; + // @public export class Lockfile { createSimplifiedDependencyGraph(): Map>; @@ -109,6 +144,30 @@ export type LockfileDiffEntry = { range: string; }; +// @public +export const packageFeatureTypes: readonly [ + '@backstage/BackendFeature', + '@backstage/BackendFeatureFactory', + '@backstage/BackstageCredentials', + '@backstage/BackstagePlugin', + '@backstage/Extension', + '@backstage/ExtensionDataRef', + '@backstage/ExtensionDataValue', + '@backstage/ExtensionDefinition', + '@backstage/ExtensionInput', + '@backstage/ExtensionOverrides', + '@backstage/ExtensionPoint', + '@backstage/ExternalRouteRef', + '@backstage/FrontendPlugin', + '@backstage/FrontendModule', + '@backstage/RouteRef', + '@backstage/ServiceRef', + '@backstage/SubRouteRef', + '@backstage/TranslationMessages', + '@backstage/TranslationRef', + '@backstage/TranslationResource', +]; + // @public export class PackageGraph extends Map { collectPackageNames( diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index 69ec99c2dd..374be3add7 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -22,6 +22,64 @@ import { GitUtils } from '../git'; import { Lockfile } from './Lockfile'; import { JsonValue } from '@backstage/types'; +/** + * All Backstage system features + * + * @public + */ +export const packageFeatureTypes = [ + '@backstage/BackendFeature', + '@backstage/BackendFeatureFactory', + '@backstage/BackstageCredentials', + '@backstage/BackstagePlugin', + '@backstage/Extension', + '@backstage/ExtensionDataRef', + '@backstage/ExtensionDataValue', + '@backstage/ExtensionDefinition', + '@backstage/ExtensionInput', + '@backstage/ExtensionOverrides', + '@backstage/ExtensionPoint', + '@backstage/ExternalRouteRef', + '@backstage/FrontendPlugin', + '@backstage/FrontendModule', + '@backstage/RouteRef', + '@backstage/ServiceRef', + '@backstage/SubRouteRef', + '@backstage/TranslationMessages', + '@backstage/TranslationRef', + '@backstage/TranslationResource', +] as const; + +/** + * Checks if a string is a valid package feature type. + * + * @public + */ +export const isValidPackageFeatureType = ( + value: string, +): value is BackstagePackageFeatureType => { + return packageFeatureTypes.includes(value as any); +}; + +/** + * The type of a Backstage system feature + * + * @public + */ +export type BackstagePackageFeatureType = (typeof packageFeatureTypes)[number]; + +/** + * Metadata relating to an exported Backstage system component + * + * @public + */ +export type BackstagePackageFeature = { + // The export path, if omitted then it is the default package export + path?: string; + // The type of the export + type: BackstagePackageFeatureType; +}; + /** * Known fields in Backstage package.json files. * @@ -70,6 +128,11 @@ export interface BackstagePackageJson { * All packages that are part of the plugin. Must always and only be set for plugin packages and plugin library packages. */ pluginPackages?: string[]; + + /** + * The Backstage system components exported from this package + */ + features?: BackstagePackageFeature[]; }; exports?: JsonValue; diff --git a/packages/cli-node/src/monorepo/index.ts b/packages/cli-node/src/monorepo/index.ts index fdb8395c16..b0e595077d 100644 --- a/packages/cli-node/src/monorepo/index.ts +++ b/packages/cli-node/src/monorepo/index.ts @@ -16,10 +16,14 @@ export { isMonoRepo } from './isMonoRepo'; export { + packageFeatureTypes, + isValidPackageFeatureType, PackageGraph, type PackageGraphNode, type BackstagePackage, type BackstagePackageJson, + type BackstagePackageFeature, + type BackstagePackageFeatureType, } from './PackageGraph'; export { Lockfile, diff --git a/packages/cli/package.json b/packages/cli/package.json index 9208b5e70e..e33fddee90 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -147,6 +147,7 @@ "swc-loader": "^0.2.3", "tar": "^6.1.12", "terser-webpack-plugin": "^5.1.3", + "ts-morph": "^23.0.0", "util": "^0.12.3", "webpack": "^5.70.0", "webpack-dev-server": "^5.0.0", diff --git a/packages/cli/src/commands/repo/fix.ts b/packages/cli/src/commands/repo/fix.ts index a55440c53f..68b07564fb 100644 --- a/packages/cli/src/commands/repo/fix.ts +++ b/packages/cli/src/commands/repo/fix.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { findPaths } from '@backstage/cli-common'; import { BackstagePackage, BackstagePackageJson, @@ -23,9 +24,13 @@ import { } from '@backstage/cli-node'; import { OptionValues } from 'commander'; import fs from 'fs-extra'; +import isEqual from 'lodash/isEqual'; import { resolve as resolvePath, posix, relative as relativePath } from 'path'; +import { Project } from 'ts-morph'; import { paths } from '../../lib/paths'; import { publishPreflightCheck } from '../../lib/publishing'; +import { getFeaturesMetadata } from '../../lib/features'; +import { readEntryPoints } from '../../lib/entryPoints'; /** * A mutable object representing a package.json file with potential fixes. @@ -426,7 +431,46 @@ export function fixPluginPackages( } } -type PackageFixer = (pkg: FixablePackage, packages: FixablePackage[]) => void; +// For each of the defined export locations, we want to annotate +// the backstage field with the exported system components. The +// "exports" field in package.json is a mapping of import paths to file paths. +// +// While the exports field is really flexible, this function will enforce a +// particular structure, which is a Record where the key is the +// import path and the value is the file path. +export function fixPluginFeatures( + pkg: FixablePackage, + _packages: FixablePackage[], + project: Project, +) { + const { dir, packageJson } = pkg; + + if ( + !packageJson.backstage || + !packageJson.backstage.role || + !packageJson.exports + ) { + return; + } + + const entryPoints = readEntryPoints(packageJson); + const { role } = packageJson.backstage; + const featuresMetadata = getFeaturesMetadata(project, role, dir, entryPoints); + + if ( + featuresMetadata.length && + !isEqual(packageJson.backstage.features, featuresMetadata) + ) { + packageJson.backstage.features = featuresMetadata; + pkg.changed = true; + } +} + +type PackageFixer = ( + pkg: FixablePackage, + packages: FixablePackage[], + project: Project, +) => void; export async function command(opts: OptionValues): Promise { const packages = await readFixablePackages(); @@ -440,14 +484,20 @@ export async function command(opts: OptionValues): Promise { fixRepositoryField, fixPluginId, fixPluginPackages, + fixPluginFeatures, // Run the publish preflight check too, to make sure we don't uncover errors during publishing publishPreflightCheck, ); } + const workspaceRoot = findPaths(process.cwd()).targetRoot; + const project = new Project({ + tsConfigFilePath: resolvePath(workspaceRoot, 'tsconfig.json'), + }); + for (const fixer of fixers) { for (const pkg of packages) { - fixer(pkg, packages); + fixer(pkg, packages, project); } } diff --git a/packages/cli/src/lib/features.test.ts b/packages/cli/src/lib/features.test.ts new file mode 100644 index 0000000000..b20ba7c6ac --- /dev/null +++ b/packages/cli/src/lib/features.test.ts @@ -0,0 +1,273 @@ +/* + * Copyright 2024 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 { BackstagePackageFeatureType, PackageRole } from '@backstage/cli-node'; +import createFeatureEnvironment from '../tests/createFeatureEnvironment'; +import { getEntryPointExports, getFeaturesMetadata } from './features'; + +describe('features', () => { + describe('for package role', () => { + // This Record makes sure we're checking all package roles + const packageRoles: Record = { + // Allowed + 'backend-plugin': true, + 'backend-plugin-module': true, + 'frontend-plugin': true, + 'frontend-plugin-module': true, + // Disallowed + frontend: false, + backend: false, + cli: false, + 'web-library': false, + 'node-library': false, + 'common-library': false, + }; + + const allowedPackageRoles = Object.keys(packageRoles).filter( + role => packageRoles[role as PackageRole], + ); + + const disallowedPackageRoles = Object.keys(packageRoles).filter( + role => !packageRoles[role as PackageRole], + ); + + it.each(allowedPackageRoles)(`returns features for %s`, r => { + const { project, role, dir, entryPoints } = createFeatureEnvironment({ + role: r as PackageRole, + }); + + expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([ + { + type: '@backstage/BackendFeature', + }, + ]); + }); + + it.each(disallowedPackageRoles)(`does not return features for %s`, r => { + const { project, role, dir, entryPoints } = createFeatureEnvironment({ + role: r as PackageRole, + }); + + expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([]); + }); + }); + + describe('for feature $$type', () => { + // This Record makes sure we're checking all feature types + const featureTypes: Record = { + // Allowed + '@backstage/BackendFeature': true, + '@backstage/BackstagePlugin': true, + '@backstage/FrontendPlugin': true, + '@backstage/FrontendModule': true, + // Disallowed + '@backstage/BackendFeatureFactory': false, + '@backstage/BackstageCredentials': false, + '@backstage/Extension': false, + '@backstage/ExtensionDataRef': false, + '@backstage/ExtensionDataValue': false, + '@backstage/ExtensionDefinition': false, + '@backstage/ExtensionInput': false, + '@backstage/ExtensionOverrides': false, + '@backstage/ExtensionPoint': false, + '@backstage/ExternalRouteRef': false, + '@backstage/RouteRef': false, + '@backstage/ServiceRef': false, + '@backstage/SubRouteRef': false, + '@backstage/TranslationMessages': false, + '@backstage/TranslationRef': false, + '@backstage/TranslationResource': false, + }; + + const allowedFeatureTypes = Object.keys(featureTypes).filter( + $$type => featureTypes[$$type as BackstagePackageFeatureType], + ); + + const disallowedFeatureTypes = Object.keys(featureTypes).filter( + $$type => !featureTypes[$$type as BackstagePackageFeatureType], + ); + + it.each(allowedFeatureTypes)(`returns features for "%s" $$type`, $$type => { + const { project, role, dir, entryPoints } = createFeatureEnvironment({ + $$type: $$type as BackstagePackageFeatureType, + }); + + expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([ + { + type: $$type, + }, + ]); + }); + + it.each(disallowedFeatureTypes)( + `does not return features for "%s" $$type`, + $$type => { + const { project, role, dir, entryPoints } = createFeatureEnvironment({ + $$type: $$type as BackstagePackageFeatureType, + }); + + expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual( + [], + ); + }, + ); + }); + + describe('export declaration formats', () => { + it('supports format of "export default ..."', () => { + const { + project, + dir, + entryPoints: [entryPoint], + } = createFeatureEnvironment({ + format: 'DefaultExport', + }); + + expect(getEntryPointExports(entryPoint, project, dir)).toEqual([ + { + location: '.', + name: 'default', + type: '@backstage/BackendFeature', + }, + ]); + }); + + it('supports format of "export { default } from ..."', () => { + const { + project, + dir, + entryPoints: [entryPoint], + } = createFeatureEnvironment({ + format: 'DefaultExportLinked', + }); + + expect(getEntryPointExports(entryPoint, project, dir)).toEqual([ + { + location: '.', + name: 'default', + type: '@backstage/BackendFeature', + }, + ]); + }); + + it('supports format of "export { default, ... } from ..."', () => { + const { + project, + dir, + entryPoints: [entryPoint], + } = createFeatureEnvironment({ + format: 'DefaultExportLinkedWithSibling', + }); + + expect(getEntryPointExports(entryPoint, project, dir)).toEqual([ + { + location: '.', + name: 'default', + type: '@backstage/BackendFeature', + }, + { + location: '.', + name: 'test', + type: '@backstage/BackendFeature', + }, + ]); + }); + + it('supports format of "export const foo = ..."', () => { + const { + project, + dir, + entryPoints: [entryPoint], + } = createFeatureEnvironment({ + format: 'NamedExport', + }); + + expect(getEntryPointExports(entryPoint, project, dir)).toEqual([ + { + location: '.', + name: 'test', + type: '@backstage/BackendFeature', + }, + ]); + }); + + it('supports format of "export * from ..."', () => { + const { + project, + dir, + entryPoints: [entryPoint], + } = createFeatureEnvironment({ + format: 'WildCardExport', + }); + + expect(getEntryPointExports(entryPoint, project, dir)).toEqual([ + { + location: '.', + name: 'test', + type: '@backstage/BackendFeature', + }, + ]); + }); + }); + + describe('entry points', () => { + it('returns features for multiple entry points', () => { + const { project, role, dir, entryPoints } = createFeatureEnvironment({ + exports: { + '.': 'src/index.ts', + './alpha': 'src/alpha.ts', + './beta': 'src/beta.ts', + }, + }); + + expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([ + { + type: '@backstage/BackendFeature', + }, + { + path: './alpha', + type: '@backstage/BackendFeature', + }, + { + path: './beta', + type: '@backstage/BackendFeature', + }, + ]); + }); + + it('ignores entry points that are not .ts or .tsx files', () => { + const { project, role, dir, entryPoints } = createFeatureEnvironment({ + exports: { + '.': 'src/index.js', + }, + }); + + expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([]); + }); + }); + + it('only returns default exports', () => { + const { project, role, dir, entryPoints } = createFeatureEnvironment({ + format: 'DefaultExportLinkedWithSibling', + }); + + expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([ + { + type: '@backstage/BackendFeature', + }, + ]); + }); +}); diff --git a/packages/cli/src/lib/features.ts b/packages/cli/src/lib/features.ts new file mode 100644 index 0000000000..55245abea3 --- /dev/null +++ b/packages/cli/src/lib/features.ts @@ -0,0 +1,163 @@ +/* + * 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 { + isValidPackageFeatureType, + PackageRole, + type BackstagePackageFeature, + type BackstagePackageFeatureType, +} from '@backstage/cli-node'; +import { Project, SyntaxKind, ts, Type } from 'ts-morph'; +import { resolve as resolvePath } from 'node:path'; +import { EntryPoint } from './entryPoints'; + +// A list of the package roles we want to extract features for +const targetPackageRoles: PackageRole[] = [ + 'backend-plugin', + 'backend-plugin-module', + 'frontend-plugin', + 'frontend-plugin-module', +]; + +// A list of the feature types we want to extract from the project +// and include in the metadata +const targetFeatureTypes: BackstagePackageFeatureType[] = [ + '@backstage/BackendFeature', + '@backstage/BackstagePlugin', + '@backstage/FrontendPlugin', + '@backstage/FrontendModule', +]; + +// Returns all valid Backstage package features from a project +// for all entry points. +export function getFeaturesMetadata( + project: Project, + role: PackageRole, + dir: string, + entryPoints: EntryPoint[], +): BackstagePackageFeature[] { + if (!targetPackageRoles.includes(role)) { + return []; + } + + return entryPoints + .flatMap(entryPoint => getEntryPointExports(entryPoint, project, dir)) + .filter(isDefaultExport) + .filter(isTargetFeatureType) + .map(toBackstagePackageFeature); +} + +type EntryPointExport = { + name: string; + location: string; + type: BackstagePackageFeatureType; +}; + +// Returns all exports (default and named) from an entry point +// that are valid Backstage package features +export function getEntryPointExports( + { mount: location, path, ext }: EntryPoint, + project: Project, + dir: string, +): EntryPointExport[] { + const fullFilePath = resolvePath(dir, path); + const sourceFile = project.getSourceFile(fullFilePath); + + if (!sourceFile || (ext !== '.ts' && ext !== '.tsx')) { + return []; + } + + const exports = []; + + for (const exportSymbol of sourceFile.getExportSymbols()) { + const declaration = exportSymbol.getDeclarations()[0]; + const exportName = declaration.getSymbol()?.getName(); + let exportType: Type | undefined; + + if (declaration) { + if (declaration.isKind(SyntaxKind.ExportAssignment)) { + exportType = declaration.getExpression().getType(); + } else if (declaration.isKind(SyntaxKind.ExportSpecifier)) { + if (!declaration.isTypeOnly()) { + exportType = declaration.getType(); + } + } else if (declaration.isKind(SyntaxKind.VariableDeclaration)) { + exportType = declaration.getType(); + } + } + + if (exportName && exportType) { + const $$type = getBackstagePackageFeature$$TypeFromType(exportType); + + if ($$type) { + exports.push({ + name: exportName, + location, + type: $$type, + }); + } + } + } + + return exports; +} + +// Given a TS type, returns the Backstage package feature $$type value +function getBackstagePackageFeature$$TypeFromType( + type: Type, +): BackstagePackageFeatureType | null { + // Returns the concrete type of a generic type + const exportType = type.getTargetType() ?? type; + + for (const property of exportType.getProperties()) { + if (property.getName() === '$$type') { + const $$type = property + .getValueDeclaration() + ?.getText() + .match(/(?@backstage\/\w+)/)?.groups?.type; + + if ($$type && isValidPackageFeatureType($$type)) { + return $$type; + } + } + } + + return null; +} + +// Returns whether an export is the default export +function isDefaultExport({ name }: EntryPointExport): boolean { + return name === 'default'; +} + +// Returns whether an export is a valid Backstage package feature type +function isTargetFeatureType({ type }: EntryPointExport): boolean { + return targetFeatureTypes.includes(type); +} + +// Converts an entry point export to a Backstage package feature +function toBackstagePackageFeature({ + type, + location, +}: EntryPointExport): BackstagePackageFeature { + const feature: BackstagePackageFeature = { type }; + + if (location !== '.') { + feature.path = location; + } + + return feature; +} diff --git a/packages/cli/src/tests/createFeatureEnvironment.ts b/packages/cli/src/tests/createFeatureEnvironment.ts new file mode 100644 index 0000000000..6313e6439b --- /dev/null +++ b/packages/cli/src/tests/createFeatureEnvironment.ts @@ -0,0 +1,196 @@ +/* + * Copyright 2024 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 { BackstagePackageFeatureType, PackageRole } from '@backstage/cli-node'; +import path from 'path'; +import { Project } from 'ts-morph'; +import { EntryPoint } from '../lib/entryPoints'; + +type CreateFeatureEnvironmentOptions = { + $$type?: BackstagePackageFeatureType; + exports?: Record; + format?: + | 'DefaultExport' + | 'DefaultExportLinked' + | 'DefaultExportLinkedWithSibling' + | 'NamedExport' + | 'WildCardExport'; + role?: PackageRole; +}; + +type FeatureEnvironment = { + project: Project; + role: PackageRole; + dir: string; + entryPoints: EntryPoint[]; +}; + +type File = { + path: string; + content: string; +}; + +const createForExports = ( + exports: Record, + content: string, +): File[] => { + return Object.entries(exports).map(([_, filePath]) => ({ + path: `./${filePath}`, + content, + })); +}; + +const createTestType = ($$type: BackstagePackageFeatureType): File[] => [ + { + path: './src/createTestType.ts', + content: ` +export interface TestType { + readonly $$type: '${$$type}'; +}; + +export function createTestType(): TestType { + return { + $$type: '${$$type}', + }; +}; + `, + }, +]; + +const createTestDefaultExport = (exports: Record): File[] => + createForExports( + exports, + ` +import { createTestType } from './createTestType'; + +export { TestType } from './createTestType'; +export default createTestType(); + `, + ); + +const createTestDefaultExportLinked = ( + exports: Record, +): File[] => [ + ...createForExports( + exports, + ` +export { TestType } from './createTestType'; +export { default } from './linked'; + `, + ), + { + path: './src/linked.ts', + content: ` +import { createTestType } from './createTestType'; + +export default createTestType(); + `, + }, +]; + +const createTestDefaultExportLinkedWithSibling = ( + exports: Record, +): File[] => [ + ...createForExports( + exports, + ` +export { TestType } from './createTestType'; +export { default, test } from './linked'; + `, + ), + { + path: './src/linked.ts', + content: ` +import { createTestType } from './createTestType'; + +export const test = createTestType(); +export default createTestType(); + `, + }, +]; + +const createTestNamedExport = (exports: Record): File[] => [ + ...createForExports( + exports, + ` +import { createTestType } from './createTestType'; + +export { TestType } from './createTestType'; +export const test = createTestType(); + `, + ), +]; + +const createTestWildCardExport = (exports: Record): File[] => [ + ...createForExports( + exports, + ` +export * from './linked'; + `, + ), + { + path: './src/linked.ts', + content: ` +import { createTestType } from './createTestType'; + +export { TestType } from './createTestType'; +export const test = createTestType(); +export default createTestType(); + `, + }, +]; + +const formatToFiles = { + DefaultExport: createTestDefaultExport, + DefaultExportLinked: createTestDefaultExportLinked, + DefaultExportLinkedWithSibling: createTestDefaultExportLinkedWithSibling, + NamedExport: createTestNamedExport, + WildCardExport: createTestWildCardExport, +}; + +export default function createFeatureEnvironment( + options?: CreateFeatureEnvironmentOptions, +): FeatureEnvironment { + const { + $$type = '@backstage/BackendFeature', + exports = { '.': 'src/index.ts' }, + format = 'DefaultExport', + role = 'backend-plugin', + } = options ?? {}; + + const project = new Project(); + const entryPoints: EntryPoint[] = Object.entries(exports ?? {}).map( + ([mount, filePath]) => ({ + mount, + path: filePath, + name: mount, + ext: path.extname(filePath), + }), + ); + + const files = [...createTestType($$type), ...formatToFiles[format](exports)]; + + for (const file of files) { + project.createSourceFile(file.path, file.content); + } + + return { + project, + role, + dir: project.getFileSystem().getCurrentDirectory(), + entryPoints, + }; +} diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 6858d6a81f..4576a93f85 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -7,6 +7,12 @@ "pluginId": "api-docs", "pluginPackages": [ "@backstage/plugin-api-docs" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 606d5adea6..2d9978e254 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -9,6 +9,12 @@ "@backstage/plugin-app", "@backstage/plugin-app-backend", "@backstage/plugin-app-node" + ], + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 92f11d4b63..c6796e35bd 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index aa0b3663c7..84e3368efb 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 4f0007bb44..5a24423396 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index af12601f87..030d8511ca 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -4,7 +4,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 13904c905b..b9609dbadf 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index caebb75b34..ce2af52542 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -4,7 +4,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 8b05d0f896..f58d08192b 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index ef8d1f9148..126cad0bed 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 4e964ccc9d..1c55b088e1 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index ebdd9bc316..e8c1a41ecb 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 670dbe23a2..5d71befbb5 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index d64b3b468e..565fe94206 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend" + "pluginPackage": "@backstage/plugin-catalog-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 4826758d01..8aac88ac51 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -11,6 +11,12 @@ "@backstage/plugin-catalog-common", "@backstage/plugin-catalog-node", "@backstage/plugin-catalog-react" + ], + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 4e83990c95..14dfe03579 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -6,6 +6,12 @@ "pluginId": "catalog-graph", "pluginPackages": [ "@backstage/plugin-catalog-graph" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 964dbd3c33..fb6d40220f 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -7,6 +7,12 @@ "pluginId": "catalog-import", "pluginPackages": [ "@backstage/plugin-catalog-import" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 317e91fa4d..606ebb16d1 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -11,6 +11,12 @@ "@backstage/plugin-catalog-common", "@backstage/plugin-catalog-node", "@backstage/plugin-catalog-react" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 9e4c0caab6..1b0f14b193 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -8,6 +8,12 @@ "@backstage/plugin-devtools", "@backstage/plugin-devtools-backend", "@backstage/plugin-devtools-common" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index d109490dce..8eac12d934 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -4,7 +4,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "events", - "pluginPackage": "@backstage/plugin-events-backend" + "pluginPackage": "@backstage/plugin-events-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 91cd11d286..376f507c07 100644 --- a/plugins/events-backend-module-azure/package.json +++ b/plugins/events-backend-module-azure/package.json @@ -4,7 +4,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "events", - "pluginPackage": "@backstage/plugin-events-backend" + "pluginPackage": "@backstage/plugin-events-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 423fbde187..e6c6f835eb 100644 --- a/plugins/events-backend-module-bitbucket-cloud/package.json +++ b/plugins/events-backend-module-bitbucket-cloud/package.json @@ -4,7 +4,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "events", - "pluginPackage": "@backstage/plugin-events-backend" + "pluginPackage": "@backstage/plugin-events-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index 6f1bc451d9..65c09cd4ad 100644 --- a/plugins/events-backend-module-gerrit/package.json +++ b/plugins/events-backend-module-gerrit/package.json @@ -4,7 +4,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "events", - "pluginPackage": "@backstage/plugin-events-backend" + "pluginPackage": "@backstage/plugin-events-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 84ff688b16..998b76b091 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -8,6 +8,12 @@ "@backstage/plugin-events-backend", "@backstage/plugin-events-backend-test-utils", "@backstage/plugin-events-node" + ], + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/home/package.json b/plugins/home/package.json index 617778dd52..f5987fcf24 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -8,6 +8,12 @@ "pluginPackages": [ "@backstage/plugin-home", "@backstage/plugin-home-react" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index f47fd650e4..5ee33a8357 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -11,6 +11,12 @@ "@backstage/plugin-kubernetes-common", "@backstage/plugin-kubernetes-node", "@backstage/plugin-kubernetes-react" + ], + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 789c3375ce..e8a798d85b 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -11,6 +11,12 @@ "@backstage/plugin-kubernetes-common", "@backstage/plugin-kubernetes-node", "@backstage/plugin-kubernetes-react" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/org/package.json b/plugins/org/package.json index 88d1e783ca..fb9878fa52 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -8,6 +8,12 @@ "pluginPackages": [ "@backstage/plugin-org", "@backstage/plugin-org-react" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index be82562f0f..eec76b67ad 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -9,6 +9,12 @@ "@backstage/plugin-permission-common", "@backstage/plugin-permission-node", "@backstage/plugin-permission-react" + ], + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 4ba5b7df15..08edd2c406 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -7,6 +7,12 @@ "pluginId": "proxy", "pluginPackages": [ "@backstage/plugin-proxy-backend" + ], + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 7c5dd0a317..3203ac11f8 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index cb8cbd7400..9da56ff11f 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 585668ddb5..bf0dc415df 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index f579ea62b3..43149fa2ba 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 9259eb0798..4e872be8a8 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 467afb4339..5776875d16 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index 355b84c774..4b29179a44 100644 --- a/plugins/scaffolder-backend-module-gcp/package.json +++ b/plugins/scaffolder-backend-module-gcp/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 90136f4c34..8f6a772b08 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 1f597f70ae..98c0e80e3e 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 9ebd61f809..fe4ad110ad 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 2b73a9ce71..6c4b80ec92 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -4,7 +4,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 26c2c9aca1..63ed830f47 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -5,7 +5,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 8b15f66689..0e1546a247 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -4,7 +4,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 5303cc543e..60a68975f1 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -4,7 +4,12 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" + "pluginPackage": "@backstage/plugin-scaffolder-backend", + "features": [ + { + "type": "@backstage/BackendFeature" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index a4a8b1fdf2..0471eb5fba 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -12,6 +12,12 @@ "@backstage/plugin-scaffolder-node", "@backstage/plugin-scaffolder-node-test-utils", "@backstage/plugin-scaffolder-react" + ], + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index c4fe3ab8ad..482ce13631 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -12,6 +12,12 @@ "@backstage/plugin-scaffolder-node", "@backstage/plugin-scaffolder-node-test-utils", "@backstage/plugin-scaffolder-react" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index f3cd8ff7b0..3ca2f84501 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "search", - "pluginPackage": "@backstage/plugin-search-backend" + "pluginPackage": "@backstage/plugin-search-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 4b1d173048..840cf23f0d 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "search", - "pluginPackage": "@backstage/plugin-search-backend" + "pluginPackage": "@backstage/plugin-search-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index aef6c7fd61..312a7af1b9 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "search", - "pluginPackage": "@backstage/plugin-search-backend" + "pluginPackage": "@backstage/plugin-search-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 7147f83840..ef2a583d86 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "search", - "pluginPackage": "@backstage/plugin-search-backend" + "pluginPackage": "@backstage/plugin-search-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 6d89f7601a..81a594cc9c 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -5,7 +5,13 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "search", - "pluginPackage": "@backstage/plugin-search-backend" + "pluginPackage": "@backstage/plugin-search-backend", + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } + ] }, "publishConfig": { "access": "public" diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 05dc6ba72e..f1ec4087f1 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -11,6 +11,12 @@ "@backstage/plugin-search-backend-node", "@backstage/plugin-search-common", "@backstage/plugin-search-react" + ], + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/search/package.json b/plugins/search/package.json index eb45b7f3b1..279c926428 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -11,6 +11,12 @@ "@backstage/plugin-search-backend-node", "@backstage/plugin-search-common", "@backstage/plugin-search-react" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 7006bd2758..872e0752bd 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -11,6 +11,12 @@ "@backstage/plugin-techdocs-common", "@backstage/plugin-techdocs-node", "@backstage/plugin-techdocs-react" + ], + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 4274f77072..70684ce581 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -11,6 +11,12 @@ "@backstage/plugin-techdocs-common", "@backstage/plugin-techdocs-node", "@backstage/plugin-techdocs-react" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 8a6b5e2882..415202297d 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -9,6 +9,12 @@ "@backstage/plugin-user-settings", "@backstage/plugin-user-settings-backend", "@backstage/plugin-user-settings-common" + ], + "features": [ + { + "type": "@backstage/BackendFeature", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 3d545ca6ee..772cccee42 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -9,6 +9,12 @@ "@backstage/plugin-user-settings", "@backstage/plugin-user-settings-backend", "@backstage/plugin-user-settings-common" + ], + "features": [ + { + "type": "@backstage/FrontendPlugin", + "path": "./alpha" + } ] }, "publishConfig": { diff --git a/yarn.lock b/yarn.lock index 659c3d4635..ad8a46a2f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4025,6 +4025,7 @@ __metadata: swc-loader: ^0.2.3 tar: ^6.1.12 terser-webpack-plugin: ^5.1.3 + ts-morph: ^23.0.0 util: ^0.12.3 vite: ^5.0.0 vite-plugin-html: ^3.2.2 From 4e8a82eb68e6230e33388f4eb003e274cbfc3a75 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Wed, 11 Sep 2024 14:40:37 +0100 Subject: [PATCH 02/12] Include web-library and node-library as target package roles Signed-off-by: Harrison Hogg --- packages/cli/src/lib/features.test.ts | 4 ++-- packages/cli/src/lib/features.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/lib/features.test.ts b/packages/cli/src/lib/features.test.ts index b20ba7c6ac..43b9a8923f 100644 --- a/packages/cli/src/lib/features.test.ts +++ b/packages/cli/src/lib/features.test.ts @@ -27,12 +27,12 @@ describe('features', () => { 'backend-plugin-module': true, 'frontend-plugin': true, 'frontend-plugin-module': true, + 'web-library': true, + 'node-library': true, // Disallowed frontend: false, backend: false, cli: false, - 'web-library': false, - 'node-library': false, 'common-library': false, }; diff --git a/packages/cli/src/lib/features.ts b/packages/cli/src/lib/features.ts index 55245abea3..fd1b35d972 100644 --- a/packages/cli/src/lib/features.ts +++ b/packages/cli/src/lib/features.ts @@ -30,6 +30,8 @@ const targetPackageRoles: PackageRole[] = [ 'backend-plugin-module', 'frontend-plugin', 'frontend-plugin-module', + 'web-library', + 'node-library', ]; // A list of the feature types we want to extract from the project From c837e6d7d89b5be75c1961412592eca67901f442 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Thu, 12 Sep 2024 14:16:20 +0100 Subject: [PATCH 03/12] Change default feature export to happen in publish script Signed-off-by: Harrison Hogg --- .../cli-node/src/monorepo/PackageGraph.ts | 17 -- packages/cli-node/src/monorepo/index.ts | 1 - packages/cli/src/commands/repo/fix.ts | 54 +--- .../__testUtils__/createFeatureEnvironment.ts | 155 ++++++++++ packages/cli/src/lib/features.test.ts | 273 ------------------ packages/cli/src/lib/features.ts | 165 ----------- .../cli/src/lib/packager/productionPack.ts | 20 ++ packages/cli/src/lib/typeDistProject.test.ts | 141 +++++++++ packages/cli/src/lib/typeDistProject.ts | 240 +++++++++++++++ .../cli/src/tests/createFeatureEnvironment.ts | 196 ------------- 10 files changed, 558 insertions(+), 704 deletions(-) create mode 100644 packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts delete mode 100644 packages/cli/src/lib/features.test.ts delete mode 100644 packages/cli/src/lib/features.ts create mode 100644 packages/cli/src/lib/typeDistProject.test.ts create mode 100644 packages/cli/src/lib/typeDistProject.ts delete mode 100644 packages/cli/src/tests/createFeatureEnvironment.ts diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index 374be3add7..0394fd34ca 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -68,18 +68,6 @@ export const isValidPackageFeatureType = ( */ export type BackstagePackageFeatureType = (typeof packageFeatureTypes)[number]; -/** - * Metadata relating to an exported Backstage system component - * - * @public - */ -export type BackstagePackageFeature = { - // The export path, if omitted then it is the default package export - path?: string; - // The type of the export - type: BackstagePackageFeatureType; -}; - /** * Known fields in Backstage package.json files. * @@ -128,11 +116,6 @@ export interface BackstagePackageJson { * All packages that are part of the plugin. Must always and only be set for plugin packages and plugin library packages. */ pluginPackages?: string[]; - - /** - * The Backstage system components exported from this package - */ - features?: BackstagePackageFeature[]; }; exports?: JsonValue; diff --git a/packages/cli-node/src/monorepo/index.ts b/packages/cli-node/src/monorepo/index.ts index b0e595077d..b4ed921613 100644 --- a/packages/cli-node/src/monorepo/index.ts +++ b/packages/cli-node/src/monorepo/index.ts @@ -22,7 +22,6 @@ export { type PackageGraphNode, type BackstagePackage, type BackstagePackageJson, - type BackstagePackageFeature, type BackstagePackageFeatureType, } from './PackageGraph'; export { diff --git a/packages/cli/src/commands/repo/fix.ts b/packages/cli/src/commands/repo/fix.ts index 68b07564fb..a55440c53f 100644 --- a/packages/cli/src/commands/repo/fix.ts +++ b/packages/cli/src/commands/repo/fix.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { findPaths } from '@backstage/cli-common'; import { BackstagePackage, BackstagePackageJson, @@ -24,13 +23,9 @@ import { } from '@backstage/cli-node'; import { OptionValues } from 'commander'; import fs from 'fs-extra'; -import isEqual from 'lodash/isEqual'; import { resolve as resolvePath, posix, relative as relativePath } from 'path'; -import { Project } from 'ts-morph'; import { paths } from '../../lib/paths'; import { publishPreflightCheck } from '../../lib/publishing'; -import { getFeaturesMetadata } from '../../lib/features'; -import { readEntryPoints } from '../../lib/entryPoints'; /** * A mutable object representing a package.json file with potential fixes. @@ -431,46 +426,7 @@ export function fixPluginPackages( } } -// For each of the defined export locations, we want to annotate -// the backstage field with the exported system components. The -// "exports" field in package.json is a mapping of import paths to file paths. -// -// While the exports field is really flexible, this function will enforce a -// particular structure, which is a Record where the key is the -// import path and the value is the file path. -export function fixPluginFeatures( - pkg: FixablePackage, - _packages: FixablePackage[], - project: Project, -) { - const { dir, packageJson } = pkg; - - if ( - !packageJson.backstage || - !packageJson.backstage.role || - !packageJson.exports - ) { - return; - } - - const entryPoints = readEntryPoints(packageJson); - const { role } = packageJson.backstage; - const featuresMetadata = getFeaturesMetadata(project, role, dir, entryPoints); - - if ( - featuresMetadata.length && - !isEqual(packageJson.backstage.features, featuresMetadata) - ) { - packageJson.backstage.features = featuresMetadata; - pkg.changed = true; - } -} - -type PackageFixer = ( - pkg: FixablePackage, - packages: FixablePackage[], - project: Project, -) => void; +type PackageFixer = (pkg: FixablePackage, packages: FixablePackage[]) => void; export async function command(opts: OptionValues): Promise { const packages = await readFixablePackages(); @@ -484,20 +440,14 @@ export async function command(opts: OptionValues): Promise { fixRepositoryField, fixPluginId, fixPluginPackages, - fixPluginFeatures, // Run the publish preflight check too, to make sure we don't uncover errors during publishing publishPreflightCheck, ); } - const workspaceRoot = findPaths(process.cwd()).targetRoot; - const project = new Project({ - tsConfigFilePath: resolvePath(workspaceRoot, 'tsconfig.json'), - }); - for (const fixer of fixers) { for (const pkg of packages) { - fixer(pkg, packages, project); + fixer(pkg, packages); } } diff --git a/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts b/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts new file mode 100644 index 0000000000..7d7eeeeeaf --- /dev/null +++ b/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts @@ -0,0 +1,155 @@ +/* + * Copyright 2024 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 { BackstagePackageFeatureType, PackageRole } from '@backstage/cli-node'; +import { resolve as resolvePath } from 'path'; +import { Project } from 'ts-morph'; +import { EntryPoint } from '../entryPoints'; +import { getDistTypeRoot } from '../typeDistProject'; + +const mockEntryPoint = { + mount: '.', + path: './src/index.d.ts', + name: 'index', + ext: '.d.ts', +}; + +type CreateFeatureEnvironmentOptions = { + $$type?: BackstagePackageFeatureType; + format?: + | 'DefaultExportAssignment' + | 'DefaultExportFromFile' + | 'DefaultExportFromFileAsDefault' + | 'DefaultExportFromFileWithSibling'; + role?: PackageRole; +}; + +type FeatureEnvironment = { + project: Project; + role: PackageRole; + dir: string; + entryPoint: EntryPoint; +}; + +type File = { + path: string; + content: string; +}; + +const createTestType = ($$type: BackstagePackageFeatureType): File[] => [ + { + path: './src/createTestType.d.ts', + content: ` +export interface TestType { + readonly $$type: '${$$type}'; +}; + +export function createTestType(): TestType { + return { + $$type: '${$$type}', + }; +}; + `, + }, +]; + +const createMockDefaultExportAssignment = (): File[] => [ + { + path: mockEntryPoint.path, + content: ` +declare const _default: import("./createTestType").TestType; +export default _default; + `, + }, +]; + +const createMockDefaultExportFromFile = (): File[] => [ + { + path: mockEntryPoint.path, + content: `export { default } from './linked';`, + }, + { + path: './src/linked.d.ts', + content: ` +declare const _default: import("./createTestType").TestType; +export default _default; +`, + }, +]; + +const createMockDefaultExportFromFileAsDefault = (): File[] => [ + { + path: mockEntryPoint.path, + content: `export { test as default } from './linked';`, + }, + { + path: './src/linked.d.ts', + content: ` +export declare const test: import("./createTestType").TestType; + `, + }, +]; + +const createMockDefaultExportFromFileWithSibling = (): File[] => [ + { + path: mockEntryPoint.path, + content: `export { default, test } from './linked';`, + }, + { + path: './src/linked.d.ts', + content: ` +import { createTestType } from './createTestType'; + +export declare const test: import("./createTestType").TestType; +declare const _default: import("./createTestType").TestType; +export default _default; + `, + }, +]; + +const formatToFiles = { + DefaultExportAssignment: createMockDefaultExportAssignment, + DefaultExportFromFile: createMockDefaultExportFromFile, + DefaultExportFromFileAsDefault: createMockDefaultExportFromFileAsDefault, + DefaultExportFromFileWithSibling: createMockDefaultExportFromFileWithSibling, +}; + +export default function createFeatureEnvironment( + options?: CreateFeatureEnvironmentOptions, +): FeatureEnvironment { + const { + $$type = '@backstage/BackendFeature', + format = 'DefaultExportAssignment', + role = 'backend-plugin', + } = options ?? {}; + + const project = new Project(); + const files = [...createTestType($$type), ...formatToFiles[format]()]; + + for (const file of files) { + project.createSourceFile( + resolvePath(getDistTypeRoot(''), file.path), + file.content, + ); + } + + return { + project, + role, + dir: project.getFileSystem().getCurrentDirectory(), + entryPoint: mockEntryPoint, + }; +} diff --git a/packages/cli/src/lib/features.test.ts b/packages/cli/src/lib/features.test.ts deleted file mode 100644 index 43b9a8923f..0000000000 --- a/packages/cli/src/lib/features.test.ts +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2024 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 { BackstagePackageFeatureType, PackageRole } from '@backstage/cli-node'; -import createFeatureEnvironment from '../tests/createFeatureEnvironment'; -import { getEntryPointExports, getFeaturesMetadata } from './features'; - -describe('features', () => { - describe('for package role', () => { - // This Record makes sure we're checking all package roles - const packageRoles: Record = { - // Allowed - 'backend-plugin': true, - 'backend-plugin-module': true, - 'frontend-plugin': true, - 'frontend-plugin-module': true, - 'web-library': true, - 'node-library': true, - // Disallowed - frontend: false, - backend: false, - cli: false, - 'common-library': false, - }; - - const allowedPackageRoles = Object.keys(packageRoles).filter( - role => packageRoles[role as PackageRole], - ); - - const disallowedPackageRoles = Object.keys(packageRoles).filter( - role => !packageRoles[role as PackageRole], - ); - - it.each(allowedPackageRoles)(`returns features for %s`, r => { - const { project, role, dir, entryPoints } = createFeatureEnvironment({ - role: r as PackageRole, - }); - - expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([ - { - type: '@backstage/BackendFeature', - }, - ]); - }); - - it.each(disallowedPackageRoles)(`does not return features for %s`, r => { - const { project, role, dir, entryPoints } = createFeatureEnvironment({ - role: r as PackageRole, - }); - - expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([]); - }); - }); - - describe('for feature $$type', () => { - // This Record makes sure we're checking all feature types - const featureTypes: Record = { - // Allowed - '@backstage/BackendFeature': true, - '@backstage/BackstagePlugin': true, - '@backstage/FrontendPlugin': true, - '@backstage/FrontendModule': true, - // Disallowed - '@backstage/BackendFeatureFactory': false, - '@backstage/BackstageCredentials': false, - '@backstage/Extension': false, - '@backstage/ExtensionDataRef': false, - '@backstage/ExtensionDataValue': false, - '@backstage/ExtensionDefinition': false, - '@backstage/ExtensionInput': false, - '@backstage/ExtensionOverrides': false, - '@backstage/ExtensionPoint': false, - '@backstage/ExternalRouteRef': false, - '@backstage/RouteRef': false, - '@backstage/ServiceRef': false, - '@backstage/SubRouteRef': false, - '@backstage/TranslationMessages': false, - '@backstage/TranslationRef': false, - '@backstage/TranslationResource': false, - }; - - const allowedFeatureTypes = Object.keys(featureTypes).filter( - $$type => featureTypes[$$type as BackstagePackageFeatureType], - ); - - const disallowedFeatureTypes = Object.keys(featureTypes).filter( - $$type => !featureTypes[$$type as BackstagePackageFeatureType], - ); - - it.each(allowedFeatureTypes)(`returns features for "%s" $$type`, $$type => { - const { project, role, dir, entryPoints } = createFeatureEnvironment({ - $$type: $$type as BackstagePackageFeatureType, - }); - - expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([ - { - type: $$type, - }, - ]); - }); - - it.each(disallowedFeatureTypes)( - `does not return features for "%s" $$type`, - $$type => { - const { project, role, dir, entryPoints } = createFeatureEnvironment({ - $$type: $$type as BackstagePackageFeatureType, - }); - - expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual( - [], - ); - }, - ); - }); - - describe('export declaration formats', () => { - it('supports format of "export default ..."', () => { - const { - project, - dir, - entryPoints: [entryPoint], - } = createFeatureEnvironment({ - format: 'DefaultExport', - }); - - expect(getEntryPointExports(entryPoint, project, dir)).toEqual([ - { - location: '.', - name: 'default', - type: '@backstage/BackendFeature', - }, - ]); - }); - - it('supports format of "export { default } from ..."', () => { - const { - project, - dir, - entryPoints: [entryPoint], - } = createFeatureEnvironment({ - format: 'DefaultExportLinked', - }); - - expect(getEntryPointExports(entryPoint, project, dir)).toEqual([ - { - location: '.', - name: 'default', - type: '@backstage/BackendFeature', - }, - ]); - }); - - it('supports format of "export { default, ... } from ..."', () => { - const { - project, - dir, - entryPoints: [entryPoint], - } = createFeatureEnvironment({ - format: 'DefaultExportLinkedWithSibling', - }); - - expect(getEntryPointExports(entryPoint, project, dir)).toEqual([ - { - location: '.', - name: 'default', - type: '@backstage/BackendFeature', - }, - { - location: '.', - name: 'test', - type: '@backstage/BackendFeature', - }, - ]); - }); - - it('supports format of "export const foo = ..."', () => { - const { - project, - dir, - entryPoints: [entryPoint], - } = createFeatureEnvironment({ - format: 'NamedExport', - }); - - expect(getEntryPointExports(entryPoint, project, dir)).toEqual([ - { - location: '.', - name: 'test', - type: '@backstage/BackendFeature', - }, - ]); - }); - - it('supports format of "export * from ..."', () => { - const { - project, - dir, - entryPoints: [entryPoint], - } = createFeatureEnvironment({ - format: 'WildCardExport', - }); - - expect(getEntryPointExports(entryPoint, project, dir)).toEqual([ - { - location: '.', - name: 'test', - type: '@backstage/BackendFeature', - }, - ]); - }); - }); - - describe('entry points', () => { - it('returns features for multiple entry points', () => { - const { project, role, dir, entryPoints } = createFeatureEnvironment({ - exports: { - '.': 'src/index.ts', - './alpha': 'src/alpha.ts', - './beta': 'src/beta.ts', - }, - }); - - expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([ - { - type: '@backstage/BackendFeature', - }, - { - path: './alpha', - type: '@backstage/BackendFeature', - }, - { - path: './beta', - type: '@backstage/BackendFeature', - }, - ]); - }); - - it('ignores entry points that are not .ts or .tsx files', () => { - const { project, role, dir, entryPoints } = createFeatureEnvironment({ - exports: { - '.': 'src/index.js', - }, - }); - - expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([]); - }); - }); - - it('only returns default exports', () => { - const { project, role, dir, entryPoints } = createFeatureEnvironment({ - format: 'DefaultExportLinkedWithSibling', - }); - - expect(getFeaturesMetadata(project, role, dir, entryPoints)).toEqual([ - { - type: '@backstage/BackendFeature', - }, - ]); - }); -}); diff --git a/packages/cli/src/lib/features.ts b/packages/cli/src/lib/features.ts deleted file mode 100644 index fd1b35d972..0000000000 --- a/packages/cli/src/lib/features.ts +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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 { - isValidPackageFeatureType, - PackageRole, - type BackstagePackageFeature, - type BackstagePackageFeatureType, -} from '@backstage/cli-node'; -import { Project, SyntaxKind, ts, Type } from 'ts-morph'; -import { resolve as resolvePath } from 'node:path'; -import { EntryPoint } from './entryPoints'; - -// A list of the package roles we want to extract features for -const targetPackageRoles: PackageRole[] = [ - 'backend-plugin', - 'backend-plugin-module', - 'frontend-plugin', - 'frontend-plugin-module', - 'web-library', - 'node-library', -]; - -// A list of the feature types we want to extract from the project -// and include in the metadata -const targetFeatureTypes: BackstagePackageFeatureType[] = [ - '@backstage/BackendFeature', - '@backstage/BackstagePlugin', - '@backstage/FrontendPlugin', - '@backstage/FrontendModule', -]; - -// Returns all valid Backstage package features from a project -// for all entry points. -export function getFeaturesMetadata( - project: Project, - role: PackageRole, - dir: string, - entryPoints: EntryPoint[], -): BackstagePackageFeature[] { - if (!targetPackageRoles.includes(role)) { - return []; - } - - return entryPoints - .flatMap(entryPoint => getEntryPointExports(entryPoint, project, dir)) - .filter(isDefaultExport) - .filter(isTargetFeatureType) - .map(toBackstagePackageFeature); -} - -type EntryPointExport = { - name: string; - location: string; - type: BackstagePackageFeatureType; -}; - -// Returns all exports (default and named) from an entry point -// that are valid Backstage package features -export function getEntryPointExports( - { mount: location, path, ext }: EntryPoint, - project: Project, - dir: string, -): EntryPointExport[] { - const fullFilePath = resolvePath(dir, path); - const sourceFile = project.getSourceFile(fullFilePath); - - if (!sourceFile || (ext !== '.ts' && ext !== '.tsx')) { - return []; - } - - const exports = []; - - for (const exportSymbol of sourceFile.getExportSymbols()) { - const declaration = exportSymbol.getDeclarations()[0]; - const exportName = declaration.getSymbol()?.getName(); - let exportType: Type | undefined; - - if (declaration) { - if (declaration.isKind(SyntaxKind.ExportAssignment)) { - exportType = declaration.getExpression().getType(); - } else if (declaration.isKind(SyntaxKind.ExportSpecifier)) { - if (!declaration.isTypeOnly()) { - exportType = declaration.getType(); - } - } else if (declaration.isKind(SyntaxKind.VariableDeclaration)) { - exportType = declaration.getType(); - } - } - - if (exportName && exportType) { - const $$type = getBackstagePackageFeature$$TypeFromType(exportType); - - if ($$type) { - exports.push({ - name: exportName, - location, - type: $$type, - }); - } - } - } - - return exports; -} - -// Given a TS type, returns the Backstage package feature $$type value -function getBackstagePackageFeature$$TypeFromType( - type: Type, -): BackstagePackageFeatureType | null { - // Returns the concrete type of a generic type - const exportType = type.getTargetType() ?? type; - - for (const property of exportType.getProperties()) { - if (property.getName() === '$$type') { - const $$type = property - .getValueDeclaration() - ?.getText() - .match(/(?@backstage\/\w+)/)?.groups?.type; - - if ($$type && isValidPackageFeatureType($$type)) { - return $$type; - } - } - } - - return null; -} - -// Returns whether an export is the default export -function isDefaultExport({ name }: EntryPointExport): boolean { - return name === 'default'; -} - -// Returns whether an export is a valid Backstage package feature type -function isTargetFeatureType({ type }: EntryPointExport): boolean { - return targetFeatureTypes.includes(type); -} - -// Converts an entry point export to a Backstage package feature -function toBackstagePackageFeature({ - type, - location, -}: EntryPointExport): BackstagePackageFeature { - const feature: BackstagePackageFeature = { type }; - - if (location !== '.') { - feature.path = location; - } - - return feature; -} diff --git a/packages/cli/src/lib/packager/productionPack.ts b/packages/cli/src/lib/packager/productionPack.ts index 3c2b5aa89e..e4a1c13ffd 100644 --- a/packages/cli/src/lib/packager/productionPack.ts +++ b/packages/cli/src/lib/packager/productionPack.ts @@ -19,6 +19,10 @@ import npmPackList from 'npm-packlist'; import { resolve as resolvePath, posix as posixPath } from 'path'; import { BackstagePackageJson } from '@backstage/cli-node'; import { readEntryPoints } from '../entryPoints'; +import { + createTypeDistProject, + getEntryPointDefaultFeatureType, +} from '../typeDistProject'; const PKG_PATH = 'package.json'; const PKG_BACKUP_PATH = 'package.json-prepack'; @@ -147,11 +151,14 @@ async function prepareExportsEntryPoints( >(); const entryPoints = readEntryPoints(pkg); + const project = await createTypeDistProject(); + for (const entryPoint of entryPoints) { if (!SCRIPT_EXTS.includes(entryPoint.ext)) { outputExports[entryPoint.mount] = entryPoint.path; continue; } + const exp = {} as Record; for (const [key, ext] of Object.entries(EXPORT_MAP)) { const name = `${entryPoint.name}${ext}`; @@ -161,6 +168,19 @@ async function prepareExportsEntryPoints( } exp.default = exp.require ?? exp.import; + const defaultFeatureType = + pkg.backstage?.role && + getEntryPointDefaultFeatureType( + pkg.backstage?.role, + packageDir, + project, + entryPoint, + ); + + if (defaultFeatureType) { + exp.backstage = defaultFeatureType; + } + // This creates a directory with a lone package.json for backwards compatibility if (entryPoint.mount === '.') { if (exp.default) { diff --git a/packages/cli/src/lib/typeDistProject.test.ts b/packages/cli/src/lib/typeDistProject.test.ts new file mode 100644 index 0000000000..f3168dd9be --- /dev/null +++ b/packages/cli/src/lib/typeDistProject.test.ts @@ -0,0 +1,141 @@ +/* + * Copyright 2024 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 { BackstagePackageFeatureType, PackageRole } from '@backstage/cli-node'; +import createFeatureEnvironment from './__testUtils__/createFeatureEnvironment'; +import { getEntryPointDefaultFeatureType } from './typeDistProject'; + +describe('typeDistProject', () => { + describe('for package role', () => { + // This Record makes sure we're checking all package roles + const packageRoles: Record = { + // Allowed + 'backend-plugin': true, + 'backend-plugin-module': true, + 'frontend-plugin': true, + 'frontend-plugin-module': true, + 'web-library': true, + 'node-library': true, + // Disallowed + frontend: false, + backend: false, + cli: false, + 'common-library': false, + }; + + const allowedPackageRoles = Object.keys(packageRoles).filter( + role => packageRoles[role as PackageRole], + ); + + const disallowedPackageRoles = Object.keys(packageRoles).filter( + role => !packageRoles[role as PackageRole], + ); + + it.each(allowedPackageRoles)(`returns features for %s`, r => { + const { project, role, dir, entryPoint } = createFeatureEnvironment({ + role: r as PackageRole, + }); + + expect( + getEntryPointDefaultFeatureType(role, dir, project, entryPoint), + ).toEqual('@backstage/BackendFeature'); + }); + + it.each(disallowedPackageRoles)(`does not return features for %s`, r => { + const { project, role, dir, entryPoint } = createFeatureEnvironment({ + role: r as PackageRole, + }); + + expect( + getEntryPointDefaultFeatureType(role, dir, project, entryPoint), + ).toEqual(null); + }); + }); + + describe('for feature $$type', () => { + // This Record makes sure we're checking all feature types + const featureTypes: Record = { + // Allowed + '@backstage/BackendFeature': true, + '@backstage/BackstagePlugin': true, + '@backstage/FrontendPlugin': true, + '@backstage/FrontendModule': true, + // Disallowed + '@backstage/BackendFeatureFactory': false, + '@backstage/BackstageCredentials': false, + '@backstage/Extension': false, + '@backstage/ExtensionDataRef': false, + '@backstage/ExtensionDataValue': false, + '@backstage/ExtensionDefinition': false, + '@backstage/ExtensionInput': false, + '@backstage/ExtensionOverrides': false, + '@backstage/ExtensionPoint': false, + '@backstage/ExternalRouteRef': false, + '@backstage/RouteRef': false, + '@backstage/ServiceRef': false, + '@backstage/SubRouteRef': false, + '@backstage/TranslationMessages': false, + '@backstage/TranslationRef': false, + '@backstage/TranslationResource': false, + }; + + const allowedFeatureTypes = Object.keys(featureTypes).filter( + $$type => featureTypes[$$type as BackstagePackageFeatureType], + ); + + const disallowedFeatureTypes = Object.keys(featureTypes).filter( + $$type => !featureTypes[$$type as BackstagePackageFeatureType], + ); + + it.each(allowedFeatureTypes)(`returns features for "%s" $$type`, $$type => { + const { project, role, dir, entryPoint } = createFeatureEnvironment({ + $$type: $$type as BackstagePackageFeatureType, + }); + + expect( + getEntryPointDefaultFeatureType(role, dir, project, entryPoint), + ).toEqual($$type); + }); + + it.each(disallowedFeatureTypes)( + `does not return features for "%s" $$type`, + $$type => { + const { project, role, dir, entryPoint } = createFeatureEnvironment({ + $$type: $$type as BackstagePackageFeatureType, + }); + + expect( + getEntryPointDefaultFeatureType(role, dir, project, entryPoint), + ).toEqual(null); + }, + ); + }); + + it.each([ + 'DefaultExportAssignment', + 'DefaultExportFromFile', + 'DefaultExportFromFileAsDefault', + 'DefaultExportFromFileWithSibling', + ] as const)('returns features for format "%s"', format => { + const { project, role, dir, entryPoint } = createFeatureEnvironment({ + format, + }); + + expect( + getEntryPointDefaultFeatureType(role, dir, project, entryPoint), + ).toEqual('@backstage/BackendFeature'); + }); +}); diff --git a/packages/cli/src/lib/typeDistProject.ts b/packages/cli/src/lib/typeDistProject.ts new file mode 100644 index 0000000000..4fa970957d --- /dev/null +++ b/packages/cli/src/lib/typeDistProject.ts @@ -0,0 +1,240 @@ +/* + * Copyright 2024 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 { findPaths } from '@backstage/cli-common'; +import { + BackstagePackageFeatureType, + isValidPackageFeatureType, + PackageGraph, + PackageRole, +} from '@backstage/cli-node'; +import { builtinModules } from 'module'; +import { resolve as resolvePath } from 'path'; +import { Project, SourceFile, SyntaxKind, ts, Type } from 'ts-morph'; +import { EntryPoint, readEntryPoints } from './entryPoints'; + +export const getDistTypeRoot = (dir: string) => { + const workspaceRoot = findPaths(process.cwd()).targetRoot; + const relativeDirectory = dir.replace(workspaceRoot, ''); + const distTypeRoot = resolvePath( + workspaceRoot, + `./dist-types${relativeDirectory}`, + ); + + return distTypeRoot; +}; + +const getPackagesDistTypeMap = async () => { + const packages = await PackageGraph.listTargetPackages(); + const distTypeMap: Record = {}; + + for (const { dir, packageJson } of packages) { + const distTypeRoot = getDistTypeRoot(dir); + + for (const { name, path, ext } of readEntryPoints(packageJson)) { + const dtsPath = resolvePath(distTypeRoot, path.replace(ext, '.d.ts')); + + if (name === 'index') { + distTypeMap[packageJson.name] = dtsPath; + } else { + distTypeMap[`${packageJson.name}/${name}`] = dtsPath; + } + } + } + + return distTypeMap; +}; + +export const createTypeDistProject = async () => { + const distTypeMap = await getPackagesDistTypeMap(); + const workspaceRoot = findPaths(process.cwd()).targetRoot; + + return new Project({ + tsConfigFilePath: resolvePath(workspaceRoot, 'tsconfig.json'), + skipAddingFilesFromTsConfig: true, + resolutionHost: (moduleResolutionHost, getCompilerOptions) => { + return { + resolveModuleNames: (moduleNames, containingFile) => { + const compilerOptions = getCompilerOptions(); + const resolvedModules: ts.ResolvedModule[] = []; + + for (let moduleName of moduleNames) { + // Handle resolve internal plugins and package entry points to dist-types folder + if (distTypeMap[moduleName]) { + resolvedModules.push({ + resolvedFileName: distTypeMap[moduleName], + isExternalLibraryImport: false, + resolvedUsingTsExtension: false, + }); + continue; + } + + // Handle resolving builtin node modules to @types/node + if (moduleName.startsWith('node:')) { + moduleName = moduleName.slice(5); + } + if (builtinModules.includes(moduleName)) { + const result = ts.resolveModuleName( + `@types/node/${moduleName}`, + containingFile, + compilerOptions, + moduleResolutionHost, + ); + + if (result.resolvedModule) { + resolvedModules.push(result.resolvedModule); + continue; + } + } + + // Handle resolve relative paths and external node_modules. + const result = ts.resolveModuleName( + moduleName, + containingFile, + compilerOptions, + moduleResolutionHost, + ); + + if (result.resolvedModule) { + resolvedModules.push(result.resolvedModule); + continue; + } + + throw new Error(`Failed to resolve module: ${moduleName}`); + } + + return resolvedModules; + }, + }; + }, + }); +}; + +// A list of the package roles we want to extract features for +const targetPackageRoles: PackageRole[] = [ + 'backend-plugin', + 'backend-plugin-module', + 'frontend-plugin', + 'frontend-plugin-module', + 'web-library', + 'node-library', +]; + +// A list of the feature types we want to extract from the project +// and include in the metadata +const targetFeatureTypes: BackstagePackageFeatureType[] = [ + '@backstage/BackendFeature', + '@backstage/BackstagePlugin', + '@backstage/FrontendPlugin', + '@backstage/FrontendModule', +]; + +export const getEntryPointDefaultFeatureType = ( + role: PackageRole, + packageDir: string, + project: Project, + entryPoint: EntryPoint, +): BackstagePackageFeatureType | null => { + if (isTargetPackageRole(role)) { + const dtsPath = resolvePath( + getDistTypeRoot(packageDir), + entryPoint.path.replace(entryPoint.ext, '.d.ts'), + ); + + const defaultFeatureType = getSourceFileDefaultFeatureType( + project.addSourceFileAtPath(dtsPath), + ); + + if (isTargetFeatureType(defaultFeatureType)) { + return defaultFeatureType; + } + } + + return null; +}; + +// Condition for a package role matches a target package role +function isTargetPackageRole(role: PackageRole): boolean { + return !!role && targetPackageRoles.includes(role); +} + +// Returns whether an export is a valid Backstage package feature type +function isTargetFeatureType( + type: BackstagePackageFeatureType | null, +): boolean { + return !!type && targetFeatureTypes.includes(type); +} + +// Returns all exports (default and named) from an entry point +// that are valid Backstage package features +function getSourceFileDefaultFeatureType( + sourceFile: SourceFile, +): BackstagePackageFeatureType | null { + for (const exportSymbol of sourceFile.getExportSymbols()) { + const declaration = exportSymbol.getDeclarations()[0]; + const exportName = declaration.getSymbol()?.getName(); + + if (exportName !== 'default') { + continue; + } + + let exportType: Type | undefined; + + if (declaration) { + if (declaration.isKind(SyntaxKind.ExportAssignment)) { + exportType = declaration.getExpression().getType(); + } else if (declaration.isKind(SyntaxKind.ExportSpecifier)) { + if (!declaration.isTypeOnly()) { + exportType = declaration.getType(); + } + } else if (declaration.isKind(SyntaxKind.VariableDeclaration)) { + exportType = declaration.getType(); + } + } + + if (exportName && exportType) { + const $$type = getBackstagePackageFeature$$TypeFromType(exportType); + + if ($$type) { + return $$type; + } + } + } + + return null; +} + +// Given a TS type, returns the Backstage package feature $$type value +function getBackstagePackageFeature$$TypeFromType( + type: Type, +): BackstagePackageFeatureType | null { + // Returns the concrete type of a generic type + const exportType = type.getTargetType() ?? type; + + for (const property of exportType.getProperties()) { + if (property.getName() === '$$type') { + const $$type = property + .getValueDeclaration() + ?.getText() + .match(/(?@backstage\/\w+)/)?.groups?.type; + + if ($$type && isValidPackageFeatureType($$type)) { + return $$type; + } + } + } + + return null; +} diff --git a/packages/cli/src/tests/createFeatureEnvironment.ts b/packages/cli/src/tests/createFeatureEnvironment.ts deleted file mode 100644 index 6313e6439b..0000000000 --- a/packages/cli/src/tests/createFeatureEnvironment.ts +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2024 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 { BackstagePackageFeatureType, PackageRole } from '@backstage/cli-node'; -import path from 'path'; -import { Project } from 'ts-morph'; -import { EntryPoint } from '../lib/entryPoints'; - -type CreateFeatureEnvironmentOptions = { - $$type?: BackstagePackageFeatureType; - exports?: Record; - format?: - | 'DefaultExport' - | 'DefaultExportLinked' - | 'DefaultExportLinkedWithSibling' - | 'NamedExport' - | 'WildCardExport'; - role?: PackageRole; -}; - -type FeatureEnvironment = { - project: Project; - role: PackageRole; - dir: string; - entryPoints: EntryPoint[]; -}; - -type File = { - path: string; - content: string; -}; - -const createForExports = ( - exports: Record, - content: string, -): File[] => { - return Object.entries(exports).map(([_, filePath]) => ({ - path: `./${filePath}`, - content, - })); -}; - -const createTestType = ($$type: BackstagePackageFeatureType): File[] => [ - { - path: './src/createTestType.ts', - content: ` -export interface TestType { - readonly $$type: '${$$type}'; -}; - -export function createTestType(): TestType { - return { - $$type: '${$$type}', - }; -}; - `, - }, -]; - -const createTestDefaultExport = (exports: Record): File[] => - createForExports( - exports, - ` -import { createTestType } from './createTestType'; - -export { TestType } from './createTestType'; -export default createTestType(); - `, - ); - -const createTestDefaultExportLinked = ( - exports: Record, -): File[] => [ - ...createForExports( - exports, - ` -export { TestType } from './createTestType'; -export { default } from './linked'; - `, - ), - { - path: './src/linked.ts', - content: ` -import { createTestType } from './createTestType'; - -export default createTestType(); - `, - }, -]; - -const createTestDefaultExportLinkedWithSibling = ( - exports: Record, -): File[] => [ - ...createForExports( - exports, - ` -export { TestType } from './createTestType'; -export { default, test } from './linked'; - `, - ), - { - path: './src/linked.ts', - content: ` -import { createTestType } from './createTestType'; - -export const test = createTestType(); -export default createTestType(); - `, - }, -]; - -const createTestNamedExport = (exports: Record): File[] => [ - ...createForExports( - exports, - ` -import { createTestType } from './createTestType'; - -export { TestType } from './createTestType'; -export const test = createTestType(); - `, - ), -]; - -const createTestWildCardExport = (exports: Record): File[] => [ - ...createForExports( - exports, - ` -export * from './linked'; - `, - ), - { - path: './src/linked.ts', - content: ` -import { createTestType } from './createTestType'; - -export { TestType } from './createTestType'; -export const test = createTestType(); -export default createTestType(); - `, - }, -]; - -const formatToFiles = { - DefaultExport: createTestDefaultExport, - DefaultExportLinked: createTestDefaultExportLinked, - DefaultExportLinkedWithSibling: createTestDefaultExportLinkedWithSibling, - NamedExport: createTestNamedExport, - WildCardExport: createTestWildCardExport, -}; - -export default function createFeatureEnvironment( - options?: CreateFeatureEnvironmentOptions, -): FeatureEnvironment { - const { - $$type = '@backstage/BackendFeature', - exports = { '.': 'src/index.ts' }, - format = 'DefaultExport', - role = 'backend-plugin', - } = options ?? {}; - - const project = new Project(); - const entryPoints: EntryPoint[] = Object.entries(exports ?? {}).map( - ([mount, filePath]) => ({ - mount, - path: filePath, - name: mount, - ext: path.extname(filePath), - }), - ); - - const files = [...createTestType($$type), ...formatToFiles[format](exports)]; - - for (const file of files) { - project.createSourceFile(file.path, file.content); - } - - return { - project, - role, - dir: project.getFileSystem().getCurrentDirectory(), - entryPoints, - }; -} From 48100b59bdd0d9990907819240aa6f40c075cee0 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Thu, 12 Sep 2024 15:40:07 +0100 Subject: [PATCH 04/12] Removed package json features edits Signed-off-by: Harrison Hogg --- plugins/api-docs/package.json | 6 ------ plugins/app-backend/package.json | 6 ------ plugins/catalog-backend-module-aws/package.json | 8 +------- plugins/catalog-backend-module-azure/package.json | 8 +------- .../catalog-backend-module-bitbucket-cloud/package.json | 8 +------- .../catalog-backend-module-bitbucket-server/package.json | 8 +------- plugins/catalog-backend-module-gcp/package.json | 7 +------ plugins/catalog-backend-module-gerrit/package.json | 8 +------- plugins/catalog-backend-module-github/package.json | 8 +------- plugins/catalog-backend-module-gitlab/package.json | 8 +------- .../package.json | 8 +------- plugins/catalog-backend-module-msgraph/package.json | 8 +------- plugins/catalog-backend-module-puppetdb/package.json | 8 +------- .../package.json | 7 +------ plugins/catalog-backend/package.json | 6 ------ plugins/catalog-graph/package.json | 6 ------ plugins/catalog-import/package.json | 6 ------ plugins/catalog/package.json | 6 ------ plugins/devtools/package.json | 6 ------ plugins/events-backend-module-aws-sqs/package.json | 8 +------- plugins/events-backend-module-azure/package.json | 8 +------- .../events-backend-module-bitbucket-cloud/package.json | 8 +------- plugins/events-backend-module-gerrit/package.json | 8 +------- plugins/events-backend/package.json | 6 ------ plugins/home/package.json | 6 ------ plugins/kubernetes-backend/package.json | 6 ------ plugins/kubernetes/package.json | 6 ------ plugins/org/package.json | 6 ------ plugins/permission-backend/package.json | 6 ------ plugins/proxy-backend/package.json | 6 ------ plugins/scaffolder-backend-module-azure/package.json | 7 +------ .../package.json | 7 +------ .../package.json | 7 +------ plugins/scaffolder-backend-module-bitbucket/package.json | 7 +------ .../package.json | 7 +------ .../scaffolder-backend-module-cookiecutter/package.json | 7 +------ plugins/scaffolder-backend-module-gcp/package.json | 7 +------ plugins/scaffolder-backend-module-gerrit/package.json | 7 +------ plugins/scaffolder-backend-module-gitea/package.json | 7 +------ plugins/scaffolder-backend-module-github/package.json | 7 +------ plugins/scaffolder-backend-module-gitlab/package.json | 7 +------ plugins/scaffolder-backend-module-rails/package.json | 7 +------ plugins/scaffolder-backend-module-sentry/package.json | 7 +------ plugins/scaffolder-backend-module-yeoman/package.json | 7 +------ plugins/scaffolder-backend/package.json | 6 ------ plugins/scaffolder/package.json | 6 ------ plugins/search-backend-module-catalog/package.json | 8 +------- plugins/search-backend-module-elasticsearch/package.json | 8 +------- plugins/search-backend-module-explore/package.json | 8 +------- plugins/search-backend-module-pg/package.json | 8 +------- plugins/search-backend-module-techdocs/package.json | 8 +------- plugins/search-backend/package.json | 6 ------ plugins/search/package.json | 6 ------ plugins/techdocs-backend/package.json | 6 ------ plugins/techdocs/package.json | 6 ------ plugins/user-settings-backend/package.json | 6 ------ plugins/user-settings/package.json | 6 ------ 57 files changed, 35 insertions(+), 361 deletions(-) diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 4576a93f85..6858d6a81f 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -7,12 +7,6 @@ "pluginId": "api-docs", "pluginPackages": [ "@backstage/plugin-api-docs" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 2d9978e254..606d5adea6 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -9,12 +9,6 @@ "@backstage/plugin-app", "@backstage/plugin-app-backend", "@backstage/plugin-app-node" - ], - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index c6796e35bd..92f11d4b63 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 84e3368efb..aa0b3663c7 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 5a24423396..4f0007bb44 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 030d8511ca..af12601f87 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -4,13 +4,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index b9609dbadf..13904c905b 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index ce2af52542..caebb75b34 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -4,13 +4,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index f58d08192b..8b05d0f896 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 126cad0bed..ef8d1f9148 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 1c55b088e1..4e964ccc9d 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index e8c1a41ecb..ebdd9bc316 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 5d71befbb5..670dbe23a2 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index 565fe94206..d64b3b468e 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", - "pluginPackage": "@backstage/plugin-catalog-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-catalog-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8aac88ac51..4826758d01 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -11,12 +11,6 @@ "@backstage/plugin-catalog-common", "@backstage/plugin-catalog-node", "@backstage/plugin-catalog-react" - ], - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 14dfe03579..4e83990c95 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -6,12 +6,6 @@ "pluginId": "catalog-graph", "pluginPackages": [ "@backstage/plugin-catalog-graph" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index fb6d40220f..964dbd3c33 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -7,12 +7,6 @@ "pluginId": "catalog-import", "pluginPackages": [ "@backstage/plugin-catalog-import" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 606ebb16d1..317e91fa4d 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -11,12 +11,6 @@ "@backstage/plugin-catalog-common", "@backstage/plugin-catalog-node", "@backstage/plugin-catalog-react" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 1b0f14b193..9e4c0caab6 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -8,12 +8,6 @@ "@backstage/plugin-devtools", "@backstage/plugin-devtools-backend", "@backstage/plugin-devtools-common" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 8eac12d934..d109490dce 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -4,13 +4,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "events", - "pluginPackage": "@backstage/plugin-events-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-events-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 376f507c07..91cd11d286 100644 --- a/plugins/events-backend-module-azure/package.json +++ b/plugins/events-backend-module-azure/package.json @@ -4,13 +4,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "events", - "pluginPackage": "@backstage/plugin-events-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-events-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index e6c6f835eb..423fbde187 100644 --- a/plugins/events-backend-module-bitbucket-cloud/package.json +++ b/plugins/events-backend-module-bitbucket-cloud/package.json @@ -4,13 +4,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "events", - "pluginPackage": "@backstage/plugin-events-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-events-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index 65c09cd4ad..6f1bc451d9 100644 --- a/plugins/events-backend-module-gerrit/package.json +++ b/plugins/events-backend-module-gerrit/package.json @@ -4,13 +4,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "events", - "pluginPackage": "@backstage/plugin-events-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-events-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 998b76b091..84ff688b16 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -8,12 +8,6 @@ "@backstage/plugin-events-backend", "@backstage/plugin-events-backend-test-utils", "@backstage/plugin-events-node" - ], - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/home/package.json b/plugins/home/package.json index f5987fcf24..617778dd52 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -8,12 +8,6 @@ "pluginPackages": [ "@backstage/plugin-home", "@backstage/plugin-home-react" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 5ee33a8357..f47fd650e4 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -11,12 +11,6 @@ "@backstage/plugin-kubernetes-common", "@backstage/plugin-kubernetes-node", "@backstage/plugin-kubernetes-react" - ], - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index e8a798d85b..789c3375ce 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -11,12 +11,6 @@ "@backstage/plugin-kubernetes-common", "@backstage/plugin-kubernetes-node", "@backstage/plugin-kubernetes-react" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/org/package.json b/plugins/org/package.json index fb9878fa52..88d1e783ca 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -8,12 +8,6 @@ "pluginPackages": [ "@backstage/plugin-org", "@backstage/plugin-org-react" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index eec76b67ad..be82562f0f 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -9,12 +9,6 @@ "@backstage/plugin-permission-common", "@backstage/plugin-permission-node", "@backstage/plugin-permission-react" - ], - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 08edd2c406..4ba5b7df15 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -7,12 +7,6 @@ "pluginId": "proxy", "pluginPackages": [ "@backstage/plugin-proxy-backend" - ], - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 3203ac11f8..7c5dd0a317 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index 9da56ff11f..cb8cbd7400 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index bf0dc415df..585668ddb5 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 43149fa2ba..f579ea62b3 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 4e872be8a8..9259eb0798 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 5776875d16..467afb4339 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index 4b29179a44..355b84c774 100644 --- a/plugins/scaffolder-backend-module-gcp/package.json +++ b/plugins/scaffolder-backend-module-gcp/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 8f6a772b08..90136f4c34 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 98c0e80e3e..1f597f70ae 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index fe4ad110ad..9ebd61f809 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 6c4b80ec92..2b73a9ce71 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -4,12 +4,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 63ed830f47..26c2c9aca1 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -5,12 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 0e1546a247..8b15f66689 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -4,12 +4,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 60a68975f1..5303cc543e 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -4,12 +4,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend", - "features": [ - { - "type": "@backstage/BackendFeature" - } - ] + "pluginPackage": "@backstage/plugin-scaffolder-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 0471eb5fba..a4a8b1fdf2 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -12,12 +12,6 @@ "@backstage/plugin-scaffolder-node", "@backstage/plugin-scaffolder-node-test-utils", "@backstage/plugin-scaffolder-react" - ], - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 482ce13631..c4fe3ab8ad 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -12,12 +12,6 @@ "@backstage/plugin-scaffolder-node", "@backstage/plugin-scaffolder-node-test-utils", "@backstage/plugin-scaffolder-react" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index 3ca2f84501..f3cd8ff7b0 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "search", - "pluginPackage": "@backstage/plugin-search-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-search-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 840cf23f0d..4b1d173048 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "search", - "pluginPackage": "@backstage/plugin-search-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-search-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index 312a7af1b9..aef6c7fd61 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "search", - "pluginPackage": "@backstage/plugin-search-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-search-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index ef2a583d86..7147f83840 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "search", - "pluginPackage": "@backstage/plugin-search-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-search-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 81a594cc9c..6d89f7601a 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -5,13 +5,7 @@ "backstage": { "role": "backend-plugin-module", "pluginId": "search", - "pluginPackage": "@backstage/plugin-search-backend", - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } - ] + "pluginPackage": "@backstage/plugin-search-backend" }, "publishConfig": { "access": "public" diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index f1ec4087f1..05dc6ba72e 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -11,12 +11,6 @@ "@backstage/plugin-search-backend-node", "@backstage/plugin-search-common", "@backstage/plugin-search-react" - ], - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/search/package.json b/plugins/search/package.json index 279c926428..eb45b7f3b1 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -11,12 +11,6 @@ "@backstage/plugin-search-backend-node", "@backstage/plugin-search-common", "@backstage/plugin-search-react" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 872e0752bd..7006bd2758 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -11,12 +11,6 @@ "@backstage/plugin-techdocs-common", "@backstage/plugin-techdocs-node", "@backstage/plugin-techdocs-react" - ], - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 70684ce581..4274f77072 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -11,12 +11,6 @@ "@backstage/plugin-techdocs-common", "@backstage/plugin-techdocs-node", "@backstage/plugin-techdocs-react" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 415202297d..8a6b5e2882 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -9,12 +9,6 @@ "@backstage/plugin-user-settings", "@backstage/plugin-user-settings-backend", "@backstage/plugin-user-settings-common" - ], - "features": [ - { - "type": "@backstage/BackendFeature", - "path": "./alpha" - } ] }, "publishConfig": { diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 772cccee42..3d545ca6ee 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -9,12 +9,6 @@ "@backstage/plugin-user-settings", "@backstage/plugin-user-settings-backend", "@backstage/plugin-user-settings-common" - ], - "features": [ - { - "type": "@backstage/FrontendPlugin", - "path": "./alpha" - } ] }, "publishConfig": { From b33f6b22ba7e99524ee905536d38a11127e8a53c Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Thu, 12 Sep 2024 17:00:59 +0100 Subject: [PATCH 05/12] Removed type list from cli-node Signed-off-by: Harrison Hogg --- .changeset/clever-paws-stare.md | 3 +- .changeset/healthy-moose-tease.md | 63 ------------------- packages/cli-node/api-report.md | 59 ----------------- .../cli-node/src/monorepo/PackageGraph.ts | 46 -------------- packages/cli-node/src/monorepo/index.ts | 3 - .../__testUtils__/createFeatureEnvironment.ts | 7 ++- packages/cli/src/lib/typeDistProject.test.ts | 42 +++++-------- packages/cli/src/lib/typeDistProject.ts | 43 +++++++------ 8 files changed, 43 insertions(+), 223 deletions(-) delete mode 100644 .changeset/healthy-moose-tease.md diff --git a/.changeset/clever-paws-stare.md b/.changeset/clever-paws-stare.md index b3cb421723..ad56a5306e 100644 --- a/.changeset/clever-paws-stare.md +++ b/.changeset/clever-paws-stare.md @@ -1,6 +1,5 @@ --- -'@backstage/cli-node': patch '@backstage/cli': patch --- -Added a new step to the `backstage-cli repo fix --publish` command that will annotate default export features to the 'backstage' metadata within plugin package.json. This is to help with identifying the declarative integration points for plugins without needing to fetch or run the plugins first. +Added functionality to the prepack script that will append the default export type for entry points to the `exports` object before publishing. This is to help with identifying the declarative integration points for plugins without needing to fetch or run the plugins first. diff --git a/.changeset/healthy-moose-tease.md b/.changeset/healthy-moose-tease.md deleted file mode 100644 index 1f9ba3952f..0000000000 --- a/.changeset/healthy-moose-tease.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch -'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': patch -'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch -'@backstage/plugin-catalog-backend-module-bitbucket-server': patch -'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch -'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch -'@backstage/plugin-events-backend-module-bitbucket-cloud': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket': patch -'@backstage/plugin-search-backend-module-elasticsearch': patch -'@backstage/plugin-scaffolder-backend-module-gerrit': patch -'@backstage/plugin-scaffolder-backend-module-github': patch -'@backstage/plugin-scaffolder-backend-module-gitlab': patch -'@backstage/plugin-scaffolder-backend-module-sentry': patch -'@backstage/plugin-scaffolder-backend-module-yeoman': patch -'@backstage/plugin-catalog-backend-module-puppetdb': patch -'@backstage/plugin-scaffolder-backend-module-azure': patch -'@backstage/plugin-scaffolder-backend-module-gitea': patch -'@backstage/plugin-scaffolder-backend-module-rails': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch -'@backstage/plugin-search-backend-module-techdocs': patch -'@backstage/plugin-catalog-backend-module-gerrit': patch -'@backstage/plugin-catalog-backend-module-github': patch -'@backstage/plugin-catalog-backend-module-gitlab': patch -'@backstage/plugin-events-backend-module-aws-sqs': patch -'@backstage/plugin-scaffolder-backend-module-gcp': patch -'@backstage/plugin-search-backend-module-catalog': patch -'@backstage/plugin-search-backend-module-explore': patch -'@backstage/plugin-catalog-backend-module-azure': patch -'@backstage/plugin-events-backend-module-gerrit': patch -'@backstage/plugin-events-backend-module-github': patch -'@backstage/plugin-events-backend-module-gitlab': patch -'@backstage/plugin-events-backend-module-azure': patch -'@backstage/plugin-catalog-backend-module-aws': patch -'@backstage/plugin-catalog-backend-module-gcp': patch -'@backstage/plugin-search-backend-module-pg': patch -'@backstage/plugin-user-settings-backend': patch -'@backstage/plugin-kubernetes-backend': patch -'@backstage/plugin-permission-backend': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-techdocs-backend': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-events-backend': patch -'@backstage/plugin-search-backend': patch -'@backstage/plugin-catalog-graph': patch -'@backstage/plugin-proxy-backend': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-app-backend': patch -'@backstage/plugin-kubernetes': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-devtools': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-search': patch -'@backstage/plugin-home': patch -'@backstage/plugin-org': patch ---- - -Added `features` metadata to Package JSON diff --git a/packages/cli-node/api-report.md b/packages/cli-node/api-report.md index 519fe12bf9..f22583dd25 100644 --- a/packages/cli-node/api-report.md +++ b/packages/cli-node/api-report.md @@ -12,15 +12,6 @@ export type BackstagePackage = { packageJson: BackstagePackageJson; }; -// @public -export type BackstagePackageFeature = { - path?: string; - type: BackstagePackageFeatureType; -}; - -// @public -export type BackstagePackageFeatureType = (typeof packageFeatureTypes)[number]; - // @public export interface BackstagePackageJson { // (undocumented) @@ -31,7 +22,6 @@ export interface BackstagePackageJson { pluginId?: string | null; pluginPackage?: string; pluginPackages?: string[]; - features?: BackstagePackageFeature[]; }; // (undocumented) bundled?: boolean; @@ -98,31 +88,6 @@ export class GitUtils { // @public export function isMonoRepo(): Promise; -// @public -export const isValidPackageFeatureType: ( - value: string, -) => value is - | '@backstage/TranslationResource' - | '@backstage/TranslationMessages' - | '@backstage/TranslationRef' - | '@backstage/RouteRef' - | '@backstage/SubRouteRef' - | '@backstage/ExternalRouteRef' - | '@backstage/ExtensionDataValue' - | '@backstage/ExtensionDataRef' - | '@backstage/ExtensionInput' - | '@backstage/ExtensionDefinition' - | '@backstage/Extension' - | '@backstage/ExtensionOverrides' - | '@backstage/FrontendPlugin' - | '@backstage/BackstagePlugin' - | '@backstage/FrontendModule' - | '@backstage/BackendFeatureFactory' - | '@backstage/BackendFeature' - | '@backstage/ServiceRef' - | '@backstage/BackstageCredentials' - | '@backstage/ExtensionPoint'; - // @public export class Lockfile { createSimplifiedDependencyGraph(): Map>; @@ -144,30 +109,6 @@ export type LockfileDiffEntry = { range: string; }; -// @public -export const packageFeatureTypes: readonly [ - '@backstage/BackendFeature', - '@backstage/BackendFeatureFactory', - '@backstage/BackstageCredentials', - '@backstage/BackstagePlugin', - '@backstage/Extension', - '@backstage/ExtensionDataRef', - '@backstage/ExtensionDataValue', - '@backstage/ExtensionDefinition', - '@backstage/ExtensionInput', - '@backstage/ExtensionOverrides', - '@backstage/ExtensionPoint', - '@backstage/ExternalRouteRef', - '@backstage/FrontendPlugin', - '@backstage/FrontendModule', - '@backstage/RouteRef', - '@backstage/ServiceRef', - '@backstage/SubRouteRef', - '@backstage/TranslationMessages', - '@backstage/TranslationRef', - '@backstage/TranslationResource', -]; - // @public export class PackageGraph extends Map { collectPackageNames( diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index 0394fd34ca..69ec99c2dd 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -22,52 +22,6 @@ import { GitUtils } from '../git'; import { Lockfile } from './Lockfile'; import { JsonValue } from '@backstage/types'; -/** - * All Backstage system features - * - * @public - */ -export const packageFeatureTypes = [ - '@backstage/BackendFeature', - '@backstage/BackendFeatureFactory', - '@backstage/BackstageCredentials', - '@backstage/BackstagePlugin', - '@backstage/Extension', - '@backstage/ExtensionDataRef', - '@backstage/ExtensionDataValue', - '@backstage/ExtensionDefinition', - '@backstage/ExtensionInput', - '@backstage/ExtensionOverrides', - '@backstage/ExtensionPoint', - '@backstage/ExternalRouteRef', - '@backstage/FrontendPlugin', - '@backstage/FrontendModule', - '@backstage/RouteRef', - '@backstage/ServiceRef', - '@backstage/SubRouteRef', - '@backstage/TranslationMessages', - '@backstage/TranslationRef', - '@backstage/TranslationResource', -] as const; - -/** - * Checks if a string is a valid package feature type. - * - * @public - */ -export const isValidPackageFeatureType = ( - value: string, -): value is BackstagePackageFeatureType => { - return packageFeatureTypes.includes(value as any); -}; - -/** - * The type of a Backstage system feature - * - * @public - */ -export type BackstagePackageFeatureType = (typeof packageFeatureTypes)[number]; - /** * Known fields in Backstage package.json files. * diff --git a/packages/cli-node/src/monorepo/index.ts b/packages/cli-node/src/monorepo/index.ts index b4ed921613..fdb8395c16 100644 --- a/packages/cli-node/src/monorepo/index.ts +++ b/packages/cli-node/src/monorepo/index.ts @@ -16,13 +16,10 @@ export { isMonoRepo } from './isMonoRepo'; export { - packageFeatureTypes, - isValidPackageFeatureType, PackageGraph, type PackageGraphNode, type BackstagePackage, type BackstagePackageJson, - type BackstagePackageFeatureType, } from './PackageGraph'; export { Lockfile, diff --git a/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts b/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts index 7d7eeeeeaf..51848a9d68 100644 --- a/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts +++ b/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts @@ -14,11 +14,14 @@ * limitations under the License. */ -import { BackstagePackageFeatureType, PackageRole } from '@backstage/cli-node'; +import { PackageRole } from '@backstage/cli-node'; import { resolve as resolvePath } from 'path'; import { Project } from 'ts-morph'; import { EntryPoint } from '../entryPoints'; -import { getDistTypeRoot } from '../typeDistProject'; +import { + getDistTypeRoot, + BackstagePackageFeatureType, +} from '../typeDistProject'; const mockEntryPoint = { mount: '.', diff --git a/packages/cli/src/lib/typeDistProject.test.ts b/packages/cli/src/lib/typeDistProject.test.ts index f3168dd9be..dfe3e68ec0 100644 --- a/packages/cli/src/lib/typeDistProject.test.ts +++ b/packages/cli/src/lib/typeDistProject.test.ts @@ -14,9 +14,12 @@ * limitations under the License. */ -import { BackstagePackageFeatureType, PackageRole } from '@backstage/cli-node'; +import { PackageRole } from '@backstage/cli-node'; import createFeatureEnvironment from './__testUtils__/createFeatureEnvironment'; -import { getEntryPointDefaultFeatureType } from './typeDistProject'; +import { + getEntryPointDefaultFeatureType, + BackstagePackageFeatureType, +} from './typeDistProject'; describe('typeDistProject', () => { describe('for package role', () => { @@ -67,30 +70,17 @@ describe('typeDistProject', () => { describe('for feature $$type', () => { // This Record makes sure we're checking all feature types - const featureTypes: Record = { - // Allowed - '@backstage/BackendFeature': true, - '@backstage/BackstagePlugin': true, - '@backstage/FrontendPlugin': true, - '@backstage/FrontendModule': true, - // Disallowed - '@backstage/BackendFeatureFactory': false, - '@backstage/BackstageCredentials': false, - '@backstage/Extension': false, - '@backstage/ExtensionDataRef': false, - '@backstage/ExtensionDataValue': false, - '@backstage/ExtensionDefinition': false, - '@backstage/ExtensionInput': false, - '@backstage/ExtensionOverrides': false, - '@backstage/ExtensionPoint': false, - '@backstage/ExternalRouteRef': false, - '@backstage/RouteRef': false, - '@backstage/ServiceRef': false, - '@backstage/SubRouteRef': false, - '@backstage/TranslationMessages': false, - '@backstage/TranslationRef': false, - '@backstage/TranslationResource': false, - }; + const featureTypes: Record = + { + // Allowed + '@backstage/BackendFeature': true, + '@backstage/BackstagePlugin': true, + '@backstage/FrontendPlugin': true, + '@backstage/FrontendModule': true, + // Disallowed + '@backstage/Extension': false, + '@backstage/RouteRef': false, + }; const allowedFeatureTypes = Object.keys(featureTypes).filter( $$type => featureTypes[$$type as BackstagePackageFeatureType], diff --git a/packages/cli/src/lib/typeDistProject.ts b/packages/cli/src/lib/typeDistProject.ts index 4fa970957d..573f8a059e 100644 --- a/packages/cli/src/lib/typeDistProject.ts +++ b/packages/cli/src/lib/typeDistProject.ts @@ -14,12 +14,7 @@ * limitations under the License. */ import { findPaths } from '@backstage/cli-common'; -import { - BackstagePackageFeatureType, - isValidPackageFeatureType, - PackageGraph, - PackageRole, -} from '@backstage/cli-node'; +import { PackageGraph, PackageRole } from '@backstage/cli-node'; import { builtinModules } from 'module'; import { resolve as resolvePath } from 'path'; import { Project, SourceFile, SyntaxKind, ts, Type } from 'ts-morph'; @@ -134,12 +129,14 @@ const targetPackageRoles: PackageRole[] = [ // A list of the feature types we want to extract from the project // and include in the metadata -const targetFeatureTypes: BackstagePackageFeatureType[] = [ +const targetFeatureTypes = [ '@backstage/BackendFeature', '@backstage/BackstagePlugin', '@backstage/FrontendPlugin', '@backstage/FrontendModule', -]; +] as const; + +export type BackstagePackageFeatureType = (typeof targetFeatureTypes)[number]; export const getEntryPointDefaultFeatureType = ( role: PackageRole, @@ -157,7 +154,7 @@ export const getEntryPointDefaultFeatureType = ( project.addSourceFileAtPath(dtsPath), ); - if (isTargetFeatureType(defaultFeatureType)) { + if (defaultFeatureType) { return defaultFeatureType; } } @@ -165,18 +162,6 @@ export const getEntryPointDefaultFeatureType = ( return null; }; -// Condition for a package role matches a target package role -function isTargetPackageRole(role: PackageRole): boolean { - return !!role && targetPackageRoles.includes(role); -} - -// Returns whether an export is a valid Backstage package feature type -function isTargetFeatureType( - type: BackstagePackageFeatureType | null, -): boolean { - return !!type && targetFeatureTypes.includes(type); -} - // Returns all exports (default and named) from an entry point // that are valid Backstage package features function getSourceFileDefaultFeatureType( @@ -230,7 +215,7 @@ function getBackstagePackageFeature$$TypeFromType( ?.getText() .match(/(?@backstage\/\w+)/)?.groups?.type; - if ($$type && isValidPackageFeatureType($$type)) { + if ($$type && isTargetFeatureType($$type)) { return $$type; } } @@ -238,3 +223,17 @@ function getBackstagePackageFeature$$TypeFromType( return null; } + +// Condition for a package role matches a target package role +function isTargetPackageRole(role: PackageRole): boolean { + return !!role && targetPackageRoles.includes(role); +} + +// Returns whether an export is a valid Backstage package feature type +function isTargetFeatureType( + type: string | BackstagePackageFeatureType, +): type is BackstagePackageFeatureType { + return ( + !!type && targetFeatureTypes.includes(type as BackstagePackageFeatureType) + ); +} From fd8128c26ff33e10f64f61cb4f3422fa51355679 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Mon, 16 Sep 2024 12:30:32 +0100 Subject: [PATCH 06/12] Handle resolving asset type imports Signed-off-by: Harrison Hogg --- packages/cli/src/lib/typeDistProject.ts | 36 +++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/lib/typeDistProject.ts b/packages/cli/src/lib/typeDistProject.ts index 573f8a059e..24e7affcfe 100644 --- a/packages/cli/src/lib/typeDistProject.ts +++ b/packages/cli/src/lib/typeDistProject.ts @@ -54,6 +54,7 @@ const getPackagesDistTypeMap = async () => { export const createTypeDistProject = async () => { const distTypeMap = await getPackagesDistTypeMap(); + const assetTypeExtensions = await getAssetTypeExtensions(); const workspaceRoot = findPaths(process.cwd()).targetRoot; return new Project({ @@ -66,7 +67,14 @@ export const createTypeDistProject = async () => { const resolvedModules: ts.ResolvedModule[] = []; for (let moduleName of moduleNames) { - // Handle resolve internal plugins and package entry points to dist-types folder + // Handle resolving asset-type modules + for (const ext of assetTypeExtensions) { + if (moduleName.endsWith(ext)) { + moduleName = '@backstage/cli/asset-types'; + } + } + + // Handle resolving internal plugins and package entry points to dist-types folder if (distTypeMap[moduleName]) { resolvedModules.push({ resolvedFileName: distTypeMap[moduleName], @@ -94,7 +102,7 @@ export const createTypeDistProject = async () => { } } - // Handle resolve relative paths and external node_modules. + // Handle resolving relative paths and external node_modules. const result = ts.resolveModuleName( moduleName, containingFile, @@ -237,3 +245,27 @@ function isTargetFeatureType( !!type && targetFeatureTypes.includes(type as BackstagePackageFeatureType) ); } + +// Returns an array of the ending extensions fro the asset-types +// that are supported by the Backstage CLI +async function getAssetTypeExtensions() { + const assetTypes: string[] = []; + const assetTypesDts = await getAssetTypesDtsFilePath(); + const project = new Project({}); + const sourceFile = project.addSourceFileAtPath(assetTypesDts); + + for (const module of sourceFile.getModules()) { + assetTypes.push( + module + .getName() + .replace(/'/g, '') // remove surrounding single quotes + .replace(/^\*/g, ''), // remove leading * + ); + } + + return assetTypes; +} + +async function getAssetTypesDtsFilePath() { + return require.resolve('@backstage/cli/asset-types/asset-types.d.ts'); +} From e1b002c2ecdd35d0346a591132423109eb619183 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Mon, 16 Sep 2024 12:48:03 +0100 Subject: [PATCH 07/12] Don't fail publishing on default feature detection Signed-off-by: Harrison Hogg --- packages/cli/src/lib/typeDistProject.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/lib/typeDistProject.ts b/packages/cli/src/lib/typeDistProject.ts index 24e7affcfe..6845272433 100644 --- a/packages/cli/src/lib/typeDistProject.ts +++ b/packages/cli/src/lib/typeDistProject.ts @@ -158,12 +158,19 @@ export const getEntryPointDefaultFeatureType = ( entryPoint.path.replace(entryPoint.ext, '.d.ts'), ); - const defaultFeatureType = getSourceFileDefaultFeatureType( - project.addSourceFileAtPath(dtsPath), - ); + try { + const defaultFeatureType = getSourceFileDefaultFeatureType( + project.addSourceFileAtPath(dtsPath), + ); - if (defaultFeatureType) { - return defaultFeatureType; + if (defaultFeatureType) { + return defaultFeatureType; + } + } catch (error) { + console.error( + `Failed to extract default feature type from ${dtsPath}, ${error}. ` + + 'Your package will publish fine but it may be missing metadata about its default feature.', + ); } } From cdab6c57b64dcb4a6c81d9283cba2384c4c18830 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Mon, 16 Sep 2024 14:39:41 +0100 Subject: [PATCH 08/12] Swap from reading dist-types/package/type to package/dist/type Signed-off-by: Harrison Hogg --- .../__testUtils__/createFeatureEnvironment.ts | 36 ++--- .../cli/src/lib/packager/productionPack.ts | 23 +-- packages/cli/src/lib/typeDistProject.ts | 135 +----------------- 3 files changed, 30 insertions(+), 164 deletions(-) diff --git a/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts b/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts index 51848a9d68..07c8f4f853 100644 --- a/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts +++ b/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts @@ -15,20 +15,11 @@ */ import { PackageRole } from '@backstage/cli-node'; -import { resolve as resolvePath } from 'path'; import { Project } from 'ts-morph'; import { EntryPoint } from '../entryPoints'; -import { - getDistTypeRoot, - BackstagePackageFeatureType, -} from '../typeDistProject'; +import { BackstagePackageFeatureType } from '../typeDistProject'; -const mockEntryPoint = { - mount: '.', - path: './src/index.d.ts', - name: 'index', - ext: '.d.ts', -}; +const mockEntryPoint = 'dist/index.d.ts'; type CreateFeatureEnvironmentOptions = { $$type?: BackstagePackageFeatureType; @@ -44,7 +35,7 @@ type FeatureEnvironment = { project: Project; role: PackageRole; dir: string; - entryPoint: EntryPoint; + entryPoint: string; }; type File = { @@ -54,7 +45,7 @@ type File = { const createTestType = ($$type: BackstagePackageFeatureType): File[] => [ { - path: './src/createTestType.d.ts', + path: './dist/createTestType.d.ts', content: ` export interface TestType { readonly $$type: '${$$type}'; @@ -71,7 +62,7 @@ export function createTestType(): TestType { const createMockDefaultExportAssignment = (): File[] => [ { - path: mockEntryPoint.path, + path: mockEntryPoint, content: ` declare const _default: import("./createTestType").TestType; export default _default; @@ -81,11 +72,11 @@ export default _default; const createMockDefaultExportFromFile = (): File[] => [ { - path: mockEntryPoint.path, + path: mockEntryPoint, content: `export { default } from './linked';`, }, { - path: './src/linked.d.ts', + path: './dist/linked.d.ts', content: ` declare const _default: import("./createTestType").TestType; export default _default; @@ -95,11 +86,11 @@ export default _default; const createMockDefaultExportFromFileAsDefault = (): File[] => [ { - path: mockEntryPoint.path, + path: mockEntryPoint, content: `export { test as default } from './linked';`, }, { - path: './src/linked.d.ts', + path: './dist/linked.d.ts', content: ` export declare const test: import("./createTestType").TestType; `, @@ -108,11 +99,11 @@ export declare const test: import("./createTestType").TestType; const createMockDefaultExportFromFileWithSibling = (): File[] => [ { - path: mockEntryPoint.path, + path: mockEntryPoint, content: `export { default, test } from './linked';`, }, { - path: './src/linked.d.ts', + path: './dist/linked.d.ts', content: ` import { createTestType } from './createTestType'; @@ -143,10 +134,7 @@ export default function createFeatureEnvironment( const files = [...createTestType($$type), ...formatToFiles[format]()]; for (const file of files) { - project.createSourceFile( - resolvePath(getDistTypeRoot(''), file.path), - file.content, - ); + project.createSourceFile(file.path, file.content); } return { diff --git a/packages/cli/src/lib/packager/productionPack.ts b/packages/cli/src/lib/packager/productionPack.ts index e4a1c13ffd..656b98f9fb 100644 --- a/packages/cli/src/lib/packager/productionPack.ts +++ b/packages/cli/src/lib/packager/productionPack.ts @@ -166,19 +166,22 @@ async function prepareExportsEntryPoints( exp[key] = `./${posixPath.join(`dist`, name)}`; } } + exp.default = exp.require ?? exp.import; - const defaultFeatureType = - pkg.backstage?.role && - getEntryPointDefaultFeatureType( - pkg.backstage?.role, - packageDir, - project, - entryPoint, - ); + if (exp.types) { + const defaultFeatureType = + pkg.backstage?.role && + getEntryPointDefaultFeatureType( + pkg.backstage?.role, + packageDir, + project, + exp.types, + ); - if (defaultFeatureType) { - exp.backstage = defaultFeatureType; + if (defaultFeatureType) { + exp.backstage = defaultFeatureType; + } } // This creates a directory with a lone package.json for backwards compatibility diff --git a/packages/cli/src/lib/typeDistProject.ts b/packages/cli/src/lib/typeDistProject.ts index 6845272433..cd5eb9cd74 100644 --- a/packages/cli/src/lib/typeDistProject.ts +++ b/packages/cli/src/lib/typeDistProject.ts @@ -14,114 +14,16 @@ * limitations under the License. */ import { findPaths } from '@backstage/cli-common'; -import { PackageGraph, PackageRole } from '@backstage/cli-node'; -import { builtinModules } from 'module'; +import { PackageRole } from '@backstage/cli-node'; import { resolve as resolvePath } from 'path'; import { Project, SourceFile, SyntaxKind, ts, Type } from 'ts-morph'; -import { EntryPoint, readEntryPoints } from './entryPoints'; - -export const getDistTypeRoot = (dir: string) => { - const workspaceRoot = findPaths(process.cwd()).targetRoot; - const relativeDirectory = dir.replace(workspaceRoot, ''); - const distTypeRoot = resolvePath( - workspaceRoot, - `./dist-types${relativeDirectory}`, - ); - - return distTypeRoot; -}; - -const getPackagesDistTypeMap = async () => { - const packages = await PackageGraph.listTargetPackages(); - const distTypeMap: Record = {}; - - for (const { dir, packageJson } of packages) { - const distTypeRoot = getDistTypeRoot(dir); - - for (const { name, path, ext } of readEntryPoints(packageJson)) { - const dtsPath = resolvePath(distTypeRoot, path.replace(ext, '.d.ts')); - - if (name === 'index') { - distTypeMap[packageJson.name] = dtsPath; - } else { - distTypeMap[`${packageJson.name}/${name}`] = dtsPath; - } - } - } - - return distTypeMap; -}; export const createTypeDistProject = async () => { - const distTypeMap = await getPackagesDistTypeMap(); - const assetTypeExtensions = await getAssetTypeExtensions(); const workspaceRoot = findPaths(process.cwd()).targetRoot; return new Project({ tsConfigFilePath: resolvePath(workspaceRoot, 'tsconfig.json'), skipAddingFilesFromTsConfig: true, - resolutionHost: (moduleResolutionHost, getCompilerOptions) => { - return { - resolveModuleNames: (moduleNames, containingFile) => { - const compilerOptions = getCompilerOptions(); - const resolvedModules: ts.ResolvedModule[] = []; - - for (let moduleName of moduleNames) { - // Handle resolving asset-type modules - for (const ext of assetTypeExtensions) { - if (moduleName.endsWith(ext)) { - moduleName = '@backstage/cli/asset-types'; - } - } - - // Handle resolving internal plugins and package entry points to dist-types folder - if (distTypeMap[moduleName]) { - resolvedModules.push({ - resolvedFileName: distTypeMap[moduleName], - isExternalLibraryImport: false, - resolvedUsingTsExtension: false, - }); - continue; - } - - // Handle resolving builtin node modules to @types/node - if (moduleName.startsWith('node:')) { - moduleName = moduleName.slice(5); - } - if (builtinModules.includes(moduleName)) { - const result = ts.resolveModuleName( - `@types/node/${moduleName}`, - containingFile, - compilerOptions, - moduleResolutionHost, - ); - - if (result.resolvedModule) { - resolvedModules.push(result.resolvedModule); - continue; - } - } - - // Handle resolving relative paths and external node_modules. - const result = ts.resolveModuleName( - moduleName, - containingFile, - compilerOptions, - moduleResolutionHost, - ); - - if (result.resolvedModule) { - resolvedModules.push(result.resolvedModule); - continue; - } - - throw new Error(`Failed to resolve module: ${moduleName}`); - } - - return resolvedModules; - }, - }; - }, }); }; @@ -150,17 +52,14 @@ export const getEntryPointDefaultFeatureType = ( role: PackageRole, packageDir: string, project: Project, - entryPoint: EntryPoint, + entryPoint: string, ): BackstagePackageFeatureType | null => { if (isTargetPackageRole(role)) { - const dtsPath = resolvePath( - getDistTypeRoot(packageDir), - entryPoint.path.replace(entryPoint.ext, '.d.ts'), - ); + const distPath = resolvePath(packageDir, entryPoint); try { const defaultFeatureType = getSourceFileDefaultFeatureType( - project.addSourceFileAtPath(dtsPath), + project.addSourceFileAtPath(distPath), ); if (defaultFeatureType) { @@ -168,7 +67,7 @@ export const getEntryPointDefaultFeatureType = ( } } catch (error) { console.error( - `Failed to extract default feature type from ${dtsPath}, ${error}. ` + + `Failed to extract default feature type from ${distPath}, ${error}. ` + 'Your package will publish fine but it may be missing metadata about its default feature.', ); } @@ -252,27 +151,3 @@ function isTargetFeatureType( !!type && targetFeatureTypes.includes(type as BackstagePackageFeatureType) ); } - -// Returns an array of the ending extensions fro the asset-types -// that are supported by the Backstage CLI -async function getAssetTypeExtensions() { - const assetTypes: string[] = []; - const assetTypesDts = await getAssetTypesDtsFilePath(); - const project = new Project({}); - const sourceFile = project.addSourceFileAtPath(assetTypesDts); - - for (const module of sourceFile.getModules()) { - assetTypes.push( - module - .getName() - .replace(/'/g, '') // remove surrounding single quotes - .replace(/^\*/g, ''), // remove leading * - ); - } - - return assetTypes; -} - -async function getAssetTypesDtsFilePath() { - return require.resolve('@backstage/cli/asset-types/asset-types.d.ts'); -} From 87b6b83180b60643672ce2e59769da56bd524820 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Mon, 16 Sep 2024 14:49:11 +0100 Subject: [PATCH 09/12] Removed left over import Signed-off-by: Harrison Hogg --- packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts b/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts index 07c8f4f853..a12a48c523 100644 --- a/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts +++ b/packages/cli/src/lib/__testUtils__/createFeatureEnvironment.ts @@ -16,7 +16,6 @@ import { PackageRole } from '@backstage/cli-node'; import { Project } from 'ts-morph'; -import { EntryPoint } from '../entryPoints'; import { BackstagePackageFeatureType } from '../typeDistProject'; const mockEntryPoint = 'dist/index.d.ts'; From 59d6bf3ab48e037a123a33e022ed3ef9d2da6741 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Mon, 23 Sep 2024 09:46:16 +0100 Subject: [PATCH 10/12] Ensure backstage field is at the top Signed-off-by: Harrison Hogg --- packages/cli/src/lib/packager/productionPack.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/lib/packager/productionPack.ts b/packages/cli/src/lib/packager/productionPack.ts index 656b98f9fb..8e89be603c 100644 --- a/packages/cli/src/lib/packager/productionPack.ts +++ b/packages/cli/src/lib/packager/productionPack.ts @@ -159,7 +159,8 @@ async function prepareExportsEntryPoints( continue; } - const exp = {} as Record; + let exp = {} as Record; + for (const [key, ext] of Object.entries(EXPORT_MAP)) { const name = `${entryPoint.name}${ext}`; if (distFiles.includes(name)) { @@ -169,6 +170,7 @@ async function prepareExportsEntryPoints( exp.default = exp.require ?? exp.import; + // Find the default export type for the entry point if (exp.types) { const defaultFeatureType = pkg.backstage?.role && @@ -180,7 +182,10 @@ async function prepareExportsEntryPoints( ); if (defaultFeatureType) { - exp.backstage = defaultFeatureType; + // This ensures that the `backstage` field is at the top of the + // `exports` field in the package.json because order is important. + // https://nodejs.org/docs/latest-v20.x/api/packages.html#conditional-exports + exp = { backstage: defaultFeatureType, ...exp }; } } From e69428c5f848095528767d92d97ed74c6812b439 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Mon, 23 Sep 2024 09:46:39 +0100 Subject: [PATCH 11/12] Use the paths to find workspace files Signed-off-by: Harrison Hogg --- packages/cli/src/lib/typeDistProject.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/typeDistProject.ts b/packages/cli/src/lib/typeDistProject.ts index cd5eb9cd74..8721f3fa24 100644 --- a/packages/cli/src/lib/typeDistProject.ts +++ b/packages/cli/src/lib/typeDistProject.ts @@ -13,16 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { findPaths } from '@backstage/cli-common'; import { PackageRole } from '@backstage/cli-node'; import { resolve as resolvePath } from 'path'; import { Project, SourceFile, SyntaxKind, ts, Type } from 'ts-morph'; +import { paths } from './paths'; export const createTypeDistProject = async () => { - const workspaceRoot = findPaths(process.cwd()).targetRoot; - return new Project({ - tsConfigFilePath: resolvePath(workspaceRoot, 'tsconfig.json'), + tsConfigFilePath: paths.resolveTargetRoot('tsconfig.json'), skipAddingFilesFromTsConfig: true, }); }; From 0b2794a3875afb161c0f3e84b1065a68a9ee6779 Mon Sep 17 00:00:00 2001 From: Harrison Hogg Date: Mon, 23 Sep 2024 12:45:07 +0100 Subject: [PATCH 12/12] Removed @backstage from matching regex Signed-off-by: Harrison Hogg --- packages/cli/src/lib/typeDistProject.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/lib/typeDistProject.ts b/packages/cli/src/lib/typeDistProject.ts index 8721f3fa24..59dc503f22 100644 --- a/packages/cli/src/lib/typeDistProject.ts +++ b/packages/cli/src/lib/typeDistProject.ts @@ -125,7 +125,7 @@ function getBackstagePackageFeature$$TypeFromType( const $$type = property .getValueDeclaration() ?.getText() - .match(/(?@backstage\/\w+)/)?.groups?.type; + .match(/(\$\$type: '(?.+)')/)?.groups?.type; if ($$type && isTargetFeatureType($$type)) { return $$type;