Move all new utils into lib/new
Signed-off-by: Min Kim <minkimcello@gmail.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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'));
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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();
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user