diff --git a/.changeset/spotty-jokes-unite.md b/.changeset/spotty-jokes-unite.md new file mode 100644 index 0000000000..15097e42d0 --- /dev/null +++ b/.changeset/spotty-jokes-unite.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Harmonize the package naming and allow custom prefix diff --git a/packages/cli/src/lib/new/factories/backendModule.ts b/packages/cli/src/lib/new/factories/backendModule.ts index 221adcb1f3..1450e7b6ce 100644 --- a/packages/cli/src/lib/new/factories/backendModule.ts +++ b/packages/cli/src/lib/new/factories/backendModule.ts @@ -19,7 +19,7 @@ import chalk from 'chalk'; import camelCase from 'lodash/camelCase'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; -import { createFactory, CreateContext } from '../types'; +import { CreateContext, createFactory } from '../types'; import { addPackageDependency, Task } from '../../tasks'; import { moduleIdIdPrompt, @@ -27,6 +27,7 @@ import { pluginIdPrompt, } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; +import { resolvePackageName } from './common/util'; type Options = { id: string; @@ -45,9 +46,11 @@ export const backendModule = createFactory({ async create(options: Options, ctx: CreateContext) { const { id: pluginId, moduleId } = options; const dirName = `${pluginId}-backend-module-${moduleId}`; - const name = ctx.scope - ? `@${ctx.scope}/plugin-${dirName}` - : `backstage-plugin-${dirName}`; + const name = resolvePackageName({ + baseName: dirName, + scope: ctx.scope, + plugin: true, + }); Task.log(); Task.log(`Creating backend module ${chalk.cyan(name)}`); diff --git a/packages/cli/src/lib/new/factories/backendPlugin.ts b/packages/cli/src/lib/new/factories/backendPlugin.ts index c5c9b5bd0e..09ea98fbe7 100644 --- a/packages/cli/src/lib/new/factories/backendPlugin.ts +++ b/packages/cli/src/lib/new/factories/backendPlugin.ts @@ -19,10 +19,11 @@ import chalk from 'chalk'; import camelCase from 'lodash/camelCase'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; -import { createFactory, CreateContext } from '../types'; +import { CreateContext, createFactory } from '../types'; import { addPackageDependency, Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; +import { resolvePackageName } from './common/util'; type Options = { id: string; @@ -40,9 +41,11 @@ export const backendPlugin = createFactory({ async create(options: Options, ctx: CreateContext) { const { id } = options; const pluginId = `${id}-backend`; - const name = ctx.scope - ? `@${ctx.scope}/plugin-${pluginId}` - : `backstage-plugin-${pluginId}`; + const name = resolvePackageName({ + baseName: pluginId, + scope: ctx.scope, + plugin: true, + }); Task.log(); Task.log(`Creating backend plugin ${chalk.cyan(name)}`); diff --git a/packages/cli/src/lib/new/factories/common/util.test.ts b/packages/cli/src/lib/new/factories/common/util.test.ts new file mode 100644 index 0000000000..6f168d8035 --- /dev/null +++ b/packages/cli/src/lib/new/factories/common/util.test.ts @@ -0,0 +1,78 @@ +/* + * 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 } from './util'; + +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'); + }); +}); diff --git a/packages/cli/src/lib/new/factories/common/util.ts b/packages/cli/src/lib/new/factories/common/util.ts new file mode 100644 index 0000000000..d9c6970afd --- /dev/null +++ b/packages/cli/src/lib/new/factories/common/util.ts @@ -0,0 +1,38 @@ +/* + * 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. + */ + +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; +}; diff --git a/packages/cli/src/lib/new/factories/frontendPlugin.test.ts b/packages/cli/src/lib/new/factories/frontendPlugin.test.ts index 94d4a7eb4c..cca79dea2a 100644 --- a/packages/cli/src/lib/new/factories/frontendPlugin.test.ts +++ b/packages/cli/src/lib/new/factories/frontendPlugin.test.ts @@ -180,7 +180,7 @@ const router = ( fs.readJson(mockDir.resolve('packages/app/package.json')), ).resolves.toEqual({ dependencies: { - '@internal/plugin-test': '^1.0.0', + '@internal/backstage-plugin-test': '^1.0.0', }, }); @@ -188,7 +188,7 @@ const router = ( fs.readFile(mockDir.resolve('packages/app/src/App.tsx'), 'utf8'), ).resolves.toBe(` import { createApp } from '@backstage/app-defaults'; -import { TestPage } from '@internal/plugin-test'; +import { TestPage } from '@internal/backstage-plugin-test'; const router = ( diff --git a/packages/cli/src/lib/new/factories/frontendPlugin.ts b/packages/cli/src/lib/new/factories/frontendPlugin.ts index d0e81207ca..1058c879c1 100644 --- a/packages/cli/src/lib/new/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/new/factories/frontendPlugin.ts @@ -20,10 +20,11 @@ import camelCase from 'lodash/camelCase'; import upperFirst from 'lodash/upperFirst'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; -import { createFactory, CreateContext } from '../types'; +import { CreateContext, createFactory } from '../types'; import { addPackageDependency, Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; +import { resolvePackageName } from './common/util'; type Options = { id: string; @@ -41,9 +42,11 @@ export const frontendPlugin = createFactory({ async create(options: Options, ctx: CreateContext) { const { id } = options; - const name = ctx.scope - ? `@${ctx.scope}/plugin-${id}` - : `backstage-plugin-${id}`; + const name = resolvePackageName({ + baseName: id, + scope: ctx.scope, + plugin: true, + }); const extensionName = `${upperFirst(camelCase(id))}Page`; Task.log(); diff --git a/packages/cli/src/lib/new/factories/nodeLibraryPackage.ts b/packages/cli/src/lib/new/factories/nodeLibraryPackage.ts index 4b1fda4abc..3fae1242a2 100644 --- a/packages/cli/src/lib/new/factories/nodeLibraryPackage.ts +++ b/packages/cli/src/lib/new/factories/nodeLibraryPackage.ts @@ -17,10 +17,11 @@ import chalk from 'chalk'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; -import { createFactory, CreateContext } from '../types'; +import { CreateContext, createFactory } from '../types'; import { Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; +import { resolvePackageName } from './common/util'; type Options = { id: string; @@ -37,7 +38,11 @@ export const nodeLibraryPackage = createFactory({ optionsPrompts: [pluginIdPrompt(), ownerPrompt()], async create(options: Options, ctx: CreateContext) { const { id } = options; - const name = ctx.scope ? `@${ctx.scope}/${id}` : `${id}`; + const name = resolvePackageName({ + baseName: id, + scope: ctx.scope, + plugin: false, + }); Task.log(); Task.log(`Creating node-library package ${chalk.cyan(name)}`); diff --git a/packages/cli/src/lib/new/factories/pluginCommon.ts b/packages/cli/src/lib/new/factories/pluginCommon.ts index 1bcca2fb6a..560386c618 100644 --- a/packages/cli/src/lib/new/factories/pluginCommon.ts +++ b/packages/cli/src/lib/new/factories/pluginCommon.ts @@ -17,10 +17,11 @@ import chalk from 'chalk'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; -import { createFactory, CreateContext } from '../types'; +import { CreateContext, createFactory } from '../types'; import { Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; +import { resolvePackageName } from './common/util'; type Options = { id: string; @@ -38,9 +39,11 @@ export const pluginCommon = createFactory({ async create(options: Options, ctx: CreateContext) { const { id } = options; const suffix = `${id}-common`; - const name = ctx.scope - ? `@${ctx.scope}/plugin-${suffix}` - : `backstage-plugin-${suffix}`; + const name = resolvePackageName({ + baseName: suffix, + scope: ctx.scope, + plugin: true, + }); Task.log(); Task.log(`Creating backend plugin ${chalk.cyan(name)}`); diff --git a/packages/cli/src/lib/new/factories/pluginNode.ts b/packages/cli/src/lib/new/factories/pluginNode.ts index 44dbea9fce..c7f9ba6f6a 100644 --- a/packages/cli/src/lib/new/factories/pluginNode.ts +++ b/packages/cli/src/lib/new/factories/pluginNode.ts @@ -17,10 +17,11 @@ import chalk from 'chalk'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; -import { createFactory, CreateContext } from '../types'; +import { CreateContext, createFactory } from '../types'; import { Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; +import { resolvePackageName } from './common/util'; type Options = { id: string; @@ -38,9 +39,11 @@ export const pluginNode = createFactory({ async create(options: Options, ctx: CreateContext) { const { id } = options; const suffix = `${id}-node`; - const name = ctx.scope - ? `@${ctx.scope}/plugin-${suffix}` - : `backstage-plugin-${suffix}`; + const name = resolvePackageName({ + baseName: suffix, + scope: ctx.scope, + plugin: true, + }); Task.log(); Task.log(`Creating Node.js plugin library ${chalk.cyan(name)}`); diff --git a/packages/cli/src/lib/new/factories/pluginWeb.ts b/packages/cli/src/lib/new/factories/pluginWeb.ts index 8189654eb2..dc8cb73dce 100644 --- a/packages/cli/src/lib/new/factories/pluginWeb.ts +++ b/packages/cli/src/lib/new/factories/pluginWeb.ts @@ -17,10 +17,11 @@ import chalk from 'chalk'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; -import { createFactory, CreateContext } from '../types'; +import { CreateContext, createFactory } from '../types'; import { Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; +import { resolvePackageName } from './common/util'; type Options = { id: string; @@ -38,9 +39,11 @@ export const pluginWeb = createFactory({ async create(options: Options, ctx: CreateContext) { const { id } = options; const suffix = `${id}-react`; - const name = ctx.scope - ? `@${ctx.scope}/plugin-${suffix}` - : `backstage-plugin-${suffix}`; + const name = resolvePackageName({ + baseName: suffix, + scope: ctx.scope, + plugin: true, + }); Task.log(); Task.log(`Creating web plugin library ${chalk.cyan(name)}`); diff --git a/packages/cli/src/lib/new/factories/scaffolderModule.ts b/packages/cli/src/lib/new/factories/scaffolderModule.ts index b89f0fc691..07f67ce78c 100644 --- a/packages/cli/src/lib/new/factories/scaffolderModule.ts +++ b/packages/cli/src/lib/new/factories/scaffolderModule.ts @@ -17,10 +17,11 @@ import chalk from 'chalk'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; -import { createFactory, CreateContext } from '../types'; +import { CreateContext, createFactory } from '../types'; import { Task } from '../../tasks'; import { ownerPrompt } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; +import { resolvePackageName } from './common/util'; type Options = { id: string; @@ -55,14 +56,11 @@ export const scaffolderModule = createFactory({ const { id } = options; const slug = `scaffolder-backend-module-${id}`; - let name = `backstage-plugin-${slug}`; - if (ctx.scope) { - if (ctx.scope === 'backstage') { - name = `@backstage/plugin-${slug}`; - } else { - name = `@${ctx.scope}/backstage-plugin-${slug}`; - } - } + const name = resolvePackageName({ + baseName: slug, + scope: ctx.scope, + plugin: true, + }); Task.log(); Task.log(`Creating module ${chalk.cyan(name)}`); diff --git a/packages/cli/src/lib/new/factories/webLibraryPackage.ts b/packages/cli/src/lib/new/factories/webLibraryPackage.ts index a160220cab..8f4488926d 100644 --- a/packages/cli/src/lib/new/factories/webLibraryPackage.ts +++ b/packages/cli/src/lib/new/factories/webLibraryPackage.ts @@ -17,10 +17,11 @@ import chalk from 'chalk'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; -import { createFactory, CreateContext } from '../types'; +import { CreateContext, createFactory } from '../types'; import { Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; +import { resolvePackageName } from './common/util'; type Options = { id: string; @@ -37,7 +38,11 @@ export const webLibraryPackage = createFactory({ optionsPrompts: [pluginIdPrompt(), ownerPrompt()], async create(options: Options, ctx: CreateContext) { const { id } = options; - const name = ctx.scope ? `@${ctx.scope}/${id}` : `${id}`; + const name = resolvePackageName({ + baseName: id, + scope: ctx.scope, + plugin: false, + }); Task.log(); Task.log(`Creating web-library package ${chalk.cyan(name)}`);