mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Server: Moved CLI commands to separate files
This commit is contained in:
parent
5c1cef8476
commit
dca13b3a68
47846
packages/server/package-lock.json
generated
47846
packages/server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -52,7 +52,7 @@
|
||||
"sqlite3": "^4.1.0",
|
||||
"stripe": "^8.150.0",
|
||||
"uuid": "^8.3.2",
|
||||
"yargs": "^14.0.0",
|
||||
"yargs": "^17.2.1",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -65,7 +65,7 @@
|
||||
"@types/markdown-it": "^12.0.0",
|
||||
"@types/mustache": "^0.8.32",
|
||||
"@types/nodemailer": "^6.4.1",
|
||||
"@types/yargs": "^13.0.2",
|
||||
"@types/yargs": "^17.0.4",
|
||||
"@types/zxcvbn": "^4.4.1",
|
||||
"gulp": "^4.0.2",
|
||||
"jest": "^26.6.3",
|
||||
|
@ -3,11 +3,9 @@ require('source-map-support').install();
|
||||
|
||||
import * as Koa from 'koa';
|
||||
import * as fs from 'fs-extra';
|
||||
import { argv as yargsArgv } from 'yargs';
|
||||
import Logger, { LoggerWrapper, TargetType } from '@joplin/lib/Logger';
|
||||
import config, { initConfig, runningInDocker, EnvVariables } from './config';
|
||||
import { createDb, dropDb } from './tools/dbTools';
|
||||
import { dropTables, connectDb, disconnectDb, migrateLatest, waitForConnection, sqliteDefaultDir, migrateList, migrateUp, migrateDown } from './db';
|
||||
import { migrateLatest, waitForConnection, sqliteDefaultDir } from './db';
|
||||
import { AppContext, Env, KoaNext } from './utils/types';
|
||||
import FsDriverNode from '@joplin/lib/fs-driver-node';
|
||||
import routeHandler from './middleware/routeHandler';
|
||||
@ -19,35 +17,21 @@ import startServices from './utils/startServices';
|
||||
import { credentialFile } from './utils/testing/testUtils';
|
||||
import apiVersionHandler from './middleware/apiVersionHandler';
|
||||
import clickJackingHandler from './middleware/clickJackingHandler';
|
||||
import deleteOldChanges from './commands/deleteOldChanges';
|
||||
import newModelFactory from './models/factory';
|
||||
import deleteOldChanges90 from './commands/deleteOldChanges90';
|
||||
import setupCommands from './utils/setupCommands';
|
||||
|
||||
interface Argv {
|
||||
env?: Env;
|
||||
migrateLatest?: boolean;
|
||||
migrateUp?: boolean;
|
||||
migrateDown?: boolean;
|
||||
migrateList?: boolean;
|
||||
dropDb?: boolean;
|
||||
pidfile?: string;
|
||||
dropTables?: boolean;
|
||||
createDb?: boolean;
|
||||
envFile?: string;
|
||||
deleteOldChanges?: boolean;
|
||||
deleteOldChanges90?: boolean;
|
||||
}
|
||||
|
||||
const argv: Argv = yargsArgv as any;
|
||||
|
||||
const nodeSqlite = require('sqlite3');
|
||||
const cors = require('@koa/cors');
|
||||
const nodeEnvFile = require('node-env-file');
|
||||
const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||
shimInit({ nodeSqlite });
|
||||
|
||||
const env: Env = argv.env as Env || Env.Prod;
|
||||
|
||||
const defaultEnvVariables: Record<Env, EnvVariables> = {
|
||||
dev: {
|
||||
// To test with the Postgres database, uncomment DB_CLIENT below and
|
||||
@ -99,6 +83,11 @@ async function getEnvFilePath(env: Env, argv: any): Promise<string> {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { selectedCommand, argv: yargsArgv } = await setupCommands();
|
||||
|
||||
const argv: Argv = yargsArgv as any;
|
||||
const env: Env = argv.env as Env || Env.Prod;
|
||||
|
||||
const envFilePath = await getEnvFilePath(env, argv);
|
||||
|
||||
if (envFilePath) nodeEnvFile(envFilePath);
|
||||
@ -222,42 +211,26 @@ async function main() {
|
||||
|
||||
let runCommandAndExitApp = true;
|
||||
|
||||
if (argv.migrateLatest) {
|
||||
const db = await connectDb(config().database);
|
||||
await migrateLatest(db);
|
||||
await disconnectDb(db);
|
||||
} else if (argv.migrateUp) {
|
||||
const db = await connectDb(config().database);
|
||||
await migrateUp(db);
|
||||
await disconnectDb(db);
|
||||
} else if (argv.migrateDown) {
|
||||
const db = await connectDb(config().database);
|
||||
await migrateDown(db);
|
||||
await disconnectDb(db);
|
||||
} else if (argv.migrateList) {
|
||||
const db = await connectDb(config().database);
|
||||
console.info(await migrateList(db));
|
||||
} else if (argv.dropDb) {
|
||||
await dropDb(config().database, { ignoreIfNotExists: true });
|
||||
} else if (argv.dropTables) {
|
||||
const db = await connectDb(config().database);
|
||||
await dropTables(db);
|
||||
await disconnectDb(db);
|
||||
} else if (argv.createDb) {
|
||||
await createDb(config().database);
|
||||
} else if (argv.deleteOldChanges || argv.deleteOldChanges90) {
|
||||
// Eventually all commands should be started in a more generic way. All
|
||||
// should go under /commands, and they will receive a context object
|
||||
// with an intialized models property.
|
||||
//
|
||||
// Also should use yargs command system.
|
||||
const connectionCheck = await waitForConnection(config().database);
|
||||
const models = newModelFactory(connectionCheck.connection, config());
|
||||
if (selectedCommand) {
|
||||
const commandArgv = {
|
||||
...argv,
|
||||
_: (argv as any)._.slice(),
|
||||
};
|
||||
commandArgv._.splice(0, 1);
|
||||
|
||||
if (argv.deleteOldChanges90) {
|
||||
await deleteOldChanges90({ models });
|
||||
if (selectedCommand.commandName() === 'db') {
|
||||
await selectedCommand.run(commandArgv, {
|
||||
db: null,
|
||||
models: null,
|
||||
});
|
||||
} else {
|
||||
await deleteOldChanges({ models });
|
||||
const connectionCheck = await waitForConnection(config().database);
|
||||
const models = newModelFactory(connectionCheck.connection, config());
|
||||
|
||||
await selectedCommand.run(commandArgv, {
|
||||
db: connectionCheck.connection,
|
||||
models,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
runCommandAndExitApp = false;
|
||||
|
36
packages/server/src/commands/BaseCommand.ts
Normal file
36
packages/server/src/commands/BaseCommand.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Options, PositionalOptions } from 'yargs';
|
||||
import { DbConnection } from '../db';
|
||||
import { Models } from '../models/factory';
|
||||
|
||||
export interface RunContext {
|
||||
db: DbConnection;
|
||||
models: Models;
|
||||
}
|
||||
|
||||
export default abstract class BaseCommand {
|
||||
|
||||
public commandName(): string {
|
||||
const splitted = this.command().split(' ');
|
||||
if (!splitted.length) throw new Error(`Invalid command: ${this.command()}`);
|
||||
return splitted[0];
|
||||
}
|
||||
|
||||
public command(): string {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public description(): string {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public positionals(): Record<string, PositionalOptions> {
|
||||
return {};
|
||||
}
|
||||
|
||||
public options(): Record<string, Options> {
|
||||
return {};
|
||||
}
|
||||
|
||||
public abstract run(argv: any, context: RunContext): Promise<void>;
|
||||
|
||||
}
|
57
packages/server/src/commands/DbCommand.ts
Normal file
57
packages/server/src/commands/DbCommand.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { PositionalOptions } from 'yargs';
|
||||
import config from '../config';
|
||||
import { connectDb, disconnectDb, dropTables } from '../db';
|
||||
import BaseCommand from './BaseCommand';
|
||||
import { createDb } from '../tools/dbTools';
|
||||
|
||||
enum ArgvCommand {
|
||||
DropTables = 'dropTables',
|
||||
Create = 'create',
|
||||
}
|
||||
|
||||
interface Argv {
|
||||
command: ArgvCommand;
|
||||
}
|
||||
|
||||
export default class DbCommand extends BaseCommand {
|
||||
|
||||
public command() {
|
||||
return 'db <command>';
|
||||
}
|
||||
|
||||
public description() {
|
||||
return 'execute a database command';
|
||||
}
|
||||
|
||||
public positionals(): Record<string, PositionalOptions> {
|
||||
return {
|
||||
'command': {
|
||||
description: 'command to execute',
|
||||
choices: [
|
||||
ArgvCommand.Create,
|
||||
ArgvCommand.DropTables,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async run(argv: Argv): Promise<void> {
|
||||
|
||||
|
||||
const commands: Record<ArgvCommand, Function> = {
|
||||
create: async () => {
|
||||
await createDb(config().database);
|
||||
},
|
||||
dropTables: async () => {
|
||||
const db = await connectDb(config().database);
|
||||
await dropTables(db);
|
||||
await disconnectDb(db);
|
||||
},
|
||||
};
|
||||
|
||||
if (!commands[argv.command]) throw new Error(`Invalid command: ${argv.command}`);
|
||||
|
||||
await commands[argv.command]();
|
||||
}
|
||||
|
||||
}
|
32
packages/server/src/commands/DeleteOldChangesCommand.ts
Normal file
32
packages/server/src/commands/DeleteOldChangesCommand.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Options } from 'yargs';
|
||||
import { Day } from '../utils/time';
|
||||
import BaseCommand, { RunContext } from './BaseCommand';
|
||||
|
||||
interface Argv {
|
||||
ttl: number;
|
||||
}
|
||||
|
||||
export default class DeleteOldChangesCommand extends BaseCommand {
|
||||
|
||||
public command() {
|
||||
return 'deleteOldChanges';
|
||||
}
|
||||
|
||||
public description() {
|
||||
return 'deletes old changes';
|
||||
}
|
||||
|
||||
public options(): Record<string, Options> {
|
||||
return {
|
||||
'ttl': {
|
||||
type: 'number',
|
||||
description: 'TTL in days',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async run(argv: Argv, runContext: RunContext): Promise<void> {
|
||||
await runContext.models.change().deleteOldChanges(argv.ttl ? argv.ttl * Day : null);
|
||||
}
|
||||
|
||||
}
|
80
packages/server/src/commands/MigrateCommand.ts
Normal file
80
packages/server/src/commands/MigrateCommand.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { PositionalOptions, Options } from 'yargs';
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
import { disconnectDb, migrateDown, migrateLatest, migrateList, migrateUnlock, migrateUp } from '../db';
|
||||
import BaseCommand, { RunContext } from './BaseCommand';
|
||||
|
||||
const logger = Logger.create('MigrateCommand');
|
||||
|
||||
enum ArgvCommand {
|
||||
Up = 'up',
|
||||
Down = 'down',
|
||||
Latest = 'latest',
|
||||
List = 'list',
|
||||
Unlock = 'unlock',
|
||||
}
|
||||
|
||||
interface Argv {
|
||||
command: ArgvCommand;
|
||||
}
|
||||
|
||||
export default class MigrateCommand extends BaseCommand {
|
||||
|
||||
public command() {
|
||||
return 'migrate <command>';
|
||||
}
|
||||
|
||||
public description() {
|
||||
return 'execute a database migration';
|
||||
}
|
||||
|
||||
public positionals(): Record<string, PositionalOptions> {
|
||||
return {
|
||||
'command': {
|
||||
description: 'command to execute',
|
||||
choices: [
|
||||
ArgvCommand.Up,
|
||||
ArgvCommand.Down,
|
||||
ArgvCommand.Latest,
|
||||
ArgvCommand.List,
|
||||
ArgvCommand.Unlock,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public options(): Record<string, Options> {
|
||||
return {
|
||||
'disable-transactions': {
|
||||
type: 'boolean',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async run(argv: Argv, runContext: RunContext): Promise<void> {
|
||||
const commands: Record<ArgvCommand, Function> = {
|
||||
up: async () => {
|
||||
await migrateUp(runContext.db);
|
||||
},
|
||||
down: async () => {
|
||||
await migrateDown(runContext.db);
|
||||
},
|
||||
latest: async () => {
|
||||
await migrateLatest(runContext.db);
|
||||
},
|
||||
list: async () => {
|
||||
const s = (await migrateList(runContext.db)) as string;
|
||||
s.split('\n').forEach(l => logger.info(l));
|
||||
},
|
||||
unlock: async () => {
|
||||
await migrateUnlock(runContext.db);
|
||||
},
|
||||
};
|
||||
|
||||
if (!commands[argv.command]) throw new Error(`Invalid command: ${argv.command}`);
|
||||
|
||||
await commands[argv.command]();
|
||||
|
||||
await disconnectDb(runContext.db);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { CommandContext } from '../utils/types';
|
||||
|
||||
export default async function(ctx: CommandContext) {
|
||||
await ctx.models.change().deleteOldChanges();
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import { Day } from '../utils/time';
|
||||
import { CommandContext } from '../utils/types';
|
||||
|
||||
export default async function(ctx: CommandContext) {
|
||||
await ctx.models.change().deleteOldChanges(90 * Day);
|
||||
}
|
@ -195,24 +195,31 @@ export async function disconnectDb(db: DbConnection) {
|
||||
await db.destroy();
|
||||
}
|
||||
|
||||
export async function migrateLatest(db: DbConnection) {
|
||||
export async function migrateLatest(db: DbConnection, disableTransactions = false) {
|
||||
await db.migrate.latest({
|
||||
directory: migrationDir,
|
||||
disableTransactions,
|
||||
});
|
||||
}
|
||||
|
||||
export async function migrateUp(db: DbConnection) {
|
||||
export async function migrateUp(db: DbConnection, disableTransactions = false) {
|
||||
await db.migrate.up({
|
||||
directory: migrationDir,
|
||||
disableTransactions,
|
||||
});
|
||||
}
|
||||
|
||||
export async function migrateDown(db: DbConnection) {
|
||||
export async function migrateDown(db: DbConnection, disableTransactions = false) {
|
||||
await db.migrate.down({
|
||||
directory: migrationDir,
|
||||
disableTransactions,
|
||||
});
|
||||
}
|
||||
|
||||
export async function migrateUnlock(db: DbConnection) {
|
||||
await db.migrate.forceFreeMigrationsLock();
|
||||
}
|
||||
|
||||
export async function migrateList(db: DbConnection, asString: boolean = true) {
|
||||
const migrations: any = await db.migrate.list({
|
||||
directory: migrationDir,
|
||||
|
77
packages/server/src/utils/setupCommands.ts
Normal file
77
packages/server/src/utils/setupCommands.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import yargs = require('yargs');
|
||||
import BaseCommand from '../commands/BaseCommand';
|
||||
import DbCommand from '../commands/DbCommand';
|
||||
import DeleteOldChangesCommand from '../commands/DeleteOldChangesCommand';
|
||||
import MigrateCommand from '../commands/MigrateCommand';
|
||||
|
||||
export interface Commands {
|
||||
commands: BaseCommand[];
|
||||
argv: typeof yargs.argv;
|
||||
selectedCommand: BaseCommand;
|
||||
yargs: typeof yargs;
|
||||
}
|
||||
|
||||
export default async function setupCommands(): Promise<Commands> {
|
||||
const commands: BaseCommand[] = [
|
||||
new MigrateCommand(),
|
||||
new DbCommand(),
|
||||
new DeleteOldChangesCommand(),
|
||||
];
|
||||
|
||||
for (const cmd of commands) {
|
||||
yargs.command(cmd.command(), cmd.description(), (yargs) => {
|
||||
const positionals = cmd.positionals ? cmd.positionals() : {};
|
||||
|
||||
for (const [name, options] of Object.entries(positionals)) {
|
||||
yargs.positional(name, options ? options : {});
|
||||
}
|
||||
|
||||
const commandOptions = cmd.options() ? cmd.options() : {};
|
||||
for (const [name, options] of Object.entries(commandOptions)) {
|
||||
yargs.options(name, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// yargs.option('env', {
|
||||
// default: 'prod',
|
||||
// type: 'string',
|
||||
// choices: ['dev', 'prod'],
|
||||
// hidden: true,
|
||||
// });
|
||||
|
||||
// yargs.option('stack-trace', {
|
||||
// default: '1',
|
||||
// type: 'boolean',
|
||||
// hidden: true,
|
||||
// });
|
||||
|
||||
// yargs.option('db-config-filename', {
|
||||
// type: 'string',
|
||||
// hidden: true,
|
||||
// });
|
||||
|
||||
yargs.help();
|
||||
|
||||
const argv = await yargs.argv;
|
||||
|
||||
const cmdName = argv._ && argv._.length ? argv._[0] : null;
|
||||
let selectedCommand = null;
|
||||
|
||||
for (const cmd of commands) {
|
||||
if (cmd.commandName() === cmdName) selectedCommand = cmd;
|
||||
}
|
||||
|
||||
if (cmdName && !selectedCommand) {
|
||||
yargs.showHelp();
|
||||
console.info('');
|
||||
throw new Error(`Invalid command: ${cmdName}`);
|
||||
}
|
||||
|
||||
return {
|
||||
commands,
|
||||
argv,
|
||||
selectedCommand,
|
||||
yargs,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user