From dcb91428e6ef1021e383270e66d3e67bfc83e593 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Fri, 14 Oct 2022 16:01:23 +0800 Subject: [PATCH 1/2] feat(cli): list connectors --- packages/cli/src/commands/connector/add.ts | 8 ++-- packages/cli/src/commands/connector/index.ts | 8 +++- packages/cli/src/commands/connector/list.ts | 42 ++++++++++++++++++++ packages/cli/src/commands/connector/utils.ts | 23 ++++++++++- 4 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 packages/cli/src/commands/connector/list.ts diff --git a/packages/cli/src/commands/connector/add.ts b/packages/cli/src/commands/connector/add.ts index 0e59c6ffd..196d0b30f 100644 --- a/packages/cli/src/commands/connector/add.ts +++ b/packages/cli/src/commands/connector/add.ts @@ -3,7 +3,10 @@ import { CommandModule } from 'yargs'; import { log } from '../../utilities'; import { addConnectors, addOfficialConnectors, inquireInstancePath } from './utils'; -const add: CommandModule = { +const add: CommandModule< + { path?: string }, + { packages: string[]; path?: string; official: boolean } +> = { command: ['add [packages...]', 'a', 'install', 'i'], describe: 'Add specific Logto connectors', builder: (yargs) => @@ -21,8 +24,7 @@ const add: CommandModule { const instancePath = await inquireInstancePath(path); diff --git a/packages/cli/src/commands/connector/index.ts b/packages/cli/src/commands/connector/index.ts index 0e30e34dd..2844b6420 100644 --- a/packages/cli/src/commands/connector/index.ts +++ b/packages/cli/src/commands/connector/index.ts @@ -2,11 +2,17 @@ import { noop } from '@silverhand/essentials'; import { CommandModule } from 'yargs'; import add from './add'; +import list from './list'; const connector: CommandModule = { command: ['connector', 'c'], describe: 'Command for Logto connectors', - builder: (yargs) => yargs.command(add).demandCommand(1), + builder: (yargs) => + yargs + .option('path', { alias: 'p', type: 'string', describe: 'The path to your Logto instance' }) + .command(add) + .command(list) + .demandCommand(1), handler: noop, }; diff --git a/packages/cli/src/commands/connector/list.ts b/packages/cli/src/commands/connector/list.ts new file mode 100644 index 000000000..195e44681 --- /dev/null +++ b/packages/cli/src/commands/connector/list.ts @@ -0,0 +1,42 @@ +import { readdir } from 'fs/promises'; +import path from 'path'; + +import chalk from 'chalk'; +import { CommandModule } from 'yargs'; + +import { + getConnectorDirectory, + getConnectorPackageName, + inquireInstancePath, + isOfficialConnector, +} from './utils'; + +const logConnectorNames = (type: string, names: string[]) => { + if (names.length === 0) { + return; + } + + console.log(); + console.log(chalk.blue(type)); + console.log(names.map((value) => ' ' + value).join('\n')); +}; + +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)); + + logConnectorNames('official'.toUpperCase(), officialPackages); + logConnectorNames('3rd-party'.toUpperCase(), thirdPartyPackages); + }, +}; + +export default list; diff --git a/packages/cli/src/commands/connector/utils.ts b/packages/cli/src/commands/connector/utils.ts index 9fbe72fdf..f3840de90 100644 --- a/packages/cli/src/commands/connector/utils.ts +++ b/packages/cli/src/commands/connector/utils.ts @@ -86,8 +86,29 @@ export const normalizePackageName = (name: string) => ) .join('/'); +export 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 filePath = path.join(directory, 'package.json'); + + if (!existsSync(filePath)) { + return; + } + + const json = await readFile(filePath, 'utf8'); + const { name } = z.object({ name: z.string() }).parse(JSON.parse(json)); + + if (name.startsWith('connector-') || Boolean(name.split('/')[1]?.startsWith('connector-'))) { + return name; + } +}; + export const addConnectors = async (instancePath: string, packageNames: string[]) => { - const cwd = path.join(instancePath, coreDirectory, connectorDirectory); + const cwd = getConnectorDirectory(instancePath); if (!existsSync(cwd)) { await mkdir(cwd); From 7d257c45bfa37298c287b3ac867acd0606c4f028 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Fri, 14 Oct 2022 17:01:32 +0800 Subject: [PATCH 2/2] 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);