feat: built in pm2
This commit is contained in:
@ -0,0 +1,98 @@
|
||||
import { InjectPinoLogger, PinoLogger } from 'nestjs-pino';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { promisify } from 'util';
|
||||
import * as pm2 from 'pm2';
|
||||
import { Proc, ProcessDescription, StartOptions } from 'pm2';
|
||||
import { clone, last } from 'ramda';
|
||||
|
||||
@Injectable()
|
||||
export class DeployByPm2Service {
|
||||
constructor(
|
||||
@InjectPinoLogger(DeployByPm2Service.name)
|
||||
private readonly logger: PinoLogger,
|
||||
) {}
|
||||
async deploy(filePath: string, workspace: string) {
|
||||
const baseConfig: { apps: StartOptions[] } = await import(filePath);
|
||||
const appOptionsList: StartOptions[] = clone(baseConfig.apps);
|
||||
|
||||
await promisify<void>(pm2.connect.bind(pm2))();
|
||||
const allApps = await promisify(pm2.list.bind(pm2))();
|
||||
try {
|
||||
if (!Array.isArray(baseConfig.apps)) {
|
||||
this.logger.error(
|
||||
'the "apps" in the PM2 ecosystem configuration is not array',
|
||||
);
|
||||
throw new Error('apps is not array');
|
||||
}
|
||||
|
||||
const oldApps = this.filterOldApps(appOptionsList, allApps);
|
||||
|
||||
this.replaceAppName(appOptionsList, oldApps);
|
||||
for (const appOptions of appOptionsList) {
|
||||
const proc = await promisify<StartOptions, Proc>(pm2.start.bind(pm2))({
|
||||
...appOptions,
|
||||
cwd: workspace,
|
||||
});
|
||||
this.logger.info({ proc }, `start ${appOptions.name}`);
|
||||
}
|
||||
await this.stopApps(oldApps);
|
||||
} catch (err) {
|
||||
await this.stopApps(appOptionsList);
|
||||
throw err;
|
||||
} finally {
|
||||
pm2.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private async stopApps(apps: ProcessDescription[] | StartOptions[]) {
|
||||
await Promise.all(
|
||||
apps.map(async (app: ProcessDescription | StartOptions) => {
|
||||
let procAtStop: ProcessDescription;
|
||||
let procAtDelete: ProcessDescription;
|
||||
try {
|
||||
const idOrName = 'pm_id' in app ? app.pm_id : app.name;
|
||||
procAtStop = await promisify(pm2.stop.bind(pm2))(idOrName);
|
||||
procAtDelete = await promisify(pm2.delete.bind(pm2))(idOrName);
|
||||
this.logger.info('stop & delete %s success', app.name);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
{ error, procAtStop, procAtDelete },
|
||||
'stop & delete %s error',
|
||||
app.name,
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private replaceAppName(
|
||||
optionsList: StartOptions[],
|
||||
oldApps: ProcessDescription[],
|
||||
) {
|
||||
const appSn = this.getAppsSn(oldApps);
|
||||
|
||||
optionsList.forEach((options) => {
|
||||
if (!options.name) {
|
||||
this.logger.error('please give a name for application');
|
||||
throw new Error('app name is not given');
|
||||
}
|
||||
options.name = `${options.name}#${appSn}`;
|
||||
});
|
||||
}
|
||||
|
||||
private filterOldApps(
|
||||
optionsList: StartOptions[],
|
||||
apps: ProcessDescription[],
|
||||
) {
|
||||
return apps.filter((app) =>
|
||||
optionsList.some((options) => app.name.split('#')[0] === options.name),
|
||||
);
|
||||
}
|
||||
|
||||
private getAppsSn(oldApps: ProcessDescription[]) {
|
||||
const appsSn: number[] = oldApps.map(
|
||||
(app) => +(app.name.split('#')?.[1] ?? 0),
|
||||
);
|
||||
return (last(appsSn.sort()) ?? 0) + 1;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user