diff --git a/packages/cli/src/commands/new/additionalActions.ts b/packages/cli/src/commands/new/additionalActions.ts new file mode 100644 index 0000000000..84b47cfb02 --- /dev/null +++ b/packages/cli/src/commands/new/additionalActions.ts @@ -0,0 +1,94 @@ +/* + * 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; + extension: 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.extension} } 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 57a64631ab..66c29fea8e 100644 --- a/packages/cli/src/commands/new/new.ts +++ b/packages/cli/src/commands/new/new.ts @@ -38,6 +38,7 @@ import { promptOptions, populateOptions, createDirName, + runAdditionalActions, } from './util'; export default async () => { @@ -66,43 +67,55 @@ export default async () => { const dirName = createDirName(template, options); const targetDir = paths.resolveTargetRoot(options.targetPath, dirName); + const packageName = resolvePackageName({ + baseName: dirName, + scope: options.scope, + plugin: template.plugin ?? true, + }); + + const moduleVar = `${camelCase(options.id)}Module${camelCase( + options.moduleId, + )[0].toUpperCase()}${camelCase(options.moduleId).slice(1)}`; // used in default-backend-module template + const extension = `${upperFirst(camelCase(options.id))}Page`; // used in default-plugin template + const pluginVar = `${camelCase(options.id)}Plugin`; // used in default-backend-plugin and default-plugin template + let modified = false; try { - await executePluginPackageTemplate( - { - private: options.private, - defaultVersion: options.baseVersion, - license: options.license, - isMonoRepo: await isMonoRepo(), - createTemporaryDirectory, - markAsModified() { - modified = true; + if (options.install) { + await executePluginPackageTemplate( + { + private: options.private, + defaultVersion: options.baseVersion, + license: options.license, + isMonoRepo: await isMonoRepo(), + createTemporaryDirectory, + markAsModified() { + modified = true; + }, }, - }, - { - targetDir, - templateDir: template.templatePath, - values: { - name: resolvePackageName({ - baseName: dirName, - scope: options.scope, - plugin: template.plugin ?? true, - }), - pluginVersion: options.baseVersion, - moduleVar: `${camelCase(options.id)}Module${camelCase( - options.moduleId, - )[0].toUpperCase()}${camelCase(options.moduleId).slice(1)}`, // used in default-backend-module template - extension: `${upperFirst(camelCase(options.id))}Page`, // used in default-plugin template - pluginVar: `${camelCase(options.id)}Plugin`, // used in default-backend-plugin and default-plugin template - ...options, + { + targetDir, + templateDir: template.templatePath, + values: { + name: packageName, + pluginVersion: options.baseVersion, + moduleVar, + extension, + pluginVar, + ...options, + }, }, - }, - ); + ); + } - // create additional actions - // install to app - // install to backend - // add to backend/index.ts + if (template.additionalActions?.length) { + await runAdditionalActions(template.additionalActions, { + name: packageName, + version: options.baseVersion, + id: options.id, // for frontend legacy + extension: extension, // for frontend legacy + }); + } if (options.install) { // 🚨 temporary diff --git a/packages/cli/src/commands/new/util.ts b/packages/cli/src/commands/new/util.ts index d2e5849a37..74a69e5082 100644 --- a/packages/cli/src/commands/new/util.ts +++ b/packages/cli/src/commands/new/util.ts @@ -25,7 +25,15 @@ import { npmRegistryPrompt, ownerPrompt, } from '../../lib/new/factories/common/prompts'; + import defaultTemplates from '../../../templates'; +import { + installFrontend, + addFrontendLegacy, + installBackend, + addBackend, + AdditionalActionsOptions, +} from './additionalActions'; import { Template, TemplateLocation, ConfigurablePrompt } from './types'; @@ -219,3 +227,27 @@ 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 ffff2892fb..4fd7238a81 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -198,14 +198,9 @@ export async function addPackageDependency( } } -export async function addToBackend( - name: string, - options: { - type: 'plugin' | 'module'; - }, -) { +export async function addToBackend(name: string) { if (await fs.pathExists(paths.resolveTargetRoot('packages/backend'))) { - await Task.forItem('backend', `adding ${options.type}`, async () => { + await Task.forItem('backend', `adding ${name}`, async () => { const backendFilePath = paths.resolveTargetRoot( 'packages/backend/src/index.ts', );