cli/new: move template execution impl to subdir

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-02-07 14:24:57 +01:00
parent 35f1e32e3b
commit 077aa5d2e1
11 changed files with 154 additions and 118 deletions
@@ -21,7 +21,7 @@ import * as run from '../../lib/run';
import migrate from './migrate';
import { withLogCollector } from '@backstage/test-utils';
import fs from 'fs-extra';
import { expectLogsToMatch } from '../../lib/new/testUtils';
import { expectLogsToMatch } from '../../lib/new/execution/testUtils';
// Remove log coloring to simplify log matching
jest.mock('chalk', () => ({
@@ -19,7 +19,7 @@ import { paths } from '../../paths';
import { NewConfig } from '../config/types';
import { promptOptions } from './prompts';
import { Template } from '../types';
import { Options } from '../utils';
import { Options } from '../execution/utils';
type CollectTemplateParamsOptions = {
config: NewConfig;
+10 -105
View File
@@ -14,22 +14,10 @@
* limitations under the License.
*/
import camelCase from 'lodash/camelCase';
import upperFirst from 'lodash/upperFirst';
import { isMonoRepo } from '@backstage/cli-node';
import { assertError } from '@backstage/errors';
import { paths } from '../paths';
import { Task } from '../tasks';
import { addCodeownersEntry } from '../codeowners';
import { createDirName, resolvePackageName } from './utils';
import { runAdditionalActions } from './additionalActions';
import { executePluginPackageTemplate } from './executeTemplate';
import { TemporaryDirectoryManager } from './TemporaryDirectoryManager';
import { loadNewConfig } from './config/loadNewConfig';
import { NewTemplateLoader } from './loader/NewTemplateLoader';
import { collectTemplateParams } from './collection/collectTemplateParams';
import { loadNewConfig } from './config/loadNewConfig';
import { executeNewTemplate } from './execution/executeTemplate';
import { NewTemplateLoader } from './loader/NewTemplateLoader';
export type CreateNewPackageOptions = {
preselectedTemplateId?: string;
@@ -44,107 +32,24 @@ export type CreateNewPackageOptions = {
};
export async function createNewPackage(options: CreateNewPackageOptions) {
const newConfig = await loadNewConfig();
const config = await loadNewConfig();
const selectedTemplate = await NewTemplateLoader.selectTemplateInteractively(
newConfig,
config,
options.preselectedTemplateId,
);
const template = await NewTemplateLoader.loadTemplate(selectedTemplate);
const params = await collectTemplateParams({
config: newConfig,
config,
template,
globals: options.globals,
prefilledParams: options.prefilledParams,
});
const tmpDirManager = TemporaryDirectoryManager.create();
const dirName = createDirName(template, params);
const targetDir = paths.resolveTargetRoot(params.targetPath, dirName);
const packageName = resolvePackageName({
baseName: dirName,
scope: params.scope,
plugin: template.plugin ?? true,
await executeNewTemplate({
config,
template,
params,
});
const moduleVar =
params.moduleId ??
`${camelCase(params.id)}Module${camelCase(
params.moduleId,
)[0].toUpperCase()}${camelCase(params.moduleId).slice(1)}`; // used in default-backend-module template
const extensionName = `${upperFirst(camelCase(params.id))}Page`; // used in default-plugin template
const pluginVar = `${camelCase(params.id)}Plugin`; // used in default-backend-plugin and default-plugin template
let modified = false;
try {
await executePluginPackageTemplate(
{
isMonoRepo: await isMonoRepo(),
createTemporaryDirectory: tmpDirManager.createDir,
markAsModified() {
modified = true;
},
},
{
targetDir,
templateDir: template.templatePath,
values: {
name: packageName,
privatePackage: params.private,
pluginVersion: params.baseVersion,
moduleVar,
extensionName,
pluginVar,
...params,
},
},
);
if (template.additionalActions?.length) {
await runAdditionalActions(template.additionalActions, {
name: packageName,
version: params.baseVersion,
id: params.id, // for frontend legacy
extensionName, // for frontend legacy
});
}
if (params.owner) {
await addCodeownersEntry(targetDir, params.owner);
}
await Task.forCommand('yarn install', {
cwd: targetDir,
optional: true,
});
await Task.forCommand('yarn lint --fix', {
cwd: targetDir,
optional: true,
});
Task.log();
Task.log(`🎉 Successfully created ${template.id}`);
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 ${template.id}!`);
}
} finally {
tmpDirManager.cleanup();
}
}
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import fs from 'fs-extra';
import { paths } from '../paths';
import { Task } from '../tasks';
import { paths } from '../../paths';
import { Task } from '../../tasks';
interface AdditionalActionsOptions {
name: string;
@@ -19,7 +19,7 @@ import { mockPaths } from './testUtils';
import {
executePluginPackageTemplate,
templatingTask,
} from './executeTemplate';
} from './executePluginPackageTemplate';
import { createMockDirectory } from '@backstage/backend-test-utils';
describe('executePluginPackageTemplate', () => {
@@ -25,11 +25,11 @@ import {
relative as relativePath,
} from 'path';
import { paths } from '../paths';
import { Task } from '../tasks';
import { Lockfile } from '../versioning';
import { createPackageVersionProvider } from '../version';
import { CreateContext } from './types';
import { paths } from '../../paths';
import { Task } from '../../tasks';
import { Lockfile } from '../../versioning';
import { createPackageVersionProvider } from '../../version';
import { CreateContext } from '../types';
export async function executePluginPackageTemplate(
ctx: CreateContext,
@@ -0,0 +1,131 @@
/*
* 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 camelCase from 'lodash/camelCase';
import upperFirst from 'lodash/upperFirst';
import { isMonoRepo } from '@backstage/cli-node';
import { assertError } from '@backstage/errors';
import { paths } from '../../paths';
import { Task } from '../../tasks';
import { addCodeownersEntry } from '../../codeowners';
import { createDirName, resolvePackageName } from './utils';
import { runAdditionalActions } from './additionalActions';
import { executePluginPackageTemplate } from './executePluginPackageTemplate';
import { TemporaryDirectoryManager } from './TemporaryDirectoryManager';
import { NewConfig } from '../config/types';
import { Template } from '../types';
import { Options } from './utils';
type ExecuteNewTemplateOptions = {
config: NewConfig;
template: Template;
params: Options;
};
export async function executeNewTemplate(options: ExecuteNewTemplateOptions) {
const { template, params } = options;
const tmpDirManager = TemporaryDirectoryManager.create();
const dirName = createDirName(template, params);
const targetDir = paths.resolveTargetRoot(params.targetPath, dirName);
const packageName = resolvePackageName({
baseName: dirName,
scope: params.scope,
plugin: template.plugin ?? true,
});
const moduleVar =
params.moduleId ??
`${camelCase(params.id)}Module${camelCase(
params.moduleId,
)[0].toUpperCase()}${camelCase(params.moduleId).slice(1)}`; // used in default-backend-module template
const extensionName = `${upperFirst(camelCase(params.id))}Page`; // used in default-plugin template
const pluginVar = `${camelCase(params.id)}Plugin`; // used in default-backend-plugin and default-plugin template
let modified = false;
try {
await executePluginPackageTemplate(
{
isMonoRepo: await isMonoRepo(),
createTemporaryDirectory: tmpDirManager.createDir,
markAsModified() {
modified = true;
},
},
{
targetDir,
templateDir: template.templatePath,
values: {
name: packageName,
privatePackage: params.private,
pluginVersion: params.baseVersion,
moduleVar,
extensionName,
pluginVar,
...params,
},
},
);
if (template.additionalActions?.length) {
await runAdditionalActions(template.additionalActions, {
name: packageName,
version: params.baseVersion,
id: params.id, // for frontend legacy
extensionName, // for frontend legacy
});
}
if (params.owner) {
await addCodeownersEntry(targetDir, params.owner);
}
await Task.forCommand('yarn install', {
cwd: targetDir,
optional: true,
});
await Task.forCommand('yarn lint --fix', {
cwd: targetDir,
optional: true,
});
Task.log();
Task.log(`🎉 Successfully created ${template.id}`);
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 ${template.id}!`);
}
} finally {
tmpDirManager.cleanup();
}
}
@@ -18,7 +18,7 @@
import { WriteStream } from 'tty';
import { resolve as resolvePath } from 'path';
import { paths } from '../paths';
import { paths } from '../../paths';
export function mockPaths(options: {
ownDir?: string;
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { resolvePackageName, createDirName, Options } from './utils';
import { Template } from './types';
import { Template } from '../types';
describe('resolvePackageName', () => {
it('should generate correct name without scope', () => {
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Template } from './types';
import { Template } from '../types';
export interface Options extends Record<string, string | boolean> {
id: string;