mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(cli): integrate with command line (#526)
This commit is contained in:
parent
32e9afe047
commit
72aa2f53e6
6 changed files with 124 additions and 21 deletions
|
@ -37,6 +37,8 @@
|
|||
"prettier": "@silverhand/eslint-config/.prettierrc",
|
||||
"dependencies": {
|
||||
"@logto/schemas": "^0.1.0",
|
||||
"chalk": "^4",
|
||||
"commander": "^9.1.0",
|
||||
"decamelize": "^5.0.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"roarr": "^7.11.0",
|
||||
|
|
73
packages/cli/src/commands/database/index.ts
Normal file
73
packages/cli/src/commands/database/index.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { Argument, Command, Option } from 'commander';
|
||||
|
||||
import { createDatabase, createDatabaseCli } from './library';
|
||||
import { replaceDsnDatabase } from './utilities';
|
||||
|
||||
const versionNote = 'Note Logto requires Postgres v14 or higher.';
|
||||
const database = new Command('database');
|
||||
|
||||
const args = {
|
||||
database: new Argument('[database]', 'The Logto database name.').default('logto'),
|
||||
};
|
||||
|
||||
const options = {
|
||||
dsn: new Option(
|
||||
'-d --dsn <address>',
|
||||
`Your Postgres DSN *WITHOUT* database name.\n${versionNote}`
|
||||
)
|
||||
.env('DSN')
|
||||
.makeOptionMandatory(),
|
||||
domain: new Option('--domain <domain>', 'The Logto domain for table seeding.')
|
||||
.env('DOMAIN')
|
||||
.makeOptionMandatory(),
|
||||
};
|
||||
|
||||
database.alias('db');
|
||||
|
||||
database
|
||||
.command('create')
|
||||
.description('Create a Logto databse.')
|
||||
.addArgument(args.database)
|
||||
.addOption(options.dsn)
|
||||
.action(async (databaseName: unknown, options: Record<string, unknown>) => {
|
||||
await createDatabase(String(options.dsn), String(databaseName));
|
||||
});
|
||||
|
||||
database
|
||||
.command('create-tables')
|
||||
.description('Create Logto database tables without data.')
|
||||
.addArgument(args.database)
|
||||
.addOption(options.dsn)
|
||||
.action(async (databaseName: unknown, options: Record<string, unknown>) => {
|
||||
const cli = createDatabaseCli(replaceDsnDatabase(String(options.dsn), String(databaseName)));
|
||||
await cli.createTables();
|
||||
await cli.end();
|
||||
});
|
||||
|
||||
database
|
||||
.command('seed-tables')
|
||||
.description('Seed tables with necessary data to run Logto.')
|
||||
.addArgument(args.database)
|
||||
.addOption(options.dsn)
|
||||
.addOption(options.domain)
|
||||
.action(async (databaseName: unknown, options: Record<string, unknown>) => {
|
||||
const cli = createDatabaseCli(replaceDsnDatabase(String(options.dsn), String(databaseName)));
|
||||
await cli.seedTables(String(options.domain));
|
||||
await cli.end();
|
||||
});
|
||||
|
||||
database
|
||||
.command('init')
|
||||
.description(`Create and initialize a Logto database with tables and data in the DSN.`)
|
||||
.addArgument(args.database)
|
||||
.addOption(options.dsn)
|
||||
.addOption(options.domain)
|
||||
.action(async (databaseName: unknown, options: Record<string, unknown>) => {
|
||||
const dsn = await createDatabase(String(options.dsn), String(databaseName));
|
||||
const cli = createDatabaseCli(dsn);
|
||||
await cli.createTables();
|
||||
await cli.seedTables(String(options.domain));
|
||||
await cli.end();
|
||||
});
|
||||
|
||||
export default database;
|
|
@ -2,27 +2,22 @@ import { readdir, readFile } from 'fs/promises';
|
|||
import path from 'path';
|
||||
|
||||
import { seeds } from '@logto/schemas';
|
||||
import { createPool, parseDsn, sql, stringifyDsn } from 'slonik';
|
||||
import chalk from 'chalk';
|
||||
import { createPool, sql } from 'slonik';
|
||||
import { createInterceptors } from 'slonik-interceptor-preset';
|
||||
import { raw } from 'slonik-sql-tag-raw';
|
||||
|
||||
import { insertInto } from './utilities';
|
||||
import { insertInto, replaceDsnDatabase } from './utilities';
|
||||
|
||||
const { managementResource, defaultSignInExperience, createDefaultSetting } = seeds;
|
||||
const tableDirectory = 'node_modules/@logto/schemas/tables';
|
||||
const domain = 'http://localhost:3001';
|
||||
const defaultDatabase = 'logto_test';
|
||||
|
||||
/**
|
||||
* Create a database.
|
||||
* @returns DSN with the created database name.
|
||||
*/
|
||||
export const createDatabase = async (
|
||||
dsn: string,
|
||||
databaseName = defaultDatabase
|
||||
): Promise<string> => {
|
||||
const { databaseName: _, ...restDsn } = parseDsn(dsn);
|
||||
const pool = createPool(dsn);
|
||||
export const createDatabase = async (dsn: string, databaseName: string): Promise<string> => {
|
||||
const pool = createPool(replaceDsnDatabase(dsn, 'postgres'));
|
||||
|
||||
await pool.query(sql`
|
||||
create database ${sql.identifier([databaseName])}
|
||||
|
@ -32,8 +27,11 @@ export const createDatabase = async (
|
|||
lc_ctype = 'en_US.utf8'
|
||||
connection_limit = -1;
|
||||
`);
|
||||
await pool.end();
|
||||
|
||||
return stringifyDsn({ ...restDsn, databaseName });
|
||||
console.log(`${chalk.blue('[create]')} Database ${databaseName} successfully created.`);
|
||||
|
||||
return replaceDsnDatabase(dsn, databaseName);
|
||||
};
|
||||
|
||||
export const createDatabaseCli = (dsn: string) => {
|
||||
|
@ -53,21 +51,18 @@ export const createDatabaseCli = (dsn: string) => {
|
|||
for (const [file, query] of queries) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await pool.query(sql`${raw(query)}`);
|
||||
console.log(`Create Tables: Run ${file} succeeded.`);
|
||||
console.log(`${chalk.blue('[create-tables]')} Run ${file} succeeded.`);
|
||||
}
|
||||
};
|
||||
|
||||
const seedTables = async () => {
|
||||
const seedTables = async (domain: string) => {
|
||||
await Promise.all([
|
||||
pool.query(insertInto(managementResource, 'resources')),
|
||||
pool.query(insertInto(createDefaultSetting(domain), 'settings')),
|
||||
pool.query(insertInto(defaultSignInExperience, 'sign_in_experiences')),
|
||||
]);
|
||||
console.log('Seed Tables: Seed tables succeeded.');
|
||||
console.log(`${chalk.blue('[seed-tables]')} Seed tables succeeded.`);
|
||||
};
|
||||
|
||||
return { createTables, seedTables };
|
||||
return { createTables, seedTables, end: pool.end };
|
||||
};
|
||||
|
||||
// For testing purpose, will remove later
|
||||
void createDatabase(process.env.DSN ?? '');
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { SchemaLike, SchemaValue, SchemaValuePrimitive } from '@logto/schemas';
|
||||
import decamelize from 'decamelize';
|
||||
import { sql, SqlToken } from 'slonik';
|
||||
import { parseDsn, sql, SqlToken, stringifyDsn } from 'slonik';
|
||||
|
||||
/**
|
||||
* Note `undefined` is removed from the acceptable list,
|
||||
|
@ -52,3 +52,6 @@ export const insertInto = <T extends SchemaLike>(object: T, table: string) => {
|
|||
)})
|
||||
`;
|
||||
};
|
||||
|
||||
export const replaceDsnDatabase = (dsn: string, databaseName: string): string =>
|
||||
stringifyDsn({ ...parseDsn(dsn), databaseName });
|
|
@ -1,5 +1,13 @@
|
|||
import { Command } from 'commander';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
import database from './commands/database';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export * from './database';
|
||||
const program = new Command();
|
||||
|
||||
program.addCommand(database);
|
||||
program.parse(process.argv);
|
||||
|
||||
// Export * from './database';
|
||||
|
|
|
@ -24,6 +24,8 @@ importers:
|
|||
'@silverhand/eslint-config': ^0.10.2
|
||||
'@silverhand/ts-config': ^0.10.2
|
||||
'@types/node': '14'
|
||||
chalk: ^4
|
||||
commander: ^9.1.0
|
||||
decamelize: ^5.0.0
|
||||
dotenv: ^10.0.0
|
||||
eslint: ^8.10.0
|
||||
|
@ -37,6 +39,8 @@ importers:
|
|||
typescript: ^4.6.3
|
||||
dependencies:
|
||||
'@logto/schemas': link:../schemas
|
||||
chalk: 4.1.2
|
||||
commander: 9.1.0
|
||||
decamelize: 5.0.1
|
||||
dotenv: 10.0.0
|
||||
roarr: 7.11.0
|
||||
|
@ -7693,6 +7697,11 @@ packages:
|
|||
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
/commander/9.1.0:
|
||||
resolution: {integrity: sha512-i0/MaqBtdbnJ4XQs4Pmyb+oFQl+q0lsAmokVUH92SlSw4fkeAcG3bVon+Qt7hmtF+u3Het6o4VgrcY3qAoEB6w==}
|
||||
engines: {node: ^12.20.0 || >=14}
|
||||
dev: false
|
||||
|
||||
/commondir/1.0.1:
|
||||
resolution: {integrity: sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=}
|
||||
dev: false
|
||||
|
@ -14489,7 +14498,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
shell-quote: 1.7.3
|
||||
yargs: 17.3.1
|
||||
yargs: 17.4.1
|
||||
|
||||
/pg-int8/1.0.1:
|
||||
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
||||
|
@ -19602,6 +19611,19 @@ packages:
|
|||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.0.1
|
||||
dev: true
|
||||
|
||||
/yargs/17.4.1:
|
||||
resolution: {integrity: sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
cliui: 7.0.4
|
||||
escalade: 3.1.1
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.0.1
|
||||
|
||||
/ylru/1.2.1:
|
||||
resolution: {integrity: sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==}
|
||||
|
|
Loading…
Reference in a new issue