0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

Merge pull request #2161 from logto-io/gao-log-4388-cli-listremove-connector

feat(cli): list & remove connectors
This commit is contained in:
Gao Sun 2022-10-17 12:03:25 +08:00 committed by GitHub
commit 856c831308
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 158 additions and 10 deletions

View file

@ -3,16 +3,19 @@ import { CommandModule } from 'yargs';
import { log } from '../../utilities';
import { addConnectors, addOfficialConnectors, inquireInstancePath } from './utils';
const add: CommandModule<unknown, { packages: string[]; path?: string; official: boolean }> = {
const add: CommandModule<
{ path?: string },
{ 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',
@ -21,17 +24,19 @@ const add: CommandModule<unknown, { packages: string[]; path?: string; official:
describe:
'Add all official connectors.\n' +
"If it's true, the specified package names will be ignored.",
})
.option('path', { alias: 'p', type: 'string', describe: 'The path to your Logto instance' }),
}),
handler: async ({ packages: packageNames, path, official }) => {
const instancePath = await inquireInstancePath(path);
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.');
},
};

View file

@ -2,11 +2,23 @@ import { noop } from '@silverhand/essentials';
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.command(add).demandCommand(1),
builder: (yargs) =>
yargs
.option('path', {
alias: 'p',
type: 'string',
describe: 'The path to your Logto instance directory',
})
.command(add)
.command(list)
.command(remove)
.demandCommand(1),
handler: noop,
};

View file

@ -0,0 +1,30 @@
import chalk from 'chalk';
import { CommandModule } from 'yargs';
import { getConnectorPackagesFrom, 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 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);
},
};
export default list;

View file

@ -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;

View file

@ -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,8 +86,50 @@ export const normalizePackageName = (name: string) =>
)
.join('/');
const getConnectorDirectory = (instancePath: string) =>
path.join(instancePath, coreDirectory, connectorDirectory);
export const isOfficialConnector = (packageName: string) =>
packageName.startsWith('@logto/connector-');
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 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 = path.join(instancePath, coreDirectory, connectorDirectory);
const cwd = getConnectorDirectory(instancePath);
if (!existsSync(cwd)) {
await mkdir(cwd);