From 9940bdca73f2cd0a4e47576a960b6d2a8abc0e1b Mon Sep 17 00:00:00 2001 From: Min Kim Date: Thu, 19 Sep 2024 20:13:44 -0400 Subject: [PATCH 001/145] Create yaml files for existing templates Signed-off-by: Min Kim --- .../templates/alpha/all-default-templates.ts | 38 +++++++++++++++++++ .../alpha/default-backend-module.yaml | 8 ++++ .../alpha/default-backend-plugin.yaml | 8 ++++ .../alpha/default-common-plugin-package.yaml | 8 ++++ .../alpha/default-node-plugin-package.yaml | 8 ++++ .../cli/templates/alpha/default-plugin.yaml | 8 ++++ .../alpha/default-react-plugin-package.yaml | 8 ++++ .../templates/alpha/node-library-package.yaml | 8 ++++ .../templates/alpha/scaffolder-module.yaml | 8 ++++ .../templates/alpha/web-library-package.yaml | 8 ++++ 10 files changed, 110 insertions(+) create mode 100644 packages/cli/templates/alpha/all-default-templates.ts create mode 100644 packages/cli/templates/alpha/default-backend-module.yaml create mode 100644 packages/cli/templates/alpha/default-backend-plugin.yaml create mode 100644 packages/cli/templates/alpha/default-common-plugin-package.yaml create mode 100644 packages/cli/templates/alpha/default-node-plugin-package.yaml create mode 100644 packages/cli/templates/alpha/default-plugin.yaml create mode 100644 packages/cli/templates/alpha/default-react-plugin-package.yaml create mode 100644 packages/cli/templates/alpha/node-library-package.yaml create mode 100644 packages/cli/templates/alpha/scaffolder-module.yaml create mode 100644 packages/cli/templates/alpha/web-library-package.yaml diff --git a/packages/cli/templates/alpha/all-default-templates.ts b/packages/cli/templates/alpha/all-default-templates.ts new file mode 100644 index 0000000000..0a342ea3ea --- /dev/null +++ b/packages/cli/templates/alpha/all-default-templates.ts @@ -0,0 +1,38 @@ +export default [ + { + id: 'backend-module', + target: '@backstage/cli/templates/alpha/default-backend-module.yaml', + }, + { + id: 'backend-plugin', + target: '@backstage/cli/templates/alpha/default-backend-plugin.yaml', + }, + { + id: 'plugin-common', + target: '@backstage/cli/templates/alpha/default-common-plugin-package.yaml', + }, + { + id: 'plugin-node', + target: '@backstage/cli/templates/alpha/default-node-plugin-package.yaml', + }, + { + id: 'frontend-plugion', // changed from 'plugin' + target: '@backstage/cli/templates/alpha/default-plugin.yaml', + }, + { + id: 'plugin-react', + target: '@backstage/cli/templates/alpha/default-react-plugin-package.yaml', + }, + { + id: 'node-library', + target: '@backstage/cli/templates/alpha/node-library-package.yaml', + }, + { + id: 'scaffolder-module', + target: '@backstage/cli/templates/alpha/scaffolder-module.yaml', + }, + { + id: 'web-library', + target: '@backstage/cli/templates/alpha/web-library-package.yaml', + }, +]; diff --git a/packages/cli/templates/alpha/default-backend-module.yaml b/packages/cli/templates/alpha/default-backend-module.yaml new file mode 100644 index 0000000000..54906866e0 --- /dev/null +++ b/packages/cli/templates/alpha/default-backend-module.yaml @@ -0,0 +1,8 @@ +# lib/new/factories/backendModule.ts +description: A new backend module that extends an existing backend plugin with additional features +template: ../default-backend-module +targetPath: plugins +prompts: + - id: test + prompt: Test prompt + default: test-default diff --git a/packages/cli/templates/alpha/default-backend-plugin.yaml b/packages/cli/templates/alpha/default-backend-plugin.yaml new file mode 100644 index 0000000000..e9484fed39 --- /dev/null +++ b/packages/cli/templates/alpha/default-backend-plugin.yaml @@ -0,0 +1,8 @@ +# lib/new/factories/backendPlugin.ts +description: A new backend plugin +template: ../default-backend-plugin +targetPath: plugins +prompts: + - id: test + prompt: Test prompt + default: test-default diff --git a/packages/cli/templates/alpha/default-common-plugin-package.yaml b/packages/cli/templates/alpha/default-common-plugin-package.yaml new file mode 100644 index 0000000000..0f94432e2a --- /dev/null +++ b/packages/cli/templates/alpha/default-common-plugin-package.yaml @@ -0,0 +1,8 @@ +# lib/new/factories/pluginCommon.ts +description: A new isomorphic common plugin package +template: ../default-common-plugin-package +targetPath: plugins +prompts: + - id: test + prompt: Test prompt + default: test-default diff --git a/packages/cli/templates/alpha/default-node-plugin-package.yaml b/packages/cli/templates/alpha/default-node-plugin-package.yaml new file mode 100644 index 0000000000..a461cac8a6 --- /dev/null +++ b/packages/cli/templates/alpha/default-node-plugin-package.yaml @@ -0,0 +1,8 @@ +# lib/new/factories/pluginNode.ts +description: A new Node.js library plugin package +template: ../default-node-plugin-package +targetPath: plugins +prompts: + - id: test + prompt: Test prompt + default: test-default diff --git a/packages/cli/templates/alpha/default-plugin.yaml b/packages/cli/templates/alpha/default-plugin.yaml new file mode 100644 index 0000000000..ac42b393cb --- /dev/null +++ b/packages/cli/templates/alpha/default-plugin.yaml @@ -0,0 +1,8 @@ +# lib/new/factories/frontendPlugin.ts +description: A new frontend plugin +template: ../default-plugin +targetPath: plugins +prompts: + - id: test + prompt: Test prompt + default: test-default diff --git a/packages/cli/templates/alpha/default-react-plugin-package.yaml b/packages/cli/templates/alpha/default-react-plugin-package.yaml new file mode 100644 index 0000000000..f022d990f5 --- /dev/null +++ b/packages/cli/templates/alpha/default-react-plugin-package.yaml @@ -0,0 +1,8 @@ +# lib/new/factories/pluginWeb.ts +description: A new web library plugin package +template: ../default-react-plugin-package +targetPath: plugins +prompts: + - id: test + prompt: Test prompt + default: test-default diff --git a/packages/cli/templates/alpha/node-library-package.yaml b/packages/cli/templates/alpha/node-library-package.yaml new file mode 100644 index 0000000000..51146363a0 --- /dev/null +++ b/packages/cli/templates/alpha/node-library-package.yaml @@ -0,0 +1,8 @@ +# lib/new/factories/nodeLibraryPackage.ts +description: A new node-library package, exporting shared functionality for backend plugins and modules +template: ../node-library-package +targetPath: packages +prompts: + - id: test + prompt: Test prompt + default: test-default diff --git a/packages/cli/templates/alpha/scaffolder-module.yaml b/packages/cli/templates/alpha/scaffolder-module.yaml new file mode 100644 index 0000000000..59fd2e9e3f --- /dev/null +++ b/packages/cli/templates/alpha/scaffolder-module.yaml @@ -0,0 +1,8 @@ +# lib/new/factories/scaffolderModule.ts +description: A module exporting custom actions for @backstage/plugin-scaffolder-backend +template: ../scaffolder-module +targetPath: plugins +prompts: + - id: test + prompt: Test prompt + default: test-default diff --git a/packages/cli/templates/alpha/web-library-package.yaml b/packages/cli/templates/alpha/web-library-package.yaml new file mode 100644 index 0000000000..8580e6ea65 --- /dev/null +++ b/packages/cli/templates/alpha/web-library-package.yaml @@ -0,0 +1,8 @@ +# lib/new/factories/webLibraryPackage.ts +description: A new web-library package, exporting shared functionality for frontend plugins +template: ../web-library-package +targetPath: packages +prompts: + - id: test + prompt: Test prompt + default: test-default From fb8c558f17440ab65a678656bea842c72357e0e9 Mon Sep 17 00:00:00 2001 From: Min Kim Date: Thu, 26 Sep 2024 09:33:45 -0400 Subject: [PATCH 002/145] Add cli config in root pkg.json Signed-off-by: Min Kim --- package.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/package.json b/package.json index 11b419d3fb..8653f76f68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,20 @@ { "name": "root", "version": "1.36.0-next.0", + "backstage": { + "cli": { + "defaults": false, + "globals": { + "private": false + }, + "templates": [ + { + "id": "local web plugin", + "target": "./packages/cli/templates/alpha/web-library-package.yaml" + } + ] + } + }, "private": true, "repository": { "type": "git", From 03c9b8744bb90f2cd78e0f63f717cb46cbe9bd3f Mon Sep 17 00:00:00 2001 From: Min Kim Date: Thu, 26 Sep 2024 09:35:39 -0400 Subject: [PATCH 003/145] Get templates and globals from pkg.json Signed-off-by: Min Kim --- packages/cli/src/commands/new/new.ts | 194 +++++++++++++++------------ 1 file changed, 105 insertions(+), 89 deletions(-) diff --git a/packages/cli/src/commands/new/new.ts b/packages/cli/src/commands/new/new.ts index 2733b0be9b..7dda08626b 100644 --- a/packages/cli/src/commands/new/new.ts +++ b/packages/cli/src/commands/new/new.ts @@ -23,98 +23,114 @@ import { isMonoRepo } from '@backstage/cli-node'; import { paths } from '../../lib/paths'; import { assertError } from '@backstage/errors'; import { Task } from '../../lib/tasks'; +import defaultTemplates from '../../../templates/alpha/all-default-templates'; -function parseOptions(optionStrings: string[]): Record { - const options: Record = {}; - - for (const str of optionStrings) { - const [key] = str.split('=', 1); - const value = str.slice(key.length + 1); - if (!key || str[key.length] !== '=') { - throw new Error( - `Invalid option '${str}', must be of the format =`, - ); - } - options[key] = value; - } - - return options; +interface TemplateLocation { + id: string; + target: string; } -export default async (opts: OptionValues) => { - const factory = await FactoryRegistry.interactiveSelect(opts.select); - - const providedOptions = parseOptions(opts.option); - const options = await FactoryRegistry.populateOptions( - factory, - providedOptions, - ); - - let defaultVersion = '0.1.0'; - if (opts.baseVersion) { - defaultVersion = opts.baseVersion; - } else { - const lernaVersion = await fs - .readJson(paths.resolveTargetRoot('lerna.json')) - .then(pkg => pkg.version) - .catch(() => undefined); - if (lernaVersion) { - defaultVersion = lernaVersion; - } - } - - const tempDirs = new Array(); - async function createTemporaryDirectory(name: string): Promise { - const dir = await fs.mkdtemp(joinPath(os.tmpdir(), name)); - tempDirs.push(dir); - return dir; - } - - const license = opts.license ?? 'Apache-2.0'; - - let modified = false; - try { - await factory.create(options, { - isMonoRepo: await isMonoRepo(), - defaultVersion, - license, - scope: opts.scope?.replace(/^@/, ''), - npmRegistry: opts.npmRegistry, - private: Boolean(opts.private), - createTemporaryDirectory, - markAsModified() { - modified = true; - }, - }); - - Task.log(); - Task.log(`🎉 Successfully created ${factory.name}`); - Task.log(); - } catch (error) { - assertError(error); - Task.error(error.message); - - if (modified) { - Task.log('It seems that something went wrong in the creation process 🤔'); - Task.log(); - Task.log( - 'We have left the changes that were made intact in case you want to', - ); - Task.log( - 'continue manually, but you can also revert the changes and try again.', - ); - - Task.error(`🔥 Failed to create ${factory.name}!`); - } - } finally { - for (const dir of tempDirs) { - try { - await fs.remove(dir); - } catch (error) { - console.error( - `Failed to remove temporary directory '${dir}', ${error}`, - ); +async function readCliConfig( + cliConfig: + | { + defaults?: boolean; + templates?: TemplateLocation[]; + globals?: Record; } - } + | undefined, +) { + let templates: TemplateLocation[] = []; + const cliTemplates = cliConfig?.templates; + + if (!cliConfig || cliConfig?.defaults) { + templates = defaultTemplates; } + if (cliTemplates?.length) { + cliTemplates.forEach((template: TemplateLocation) => { + templates.push({ + id: template.id, + target: template.target, + }); + }); + } + return { + templates, + globals: { ...cliConfig?.globals }, + }; +} + +export default async () => { + const pkgJson = await fs.readJson(paths.resolveTargetRoot('package.json')); + const cliConfig = pkgJson.backstage?.cli; + + const { templates, globals } = await readCliConfig(cliConfig); + console.log(templates, globals); + + // let defaultVersion = '0.1.0'; + // if (opts.baseVersion) { + // defaultVersion = opts.baseVersion; + // } else { + // const lernaVersion = await fs + // .readJson(paths.resolveTargetRoot('lerna.json')) + // .then(pkg => pkg.version) + // .catch(() => undefined); + // if (lernaVersion) { + // defaultVersion = lernaVersion; + // } + // } + + // const tempDirs = new Array(); + // async function createTemporaryDirectory(name: string): Promise { + // const dir = await fs.mkdtemp(joinPath(os.tmpdir(), name)); + // tempDirs.push(dir); + // return dir; + // } + + // const license = opts.license ?? 'Apache-2.0'; + + // let modified = false; + // try { + // await factory.create(options, { + // isMonoRepo: await isMonoRepo(), + // defaultVersion, + // license, + // scope: opts.scope?.replace(/^@/, ''), + // npmRegistry: opts.npmRegistry, + // private: Boolean(opts.private), + // createTemporaryDirectory, + // markAsModified() { + // modified = true; + // }, + // }); + + // Task.log(); + // Task.log(`🎉 Successfully created ${factory.name}`); + // Task.log(); + // } catch (error) { + // assertError(error); + // Task.error(error.message); + + // if (modified) { + // Task.log('It seems that something went wrong in the creation process 🤔'); + // Task.log(); + // Task.log( + // 'We have left the changes that were made intact in case you want to', + // ); + // Task.log( + // 'continue manually, but you can also revert the changes and try again.', + // ); + + // Task.error(`🔥 Failed to create ${factory.name}!`); + // } + // } finally { + // for (const dir of tempDirs) { + // try { + // await fs.remove(dir); + // } catch (error) { + // console.error( + // `Failed to remove temporary directory '${dir}', ${error}`, + // ); + // } + // } + // } }; From fddab213118d1b669943826a9efcce904b47c131 Mon Sep 17 00:00:00 2001 From: Min Kim Date: Thu, 26 Sep 2024 09:48:02 -0400 Subject: [PATCH 004/145] Interactively select a template from dynamic list Signed-off-by: Min Kim --- packages/cli/src/commands/new/new.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/new/new.ts b/packages/cli/src/commands/new/new.ts index 7dda08626b..6eb027aed2 100644 --- a/packages/cli/src/commands/new/new.ts +++ b/packages/cli/src/commands/new/new.ts @@ -18,6 +18,7 @@ import os from 'os'; import fs from 'fs-extra'; import { join as joinPath } from 'path'; import { OptionValues } from 'commander'; +import inquirer from 'inquirer'; import { FactoryRegistry } from '../../lib/new/FactoryRegistry'; import { isMonoRepo } from '@backstage/cli-node'; import { paths } from '../../lib/paths'; @@ -59,12 +60,32 @@ async function readCliConfig( }; } +async function templateSelector( + templates: TemplateLocation[], +): Promise { + const answer = await inquirer.prompt<{ name: TemplateLocation }>([ + { + type: 'list', + name: 'name', + message: 'What do you want to create?', + choices: templates.map(template => { + return { + name: template.id, + value: template, + }; + }), + }, + ]); + return answer.name; +} + export default async () => { const pkgJson = await fs.readJson(paths.resolveTargetRoot('package.json')); const cliConfig = pkgJson.backstage?.cli; const { templates, globals } = await readCliConfig(cliConfig); - console.log(templates, globals); + const template = await templateSelector(templates); + console.log(template, globals); // let defaultVersion = '0.1.0'; // if (opts.baseVersion) { From ce4dcd91aeacbd9cd770fb3454efe80a777b8d08 Mon Sep 17 00:00:00 2001 From: Min Kim Date: Thu, 26 Sep 2024 11:40:05 -0400 Subject: [PATCH 005/145] Verify that a template's skeleton path exists Signed-off-by: Min Kim --- packages/cli/src/commands/new/new.ts | 38 ++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/new/new.ts b/packages/cli/src/commands/new/new.ts index 6eb027aed2..d683aeaa6b 100644 --- a/packages/cli/src/commands/new/new.ts +++ b/packages/cli/src/commands/new/new.ts @@ -16,9 +16,10 @@ import os from 'os'; import fs from 'fs-extra'; -import { join as joinPath } from 'path'; +import { join as joinPath, dirname } from 'path'; import { OptionValues } from 'commander'; import inquirer from 'inquirer'; +import { parse } from 'yaml'; import { FactoryRegistry } from '../../lib/new/FactoryRegistry'; import { isMonoRepo } from '@backstage/cli-node'; import { paths } from '../../lib/paths'; @@ -26,6 +27,22 @@ import { assertError } from '@backstage/errors'; import { Task } from '../../lib/tasks'; import defaultTemplates from '../../../templates/alpha/all-default-templates'; +type ConfigurablePrompt = + | { + id: string; + prompt: string; + default?: string | boolean; + } + | string; + +interface Template { + description?: string; + template: string; + targetPath: string; + prompts?: ConfigurablePrompt[]; + additionalActions?: string[]; +} + interface TemplateLocation { id: string; target: string; @@ -79,12 +96,29 @@ async function templateSelector( return answer.name; } +async function verifyTemplate({ target }: TemplateLocation): Promise