0
Fork 0
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:
Gao Sun 2022-04-11 16:25:57 +08:00 committed by GitHub
parent 32e9afe047
commit 72aa2f53e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 21 deletions

View file

@ -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",

View 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;

View file

@ -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 ?? '');

View file

@ -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 });

View file

@ -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';

View file

@ -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==}