mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(cli): add system get and set commands (#3318)
This commit is contained in:
parent
3c0c82a4bf
commit
822ef08bab
4 changed files with 117 additions and 3 deletions
5
.changeset-staged/loud-snakes-cross.md
Normal file
5
.changeset-staged/loud-snakes-cross.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@logto/cli": minor
|
||||
---
|
||||
|
||||
Add CLI command to get/set db's system table value
|
|
@ -4,11 +4,13 @@ import type { CommandModule } from 'yargs';
|
|||
import alteration from './alteration/index.js';
|
||||
import config from './config.js';
|
||||
import seed from './seed/index.js';
|
||||
import system from './system.js';
|
||||
|
||||
const database: CommandModule = {
|
||||
command: ['database', 'db'],
|
||||
describe: 'Commands for Logto database',
|
||||
builder: (yargs) => yargs.command(config).command(seed).command(alteration).demandCommand(1),
|
||||
builder: (yargs) =>
|
||||
yargs.command(config).command(seed).command(alteration).command(system).demandCommand(1),
|
||||
handler: noop,
|
||||
};
|
||||
|
||||
|
|
87
packages/cli/src/commands/database/system.ts
Normal file
87
packages/cli/src/commands/database/system.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
import type { SystemKey } from '@logto/schemas';
|
||||
import { systemGuards, systemKeys } from '@logto/schemas';
|
||||
import { noop } from '@silverhand/essentials';
|
||||
import chalk from 'chalk';
|
||||
import type { CommandModule } from 'yargs';
|
||||
|
||||
import { createPoolFromConfig } from '../../database.js';
|
||||
import { getRowByKey, updateValueByKey } from '../../queries/system.js';
|
||||
import { log } from '../../utils.js';
|
||||
|
||||
const validKeysDisplay = chalk.green(systemKeys.join(', '));
|
||||
|
||||
type ValidateKeysFunction = {
|
||||
(keys: string[]): asserts keys is SystemKey[];
|
||||
(key: string): asserts key is SystemKey;
|
||||
};
|
||||
|
||||
const validateKey: ValidateKeysFunction = (key) => {
|
||||
// Using `.includes()` will result a type error
|
||||
// eslint-disable-next-line unicorn/prefer-includes
|
||||
if (!systemKeys.some((element) => element === key)) {
|
||||
log.error(`Invalid config key ${chalk.red(key)} found, expected one of ${validKeysDisplay}`);
|
||||
}
|
||||
};
|
||||
|
||||
const getConfig: CommandModule<unknown, { key: string }> = {
|
||||
command: 'get <key>',
|
||||
describe: 'Get system value of the given key in Logto database',
|
||||
builder: (yargs) =>
|
||||
yargs.positional('key', {
|
||||
describe: `The key to get from database system table, one of ${validKeysDisplay}`,
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}),
|
||||
handler: async ({ key }) => {
|
||||
validateKey(key);
|
||||
|
||||
const pool = await createPoolFromConfig();
|
||||
const row = await getRowByKey(pool, key);
|
||||
await pool.end();
|
||||
|
||||
const value = row?.value;
|
||||
|
||||
console.log(
|
||||
chalk.magenta(key) +
|
||||
'=' +
|
||||
(value === undefined ? chalk.gray(value) : chalk.green(JSON.stringify(value)))
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const setConfig: CommandModule<unknown, { key: string; value: string }> = {
|
||||
command: 'set <key> <value>',
|
||||
describe: 'Set config value of the given key in Logto database',
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional('key', {
|
||||
describe: `The key to get from database system table, one of ${validKeysDisplay}`,
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
})
|
||||
.positional('value', {
|
||||
describe: 'The value to set, should be a valid JSON string',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}),
|
||||
handler: async ({ key, value }) => {
|
||||
validateKey(key);
|
||||
|
||||
const guarded = systemGuards[key].parse(JSON.parse(value));
|
||||
|
||||
const pool = await createPoolFromConfig();
|
||||
await updateValueByKey(pool, key, guarded);
|
||||
await pool.end();
|
||||
|
||||
log.info(`Update ${chalk.green(key)} succeeded`);
|
||||
},
|
||||
};
|
||||
|
||||
const system: CommandModule = {
|
||||
command: ['system'],
|
||||
describe: 'Commands for Logto system config',
|
||||
builder: (yargs) => yargs.command(getConfig).command(setConfig).demandCommand(1),
|
||||
handler: noop,
|
||||
};
|
||||
|
||||
export default system;
|
|
@ -1,4 +1,4 @@
|
|||
import type { AlterationState, System } from '@logto/schemas';
|
||||
import type { AlterationState, System, SystemKey } from '@logto/schemas';
|
||||
import { systemGuards, Systems, AlterationStateKey } from '@logto/schemas';
|
||||
import { convertToIdentifiers } from '@logto/shared';
|
||||
import type { Nullable } from '@silverhand/essentials';
|
||||
|
@ -6,7 +6,7 @@ import type { CommonQueryMethods, DatabaseTransactionConnection } from 'slonik';
|
|||
import { sql } from 'slonik';
|
||||
import { z } from 'zod';
|
||||
|
||||
const { fields } = convertToIdentifiers(Systems);
|
||||
const { fields, table } = convertToIdentifiers(Systems);
|
||||
|
||||
const doesTableExist = async (pool: CommonQueryMethods, table: string) => {
|
||||
const { rows } = await pool.query<{ regclass: Nullable<string> }>(
|
||||
|
@ -67,3 +67,23 @@ export const updateDatabaseTimestamp = async (
|
|||
`
|
||||
);
|
||||
};
|
||||
|
||||
export const getRowByKey = async (pool: CommonQueryMethods, key: SystemKey) =>
|
||||
pool.maybeOne<System>(sql`
|
||||
select ${sql.join([fields.key, fields.value], sql`,`)} from ${table}
|
||||
where ${fields.key} = ${key}
|
||||
`);
|
||||
|
||||
export const updateValueByKey = async <T extends SystemKey>(
|
||||
pool: CommonQueryMethods,
|
||||
key: T,
|
||||
value: z.infer<(typeof systemGuards)[T]>
|
||||
) =>
|
||||
pool.query(
|
||||
sql`
|
||||
insert into ${table} (${fields.key}, ${fields.value})
|
||||
values (${key}, ${sql.jsonb(value)})
|
||||
on conflict (${fields.key})
|
||||
do update set ${fields.value}=excluded.${fields.value}
|
||||
`
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue