@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.
Files changed (87) hide show
  1. package/README.md +32 -25
  2. package/bin/blocklet.js +292 -1
  3. package/config.example.yml +33 -0
  4. package/lib/arcblock.js +53 -0
  5. package/lib/commands/blocklet/add.js +124 -0
  6. package/lib/commands/blocklet/assets/git-ignore +28 -0
  7. package/lib/commands/blocklet/assets/index.html +9 -0
  8. package/lib/commands/blocklet/assets/index.js +14 -0
  9. package/lib/commands/blocklet/assets/logo.png +0 -0
  10. package/lib/commands/blocklet/bundle/bundle.js +184 -0
  11. package/lib/commands/blocklet/bundle/bundlers/blocklet.js +138 -0
  12. package/lib/commands/blocklet/bundle/bundlers/changelog.js +100 -0
  13. package/lib/commands/blocklet/bundle/bundlers/logo.js +56 -0
  14. package/lib/commands/blocklet/bundle/bundlers/markdown.js +241 -0
  15. package/lib/commands/blocklet/bundle/bundlers/preference.js +50 -0
  16. package/lib/commands/blocklet/bundle/bundlers/readme.js +43 -0
  17. package/lib/commands/blocklet/bundle/bundlers/screenshots.js +94 -0
  18. package/lib/commands/blocklet/bundle/bundlers/simple.js +70 -0
  19. package/lib/commands/blocklet/bundle/compact/bundle-compact-file.js +48 -0
  20. package/lib/commands/blocklet/bundle/compact/bundle-merge-extra.js +66 -0
  21. package/lib/commands/blocklet/bundle/compact/default-external.js +5 -0
  22. package/lib/commands/blocklet/bundle/compact/index.js +88 -0
  23. package/lib/commands/blocklet/bundle/index.js +139 -0
  24. package/lib/commands/blocklet/bundle/pack.js +8 -0
  25. package/lib/commands/blocklet/bundle/parse-external-dependencies.js +97 -0
  26. package/lib/commands/blocklet/bundle/simple/index.js +62 -0
  27. package/lib/commands/blocklet/bundle/zip/archive.js +35 -0
  28. package/lib/commands/blocklet/bundle/zip/dependencies.js +333 -0
  29. package/lib/commands/blocklet/bundle/zip/index.js +165 -0
  30. package/lib/commands/blocklet/bundle/zip/main.js +124 -0
  31. package/lib/commands/blocklet/bundle/zip/node.js +59 -0
  32. package/lib/commands/blocklet/bundle/zip/resolve.js +93 -0
  33. package/lib/commands/blocklet/cleanup.js +52 -0
  34. package/lib/commands/blocklet/config.js +108 -0
  35. package/lib/commands/blocklet/connect.js +87 -0
  36. package/lib/commands/blocklet/create.js +38 -0
  37. package/lib/commands/blocklet/deploy.js +435 -0
  38. package/lib/commands/blocklet/dev.js +1000 -0
  39. package/lib/commands/blocklet/document.js +39 -0
  40. package/lib/commands/blocklet/exec.js +106 -0
  41. package/lib/commands/blocklet/init.js +300 -0
  42. package/lib/commands/blocklet/meta.js +22 -0
  43. package/lib/commands/blocklet/remove.js +35 -0
  44. package/lib/commands/blocklet/test.js +201 -0
  45. package/lib/commands/blocklet/upload.js +105 -0
  46. package/lib/commands/blocklet/version.js +81 -0
  47. package/lib/commands/server/cleanup.js +32 -0
  48. package/lib/commands/server/command.js +131 -0
  49. package/lib/commands/server/info.js +92 -0
  50. package/lib/commands/server/init.js +433 -0
  51. package/lib/commands/server/logs.js +99 -0
  52. package/lib/commands/server/rescue.js +71 -0
  53. package/lib/commands/server/start.js +821 -0
  54. package/lib/commands/server/status.js +107 -0
  55. package/lib/commands/server/stop.js +163 -0
  56. package/lib/commands/server/upgrade.js +123 -0
  57. package/lib/constant.js +21 -2
  58. package/lib/debug.js +20 -0
  59. package/lib/manager/config.js +122 -0
  60. package/lib/manager/deploy.js +75 -0
  61. package/lib/manager/index.js +23 -0
  62. package/lib/manager/process.js +47 -0
  63. package/lib/node.js +214 -0
  64. package/lib/port.js +19 -0
  65. package/lib/postinstall.js +3 -0
  66. package/lib/process/daemon.js +196 -0
  67. package/lib/process/service.js +86 -0
  68. package/lib/ui.js +137 -0
  69. package/lib/util/blocklet/config.js +78 -0
  70. package/lib/util/blocklet/env.js +172 -0
  71. package/lib/util/blocklet/meta.js +36 -0
  72. package/lib/util/blocklet/payment.js +88 -0
  73. package/lib/util/blocklet/sign.js +21 -0
  74. package/lib/util/blocklet/tar.js +119 -0
  75. package/lib/util/convert-to-nosources-sourcemap.js +37 -0
  76. package/lib/util/docker-status-log.js +17 -0
  77. package/lib/util/exit-when-server-stopped.js +44 -0
  78. package/lib/util/get-cli-binary-name.js +8 -0
  79. package/lib/util/get-download-bundle-step.js +36 -0
  80. package/lib/util/get-service-instance-number.js +12 -0
  81. package/lib/util/index.js +626 -0
  82. package/lib/util/print-error.js +11 -0
  83. package/lib/util/print.js +9 -0
  84. package/lib/util/what-uri.js +40 -0
  85. package/package.json +123 -27
  86. package/lib/run.d.ts +0 -2
  87. package/lib/run.js +0 -73
@@ -0,0 +1,433 @@
1
+ const fs = require('fs');
2
+ const crypto = require('crypto');
3
+ const chalk = require('chalk');
4
+ const yaml = require('js-yaml');
5
+ const { types, getRandomBytes } = require('@ocap/mcrypto');
6
+ const { fromRandom, fromSecretKey, WalletType } = require('@ocap/wallet');
7
+ const inquirer = require('inquirer');
8
+ const isUrl = require('is-url');
9
+ const security = require('@abtnode/util/lib/security');
10
+ const isValidPort = require('@abtnode/util/lib/is-valid-port');
11
+ const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
12
+ const { getProviderNames, findExistsProvider, getProvider } = require('@abtnode/router-provider');
13
+ const {
14
+ DEFAULT_DAEMON_PORT,
15
+ BLOCKLET_MAX_MEM_LIMIT_IN_MB,
16
+ DAEMON_MAX_MEM_LIMIT_IN_MB,
17
+ WELLKNOWN_SERVER_ADMIN_PATH,
18
+ DEFAULT_WILDCARD_CERT_HOST,
19
+ DEFAULT_IP_DOMAIN,
20
+ MAX_UPLOAD_FILE_SIZE,
21
+ NODE_MODES,
22
+ DEFAULT_DID_REGISTRY,
23
+ PROXY_MAX_MEM_LIMIT_IN_MB,
24
+ DEFAULT_DID_DOMAIN,
25
+ DEFAULT_SLP_DOMAIN,
26
+ } = require('@abtnode/constant');
27
+
28
+ const debug = require('../../debug')('init');
29
+ const {
30
+ print,
31
+ printInfo,
32
+ printError,
33
+ printSuccess,
34
+ printWarning,
35
+ getCLICommandName,
36
+ readInitInfoFromEc2,
37
+ getDefaultName,
38
+ getDefaultDescription,
39
+ printInvalidModeInfo,
40
+ } = require('../../util');
41
+ const {
42
+ getBaseConfigDataDirectory,
43
+ joinConfigFile,
44
+ joinKFile,
45
+ joinConfigDir,
46
+ getConfigDirFromCurrentDir,
47
+ verifyDataDirectory,
48
+ } = require('../../manager');
49
+
50
+ const { version } = require('../../../package.json');
51
+
52
+ const isValidDaemonMemoryLimit = (x) => Number(x) >= 0.5 && Number(x) <= 128;
53
+ const isValidBlockletMemoryLimit = (x) => Number(x) >= 0.2 && Number(x) <= 64;
54
+
55
+ async function getQuestions({
56
+ defaultName,
57
+ defaultDescription,
58
+ defaultHttps,
59
+ defaultHttpPort,
60
+ defaultHttpsPort,
61
+ defaultWebWalletUrl,
62
+ }) {
63
+ const defaultProvider = await findExistsProvider();
64
+ const providerNames = getProviderNames();
65
+
66
+ const questions = [
67
+ {
68
+ type: 'text',
69
+ name: 'name',
70
+ message: 'Naming the server?',
71
+ validate: (v) => (v.trim() ? true : 'Please input a valid name'),
72
+ default: defaultName,
73
+ },
74
+ {
75
+ type: 'text',
76
+ name: 'description',
77
+ message: 'Description of the server?',
78
+ validate: (v) => (v.trim() ? true : 'Please input a valid description'),
79
+ default: defaultDescription,
80
+ },
81
+ {
82
+ type: 'number',
83
+ name: 'port',
84
+ message: 'Which port do you want your server to listen on?',
85
+ validate: (v) => (isValidPort(v) ? true : 'Please input a valid port'),
86
+ default: isValidPort(Number(process.env.ABT_NODE_PORT)) ? Number(process.env.ABT_NODE_PORT) : DEFAULT_DAEMON_PORT,
87
+ },
88
+ {
89
+ type: 'list',
90
+ name: 'provider',
91
+ message: 'Choose routing engine',
92
+ choices: [...providerNames],
93
+ default: defaultProvider,
94
+ },
95
+ {
96
+ type: 'text',
97
+ name: 'adminPath',
98
+ message: 'Where do you want to mount your admin dashboard?',
99
+ validate: (v) => (v.trim() ? true : 'Please input a valid path prefix'),
100
+ default: WELLKNOWN_SERVER_ADMIN_PATH,
101
+ },
102
+ {
103
+ type: 'text',
104
+ name: 'webWalletUrl',
105
+ message: 'Which web wallet do you want to use?',
106
+ validate: (v) => (isUrl(v.trim()) ? true : 'Please input a valid url'),
107
+ default: defaultWebWalletUrl,
108
+ },
109
+ {
110
+ type: 'confirm',
111
+ name: 'https',
112
+ message: 'Do you want to enable https support for dashboard and blocklets',
113
+ default: defaultHttps,
114
+ },
115
+ {
116
+ type: 'text',
117
+ name: 'ipWildcardDomain',
118
+ message: "What's ip wildcard domain?", // eslint-disable-line quotes
119
+ validate: (v) => (v.trim() ? true : 'Please input a valid dashboard domain'),
120
+ default: DEFAULT_IP_DOMAIN,
121
+ },
122
+ {
123
+ type: 'text',
124
+ name: 'wildcardCertHost',
125
+ message: 'Where to download the wildcard certificate?',
126
+ validate: (v) => (isUrl(v) ? true : 'Please input a valid download address url'),
127
+ default: process.env.ABT_NODE_WILDCARD_CERT_HOST || DEFAULT_WILDCARD_CERT_HOST,
128
+ },
129
+ {
130
+ name: 'maxUploadFileSize',
131
+ message: 'Max Upload File Size allowed by the server (MB)?',
132
+ validate: (v) => (Number.isInteger(+v) && +v > 0 ? true : 'Please input a positive integer'),
133
+ // eslint-disable-next-line max-len
134
+ default: Number(process.env.MAX_UPLOAD_FILE_SIZE) > 0 ? Number(process.env.MAX_UPLOAD_FILE_SIZE) : MAX_UPLOAD_FILE_SIZE, // prettier-ignore
135
+ },
136
+ {
137
+ type: 'number',
138
+ name: 'httpPort',
139
+ message: 'Which http port do you want your service gateway to listen on?',
140
+ validate: (v) => (isValidPort(v) ? true : 'Please input a valid port'),
141
+ default: isValidPort(Number(process.env.ABT_NODE_HTTP_PORT))
142
+ ? Number(process.env.ABT_NODE_HTTP_PORT)
143
+ : Number(defaultHttpPort),
144
+ },
145
+ {
146
+ type: 'number',
147
+ name: 'httpsPort',
148
+ message: 'Which https port do you want your service gateway to listen on?',
149
+ validate: (v) => (isValidPort(v) ? true : 'Please input a valid port'),
150
+ default: isValidPort(Number(process.env.ABT_NODE_HTTPS_PORT))
151
+ ? Number(process.env.ABT_NODE_HTTPS_PORT)
152
+ : Number(defaultHttpsPort),
153
+ },
154
+ {
155
+ type: 'number',
156
+ name: 'daemonMaxMemoryLimit',
157
+ message: 'What is the max memory limit of Blocklet Server (GB)?',
158
+ validate: (v) => (isValidDaemonMemoryLimit(v) ? true : 'The memory limit must between 0.5 ~ 128'),
159
+ default: DAEMON_MAX_MEM_LIMIT_IN_MB / 1000,
160
+ },
161
+ {
162
+ type: 'number',
163
+ name: 'blockletMaxMemoryLimit',
164
+ message: 'What is the max memory limit of each Blocklet (GB)?',
165
+ validate: (v) => (isValidBlockletMemoryLimit(v) ? true : 'The memory limit must between 0.5 ~ 128'),
166
+ default: BLOCKLET_MAX_MEM_LIMIT_IN_MB / 1000,
167
+ },
168
+ ];
169
+
170
+ return questions;
171
+ }
172
+
173
+ const askQuestions = async (interactive, defaultAnswers = {}) => {
174
+ const questions = await getQuestions(defaultAnswers);
175
+ if (!interactive) {
176
+ debug('init in non-interactive mode by accepting the defaults');
177
+ return questions.reduce((acc, x) => {
178
+ acc[x.name] = x.default;
179
+ return acc;
180
+ }, {});
181
+ }
182
+
183
+ return inquirer.prompt(questions);
184
+ };
185
+
186
+ exports.run = async ({
187
+ interactive = false,
188
+ force,
189
+ mode: defaultMode = NODE_MODES.PRODUCTION,
190
+ https: defaultHttps = true,
191
+ httpPort: defaultHttpPort = 80,
192
+ httpsPort: defaultHttpsPort = 443,
193
+ ownerNftHolder,
194
+ ownerNftIssuer,
195
+ disablePassportIssuance: defaultDisablePassportIssuance,
196
+ trustedPassportIssuer: defaultTrustedPassportIssuer,
197
+ webWalletUrl: defaultWebWalletUrl,
198
+ sk: customSk,
199
+ }) => {
200
+ const workingDir = process.cwd();
201
+ const baseConfigDirectory = getBaseConfigDataDirectory(workingDir);
202
+
203
+ if (baseConfigDirectory) {
204
+ print(`Can't initialize in a configuration data directory: ${baseConfigDirectory}`);
205
+ process.exit(1);
206
+ }
207
+
208
+ const existedConfigDir = getConfigDirFromCurrentDir(workingDir);
209
+ if (existedConfigDir) {
210
+ try {
211
+ verifyDataDirectory(existedConfigDir);
212
+ } catch (error) {
213
+ debug('validate configuration directory error', error);
214
+ printError('The data directory already exists, but there is an error:', error.message);
215
+ process.exit(1);
216
+ }
217
+
218
+ print('The data directory already exists in the current directory');
219
+ printInfo(`Run ${chalk.cyan(`${getCLICommandName()} start`)} to start`);
220
+ process.exit(0);
221
+ }
222
+
223
+ if (![NODE_MODES.PRODUCTION, NODE_MODES.DEBUG].includes(defaultMode)) {
224
+ printInvalidModeInfo();
225
+ process.exit(1);
226
+ }
227
+
228
+ if (!isValidPort(Number(defaultHttpPort))) {
229
+ printError('Invalid http port');
230
+ printInfo('Valid http port should be 0-65535');
231
+ process.exit(1);
232
+ }
233
+
234
+ if (!isValidPort(Number(defaultHttpsPort))) {
235
+ printError('Invalid https port');
236
+ printInfo('Valid https port should be 0-65535');
237
+ process.exit(1);
238
+ }
239
+
240
+ if (force !== true) {
241
+ const { confirmCreate } = await inquirer.prompt({
242
+ type: 'confirm',
243
+ name: 'confirmCreate',
244
+ message: `Are you sure to initialize a Blocklet Server instance in the current directory(${workingDir})`,
245
+ default: false,
246
+ });
247
+
248
+ if (!confirmCreate) {
249
+ print('Aborted!');
250
+ process.exit(0);
251
+ }
252
+ }
253
+
254
+ const type = WalletType({
255
+ role: types.RoleType.ROLE_APPLICATION,
256
+ pk: types.KeyType.ED25519,
257
+ hash: types.HashType.SHA3,
258
+ });
259
+ let wallet = fromRandom(type);
260
+ if (customSk) {
261
+ try {
262
+ wallet = fromSecretKey(customSk, type);
263
+ printInfo(`Using custom secret key: ${wallet.address}`);
264
+ } catch (err) {
265
+ printError(`Invalid custom secret key: ${err.message}`);
266
+ process.exit(1);
267
+ }
268
+ }
269
+
270
+ let interactiveCreate = interactive;
271
+ if (force === true) {
272
+ interactiveCreate = false;
273
+ }
274
+
275
+ const {
276
+ name,
277
+ description,
278
+ ownerDid,
279
+ issuerDid,
280
+ initialBlocklets,
281
+ launcher,
282
+ launcherSessionId,
283
+ didRegistry,
284
+ didDomain,
285
+ slpDomain,
286
+ webWalletUrl = defaultWebWalletUrl,
287
+ disablePassportIssuance = defaultDisablePassportIssuance,
288
+ trustedPassportIssuer = defaultTrustedPassportIssuer,
289
+ } = readInitInfoFromEc2();
290
+
291
+ const answers = await askQuestions(interactiveCreate, {
292
+ defaultName: name || getDefaultName(),
293
+ defaultDescription: description || (await getDefaultDescription()),
294
+ defaultHttps,
295
+ defaultHttpPort,
296
+ defaultHttpsPort,
297
+ defaultWebWalletUrl: webWalletUrl,
298
+ });
299
+ const {
300
+ name: abtnodeName,
301
+ description: abtnodeDescription,
302
+ port,
303
+ provider,
304
+ adminPath,
305
+ wildcardCertHost,
306
+ ipWildcardDomain,
307
+ maxUploadFileSize,
308
+ https,
309
+ daemonMaxMemoryLimit,
310
+ blockletMaxMemoryLimit,
311
+ } = answers;
312
+ let { httpPort, httpsPort } = answers;
313
+
314
+ // Ensure routing engine exist
315
+ const Provider = getProvider(provider);
316
+ try {
317
+ if (Provider) {
318
+ const exists = await Provider.exists();
319
+ if (!exists) {
320
+ printWarning(`Routing engine ${provider} does not exists`);
321
+ }
322
+ }
323
+ } catch (error) {
324
+ debug(error);
325
+ printError(`Routing engine availability check failed: ${error.message}`);
326
+ printInfo(`Checkout out documentation for help: ${chalk.cyan('https://developer.blocklet.io/docs')}`);
327
+ printInfo('The above issue must be resolved to make blocklet server work');
328
+ process.exit(1);
329
+ }
330
+
331
+ // Ensure selected ports are available
332
+ if (httpPort === 80 && httpsPort === 443) {
333
+ const result = await Provider.getUsablePorts();
334
+ if (result) {
335
+ if (result.httpPort !== httpPort) {
336
+ printWarning(`Pick ${result.httpPort} as http port since ${httpPort} is not available`);
337
+ }
338
+ if (result.httpsPort !== httpsPort) {
339
+ printWarning(`Pick ${result.httpsPort} as https port since ${httpsPort} is not available`);
340
+ }
341
+
342
+ // eslint-disable-next-line no-param-reassign
343
+ httpPort = result.httpPort;
344
+ // eslint-disable-next-line no-param-reassign
345
+ httpsPort = result.httpsPort;
346
+ }
347
+ }
348
+
349
+ const ownerNft = { holder: ownerNftHolder, issuer: ownerNftIssuer };
350
+ if (!ownerNftHolder || !ownerNftIssuer) {
351
+ ownerNft.holder = ownerDid;
352
+ ownerNft.issuer = issuerDid;
353
+ }
354
+
355
+ if (launcherSessionId) {
356
+ ownerNft.launcherSessionId = launcherSessionId;
357
+ }
358
+
359
+ ownerNft.holder = ownerNft.holder || '';
360
+ ownerNft.issuer = ownerNft.issuer || '';
361
+
362
+ const dk = crypto.randomBytes(32);
363
+ const config = {
364
+ node: {
365
+ name: abtnodeName.trim(),
366
+ description: abtnodeDescription.trim(),
367
+ mode: defaultMode,
368
+ version,
369
+ sk: security.encrypt(wallet.secretKey, wallet.address, dk),
370
+ pk: wallet.publicKey,
371
+ did: wallet.address,
372
+ port: +port,
373
+ secret: getRandomBytes(32),
374
+ owner: { pk: '', did: '' },
375
+ ownerNft,
376
+ routing: {
377
+ provider,
378
+ adminPath: normalizePathPrefix(adminPath || WELLKNOWN_SERVER_ADMIN_PATH),
379
+ headers: { 'X-Powered-By': `"Blocklet Server/${version}"` },
380
+ maxUploadFileSize: Number(maxUploadFileSize),
381
+ https: !!https,
382
+ httpPort: +httpPort,
383
+ httpsPort: +httpsPort,
384
+ wildcardCertHost: wildcardCertHost.trim(),
385
+ ipWildcardDomain: ipWildcardDomain.trim(),
386
+ },
387
+ runtimeConfig: {
388
+ daemonMaxMemoryLimit: Math.floor(daemonMaxMemoryLimit * 1024),
389
+ proxyMaxMemoryLimit: PROXY_MAX_MEM_LIMIT_IN_MB,
390
+ blockletMaxMemoryLimit: Math.floor(blockletMaxMemoryLimit * 1024),
391
+ },
392
+ didRegistry: (process.env.ABT_NODE_DID_REGISTRY || didRegistry || DEFAULT_DID_REGISTRY).trim(),
393
+ didDomain: (process.env.ABT_NODE_DID_DOMAIN || didDomain || DEFAULT_DID_DOMAIN).trim(),
394
+ slpDomain: (process.env.ABT_NODE_SLP_DOMAIN || slpDomain || DEFAULT_SLP_DOMAIN).trim(),
395
+ enablePassportIssuance: !disablePassportIssuance,
396
+ trustedPassports: [trustedPassportIssuer].filter(Boolean).map((x) => ({ issuerDid: x, mappings: [] })),
397
+ webWalletUrl: answers.webWalletUrl.trim(),
398
+ database: {
399
+ engine: 'sqlite',
400
+ },
401
+ },
402
+ blocklet: {
403
+ port, // note: this is required to tell core that blocklet port starts from `node.port + 1`
404
+ },
405
+ initialize: {
406
+ blocklets: initialBlocklets || [],
407
+ },
408
+ };
409
+
410
+ if (launcher) {
411
+ config.node.launcher = launcher;
412
+ }
413
+
414
+ debug('generated config', config);
415
+
416
+ try {
417
+ const configDir = joinConfigDir(workingDir);
418
+ const configFile = joinConfigFile(configDir);
419
+
420
+ fs.mkdirSync(configDir);
421
+ fs.writeFileSync(joinKFile(configDir), dk, { encoding: 'binary', mode: '0600' });
422
+ fs.writeFileSync(configFile, yaml.dump(config));
423
+
424
+ printSuccess(`Blocklet Server configuration is successfully generated ${chalk.cyan(configFile)}`);
425
+ printInfo(chalk.cyan(`${getCLICommandName()} start`));
426
+ print();
427
+
428
+ process.exit(0);
429
+ } catch (err) {
430
+ printError(`Failed to generate config ${err.message}`);
431
+ process.exit(1);
432
+ }
433
+ };
@@ -0,0 +1,99 @@
1
+ /* eslint-disable no-shadow */
2
+ const chalk = require('chalk');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+
6
+ const { print, printError, printWarning } = require('../../util');
7
+ const { getNode } = require('../../node');
8
+ const { getDaemonLogDir, checkRunning } = require('../../manager');
9
+
10
+ const LOG_DATE_FORMAT = 'YYYY-MM-DD';
11
+
12
+ const getLatestBusinessLog = (logDir) => {
13
+ if (!fs.existsSync(logDir)) {
14
+ throw new Error(`logs directory: ${logDir} does not exist`);
15
+ }
16
+
17
+ const files = fs.readdirSync(logDir);
18
+
19
+ if (files.length === 0) {
20
+ return '';
21
+ }
22
+
23
+ const businessLogFiles = files.filter((f) => f.startsWith('daemon-') && !f.startsWith('daemon-error'));
24
+ if (businessLogFiles.length === 0) {
25
+ return '';
26
+ }
27
+
28
+ let resultFile = businessLogFiles[0];
29
+ const prefixLength = 'daemon-'.length;
30
+
31
+ const extractDateFromFilename = (filename) =>
32
+ path.basename(filename, path.extname(filename)).substr(prefixLength, LOG_DATE_FORMAT.length);
33
+
34
+ businessLogFiles.forEach((filename) => {
35
+ if (new Date(extractDateFromFilename(filename)) > new Date(extractDateFromFilename(resultFile))) {
36
+ resultFile = filename;
37
+ }
38
+ });
39
+
40
+ return path.join(logDir, resultFile);
41
+ };
42
+
43
+ exports.run = async () => {
44
+ const { node, dataDir } = await getNode({ dir: process.cwd() });
45
+
46
+ try {
47
+ const daemonLogDir = getDaemonLogDir(dataDir);
48
+ const businessLogFile = getLatestBusinessLog(daemonLogDir);
49
+
50
+ print();
51
+ print(chalk.cyan(chalk.bold('Blocklet Server Logs')));
52
+
53
+ if (fs.existsSync(businessLogFile)) {
54
+ print(`- Latest logs: ${businessLogFile}`);
55
+ } else {
56
+ print('- No logs');
57
+ }
58
+ print(`- Daemon Logs Directory: ${daemonLogDir}`);
59
+
60
+ print(`\n ${chalk.cyan('Daemon Logs')}`);
61
+ print(' - access-<date>.log: access logs rotated by day');
62
+ print(' - daemon-<date>.log: business logs rotated by day');
63
+ print(' - daemon-error-<date>.log: error logs rotated by day');
64
+ print(' - service.log: abtnode service logs');
65
+ print(' - stderr.log: stderr logs');
66
+ print(' - stdout.log: stdout logs');
67
+
68
+ const isRunning = await checkRunning();
69
+ if (!isRunning) {
70
+ print('');
71
+ printWarning('Unable to get blocklet logs because Blocklet Server is not running');
72
+ process.exit(0);
73
+ }
74
+ const nodeInfo = await node.getNodeInfo();
75
+ if (nodeInfo.initialized) {
76
+ const blocklets = await node.getBlocklets({ includeRuntimeInfo: false });
77
+ if (blocklets.length) {
78
+ blocklets.forEach(({ meta }) => {
79
+ print();
80
+ const logsDir = path.join(dataDir, 'logs', meta.name);
81
+ const output = path.join(logsDir, 'output.log');
82
+ const error = path.join(logsDir, 'error.log');
83
+ print(chalk.cyan(chalk.bold(`${meta.name}@${meta.version}`)));
84
+ print(`- Output: ${output}`);
85
+ print(`- Error: ${error}`);
86
+ });
87
+ } else {
88
+ printWarning('No blocklets installed yet.');
89
+ }
90
+ }
91
+ process.exit(0);
92
+ } catch (err) {
93
+ printError(`Failed to list log files: ${err.message}`);
94
+ process.exit(1);
95
+ }
96
+ };
97
+
98
+ // eslint-disable-next-line no-underscore-dangle
99
+ exports._getLatestBusinessLog = getLatestBusinessLog;
@@ -0,0 +1,71 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ /* eslint-disable no-continue */
3
+
4
+ const trim = require('lodash/trim');
5
+ const { BLOCKLET_MODES, BlockletStatus } = require('@blocklet/constant');
6
+
7
+ const { getDisplayName } = require('@blocklet/meta/lib/util');
8
+ const { printError, printSuccess, printWarning, printInfo } = require('../../util');
9
+ const { getNode } = require('../../node');
10
+ const { checkRunning } = require('../../manager');
11
+ const { wrapSpinner } = require('../../ui');
12
+
13
+ const isAfter = (t1, t2) => new Date(t1).getTime() >= new Date(t2).getTime();
14
+
15
+ exports.run = async ({ force = false, stoppedAfter }) => {
16
+ const { node } = await getNode({ dir: process.cwd() });
17
+
18
+ if (!trim(stoppedAfter) || !new Date(stoppedAfter).getTime()) {
19
+ printError('Please provide a valid --stopped-after time');
20
+ process.exit(1);
21
+ }
22
+
23
+ const isRunning = await checkRunning();
24
+ if (!isRunning) {
25
+ printWarning('Blocklet Server is not running!');
26
+ process.exit(0);
27
+ }
28
+
29
+ node.onReady(async () => {
30
+ let blocklets = [];
31
+
32
+ blocklets = await node.getBlocklets({ includeRuntimeInfo: false });
33
+
34
+ for (let i = 0; i < blocklets.length; i++) {
35
+ const blocklet = blocklets[i];
36
+ const title = getDisplayName(blocklet);
37
+ if (blocklet.mode === BLOCKLET_MODES.DEVELOPMENT) {
38
+ printInfo(`Skip development blocklet: ${title}`);
39
+ continue;
40
+ }
41
+
42
+ if (blocklet.status === BlockletStatus.running && !force) {
43
+ printInfo(`Skip running blocklet: ${title}`);
44
+ continue;
45
+ }
46
+
47
+ let minStoppedAt = 0;
48
+ blocklet.children.forEach((child) => {
49
+ if (isAfter(child.stoppedAt, minStoppedAt)) {
50
+ minStoppedAt = child.stoppedAt;
51
+ }
52
+ });
53
+
54
+ if (!isAfter(minStoppedAt, stoppedAfter)) {
55
+ printInfo(`Skip blocklet: ${title} because it was not stopped after ${stoppedAfter}`);
56
+ continue;
57
+ }
58
+
59
+ try {
60
+ await wrapSpinner(`Starting blocklet: ${title}`, async () => {
61
+ await node.startBlocklet({ did: blocklet.appDid, checkHealthImmediately: true, throwOnError: true });
62
+ });
63
+ } catch (err) {
64
+ printError(`Failed to start blocklet ${title}`, err.message);
65
+ }
66
+ }
67
+
68
+ printSuccess('Done!');
69
+ process.exit(0);
70
+ });
71
+ };