From 3f8252f343517638578424643010fff1781d1078 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Feb 2025 12:02:37 +0100 Subject: [PATCH] cli/new: refactor config reading Signed-off-by: Patrik Oldsberg --- package.json | 6 +- packages/cli/package.json | 3 +- .../cli/src/lib/new/config/loadNewConfig.ts | 69 +++++++++++++++++++ packages/cli/src/lib/new/config/types.ts | 39 +++++++++++ packages/cli/src/lib/new/createNewPackage.ts | 19 +++-- packages/cli/src/lib/new/defaultTemplates.ts | 68 ++++++++++++++++++ packages/cli/src/lib/new/prompts.ts | 2 +- packages/cli/src/lib/new/templateSelector.ts | 26 +------ .../cli/templates/all-default-templates.ts | 40 ----------- yarn.lock | 1 + 10 files changed, 192 insertions(+), 81 deletions(-) create mode 100644 packages/cli/src/lib/new/config/loadNewConfig.ts create mode 100644 packages/cli/src/lib/new/config/types.ts create mode 100644 packages/cli/src/lib/new/defaultTemplates.ts delete mode 100644 packages/cli/templates/all-default-templates.ts diff --git a/package.json b/package.json index 0f81625707..ffee986509 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,11 @@ "url": "https://github.com/backstage/backstage" }, "backstage": { - "cli": { - "defaults": true, + "new": { "globals": { "scope": "backstage", "private": false - }, - "templates": [] + } } }, "workspaces": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 37ce3665a1..6e7fd8025f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -160,7 +160,8 @@ "yargs": "^16.2.0", "yml-loader": "^2.1.0", "yn": "^4.0.0", - "zod": "^3.22.4" + "zod": "^3.22.4", + "zod-validation-error": "^3.4.0" }, "devDependencies": { "@backstage/backend-plugin-api": "workspace:^", diff --git a/packages/cli/src/lib/new/config/loadNewConfig.ts b/packages/cli/src/lib/new/config/loadNewConfig.ts new file mode 100644 index 0000000000..1e9f54e13e --- /dev/null +++ b/packages/cli/src/lib/new/config/loadNewConfig.ts @@ -0,0 +1,69 @@ +/* + * 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 fs from 'fs-extra'; +import { paths } from '../../paths'; +import { defaultTemplates } from '../defaultTemplates'; +import { NewConfig } from './types'; +import { z } from 'zod'; +import { fromZodError } from 'zod-validation-error'; +import { ForwardedError } from '@backstage/errors'; + +const pkgJsonWithNewConfigSchema = z.object({ + backstage: z + .object({ + new: z + .object({ + templates: z + .array( + z + .object({ + id: z.string(), + target: z.string(), + }) + .strict(), + ) + .optional(), + globals: z + .record(z.union([z.string(), z.number(), z.boolean()])) + .optional(), + }) + .strict() + .optional(), + }) + .optional(), +}); + +export async function loadNewConfig(): Promise { + const pkgPath = paths.resolveTargetRoot('package.json'); + const pkgJson = await fs.readJson(pkgPath); + + const parsed = pkgJsonWithNewConfigSchema.safeParse(pkgJson); + if (!parsed.success) { + throw new ForwardedError( + `Failed to load templating configuration from '${pkgPath}'`, + fromZodError(parsed.error), + ); + } + + const newConfig = parsed.data.backstage?.new; + + return { + isUsingDefaultTemplates: !newConfig?.templates, + templatePointers: newConfig?.templates ?? defaultTemplates, + globals: newConfig?.globals ?? {}, + }; +} diff --git a/packages/cli/src/lib/new/config/types.ts b/packages/cli/src/lib/new/config/types.ts new file mode 100644 index 0000000000..b4949b59a1 --- /dev/null +++ b/packages/cli/src/lib/new/config/types.ts @@ -0,0 +1,39 @@ +/* + * 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. + */ + +export type NewConfig = { + /** + * The pointers to templates that can be used. + */ + templatePointers: NewTemplatePointer[]; + + /** + * Whether the default set of templates are being used or not. + */ + isUsingDefaultTemplates: boolean; + + /** + * Templating globals that should apply to all templates. + */ + globals: { + [KName in string]?: number | string | boolean; + }; +}; + +export type NewTemplatePointer = { + id: string; + target: string; +}; diff --git a/packages/cli/src/lib/new/createNewPackage.ts b/packages/cli/src/lib/new/createNewPackage.ts index 56d089277b..d3f3f58ec7 100644 --- a/packages/cli/src/lib/new/createNewPackage.ts +++ b/packages/cli/src/lib/new/createNewPackage.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import fs from 'fs-extra'; import camelCase from 'lodash/camelCase'; import upperFirst from 'lodash/upperFirst'; import { isMonoRepo } from '@backstage/cli-node'; @@ -24,16 +23,13 @@ import { paths } from '../paths'; import { Task } from '../tasks'; import { addCodeownersEntry, getCodeownersFilePath } from '../codeowners'; -import { - readCliConfig, - templateSelector, - verifyTemplate, -} from './templateSelector'; +import { templateSelector, verifyTemplate } from './templateSelector'; import { promptOptions } from './prompts'; import { populateOptions, createDirName, resolvePackageName } from './utils'; import { runAdditionalActions } from './additionalActions'; import { executePluginPackageTemplate } from './executeTemplate'; import { TemporaryDirectoryManager } from './TemporaryDirectoryManager'; +import { loadNewConfig } from './config/loadNewConfig'; export type CreateNewPackageOptions = { preselectedTemplateId?: string; @@ -48,12 +44,13 @@ export type CreateNewPackageOptions = { }; export async function createNewPackage(options: CreateNewPackageOptions) { - const pkgJson = await fs.readJson(paths.resolveTargetRoot('package.json')); - const cliConfig = pkgJson.backstage?.cli; + const newConfig = await loadNewConfig(); - const { templates, globals } = readCliConfig(cliConfig); const template = verifyTemplate( - await templateSelector(templates, options.preselectedTemplateId), + await templateSelector( + newConfig.templatePointers, + options.preselectedTemplateId, + ), ); const codeOwnersFilePath = await getCodeownersFilePath(paths.targetRoot); @@ -74,7 +71,7 @@ export async function createNewPackage(options: CreateNewPackageOptions) { typeof prompt === 'string' ? prompt : prompt.id, ), ) ?? [], - globals, + globals: newConfig.globals, codeOwnersFilePath, }); const answers = { ...prefilledAnswers, ...promptAnswers }; diff --git a/packages/cli/src/lib/new/defaultTemplates.ts b/packages/cli/src/lib/new/defaultTemplates.ts new file mode 100644 index 0000000000..b8b4d0c086 --- /dev/null +++ b/packages/cli/src/lib/new/defaultTemplates.ts @@ -0,0 +1,68 @@ +/* + * 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. + */ + +export const defaultTemplates = [ + { + id: 'backend-module', + target: require.resolve( + '@backstage/cli/templates/default-backend-module.yaml', + ), + }, + { + id: 'backend-plugin', + target: require.resolve( + '@backstage/cli/templates/default-backend-plugin.yaml', + ), + }, + { + id: 'plugin-common', + target: require.resolve( + '@backstage/cli/templates/default-common-plugin-package.yaml', + ), + }, + { + id: 'plugin-node', + target: require.resolve( + '@backstage/cli/templates/default-node-plugin-package.yaml', + ), + }, + { + id: 'frontend-plugin', + target: require.resolve('@backstage/cli/templates/default-plugin.yaml'), + }, + { + id: 'plugin-react', + target: require.resolve( + '@backstage/cli/templates/default-react-plugin-package.yaml', + ), + }, + { + id: 'node-library', + target: require.resolve( + '@backstage/cli/templates/node-library-package.yaml', + ), + }, + { + id: 'scaffolder-module', + target: require.resolve('@backstage/cli/templates/scaffolder-module.yaml'), + }, + { + id: 'web-library', + target: require.resolve( + '@backstage/cli/templates/web-library-package.yaml', + ), + }, +]; diff --git a/packages/cli/src/lib/new/prompts.ts b/packages/cli/src/lib/new/prompts.ts index 33654c8cc3..c7ba0fcaba 100644 --- a/packages/cli/src/lib/new/prompts.ts +++ b/packages/cli/src/lib/new/prompts.ts @@ -95,7 +95,7 @@ export async function promptOptions({ codeOwnersFilePath, }: { prompts: ConfigurablePrompt[]; - globals: Record; + globals: { [name in string]?: string | boolean | number }; codeOwnersFilePath: string | undefined; }): Promise> { const answers = await inquirer.prompt( diff --git a/packages/cli/src/lib/new/templateSelector.ts b/packages/cli/src/lib/new/templateSelector.ts index eca4a09bae..9be7c144c2 100644 --- a/packages/cli/src/lib/new/templateSelector.ts +++ b/packages/cli/src/lib/new/templateSelector.ts @@ -19,31 +19,9 @@ import { parse } from 'yaml'; import fs from 'fs-extra'; import { paths } from '../paths'; -import { Template, TemplateLocation, CliConfig } from './types'; +import { Template, TemplateLocation } from './types'; -import defaultTemplates from '../../../templates/all-default-templates'; - -export function readCliConfig(cliConfig: CliConfig) { - let templates: TemplateLocation[] = []; - - if (!cliConfig || cliConfig?.defaults) { - templates = defaultTemplates; - } - - const cliTemplates = cliConfig?.templates; - if (cliTemplates?.length) { - cliTemplates.forEach((template: TemplateLocation) => { - templates.push({ - id: template.id, - target: template.target, - }); - }); - } - return { - templates, - globals: { ...cliConfig?.globals }, - }; -} +import { defaultTemplates } from './defaultTemplates'; export async function templateSelector( templates: TemplateLocation[], diff --git a/packages/cli/templates/all-default-templates.ts b/packages/cli/templates/all-default-templates.ts deleted file mode 100644 index 9295982f06..0000000000 --- a/packages/cli/templates/all-default-templates.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { paths } from '../src/lib/paths'; - -export default [ - { - id: 'backend-module', - target: paths.resolveTargetRoot('node_modules/@backstage/cli/templates/default-backend-module.yaml'), - }, - { - id: 'backend-plugin', - target: paths.resolveTargetRoot('node_modules/@backstage/cli/templates/default-backend-plugin.yaml'), - }, - { - id: 'plugin-common', - target: paths.resolveTargetRoot('node_modules/@backstage/cli/templates/default-common-plugin-package.yaml'), - }, - { - id: 'plugin-node', - target: paths.resolveTargetRoot('node_modules/@backstage/cli/templates/default-node-plugin-package.yaml'), - }, - { - id: 'frontend-plugin', // changed from 'plugin' - target: paths.resolveTargetRoot('node_modules/@backstage/cli/templates/default-plugin.yaml'), - }, - { - id: 'plugin-react', - target: paths.resolveTargetRoot('node_modules/@backstage/cli/templates/default-react-plugin-package.yaml'), - }, - { - id: 'node-library', - target: paths.resolveTargetRoot('node_modules/@backstage/cli/templates/node-library-package.yaml'), - }, - { - id: 'scaffolder-module', - target: paths.resolveTargetRoot('node_modules/@backstage/cli/templates/scaffolder-module.yaml'), - }, - { - id: 'web-library', - target: paths.resolveTargetRoot('node_modules/@backstage/cli/templates/web-library-package.yaml'), - }, -]; diff --git a/yarn.lock b/yarn.lock index fb1806e230..13f261c890 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4051,6 +4051,7 @@ __metadata: yml-loader: ^2.1.0 yn: ^4.0.0 zod: ^3.22.4 + zod-validation-error: ^3.4.0 peerDependencies: "@rspack/core": ^1.0.10 "@rspack/dev-server": ^1.0.9