@blocklet/cli 1.16.33 → 1.16.34-beta-20241120-080738-bbbe036c
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/README.md +32 -25
- package/bin/blocklet.js +292 -1
- package/config.example.yml +33 -0
- package/lib/arcblock.js +53 -0
- package/lib/commands/blocklet/add.js +124 -0
- package/lib/commands/blocklet/assets/git-ignore +28 -0
- package/lib/commands/blocklet/assets/index.html +9 -0
- package/lib/commands/blocklet/assets/index.js +14 -0
- package/lib/commands/blocklet/assets/logo.png +0 -0
- package/lib/commands/blocklet/bundle/bundle.js +184 -0
- package/lib/commands/blocklet/bundle/bundlers/blocklet.js +138 -0
- package/lib/commands/blocklet/bundle/bundlers/changelog.js +100 -0
- package/lib/commands/blocklet/bundle/bundlers/logo.js +56 -0
- package/lib/commands/blocklet/bundle/bundlers/markdown.js +241 -0
- package/lib/commands/blocklet/bundle/bundlers/preference.js +50 -0
- package/lib/commands/blocklet/bundle/bundlers/readme.js +43 -0
- package/lib/commands/blocklet/bundle/bundlers/screenshots.js +94 -0
- package/lib/commands/blocklet/bundle/bundlers/simple.js +70 -0
- package/lib/commands/blocklet/bundle/compact/bundle-compact-file.js +48 -0
- package/lib/commands/blocklet/bundle/compact/bundle-merge-extra.js +66 -0
- package/lib/commands/blocklet/bundle/compact/default-external.js +5 -0
- package/lib/commands/blocklet/bundle/compact/index.js +88 -0
- package/lib/commands/blocklet/bundle/index.js +139 -0
- package/lib/commands/blocklet/bundle/pack.js +8 -0
- package/lib/commands/blocklet/bundle/parse-external-dependencies.js +97 -0
- package/lib/commands/blocklet/bundle/simple/index.js +62 -0
- package/lib/commands/blocklet/bundle/zip/archive.js +35 -0
- package/lib/commands/blocklet/bundle/zip/dependencies.js +333 -0
- package/lib/commands/blocklet/bundle/zip/index.js +165 -0
- package/lib/commands/blocklet/bundle/zip/main.js +124 -0
- package/lib/commands/blocklet/bundle/zip/node.js +59 -0
- package/lib/commands/blocklet/bundle/zip/resolve.js +93 -0
- package/lib/commands/blocklet/cleanup.js +52 -0
- package/lib/commands/blocklet/config.js +108 -0
- package/lib/commands/blocklet/connect.js +87 -0
- package/lib/commands/blocklet/create.js +38 -0
- package/lib/commands/blocklet/deploy.js +435 -0
- package/lib/commands/blocklet/dev.js +1000 -0
- package/lib/commands/blocklet/document.js +39 -0
- package/lib/commands/blocklet/exec.js +106 -0
- package/lib/commands/blocklet/init.js +300 -0
- package/lib/commands/blocklet/meta.js +22 -0
- package/lib/commands/blocklet/remove.js +35 -0
- package/lib/commands/blocklet/test.js +201 -0
- package/lib/commands/blocklet/upload.js +105 -0
- package/lib/commands/blocklet/version.js +81 -0
- package/lib/commands/server/cleanup.js +32 -0
- package/lib/commands/server/command.js +131 -0
- package/lib/commands/server/info.js +92 -0
- package/lib/commands/server/init.js +433 -0
- package/lib/commands/server/logs.js +99 -0
- package/lib/commands/server/rescue.js +71 -0
- package/lib/commands/server/start.js +821 -0
- package/lib/commands/server/status.js +107 -0
- package/lib/commands/server/stop.js +163 -0
- package/lib/commands/server/upgrade.js +123 -0
- package/lib/constant.js +21 -2
- package/lib/debug.js +20 -0
- package/lib/manager/config.js +122 -0
- package/lib/manager/deploy.js +75 -0
- package/lib/manager/index.js +23 -0
- package/lib/manager/process.js +47 -0
- package/lib/node.js +214 -0
- package/lib/port.js +19 -0
- package/lib/postinstall.js +3 -0
- package/lib/process/daemon.js +196 -0
- package/lib/process/service.js +86 -0
- package/lib/ui.js +137 -0
- package/lib/util/blocklet/config.js +78 -0
- package/lib/util/blocklet/env.js +172 -0
- package/lib/util/blocklet/meta.js +36 -0
- package/lib/util/blocklet/payment.js +88 -0
- package/lib/util/blocklet/sign.js +21 -0
- package/lib/util/blocklet/tar.js +119 -0
- package/lib/util/convert-to-nosources-sourcemap.js +37 -0
- package/lib/util/docker-status-log.js +17 -0
- package/lib/util/exit-when-server-stopped.js +44 -0
- package/lib/util/get-cli-binary-name.js +8 -0
- package/lib/util/get-download-bundle-step.js +36 -0
- package/lib/util/get-service-instance-number.js +12 -0
- package/lib/util/index.js +626 -0
- package/lib/util/print-error.js +11 -0
- package/lib/util/print.js +9 -0
- package/lib/util/what-uri.js +40 -0
- package/package.json +123 -27
- package/lib/run.d.ts +0 -2
- package/lib/run.js +0 -73
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const pm2 = require('@abtnode/util/lib/async-pm2');
|
|
3
|
+
|
|
4
|
+
const { PROCESS_NAME_DAEMON } = require('@abtnode/constant');
|
|
5
|
+
|
|
6
|
+
const getPm2ProcessInfo = (name) =>
|
|
7
|
+
new Promise((resolve, reject) => {
|
|
8
|
+
pm2.describe(name, (err, [info]) => {
|
|
9
|
+
if (err) {
|
|
10
|
+
return reject(err);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return resolve(info);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const isRunning = async (name) => {
|
|
18
|
+
const info = await getPm2ProcessInfo(name);
|
|
19
|
+
return !!info;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const checkRunning = async () => {
|
|
23
|
+
const isDaemonRunning = await isRunning(PROCESS_NAME_DAEMON);
|
|
24
|
+
return isDaemonRunning === true;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getRunningConfigFile = async () => {
|
|
28
|
+
if (!(await isRunning(PROCESS_NAME_DAEMON))) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const instanceInfo = await getPm2ProcessInfo(PROCESS_NAME_DAEMON);
|
|
33
|
+
const { ABT_NODE_CONFIG_FILE: runningInstanceConfigFile = '' } = instanceInfo.pm2_env.env;
|
|
34
|
+
|
|
35
|
+
return runningInstanceConfigFile;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const getRunningConfigDataDir = async () => {
|
|
39
|
+
const configFile = await getRunningConfigFile();
|
|
40
|
+
if (configFile) {
|
|
41
|
+
return path.dirname(configFile);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return '';
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
module.exports = { checkRunning, getPm2ProcessInfo, getRunningConfigFile, getRunningConfigDataDir };
|
package/lib/node.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const yaml = require('js-yaml');
|
|
4
|
+
const ABTNode = require('@abtnode/core');
|
|
5
|
+
const security = require('@abtnode/util/lib/security');
|
|
6
|
+
const { WsClient } = require('@arcblock/ws');
|
|
7
|
+
const { isFromPublicKey } = require('@arcblock/did');
|
|
8
|
+
const { getBaseUrls } = require('@abtnode/core/lib/util');
|
|
9
|
+
|
|
10
|
+
const debug = require('./debug')('node');
|
|
11
|
+
const { version } = require('../package.json');
|
|
12
|
+
|
|
13
|
+
const util = require('./util');
|
|
14
|
+
const {
|
|
15
|
+
getDataDirectoryByConfigFile,
|
|
16
|
+
getConfigFile,
|
|
17
|
+
joinKFile,
|
|
18
|
+
getConfigDir,
|
|
19
|
+
verifyDataDirectory,
|
|
20
|
+
} = require('./manager');
|
|
21
|
+
|
|
22
|
+
const { print, printError, printInfo } = util;
|
|
23
|
+
|
|
24
|
+
const isHex = (str) => /^0x[0-9a-f]+$/i.test(str);
|
|
25
|
+
|
|
26
|
+
const getOwner = (info) => {
|
|
27
|
+
if (info && info.pk && info.did && isFromPublicKey(info.did, info.pk)) {
|
|
28
|
+
return info;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const printConfigError = () => {
|
|
35
|
+
printError('Cannot find config file for Blocklet Server');
|
|
36
|
+
printInfo(`Please initialize a configuration directory first: run ${chalk.cyan('blocklet server init')}`);
|
|
37
|
+
print();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let instance = null;
|
|
41
|
+
let client = null;
|
|
42
|
+
|
|
43
|
+
const readNodeConfig = async (dir) => {
|
|
44
|
+
const configDir = await getConfigDir(dir);
|
|
45
|
+
if (fs.existsSync(configDir)) {
|
|
46
|
+
try {
|
|
47
|
+
verifyDataDirectory(configDir);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
debug('validate data directory failed:', error);
|
|
50
|
+
printError('Validate data directory failed:', error.message);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
printConfigError();
|
|
55
|
+
process.exit(1); // TODO: 太讨厌这种提前 exit 的函数了,非常不好调试, polunzh
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const configFile = await getConfigFile(dir);
|
|
59
|
+
|
|
60
|
+
let config = {};
|
|
61
|
+
try {
|
|
62
|
+
config = yaml.load(fs.readFileSync(configFile).toString(), { json: true });
|
|
63
|
+
const ukFile = joinKFile(configDir);
|
|
64
|
+
if (fs.existsSync(ukFile) && isHex(config.node.sk) === false) {
|
|
65
|
+
config.node.sk = security.decrypt(config.node.sk, config.node.did, fs.readFileSync(ukFile));
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
debug(err);
|
|
69
|
+
printError('Blocklet Server config parse failed', err.message);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { config, configFile, dataDir: getDataDirectoryByConfigFile(configFile) };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const readNodeConfigWithValidate = async (dir) => {
|
|
77
|
+
const { config, configFile, dataDir } = await readNodeConfig(dir);
|
|
78
|
+
|
|
79
|
+
// validate node config
|
|
80
|
+
const wallet = util.getWallet(config.node.sk);
|
|
81
|
+
if (wallet.address !== config.node.did) {
|
|
82
|
+
printError('config.node.sk and config.node.did does not match');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!config.node.port) {
|
|
87
|
+
printError('config.node.port must be specified');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
printInfo('Server DID', chalk.cyan(config.node.did));
|
|
92
|
+
|
|
93
|
+
return { config, configFile, dataDir };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
async function getNode({ dir = process.cwd() } = {}) {
|
|
97
|
+
const { config, configFile, dataDir } = await readNodeConfigWithValidate(dir);
|
|
98
|
+
|
|
99
|
+
util.ensurePermission(dataDir);
|
|
100
|
+
|
|
101
|
+
printInfo('Server config from', chalk.cyan(configFile));
|
|
102
|
+
|
|
103
|
+
const wallet = util.getWallet(config.node.sk);
|
|
104
|
+
const node = ABTNode({
|
|
105
|
+
version,
|
|
106
|
+
nodeSk: wallet.secretKey,
|
|
107
|
+
nodePk: wallet.publicKey,
|
|
108
|
+
nodeDid: wallet.address,
|
|
109
|
+
port: config.node.port,
|
|
110
|
+
dataDir,
|
|
111
|
+
nodeOwner: getOwner(config.node.owner),
|
|
112
|
+
name: config.node.name,
|
|
113
|
+
description: config.node.description,
|
|
114
|
+
blockletPort: config.blocklet.port,
|
|
115
|
+
routing: config.node.routing,
|
|
116
|
+
docker: config.node.docker,
|
|
117
|
+
mode: config.node.mode,
|
|
118
|
+
runtimeConfig: config.node.runtimeConfig,
|
|
119
|
+
ownerNft: config.node.ownerNft || { holder: '', issuer: '' },
|
|
120
|
+
launcher: config.node.launcher,
|
|
121
|
+
didRegistry: config.node.didRegistry,
|
|
122
|
+
didDomain: config.node.didDomain,
|
|
123
|
+
slpDomain: config.node.slpDomain,
|
|
124
|
+
enablePassportIssuance: config.node.enablePassportIssuance,
|
|
125
|
+
trustedPassports: config.node.trustedPassports,
|
|
126
|
+
webWalletUrl: config.node.webWalletUrl,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
node,
|
|
131
|
+
config,
|
|
132
|
+
dataDir,
|
|
133
|
+
configFile,
|
|
134
|
+
wallet,
|
|
135
|
+
getBaseUrls: (ips) => getBaseUrls(node, ips),
|
|
136
|
+
publishEvent: (event, data) => publishEvent(node, event, data),
|
|
137
|
+
getWsClient: () => getWsClient(node),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function getWsClient(node) {
|
|
142
|
+
if (!client) {
|
|
143
|
+
const info = await node.getNodeInfo();
|
|
144
|
+
const endpoint = `ws://127.0.0.1:${info.port}`;
|
|
145
|
+
const wallet = util.getWallet(info.sk);
|
|
146
|
+
const timestamp = Date.now().toString();
|
|
147
|
+
|
|
148
|
+
client = new WsClient(endpoint, {
|
|
149
|
+
silent: true,
|
|
150
|
+
params: () => ({
|
|
151
|
+
token: wallet.sign(timestamp),
|
|
152
|
+
timestamp,
|
|
153
|
+
}),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
process.on('exit', () => {
|
|
157
|
+
client.disconnect();
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (client.isConnected()) {
|
|
162
|
+
return client;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
client.onOpen(() => {
|
|
167
|
+
resolve(client);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
client.onError((err) => {
|
|
171
|
+
reject(new Error(`Failed to connect to daemon socket server: ${err.message}`));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
client.connect();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function publishEvent(node, event, data) {
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
getWsClient(node)
|
|
181
|
+
.then((c) => {
|
|
182
|
+
const channel = c.channel('cli');
|
|
183
|
+
channel
|
|
184
|
+
.join()
|
|
185
|
+
.receive('ok', () => {
|
|
186
|
+
channel.push(event, data);
|
|
187
|
+
channel.leave();
|
|
188
|
+
resolve(true);
|
|
189
|
+
})
|
|
190
|
+
.receive('error', (err) => {
|
|
191
|
+
reject(new Error(`join channel error: ${err.message}`));
|
|
192
|
+
})
|
|
193
|
+
.receive('timeout', () => {
|
|
194
|
+
reject(new Error('join channel timeout: cli'));
|
|
195
|
+
});
|
|
196
|
+
})
|
|
197
|
+
.catch((err) => reject(err));
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
getNode: async (...args) => {
|
|
203
|
+
if (instance) {
|
|
204
|
+
return instance;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
instance = await getNode(...args);
|
|
208
|
+
return instance;
|
|
209
|
+
},
|
|
210
|
+
readNodeConfig,
|
|
211
|
+
readNodeConfigWithValidate,
|
|
212
|
+
publishEvent,
|
|
213
|
+
getWsClient,
|
|
214
|
+
};
|
package/lib/port.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const {
|
|
2
|
+
PROCESS_NAME_PROXY,
|
|
3
|
+
PROCESS_NAME_UPDATER,
|
|
4
|
+
PROCESS_NAME_SERVICE,
|
|
5
|
+
PROCESS_NAME_EVENT_HUB,
|
|
6
|
+
} = require('@abtnode/constant');
|
|
7
|
+
|
|
8
|
+
const ports = {
|
|
9
|
+
[PROCESS_NAME_PROXY]: 40404,
|
|
10
|
+
[PROCESS_NAME_UPDATER]: 40405,
|
|
11
|
+
[PROCESS_NAME_SERVICE]: 40406,
|
|
12
|
+
[PROCESS_NAME_EVENT_HUB]: 40407,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getInternalPort = (processName) => {
|
|
16
|
+
return ports[processName];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = { getInternalPort };
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/* eslint-disable no-await-in-loop */
|
|
3
|
+
if (!process.env.NODE_ENV) {
|
|
4
|
+
process.env.NODE_ENV = 'production';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
process.env.ABT_NODE_LOG_NAME = 'daemon';
|
|
11
|
+
process.env.ABT_NODE_LOG_DIR = path.join(process.env.ABT_NODE_DATA_DIR, 'logs', '_abtnode'); // enable log
|
|
12
|
+
|
|
13
|
+
const runningBefore = !!process.env.BLOCKLET_SERVER_RUNNING_BEFORE;
|
|
14
|
+
|
|
15
|
+
const logger = require('@abtnode/logger')('daemon');
|
|
16
|
+
const ABTNode = require('@abtnode/core');
|
|
17
|
+
const AsyncQueue = require('async/queue');
|
|
18
|
+
const formatContext = require('@abtnode/util/lib/format-context');
|
|
19
|
+
const getNodeWallet = require('@abtnode/util/lib/get-app-wallet');
|
|
20
|
+
const { BlockletStatus } = require('@blocklet/constant');
|
|
21
|
+
const { getBlockletEngine } = require('@blocklet/meta/lib/engine');
|
|
22
|
+
const { findComponentV2, forEachComponentV2, hasStartEngine, getDisplayName } = require('@blocklet/meta/lib/util');
|
|
23
|
+
const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-id');
|
|
24
|
+
const getPm2ProcessInfo = require('@abtnode/util/lib/get-pm2-process-info');
|
|
25
|
+
const { SERVER_STATUS } = require('@abtnode/constant');
|
|
26
|
+
|
|
27
|
+
const createServer = require('@abtnode/webapp/blocklet'); // eslint-disable-line
|
|
28
|
+
|
|
29
|
+
const wallet = getNodeWallet(process.env.ABT_NODE_SK);
|
|
30
|
+
const dataDir = process.env.ABT_NODE_DATA_DIR;
|
|
31
|
+
|
|
32
|
+
const node = ABTNode({
|
|
33
|
+
nodeSk: wallet.secretKey,
|
|
34
|
+
nodePk: wallet.publicKey,
|
|
35
|
+
nodeDid: wallet.address,
|
|
36
|
+
port: process.env.ABT_NODE_PORT,
|
|
37
|
+
dataDir,
|
|
38
|
+
blockletPort: process.env.ABT_NODE_BLOCKLET_PORT,
|
|
39
|
+
daemon: true,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
node.collectorPath = path.dirname(require.resolve('@blocklet/form-collector'));
|
|
43
|
+
|
|
44
|
+
const createAuditLog = (action, args, result) => {
|
|
45
|
+
return node
|
|
46
|
+
.createAuditLog(
|
|
47
|
+
{
|
|
48
|
+
action,
|
|
49
|
+
args,
|
|
50
|
+
context: formatContext({
|
|
51
|
+
user: { fullName: 'CLI', role: 'admin', did: wallet.address },
|
|
52
|
+
headers: { 'user-agent': 'CLI' },
|
|
53
|
+
}),
|
|
54
|
+
result,
|
|
55
|
+
},
|
|
56
|
+
node
|
|
57
|
+
)
|
|
58
|
+
.catch(console.error);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const getComponentDids = async ({ app }) => {
|
|
62
|
+
const processNotRunnings = [];
|
|
63
|
+
await forEachComponentV2(app, async (component) => {
|
|
64
|
+
if (component.status !== BlockletStatus.running) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!hasStartEngine(component.meta)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (getBlockletEngine(component.meta)?.interpreter === 'blocklet') {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const processId = getComponentProcessId(component, [app]);
|
|
77
|
+
try {
|
|
78
|
+
const info = await getPm2ProcessInfo(processId);
|
|
79
|
+
if (info.pm2_env.status !== 'online') {
|
|
80
|
+
// process not running
|
|
81
|
+
processNotRunnings.push(component.meta.did);
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// probably process not exists
|
|
85
|
+
processNotRunnings.push(component.meta.did);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return processNotRunnings;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const restartRunningComponent = async () => {
|
|
93
|
+
const queue = AsyncQueue(async (app, next) => {
|
|
94
|
+
logger.info(`Restarting app ${getDisplayName(app)}...`);
|
|
95
|
+
const componentDids = await getComponentDids({ app });
|
|
96
|
+
|
|
97
|
+
if (!componentDids.length) {
|
|
98
|
+
logger.info(`> No component need to restart for app ${getDisplayName(app)}`);
|
|
99
|
+
next();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const result = await node.startBlocklet({ did: app.meta.did, checkHealthImmediately: true, componentDids }, {});
|
|
105
|
+
logger.info(`> Restarted app ${getDisplayName(app)} / [${componentDids.join(',')}]`);
|
|
106
|
+
await createAuditLog('startBlocklet', { did: app.meta.did, reason: 'on server restart' }, result);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
logger.error(`> Restarted app ${getDisplayName(app)} / [${componentDids.join(',')}] failed`, err);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
next();
|
|
112
|
+
}, 1);
|
|
113
|
+
|
|
114
|
+
const apps = await node.getBlocklets({ includeRuntimeInfo: false });
|
|
115
|
+
const running = apps.filter((app) =>
|
|
116
|
+
findComponentV2(app, (component) => component.status === BlockletStatus.running)
|
|
117
|
+
);
|
|
118
|
+
if (running.length === 0) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
running.forEach((app) => queue.push(app));
|
|
123
|
+
await queue.drain();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const syncComponentStatus = async () => {
|
|
127
|
+
const apps = await node.getBlocklets({ includeRuntimeInfo: false });
|
|
128
|
+
for (let i = 0; i < apps.length; i++) {
|
|
129
|
+
const app = apps[i];
|
|
130
|
+
try {
|
|
131
|
+
await node.syncBlockletStatus(app.meta.did, { forceSync: true });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
logger.error(`Blocklet ${getDisplayName(app)} status sync failed: ${err.message}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
logger.info('Waiting for Blocklet Server Daemon instance created');
|
|
139
|
+
node.onReady(async () => {
|
|
140
|
+
logger.info('Blocklet Server Daemon instance was created');
|
|
141
|
+
const info = await node.getNodeInfo();
|
|
142
|
+
|
|
143
|
+
await node.handleRouting(info);
|
|
144
|
+
logger.info('Service gateway was restarted');
|
|
145
|
+
|
|
146
|
+
const server = createServer(node);
|
|
147
|
+
server.listen(process.env.ABT_NODE_PORT, async (err) => {
|
|
148
|
+
if (err) throw err;
|
|
149
|
+
console.info(`> Blocklet Server Daemon ready on ${process.env.ABT_NODE_PORT}`);
|
|
150
|
+
|
|
151
|
+
// Should resume upgrading if we are in process
|
|
152
|
+
const session = await node.isBeingMaintained();
|
|
153
|
+
if (session) {
|
|
154
|
+
console.info('> Resume maintain', session);
|
|
155
|
+
await node.resumeMaintain(session);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
await node.cleanupDirtyMaintainState();
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.error('Blocklet Server failed to cleanup dirty maintain state', e);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const initialized = await node.isInitialized();
|
|
166
|
+
if (initialized) {
|
|
167
|
+
// 无论之前是否 crash, 当启动 server (非热重启) 时,永远重启之前运行中的组件
|
|
168
|
+
await node.updateNodeStatus(SERVER_STATUS.RUNNING);
|
|
169
|
+
if (!runningBefore) {
|
|
170
|
+
await restartRunningComponent();
|
|
171
|
+
} else {
|
|
172
|
+
logger.info('> Blocklet Server is restarted from running before');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
await syncComponentStatus();
|
|
176
|
+
}
|
|
177
|
+
} catch (e) {
|
|
178
|
+
console.error('> Blocklet Server failed to restart running blocklets', e);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
if (dataDir && fs.existsSync(path.join(dataDir, 'start.lock')) === false) {
|
|
183
|
+
await node.createNotification({
|
|
184
|
+
title: 'Blocklet Server Crashed',
|
|
185
|
+
description: `Seems like blocklet server (did: ${info.did}, pid: ${process.pid}) just recovered from a crash, please check logs for more detail.`,
|
|
186
|
+
entityType: 'node',
|
|
187
|
+
severity: 'error',
|
|
188
|
+
sticky: true,
|
|
189
|
+
action: '/logs/abtnode',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
// Do nothing
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
if (!process.env.NODE_ENV) {
|
|
2
|
+
process.env.NODE_ENV = 'production';
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const { BlockletStatus, BlockletInternalEvents } = require('@blocklet/constant');
|
|
9
|
+
const { filterComponentsV2 } = require('@blocklet/meta/lib/util');
|
|
10
|
+
|
|
11
|
+
process.env.ABT_NODE_LOG_NAME = 'service';
|
|
12
|
+
process.env.ABT_NODE_LOG_DIR = path.join(process.env.ABT_NODE_DATA_DIR, 'logs', '_abtnode'); // enable log
|
|
13
|
+
|
|
14
|
+
const ABTNode = require('@abtnode/core');
|
|
15
|
+
const getNodeWallet = require('@abtnode/util/lib/get-app-wallet');
|
|
16
|
+
|
|
17
|
+
const createServer = require('@abtnode/blocklet-services');
|
|
18
|
+
|
|
19
|
+
const { version } = require('../../package.json');
|
|
20
|
+
|
|
21
|
+
const wallet = getNodeWallet(process.env.ABT_NODE_SK);
|
|
22
|
+
const port = Number(process.env.ABT_NODE_SERVICE_PORT);
|
|
23
|
+
const dataDir = process.env.ABT_NODE_DATA_DIR;
|
|
24
|
+
|
|
25
|
+
const node = ABTNode({
|
|
26
|
+
nodeSk: wallet.secretKey,
|
|
27
|
+
nodePk: wallet.publicKey,
|
|
28
|
+
nodeDid: wallet.address,
|
|
29
|
+
port: process.env.ABT_NODE_PORT,
|
|
30
|
+
dataDir,
|
|
31
|
+
blockletPort: process.env.ABT_NODE_BLOCKLET_PORT,
|
|
32
|
+
service: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const notifyServerVersionToApps = async (server) => {
|
|
36
|
+
try {
|
|
37
|
+
const apps = await node.getBlocklets({ includeRuntimeInfo: false });
|
|
38
|
+
const runningApps = apps.filter(
|
|
39
|
+
(app) => filterComponentsV2(app, (x) => x.status === BlockletStatus.running).length
|
|
40
|
+
);
|
|
41
|
+
await Promise.all(
|
|
42
|
+
runningApps.map(async (app) => {
|
|
43
|
+
await server.sendToAppComponents({
|
|
44
|
+
appDid: app.appDid,
|
|
45
|
+
event: BlockletInternalEvents.appConfigChanged,
|
|
46
|
+
data: { configs: [{ key: 'ABT_NODE_VERSION', value: version }] },
|
|
47
|
+
});
|
|
48
|
+
await node.syncAppConfig(app, { serverVersion: version });
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(error);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
node.onReady(() => {
|
|
57
|
+
const server = createServer(node);
|
|
58
|
+
|
|
59
|
+
server.listen(port, '127.0.0.1', async (err) => {
|
|
60
|
+
if (err) throw err;
|
|
61
|
+
// eslint-disable-next-line
|
|
62
|
+
console.log(`> Blocklet Service ready on ${port}`);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
if (dataDir && fs.existsSync(path.join(dataDir, 'start.lock')) === false) {
|
|
66
|
+
await node.createNotification({
|
|
67
|
+
title: 'Blocklet Service Crashed',
|
|
68
|
+
description: `Seems like blocklet service (pid: ${process.pid}) just recovered from a crash, please check logs for more detail.`,
|
|
69
|
+
entityType: 'node',
|
|
70
|
+
severity: 'error',
|
|
71
|
+
sticky: true,
|
|
72
|
+
action: '/logs/abtnode',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
// Do nothing
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
// Let first worker process do something as master
|
|
81
|
+
if (process.env.NODE_APP_INSTANCE === '0') {
|
|
82
|
+
notifyServerVersionToApps(server);
|
|
83
|
+
}
|
|
84
|
+
}, 1000 * 30);
|
|
85
|
+
});
|
|
86
|
+
});
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const ora = require('ora');
|
|
2
|
+
const util = require('util');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const progress = require('cli-progress');
|
|
5
|
+
const symbols = require('log-symbols');
|
|
6
|
+
|
|
7
|
+
// https://github.com/sindresorhus/cli-spinners/blob/master/spinners.json
|
|
8
|
+
const spinners = [
|
|
9
|
+
'dots',
|
|
10
|
+
'dots2',
|
|
11
|
+
'dots3',
|
|
12
|
+
'dots4',
|
|
13
|
+
'dots5',
|
|
14
|
+
'dots6',
|
|
15
|
+
'dots7',
|
|
16
|
+
'dots8',
|
|
17
|
+
'dots9',
|
|
18
|
+
'dots10',
|
|
19
|
+
'dots11',
|
|
20
|
+
'dots12',
|
|
21
|
+
'line',
|
|
22
|
+
'line2',
|
|
23
|
+
'pipe',
|
|
24
|
+
'simpleDots',
|
|
25
|
+
'simpleDotsScrolling',
|
|
26
|
+
'star',
|
|
27
|
+
'star2',
|
|
28
|
+
'flip',
|
|
29
|
+
'hamburger',
|
|
30
|
+
'growVertical',
|
|
31
|
+
'growHorizontal',
|
|
32
|
+
'balloon',
|
|
33
|
+
'balloon2',
|
|
34
|
+
'noise',
|
|
35
|
+
'bounce',
|
|
36
|
+
'boxBounce',
|
|
37
|
+
'boxBounce2',
|
|
38
|
+
'triangle',
|
|
39
|
+
'arc',
|
|
40
|
+
'circle',
|
|
41
|
+
'squareCorners',
|
|
42
|
+
'circleQuarters',
|
|
43
|
+
'circleHalves',
|
|
44
|
+
'squish',
|
|
45
|
+
'toggle',
|
|
46
|
+
'toggle2',
|
|
47
|
+
'toggle3',
|
|
48
|
+
'toggle4',
|
|
49
|
+
'toggle5',
|
|
50
|
+
'toggle6',
|
|
51
|
+
'toggle7',
|
|
52
|
+
'toggle8',
|
|
53
|
+
'toggle9',
|
|
54
|
+
'toggle10',
|
|
55
|
+
'toggle11',
|
|
56
|
+
'toggle12',
|
|
57
|
+
'toggle13',
|
|
58
|
+
'arrow',
|
|
59
|
+
'arrow2',
|
|
60
|
+
'arrow3',
|
|
61
|
+
'bouncingBar',
|
|
62
|
+
'bouncingBall',
|
|
63
|
+
'smiley',
|
|
64
|
+
'monkey',
|
|
65
|
+
'hearts',
|
|
66
|
+
'clock',
|
|
67
|
+
'earth',
|
|
68
|
+
'moon',
|
|
69
|
+
'runner',
|
|
70
|
+
'pong',
|
|
71
|
+
'shark',
|
|
72
|
+
'dqpb',
|
|
73
|
+
'weather',
|
|
74
|
+
'christmas',
|
|
75
|
+
'grenade',
|
|
76
|
+
'point',
|
|
77
|
+
'layer',
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const getSpinner = (opts) => {
|
|
81
|
+
const random = Math.floor(Math.random() * spinners.length);
|
|
82
|
+
const spinner = ora(Object.assign({ spinner: spinners[random] }, opts || {}));
|
|
83
|
+
if (typeof opts === 'string') {
|
|
84
|
+
spinner.text = opts;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return spinner;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const wrapSpinner = async (
|
|
91
|
+
// eslint-disable-next-line default-param-last
|
|
92
|
+
message = '',
|
|
93
|
+
func,
|
|
94
|
+
{ throwOnError = true, printErrorFn = console.error, ref = {} } = {}
|
|
95
|
+
) => {
|
|
96
|
+
const spinner = getSpinner(message);
|
|
97
|
+
ref.spinner = spinner;
|
|
98
|
+
const startTime = Date.now();
|
|
99
|
+
spinner.start();
|
|
100
|
+
try {
|
|
101
|
+
const result = await func();
|
|
102
|
+
spinner.succeed(`${message} Done in ${chalk.cyan((Date.now() - startTime) / 1000)}s`);
|
|
103
|
+
return result;
|
|
104
|
+
} catch (err) {
|
|
105
|
+
spinner.fail(`${message} Fail after ${chalk.cyan((Date.now() - startTime) / 1000)}s`);
|
|
106
|
+
|
|
107
|
+
if (typeof printErrorFn === 'function') {
|
|
108
|
+
printErrorFn(err.message);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (throwOnError) {
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return err;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
module.exports = {
|
|
120
|
+
symbols,
|
|
121
|
+
pretty: (data, options) => {
|
|
122
|
+
if (data && typeof data === 'object') {
|
|
123
|
+
return `\n${JSON.stringify(data, null, 2)}\n`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return util.inspect(data, Object.assign({ depth: 8, colors: true, compact: false }, options));
|
|
127
|
+
},
|
|
128
|
+
getProgress: ({ title, unit = 'MB' }) =>
|
|
129
|
+
new progress.Bar({
|
|
130
|
+
format: `${title} |${chalk.cyan('{bar}')} {percentage}% || {value}/{total} ${unit}`,
|
|
131
|
+
barCompleteChar: '\u2588',
|
|
132
|
+
barIncompleteChar: '\u2591',
|
|
133
|
+
hideCursor: true,
|
|
134
|
+
}),
|
|
135
|
+
getSpinner,
|
|
136
|
+
wrapSpinner,
|
|
137
|
+
};
|