diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index 4fa3d1ac6e..36d910fe5b 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -20,17 +20,8 @@ import { buildBundle } from '../../lib/bundler'; import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel'; import { loadCliConfig } from '../../lib/config'; import { paths } from '../../lib/paths'; -import chalk from 'chalk'; export default async (cmd: Command) => { - if (cmd.lax) { - console.warn( - chalk.yellow( - `[DEPRECATED] - The --lax option is deprecated and will be removed in the future. Please open an issue towards https://github.com/backstage/backstage that describes your use-case if you need the flag to stay around.`, - ), - ); - } - const { name } = await fs.readJson(paths.resolveTarget('package.json')); await buildBundle({ entry: 'src/index', @@ -39,7 +30,6 @@ export default async (cmd: Command) => { ...(await loadCliConfig({ args: cmd.config, fromPackage: name, - mockEnv: cmd.lax, })), }); }; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 12fce66bd4..362771f8f2 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -30,10 +30,6 @@ export function registerCommands(program: CommanderStatic) { .command('app:build') .description('Build an app for a production release') .option('--stats', 'Write bundle stats to output directory') - .option( - '--lax', - '[DEPRECATED] - Do not require environment variables to be set', - ) .option(...configOption) .action(lazy(() => import('./app/build').then(m => m.default))); @@ -108,13 +104,6 @@ export function registerCommands(program: CommanderStatic) { lazy(() => import('./create-plugin/createPlugin').then(m => m.default)), ); - program - .command('remove-plugin') - .description('[DEPRECATED] - Removes plugin in the current repository') - .action( - lazy(() => import('./remove-plugin/removePlugin').then(m => m.default)), - ); - program .command('plugin:build') .description('Build a plugin') diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts deleted file mode 100644 index 8859b5d4cd..0000000000 --- a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright 2020 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 fse from 'fs-extra'; -import path from 'path'; -import mockFs from 'mock-fs'; -import { paths } from '../../lib/paths'; -import { addExportStatement, capitalize } from '../create-plugin/createPlugin'; -import { addCodeownersEntry } from '../../lib/codeowners'; -import { - removeReferencesFromAppPackage, - removeReferencesFromPluginsFile, - removePluginDirectory, - removeSymLink, - removePluginFromCodeOwners, -} from './removePlugin'; -import { - codeownersFileContent, - packageFileContent, - pluginsFileContent, -} from './file-mocks'; - -const BACKSTAGE = `@backstage`; -const testPluginName = 'yarn-test-package'; -const testPluginPackage = `${BACKSTAGE}/plugin-${testPluginName}`; -const tempDir = '/remove-plugin-test'; - -const removeEmptyLines = (file: string): string => - file.split(/\r?\n/).filter(Boolean).join('\n'); - -const createTestPackageFile = async (testFilePath: string) => { - const testFileContent = { - ...packageFileContent, - dependencies: { - ...packageFileContent.dependencies, - [testPluginPackage]: '0.1.0', - }, - }; - - mockFs({ - packages: { - app: { - 'package.json': `${JSON.stringify(packageFileContent, null, 2)}\n`, - }, - }, - [tempDir]: { - [testFilePath]: `${JSON.stringify(testFileContent, null, 2)}\n`, - }, - }); - return; -}; - -const createTestPluginFile = async ( - testFileName: string, - pluginsFileName: string, -) => { - mockFs({ - [tempDir]: { - [testFileName]: `${pluginsFileContent}\n`, - [pluginsFileName]: `${pluginsFileContent}\n`, - }, - packages: { - app: { - src: { - 'plugin.ts': `${pluginsFileContent}\n`, - }, - }, - }, - }); - - const pluginNameCapitalized = testPluginName - .split('-') - .map(name => capitalize(name)) - .join(''); - const exportStatement = `export { plugin as ${pluginNameCapitalized}} from ${testPluginPackage}`; - await addExportStatement(path.join(tempDir, testFileName), exportStatement); -}; - -const mkTestPluginDir = (testDirPath: string) => { - const pluginFiles: { [index: string]: string } = {}; - for (let i = 0; i < 50; i++) { - pluginFiles[`testFile${i}.ts`] = ''; - } - - mockFs({ - [testDirPath]: pluginFiles, - }); -}; - -describe('removePlugin', () => { - beforeAll(() => { - // Create temporary directory for all tests - mockFs({ - [tempDir]: { - 'package.json': packageFileContent, - src: { - 'plugin.ts': pluginsFileContent, - }, - }, - }); - }); - - afterAll(() => { - mockFs.restore(); - }); - - describe('Remove Plugin Dependencies', () => { - it('removes plugin references from /packages/app/package.json', async () => { - // Set up test - const testFilePath = 'test.json'; - createTestPackageFile(testFilePath); - await removeReferencesFromAppPackage( - path.join(tempDir, testFilePath), - testPluginName, - ); - const testFileContent = removeEmptyLines( - fse.readFileSync(path.join(tempDir, testFilePath), 'utf8'), - ); - - const mockedPackageFileContent = removeEmptyLines( - fse.readFileSync(path.join('packages', 'app', 'package.json'), 'utf8'), - ); - expect(testFileContent).toBe(mockedPackageFileContent); - }); - it('removes plugin exports from /packages/app/src/package.json', async () => { - const testFileName = 'test.ts'; - const pluginsFileName = 'plugin.ts'; - createTestPluginFile(testFileName, pluginsFileName); - await removeReferencesFromPluginsFile( - path.join(tempDir, testFileName), - testPluginName, - ); - const testFileContent = removeEmptyLines( - fse.readFileSync(path.join(tempDir, testFileName), 'utf8'), - ); - const mockedPluginsFileContent = removeEmptyLines( - fse.readFileSync( - path.join('packages', 'app', 'src', pluginsFileName), - 'utf8', - ), - ); - expect(testFileContent).toBe(mockedPluginsFileContent); - }); - - it('removes codeOwners references', async () => { - const testFileName = 'test'; - const testFilePath = path.join(tempDir, testFileName); - - const mockedCodeownersPath = path.join('.github', 'CODEOWNERS'); - - mockFs({ - [tempDir]: { - [testFileName]: '', - }, - '.github': { - CODEOWNERS: codeownersFileContent, - }, - }); - fse.copySync(mockedCodeownersPath, testFilePath); - const testFileContent = removeEmptyLines( - fse.readFileSync(testFilePath, 'utf8'), - ); - const codeOwnersFileContent = removeEmptyLines( - fse.readFileSync(mockedCodeownersPath, 'utf8'), - ); - await addCodeownersEntry( - path.join('plugins', testPluginName), - '@thisIsAtestTeam test@gmail.com', - testFilePath, - ); - await removePluginFromCodeOwners(testFilePath, testPluginName); - expect(testFileContent).toBe(codeOwnersFileContent); - }); - }); - - describe('Remove files', () => { - const testDirPath = path.join( - paths.resolveTargetRoot(), - 'plugins', - testPluginName, - ); - - describe('Removes Plugin Directory', () => { - it('removes plugin directory from /plugins', async () => { - mkTestPluginDir(testDirPath); - expect(fse.existsSync(testDirPath)).toBeTruthy(); - await removePluginDirectory(testDirPath); - expect(fse.existsSync(testDirPath)).toBeFalsy(); - }); - }); - - describe('Removes System Link', () => { - it('removes system link from @backstage', async () => { - const symLink = `plugin-${testPluginName}`; - const testSymLinkPath = path.join( - '/', - 'node_modules', - '@backstage', - symLink, - ); - const mockedTestDirPath = path.join('/', 'plugins', testPluginName); - - mockFs({ - '/plugins': { - [testPluginName]: {}, - }, - '/node_modules': { - '@backstage': { - [symLink]: mockFs.symlink({ - path: mockedTestDirPath, - }), - }, - }, - }); - - expect(fse.existsSync(testSymLinkPath)).toBeTruthy(); - await removeSymLink(testSymLinkPath); - expect(fse.existsSync(testSymLinkPath)).toBeFalsy(); - }); - }); - }); -}); diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.ts b/packages/cli/src/commands/remove-plugin/removePlugin.ts deleted file mode 100644 index a82bfda36d..0000000000 --- a/packages/cli/src/commands/remove-plugin/removePlugin.ts +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright 2020 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 fse from 'fs-extra'; -import path from 'path'; -import chalk from 'chalk'; -import inquirer, { Answers, Question } from 'inquirer'; -import { getCodeownersFilePath } from '../../lib/codeowners'; -import { paths } from '../../lib/paths'; -import { Task } from '../../lib/tasks'; -import { assertError } from '@backstage/errors'; - -const BACKSTAGE = '@backstage'; - -export const checkExists = async (rootDir: string, pluginName: string) => { - await Task.forItem('checking', pluginName, async () => { - try { - const destination = path.join(rootDir, 'plugins', pluginName); - const pathExist = await fse.pathExists(destination); - - if (!pathExist) { - throw new Error( - chalk.red(` Plugin ${chalk.cyan(pluginName)} does not exist!`), - ); - } - } catch (e) { - assertError(e); - throw new Error( - chalk.red( - ` There was an error removing plugin ${chalk.cyan(pluginName)}: ${ - e.message - }`, - ), - ); - } - }); -}; - -export const removePluginDirectory = async (destination: string) => { - await Task.forItem('removing', 'plugin files', async () => { - try { - await fse.remove(destination); - } catch (e) { - assertError(e); - throw Error( - chalk.red( - ` There was a problem removing the plugin directory: ${e.message}`, - ), - ); - } - }); -}; - -export const removeSymLink = async (destination: string) => { - await Task.forItem('removing', 'symbolic link', async () => { - const symLinkExists = await fse.pathExists(destination); - if (symLinkExists) { - try { - await fse.remove(destination); - } catch (e) { - assertError(e); - throw Error( - chalk.red( - ` Could not remove symbolic link\t${chalk.cyan(destination)}: ${ - e.message - }`, - ), - ); - } - } - }); -}; - -const removeAllStatementsContainingID = async (file: string, ID: string) => { - const originalContent = await fse.readFile(file, 'utf8'); - const contentAfterRemoval = originalContent - .split('\n') - .filter(statement => !statement.includes(`${ID}`)) // get rid of lines with pluginName - .join('\n'); - if (originalContent !== contentAfterRemoval) { - await fse.writeFile(file, contentAfterRemoval, 'utf8'); - } -}; - -const capitalize = (str: string): string => - str.charAt(0).toUpperCase() + str.slice(1); - -export const removeReferencesFromPluginsFile = async ( - pluginsFile: string, - pluginName: string, -) => { - const pluginNameCapitalized = pluginName - .split('-') - .map(name => capitalize(name)) - .join(''); - - await Task.forItem('removing', 'export references', async () => { - try { - await removeAllStatementsContainingID(pluginsFile, pluginNameCapitalized); - } catch (e) { - assertError(e); - throw new Error( - chalk.red( - ` There was an error removing export statement for plugin ${chalk.cyan( - pluginNameCapitalized, - )}: ${e.message}`, - ), - ); - } - }); -}; - -export const removePluginFromCodeOwners = async ( - codeOwnersFile: string, - pluginName: string, -) => { - await Task.forItem('removing', 'codeowners references', async () => { - try { - await removeAllStatementsContainingID(codeOwnersFile, pluginName); - } catch (e) { - assertError(e); - throw new Error( - chalk.red( - ` There was an error removing code owners statement for plugin ${chalk.cyan( - pluginName, - )}: ${e.message}`, - ), - ); - } - }); -}; - -export const removeReferencesFromAppPackage = async ( - appPackageFile: string, - pluginName: string, -) => { - const pluginPackage = `${BACKSTAGE}/plugin-${pluginName}`; - - await Task.forItem('removing', 'plugin app dependency', async () => { - try { - const appPackageFileContent = await fse.readFile(appPackageFile, 'utf-8'); - const appPackageFileContentJSON = JSON.parse(appPackageFileContent); - const dependencies = appPackageFileContentJSON.dependencies; - - if (!dependencies[pluginPackage]) { - throw new Error( - chalk.red( - ` Plugin ${chalk.cyan( - pluginPackage, - )} does not exist in ${chalk.cyan(appPackageFile)}`, - ), - ); - } - - delete dependencies[pluginPackage]; - await fse.writeFile( - appPackageFile, - `${JSON.stringify(appPackageFileContentJSON, null, 2)}\n`, - 'utf-8', - ); - } catch (e) { - assertError(e); - throw new Error( - chalk.red( - ` Failed to remove plugin as dependency in app: ${chalk.cyan( - appPackageFile, - )}: ${e.message}`, - ), - ); - } - }); -}; - -export default async () => { - console.warn( - chalk.yellow( - '[DEPRECATED] - The remove-plugin command is deprecated and will be removed in the future.', - ), - ); - - const questions: Question[] = [ - { - type: 'input', - name: 'pluginName', - message: chalk.blue( - 'Enter the ID of the plugin to be removed [required]', - ), - validate: (value: any) => { - if (!value) { - return chalk.red('Please enter an ID for the plugin'); - } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) { - return chalk.red( - 'Plugin IDs must be lowercase and contain only letters, digits and dashes.', - ); - } - return true; - }, - }, - ]; - - const answers: Answers = await inquirer.prompt(questions); - const pluginName: string = answers.pluginName; - const appPackage = paths.resolveTargetRoot('packages/app'); - const pluginDir = paths.resolveTargetRoot('plugins', answers.pluginName); - const codeOwnersFile = await getCodeownersFilePath(paths.targetRoot); - const appPackageFile = path.join(appPackage, 'package.json'); - const appPluginsFile = path.join(appPackage, 'src', 'plugins.ts'); - const pluginScopedDirectory = paths.resolveTargetRoot( - 'node_modules', - BACKSTAGE, - `plugin-${pluginName}`, - ); - - Task.log(); - Task.log('Removing the plugin...'); - - console.log(pluginScopedDirectory); - try { - Task.section('Checking the plugin exists.'); - await checkExists(paths.targetRoot, pluginName); - - Task.section('Removing plugin files.'); - await removePluginDirectory(pluginDir); - - Task.section('Removing symbolic link from @backstage.'); - await removeSymLink(pluginScopedDirectory); - - if (await fse.pathExists(appPackage)) { - Task.section('Removing references from plugins.ts.'); - await removeReferencesFromPluginsFile(appPluginsFile, pluginName); - - Task.section('Removing plugin dependency from app.'); - await removeReferencesFromAppPackage(appPackageFile, pluginName); - } - - if (codeOwnersFile) { - Task.section('Removing codeowners reference.'); - await removePluginFromCodeOwners(codeOwnersFile, pluginName); - } - - Task.log(); - Task.log( - `🥇 Successfully removed ${chalk.cyan( - `@backstage/plugin-${answers.id}`, - )}`, - ); - Task.log(); - } catch (error) { - assertError(error); - Task.error(error.message); - Task.log('It seems that something went wrong when removing the plugin 🤔'); - } -};