0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

refactor(cli): use env to replace config json

This commit is contained in:
Gao Sun 2022-10-08 18:06:40 +08:00
parent 4a5dfb1b8a
commit a8c364e9b3
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
10 changed files with 28 additions and 104 deletions

View file

@ -39,7 +39,7 @@
"@silverhand/essentials": "^1.2.1", "@silverhand/essentials": "^1.2.1",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"decamelize": "^5.0.0", "decamelize": "^5.0.0",
"find-up": "^5.0.0", "dotenv": "^16.0.0",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"got": "^11.8.2", "got": "^11.8.2",
"hpagent": "^1.0.0", "hpagent": "^1.0.0",

View file

@ -7,7 +7,7 @@ import { copy, existsSync, remove, readdir } from 'fs-extra';
import { DatabasePool } from 'slonik'; import { DatabasePool } from 'slonik';
import { CommandModule } from 'yargs'; import { CommandModule } from 'yargs';
import { createPoolFromConfig } from '../../database'; import { createPoolFromEnv } from '../../database';
import { import {
getCurrentDatabaseAlterationTimestamp, getCurrentDatabaseAlterationTimestamp,
updateDatabaseTimestamp, updateDatabaseTimestamp,
@ -116,7 +116,7 @@ const alteration: CommandModule<unknown, { action: string }> = {
log.error('Unsupported action'); log.error('Unsupported action');
} }
const pool = await createPoolFromConfig(); const pool = await createPoolFromEnv();
const alterations = await getUndeployedAlterations(pool); const alterations = await getUndeployedAlterations(pool);
log.info( log.info(

View file

@ -2,7 +2,7 @@ import { logtoConfigGuards, LogtoConfigKey, logtoConfigKeys } from '@logto/schem
import chalk from 'chalk'; import chalk from 'chalk';
import { CommandModule } from 'yargs'; import { CommandModule } from 'yargs';
import { createPoolFromConfig } from '../../database'; import { createPoolFromEnv } from '../../database';
import { getRowsByKeys, updateValueByKey } from '../../queries/logto-config'; import { getRowsByKeys, updateValueByKey } from '../../queries/logto-config';
import { deduplicate, log } from '../../utilities'; import { deduplicate, log } from '../../utilities';
@ -47,7 +47,7 @@ export const getConfig: CommandModule<unknown, { key: string; keys: string[] }>
const queryKeys = deduplicate([key, ...keys]); const queryKeys = deduplicate([key, ...keys]);
validateKeys(queryKeys); validateKeys(queryKeys);
const pool = await createPoolFromConfig(); const pool = await createPoolFromEnv();
const { rows } = await getRowsByKeys(pool, queryKeys); const { rows } = await getRowsByKeys(pool, queryKeys);
await pool.end(); await pool.end();
@ -87,7 +87,7 @@ export const setConfig: CommandModule<unknown, { key: string; value: string }> =
const guarded = logtoConfigGuards[key].parse(JSON.parse(value)); const guarded = logtoConfigGuards[key].parse(JSON.parse(value));
const pool = await createPoolFromConfig(); const pool = await createPoolFromEnv();
await updateValueByKey(pool, key, guarded); await updateValueByKey(pool, key, guarded);
await pool.end(); await pool.end();

View file

@ -4,20 +4,12 @@ import { noop } from '../../utilities';
import alteration from './alteration'; import alteration from './alteration';
import { getConfig, setConfig } from './config'; import { getConfig, setConfig } from './config';
import seed from './seed'; import seed from './seed';
import { getUrl, setUrl } from './url';
const database: CommandModule = { const database: CommandModule = {
command: ['database', 'db'], command: ['database', 'db'],
describe: 'Commands for Logto database', describe: 'Commands for Logto database',
builder: (yargs) => builder: (yargs) =>
yargs yargs.command(getConfig).command(setConfig).command(seed).command(alteration).demandCommand(1),
.command(getUrl)
.command(setUrl)
.command(getConfig)
.command(setConfig)
.command(seed)
.command(alteration)
.demandCommand(1),
handler: noop, handler: noop,
}; };

View file

@ -1,26 +0,0 @@
import { CommandModule } from 'yargs';
import { getConfig, patchConfig } from '../../config';
export const getUrl: CommandModule = {
command: 'get-url',
describe: 'Get database URL in Logto config file',
handler: async () => {
const { databaseUrl } = await getConfig();
console.log(databaseUrl);
},
};
export const setUrl: CommandModule<unknown, { url: string }> = {
command: 'set-url <url>',
describe: 'Set database URL and save to config file',
builder: (yargs) =>
yargs.positional('url', {
describe: 'The database URL (DSN) to use, including database name',
type: 'string',
demandOption: true,
}),
handler: async (argv) => {
await patchConfig({ databaseUrl: String(argv.url) });
},
};

View file

@ -12,7 +12,7 @@ import * as semver from 'semver';
import tar from 'tar'; import tar from 'tar';
import { CommandModule } from 'yargs'; import { CommandModule } from 'yargs';
import { createPoolAndDatabaseIfNeeded, getDatabaseUrlFromConfig } from '../database'; import { createPoolAndDatabaseIfNeeded, getDatabaseUrlFromEnv } from '../database';
import { downloadFile, log, oraPromise, safeExecSync } from '../utilities'; import { downloadFile, log, oraPromise, safeExecSync } from '../utilities';
import { seedByPool } from './database/seed'; import { seedByPool } from './database/seed';
@ -149,7 +149,7 @@ const installLogto = async ({ path: pathArgument = defaultPath, silent = false }
} }
// Save to dot env // Save to dot env
const databaseUrl = await getDatabaseUrlFromConfig(); const databaseUrl = await getDatabaseUrlFromEnv();
const dotEnvPath = path.resolve(instancePath, '.env'); const dotEnvPath = path.resolve(instancePath, '.env');
await writeFile(dotEnvPath, `DB_URL=${databaseUrl}`, { await writeFile(dotEnvPath, `DB_URL=${databaseUrl}`, {
encoding: 'utf8', encoding: 'utf8',

View file

@ -1,45 +0,0 @@
import { readFile, writeFile } from 'fs/promises';
import os from 'os';
import path from 'path';
import chalk from 'chalk';
import findUp from 'find-up';
// eslint-disable-next-line id-length
import z from 'zod';
import { log } from './utilities';
// Logto config
const logtoConfigFilename = '.logto.json';
const getConfigPath = async () =>
(await findUp(logtoConfigFilename)) ?? path.join(os.homedir(), logtoConfigFilename);
const getConfigJson = async () => {
const configPath = await getConfigPath();
try {
const raw = await readFile(configPath, 'utf8');
// Prefer `unknown` over the original return type `any`, will guard later
// eslint-disable-next-line no-restricted-syntax
return JSON.parse(raw) as unknown;
} catch {}
};
const configGuard = z
.object({
databaseUrl: z.string().optional(),
})
.default({});
type LogtoConfig = z.infer<typeof configGuard>;
export const getConfig = async () => {
return configGuard.parse(await getConfigJson());
};
export const patchConfig = async (config: LogtoConfig) => {
const configPath = await getConfigPath();
await writeFile(configPath, JSON.stringify({ ...(await getConfig()), ...config }, undefined, 2));
log.info(`Updated config in ${chalk.blue(configPath)}`);
};

View file

@ -6,13 +6,12 @@ import { createPool, IdentifierSqlToken, parseDsn, sql, SqlToken, stringifyDsn }
import { createInterceptors } from 'slonik-interceptor-preset'; import { createInterceptors } from 'slonik-interceptor-preset';
import { z } from 'zod'; import { z } from 'zod';
import { getConfig, patchConfig } from './config';
import { log } from './utilities'; import { log } from './utilities';
export const defaultDatabaseUrl = 'postgresql://localhost:5432/logto'; export const defaultDatabaseUrl = 'postgresql://localhost:5432/logto';
export const getDatabaseUrlFromConfig = async () => { export const getDatabaseUrlFromEnv = async () => {
const { databaseUrl } = await getConfig(); const { DB_URL: databaseUrl } = process.env;
if (!databaseUrl) { if (!databaseUrl) {
const { value } = await inquirer const { value } = await inquirer
@ -24,18 +23,13 @@ export const getDatabaseUrlFromConfig = async () => {
}) })
.catch(async (error) => { .catch(async (error) => {
if (error.isTtyError) { if (error.isTtyError) {
log.error( log.error('No database URL configured in env');
`No database URL configured. Set it via ${chalk.green(
'database set-url'
)} command first.`
);
} }
// The type definition does not give us type except `any`, throw it directly will honor the original behavior. // The type definition does not give us type except `any`, throw it directly will honor the original behavior.
// eslint-disable-next-line @typescript-eslint/no-throw-literal // eslint-disable-next-line @typescript-eslint/no-throw-literal
throw error; throw error;
}); });
await patchConfig({ databaseUrl: value });
return value; return value;
} }
@ -43,8 +37,8 @@ export const getDatabaseUrlFromConfig = async () => {
return databaseUrl; return databaseUrl;
}; };
export const createPoolFromConfig = async () => { export const createPoolFromEnv = async () => {
const databaseUrl = await getDatabaseUrlFromConfig(); const databaseUrl = await getDatabaseUrlFromEnv();
return createPool(databaseUrl, { return createPool(databaseUrl, {
interceptors: createInterceptors(), interceptors: createInterceptors(),
@ -59,7 +53,7 @@ export const createPoolFromConfig = async () => {
*/ */
export const createPoolAndDatabaseIfNeeded = async () => { export const createPoolAndDatabaseIfNeeded = async () => {
try { try {
return await createPoolFromConfig(); return await createPoolFromEnv();
} catch (error: unknown) { } catch (error: unknown) {
const result = z.object({ code: z.string() }).safeParse(error); const result = z.object({ code: z.string() }).safeParse(error);
@ -69,7 +63,7 @@ export const createPoolAndDatabaseIfNeeded = async () => {
log.error(error); log.error(error);
} }
const databaseUrl = await getDatabaseUrlFromConfig(); const databaseUrl = await getDatabaseUrlFromEnv();
const dsn = parseDsn(databaseUrl); const dsn = parseDsn(databaseUrl);
// It's ok to fall back to '?' since: // It's ok to fall back to '?' since:
// - Database name is required to connect in the previous pool // - Database name is required to connect in the previous pool
@ -86,7 +80,7 @@ export const createPoolAndDatabaseIfNeeded = async () => {
log.info(`${chalk.green('✔')} Created database ${databaseName}`); log.info(`${chalk.green('✔')} Created database ${databaseName}`);
return createPoolFromConfig(); return createPoolFromEnv();
} }
}; };

View file

@ -1,9 +1,12 @@
import dotenv from 'dotenv';
import yargs from 'yargs'; import yargs from 'yargs';
import { hideBin } from 'yargs/helpers'; import { hideBin } from 'yargs/helpers';
import database from './commands/database'; import database from './commands/database';
import install from './commands/install'; import install from './commands/install';
dotenv.config();
void yargs(hideBin(process.argv)) void yargs(hideBin(process.argv))
.command(install) .command(install)
.command(database) .command(database)

10
pnpm-lock.yaml generated
View file

@ -35,8 +35,8 @@ importers:
'@types/yargs': ^17.0.13 '@types/yargs': ^17.0.13
chalk: ^4.1.2 chalk: ^4.1.2
decamelize: ^5.0.0 decamelize: ^5.0.0
dotenv: ^16.0.0
eslint: ^8.21.0 eslint: ^8.21.0
find-up: ^5.0.0
fs-extra: ^10.1.0 fs-extra: ^10.1.0
got: ^11.8.2 got: ^11.8.2
hpagent: ^1.0.0 hpagent: ^1.0.0
@ -62,7 +62,7 @@ importers:
'@silverhand/essentials': 1.2.1 '@silverhand/essentials': 1.2.1
chalk: 4.1.2 chalk: 4.1.2
decamelize: 5.0.1 decamelize: 5.0.1
find-up: 5.0.0 dotenv: 16.0.0
fs-extra: 10.1.0 fs-extra: 10.1.0
got: 11.8.3 got: 11.8.3
hpagent: 1.0.0 hpagent: 1.0.0
@ -7528,6 +7528,7 @@ packages:
dependencies: dependencies:
locate-path: 6.0.0 locate-path: 6.0.0
path-exists: 4.0.0 path-exists: 4.0.0
dev: true
/flat-cache/3.0.4: /flat-cache/3.0.4:
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
@ -10327,6 +10328,7 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
p-locate: 5.0.0 p-locate: 5.0.0
dev: true
/lodash._reinterpolate/3.0.0: /lodash._reinterpolate/3.0.0:
resolution: {integrity: sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=} resolution: {integrity: sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=}
@ -11963,6 +11965,7 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
yocto-queue: 0.1.0 yocto-queue: 0.1.0
dev: true
/p-locate/2.0.0: /p-locate/2.0.0:
resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==}
@ -11983,6 +11986,7 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
p-limit: 3.1.0 p-limit: 3.1.0
dev: true
/p-map-series/2.1.0: /p-map-series/2.1.0:
resolution: {integrity: sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q==} resolution: {integrity: sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q==}
@ -12247,6 +12251,7 @@ packages:
/path-exists/4.0.0: /path-exists/4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true
/path-is-absolute/1.0.1: /path-is-absolute/1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
@ -15810,6 +15815,7 @@ packages:
/yocto-queue/0.1.0: /yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/zod/3.18.0: /zod/3.18.0:
resolution: {integrity: sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA==} resolution: {integrity: sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA==}