lupine.api 1.1.52 → 1.1.54
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.
- package/admin/admin-release.tsx +10 -0
- package/package.json +1 -1
- package/src/admin-api/admin-release.ts +7 -0
- package/src/api/debug-service.ts +1 -1
- package/src/api/static-server.ts +1 -1
- package/src/app/app-loader.ts +1 -1
- package/src/app/app-message.ts +12 -3
- package/src/app/app-shared-storage.ts +4 -4
- package/src/app/app-start.ts +11 -12
- package/src/app/cleanup-exit.ts +4 -0
- package/src/app/process-dev-requests.ts +29 -13
- package/src/app/app-restart.ts +0 -54
package/admin/admin-release.tsx
CHANGED
|
@@ -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
|
@@ -421,6 +421,11 @@ export class AdminRelease implements IApiBase {
|
|
|
421
421
|
ApiHelper.sendJson(req, res, result);
|
|
422
422
|
return true;
|
|
423
423
|
}
|
|
424
|
+
const result2 = await this.updateSendFile(data, 'app-loader');
|
|
425
|
+
if (!result2 || result2.status !== 'ok') {
|
|
426
|
+
ApiHelper.sendJson(req, res, result2);
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
424
429
|
}
|
|
425
430
|
|
|
426
431
|
const response = {
|
|
@@ -442,6 +447,8 @@ export class AdminRelease implements IApiBase {
|
|
|
442
447
|
let sendFile = '';
|
|
443
448
|
if (chkOption === 'server') {
|
|
444
449
|
sendFile = path.join(appData.apiPath, '..', 'server', 'index.js');
|
|
450
|
+
} else if (chkOption === 'app-loader') {
|
|
451
|
+
sendFile = path.join(appData.apiPath, '..', 'server', 'app-loader.js');
|
|
445
452
|
} else if (chkOption === 'api') {
|
|
446
453
|
sendFile = path.join(appData.apiPath, '..', fromList + '_api', 'index.js');
|
|
447
454
|
} else if (chkOption === 'web') {
|
package/src/api/debug-service.ts
CHANGED
|
@@ -48,7 +48,7 @@ export class DebugService {
|
|
|
48
48
|
|
|
49
49
|
// broadcast to all frontend clients
|
|
50
50
|
public static broadcastRefresh() {
|
|
51
|
-
console.log(
|
|
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));
|
package/src/api/static-server.ts
CHANGED
|
@@ -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(
|
|
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]}`);
|
package/src/app/app-loader.ts
CHANGED
|
@@ -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(
|
|
54
|
+
console.log(`${process.pid} - clear cache: ${path}`);
|
|
55
55
|
delete require.cache[path];
|
|
56
56
|
}
|
|
57
57
|
}
|
package/src/app/app-message.ts
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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];
|
package/src/app/app-start.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
`${
|
|
45
|
-
|
|
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(
|
|
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(
|
|
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 (!
|
|
77
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
|
package/src/app/cleanup-exit.ts
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
75
|
+
snedRestartAppMsgToLoader();
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
// this is
|
|
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(
|
|
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(
|
|
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
|
+
};
|
package/src/app/app-restart.ts
DELETED
|
@@ -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
|
-
};
|