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:
parent
4a5dfb1b8a
commit
a8c364e9b3
10 changed files with 28 additions and 104 deletions
|
@ -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",
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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) });
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -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',
|
||||||
|
|
|
@ -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)}`);
|
|
||||||
};
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
10
pnpm-lock.yaml
generated
|
@ -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==}
|
||||||
|
|
Loading…
Add table
Reference in a new issue