cli/new: refactor package info resolution

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-02-08 12:36:29 +01:00
parent 08bf562c98
commit b751402fc6
9 changed files with 205 additions and 193 deletions
+17 -2
View File
@@ -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,
},
});
};
+1 -1
View File
@@ -25,7 +25,7 @@ import { PortableTemplateGlobals, PortableTemplateParams } from './types';
export type CreateNewPackageOptions = {
preselectedTemplateId?: string;
globals: PortableTemplateGlobals;
globals: Partial<PortableTemplateGlobals>;
prefilledParams: PortableTemplateParams;
};
@@ -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 ??
@@ -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);
});
});
@@ -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}`);
}
}
@@ -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');
});
});
@@ -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;
}
@@ -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<PortableTemplateGlobals>;
};
export async function loadPortableTemplateConfig(
+6 -5
View File
@@ -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;
};