lupine.api 1.1.52 → 1.1.53

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.
@@ -8,6 +8,7 @@ import {
8
8
  NotificationMessage,
9
9
  formatBytes,
10
10
  downloadStream,
11
+ ActionSheetSelectPromise,
11
12
  } from 'lupine.components';
12
13
 
13
14
  interface ReleaseListProps {
@@ -308,6 +309,15 @@ export const AdminReleasePage = () => {
308
309
  }
309
310
  }
310
311
 
312
+ const index = await ActionSheetSelectPromise({
313
+ title: 'Restart App (users may get disconnected errors) ?',
314
+ options: ['OK'],
315
+ cancelButtonText: 'Cancel',
316
+ });
317
+ if (index !== 0) {
318
+ return;
319
+ }
320
+
311
321
  const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/restart-app', {
312
322
  ...data,
313
323
  isLocal,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lupine.api",
3
- "version": "1.1.52",
3
+ "version": "1.1.53",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -48,7 +48,7 @@ export class DebugService {
48
48
 
49
49
  // broadcast to all frontend clients
50
50
  public static broadcastRefresh() {
51
- console.log(`broadcast refresh request to clients.`);
51
+ console.log(`${process.pid} - broadcast refresh request to clients.`);
52
52
  this.clientRefreshFlag = Date.now();
53
53
  const msg = { message: 'Refresh', flag: this.clientRefreshFlag };
54
54
  this.miniWebSocket.broadcast(JSON.stringify(msg));
@@ -71,7 +71,7 @@ export class StaticServer {
71
71
 
72
72
  // if fullPath doesn't exist, it will throw ENOENT error
73
73
  const realPath = await fs.promises.realpath(fullPath);
74
- console.log(`request: ${realPath}`);
74
+ console.log(`${process.pid} - request: ${realPath}`);
75
75
  // for security reason, the requested file should be inside of wwwRoot
76
76
  if (realPath.substring(0, hostPath.webPath.length) !== hostPath.webPath) {
77
77
  this.logger.warn(`ACCESS DENIED: ${urlSplit[0]}`);
@@ -51,7 +51,7 @@ class AppLoader {
51
51
  const apiPath = path.join(process.cwd(), 'dist/server_root', appConfig.appName + '_api/index.js');
52
52
  for (const path in require.cache) {
53
53
  if (path.endsWith('.js') && path.indexOf(apiPath) <= 0) {
54
- console.log(`clear cache: ${path}`);
54
+ console.log(`${process.pid} - clear cache: ${path}`);
55
55
  delete require.cache[path];
56
56
  }
57
57
  }
@@ -1,8 +1,7 @@
1
1
  import cluster from 'cluster';
2
2
  import { Logger, LogWriter, LogWriterMessageId } from '../lib';
3
- import { processDebugMessage } from './process-dev-requests';
3
+ import { processDebugMessage, snedRestartAppMsgToLoader } from './process-dev-requests';
4
4
  import { cleanupAndExit } from './cleanup-exit';
5
- import { restartApp } from './app-restart';
6
5
 
7
6
  export type AppMessageProps = {
8
7
  id: string;
@@ -80,7 +79,7 @@ export const processMessageFromWorker = (msgObject: AppMessageProps) => {
80
79
  `Message from worker ${cluster.worker?.id}, message: ${msgObject.message}, appName: ${msgObject.appName}`
81
80
  );
82
81
  if (msgObject.message === 'restartApp') {
83
- restartApp();
82
+ snedRestartAppMsgToLoader();
84
83
  return;
85
84
  } else if (msgObject.message === 'refresh') {
86
85
  broadcast(msgObject);
@@ -98,3 +97,13 @@ export const processMessageFromWorker = (msgObject: AppMessageProps) => {
98
97
  logger.warn(`Unknown message id: ${msgObject.id}`);
99
98
  }
100
99
  };
100
+
101
+ // this is primary, and receive messages from loader
102
+ export const receiveMessageFromLoader = () => {
103
+ process.on('message', async (msg: AppMessageProps) => {
104
+ if (msg?.id === 'debug' && msg?.message === 'shutdown') {
105
+ console.log(`App ${process.pid}: received shutdown message from loader.`);
106
+ processMessageFromWorker(msg);
107
+ }
108
+ });
109
+ };
@@ -166,7 +166,7 @@ export class AppSharedStorage implements IAppSharedStorage {
166
166
  // this can be called in primary or worker
167
167
  get(appName: string, key: string): Promise<string> {
168
168
  return new Promise((resolve, reject) => {
169
- console.log(`AppStorage get value start in ${process.pid}, for key: ${key}`);
169
+ console.log(`${process.pid} - AppStorage get value for key: ${key}`);
170
170
 
171
171
  if (!cluster.isPrimary) {
172
172
  AppSharedStorageWorker.get(appName, key, resolve, reject);
@@ -196,7 +196,7 @@ export class AppSharedStorage implements IAppSharedStorage {
196
196
  }
197
197
  getWithPrefix(appName: string, prefixKey: string): Promise<SimpleStorageDataProps> {
198
198
  return new Promise((resolve, reject) => {
199
- console.log(`AppStorage getWithPrefix start in ${process.pid}, for prefixKey: ${prefixKey}`);
199
+ console.log(`${process.pid} - AppStorage getWithPrefix for prefixKey: ${prefixKey}`);
200
200
 
201
201
  if (!cluster.isPrimary) {
202
202
  AppSharedStorageWorker.getWithPrefix(appName, prefixKey, resolve, reject);
@@ -238,7 +238,7 @@ class AppSharedStorageWorker {
238
238
  }
239
239
 
240
240
  if (msgObject.action === 'get') {
241
- console.log(`AppStorage get value end in process: ${process.pid}, for key: ${msgObject.key}`);
241
+ console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
242
242
 
243
243
  const value = msgObject.value;
244
244
  // how to pass the value to the caller
@@ -250,7 +250,7 @@ class AppSharedStorageWorker {
250
250
  throw new Error(`Unknown uniqueKey: ${msgObject.uniqueKey}`);
251
251
  }
252
252
  } else if (msgObject.action === 'getWithPrefix') {
253
- console.log(`AppStorage get value end in process: ${process.pid}, for key: ${msgObject.key}`);
253
+ console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
254
254
  const value = JSON.parse(msgObject.value);
255
255
  // how to pass the value to the caller
256
256
  const map = this.handleMap[msgObject.uniqueKey];
@@ -8,8 +8,8 @@ import { appCache } from './app-cache';
8
8
  import { AppStartProps, InitStartProps, AppCacheGlobal, AppCacheKeys } from '../models';
9
9
  import { appStorage } from './app-shared-storage';
10
10
  import { HostToPath } from './host-to-path';
11
- import { cleanupAndExit } from './cleanup-exit';
12
- import { _restartApp, receiveMessageFromLoader } from './app-restart';
11
+ import { _exitApp, cleanupAndExit } from './cleanup-exit';
12
+ import { receiveMessageFromLoader } from './app-message';
13
13
 
14
14
  // Don't use logger before set process message
15
15
  class AppStart {
@@ -41,9 +41,9 @@ class AppStart {
41
41
 
42
42
  // call the Logger after initLog
43
43
  console.log(
44
- `${cluster.isPrimary ? 'Primary Process' : 'Worker Process'}, Starting Server - process id ${
45
- process.pid
46
- }, path: ${process.cwd()}`
44
+ `${process.pid} - ${
45
+ cluster.isPrimary ? 'Primary Process' : 'Worker Process'
46
+ }, Starting Server, path: ${process.cwd()}`
47
47
  );
48
48
 
49
49
  // when it's cluster.isPrimary or props.debug, initialize the shared storage first
@@ -54,7 +54,7 @@ class AppStart {
54
54
  }
55
55
 
56
56
  if (!cluster.isPrimary) {
57
- console.log(`Worker id ${this.getWorkerId()}`);
57
+ console.log(`${process.pid} - Worker id ${this.getWorkerId()}`);
58
58
 
59
59
  process.on('message', processMessageFromPrimary);
60
60
 
@@ -63,7 +63,7 @@ class AppStart {
63
63
  this.initServer(props.serverConfig);
64
64
  } else if (cluster.isPrimary) {
65
65
  const numCPUs = props.debug ? 1 : require('os').cpus().length;
66
- console.log(`Master Process is trying to fork ${numCPUs} processes`);
66
+ console.log(`${process.pid} - Primary Process is trying to fork ${numCPUs} processes`);
67
67
 
68
68
  receiveMessageFromLoader();
69
69
 
@@ -73,11 +73,11 @@ class AppStart {
73
73
  }
74
74
 
75
75
  cluster.on('death', (worker: any) => {
76
- if (!_restartApp.isRestarting) {
77
- console.log(`Worker ${worker.pid} died; starting a new one...`);
76
+ if (!_exitApp.isExiting) {
77
+ console.log(`${worker.pid} - Worker died; starting a new one...`);
78
78
  cluster.fork();
79
79
  } else {
80
- console.log(`Worker ${worker.pid} exited during restart`);
80
+ console.log(`${worker.pid} - Worker exited during restart`);
81
81
  }
82
82
  });
83
83
  }
@@ -125,12 +125,11 @@ class AppStart {
125
125
  const httpServer = httpPort && this.webServer!.startHttp(httpPort, bindIp);
126
126
  const heepsServer = httpsPort && this.webServer!.startHttps(httpsPort, bindIp, sslKeyPath, sslCrtPath);
127
127
 
128
- process.on("SIGTERM", () => {
128
+ process.on('SIGTERM', () => {
129
129
  console.log(`${process.pid} - Worker closing servers...`);
130
130
  httpServer && httpServer.close();
131
131
  heepsServer && heepsServer.close();
132
132
  });
133
-
134
133
  }
135
134
  }
136
135
 
@@ -1,6 +1,9 @@
1
1
  import cluster from 'cluster';
2
2
  import { appStorage } from './app-shared-storage';
3
3
 
4
+ export const _exitApp = {
5
+ isExiting: false,
6
+ };
4
7
  export const cleanupAndExit = async () => {
5
8
  console.log(`${process.pid} - Process on SIGINT, exit.`);
6
9
  // save shared storage first
@@ -8,5 +11,6 @@ export const cleanupAndExit = async () => {
8
11
  // save only happens once
9
12
  await appStorage.save('', true);
10
13
  }
14
+ _exitApp.isExiting = true;
11
15
  process.exit(0);
12
16
  };
@@ -1,3 +1,4 @@
1
+ import cluster from 'cluster';
1
2
  import { Logger } from '../lib/logger';
2
3
  import { ServerResponse } from 'http';
3
4
  import { AddressInfo } from 'net';
@@ -5,7 +6,6 @@ import { appLoader } from './app-loader';
5
6
  import { DebugService } from '../api/debug-service';
6
7
  import { AppCacheGlobal, AppCacheKeys, getAppCache, ServerRequest } from '../models';
7
8
  import { cleanupAndExit } from './cleanup-exit';
8
- import { restartApp } from './app-restart';
9
9
  const logger = new Logger('process-dev-requests');
10
10
 
11
11
  function deleteRequireCache(moduleName: string) {
@@ -45,18 +45,18 @@ export const processDebugMessage = async (msgObject: any) => {
45
45
  }
46
46
  if (msgObject.id === 'debug' && msgObject.message === 'shutdown') {
47
47
  // Only when it's debug mode, it can go here, otherwise shutdown should be processed in processMessageFromWorker
48
- console.log(`[server] Received shutdown command.`);
48
+ console.log(`${process.pid} - [server] Received shutdown command.`);
49
49
  await cleanupAndExit();
50
50
  }
51
51
  };
52
52
 
53
53
  export async function processRefreshCache(req: ServerRequest) {
54
- // if this is a child process, we need to notice parent process to broadcast to all clients to refresh
55
- if (process.send) {
54
+ // if this is a child process, we need to notice parent process to broadcast to all clients
55
+ if (!cluster.isPrimary && process.send) {
56
56
  const appName = req.locals.query.get('appName');
57
57
  process.send({ id: 'debug', message: 'refresh', appName });
58
58
  }
59
- // if it's debug mode (only one process)
59
+ // in case if it's only one process (primary process)
60
60
  else {
61
61
  // if (getAppCache().get(APP_GLOBAL, AppCacheKeys.API_DEBUG) === true)
62
62
  const appName = req.locals.query.get('appName');
@@ -65,27 +65,27 @@ export async function processRefreshCache(req: ServerRequest) {
65
65
  }
66
66
 
67
67
  export async function processRestartApp(req: ServerRequest) {
68
- // if this is a child process, we need to notice parent process to broadcast to all clients to refresh
69
- if (process.send) {
68
+ // if this is a child process, we need to notice parent process to broadcast to all clients
69
+ if (!cluster.isPrimary && process.send) {
70
70
  // send message to Primary to handle it
71
71
  process.send({ id: 'debug', message: 'restartApp' });
72
72
  }
73
73
  // in case if it's only one process (primary process)
74
74
  else {
75
- await restartApp();
75
+ snedRestartAppMsgToLoader();
76
76
  }
77
77
  }
78
78
 
79
- // this is only for local development
79
+ // this is called from a request in debug mode
80
80
  export async function processDevRequests(req: ServerRequest, res: ServerResponse, rootUrl?: string) {
81
81
  res.end();
82
82
  const address = req.socket.address() as AddressInfo;
83
83
  if (address.address !== '127.0.0.1') {
84
- console.log(`[server] Ignore request from: `, req.url, address.address);
84
+ console.log(`${process.pid} - [server] Ignore request from: `, req.url, address.address);
85
85
  return true;
86
86
  }
87
87
  if (req.url === '/debug/shutdown') {
88
- console.log(`[server] Received shutdown command.`);
88
+ console.log(`${process.pid} - [server] Received shutdown command.`);
89
89
  if (process.send) {
90
90
  // send to parent process to kill all
91
91
  process.send({ id: 'debug', message: 'shutdown' });
@@ -97,7 +97,23 @@ export async function processDevRequests(req: ServerRequest, res: ServerResponse
97
97
  } else if (req.url === '/debug/refresh') {
98
98
  await processRefreshCache(req);
99
99
  }
100
- if (req.url === '/debug/client') {
101
- }
100
+ // else if (req.url === '/debug/client') {
101
+ // }
102
102
  return true;
103
103
  }
104
+
105
+ // this is called from a request and passes the restartApp message to loader
106
+ export const snedRestartAppMsgToLoader = async () => {
107
+ if (!cluster.isPrimary) {
108
+ console.warn(`restartApp: shouldn't come here`);
109
+ return;
110
+ }
111
+
112
+ if (!process.send) {
113
+ console.log(`${process.pid} - The primary process is not focked from loader, so cannot restart.`);
114
+ } else {
115
+ console.log(`${process.pid} - Old app sends restartApp to loader (${process.execPath})`);
116
+
117
+ process.send({ id: 'debug', message: 'restartApp' });
118
+ }
119
+ };
@@ -1,54 +0,0 @@
1
- import cluster from 'cluster';
2
- import { spawn } from 'child_process';
3
- import { appStorage } from './app-shared-storage';
4
- import { AppMessageProps, processMessageFromWorker } from './app-message';
5
-
6
- export const _restartApp = {
7
- isRestarting: false,
8
- };
9
- export const restartApp = async () => {
10
- if (!cluster.isPrimary) {
11
- console.warn(`restartApp: shouldn't come here`);
12
- return;
13
- }
14
-
15
- await appStorage.save('', true);
16
- console.log(`Old app ${process.pid} sends SIGTERM to all workors.`);
17
- // const closeAll = () => {
18
- // _restartApp.isRestarting = true;
19
-
20
- // return Object.values(cluster.workers!).map(
21
- // (w) =>
22
- // new Promise((resolve) => {
23
- // console.log(`Sending SIGTERM to workor ${w!.id}.`);
24
- // w!.once('exit', resolve);
25
- // w!.kill('SIGTERM');
26
- // })
27
- // );
28
- // };
29
- // await Promise.all(closeAll());
30
-
31
- console.log(`Old app ${process.pid} starts new app ${process.execPath}`, process.argv);
32
- // spawn(process.execPath, process.argv.slice(1), {
33
- // stdio: 'inherit',
34
- // env: { ...process.env, RESTARTING: '1' },
35
- // });
36
-
37
- console.log(`Old app ${process.pid} exists.`);
38
- // setTimeout(process.exit, 3000);
39
- if (!process.send) {
40
- console.log(`The primary process is not focked from loader, so cannot restart.`);
41
- } else {
42
- process.send({ id: 'debug', message: 'restartApp' });
43
- }
44
- };
45
-
46
- // this is primary, and receive messages from loader
47
- export const receiveMessageFromLoader = () => {
48
- process.on('message', async (msg: AppMessageProps) => {
49
- if (msg?.id === 'debug' && msg?.message === 'shutdown') {
50
- console.log(`App ${process.pid}: received shutdown message from loader.`);
51
- processMessageFromWorker(msg);
52
- }
53
- });
54
- };