From b751402fc6e3e178c4fe50b0a6a8a3fcdc0742d9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 8 Feb 2025 12:36:29 +0100 Subject: [PATCH] cli/new: refactor package info resolution Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/new/new.ts | 19 ++- packages/cli/src/lib/new/createNewPackage.ts | 2 +- .../new/execution/executePortableTemplate.ts | 11 +- .../new/execution/resolvePackageInfo.test.ts | 108 ++++++++++++++++ .../lib/new/execution/resolvePackageInfo.ts | 65 ++++++++++ .../cli/src/lib/new/execution/utils.test.ts | 120 ------------------ packages/cli/src/lib/new/execution/utils.ts | 54 -------- .../preparation/loadPortableTemplateConfig.ts | 8 +- packages/cli/src/lib/new/types.ts | 11 +- 9 files changed, 205 insertions(+), 193 deletions(-) create mode 100644 packages/cli/src/lib/new/execution/resolvePackageInfo.test.ts create mode 100644 packages/cli/src/lib/new/execution/resolvePackageInfo.ts delete mode 100644 packages/cli/src/lib/new/execution/utils.test.ts delete mode 100644 packages/cli/src/lib/new/execution/utils.ts diff --git a/packages/cli/src/commands/new/new.ts b/packages/cli/src/commands/new/new.ts index 0ab5cf027f..aa38f05aa1 100644 --- a/packages/cli/src/commands/new/new.ts +++ b/packages/cli/src/commands/new/new.ts @@ -30,15 +30,30 @@ export default async (opts: ArgOptions) => { const { option: rawArgOptions, select: preselectedTemplateId, - ...globals + scope, + ...otherGlobals } = opts; const prefilledParams = parseParams(rawArgOptions); + let pluginInfix: string | undefined = undefined; + let packagePrefix: string | undefined = undefined; + if (scope) { + const backstagePrefix = scope.startsWith('backstage') ? '' : 'backstage-'; + packagePrefix = scope.includes('/') + ? `@${scope}${backstagePrefix}` + : `@${scope}/${backstagePrefix}`; + pluginInfix = scope.includes('backstage') ? 'plugin-' : 'backstage-plugin-'; + } + await createNewPackage({ prefilledParams, preselectedTemplateId, - globals, + globals: { + ...otherGlobals, + packagePrefix, + pluginInfix, + }, }); }; diff --git a/packages/cli/src/lib/new/createNewPackage.ts b/packages/cli/src/lib/new/createNewPackage.ts index 3fc3317eed..849adccf43 100644 --- a/packages/cli/src/lib/new/createNewPackage.ts +++ b/packages/cli/src/lib/new/createNewPackage.ts @@ -25,7 +25,7 @@ import { PortableTemplateGlobals, PortableTemplateParams } from './types'; export type CreateNewPackageOptions = { preselectedTemplateId?: string; - globals: PortableTemplateGlobals; + globals: Partial; prefilledParams: PortableTemplateParams; }; diff --git a/packages/cli/src/lib/new/execution/executePortableTemplate.ts b/packages/cli/src/lib/new/execution/executePortableTemplate.ts index 586515a0fe..31b9e1e53c 100644 --- a/packages/cli/src/lib/new/execution/executePortableTemplate.ts +++ b/packages/cli/src/lib/new/execution/executePortableTemplate.ts @@ -23,12 +23,12 @@ import { paths } from '../../paths'; import { Task } from '../../tasks'; import { addCodeownersEntry } from '../../codeowners'; -import { createDirName, resolvePackageName } from './utils'; import { runAdditionalActions } from './additionalActions'; import { executePluginPackageTemplate } from './executePluginPackageTemplate'; import { TemporaryDirectoryManager } from './TemporaryDirectoryManager'; import { PortableTemplateConfig, PortableTemplateInput } from '../types'; import { PortableTemplate } from '../types'; +import { resolvePackageInfo } from './resolvePackageInfo'; type ExecuteNewTemplateOptions = { config: PortableTemplateConfig; @@ -43,14 +43,9 @@ export async function executePortableTemplate( const tmpDirManager = TemporaryDirectoryManager.create(); - const dirName = createDirName(template, params); - const targetDir = paths.resolveTargetRoot(params.targetPath, dirName); + const packageInfo = resolvePackageInfo(input); - const packageName = resolvePackageName({ - baseName: dirName, - scope: params.scope, - plugin: template.plugin ?? true, - }); + const targetDir = paths.resolveTargetRoot(packageInfo.packagePath); const moduleVar = params.moduleId ?? diff --git a/packages/cli/src/lib/new/execution/resolvePackageInfo.test.ts b/packages/cli/src/lib/new/execution/resolvePackageInfo.test.ts new file mode 100644 index 0000000000..30223171c4 --- /dev/null +++ b/packages/cli/src/lib/new/execution/resolvePackageInfo.test.ts @@ -0,0 +1,108 @@ +/* + * 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 { resolvePackageInfo } from './resolvePackageInfo'; + +const baseGlobals = { + baseVersion: '0.0.0', + license: 'Apache-2.0', + private: true, + packagePrefix: '@internal/', + pluginInfix: 'plugin-', +}; + +describe.each([ + [ + { role: 'web-library', name: 'test' }, + { + packageName: '@internal/test', + packagePath: 'packages/test', + }, + ], + [ + { role: 'node-library', name: 'test' }, + { + packageName: '@internal/test', + packagePath: 'packages/test', + }, + ], + [ + { role: 'common-library', name: 'test' }, + { + packageName: '@internal/test', + packagePath: 'packages/test', + }, + ], + [ + { role: 'plugin-web-library', pluginId: 'test' }, + { + packageName: '@internal/plugin-test-react', + packagePath: 'plugins/test-react', + }, + ], + [ + { role: 'plugin-node-library', pluginId: 'test' }, + { + packageName: '@internal/plugin-test-node', + packagePath: 'plugins/test-node', + }, + ], + [ + { role: 'plugin-common-library', pluginId: 'test' }, + { + packageName: '@internal/plugin-test-common', + packagePath: 'plugins/test-common', + }, + ], + [ + { role: 'frontend-plugin', pluginId: 'test' }, + { + packageName: '@internal/plugin-test', + packagePath: 'plugins/test', + }, + ], + [ + { role: 'backend-plugin', pluginId: 'test' }, + { + packageName: '@internal/plugin-test-backend', + packagePath: 'plugins/test-backend', + }, + ], + [ + { role: 'frontend-plugin-module', pluginId: 'test1', moduleId: 'test2' }, + { + packageName: '@internal/plugin-test1-module-test2', + packagePath: 'plugins/test1-module-test2', + }, + ], + [ + { role: 'backend-plugin-module', pluginId: 'test1', moduleId: 'test2' }, + { + packageName: '@internal/plugin-test1-backend-module-test2', + packagePath: 'plugins/test1-backend-module-test2', + }, + ], +] as const)('resolvePackageInfo', (roleParams, packageInfo) => { + it(`should generate correct info with default config for ${roleParams.role}`, () => { + expect( + resolvePackageInfo({ + builtInParams: {}, + roleParams, + params: {}, + globals: baseGlobals, + }), + ).toEqual(packageInfo); + }); +}); diff --git a/packages/cli/src/lib/new/execution/resolvePackageInfo.ts b/packages/cli/src/lib/new/execution/resolvePackageInfo.ts new file mode 100644 index 0000000000..bb6aba272f --- /dev/null +++ b/packages/cli/src/lib/new/execution/resolvePackageInfo.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2025 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 { join as joinPath } from 'path'; +import { + PortableTemplateInput, + PortableTemplateInputRoleParams, +} from '../types'; + +export type PortableTemplatePackageInfo = { + packageName: string; + packagePath: string; +}; + +export function resolvePackageInfo( + input: PortableTemplateInput, +): PortableTemplatePackageInfo { + const baseName = getBaseNameForRole(input.roleParams); + const isPlugin = input.roleParams.role.includes('plugin'); + const pluginInfix = isPlugin ? input.globals.pluginInfix : ''; + return { + packageName: `${input.globals.packagePrefix}${pluginInfix}${baseName}`, + packagePath: joinPath(isPlugin ? 'plugins' : 'packages', baseName), + }; +} + +function getBaseNameForRole( + roleParams: PortableTemplateInputRoleParams, +): string { + switch (roleParams.role) { + case 'web-library': + case 'node-library': + case 'common-library': + return roleParams.name; + case 'plugin-web-library': + return `${roleParams.pluginId}-react`; + case 'plugin-node-library': + return `${roleParams.pluginId}-node`; + case 'plugin-common-library': + return `${roleParams.pluginId}-common`; + case 'frontend-plugin': + return `${roleParams.pluginId}`; + case 'frontend-plugin-module': + return `${roleParams.pluginId}-module-${roleParams.moduleId}`; + case 'backend-plugin': + return `${roleParams.pluginId}-backend`; + case 'backend-plugin-module': + return `${roleParams.pluginId}-backend-module-${roleParams.moduleId}`; + default: + throw new Error(`Unknown role ${(roleParams as { role: string }).role}`); + } +} diff --git a/packages/cli/src/lib/new/execution/utils.test.ts b/packages/cli/src/lib/new/execution/utils.test.ts deleted file mode 100644 index 9528186447..0000000000 --- a/packages/cli/src/lib/new/execution/utils.test.ts +++ /dev/null @@ -1,120 +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 { resolvePackageName, createDirName, Options } from './utils'; -import { PortableTemplate } from '../types'; - -describe('resolvePackageName', () => { - it('should generate correct name without scope', () => { - expect(resolvePackageName({ baseName: 'test', plugin: true })).toEqual( - 'backstage-plugin-test', - ); - expect(resolvePackageName({ baseName: 'test', plugin: false })).toEqual( - 'test', - ); - }); - - it('should generate correct name for backstage scope', () => { - expect( - resolvePackageName({ - baseName: 'test', - scope: 'backstage', - plugin: true, - }), - ).toEqual('@backstage/plugin-test'); - expect( - resolvePackageName({ - baseName: 'test', - scope: 'backstage', - plugin: false, - }), - ).toEqual('@backstage/test'); - }); - - it('should generate correct name for custom scope', () => { - expect( - resolvePackageName({ - baseName: 'test', - scope: 'custom', - plugin: true, - }), - ).toEqual('@custom/backstage-plugin-test'); - expect( - resolvePackageName({ - baseName: 'test', - scope: 'custom', - plugin: false, - }), - ).toEqual('@custom/test'); - }); - - it('should generate correct name for custom scope and custom prefix', () => { - expect( - resolvePackageName({ - baseName: 'test', - scope: 'custom/myapp.', - plugin: true, - }), - ).toEqual('@custom/myapp.backstage-plugin-test'); - expect( - resolvePackageName({ - baseName: 'test', - scope: 'custom/myapp.', - plugin: false, - }), - ).toEqual('@custom/myapp.test'); - }); -}); - -describe('createDirName', () => { - it('should return name in the backend-module format if backendModulePrefix is set to true', () => { - expect( - createDirName( - { backendModulePrefix: true } as PortableTemplate, - { - id: 'foo', - moduleId: 'bar', - } as Options, - ), - ).toEqual('foo-backend-module-bar'); - }); - - it('should throw an error if backendModulePrefix is configured as true but is missing moduleId', () => { - expect(() => - createDirName( - { backendModulePrefix: true } as PortableTemplate, - { - id: 'foo', - moduleId: '', - } as Options, - ), - ).toThrow('backendModulePrefix requires moduleId prompt'); - }); - - it('should append the suffix value if one is provided', () => { - expect( - createDirName( - { suffix: 'foo' } as PortableTemplate, - { id: 'bar' } as Options, - ), - ).toEqual('bar-foo'); - }); - - it('should return id if neither backendModulePrefix nor suffix is specified', () => { - expect( - createDirName({} as PortableTemplate, { id: 'foo' } as Options), - ).toEqual('foo'); - }); -}); diff --git a/packages/cli/src/lib/new/execution/utils.ts b/packages/cli/src/lib/new/execution/utils.ts deleted file mode 100644 index 4a9a1f3a53..0000000000 --- a/packages/cli/src/lib/new/execution/utils.ts +++ /dev/null @@ -1,54 +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 { PortableTemplate } from '../types'; - -export const resolvePackageName = (options: { - baseName: string; - scope?: string; - plugin: boolean; -}) => { - const { baseName, scope, plugin } = options; - if (scope) { - if (plugin) { - const pluginName = scope.startsWith('backstage') - ? 'plugin' - : 'backstage-plugin'; - return scope.includes('/') - ? `@${scope}${pluginName}-${baseName}` - : `@${scope}/${pluginName}-${baseName}`; - } - return scope.includes('/') - ? `@${scope}${baseName}` - : `@${scope}/${baseName}`; - } - - return plugin ? `backstage-plugin-${baseName}` : baseName; -}; - -export function createDirName(template: PortableTemplate, options: Options) { - if (!options.id) { - throw new Error(`id prompt is mandatory for all cli templates`); - } - if (template.backendModulePrefix) { - if (!options.moduleId) { - throw new Error(`backendModulePrefix requires moduleId prompt`); - } - return `${options.id}-backend-module-${options.moduleId}`; - } else if (template.suffix) { - return `${options.id}-${template.suffix}`; - } - return options.id; -} diff --git a/packages/cli/src/lib/new/preparation/loadPortableTemplateConfig.ts b/packages/cli/src/lib/new/preparation/loadPortableTemplateConfig.ts index 124c6d05eb..d88f1fd0ea 100644 --- a/packages/cli/src/lib/new/preparation/loadPortableTemplateConfig.ts +++ b/packages/cli/src/lib/new/preparation/loadPortableTemplateConfig.ts @@ -26,7 +26,8 @@ const defaultGlobals = { license: 'Apache-2.0', baseVersion: '0.1.0', private: true, - scope: undefined, + packagePrefix: '@internal/', + pluginInfix: 'plugin-', }; const pkgJsonWithNewConfigSchema = z.object({ @@ -49,7 +50,8 @@ const pkgJsonWithNewConfigSchema = z.object({ license: z.string().optional(), baseVersion: z.string().optional(), private: z.boolean().optional(), - scope: z.string().optional(), + packagePrefix: z.string().optional(), + pluginInfix: z.string().optional(), }) .optional(), }) @@ -61,7 +63,7 @@ const pkgJsonWithNewConfigSchema = z.object({ type LoadConfigOptions = { packagePath?: string; - globalOverrides?: PortableTemplateGlobals; + globalOverrides?: Partial; }; export async function loadPortableTemplateConfig( diff --git a/packages/cli/src/lib/new/types.ts b/packages/cli/src/lib/new/types.ts index 4a70e03d7f..33f56388fa 100644 --- a/packages/cli/src/lib/new/types.ts +++ b/packages/cli/src/lib/new/types.ts @@ -73,10 +73,11 @@ export type PortableTemplateParams = { }; export type PortableTemplateGlobals = { - license?: string; - baseVersion?: string; - private?: boolean; - scope?: string; + license: string; + baseVersion: string; + private: boolean; + packagePrefix: string; + pluginInfix: string; }; export type PortableTemplateInputRoleParams = @@ -107,5 +108,5 @@ export type PortableTemplateInput = { roleParams: PortableTemplateInputRoleParams; builtInParams: PortableTemplateInputBuiltInParams; params: PortableTemplateParams; - globals: PortableTemplateParams; + globals: PortableTemplateGlobals; };