cli/new: flatten config globals

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-02-09 14:27:20 +01:00
parent 8630bc6548
commit 8bd62ff071
12 changed files with 163 additions and 161 deletions
+6 -4
View File
@@ -49,10 +49,12 @@ export default async (opts: ArgOptions) => {
await createNewPackage({
prefilledParams,
preselectedTemplateId,
globals: {
...otherGlobals,
packagePrefix,
pluginInfix,
configOverrides: {
license: otherGlobals.license,
version: otherGlobals.baseVersion,
private: otherGlobals.private,
packageNamePrefix: packagePrefix,
packageNamePluginInfix: pluginInfix,
},
});
};
+3 -3
View File
@@ -21,17 +21,17 @@ import {
selectTemplateInteractively,
} from './preparation';
import { executePortableTemplate } from './execution';
import { PortableTemplateGlobals, PortableTemplateParams } from './types';
import { PortableTemplateConfig, PortableTemplateParams } from './types';
export type CreateNewPackageOptions = {
preselectedTemplateId?: string;
globals: Partial<PortableTemplateGlobals>;
configOverrides: Partial<PortableTemplateConfig>;
prefilledParams: PortableTemplateParams;
};
export async function createNewPackage(options: CreateNewPackageOptions) {
const config = await loadPortableTemplateConfig({
globalOverrides: options.globals,
overrides: options.configOverrides,
});
const selectedTemplate = await selectTemplateInteractively(
@@ -95,7 +95,7 @@ async function installFrontend(input: PortableTemplateInput) {
paths.resolveTargetRoot('packages/app/package.json'),
{
dependencies: {
[input.packageParams.packageName]: `workspace:^`,
[input.packageName]: `workspace:^`,
},
},
);
@@ -104,7 +104,7 @@ async function installFrontend(input: PortableTemplateInput) {
}
async function addFrontendLegacy(input: PortableTemplateInput) {
const { roleParams, packageParams } = input;
const { roleParams } = input;
if (roleParams.role !== 'frontend-plugin') {
throw new Error(
'add-frontend-legacy can only be used for frontend plugins',
@@ -127,10 +127,8 @@ async function addFrontendLegacy(input: PortableTemplateInput) {
);
if (lastImportIndex !== -1 && lastRouteIndex !== -1) {
const extensionName = upperFirst(
`${camelCase(packageParams.packageName)}Page`,
);
const importLine = `import { ${extensionName} } from '${packageParams.packageName}';`;
const extensionName = upperFirst(`${camelCase(roleParams.pluginId)}Page`);
const importLine = `import { ${extensionName} } from '${input.packageName}';`;
if (!content.includes(importLine)) {
revLines.splice(lastImportIndex, 0, importLine);
}
@@ -154,7 +152,7 @@ async function installBackend(input: PortableTemplateInput) {
paths.resolveTargetRoot('packages/backend/package.json'),
{
dependencies: {
[input.packageParams.packageName]: `workspace:^`,
[input.packageName]: `workspace:^`,
},
},
);
@@ -164,33 +162,29 @@ async function installBackend(input: PortableTemplateInput) {
async function addBackend(input: PortableTemplateInput) {
if (await fs.pathExists(paths.resolveTargetRoot('packages/backend'))) {
await Task.forItem(
'backend',
`adding ${input.packageParams.packageName}`,
async () => {
const backendFilePath = paths.resolveTargetRoot(
'packages/backend/src/index.ts',
);
if (!(await fs.pathExists(backendFilePath))) {
return;
}
await Task.forItem('backend', `adding ${input.packageName}`, async () => {
const backendFilePath = paths.resolveTargetRoot(
'packages/backend/src/index.ts',
);
if (!(await fs.pathExists(backendFilePath))) {
return;
}
const content = await fs.readFile(backendFilePath, 'utf8');
const lines = content.split('\n');
const backendAddLine = `backend.add(import('${input.packageParams.packageName}'));`;
const content = await fs.readFile(backendFilePath, 'utf8');
const lines = content.split('\n');
const backendAddLine = `backend.add(import('${input.packageName}'));`;
const backendStartIndex = lines.findIndex(line =>
line.match(/backend.start/),
);
const backendStartIndex = lines.findIndex(line =>
line.match(/backend.start/),
);
if (backendStartIndex !== -1) {
const [indentation] = lines[backendStartIndex].match(/^\s*/)!;
lines.splice(backendStartIndex, 0, `${indentation}${backendAddLine}`);
if (backendStartIndex !== -1) {
const [indentation] = lines[backendStartIndex].match(/^\s*/)!;
lines.splice(backendStartIndex, 0, `${indentation}${backendAddLine}`);
const newContent = lines.join('\n');
await fs.writeFile(backendFilePath, newContent, 'utf8');
}
},
);
const newContent = lines.join('\n');
await fs.writeFile(backendFilePath, newContent, 'utf8');
}
});
}
}
@@ -19,12 +19,10 @@ import { writeTemplateContents } from './writeTemplateContents';
import { createMockDirectory } from '@backstage/backend-test-utils';
import { paths } from '../../paths';
const mockGlobals = {
baseVersion: '0.1.0',
const baseConfig = {
version: '0.1.0',
license: 'Apache-2.0',
private: true,
packagePrefix: '@internal/',
pluginInfix: 'plugin-',
};
describe('writeTemplateContents', () => {
@@ -47,14 +45,12 @@ describe('writeTemplateContents', () => {
templateValues: {},
},
{
...baseConfig,
roleParams: { role: 'frontend-plugin', pluginId: 'test' },
globals: mockGlobals,
builtInParams: {},
packageParams: {
packageName: '@internal/plugin-test',
packagePath: 'plugins/plugin-test',
},
params: {},
packageName: '@internal/plugin-test',
packagePath: 'plugins/plugin-test',
},
);
@@ -85,16 +81,14 @@ describe('writeTemplateContents', () => {
templateValues: {},
},
{
...baseConfig,
roleParams: { role: 'frontend-plugin', pluginId: 'test' },
globals: mockGlobals,
builtInParams: {},
packageParams: {
packageName: '@internal/plugin-test',
packagePath: 'out',
},
params: {
pluginId: 'test',
},
packageName: '@internal/plugin-test',
packagePath: 'out',
},
);
@@ -27,12 +27,10 @@ export async function writeTemplateContents(
template: PortableTemplate,
input: PortableTemplateInput,
): Promise<{ targetDir: string }> {
const targetDir = paths.resolveTargetRoot(input.packageParams.packagePath);
const targetDir = paths.resolveTargetRoot(input.packagePath);
if (await fs.pathExists(targetDir)) {
throw new InputError(
`Package '${input.packageParams.packagePath}' already exists`,
);
throw new InputError(`Package '${input.packagePath}' already exists`);
}
try {
@@ -23,13 +23,11 @@ describe('collectTemplateParams', () => {
config: {
isUsingDefaultTemplates: false,
templatePointers: [],
globals: {
baseVersion: '0.1.0',
license: 'Apache-2.0',
private: true,
packagePrefix: '@internal/',
pluginInfix: 'plugin-',
},
version: '0.1.0',
license: 'Apache-2.0',
private: true,
packageNamePrefix: '@internal/',
packageNamePluginInfix: 'plugin-',
} satisfies PortableTemplateConfig,
template: {
id: 'test',
@@ -53,10 +51,6 @@ describe('collectTemplateParams', () => {
builtInParams: {
owner: 'me',
},
packageParams: {
packageName: '@internal/plugin-test',
packagePath: 'plugins/test',
},
params: {
license: 'Apache-2.0',
packageName: '@internal/plugin-test',
@@ -65,7 +59,11 @@ describe('collectTemplateParams', () => {
pluginId: 'test',
owner: 'me',
},
globals: baseOptions.config.globals,
version: '0.1.0',
license: 'Apache-2.0',
private: true,
packageName: '@internal/plugin-test',
packagePath: 'plugins/test',
});
});
@@ -85,10 +83,6 @@ describe('collectTemplateParams', () => {
builtInParams: {
owner: undefined,
},
packageParams: {
packageName: '@internal/plugin-other',
packagePath: 'plugins/other',
},
params: {
license: 'Apache-2.0',
packageName: '@internal/plugin-other',
@@ -97,7 +91,11 @@ describe('collectTemplateParams', () => {
pluginId: 'other',
owner: undefined,
},
globals: baseOptions.config.globals,
version: '0.1.0',
license: 'Apache-2.0',
private: true,
packageName: '@internal/plugin-other',
packagePath: 'plugins/other',
});
});
});
@@ -73,22 +73,29 @@ export async function collectPortableTemplateInput(
moduleId: answers.moduleId,
} as PortableTemplateInputRoleParams;
const packageParams = resolvePackageParams(roleParams, config.globals);
const packageParams = resolvePackageParams({
roleParams,
packagePrefix: config.packageNamePrefix,
pluginInfix: config.packageNamePluginInfix,
});
return {
roleParams,
packageParams,
builtInParams: {
owner: answers.owner,
} as PortableTemplateInputBuiltInParams,
params: {
...answers,
packageName: packageParams.packageName,
privatePackage: config.globals.private,
packageVersion: config.globals.baseVersion,
license: config.globals.license,
privatePackage: config.private,
packageVersion: config.version,
license: config.license,
},
globals: config.globals,
license: config.license,
version: config.version,
private: config.private,
packageName: packageParams.packageName,
packagePath: packageParams.packagePath,
};
}
@@ -37,6 +37,8 @@ describe('loadPortableTemplateConfig', () => {
globals: {
license: 'MIT',
private: true,
namePrefix: '@acme/',
namePluginInfix: 'backstage-plugin-',
},
},
},
@@ -53,17 +55,17 @@ describe('loadPortableTemplateConfig', () => {
{ id: 'template1', target: 'path/to/template1' },
{ id: 'template2', target: 'path/to/template2' },
],
globals: {
license: 'MIT',
private: true,
baseVersion: '0.1.0',
},
license: 'MIT',
private: true,
version: '0.1.0',
packageNamePrefix: '@acme/',
packageNamePluginInfix: 'backstage-plugin-',
});
await expect(
loadPortableTemplateConfig({
packagePath: mockDir.resolve('package.json'),
globalOverrides: {
overrides: {
license: 'nope',
private: false,
},
@@ -74,11 +76,11 @@ describe('loadPortableTemplateConfig', () => {
{ id: 'template1', target: 'path/to/template1' },
{ id: 'template2', target: 'path/to/template2' },
],
globals: {
license: 'nope',
private: false,
baseVersion: '0.1.0',
},
license: 'nope',
version: '0.1.0',
private: false,
packageNamePrefix: '@acme/',
packageNamePluginInfix: 'backstage-plugin-',
});
});
@@ -103,11 +105,11 @@ describe('loadPortableTemplateConfig', () => {
).resolves.toEqual({
isUsingDefaultTemplates: true,
templatePointers: defaultTemplates,
globals: {
license: 'MIT',
private: true,
baseVersion: '0.1.0',
},
license: 'MIT',
private: true,
version: '0.1.0',
packageNamePrefix: '@internal/',
packageNamePluginInfix: 'plugin-',
});
});
@@ -143,28 +145,28 @@ describe('loadPortableTemplateConfig', () => {
).resolves.toEqual({
isUsingDefaultTemplates: true,
templatePointers: defaultTemplates,
globals: {
license: 'Apache-2.0',
baseVersion: '0.1.0',
private: true,
},
license: 'Apache-2.0',
version: '0.1.0',
private: true,
packageNamePrefix: '@internal/',
packageNamePluginInfix: 'plugin-',
});
await expect(
loadPortableTemplateConfig({
packagePath: mockDir.resolve('package.json'),
globalOverrides: {
overrides: {
license: 'nope',
},
}),
).resolves.toEqual({
isUsingDefaultTemplates: true,
templatePointers: defaultTemplates,
globals: {
license: 'nope',
baseVersion: '0.1.0',
private: true,
},
license: 'nope',
version: '0.1.0',
private: true,
packageNamePrefix: '@internal/',
packageNamePluginInfix: 'plugin-',
});
});
});
@@ -17,17 +17,17 @@
import fs from 'fs-extra';
import { paths } from '../../paths';
import { defaultTemplates } from '../defaultTemplates';
import { PortableTemplateConfig, PortableTemplateGlobals } from '../types';
import { PortableTemplateConfig } from '../types';
import { z } from 'zod';
import { fromZodError } from 'zod-validation-error';
import { ForwardedError } from '@backstage/errors';
const defaultGlobals = {
const defaults = {
license: 'Apache-2.0',
baseVersion: '0.1.0',
version: '0.1.0',
private: true,
packagePrefix: '@internal/',
pluginInfix: 'plugin-',
packageNamePrefix: '@internal/',
packageNamePluginInfix: 'plugin-',
};
const pkgJsonWithNewConfigSchema = z.object({
@@ -48,10 +48,10 @@ const pkgJsonWithNewConfigSchema = z.object({
globals: z
.object({
license: z.string().optional(),
baseVersion: z.string().optional(),
version: z.string().optional(),
private: z.boolean().optional(),
packagePrefix: z.string().optional(),
pluginInfix: z.string().optional(),
namePrefix: z.string().optional(),
namePluginInfix: z.string().optional(),
})
.optional(),
})
@@ -63,12 +63,13 @@ const pkgJsonWithNewConfigSchema = z.object({
type LoadConfigOptions = {
packagePath?: string;
globalOverrides?: Partial<PortableTemplateGlobals>;
overrides?: Partial<PortableTemplateConfig>;
};
export async function loadPortableTemplateConfig(
options: LoadConfigOptions = {},
): Promise<PortableTemplateConfig> {
const { overrides = {} } = options;
const pkgPath =
options.packagePath ?? paths.resolveTargetRoot('package.json');
const pkgJson = await fs.readJson(pkgPath);
@@ -81,15 +82,21 @@ export async function loadPortableTemplateConfig(
);
}
const newConfig = parsed.data.backstage?.new;
const config = parsed.data.backstage?.new;
return {
isUsingDefaultTemplates: !newConfig?.templates,
templatePointers: newConfig?.templates ?? defaultTemplates,
globals: {
...defaultGlobals,
...newConfig?.globals,
...options.globalOverrides,
},
isUsingDefaultTemplates: !config?.templates,
templatePointers: config?.templates ?? defaultTemplates,
license: overrides.license ?? config?.globals?.license ?? defaults.license,
version: overrides.version ?? config?.globals?.version ?? defaults.version,
private: overrides.private ?? config?.globals?.private ?? defaults.private,
packageNamePrefix:
overrides.packageNamePrefix ??
config?.globals?.namePrefix ??
defaults.packageNamePrefix,
packageNamePluginInfix:
overrides.packageNamePluginInfix ??
config?.globals?.namePluginInfix ??
defaults.packageNamePluginInfix,
};
}
@@ -15,14 +15,6 @@
*/
import { resolvePackageParams } from './resolvePackageParams';
const baseGlobals = {
baseVersion: '0.0.0',
license: 'Apache-2.0',
private: true,
packagePrefix: '@internal/',
pluginInfix: 'plugin-',
};
describe.each([
[
{ role: 'web-library', name: 'test' },
@@ -96,6 +88,12 @@ describe.each([
],
] as const)('resolvePackageInfo', (roleParams, packageInfo) => {
it(`should generate correct info with default config for ${roleParams.role}`, () => {
expect(resolvePackageParams(roleParams, baseGlobals)).toEqual(packageInfo);
expect(
resolvePackageParams({
roleParams,
packagePrefix: '@internal/',
pluginInfix: 'plugin-',
}),
).toEqual(packageInfo);
});
});
@@ -15,10 +15,13 @@
*/
import { join as joinPath } from 'path';
import {
PortableTemplateGlobals,
PortableTemplateInputRoleParams,
} from '../types';
import { PortableTemplateInputRoleParams } from '../types';
export type ResolvePackageParamsOptions = {
roleParams: PortableTemplateInputRoleParams;
pluginInfix: string;
packagePrefix: string;
};
export type PortableTemplatePackageInfo = {
packageName: string;
@@ -26,14 +29,13 @@ export type PortableTemplatePackageInfo = {
};
export function resolvePackageParams(
roleParams: PortableTemplateInputRoleParams,
globals: PortableTemplateGlobals,
options: ResolvePackageParamsOptions,
): PortableTemplatePackageInfo {
const baseName = getBaseNameForRole(roleParams);
const isPlugin = roleParams.role.includes('plugin');
const pluginInfix = isPlugin ? globals.pluginInfix : '';
const baseName = getBaseNameForRole(options.roleParams);
const isPlugin = options.roleParams.role.includes('plugin');
const pluginInfix = isPlugin ? options.pluginInfix : '';
return {
packageName: `${globals.packagePrefix}${pluginInfix}${baseName}`,
packageName: `${options.packagePrefix}${pluginInfix}${baseName}`,
packagePath: joinPath(isPlugin ? 'plugins' : 'packages', baseName),
};
}
+19 -19
View File
@@ -25,10 +25,15 @@ export type PortableTemplateConfig = {
*/
isUsingDefaultTemplates: boolean;
/**
* Templating globals that should apply to all templates.
*/
globals: PortableTemplateGlobals;
license: string;
version: string;
private: boolean;
packageNamePrefix: string;
packageNamePluginInfix: string;
};
export type PortableTemplatePointer = {
@@ -77,14 +82,6 @@ export type PortableTemplateParams = {
[KName in string]?: string | number | boolean;
};
export type PortableTemplateGlobals = {
license: string;
baseVersion: string;
private: boolean;
packagePrefix: string;
pluginInfix: string;
};
export type PortableTemplateInputRoleParams =
| {
role: 'web-library' | 'node-library' | 'common-library';
@@ -109,15 +106,18 @@ export type PortableTemplateInputBuiltInParams = {
owner?: string;
};
export type PortableTemplateInputPackageParams = {
packageName: string;
packagePath: string;
};
export type PortableTemplateInput = {
roleParams: PortableTemplateInputRoleParams;
builtInParams: PortableTemplateInputBuiltInParams;
packageParams: PortableTemplateInputPackageParams;
params: PortableTemplateParams;
globals: PortableTemplateGlobals;
license: string;
version: string;
private: boolean;
packageName: string;
packagePath: string;
};