From 246a4b4cc143eb40ae2202da8932c0603fc3e2bc Mon Sep 17 00:00:00 2001 From: Min Kim Date: Thu, 19 Dec 2024 16:41:43 -0500 Subject: [PATCH] Move all new utils into lib/new Signed-off-by: Min Kim --- .../cli/src/commands/new/additionalActions.ts | 94 --------- packages/cli/src/commands/new/new.ts | 6 +- packages/cli/src/commands/new/types.ts | 41 ---- packages/cli/src/lib/new/additionalActions.ts | 184 ++++++++++++++++++ ...s => executePluginPackageTemplate.test.ts} | 2 +- ...sks.ts => executePluginPackageTemplate.ts} | 0 packages/cli/src/lib/new/types.ts | 27 +++ .../new/util.ts => lib/new/utils.ts} | 35 +--- packages/cli/src/lib/tasks.ts | 71 ------- 9 files changed, 217 insertions(+), 243 deletions(-) delete mode 100644 packages/cli/src/commands/new/additionalActions.ts delete mode 100644 packages/cli/src/commands/new/types.ts create mode 100644 packages/cli/src/lib/new/additionalActions.ts rename packages/cli/src/lib/new/{tasks.test.ts => executePluginPackageTemplate.test.ts} (97%) rename packages/cli/src/lib/new/{tasks.ts => executePluginPackageTemplate.ts} (100%) rename packages/cli/src/{commands/new/util.ts => lib/new/utils.ts} (87%) diff --git a/packages/cli/src/commands/new/additionalActions.ts b/packages/cli/src/commands/new/additionalActions.ts deleted file mode 100644 index a2dce11d25..0000000000 --- a/packages/cli/src/commands/new/additionalActions.ts +++ /dev/null @@ -1,94 +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 fs from 'fs-extra'; -import { paths } from '../../lib/paths'; -import { addPackageDependency, addToBackend, Task } from '../../lib/tasks'; - -export interface AdditionalActionsOptions { - name: string; - version: string; - id: string; - extensionName: string; -} - -export async function installFrontend(options: AdditionalActionsOptions) { - if (await fs.pathExists(paths.resolveTargetRoot('packages/app'))) { - await Task.forItem('app', 'adding dependency', async () => { - await addPackageDependency( - paths.resolveTargetRoot('packages/app/package.json'), - { - dependencies: { - [options.name]: `^${options.version}`, - }, - }, - ); - }); - } -} - -export async function addFrontendLegacy(options: AdditionalActionsOptions) { - await Task.forItem('app', 'adding import', async () => { - const pluginsFilePath = paths.resolveTargetRoot('packages/app/src/App.tsx'); - if (!(await fs.pathExists(pluginsFilePath))) { - return; - } - - const content = await fs.readFile(pluginsFilePath, 'utf8'); - const revLines = content.split('\n').reverse(); - - const lastImportIndex = revLines.findIndex(line => - line.match(/ from ("|').*("|')/), - ); - const lastRouteIndex = revLines.findIndex(line => - line.match(/<\/FlatRoutes/), - ); - - if (lastImportIndex !== -1 && lastRouteIndex !== -1) { - const importLine = `import { ${options.extensionName} } from '${options.name}';`; - if (!content.includes(importLine)) { - revLines.splice(lastImportIndex, 0, importLine); - } - - const componentLine = `} />`; - if (!content.includes(componentLine)) { - const [indentation] = revLines[lastRouteIndex + 1].match(/^\s*/) ?? []; - revLines.splice(lastRouteIndex + 1, 0, indentation + componentLine); - } - - const newContent = revLines.reverse().join('\n'); - await fs.writeFile(pluginsFilePath, newContent, 'utf8'); - } - }); -} - -export async function installBackend(options: AdditionalActionsOptions) { - if (await fs.pathExists(paths.resolveTargetRoot('packages/backend'))) { - await Task.forItem('backend', 'adding dependency', async () => { - await addPackageDependency( - paths.resolveTargetRoot('packages/backend/package.json'), - { - dependencies: { - [options.name]: `^${options.version}`, - }, - }, - ); - }); - } -} - -export async function addBackend(options: AdditionalActionsOptions) { - await addToBackend(options.name); -} diff --git a/packages/cli/src/commands/new/new.ts b/packages/cli/src/commands/new/new.ts index 5ce51e5b68..8acb93327e 100644 --- a/packages/cli/src/commands/new/new.ts +++ b/packages/cli/src/commands/new/new.ts @@ -30,7 +30,7 @@ import { } from '../../lib/codeowners'; import { resolvePackageName } from '../../lib/new/util'; -import { executePluginPackageTemplate } from '../../lib/new/tasks'; +import { executePluginPackageTemplate } from '../../lib/new/executePluginPackageTemplate'; import { readCliConfig, templateSelector, @@ -38,8 +38,8 @@ import { promptOptions, populateOptions, createDirName, - runAdditionalActions, -} from './util'; +} from '../../lib/new/utils'; +import { runAdditionalActions } from '../../lib/new/additionalActions'; export default async () => { const pkgJson = await fs.readJson(paths.resolveTargetRoot('package.json')); diff --git a/packages/cli/src/commands/new/types.ts b/packages/cli/src/commands/new/types.ts deleted file mode 100644 index 2cbca32e16..0000000000 --- a/packages/cli/src/commands/new/types.ts +++ /dev/null @@ -1,41 +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. - */ -export type ConfigurablePrompt = - | { - id: string; - prompt: string; - validate?: string; - default?: string | boolean; - } - | string; - -export interface Template { - id: string; - description?: string; - template: string; - templatePath: string; - targetPath: string; - plugin?: boolean; - backendModulePrefix?: boolean; - suffix?: string; - prompts?: ConfigurablePrompt[]; - additionalActions?: string[]; -} - -export interface TemplateLocation { - id: string; - target: string; -} diff --git a/packages/cli/src/lib/new/additionalActions.ts b/packages/cli/src/lib/new/additionalActions.ts new file mode 100644 index 0000000000..52aaf0369b --- /dev/null +++ b/packages/cli/src/lib/new/additionalActions.ts @@ -0,0 +1,184 @@ +/* + * 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 fs from 'fs-extra'; +import { paths } from '../paths'; +import { Task } from '../tasks'; + +interface AdditionalActionsOptions { + name: string; + version: string; + id: string; + extensionName: string; +} + +export async function runAdditionalActions( + additionalActions: string[], + options: AdditionalActionsOptions, +) { + for (const action of additionalActions) { + switch (action) { + case 'install-frontend': + await installFrontend(options); + break; + case 'add-frontend-legacy': + await addFrontendLegacy(options); + break; + case 'install-backend': + await installBackend(options); + break; + case 'add-backend': + await addBackend(options); + break; + default: + throw new Error(`${action} is not a valid additional action`); + } + } +} + +async function addPackageDependency( + path: string, + options: { + dependencies?: Record; + devDependencies?: Record; + peerDependencies?: Record; + }, +) { + try { + const pkgJson = await fs.readJson(path); + + const normalize = (obj: Record) => { + if (Object.keys(obj).length === 0) { + return undefined; + } + return Object.fromEntries( + Object.keys(obj) + .sort() + .map(key => [key, obj[key]]), + ); + }; + + pkgJson.dependencies = normalize({ + ...pkgJson.dependencies, + ...options.dependencies, + }); + pkgJson.devDependencies = normalize({ + ...pkgJson.devDependencies, + ...options.devDependencies, + }); + pkgJson.peerDependencies = normalize({ + ...pkgJson.peerDependencies, + ...options.peerDependencies, + }); + + await fs.writeJson(path, pkgJson, { spaces: 2 }); + } catch (error) { + throw new Error(`Failed to add package dependencies, ${error}`); + } +} + +async function installFrontend(options: AdditionalActionsOptions) { + if (await fs.pathExists(paths.resolveTargetRoot('packages/app'))) { + await Task.forItem('app', 'adding dependency', async () => { + await addPackageDependency( + paths.resolveTargetRoot('packages/app/package.json'), + { + dependencies: { + [options.name]: `^${options.version}`, + }, + }, + ); + }); + } +} + +async function addFrontendLegacy(options: AdditionalActionsOptions) { + await Task.forItem('app', 'adding import', async () => { + const pluginsFilePath = paths.resolveTargetRoot('packages/app/src/App.tsx'); + if (!(await fs.pathExists(pluginsFilePath))) { + return; + } + + const content = await fs.readFile(pluginsFilePath, 'utf8'); + const revLines = content.split('\n').reverse(); + + const lastImportIndex = revLines.findIndex(line => + line.match(/ from ("|').*("|')/), + ); + const lastRouteIndex = revLines.findIndex(line => + line.match(/<\/FlatRoutes/), + ); + + if (lastImportIndex !== -1 && lastRouteIndex !== -1) { + const importLine = `import { ${options.extensionName} } from '${options.name}';`; + if (!content.includes(importLine)) { + revLines.splice(lastImportIndex, 0, importLine); + } + + const componentLine = `} />`; + if (!content.includes(componentLine)) { + const [indentation] = revLines[lastRouteIndex + 1].match(/^\s*/) ?? []; + revLines.splice(lastRouteIndex + 1, 0, indentation + componentLine); + } + + const newContent = revLines.reverse().join('\n'); + await fs.writeFile(pluginsFilePath, newContent, 'utf8'); + } + }); +} + +async function installBackend(options: AdditionalActionsOptions) { + if (await fs.pathExists(paths.resolveTargetRoot('packages/backend'))) { + await Task.forItem('backend', 'adding dependency', async () => { + await addPackageDependency( + paths.resolveTargetRoot('packages/backend/package.json'), + { + dependencies: { + [options.name]: `^${options.version}`, + }, + }, + ); + }); + } +} + +async function addBackend({ name }: AdditionalActionsOptions) { + if (await fs.pathExists(paths.resolveTargetRoot('packages/backend'))) { + await Task.forItem('backend', `adding ${name}`, 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('${name}'));`; + + const backendStartIndex = lines.findIndex(line => + line.match(/backend.start/), + ); + + 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'); + } + }); + } +} diff --git a/packages/cli/src/lib/new/tasks.test.ts b/packages/cli/src/lib/new/executePluginPackageTemplate.test.ts similarity index 97% rename from packages/cli/src/lib/new/tasks.test.ts rename to packages/cli/src/lib/new/executePluginPackageTemplate.test.ts index 3e6423c531..f43601450a 100644 --- a/packages/cli/src/lib/new/tasks.test.ts +++ b/packages/cli/src/lib/new/executePluginPackageTemplate.test.ts @@ -22,7 +22,7 @@ import { mockPaths, } from './testUtils'; import { CreateContext } from './types'; -import { executePluginPackageTemplate } from './tasks'; +import { executePluginPackageTemplate } from './executePluginPackageTemplate'; import { createMockDirectory } from '@backstage/backend-test-utils'; const mockDir = createMockDirectory(); diff --git a/packages/cli/src/lib/new/tasks.ts b/packages/cli/src/lib/new/executePluginPackageTemplate.ts similarity index 100% rename from packages/cli/src/lib/new/tasks.ts rename to packages/cli/src/lib/new/executePluginPackageTemplate.ts diff --git a/packages/cli/src/lib/new/types.ts b/packages/cli/src/lib/new/types.ts index fc9d520a74..bd11702c4f 100644 --- a/packages/cli/src/lib/new/types.ts +++ b/packages/cli/src/lib/new/types.ts @@ -40,3 +40,30 @@ export interface CreateContext { export type Prompt = DistinctQuestion & { name: string; }; + +export type ConfigurablePrompt = + | { + id: string; + prompt: string; + validate?: string; + default?: string | boolean; + } + | string; + +export interface Template { + id: string; + description?: string; + template: string; + templatePath: string; + targetPath: string; + plugin?: boolean; + backendModulePrefix?: boolean; + suffix?: string; + prompts?: ConfigurablePrompt[]; + additionalActions?: string[]; +} + +export interface TemplateLocation { + id: string; + target: string; +} diff --git a/packages/cli/src/commands/new/util.ts b/packages/cli/src/lib/new/utils.ts similarity index 87% rename from packages/cli/src/commands/new/util.ts rename to packages/cli/src/lib/new/utils.ts index e6a5ec3fda..cc1cdab90a 100644 --- a/packages/cli/src/commands/new/util.ts +++ b/packages/cli/src/lib/new/utils.ts @@ -18,22 +18,15 @@ import { dirname } from 'path'; import { parse } from 'yaml'; import fs from 'fs-extra'; -import { paths } from '../../lib/paths'; +import { paths } from '../paths'; import { pluginIdPrompt, moduleIdIdPrompt, npmRegistryPrompt, ownerPrompt, -} from '../../lib/new/prompts'; +} from './prompts'; import defaultTemplates from '../../../templates/all-default-templates'; -import { - installFrontend, - addFrontendLegacy, - installBackend, - addBackend, - AdditionalActionsOptions, -} from './additionalActions'; import { Template, TemplateLocation, ConfigurablePrompt } from './types'; @@ -227,27 +220,3 @@ export function createDirName(template: Template, options: Options) { } return options.id; } - -export async function runAdditionalActions( - additionalActions: string[], - options: AdditionalActionsOptions, -) { - for (const action of additionalActions) { - switch (action) { - case 'install-frontend': - await installFrontend(options); - break; - case 'add-frontend-legacy': - await addFrontendLegacy(options); - break; - case 'install-backend': - await installBackend(options); - break; - case 'add-backend': - await addBackend(options); - break; - default: - throw new Error(`${action} is not a valid additional action`); - } - } -} diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index 4fd7238a81..47a2a98af0 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -23,7 +23,6 @@ import { basename, dirname } from 'path'; import recursive from 'recursive-readdir'; import { exec as execCb } from 'child_process'; import { assertError } from '@backstage/errors'; -import { paths } from './paths'; const exec = promisify(execCb); @@ -156,73 +155,3 @@ export async function templatingTask( } } } - -export async function addPackageDependency( - path: string, - options: { - dependencies?: Record; - devDependencies?: Record; - peerDependencies?: Record; - }, -) { - try { - const pkgJson = await fs.readJson(path); - - const normalize = (obj: Record) => { - if (Object.keys(obj).length === 0) { - return undefined; - } - return Object.fromEntries( - Object.keys(obj) - .sort() - .map(key => [key, obj[key]]), - ); - }; - - pkgJson.dependencies = normalize({ - ...pkgJson.dependencies, - ...options.dependencies, - }); - pkgJson.devDependencies = normalize({ - ...pkgJson.devDependencies, - ...options.devDependencies, - }); - pkgJson.peerDependencies = normalize({ - ...pkgJson.peerDependencies, - ...options.peerDependencies, - }); - - await fs.writeJson(path, pkgJson, { spaces: 2 }); - } catch (error) { - throw new Error(`Failed to add package dependencies, ${error}`); - } -} - -export async function addToBackend(name: string) { - if (await fs.pathExists(paths.resolveTargetRoot('packages/backend'))) { - await Task.forItem('backend', `adding ${name}`, 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('${name}'));`; - - const backendStartIndex = lines.findIndex(line => - line.match(/backend.start/), - ); - - 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'); - } - }); - } -}