add yargs bun and pm2
This commit is contained in:
parent
1ca32341fa
commit
2190ea3066
@ -3,15 +3,20 @@
|
|||||||
"collection": "@nestjs/schematics",
|
"collection": "@nestjs/schematics",
|
||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"assets": [
|
||||||
|
"assets/**/*"
|
||||||
|
],
|
||||||
"deleteOutDir": true,
|
"deleteOutDir": true,
|
||||||
"builder": "swc",
|
"builder": "swc",
|
||||||
"typeCheck": true,
|
"typeCheck": true,
|
||||||
"plugins": [{
|
"plugins": [
|
||||||
|
{
|
||||||
"name": "@nestjs/swagger",
|
"name": "@nestjs/swagger",
|
||||||
"options": {
|
"options": {
|
||||||
"introspectComments": true,
|
"introspectComments": true,
|
||||||
"controllerKeyOfComment": "summary"
|
"controllerKeyOfComment": "summary"
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"cli": "./node_modules/bun/bin/bun --bun ./console/bin.ts",
|
||||||
"prebuild": "rimraf dist",
|
"prebuild": "rimraf dist",
|
||||||
"build": "cross-env NODE_ENV=production nest build",
|
"build": "cross-env NODE_ENV=production nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"@nestjs/typeorm": "^11.0.0",
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
"bun": "^1.2.16",
|
"bun": "^1.2.16",
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
|
"chokidar": "^4.0.3",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.2",
|
"class-validator": "^0.14.2",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
@ -38,6 +40,8 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"meilisearch": "^0.51.0",
|
"meilisearch": "^0.51.0",
|
||||||
"mysql2": "^3.14.1",
|
"mysql2": "^3.14.1",
|
||||||
|
"ora": "^8.2.0",
|
||||||
|
"pm2": "^6.0.8",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
|
1267
pnpm-lock.yaml
1267
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
5
src/console/bin.ts
Normal file
5
src/console/bin.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { createApp } from '@/modules/core/helpers/app';
|
||||||
|
import { buildCli } from '@/modules/core/helpers/command';
|
||||||
|
import { createOptions } from '@/options';
|
||||||
|
|
||||||
|
buildCli(createApp(createOptions));
|
21
src/modules/core/commands/demo.command.ts
Normal file
21
src/modules/core/commands/demo.command.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { DemoCommandArguments } from '@/modules/core/commands/types';
|
||||||
|
import { CommandItem } from '@/modules/core/types';
|
||||||
|
|
||||||
|
export const DemoCommand: CommandItem<any, DemoCommandArguments> = async (app) => ({
|
||||||
|
command: ['demo', 'd'],
|
||||||
|
describe: 'a demo command',
|
||||||
|
handler: async (args: DemoCommandArguments) => {
|
||||||
|
const { configure } = app;
|
||||||
|
const appName = await configure.get<string>('app.name');
|
||||||
|
const sleep = args.sleep ? ' will to sleep' : '';
|
||||||
|
console.log(`just a demo command,app ${appName} ${sleep}`);
|
||||||
|
},
|
||||||
|
builder: {
|
||||||
|
sleep: {
|
||||||
|
type: 'boolean',
|
||||||
|
alias: 's',
|
||||||
|
describe: 'App will sleep ?',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
11
src/modules/core/commands/helpers/asset.ts
Normal file
11
src/modules/core/commands/helpers/asset.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { FSWatcher } from 'chokidar';
|
||||||
|
|
||||||
|
export class Asset {
|
||||||
|
private watchAssetsKeyValue: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
|
private watchers: FSWatcher[] = [];
|
||||||
|
|
||||||
|
private actionInProgress = false;
|
||||||
|
|
||||||
|
closeWatchers() {}
|
||||||
|
}
|
112
src/modules/core/commands/helpers/config.ts
Normal file
112
src/modules/core/commands/helpers/config.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
import { join, resolve } from 'path';
|
||||||
|
|
||||||
|
import { exit } from 'process';
|
||||||
|
|
||||||
|
import { Configuration as NestCLIConfig } from '@nestjs/cli/lib/configuration';
|
||||||
|
import { isNil } from '@nestjs/common/utils/shared.utils';
|
||||||
|
import { existsSync, readFileSync } from 'fs-extra';
|
||||||
|
import { get, omit } from 'lodash';
|
||||||
|
import { StartOptions } from 'pm2';
|
||||||
|
import ts from 'typescript';
|
||||||
|
|
||||||
|
import { Configure } from '@/modules/config/configure';
|
||||||
|
import { CLIConfig, Pm2Option } from '@/modules/core/commands/types';
|
||||||
|
import { deepMerge, panic } from '@/modules/core/helpers';
|
||||||
|
import { AppConfig } from '@/modules/core/types';
|
||||||
|
|
||||||
|
const cwdPath = resolve(__dirname, '../../../../..');
|
||||||
|
|
||||||
|
export function getCLIConfig(
|
||||||
|
tsConfigFile: string,
|
||||||
|
nestConfigFile: string,
|
||||||
|
tsEntryFile: string,
|
||||||
|
): CLIConfig {
|
||||||
|
let tsConfig: ts.CompilerOptions = {};
|
||||||
|
const tsConfigPath = join(cwdPath, tsConfigFile);
|
||||||
|
|
||||||
|
if (!existsSync(tsConfigPath)) {
|
||||||
|
panic(`ts config file ${tsConfigPath} not exists!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const allTsConfig = JSON.parse(readFileSync(tsConfigPath, 'utf8'));
|
||||||
|
tsConfig = get(allTsConfig, 'compilerOptions', {});
|
||||||
|
} catch (error) {
|
||||||
|
panic({ error, message: 'get ts config file failed.' });
|
||||||
|
}
|
||||||
|
let nestConfig: NestCLIConfig = {};
|
||||||
|
const nestConfigPath = join(cwdPath, nestConfigFile);
|
||||||
|
|
||||||
|
if (!existsSync(nestConfigPath)) {
|
||||||
|
panic(`ts config file ${nestConfigPath} not exists!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
nestConfig = JSON.parse(readFileSync(nestConfigPath, 'utf8'));
|
||||||
|
} catch (error) {
|
||||||
|
panic({ error, message: 'get nest config file failed.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const dist = get(tsConfig, 'outDir', 'dist');
|
||||||
|
const src = get(nestConfig, 'sourceRoot', 'src');
|
||||||
|
const paths = {
|
||||||
|
cwd: cwdPath,
|
||||||
|
dist,
|
||||||
|
src,
|
||||||
|
js: join(dist, nestConfig.entryFile ?? 'main.js'),
|
||||||
|
ts: join(src, tsEntryFile ?? 'main.ts'),
|
||||||
|
bun: './node_modules/bun/bin/bun',
|
||||||
|
nest: './node_modules/@nestjs//cli/bin/nest.js',
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
options: { ts: tsConfig, nest: nestConfig },
|
||||||
|
paths,
|
||||||
|
subprocess: {
|
||||||
|
bun: {
|
||||||
|
cwd: cwdPath,
|
||||||
|
stdout: 'inherit',
|
||||||
|
env: process.env,
|
||||||
|
onExit: (proc) => {
|
||||||
|
proc.kill();
|
||||||
|
if (!isNil(proc.exitCode)) {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
cwd: cwdPath,
|
||||||
|
env: process.env,
|
||||||
|
stdio: 'inherit',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPm2Config(
|
||||||
|
configure: Configure,
|
||||||
|
option: Pm2Option,
|
||||||
|
config: CLIConfig,
|
||||||
|
script: string,
|
||||||
|
): Promise<StartOptions> {
|
||||||
|
const { name, pm2: customConfig = {} } = await configure.get<AppConfig>('app');
|
||||||
|
const defaultConfig: StartOptions = {
|
||||||
|
name,
|
||||||
|
cwd: cwdPath,
|
||||||
|
script,
|
||||||
|
args: option.command,
|
||||||
|
autorestart: true,
|
||||||
|
watch: option.watch,
|
||||||
|
ignore_watch: ['node_modules'],
|
||||||
|
env: process.env,
|
||||||
|
exec_mode: 'fork',
|
||||||
|
interpreter: config.paths.bun,
|
||||||
|
};
|
||||||
|
|
||||||
|
return deepMerge(
|
||||||
|
defaultConfig,
|
||||||
|
omit(customConfig, ['name', 'cwd', 'script', 'args', 'watch', 'interpreter']),
|
||||||
|
'replace',
|
||||||
|
);
|
||||||
|
}
|
114
src/modules/core/commands/helpers/start.ts
Normal file
114
src/modules/core/commands/helpers/start.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { isNil } from '@nestjs/common/utils/shared.utils';
|
||||||
|
import { Subprocess } from 'bun';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
import {
|
||||||
|
connect as pm2Connect,
|
||||||
|
disconnect as pm2Disconnect,
|
||||||
|
restart as pm2Restart,
|
||||||
|
start as pm2Start,
|
||||||
|
} from 'pm2';
|
||||||
|
import { Arguments } from 'yargs';
|
||||||
|
|
||||||
|
import { Configure } from '@/modules/config/configure';
|
||||||
|
import { getPm2Config } from '@/modules/core/commands/helpers/config';
|
||||||
|
import { CLIConfig, StartCommandArguments } from '@/modules/core/commands/types';
|
||||||
|
import { AppConfig } from '@/modules/core/types';
|
||||||
|
|
||||||
|
export async function start(
|
||||||
|
args: Arguments<StartCommandArguments>,
|
||||||
|
config: CLIConfig,
|
||||||
|
): Promise<void> {
|
||||||
|
const script = args.typescript ? config.paths.ts : config.paths.js;
|
||||||
|
const params = [config.paths.bun, 'run'];
|
||||||
|
if (args.watch) {
|
||||||
|
params.push('--watch');
|
||||||
|
}
|
||||||
|
if (args.debug) {
|
||||||
|
const inspectFlag =
|
||||||
|
typeof args.debug === 'string' ? `--inspect=${args.debug}` : '--inspect';
|
||||||
|
params.push(inspectFlag);
|
||||||
|
}
|
||||||
|
params.push(script);
|
||||||
|
let child: Subprocess;
|
||||||
|
if (args.watch) {
|
||||||
|
const restart = () => {
|
||||||
|
if (!isNil(child)) {
|
||||||
|
child.kill();
|
||||||
|
}
|
||||||
|
child = Bun.spawn(params, config.subprocess.bun);
|
||||||
|
};
|
||||||
|
restart();
|
||||||
|
} else {
|
||||||
|
Bun.spawn(params, {
|
||||||
|
...config.subprocess.bun,
|
||||||
|
onExit(proc) {
|
||||||
|
proc.kill();
|
||||||
|
process.exit(0);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startPM2(
|
||||||
|
configure: Configure,
|
||||||
|
args: Arguments<StartCommandArguments>,
|
||||||
|
config: CLIConfig,
|
||||||
|
): Promise<void> {
|
||||||
|
const { name } = await configure.get<AppConfig>('app');
|
||||||
|
const script = args.typescript ? config.paths.ts : config.paths.js;
|
||||||
|
const pm2config = await getPm2Config(
|
||||||
|
configure,
|
||||||
|
{ command: 'start', ...pick(args, ['watch', 'typescript']) },
|
||||||
|
config,
|
||||||
|
script,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pm2config.exec_mode === 'cluster' && args.typescript) {
|
||||||
|
console.log(
|
||||||
|
chalk.yellowBright(
|
||||||
|
'Cannot directly use bun to run ts code in cluster mode, so it will automatically change to fork mode.',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log();
|
||||||
|
console.log(
|
||||||
|
chalk.bgCyanBright(
|
||||||
|
chalk.blackBright(
|
||||||
|
'If you really need the app to be started in cluster mode, be sure to compile it into js first, and then add the --no-ts arg when running',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log();
|
||||||
|
pm2config.exec_mode = 'fork';
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectCallback = (error?: any) => {
|
||||||
|
if (!isNil(error)) {
|
||||||
|
console.error(error);
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startCallback = (error?: any) => {
|
||||||
|
if (!isNil(error)) {
|
||||||
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
pm2Disconnect();
|
||||||
|
};
|
||||||
|
|
||||||
|
const restartCallback = (error?: any) => {
|
||||||
|
if (isNil(error)) {
|
||||||
|
pm2Disconnect();
|
||||||
|
} else {
|
||||||
|
pm2Start(pm2config, (err) => startCallback(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pm2Connect((err: any) => {
|
||||||
|
connectCallback(err);
|
||||||
|
args.restart
|
||||||
|
? pm2Restart(name, restartCallback)
|
||||||
|
: pm2Start(pm2config, (e) => startCallback(e));
|
||||||
|
});
|
||||||
|
}
|
1
src/modules/core/commands/index.ts
Normal file
1
src/modules/core/commands/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './demo.command';
|
71
src/modules/core/commands/start.command.ts
Normal file
71
src/modules/core/commands/start.command.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { Arguments } from 'yargs';
|
||||||
|
|
||||||
|
import { getCLIConfig } from '@/modules/core/commands/helpers/config';
|
||||||
|
import { start, startPM2 } from '@/modules/core/commands/helpers/start';
|
||||||
|
import { StartCommandArguments } from '@/modules/core/commands/types';
|
||||||
|
import { CommandItem } from '@/modules/core/types';
|
||||||
|
|
||||||
|
export const createStartCommand: CommandItem<any, StartCommandArguments> = async (app) => ({
|
||||||
|
command: ['start', 's'],
|
||||||
|
describe: 'Start app',
|
||||||
|
builder: {
|
||||||
|
nestConfig: {
|
||||||
|
type: 'string',
|
||||||
|
alias: 'n',
|
||||||
|
describe: 'nest cli config file path.',
|
||||||
|
default: 'nest-cli.json',
|
||||||
|
},
|
||||||
|
tsConfig: {
|
||||||
|
type: 'string',
|
||||||
|
alias: 't',
|
||||||
|
describe: 'typescript config file path.',
|
||||||
|
default: 'tsconfig.build.json',
|
||||||
|
},
|
||||||
|
entry: {
|
||||||
|
type: 'string',
|
||||||
|
alias: 'e',
|
||||||
|
describe:
|
||||||
|
'Specify entry file for ts runner, you can specify js entry file in nest-cli.json by entryFile.',
|
||||||
|
default: 'main.ts',
|
||||||
|
},
|
||||||
|
prod: {
|
||||||
|
type: 'boolean',
|
||||||
|
alias: 'p',
|
||||||
|
describe: 'Start app in production by pm2.',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
restart: {
|
||||||
|
type: 'boolean',
|
||||||
|
alias: 'r',
|
||||||
|
describe: 'Restart app(only pm2),pm2 will auto run start if process not exists.',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
typescript: {
|
||||||
|
type: 'boolean',
|
||||||
|
alias: 'ts',
|
||||||
|
describe: 'Run the .ts file directly.',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
type: 'boolean',
|
||||||
|
alias: 'w',
|
||||||
|
describe: ' Run in watch mode (live-reload).',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
debug: {
|
||||||
|
type: 'boolean',
|
||||||
|
alias: 'd',
|
||||||
|
describe: 'Whether to enable debug mode, only valid for non-production environments',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handler: async (args: Arguments<StartCommandArguments>) => {
|
||||||
|
const { configure } = app;
|
||||||
|
const config = getCLIConfig(args.tsConfig, args.nestConfig, args.entry);
|
||||||
|
if (args.prod || args.restart) {
|
||||||
|
await startPM2(configure, args, config);
|
||||||
|
} else {
|
||||||
|
await start(args, config);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
59
src/modules/core/commands/types.ts
Normal file
59
src/modules/core/commands/types.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
import { SpawnOptions as NodeSpawnOptions } from 'child_process';
|
||||||
|
|
||||||
|
import { Configuration as NestCLIConfig } from '@nestjs/cli/lib/configuration';
|
||||||
|
import type { SpawnOptions as BunSpawnOptions } from 'bun';
|
||||||
|
import ts from 'typescript';
|
||||||
|
|
||||||
|
export type DemoCommandArguments = {
|
||||||
|
sleep?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface CLIConfig {
|
||||||
|
options: {
|
||||||
|
ts: ts.CompilerOptions;
|
||||||
|
nest: NestCLIConfig;
|
||||||
|
};
|
||||||
|
paths: Record<'cwd' | 'dist' | 'src' | 'js' | 'ts' | 'bun' | 'nest', string>;
|
||||||
|
subprocess: {
|
||||||
|
bun: BunSpawnOptions.OptionsObject<any, any, any>;
|
||||||
|
node: NodeSpawnOptions;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StartCommandArguments = {
|
||||||
|
/**
|
||||||
|
* nest-cli.json的文件路径(相对于当前运行目录)
|
||||||
|
*/
|
||||||
|
nestConfig?: string;
|
||||||
|
/**
|
||||||
|
* 用于编译和运行的tsconfig.build.json的文件路径(相对于当前运行目录)
|
||||||
|
*/
|
||||||
|
tsConfig?: string;
|
||||||
|
/**
|
||||||
|
* 使用直接运行TS文件的入口文件,默认为main.ts. 如果是运行js文件,则通过nest-cli.json的entryFile指定
|
||||||
|
*/
|
||||||
|
entry?: string;
|
||||||
|
/**
|
||||||
|
* 是否使用PM2后台静默启动生产环境
|
||||||
|
*/
|
||||||
|
prod?: boolean;
|
||||||
|
/**
|
||||||
|
* 使用直接运行TS文件,这个配置只针对生产环境下是否通过
|
||||||
|
*/
|
||||||
|
typescript?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否监控,所有环境都可以使用(但在非PM2启动的生产环境下此选项无效)
|
||||||
|
*/
|
||||||
|
watch?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否开启debug模式,只对非生产环境有效
|
||||||
|
*/
|
||||||
|
debug?: boolean | string;
|
||||||
|
/**
|
||||||
|
* 是否重启应用(PM2进程)
|
||||||
|
*/
|
||||||
|
restart?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Pm2Option = Pick<StartCommandArguments, 'typescript' | 'watch'> & { command: string };
|
@ -10,13 +10,15 @@ import { Configure } from '@/modules/config/configure';
|
|||||||
|
|
||||||
import { DEFAULT_VALIDATION_CONFIG } from '@/modules/content/constants';
|
import { DEFAULT_VALIDATION_CONFIG } from '@/modules/content/constants';
|
||||||
|
|
||||||
|
import { createCommands } from '@/modules/core/helpers/command';
|
||||||
|
|
||||||
import { CoreModule } from '../core.module';
|
import { CoreModule } from '../core.module';
|
||||||
import { AppFilter } from '../providers/app.filter';
|
import { AppFilter } from '../providers/app.filter';
|
||||||
import { AppInterceptor } from '../providers/app.interceptor';
|
import { AppInterceptor } from '../providers/app.interceptor';
|
||||||
import { AppPipe } from '../providers/app.pipe';
|
import { AppPipe } from '../providers/app.pipe';
|
||||||
import { App, AppConfig, CreateOptions } from '../types';
|
import { App, AppConfig, CreateOptions } from '../types';
|
||||||
|
|
||||||
import { createCommands, CreateModule } from './utils';
|
import { CreateModule } from './utils';
|
||||||
|
|
||||||
export const app: App = { configure: new Configure(), commands: [] };
|
export const app: App = { configure: new Configure(), commands: [] };
|
||||||
|
|
||||||
@ -90,11 +92,11 @@ export async function createBootModule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function startApp(
|
export async function startApp(
|
||||||
creater: () => Promise<App>,
|
creator: () => Promise<App>,
|
||||||
listened: (app: App, startTime: Date) => () => Promise<void>,
|
listened: (app: App, startTime: Date) => () => Promise<void>,
|
||||||
) {
|
) {
|
||||||
const startTime = new Date();
|
const startTime = new Date();
|
||||||
const { container, configure } = await creater();
|
const { container, configure } = await creator();
|
||||||
app.container = container;
|
app.container = container;
|
||||||
app.configure = configure;
|
app.configure = configure;
|
||||||
const { port, host } = await configure.get<AppConfig>('app');
|
const { port, host } = await configure.get<AppConfig>('app');
|
||||||
|
54
src/modules/core/helpers/command.ts
Normal file
54
src/modules/core/helpers/command.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import chalk from 'chalk';
|
||||||
|
import yargs, { Arguments, CommandModule } from 'yargs';
|
||||||
|
|
||||||
|
import { hideBin } from 'yargs/helpers';
|
||||||
|
|
||||||
|
import * as coreCommands from '../commands';
|
||||||
|
import { App, CommandCollection } from '../types';
|
||||||
|
|
||||||
|
export async function buildCli(creator: () => Promise<App>) {
|
||||||
|
const app = await creator();
|
||||||
|
const bin = yargs(hideBin(process.argv));
|
||||||
|
app.commands.forEach((cmd) => {
|
||||||
|
bin.command(cmd);
|
||||||
|
});
|
||||||
|
bin.usage('Usage: $0 <command> [args]')
|
||||||
|
.scriptName('cli')
|
||||||
|
.demandCommand(1, '')
|
||||||
|
.fail((msg, err, y) => {
|
||||||
|
if (!msg && !err) {
|
||||||
|
bin.showHelp();
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
console.error(chalk.red(msg));
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
console.error(chalk.red(err.message));
|
||||||
|
}
|
||||||
|
process.exit();
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
.alias('v', 'version')
|
||||||
|
.help('h')
|
||||||
|
.alias('h', 'help')
|
||||||
|
.parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createCommands(
|
||||||
|
factory: () => CommandCollection,
|
||||||
|
app: Required<App>,
|
||||||
|
): Promise<CommandModule<any, any>[]> {
|
||||||
|
const collection: CommandCollection = [...factory(), ...Object.values(coreCommands)];
|
||||||
|
const commands = await Promise.all(collection.map(async (command) => command(app)));
|
||||||
|
return commands.map((command) => ({
|
||||||
|
...command,
|
||||||
|
handler: async (args: Arguments<RecordAny>) => {
|
||||||
|
await app.container.close();
|
||||||
|
await command.handler(args);
|
||||||
|
if (command.instant) {
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
@ -3,9 +3,7 @@ import chalk from 'chalk';
|
|||||||
import deepmerge from 'deepmerge';
|
import deepmerge from 'deepmerge';
|
||||||
import { isNil } from 'lodash';
|
import { isNil } from 'lodash';
|
||||||
|
|
||||||
import { Arguments, CommandModule } from 'yargs';
|
import { PanicOption } from '../types';
|
||||||
|
|
||||||
import { App, CommandCollection, PanicOption } from '../types';
|
|
||||||
|
|
||||||
export function toBoolean(value?: string | boolean): boolean {
|
export function toBoolean(value?: string | boolean): boolean {
|
||||||
if (isNil(value)) {
|
if (isNil(value)) {
|
||||||
@ -84,21 +82,3 @@ export async function panic(option: PanicOption | string) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createCommands(
|
|
||||||
factory: () => CommandCollection,
|
|
||||||
app: Required<App>,
|
|
||||||
): Promise<CommandModule<any, any>[]> {
|
|
||||||
const collection: CommandCollection = [...factory()];
|
|
||||||
const commands = await Promise.all(collection.map(async (command) => command(app)));
|
|
||||||
return commands.map((command) => ({
|
|
||||||
...command,
|
|
||||||
handler: async (args: Arguments<RecordAny>) => {
|
|
||||||
await app.container.close();
|
|
||||||
await command.handler(args);
|
|
||||||
if (command.instant) {
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { ModuleMetadata, PipeTransform, Type } from '@nestjs/common';
|
import { ModuleMetadata, PipeTransform, Type } from '@nestjs/common';
|
||||||
import { NestFastifyApplication } from '@nestjs/platform-fastify';
|
import { NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||||
|
|
||||||
|
import { Ora } from 'ora';
|
||||||
|
import { StartOptions } from 'pm2';
|
||||||
import { CommandModule } from 'yargs';
|
import { CommandModule } from 'yargs';
|
||||||
|
|
||||||
import { Configure } from '../config/configure';
|
import { Configure } from '../config/configure';
|
||||||
@ -58,6 +60,8 @@ export interface AppConfig {
|
|||||||
url?: string;
|
url?: string;
|
||||||
|
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
|
|
||||||
|
pm2?: Omit<StartOptions, 'name' | 'cwd' | 'script' | 'args' | 'interpreter' | 'watch'>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PanicOption {
|
export interface PanicOption {
|
||||||
@ -66,6 +70,8 @@ export interface PanicOption {
|
|||||||
error?: any;
|
error?: any;
|
||||||
|
|
||||||
exit?: boolean;
|
exit?: boolean;
|
||||||
|
|
||||||
|
spinner?: Ora;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommandOption<T = RecordAny, P = RecordAny> extends CommandModule<T, P> {
|
export interface CommandOption<T = RecordAny, P = RecordAny> extends CommandModule<T, P> {
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
"lib": ["esnext", "DOM", "ScriptHost", "WebWorker"],
|
"lib": ["esnext", "DOM", "ScriptHost", "WebWorker"],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
|
"types": ["bun-types", "@types/jest"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user