From e607456dbcb77d779ee28af8c1980ca4f9e713be Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 17 Oct 2021 16:59:59 +0200 Subject: [PATCH] cli: create command factory foundations Signed-off-by: Patrik Oldsberg --- .../src/commands/create/FactoryRegistry.ts | 76 +++++++++++++++++++ packages/cli/src/commands/create/create.ts | 13 +++- .../create/factories/frontendPlugin.ts | 46 +++++++++++ .../src/commands/create/factories/index.ts | 17 +++++ packages/cli/src/commands/create/types.ts | 34 +++++++++ 5 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 packages/cli/src/commands/create/FactoryRegistry.ts create mode 100644 packages/cli/src/commands/create/factories/frontendPlugin.ts create mode 100644 packages/cli/src/commands/create/factories/index.ts create mode 100644 packages/cli/src/commands/create/types.ts diff --git a/packages/cli/src/commands/create/FactoryRegistry.ts b/packages/cli/src/commands/create/FactoryRegistry.ts new file mode 100644 index 0000000000..0f48acc337 --- /dev/null +++ b/packages/cli/src/commands/create/FactoryRegistry.ts @@ -0,0 +1,76 @@ +/* + * Copyright 2021 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 inquirer from 'inquirer'; +import { AnyFactory } from './types'; +import * as factories from './factories'; +import partition from 'lodash/partition'; + +export class FactoryRegistry { + private static factoryMap = new Map( + Object.values(factories).map(factory => [factory.name, factory]), + ); + + static async interactiveSelect(preselected?: string): Promise { + let selected = preselected; + + if (!selected) { + const answers = await inquirer.prompt<{ name: string }>([ + { + type: 'list', + name: 'name', + message: 'What do you want to create?', + choices: Array.from(this.factoryMap.values()).map(factory => ({ + name: `${factory.name} - ${factory.description}`, + value: factory.name, + })), + }, + ]); + selected = answers.name; + } + + const factory = this.factoryMap.get(selected); + if (!factory) { + throw new Error(`Unknown selection '${selected}'`); + } + return factory; + } + + static async populateOptions( + factory: AnyFactory, + provided: Record, + ): Promise> { + const [hasAnswers, needsAnswers] = partition( + factory.options, + option => option.name in provided, + ); + + for (const option of hasAnswers) { + const value = provided[option.name]; + + if (option.validate) { + const result = option.validate(value); + if (result !== true) { + throw new Error(`Invalid option '${option.name}'. ${result}`); + } + } + } + + const answers = await inquirer.prompt(needsAnswers); + + return { ...provided, ...answers }; + } +} diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts index ed4bcc9c20..2397e7ec58 100644 --- a/packages/cli/src/commands/create/create.ts +++ b/packages/cli/src/commands/create/create.ts @@ -15,6 +15,7 @@ */ import { Command } from 'commander'; +import { FactoryRegistry } from './FactoryRegistry'; function parseOptions(optionStrings: string[]): Record { const options: Record = {}; @@ -34,9 +35,13 @@ function parseOptions(optionStrings: string[]): Record { } export default async (cmd: Command) => { - const selected = cmd.opts().select; - console.log('DEBUG: selected =', selected); + const factory = await FactoryRegistry.interactiveSelect(cmd.opts().select); - const options = parseOptions(cmd.opts().option); - console.log('DEBUG: options =', options); + const providedOptions = parseOptions(cmd.opts().option); + const options = await FactoryRegistry.populateOptions( + factory, + providedOptions, + ); + + await factory.create(options); }; diff --git a/packages/cli/src/commands/create/factories/frontendPlugin.ts b/packages/cli/src/commands/create/factories/frontendPlugin.ts new file mode 100644 index 0000000000..195953e780 --- /dev/null +++ b/packages/cli/src/commands/create/factories/frontendPlugin.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2021 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 { createFactory } from '../types'; + +type Options = { + id: string; +}; + +export const frontendPlugin = createFactory({ + name: 'plugin', + description: 'A new frontend plugin', + options: [ + { + type: 'input', + name: 'id', + message: 'Enter an ID for the plugin', + validate: (value: string) => { + if (!value) { + return 'Please enter an ID for the plugin'; + } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) { + return 'Plugin IDs must be lowercase and contain only letters, digits, and dashes.'; + } + return true; + }, + }, + ], + async create(options: Options) { + console.log( + `Creating ${this.name} with options ${JSON.stringify(options)}`, + ); + }, +}); diff --git a/packages/cli/src/commands/create/factories/index.ts b/packages/cli/src/commands/create/factories/index.ts new file mode 100644 index 0000000000..4375ba2a45 --- /dev/null +++ b/packages/cli/src/commands/create/factories/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 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 { frontendPlugin } from './frontendPlugin'; diff --git a/packages/cli/src/commands/create/types.ts b/packages/cli/src/commands/create/types.ts new file mode 100644 index 0000000000..e5252df6bc --- /dev/null +++ b/packages/cli/src/commands/create/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 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 { DistinctQuestion } from 'inquirer'; + +export type AnyOptions = Record; + +export interface Factory { + name: string; + description: string; + options: ReadonlyArray & { name: string }>; + create(options: Options): Promise; +} + +export type AnyFactory = Factory; + +export function createFactory( + config: Factory, +): AnyFactory { + return config as AnyFactory; +}