Move all new utils into lib/new

Signed-off-by: Min Kim <minkimcello@gmail.com>
This commit is contained in:
Min Kim
2024-12-19 16:41:43 -05:00
parent 9b3fc3bd73
commit 246a4b4cc1
9 changed files with 217 additions and 243 deletions
@@ -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 = `<Route path="/${options.id}" element={<${options.extensionName} />} />`;
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);
}
+3 -3
View File
@@ -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'));
-41
View File
@@ -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;
}
@@ -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<string, string>;
devDependencies?: Record<string, string>;
peerDependencies?: Record<string, string>;
},
) {
try {
const pkgJson = await fs.readJson(path);
const normalize = (obj: Record<string, string>) => {
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 = `<Route path="/${options.id}" element={<${options.extensionName} />} />`;
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');
}
});
}
}
@@ -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();
+27
View File
@@ -40,3 +40,30 @@ export interface CreateContext {
export type Prompt<TOptions extends Answers> = DistinctQuestion<TOptions> & {
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;
}
@@ -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`);
}
}
}
-71
View File
@@ -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<string, string>;
devDependencies?: Record<string, string>;
peerDependencies?: Record<string, string>;
},
) {
try {
const pkgJson = await fs.readJson(path);
const normalize = (obj: Record<string, string>) => {
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');
}
});
}
}