add yargs bun and pm2

This commit is contained in:
liuyi 2025-06-17 22:36:38 +08:00
parent 2190ea3066
commit 806cfa0ff6
18 changed files with 237 additions and 32 deletions

View File

@ -6,7 +6,8 @@
"private": true,
"license": "UNLICENSED",
"scripts": {
"cli": "./node_modules/bun/bin/bun --bun ./console/bin.ts",
"cli": "bun --bun src/console/bin.ts",
"dev": "cross-env NODE_ENV=development pnpm cli start -w",
"prebuild": "rimraf dist",
"build": "cross-env NODE_ENV=production nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",

View File

@ -200,7 +200,7 @@ importers:
version: 28.14.0(@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0)(typescript@5.8.3))(eslint@9.29.0)(typescript@5.8.3))(eslint@9.29.0)(jest@30.0.0(@types/node@24.0.3)(ts-node@10.9.2(@swc/core@1.12.1)(@types/node@24.0.3)(typescript@5.8.3)))(typescript@5.8.3)
eslint-plugin-prettier:
specifier: ^5.4.1
version: 5.4.1(@types/eslint@9.6.1)(eslint-config-prettier@10.1.5(eslint@9.29.0))(eslint@9.29.0)(prettier@3.5.3)
version: 5.5.0(@types/eslint@9.6.1)(eslint-config-prettier@10.1.5(eslint@9.29.0))(eslint@9.29.0)(prettier@3.5.3)
eslint-plugin-unused-imports:
specifier: ^4.1.4
version: 4.1.4(@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0)(typescript@5.8.3))(eslint@9.29.0)(typescript@5.8.3))(eslint@9.29.0)
@ -2752,8 +2752,8 @@ packages:
engines: {node: '>=0.10.0'}
hasBin: true
electron-to-chromium@1.5.168:
resolution: {integrity: sha512-RUNQmFLNIWVW6+z32EJQ5+qx8ci6RGvdtDC0Ls+F89wz6I2AthpXF0w0DIrn2jpLX0/PU9ZCo+Qp7bg/EckJmA==}
electron-to-chromium@1.5.169:
resolution: {integrity: sha512-q7SQx6mkLy0GTJK9K9OiWeaBMV4XQtBSdf6MJUzDB/H/5tFXfIiX38Lci1Kl6SsgiEhz1SQI1ejEOU5asWEhwQ==}
emittery@0.13.1:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
@ -2901,8 +2901,8 @@ packages:
jest:
optional: true
eslint-plugin-prettier@5.4.1:
resolution: {integrity: sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==}
eslint-plugin-prettier@5.5.0:
resolution: {integrity: sha512-8qsOYwkkGrahrgoUv76NZi23koqXOGiiEzXMrT8Q7VcYaUISR+5MorIUxfWqYXN0fN/31WbSrxCxFkVQ43wwrA==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
'@types/eslint': '>=8.0.0'
@ -7815,7 +7815,7 @@ snapshots:
browserslist@4.25.0:
dependencies:
caniuse-lite: 1.0.30001723
electron-to-chromium: 1.5.168
electron-to-chromium: 1.5.169
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.25.0)
@ -8190,7 +8190,7 @@ snapshots:
dependencies:
jake: 10.9.2
electron-to-chromium@1.5.168: {}
electron-to-chromium@1.5.169: {}
emittery@0.13.1: {}
@ -8395,7 +8395,7 @@ snapshots:
- supports-color
- typescript
eslint-plugin-prettier@5.4.1(@types/eslint@9.6.1)(eslint-config-prettier@10.1.5(eslint@9.29.0))(eslint@9.29.0)(prettier@3.5.3):
eslint-plugin-prettier@5.5.0(@types/eslint@9.6.1)(eslint-config-prettier@10.1.5(eslint@9.29.0))(eslint@9.29.0)(prettier@3.5.3):
dependencies:
eslint: 9.29.0
prettier: 3.5.3

View File

@ -5,12 +5,13 @@ import {
Entity,
OneToMany,
PrimaryColumn,
Relation,
Tree,
TreeChildren,
TreeParent,
} from 'typeorm';
import type { Relation } from 'typeorm';
import { PostEntity } from '@/modules/content/entities/post.entity';
@Exclude()

View File

@ -6,12 +6,13 @@ import {
Entity,
ManyToOne,
PrimaryColumn,
Relation,
Tree,
TreeChildren,
TreeParent,
} from 'typeorm';
import type { Relation } from 'typeorm';
import { PostEntity } from '@/modules/content/entities/post.entity';
@Exclude()

View File

@ -10,10 +10,11 @@ import {
ManyToOne,
OneToMany,
PrimaryColumn,
Relation,
UpdateDateColumn,
} from 'typeorm';
import type { Relation } from 'typeorm';
import { PostBodyType } from '@/modules/content/constants';
import { CategoryEntity } from '@/modules/content/entities/category.entity';
import { CommentEntity } from '@/modules/content/entities/comment.entity';

View File

@ -1,5 +1,6 @@
import { Exclude, Expose } from 'class-transformer';
import { Column, Entity, ManyToMany, PrimaryColumn, Relation } from 'typeorm';
import { Column, Entity, ManyToMany, PrimaryColumn } from 'typeorm';
import type { Relation } from 'typeorm';
import { PostEntity } from '@/modules/content/entities/post.entity';

View File

@ -10,7 +10,7 @@ import { PostEntity } from '@/modules/content/entities/post.entity';
import { CategoryRepository } from '@/modules/content/repositories';
import { PostRepository } from '@/modules/content/repositories/post.repository';
import { SearchService } from '@/modules/content/services/search.service';
import { SearchType } from '@/modules/content/types';
import type { SearchType } from '@/modules/content/types';
import { BaseService } from '@/modules/database/base/service';
import { SelectTrashMode } from '@/modules/database/constants';
import { QueryHook } from '@/modules/database/types';

View File

@ -0,0 +1,52 @@
import { spawn } from 'node:child_process';
import { exit } from 'process';
import { Arguments } from 'yargs';
import { getCLIConfig } from '@/modules/core/commands/helpers/config';
import { BuildCommandArguments } from '@/modules/core/commands/types';
import { CommandItem } from '@/modules/core/types';
export const createBuildCommand: CommandItem<any, BuildCommandArguments> = async (app) => ({
command: ['build', 'b'],
describe: 'Build application by nest cli.',
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',
},
watch: {
type: 'boolean',
alias: 'w',
describe: ' Run in watch mode (live-reload).',
default: false,
},
preserveWatchOutput: {
type: 'boolean',
alias: 'po',
describe: 'Use "preserveWatchOutput" option when using tsc watch mode',
default: false,
},
},
handler: async (args: Arguments<BuildCommandArguments>) => {
const config = getCLIConfig(args.tsConfig, args.nestConfig);
const params = ['build', '-c', args.nestConfig, '-p', args.tsConfig];
if (args.watch) {
params.push('-w');
}
if (args.preserveWatchOutput) {
params.push('po');
}
const child = spawn(config.paths.nest, params, config.subprocess.node);
child.on('exit', () => exit());
},
});

View File

@ -1,4 +1,13 @@
import { FSWatcher } from 'chokidar';
import { join } from 'path';
// eslint-disable-next-line import/no-extraneous-dependencies
import { ActionOnFile, AssetEntry } from '@nestjs/cli/lib/configuration';
import chokidar, { FSWatcher } from 'chokidar';
import { get } from 'lodash';
import { CLIConfig } from '@/modules/core/commands/types';
import { toBoolean } from '@/modules/core/helpers';
export class Asset {
private watchAssetsKeyValue: { [key: string]: boolean } = {};
@ -7,5 +16,82 @@ export class Asset {
private actionInProgress = false;
closeWatchers() {}
closeWatchers() {
const timeout = 500;
const closeFn = () => {
if (this.actionInProgress) {
this.actionInProgress = false;
setTimeout(closeFn, timeout);
} else {
this.watchers.forEach((watch) => watch.close());
}
};
setTimeout(closeFn, timeout);
}
watchAssets(config: CLIConfig, codePath: string, changer: () => void) {
const assets = get(config.options.nest, 'compilerOptions.assets', []) as AssetEntry[];
if (assets.length <= 0) {
return;
}
try {
const isWatchEnabled = toBoolean(get(config, 'watchAssets', 'src'));
const filesToWatch = assets.map<AssetEntry>((item) => {
if (typeof item === 'string') {
return {
glob: join(codePath, item),
};
}
return {
glob: join(codePath, item.include!),
exclude: item.exclude ? join(codePath, item.exclude) : undefined,
flat: item.flat,
watchAssets: item.watchAssets,
};
});
for (const file of filesToWatch) {
const option: ActionOnFile = {
action: 'change',
item: file,
path: '',
sourceRoot: codePath,
watchAssetsMode: isWatchEnabled,
};
const watcher = chokidar
.watch(file.glob, { ignored: file.exclude })
.on('add', (path) =>
this.actionOnFIle({ ...option, path, action: 'change' }, changer),
)
.on('change', (path) =>
this.actionOnFIle({ ...option, path, action: 'change' }, changer),
)
.on('unlink', (path) =>
this.actionOnFIle({ ...option, path, action: 'unlink' }, changer),
);
this.watchers.push(watcher);
}
} catch (e) {
throw new Error(
`An error occurred during the assets copying process. ${(e as any).message}`,
);
}
}
protected actionOnFIle(option: ActionOnFile, changer: () => void) {
const { action, item, path, watchAssetsMode } = option;
const isWatchEnabled = watchAssetsMode || item.watchAssets;
if (!isWatchEnabled && this.watchAssetsKeyValue[path]) {
return;
}
this.watchAssetsKeyValue[path] = true;
this.actionInProgress = true;
if (action === 'change') {
changer();
}
}
}

View File

@ -20,7 +20,7 @@ const cwdPath = resolve(__dirname, '../../../../..');
export function getCLIConfig(
tsConfigFile: string,
nestConfigFile: string,
tsEntryFile: string,
tsEntryFile?: string,
): CLIConfig {
let tsConfig: ts.CompilerOptions = {};
const tsConfigPath = join(cwdPath, tsConfigFile);
@ -57,7 +57,7 @@ export function getCLIConfig(
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',
nest: './node_modules/@nestjs/cli/bin/nest.js',
};
return {

View File

@ -2,16 +2,14 @@ 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 pm2 from 'pm2';
import { Arguments } from 'yargs';
import { Configure } from '@/modules/config/configure';
import { Asset } from '@/modules/core/commands/helpers/asset';
import { getPm2Config } from '@/modules/core/commands/helpers/config';
import { generateSwaggerMetadata } from '@/modules/core/commands/helpers/swagger';
import { CLIConfig, StartCommandArguments } from '@/modules/core/commands/types';
import { AppConfig } from '@/modules/core/types';
@ -19,6 +17,9 @@ export async function start(
args: Arguments<StartCommandArguments>,
config: CLIConfig,
): Promise<void> {
console.log('command start...');
console.log(args);
console.log(config);
const script = args.typescript ? config.paths.ts : config.paths.js;
const params = [config.paths.bun, 'run'];
if (args.watch) {
@ -29,9 +30,13 @@ export async function start(
typeof args.debug === 'string' ? `--inspect=${args.debug}` : '--inspect';
params.push(inspectFlag);
}
if (args.typescript) {
generateSwaggerMetadata(args, config, false);
}
params.push(script);
let child: Subprocess;
if (args.watch) {
const asset = new Asset();
const restart = () => {
if (!isNil(child)) {
child.kill();
@ -39,6 +44,12 @@ export async function start(
child = Bun.spawn(params, config.subprocess.bun);
};
restart();
asset.watchAssets(config, config.paths.cwd, restart);
process.on('exit', () => {
child.kill();
asset.closeWatchers();
process.exit(0);
});
} else {
Bun.spawn(params, {
...config.subprocess.bun,
@ -94,21 +105,22 @@ export async function startPM2(
console.error(error);
process.exit(1);
}
pm2Disconnect();
pm2.disconnect();
};
const restartCallback = (error?: any) => {
if (isNil(error)) {
pm2Disconnect();
pm2.disconnect();
} else {
pm2Start(pm2config, (err) => startCallback(err));
pm2.start(pm2config, (err) => startCallback(err));
}
};
pm2Connect((err: any) => {
pm2.connect((err: any) => {
connectCallback(err);
generateSwaggerMetadata(args, config, false);
args.restart
? pm2Restart(name, restartCallback)
: pm2Start(pm2config, (e) => startCallback(e));
? pm2.restart(name, restartCallback)
: pm2.start(pm2config, (e) => startCallback(e));
});
}

View File

@ -0,0 +1,39 @@
/* eslint-disable import/no-extraneous-dependencies */
import { join } from 'path';
import { PluginMetadataGenerator } from '@nestjs/cli/lib/compiler/plugins/plugin-metadata-generator';
import { PluginOptions } from '@nestjs/cli/lib/configuration';
import { ReadonlyVisitor } from '@nestjs/swagger/dist/plugin';
import { get, isNil } from 'lodash';
import { Arguments } from 'yargs';
import { CLIConfig, StartCommandArguments } from '@/modules/core/commands/types';
export function generateSwaggerMetadata(
args: Arguments<StartCommandArguments>,
config: CLIConfig,
watch: boolean,
) {
const cliPlugins = get(config.options.nest, 'compilerOptions.plugins', []) as (
| string
| RecordAny
)[];
const swaggerPlugin = cliPlugins.find(
(item) => item === '@nest/swagger' || (item as any).name === '@nest/swagger',
);
if (!isNil(swaggerPlugin) && args.typescript) {
const srcPath = join(config.paths.cwd, config.paths.src);
const generator = new PluginMetadataGenerator();
let swaggerPluginOption: PluginOptions;
if (typeof swaggerPlugin !== 'string' && 'options' in swaggerPlugin) {
swaggerPluginOption = swaggerPlugin.options;
}
generator.generate({
visitors: [new ReadonlyVisitor({ ...swaggerPluginOption, pathToSource: srcPath })],
outputDir: srcPath,
watch,
tsconfigPath: args.tsConfig,
printDiagnostics: false,
});
}
}

View File

@ -1 +1,3 @@
export * from './demo.command';
export * from './build.command';
export * from './start.command';

View File

@ -60,8 +60,11 @@ export const createStartCommand: CommandItem<any, StartCommandArguments> = async
},
},
handler: async (args: Arguments<StartCommandArguments>) => {
console.log('createStartCommand handler start');
console.log(app);
const { configure } = app;
const config = getCLIConfig(args.tsConfig, args.nestConfig, args.entry);
console.log(config);
if (args.prod || args.restart) {
await startPM2(configure, args, config);
} else {

View File

@ -57,3 +57,8 @@ export type StartCommandArguments = {
};
export type Pm2Option = Pick<StartCommandArguments, 'typescript' | 'watch'> & { command: string };
export type BuildCommandArguments = Pick<StartCommandArguments, 'tsConfig' | 'nestConfig'> & {
watch?: string;
preserveWatchOutput?: boolean;
};

View File

@ -7,6 +7,7 @@ import * as coreCommands from '../commands';
import { App, CommandCollection } from '../types';
export async function buildCli(creator: () => Promise<App>) {
console.log('buildCli start');
const app = await creator();
const bin = yargs(hideBin(process.argv));
app.commands.forEach((cmd) => {

View File

@ -1,7 +1,7 @@
import { ConfigureFactory, ConfigureRegister } from '../config/types';
import { createConnectionOptions } from '../config/utils';
import { MeiliConfig } from './types';
import type { MeiliConfig } from './types';
export const createMeiliConfig: (
registre: ConfigureRegister<RePartial<MeiliConfig>>,

View File

@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
import { isNil } from 'lodash';
import { MeiliSearch } from 'meilisearch';
import { MeiliConfig } from '@/modules/meilisearch/types';
import type { MeiliConfig } from '@/modules/meilisearch/types';
@Injectable()
export class MeiliService {