badmfck-api-server 4.1.36 → 4.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -249,10 +249,11 @@ class APIService extends BaseService_1.BaseService {
249
249
  origin: (origin, callback) => {
250
250
  if (!origin)
251
251
  return callback(null, true);
252
+ if (this.noCors)
253
+ return callback(null, true);
252
254
  try {
253
255
  const o = new URL(String(origin));
254
256
  const originNorm = o.origin.replace(/\/$/, "");
255
- const hostNorm = o.host.replace(/\/$/, "");
256
257
  const ok = corsSet.has(originNorm);
257
258
  return callback(null, ok);
258
259
  }
@@ -49,10 +49,12 @@ declare const _TConfig: {
49
49
  };
50
50
  readonly $__bluegreen_optional: true;
51
51
  readonly nginx: {
52
- readonly config: "";
53
- readonly $__config_optional: true;
54
- readonly symlink: "";
55
- readonly $__symlink_optional: true;
52
+ readonly config_dir: "";
53
+ readonly $__config_dir_optional: true;
54
+ readonly domain: "";
55
+ readonly $__domain_optional: true;
56
+ readonly upstream_name: "";
57
+ readonly $__upstream_name_optional: true;
56
58
  };
57
59
  readonly $__nginx_optional: true;
58
60
  };
@@ -69,8 +71,9 @@ export declare const REQ_DEPLOYMENT_CONFIG: Req<void, Map<string, {
69
71
  green_config: {};
70
72
  } | undefined;
71
73
  nginx?: {
72
- config?: string | undefined;
73
- symlink?: string | undefined;
74
+ config_dir?: string | undefined;
75
+ domain?: string | undefined;
76
+ upstream_name?: string | undefined;
74
77
  } | undefined;
75
78
  name: string;
76
79
  destination: string;
@@ -98,8 +101,9 @@ export declare const REQ_DEPLOYMENT_CONFIG_ADD: Req<{
98
101
  green_config: {};
99
102
  } | undefined;
100
103
  nginx?: {
101
- config?: string | undefined;
102
- symlink?: string | undefined;
104
+ config_dir?: string | undefined;
105
+ domain?: string | undefined;
106
+ upstream_name?: string | undefined;
103
107
  } | undefined;
104
108
  name: string;
105
109
  destination: string;
@@ -126,8 +130,9 @@ export declare const REQ_DEPLOYMENT_CONFIG_ADD: Req<{
126
130
  green_config: {};
127
131
  } | undefined;
128
132
  nginx?: {
129
- config?: string | undefined;
130
- symlink?: string | undefined;
133
+ config_dir?: string | undefined;
134
+ domain?: string | undefined;
135
+ upstream_name?: string | undefined;
131
136
  } | undefined;
132
137
  name: string;
133
138
  destination: string;
@@ -80,10 +80,12 @@ const _TConfig = {
80
80
  },
81
81
  $__bluegreen_optional: true,
82
82
  nginx: {
83
- config: "",
84
- $__config_optional: true,
85
- symlink: "",
86
- $__symlink_optional: true,
83
+ config_dir: "",
84
+ $__config_dir_optional: true,
85
+ domain: "",
86
+ $__domain_optional: true,
87
+ upstream_name: "",
88
+ $__upstream_name_optional: true,
87
89
  },
88
90
  $__nginx_optional: true,
89
91
  };
@@ -46,8 +46,13 @@ export declare class DeployerService extends BaseService {
46
46
  setup: TDeployerSetup | null;
47
47
  notifier: IDeploymentNotifier | null;
48
48
  busy: Map<string, boolean>;
49
+ private _setupWatchDebounceTimer;
50
+ private _lastSetupHash;
49
51
  constructor(options: IDeployerServiceOptions);
50
52
  init(): Promise<void>;
53
+ private _loadSetup;
54
+ private _reloadSetup;
55
+ private _watchSetup;
51
56
  proceed(data: {
52
57
  auth?: string;
53
58
  name: string;
@@ -107,6 +112,7 @@ export declare class DeployerService extends BaseService {
107
112
  }>;
108
113
  tokensMatch(a: string | undefined, b: string | undefined): boolean;
109
114
  private _checkUserAuth;
115
+ private _renderNginx;
110
116
  runPM2(found: IConfig, projectState?: IProjectState | null): Promise<IError | null>;
111
117
  private static readonly SKIP_DIRS;
112
118
  private static readonly TEMPLATE_EXTENSIONS;
@@ -37,6 +37,8 @@ class DeployerService extends BaseService_1.BaseService {
37
37
  setup = null;
38
38
  notifier = null;
39
39
  busy = new Map();
40
+ _setupWatchDebounceTimer = null;
41
+ _lastSetupHash = null;
40
42
  constructor(options) {
41
43
  super("DeployerService");
42
44
  this.options = options;
@@ -45,41 +47,84 @@ class DeployerService extends BaseService_1.BaseService {
45
47
  super.init();
46
48
  const cs = new ConfigService_1.ConfigService(this.options.projects_path);
47
49
  await cs.init();
50
+ const initial = await this._loadSetup();
51
+ if (!initial) {
52
+ throw new Error("Setup file missing/invalid at start — cannot start deployer");
53
+ }
54
+ this.setup = initial;
55
+ this._lastSetupHash = crypto_1.default.createHash("md5").update(JSON.stringify(initial)).digest("hex");
56
+ if (this.setup.notifier?.URL && this.setup.notifier?.KEY) {
57
+ this.notifier = new Notifier_1.Notifier({ URL: this.setup.notifier.URL, KEY: this.setup.notifier.KEY });
58
+ await this.notifier.init();
59
+ }
60
+ if (this.setup.watchdog) {
61
+ const wd = new Watchdog_1.Watchdog(this.notifier);
62
+ await wd.init();
63
+ }
64
+ this._watchSetup();
65
+ exports.REQ_DEPLOYMENT_PROCEED.listener = async (data) => this.proceed(data);
66
+ exports.REQ_DEPLOYMENT_SWITCH.listener = async (data) => this.switchSlot(data);
67
+ exports.REQ_DEPLOYMENT_STATUS.listener = async (data) => this.status(data);
68
+ }
69
+ async _loadSetup() {
48
70
  const setupFile = path_1.default.resolve(this.options.setup_path, "setup.json");
49
71
  if (!fs_1.default.existsSync(setupFile)) {
50
72
  (0, LogService_1.logError)("Setup file not found: " + setupFile);
51
- throw new Error("Setup file not found: " + setupFile);
73
+ return null;
52
74
  }
75
+ let raw;
53
76
  try {
54
- this.setup = JSON.parse(fs_1.default.readFileSync(setupFile, "utf-8"));
77
+ raw = fs_1.default.readFileSync(setupFile, "utf-8");
55
78
  }
56
79
  catch (e) {
57
80
  (0, LogService_1.logError)("Failed to read setup file: " + e.message);
58
- throw new Error("Failed to read setup file: " + e.message);
81
+ return null;
59
82
  }
60
- const validation = await __1.Validator.validateStructure(_TSetup, this.setup);
61
- if (validation && validation.length > 0) {
62
- (0, LogService_1.logError)("Setup file validation failed: " + JSON.stringify(validation));
63
- throw new Error("Setup file validation failed: " + JSON.stringify(validation));
83
+ let parsed;
84
+ try {
85
+ parsed = JSON.parse(raw);
64
86
  }
65
- if (!this.setup) {
66
- (0, LogService_1.logError)("Setup file is empty or invalid: " + setupFile);
67
- throw new Error("Setup file is empty or invalid: " + setupFile);
87
+ catch (e) {
88
+ (0, LogService_1.logError)("Failed to parse setup.json: " + e.message);
89
+ return null;
68
90
  }
69
- let notifier = null;
70
- if (this.setup && this.setup.notifier) {
71
- if (this.setup.notifier.URL && this.setup.notifier.KEY) {
72
- notifier = new Notifier_1.Notifier({ URL: this.setup.notifier.URL, KEY: this.setup.notifier.KEY });
73
- await notifier.init();
74
- }
91
+ const errors = await __1.Validator.validateStructure(_TSetup, parsed);
92
+ if (errors && errors.length > 0) {
93
+ (0, LogService_1.logError)("Setup validation failed: " + JSON.stringify(errors));
94
+ return null;
75
95
  }
76
- if (this.setup && this.setup.watchdog) {
77
- const wd = new Watchdog_1.Watchdog(notifier);
78
- await wd.init();
96
+ return parsed;
97
+ }
98
+ async _reloadSetup() {
99
+ const fresh = await this._loadSetup();
100
+ if (!fresh) {
101
+ (0, LogService_1.logWarn)("Setup reload failed — keeping previous good setup in memory");
102
+ return;
79
103
  }
80
- exports.REQ_DEPLOYMENT_PROCEED.listener = async (data) => this.proceed(data);
81
- exports.REQ_DEPLOYMENT_SWITCH.listener = async (data) => this.switchSlot(data);
82
- exports.REQ_DEPLOYMENT_STATUS.listener = async (data) => this.status(data);
104
+ const hash = crypto_1.default.createHash("md5").update(JSON.stringify(fresh)).digest("hex");
105
+ if (hash === this._lastSetupHash)
106
+ return;
107
+ this._lastSetupHash = hash;
108
+ this.setup = fresh;
109
+ (0, LogService_1.logWarn)("Setup reloaded from disk (users list refreshed live; notifier/watchdog need service restart to change)");
110
+ }
111
+ _watchSetup() {
112
+ try {
113
+ fs_1.default.watch(this.options.setup_path, (_eventType, filename) => {
114
+ if (filename !== "setup.json")
115
+ return;
116
+ if (this._setupWatchDebounceTimer)
117
+ clearTimeout(this._setupWatchDebounceTimer);
118
+ this._setupWatchDebounceTimer = setTimeout(() => {
119
+ this._setupWatchDebounceTimer = null;
120
+ this._reloadSetup();
121
+ }, 200);
122
+ });
123
+ }
124
+ catch (e) {
125
+ (0, LogService_1.logError)("Failed to fs.watch setup file: " + e.message);
126
+ }
127
+ setInterval(() => { this._reloadSetup(); }, 60_000).unref();
83
128
  }
84
129
  async proceed(data) {
85
130
  const user = this._checkUserAuth(data.auth);
@@ -109,6 +154,7 @@ class DeployerService extends BaseService_1.BaseService {
109
154
  const safeName = (path_1.default.basename(file.name || "").replace(/[^a-zA-Z0-9._-]/g, "_")) || "upload";
110
155
  let projectState = null;
111
156
  let projectStatePath = null;
157
+ let projectStateWasCreated = false;
112
158
  if (found.bluegreen && found.bluegreen.blue_config && found.bluegreen.green_config) {
113
159
  projectStatePath = path_1.default.resolve(this.options.projects_path, data.name + "_state_.json");
114
160
  if (fs_1.default.existsSync(projectStatePath)) {
@@ -125,6 +171,7 @@ class DeployerService extends BaseService_1.BaseService {
125
171
  lastDeployed: "blue",
126
172
  history: []
127
173
  };
174
+ projectStateWasCreated = true;
128
175
  }
129
176
  else {
130
177
  projectState.lastDeployed = projectState.active === "blue" ? "green" : "blue";
@@ -186,37 +233,31 @@ class DeployerService extends BaseService_1.BaseService {
186
233
  if (found.config) {
187
234
  await this.applyConfig(found.destination, found.config);
188
235
  }
189
- if (found.nginx?.config) {
190
- const nginxPath = path_1.default.resolve(found.destination, found.nginx.config);
191
- if (!fs_1.default.existsSync(nginxPath)) {
192
- if (found.email && found.email.length > 0) {
193
- for (let email of found.email) {
194
- this.notifier?.notifyEmailStatus(email, "error-nginx-missing", found.name, user.login);
195
- }
196
- }
197
- throw { code: 22, message: "nginx config declared but file not found: " + nginxPath, httpStatus: 500 };
198
- }
199
- try {
200
- NginxHelper_1.NginxHelper.applyTemplate(nginxPath, found.config ?? {});
201
- }
202
- catch (e) {
236
+ if (found.pm2) {
237
+ const pm2Result = await this.runPM2(found, projectState);
238
+ if (__1.ErrorUtils.isError(pm2Result)) {
203
239
  if (found.email && found.email.length > 0) {
204
240
  for (let email of found.email) {
205
- this.notifier?.notifyEmailStatus(email, "error-nginx-render", found.name, user.login);
241
+ this.notifier?.notifyEmailStatus(email, "error-pm2", found.name, user.login);
206
242
  }
207
243
  }
208
- throw { code: 23, message: "nginx template render failed: " + String(e?.message ?? e), httpStatus: 500 };
244
+ throw pm2Result;
209
245
  }
210
246
  }
211
- if (found.pm2) {
212
- const pm2Result = await this.runPM2(found, projectState);
213
- if (__1.ErrorUtils.isError(pm2Result)) {
247
+ if (found.nginx?.config_dir && (!projectState || projectStateWasCreated)) {
248
+ const activeCfg = (found.config ?? {});
249
+ const nginxErr = await this._renderNginx(found, data.name, activeCfg.PORT ?? "");
250
+ if (nginxErr) {
214
251
  if (found.email && found.email.length > 0) {
215
252
  for (let email of found.email) {
216
- this.notifier?.notifyEmailStatus(email, "error-pm2", found.name, user.login);
253
+ this.notifier?.notifyEmailStatus(email, "error-nginx", found.name, user.login);
217
254
  }
218
255
  }
219
- throw pm2Result;
256
+ throw nginxErr;
257
+ }
258
+ const hookErr = await NginxHelper_1.NginxHelper.runHook(found.bluegreen?.on_switch);
259
+ if (hookErr) {
260
+ (0, LogService_1.logWarn)("nginx reload after initial deploy failed: " + hookErr);
220
261
  }
221
262
  }
222
263
  if (projectState && projectStatePath) {
@@ -313,14 +354,18 @@ class DeployerService extends BaseService_1.BaseService {
313
354
  fs_1.default.writeFileSync(tmp, JSON.stringify(projectState, null, 4), "utf-8");
314
355
  fs_1.default.renameSync(tmp, projectStatePath);
315
356
  let hookError = null;
316
- if (found.nginx?.config && found.nginx?.symlink) {
317
- const symlinkTarget = path_1.default.resolve(found.destination, target, found.nginx.config);
318
- const swapErr = NginxHelper_1.NginxHelper.swapSymlink(found.nginx.symlink, symlinkTarget);
319
- if (swapErr) {
320
- return { error: swapErr };
357
+ if (found.nginx?.config_dir) {
358
+ const slotCfg = target === "blue" ? found.bluegreen.blue_config : found.bluegreen.green_config;
359
+ const merged = { ...(found.config ?? {}), ...(slotCfg ?? {}) };
360
+ const nginxErr = await this._renderNginx(found, data.name, merged.PORT ?? "");
361
+ if (nginxErr) {
362
+ (0, LogService_1.logError)("nginx render during switch failed (state already flipped): " + nginxErr.message);
363
+ hookError = nginxErr.message;
321
364
  }
322
365
  }
323
- hookError = await NginxHelper_1.NginxHelper.runHook(found.bluegreen.on_switch);
366
+ if (!hookError) {
367
+ hookError = await NginxHelper_1.NginxHelper.runHook(found.bluegreen.on_switch);
368
+ }
324
369
  const status = hookError ? "switch-hook-failed" : action === "rollback" ? `rolled-back-to-${target}` : `switched-to-${target}`;
325
370
  if (found.email && found.email.length > 0) {
326
371
  for (const email of found.email) {
@@ -387,6 +432,43 @@ class DeployerService extends BaseService_1.BaseService {
387
432
  }
388
433
  return matched;
389
434
  }
435
+ async _renderNginx(found, projectName, activePort) {
436
+ if (!found.nginx?.config_dir)
437
+ return null;
438
+ if (!activePort) {
439
+ return { code: 52, message: "Cannot render nginx: no PORT in merged config for project " + projectName, httpStatus: 500 };
440
+ }
441
+ const dir = found.nginx.config_dir;
442
+ try {
443
+ fs_1.default.mkdirSync(dir, { recursive: true });
444
+ }
445
+ catch (e) {
446
+ return { code: 50, message: "Cannot create nginx config_dir: " + String(e?.message ?? e), httpStatus: 500 };
447
+ }
448
+ const safeName = projectName.replace(/[^a-zA-Z0-9_]/g, "_");
449
+ const upstreamName = found.nginx.upstream_name || (safeName + "_backend");
450
+ const upstreamPath = path_1.default.resolve(dir, projectName + ".upstream.conf");
451
+ const serverPath = path_1.default.resolve(dir, projectName + ".server.conf");
452
+ const domain = found.nginx.domain || projectName;
453
+ if (!found.nginx.domain) {
454
+ (0, LogService_1.logWarn)("nginx.domain not set for project " + projectName +
455
+ " — seeding server.conf with server_name=" + projectName + " (edit or delete .server.conf to fix)");
456
+ }
457
+ const seedResult = NginxHelper_1.NginxHelper.seedServerConfig(serverPath, NginxHelper_1.DEFAULT_SERVER_TEMPLATE, {
458
+ DOMAIN: domain,
459
+ UPSTREAM: upstreamName,
460
+ });
461
+ if (seedResult.error)
462
+ return seedResult.error;
463
+ if (seedResult.seeded) {
464
+ (0, LogService_1.logWarn)("Seeded nginx server config for " + projectName + " at " + serverPath +
465
+ " — run `certbot --nginx -d " + domain + "` to add SSL");
466
+ }
467
+ const upstreamErr = NginxHelper_1.NginxHelper.writeUpstream(upstreamPath, upstreamName, activePort);
468
+ if (upstreamErr)
469
+ return upstreamErr;
470
+ return null;
471
+ }
390
472
  async runPM2(found, projectState) {
391
473
  let pm2attrs = projectState?.lastDeployed === "blue" ? found.bluegreen?.blue_pm2_attributes : found.bluegreen?.green_pm2_attributes;
392
474
  const pm2Attrs = Array.isArray(pm2attrs) ? pm2attrs.filter(s => typeof s === "string" && s.length > 0) : found.pm2_attributes;
@@ -1,9 +1,10 @@
1
+ import { IError } from "../structures/Interfaces";
2
+ export declare const DEFAULT_SERVER_TEMPLATE = "# Seeded by deployer for _{{DOMAIN}}_. SAFE TO EDIT.\n# Deployer NEVER overwrites this file after the initial seed.\n# Add SSL by running: certbot --nginx -d _{{DOMAIN}}_\n\nserver {\n server_name _{{DOMAIN}}_;\n listen 80;\n\n location / {\n proxy_pass http://_{{UPSTREAM}}_;\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_read_timeout 60s;\n }\n}\n";
1
3
  export declare class NginxHelper {
2
- static applyTemplate(filePath: string, values: Record<string, string>): void;
3
- static swapSymlink(symlinkPath: string, target: string): {
4
- code: number;
5
- message: string;
6
- httpStatus: number;
7
- } | null;
4
+ static writeUpstream(filePath: string, upstreamName: string, port: string): IError | null;
5
+ static seedServerConfig(filePath: string, template: string, values: Record<string, string>): {
6
+ seeded: boolean;
7
+ error?: IError;
8
+ };
8
9
  static runHook(cmd: string[] | undefined | null): Promise<string | null>;
9
10
  }
@@ -3,50 +3,78 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.NginxHelper = void 0;
6
+ exports.NginxHelper = exports.DEFAULT_SERVER_TEMPLATE = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const child_process_1 = require("child_process");
10
10
  const util_1 = require("util");
11
- const crypto_1 = __importDefault(require("crypto"));
12
11
  const LogService_1 = require("../LogService");
13
12
  const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
13
+ exports.DEFAULT_SERVER_TEMPLATE = `# Seeded by deployer for _{{DOMAIN}}_. SAFE TO EDIT.
14
+ # Deployer NEVER overwrites this file after the initial seed.
15
+ # Add SSL by running: certbot --nginx -d _{{DOMAIN}}_
16
+
17
+ server {
18
+ server_name _{{DOMAIN}}_;
19
+ listen 80;
20
+
21
+ location / {
22
+ proxy_pass http://_{{UPSTREAM}}_;
23
+ proxy_http_version 1.1;
24
+ proxy_set_header Host $host;
25
+ proxy_set_header X-Real-IP $remote_addr;
26
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
27
+ proxy_set_header X-Forwarded-Proto $scheme;
28
+ proxy_read_timeout 60s;
29
+ }
30
+ }
31
+ `;
14
32
  class NginxHelper {
15
- static applyTemplate(filePath, values) {
16
- if (!fs_1.default.existsSync(filePath))
17
- throw new Error("nginx config file not found: " + filePath);
18
- const original = fs_1.default.readFileSync(filePath, "utf-8");
19
- const hashBefore = crypto_1.default.createHash("md5").update(original).digest("hex");
20
- let content = original;
21
- for (const [key, value] of Object.entries(values)) {
22
- content = content.replaceAll(`_{{${key}}}_`, value);
23
- }
24
- const hashAfter = crypto_1.default.createHash("md5").update(content).digest("hex");
25
- if (hashBefore !== hashAfter) {
26
- fs_1.default.writeFileSync(filePath, content, "utf-8");
33
+ static writeUpstream(filePath, upstreamName, port) {
34
+ const content = `# Auto-generated by deployer on each activate — do not edit.
35
+ upstream ${upstreamName} {
36
+ server 127.0.0.1:${port};
37
+ }
38
+ `;
39
+ try {
40
+ const tmp = filePath + ".tmp";
41
+ try {
42
+ fs_1.default.unlinkSync(tmp);
43
+ }
44
+ catch { }
45
+ fs_1.default.writeFileSync(tmp, content, "utf-8");
46
+ fs_1.default.renameSync(tmp, filePath);
47
+ return null;
27
48
  }
28
- const leftover = content.match(/_\{\{[A-Za-z0-9_]+\}\}_/g);
29
- if (leftover && leftover.length > 0) {
30
- const unique = [...new Set(leftover)];
31
- (0, LogService_1.logWarn)("Unresolved template placeholders in nginx config " + filePath + ": " + unique.join(", "));
49
+ catch (e) {
50
+ return { code: 48, message: "Failed to write nginx upstream file: " + String(e?.message ?? e), httpStatus: 500 };
32
51
  }
33
52
  }
34
- static swapSymlink(symlinkPath, target) {
35
- if (!fs_1.default.existsSync(target)) {
36
- return { code: 48, message: "nginx symlink target missing: " + target, httpStatus: 500 };
53
+ static seedServerConfig(filePath, template, values) {
54
+ if (fs_1.default.existsSync(filePath)) {
55
+ return { seeded: false };
37
56
  }
38
57
  try {
39
- const tmp = symlinkPath + ".tmp";
58
+ let content = template;
59
+ for (const [key, value] of Object.entries(values)) {
60
+ content = content.replaceAll(`_{{${key}}}_`, value);
61
+ }
62
+ const leftover = content.match(/_\{\{[A-Za-z0-9_]+\}\}_/g);
63
+ if (leftover && leftover.length > 0) {
64
+ const unique = [...new Set(leftover)];
65
+ (0, LogService_1.logWarn)("Unresolved placeholders in seeded nginx server config " + filePath + ": " + unique.join(", "));
66
+ }
67
+ const tmp = filePath + ".tmp";
40
68
  try {
41
69
  fs_1.default.unlinkSync(tmp);
42
70
  }
43
71
  catch { }
44
- fs_1.default.symlinkSync(target, tmp);
45
- fs_1.default.renameSync(tmp, symlinkPath);
46
- return null;
72
+ fs_1.default.writeFileSync(tmp, content, "utf-8");
73
+ fs_1.default.renameSync(tmp, filePath);
74
+ return { seeded: true };
47
75
  }
48
76
  catch (e) {
49
- return { code: 49, message: "nginx symlink swap failed: " + String(e?.message ?? e), httpStatus: 500 };
77
+ return { seeded: false, error: { code: 49, message: "Failed to seed nginx server config: " + String(e?.message ?? e), httpStatus: 500 } };
50
78
  }
51
79
  }
52
80
  static async runHook(cmd) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badmfck-api-server",
3
- "version": "4.1.36",
3
+ "version": "4.1.38",
4
4
  "description": "Simple API http server based on express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",