From 7d257c45bfa37298c287b3ac867acd0606c4f028 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Fri, 14 Oct 2022 17:01:32 +0800 Subject: [PATCH] feat(cli): remove connectors --- packages/cli/src/commands/connector/add.ts | 13 ++-- packages/cli/src/commands/connector/index.ts | 8 ++- packages/cli/src/commands/connector/list.ts | 22 ++----- packages/cli/src/commands/connector/remove.ts | 59 +++++++++++++++++++ packages/cli/src/commands/connector/utils.ts | 27 ++++++++- 5 files changed, 103 insertions(+), 26 deletions(-) create mode 100644 packages/cli/src/commands/connector/remove.ts diff --git a/packages/cli/src/commands/connector/add.ts b/packages/cli/src/commands/connector/add.ts index 196d0b30f..931957187 100644 --- a/packages/cli/src/commands/connector/add.ts +++ b/packages/cli/src/commands/connector/add.ts @@ -5,17 +5,17 @@ import { addConnectors, addOfficialConnectors, inquireInstancePath } from './uti const add: CommandModule< { path?: string }, - { packages: string[]; path?: string; official: boolean } + { packages?: string[]; path?: string; official: boolean } > = { command: ['add [packages...]', 'a', 'install', 'i'], describe: 'Add specific Logto connectors', builder: (yargs) => yargs .positional('packages', { - describe: 'The additional connector package names', + describe: 'The connector package names to add', type: 'string', array: true, - default: [], + default: undefined, }) .option('official', { alias: 'o', @@ -30,10 +30,13 @@ const add: CommandModule< if (official) { await addOfficialConnectors(instancePath); + } else { + if (!packageNames?.length) { + log.error('No connector name provided'); + } + await addConnectors(instancePath, packageNames); } - await addConnectors(instancePath, packageNames); - log.info('Restart your Logto instance to get the changes reflected.'); }, }; diff --git a/packages/cli/src/commands/connector/index.ts b/packages/cli/src/commands/connector/index.ts index 2844b6420..56ae4bcef 100644 --- a/packages/cli/src/commands/connector/index.ts +++ b/packages/cli/src/commands/connector/index.ts @@ -3,15 +3,21 @@ import { CommandModule } from 'yargs'; import add from './add'; import list from './list'; +import remove from './remove'; const connector: CommandModule = { command: ['connector', 'c'], describe: 'Command for Logto connectors', builder: (yargs) => yargs - .option('path', { alias: 'p', type: 'string', describe: 'The path to your Logto instance' }) + .option('path', { + alias: 'p', + type: 'string', + describe: 'The path to your Logto instance directory', + }) .command(add) .command(list) + .command(remove) .demandCommand(1), handler: noop, }; diff --git a/packages/cli/src/commands/connector/list.ts b/packages/cli/src/commands/connector/list.ts index 195e44681..ae2af5f11 100644 --- a/packages/cli/src/commands/connector/list.ts +++ b/packages/cli/src/commands/connector/list.ts @@ -1,15 +1,7 @@ -import { readdir } from 'fs/promises'; -import path from 'path'; - import chalk from 'chalk'; import { CommandModule } from 'yargs'; -import { - getConnectorDirectory, - getConnectorPackageName, - inquireInstancePath, - isOfficialConnector, -} from './utils'; +import { getConnectorPackagesFrom, isOfficialConnector } from './utils'; const logConnectorNames = (type: string, names: string[]) => { if (names.length === 0) { @@ -25,14 +17,10 @@ const list: CommandModule<{ path?: string }, { path?: string }> = { command: ['list', 'l'], describe: 'List added Logto connectors', handler: async ({ path: inputPath }) => { - const directory = getConnectorDirectory(await inquireInstancePath(inputPath)); - const content = await readdir(directory, 'utf8'); - const rawNames = await Promise.all( - content.map(async (value) => getConnectorPackageName(path.join(directory, value))) - ); - const names = rawNames.filter((value): value is string => typeof value === 'string'); - const officialPackages = names.filter((name) => isOfficialConnector(name)); - const thirdPartyPackages = names.filter((name) => !isOfficialConnector(name)); + const packages = await getConnectorPackagesFrom(inputPath); + const packageNames = packages.map(({ name }) => name); + const officialPackages = packageNames.filter((name) => isOfficialConnector(name)); + const thirdPartyPackages = packageNames.filter((name) => !isOfficialConnector(name)); logConnectorNames('official'.toUpperCase(), officialPackages); logConnectorNames('3rd-party'.toUpperCase(), thirdPartyPackages); diff --git a/packages/cli/src/commands/connector/remove.ts b/packages/cli/src/commands/connector/remove.ts new file mode 100644 index 000000000..c3a4f37da --- /dev/null +++ b/packages/cli/src/commands/connector/remove.ts @@ -0,0 +1,59 @@ +import chalk from 'chalk'; +import fsExtra from 'fs-extra'; +import { CommandModule } from 'yargs'; + +import { log } from '../../utilities'; +import { getConnectorPackagesFrom } from './utils'; + +const remove: CommandModule<{ path?: string }, { path?: string; packages?: string[] }> = { + command: ['remove [packages...]', 'rm', 'delete'], + describe: 'Remove existing Logto connectors', + builder: (yargs) => + yargs.positional('packages', { + describe: 'The connector package names to remove', + type: 'string', + array: true, + default: undefined, + }), + handler: async ({ path: inputPath, packages: packageNames }) => { + if (!packageNames?.length) { + log.error('No connector name provided'); + } + + const existingPackages = await getConnectorPackagesFrom(inputPath); + const notFoundPackageNames = packageNames.filter( + (current) => !existingPackages.some(({ name }) => current === name) + ); + + if (notFoundPackageNames.length > 0) { + log.error( + `Cannot remove ${notFoundPackageNames + .map((name) => chalk.green(name)) + .join(', ')}: not found in your Logto instance directory` + ); + } + + const okSymbol = Symbol('Connector removed'); + const result = await Promise.all( + packageNames.map(async (current) => { + const packageInfo = existingPackages.find(({ name }) => name === current); + + try { + await fsExtra.remove(packageInfo?.path ?? ''); + + return okSymbol; + } catch (error: unknown) { + log.warn(`Error while removing ${chalk.green(packageInfo?.name)}`); + log.warn(error); + + return error; + } + }) + ); + const errorCount = result.filter((value) => value !== okSymbol).length; + + log.info(`Removed ${result.length - errorCount} connectors`); + }, +}; + +export default remove; diff --git a/packages/cli/src/commands/connector/utils.ts b/packages/cli/src/commands/connector/utils.ts index f3840de90..48e876c7c 100644 --- a/packages/cli/src/commands/connector/utils.ts +++ b/packages/cli/src/commands/connector/utils.ts @@ -1,6 +1,6 @@ import { exec } from 'child_process'; import { existsSync } from 'fs'; -import { readFile, mkdir, unlink } from 'fs/promises'; +import { readFile, mkdir, unlink, readdir } from 'fs/promises'; import path from 'path'; import { promisify } from 'util'; @@ -86,13 +86,13 @@ export const normalizePackageName = (name: string) => ) .join('/'); -export const getConnectorDirectory = (instancePath: string) => +const getConnectorDirectory = (instancePath: string) => path.join(instancePath, coreDirectory, connectorDirectory); export const isOfficialConnector = (packageName: string) => packageName.startsWith('@logto/connector-'); -export const getConnectorPackageName = async (directory: string) => { +const getConnectorPackageName = async (directory: string) => { const filePath = path.join(directory, 'package.json'); if (!existsSync(filePath)) { @@ -107,6 +107,27 @@ export const getConnectorPackageName = async (directory: string) => { } }; +export type ConnectorPackage = { + name: string; + path: string; +}; + +export const getConnectorPackagesFrom = async (instancePath?: string) => { + const directory = getConnectorDirectory(await inquireInstancePath(instancePath)); + const content = await readdir(directory, 'utf8'); + const rawPackages = await Promise.all( + content.map(async (value) => { + const currentDirectory = path.join(directory, value); + + return { name: await getConnectorPackageName(currentDirectory), path: currentDirectory }; + }) + ); + + return rawPackages.filter( + (packageInfo): packageInfo is ConnectorPackage => typeof packageInfo.name === 'string' + ); +}; + export const addConnectors = async (instancePath: string, packageNames: string[]) => { const cwd = getConnectorDirectory(instancePath);