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:
commit
856c831308
5 changed files with 158 additions and 10 deletions
|
@ -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.');
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
30
packages/cli/src/commands/connector/list.ts
Normal file
30
packages/cli/src/commands/connector/list.ts
Normal 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;
|
59
packages/cli/src/commands/connector/remove.ts
Normal file
59
packages/cli/src/commands/connector/remove.ts
Normal 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;
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue