@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,1000 @@
1
+ require('dotenv-flow').config({ silent: true, node_env: 'development' });
2
+
3
+ const path = require('path');
4
+ const fs = require('fs-extra');
5
+ const chalk = require('chalk');
6
+ const open = require('open');
7
+ const get = require('lodash/get');
8
+ const uniq = require('lodash/uniq');
9
+ const { default: axios } = require('axios');
10
+ const Jwt = require('@arcblock/jwt');
11
+ const { joinURL } = require('ufo');
12
+ const debug = require('debug')('@blocklet/cli:dev');
13
+ const Client = require('@ocap/client');
14
+ const { isValid: isValidDid, toAddress } = require('@arcblock/did');
15
+ const { evaluateURLs, isDidDomain } = require('@abtnode/util/lib/url-evaluation');
16
+ const isDocker = require('@abtnode/util/lib/is-docker');
17
+ const { toBase58, fromBase58, fromUnitToToken } = require('@ocap/util');
18
+ const getBlockletInfo = require('@blocklet/meta/lib/info');
19
+ const {
20
+ replaceSlotToIp,
21
+ isInProgress,
22
+ findComponentByIdV2,
23
+ isGatewayBlocklet,
24
+ hasStartEngine,
25
+ } = require('@blocklet/meta/lib/util');
26
+ const { getComponentsInternalInfo } = require('@blocklet/meta/lib/blocklet');
27
+ const getBlockletMeta = require('@blocklet/meta/lib/parse');
28
+ const hasReservedKey = require('@blocklet/meta/lib/has-reserved-key');
29
+ const {
30
+ BLOCKLET_MODES,
31
+ BlockletStatus,
32
+ BlockletEvents,
33
+ BlockletInternalEvents,
34
+ BLOCKLET_CONFIGURABLE_KEY,
35
+ } = require('@blocklet/constant');
36
+ const {
37
+ PROCESS_NAME_EVENT_HUB,
38
+ WELLKNOWN_PING_PREFIX,
39
+ WELLKNOWN_SERVICE_PATH_PREFIX,
40
+ ROUTING_RULE_TYPES,
41
+ } = require('@abtnode/constant');
42
+ const getIP = require('@abtnode/util/lib/get-ip');
43
+ const sleep = require('@abtnode/util/lib/sleep');
44
+ const Lock = require('@abtnode/util/lib/lock');
45
+ const { getBaseUrls } = require('@abtnode/core/lib/util');
46
+ const codespaces = require('@abtnode/util/lib/codespaces');
47
+
48
+ const getDownloadBundleStep = require('../../util/get-download-bundle-step');
49
+
50
+ process.env.ABT_NODE_EVENT_PORT = process.env.ABT_NODE_EVENT_PORT || PROCESS_NAME_EVENT_HUB;
51
+
52
+ const {
53
+ print,
54
+ printError,
55
+ printInfo,
56
+ printWarning,
57
+ printSuccess,
58
+ getCLIBinaryName,
59
+ getDevUrl,
60
+ checkTerminalProxy,
61
+ wrapDefaultStoreUrl,
62
+ printBlockletDevelopmentGuide,
63
+ } = require('../../util');
64
+ const { getNode: getProdNode } = require('../../node');
65
+ const { checkRunning } = require('../../manager');
66
+ const { wrapSpinner } = require('../../ui');
67
+ const ensureBlockletEnv = require('../../util/blocklet/env');
68
+ const { HELP_DOCS_GITHUB_CODESPACES_URL } = require('../../constant');
69
+
70
+ const lock = new Lock('exit-lock');
71
+
72
+ const ACTIONS = {
73
+ INSTALL: 'install',
74
+ START: 'start',
75
+ REMOVE: 'remove',
76
+ RESET: 'reset',
77
+ FAUCET: 'faucet',
78
+ STUDIO: 'studio',
79
+ };
80
+
81
+ const checkBlockletMode = (blocklet, { throwOnError } = {}) => {
82
+ if (blocklet.mode === BLOCKLET_MODES.PRODUCTION) {
83
+ if (throwOnError) {
84
+ throw new Error('Cannot develop blocklet which is in production mode');
85
+ }
86
+
87
+ printError('The blocklet of production mode already exists, please remove it before developing');
88
+ process.exit(1);
89
+ }
90
+
91
+ return false;
92
+ };
93
+
94
+ const isDevelopmentMode = (blocklet = {}) => blocklet.mode === BLOCKLET_MODES.DEVELOPMENT;
95
+
96
+ const checkNodeRunning = async () => {
97
+ const isRunning = await checkRunning();
98
+ if (!isRunning) {
99
+ const startCommand = chalk.cyan(`${getCLIBinaryName()} server start`);
100
+ printError('Blocklet Server is not running, can not dev anything!');
101
+ printInfo(`To start Blocklet Server, use ${startCommand}`);
102
+ process.exit(1);
103
+ }
104
+ };
105
+
106
+ const getNode = (devNode) => {
107
+ if (devNode) {
108
+ return devNode;
109
+ }
110
+
111
+ return getProdNode({ dir: process.cwd() });
112
+ };
113
+
114
+ const getAccessibleUrl = async (urls) => {
115
+ if (!urls || urls.length === 0) {
116
+ return '';
117
+ }
118
+
119
+ const ping = async (url) => {
120
+ try {
121
+ // FIXME: 如果通过 ABT_NODE_HOST 指定了 Host IP 地址,这里通过 HTTP 服务检测有问题,所以暂时直接返回 true, 绕过检测
122
+ if (isDocker()) {
123
+ return true;
124
+ }
125
+
126
+ await axios.get(joinURL(new URL(url).origin, WELLKNOWN_PING_PREFIX), { timeout: 5000 });
127
+ return true;
128
+ } catch {
129
+ return false;
130
+ }
131
+ };
132
+
133
+ const results = await evaluateURLs(urls, { checkAccessible: ping });
134
+
135
+ const result = results.find((x) => x.accessible === true);
136
+ if (result) {
137
+ return result.url;
138
+ }
139
+
140
+ let resultUrl = urls.find((url) => isDidDomain(url));
141
+ if (!resultUrl) {
142
+ [resultUrl] = urls; // 假设没有可访问的地址,直接返回第一个
143
+ }
144
+
145
+ print('');
146
+ printWarning(chalk.black(chalk.bgYellow(`The url ${resultUrl} may not be accessible due to network reasons`)));
147
+ return resultUrl;
148
+ };
149
+
150
+ const onProcessClose = (cb) => {
151
+ [
152
+ 'SIGINT',
153
+ 'SIGTERM',
154
+ 'SIGHUP', // the console window is closed
155
+ 'SIGBREAK', // <Ctrl>+<Break> (in Windows)
156
+ ].forEach((sig) => {
157
+ process.on(sig, () => {
158
+ cb();
159
+ });
160
+ });
161
+ };
162
+
163
+ const tipWhenDownloadIsSlow = (getBlocklet) => {
164
+ setTimeout(async () => {
165
+ const blocklet = await getBlocklet();
166
+ if (
167
+ !blocklet ||
168
+ [BlockletStatus.added, BlockletStatus.waiting, BlockletStatus.downloading].includes(blocklet.status)
169
+ ) {
170
+ printWarning('Component bundle download is slow, may be due to network reasons');
171
+ }
172
+ }, 30 * 1000);
173
+ };
174
+
175
+ const waitForAnyEvents = ({ blocklet, socket, events }) =>
176
+ new Promise((resolve) => {
177
+ events.forEach((event) => {
178
+ socket.on(event, (d) => {
179
+ if (d.meta.did === blocklet.meta.did) {
180
+ events.forEach((e) => socket.off(e));
181
+ resolve();
182
+ }
183
+ });
184
+ });
185
+ });
186
+
187
+ const getUtil = ({
188
+ node,
189
+ nodeInfo,
190
+ socket,
191
+ publishEvent,
192
+ meta,
193
+ rootDid,
194
+ mountPoint,
195
+ dir,
196
+ autoOpen,
197
+ e2eMode,
198
+ defaultStoreUrl,
199
+ faucetToken,
200
+ faucetHost,
201
+ autoStartAllComponents,
202
+ }) => {
203
+ let componentDids = [meta.did];
204
+
205
+ const getApp = () => node.getBlocklet({ did: rootDid });
206
+
207
+ // install app container
208
+ const installApp = async ({ updateRouting } = {}) => {
209
+ const exist = await node.getBlocklet({ did: rootDid, attachConfig: false });
210
+ if (exist) {
211
+ // should not be here
212
+ throw new Error('Blocklet already exists');
213
+ }
214
+
215
+ // add
216
+ let blocklet = null;
217
+ try {
218
+ await wrapSpinner(`Installing application ${meta.title}...`, async () => {
219
+ blocklet = await node.devBlocklet(dir, { defaultStoreUrl });
220
+ });
221
+
222
+ if (updateRouting) {
223
+ // Update routing
224
+ await publishEvent(BlockletEvents.installed, { blocklet });
225
+ await waitForAnyEvents({ blocklet, socket, events: [BlockletEvents.installed] }); // wait for db updating
226
+ }
227
+ } catch (err) {
228
+ printError(`Application ${meta.title} install failed: ${err.message}`);
229
+ if (process.env.DEBUG) {
230
+ console.error(err);
231
+ }
232
+ // eslint-disable-next-line no-use-before-define
233
+ await deleteApp();
234
+ process.exit(1);
235
+ }
236
+ };
237
+
238
+ const installComponent = async ({ skipParseDependents } = {}) => {
239
+ // check exist
240
+ const existRoot = await node.getBlocklet({ did: rootDid, attachConfig: false });
241
+
242
+ if (!existRoot) {
243
+ printError('Root blocklet does not exist');
244
+ process.exit(1);
245
+ }
246
+
247
+ // eslint-disable-next-line no-use-before-define
248
+ onProcessClose(stopDev);
249
+
250
+ // add
251
+ let blocklet = null;
252
+ try {
253
+ tipWhenDownloadIsSlow(node.getBlocklet.bind(node, { did: rootDid, attachConfig: false }));
254
+
255
+ const installingTip = `Installing Component ${meta.title}@${meta.version}...`;
256
+ const progressList = [];
257
+ const ref = {};
258
+ const paddingStart = ' ';
259
+ const progressHandler = (data) => {
260
+ const index = progressList.findIndex((x) => x.component?.did === data.component?.did);
261
+ if (data.status === 'completed') {
262
+ if (index !== -1) {
263
+ progressList.splice(index, 1);
264
+ }
265
+ } else if (index !== -1) {
266
+ progressList[index] = data;
267
+ } else {
268
+ progressList.push(data);
269
+ }
270
+ if (ref.spinner) {
271
+ ref.spinner.text = `${installingTip}\n${paddingStart}${getDownloadBundleStep(progressList, {
272
+ paddingStart,
273
+ })}`;
274
+ }
275
+ };
276
+ ref.spinner = await wrapSpinner(
277
+ installingTip,
278
+ async () => {
279
+ node.on(BlockletEvents.downloadBundleProgress, progressHandler);
280
+ blocklet = await node.devBlocklet(dir, { rootDid, mountPoint, defaultStoreUrl, skipParseDependents });
281
+ node.off(BlockletEvents.downloadBundleProgress, progressHandler);
282
+ ref.spinner = null;
283
+ },
284
+ { ref }
285
+ );
286
+ } catch (err) {
287
+ printError(`Blocklet ${meta.title}@${meta.version} install failed: ${err.message}`);
288
+ if (process.env.DEBUG) {
289
+ console.error(err);
290
+ }
291
+ // eslint-disable-next-line no-use-before-define
292
+ await deleteComponent();
293
+ process.exit(1);
294
+ }
295
+
296
+ // Update routing
297
+ await publishEvent(BlockletEvents.upgraded, { blocklet });
298
+ await waitForAnyEvents({ blocklet, socket, events: [BlockletEvents.upgraded] }); // wait for db updating
299
+
300
+ // send componentInternalInfo to other components
301
+ await publishEvent(BlockletInternalEvents.componentInstalled, {
302
+ appDid: blocklet.appDid,
303
+ components: getComponentsInternalInfo(blocklet).filter((x) => x.did === meta.did),
304
+ });
305
+
306
+ printSuccess(`Component ${meta.title}@${meta.version} was successfully installed`);
307
+
308
+ // Publish event
309
+ try {
310
+ await publishEvent(BlockletEvents.updated, blocklet);
311
+ await sleep(200);
312
+ } catch (err) {
313
+ printError(`Failed to publish event to socket server: ${err.message}`);
314
+ }
315
+
316
+ // reset config
317
+ if (!isGatewayBlocklet(meta)) {
318
+ const configs = (meta.environments || [])
319
+ .filter((x) => x.name !== 'CHAIN_TYPE')
320
+ .map((x) => {
321
+ const { name, default: defaultValue, ...opts } = x;
322
+ return {
323
+ ...opts,
324
+ key: name,
325
+ value: defaultValue || '',
326
+ };
327
+ });
328
+ await node.configBlocklet({ did: [rootDid, meta.did], configs, skipHook: true });
329
+ }
330
+
331
+ return blocklet;
332
+ };
333
+
334
+ const deleteApp = async ({ keepData = true, checkDevMode, silent } = {}) => {
335
+ if (checkDevMode) {
336
+ const blocklet = await node.getBlocklet({ did: rootDid, attachConfig: false });
337
+ if (blocklet) {
338
+ if (!isDevelopmentMode(blocklet)) {
339
+ printError('Blocklet in production mode cannot be deleted');
340
+ process.exit(1);
341
+ }
342
+ }
343
+ }
344
+
345
+ try {
346
+ const keepConfigs = keepData;
347
+ const keepRouting = keepData;
348
+ const deleted = await node.deleteBlocklet({
349
+ did: rootDid,
350
+ keepData,
351
+ keepLogsDir: false,
352
+ keepConfigs,
353
+ });
354
+ await publishEvent(BlockletEvents.removed, { blocklet: deleted, context: { keepRouting } });
355
+ if (!silent) {
356
+ print(`Application ${deleted.meta.title} was removed`);
357
+ }
358
+ } catch (error) {
359
+ // do nothing
360
+ }
361
+ await sleep(200);
362
+ };
363
+
364
+ const deleteComponent = async ({ keepData = true, checkDevMode, silent } = {}) => {
365
+ if (checkDevMode) {
366
+ const rootBlocklet = await node.getBlocklet({ did: rootDid, attachConfig: false });
367
+ if (rootBlocklet) {
368
+ const blocklet = rootBlocklet.children.find((x) => x.meta.did === meta.did);
369
+ if (blocklet) {
370
+ if (!isDevelopmentMode(blocklet)) {
371
+ printError('Component in production mode cannot be deleted');
372
+ process.exit(1);
373
+ }
374
+ }
375
+ }
376
+ }
377
+
378
+ try {
379
+ const rootBlocklet = await node.deleteComponent({
380
+ did: meta.did,
381
+ rootDid,
382
+ keepData,
383
+ keepState: false,
384
+ });
385
+ await publishEvent(BlockletEvents.upgraded, { blocklet: rootBlocklet });
386
+ if (!silent) {
387
+ print(`Component ${meta.title} was removed`);
388
+ }
389
+ } catch (error) {
390
+ // do nothing
391
+ }
392
+ await sleep(200);
393
+ };
394
+
395
+ const stopDev = async () => {
396
+ await lock.acquire();
397
+ print('\nDisconnecting from Blocklet Server daemon....');
398
+ socket.disconnect(() => {
399
+ return deleteComponent().then(() => process.exit(0));
400
+ });
401
+ };
402
+
403
+ const saveWebPort = async () => {
404
+ try {
405
+ const blocklet = await node.getBlocklet({ did: rootDid });
406
+ const child = blocklet.children.find((x) => x.meta.did === meta.did);
407
+ const { ports } = child;
408
+ const { BLOCKLET_PORT } = ports;
409
+ if (BLOCKLET_PORT) {
410
+ const str = `BLOCKLET_PORT=${BLOCKLET_PORT}`;
411
+ const envFile = path.join(process.cwd(), '.env.development.local');
412
+ if (!fs.existsSync(envFile)) {
413
+ fs.writeFileSync(envFile, str);
414
+ } else {
415
+ const content = fs.readFileSync(envFile, 'utf-8');
416
+ if (!content.includes('BLOCKLET_PORT')) {
417
+ fs.writeFileSync(envFile, `${content}\n${str}`);
418
+ } else if (!content.includes(str)) {
419
+ print('');
420
+ printWarning(
421
+ `The port of ${meta.title} has changed to ${chalk.cyan(
422
+ BLOCKLET_PORT
423
+ )} due to conflict , check your ${chalk.cyan('.env.development.local')} file to see the change.`
424
+ );
425
+ fs.writeFileSync(envFile, content.replace(/BLOCKLET_PORT=.*/, str));
426
+ }
427
+ }
428
+ }
429
+ } catch (error) {
430
+ printError(`Failed to save port: ${error.message}`);
431
+ }
432
+ };
433
+
434
+ const start = async ({ ignoreCloseEvent, componentDids: _componentDids } = {}) => {
435
+ componentDids = uniq(_componentDids);
436
+
437
+ if (!ignoreCloseEvent) {
438
+ onProcessClose(stopDev);
439
+ }
440
+
441
+ try {
442
+ const blocklet = await node.ensureBlockletIntegrity(rootDid);
443
+ const child = blocklet.children.find((x) => x.meta.did === meta.did);
444
+ debug('child in db', child);
445
+
446
+ if (!isDevelopmentMode(child)) {
447
+ print();
448
+ const command = ` ${getCLIBinaryName()} dev install --app-did ${rootDid}${mountPoint ? ' --mount-point ' : ''}${mountPoint || ''}`; // prettier-ignore
449
+ printError('The component of production mode already exists, please reinstall it before developing:');
450
+ print(chalk.cyan(command));
451
+ print();
452
+ process.exit(1);
453
+ }
454
+
455
+ // ensure environments
456
+ await ensureBlockletEnv(node, blocklet, dir);
457
+
458
+ setTimeout(async () => {
459
+ // start
460
+ try {
461
+ blocklet.status = BlockletStatus.starting;
462
+ await publishEvent(BlockletEvents.statusChange, blocklet);
463
+ } catch (err) {
464
+ printError(`Failed to publish deploy event to socket server: ${err.message}`);
465
+ }
466
+
467
+ // pipe logs to console
468
+ // NOTE: this is postponed because the log subscribe will fail before blocklet start
469
+ socket.on(`log.blocklet-${blocklet.meta.did}/${child.meta.did}`, (log) => {
470
+ if (log.level === 'error') {
471
+ console.error(log.data);
472
+ } else {
473
+ // eslint-disable-next-line no-console
474
+ console.log(log.data);
475
+ }
476
+ });
477
+ }, 500);
478
+ await node.startBlocklet({
479
+ did: rootDid,
480
+ throwOnError: true,
481
+ checkHealthImmediately: true,
482
+ e2eMode,
483
+ atomic: true,
484
+ componentDids: autoStartAllComponents ? null : componentDids,
485
+ });
486
+
487
+ blocklet.status = BlockletStatus.running;
488
+ blocklet.children.find((x) => x.meta.did === meta.did).status = BlockletStatus.running;
489
+
490
+ await publishEvent(BlockletEvents.statusChange, blocklet);
491
+
492
+ // send componentInternalInfo to other components
493
+ await publishEvent(BlockletInternalEvents.componentStarted, {
494
+ appDid: blocklet.appDid,
495
+ components: getComponentsInternalInfo(blocklet).filter((x) => x.did === meta.did),
496
+ });
497
+
498
+ await sleep(1000);
499
+ printSuccess(`Blocklet ${meta.title}@${meta.version} was successfully started`);
500
+
501
+ const blockletPort = blocklet.environmentObj?.BLOCKLET_PORT;
502
+
503
+ let url;
504
+ if (codespaces.isCodespaces()) {
505
+ await node
506
+ .addRoutingRuleToDefaultSite({
507
+ from: { pathPrefix: '/' },
508
+ to: {
509
+ type: ROUTING_RULE_TYPES.BLOCKLET,
510
+ port: blockletPort,
511
+ did: blocklet.meta.did,
512
+ interfaceName: 'publicUrl',
513
+ },
514
+ })
515
+ .then(() => {
516
+ printSuccess('Routing rule was successfully added to default site.');
517
+ })
518
+ .catch((error) => {
519
+ debug(error);
520
+ printWarning(
521
+ `Failed to add routing rule to default site. To manually add the routing rule, please follow the GitHub Codespaces development guide at: ${chalk.cyan(HELP_DOCS_GITHUB_CODESPACES_URL)}`
522
+ );
523
+ });
524
+
525
+ await node
526
+ .addDomainAlias({
527
+ id: blocklet.site?.id,
528
+ domainAlias: codespaces.getDomain(nodeInfo.routing.httpsPort),
529
+ inBlockletSetup: false,
530
+ force: true,
531
+ issueCert: false,
532
+ })
533
+ .then(() => {
534
+ printSuccess('Domain on codespaces was successfully added to current blocklet.');
535
+ })
536
+ .catch((error) => {
537
+ if (!/already exists/i.test(error.message)) {
538
+ debug(error);
539
+ printWarning(
540
+ `Failed to add domain on codespaces to current blocklet, you may handle it manually, for more help please refer the GitHub Codespaces development guide at: ${chalk.cyan(HELP_DOCS_GITHUB_CODESPACES_URL)}`
541
+ );
542
+ }
543
+ });
544
+
545
+ url = codespaces.getAccessUrl({ port: nodeInfo.routing.httpsPort });
546
+ printInfo('Developing blocklet in codespaces');
547
+ } else {
548
+ // print info
549
+ const info = await node.getNodeInfo();
550
+ const port = Number(info.routing.httpsPort) !== 443 ? `:${info.routing.httpsPort}` : '';
551
+ url = await getDevUrl({
552
+ getUrl: async () => {
553
+ const ips = await getIP();
554
+ const urls = blocklet.site.domainAliases.map(
555
+ (x) => `https://${replaceSlotToIp(x.value, ips.internal)}${port}`
556
+ );
557
+
558
+ return getAccessibleUrl(urls);
559
+ },
560
+ });
561
+ }
562
+
563
+ if (url) {
564
+ const childUrl = joinURL(url, child.mountPoint);
565
+ if (autoOpen) {
566
+ await open(childUrl);
567
+ }
568
+
569
+ // print blocklet entry
570
+ if (!hasStartEngine(meta)) {
571
+ print('');
572
+ printInfo('The blocklet has no running process');
573
+ printInfo('You can access with the following URL\n');
574
+ const [{ url: endpoint }] = await getBaseUrls(node, []);
575
+ print(`- ${chalk.cyan(joinURL(endpoint, '/blocklets', rootDid || meta.did, '/components'))}`);
576
+ } else {
577
+ print('');
578
+ printInfo('You can access with the following URL\n');
579
+ print(`- ${chalk.cyan(childUrl)}`);
580
+ }
581
+ }
582
+
583
+ print('');
584
+ printInfo('Note that your blocklet is running in development in Blocklet Server,');
585
+ // eslint-disable-next-line max-len
586
+ printInfo(
587
+ `To run it in production mode, you can use ${chalk.cyan(`${getCLIBinaryName()} bundle`)} and then ${chalk.cyan(
588
+ `${getCLIBinaryName()} deploy`
589
+ )}.`
590
+ );
591
+
592
+ printBlockletDevelopmentGuide();
593
+
594
+ saveWebPort();
595
+ } catch (err) {
596
+ printError(err.message);
597
+ await stopDev();
598
+ }
599
+ };
600
+
601
+ const resetApp = async () => {
602
+ const blocklet = await node.getBlocklet({ did: rootDid });
603
+
604
+ if (blocklet) {
605
+ if (isInProgress(blocklet.status)) {
606
+ printError('Please stop development before reset data');
607
+ process.exit(1);
608
+ }
609
+
610
+ await node.resetBlocklet({ did: rootDid });
611
+ } else {
612
+ await node.devBlocklet(dir, { defaultStoreUrl }); // install blocklet and then remove blocklet and all data
613
+ await deleteApp({
614
+ keepData: false,
615
+ silent: true,
616
+ });
617
+ }
618
+
619
+ print(`\nApplication ${meta.title} has been reset\n`);
620
+ };
621
+
622
+ const resetComponent = async () => {
623
+ const rootBlocklet = await node.getBlocklet({ did: rootDid });
624
+
625
+ if (!rootBlocklet) {
626
+ printError('Root blocklet does not exist');
627
+ process.exit(1);
628
+ }
629
+
630
+ if (isInProgress(rootBlocklet.status)) {
631
+ printError('Please stop development before reset data');
632
+ process.exit(1);
633
+ }
634
+
635
+ const blocklet = rootBlocklet.children.find((x) => x.meta.did === meta.did);
636
+
637
+ if (blocklet) {
638
+ await node.resetBlocklet({ did: rootDid, childDid: meta.did });
639
+ } else {
640
+ await node.devBlocklet(dir, { rootDid, mountPoint, defaultStoreUrl, skipParseDependents: true }); // install component and then remove blocklet and all data
641
+ await deleteComponent({
642
+ keepData: false,
643
+ silent: true,
644
+ });
645
+ }
646
+
647
+ print(`\nComponent ${meta.title} of ${rootBlocklet.meta.title} has been reset\n`);
648
+ };
649
+
650
+ const faucet = async () => {
651
+ if (!faucetToken) {
652
+ printError('Please provide a valid token address');
653
+ return;
654
+ }
655
+ const { data: tokens } = await axios.get(joinURL(faucetHost, '/api/tokens'), { timeout: 8000 });
656
+ const token = tokens.find((x) => x.symbol === faucetToken || x.address === faucetToken);
657
+ if (!token) {
658
+ printError(`Token ${faucetToken} is not supported by faucet: ${faucetHost}`);
659
+ return;
660
+ }
661
+
662
+ try {
663
+ const blocklet = await node.getBlocklet({ did: rootDid });
664
+ if (!blocklet) {
665
+ throw new Error('Application does not exist');
666
+ }
667
+ const result = await wrapSpinner(`Claim ${token.symbol} from faucet ${faucetHost}...`, async () => {
668
+ const keys = Object.keys(BLOCKLET_CONFIGURABLE_KEY);
669
+ const { wallet } = getBlockletInfo(
670
+ {
671
+ meta: blocklet.meta,
672
+ environments: keys.map((key) => ({ key, value: blocklet.configObj[key] })).filter((x) => x.value),
673
+ },
674
+ nodeInfo.sk
675
+ );
676
+
677
+ const { data } = await axios.post(
678
+ joinURL(faucetHost, '/api/claim'),
679
+ {
680
+ userPk: toBase58(wallet.publicKey),
681
+ userInfo: Jwt.sign(wallet.address, wallet.secretKey, { token: token.address }),
682
+ },
683
+ { timeout: 8000 }
684
+ );
685
+
686
+ if (Jwt.verify(data.authInfo, fromBase58(data.appPk))) {
687
+ const decoded = Jwt.decode(data.authInfo);
688
+ if (decoded.status !== 'ok') {
689
+ throw new Error(decoded.errorMessage);
690
+ }
691
+
692
+ if (decoded.response.error) {
693
+ throw new Error(decoded.response.error);
694
+ }
695
+
696
+ const client = new Client(token.chainHost);
697
+ const { info } = await client.getTx({ hash: decoded.response.hash });
698
+ const receipt = info.receipts.find((x) => x.address === wallet.address);
699
+ if (receipt) {
700
+ return {
701
+ hash: decoded.response.hash,
702
+ amount: receipt.changes.find((x) => x.target === token.address).value,
703
+ };
704
+ }
705
+
706
+ return { hash: decoded.response.hash, amount: 0 };
707
+ }
708
+
709
+ throw new Error('invalid response from faucet');
710
+ });
711
+
712
+ printInfo(`Claimed ${fromUnitToToken(result.amount, token.decimal)} ${token.symbol} from faucet`);
713
+ printInfo(joinURL(token.chainHost.replace('/api', ''), '/explorer/txs', result.hash));
714
+ } catch (err) {
715
+ printError(`Failed to claim from faucet: ${err.message}`);
716
+ }
717
+ };
718
+
719
+ const faucetApp = async () => {
720
+ const exist = await getApp();
721
+ if (!exist) {
722
+ await installApp();
723
+ }
724
+
725
+ await faucet();
726
+ if (!exist) {
727
+ await deleteApp();
728
+ }
729
+ };
730
+
731
+ const studio = async () => {
732
+ let blocklet = await getApp();
733
+ if (!blocklet) {
734
+ await installApp({ updateRouting: true });
735
+ blocklet = await getApp();
736
+ } else if (!findComponentByIdV2(blocklet, meta.did)) {
737
+ await installComponent({ skipParseDependents: true });
738
+ }
739
+
740
+ checkBlockletMode(blocklet);
741
+
742
+ const info = await node.getNodeInfo();
743
+ const port = Number(info.routing.httpsPort) !== 443 ? `:${info.routing.httpsPort}` : '';
744
+ const baseUrl = await getDevUrl({
745
+ getUrl: async () => {
746
+ const ips = await getIP();
747
+ const urls = blocklet.site.domainAliases.map((x) => `https://${replaceSlotToIp(x.value, ips.internal)}${port}`);
748
+ return getAccessibleUrl(urls);
749
+ },
750
+ });
751
+ await open(joinURL(baseUrl, WELLKNOWN_SERVICE_PATH_PREFIX, '/studio/home'));
752
+ };
753
+
754
+ return {
755
+ getApp,
756
+ installApp,
757
+ installComponent,
758
+ start,
759
+ resetApp,
760
+ resetComponent,
761
+ deleteApp,
762
+ deleteComponent,
763
+ faucetApp,
764
+ faucetComponent: faucet,
765
+ studio,
766
+ };
767
+ };
768
+
769
+ const run = async (
770
+ action,
771
+ {
772
+ open: autoOpen,
773
+ host: faucetHost,
774
+ token: faucetToken,
775
+ devNode,
776
+ e2eMode,
777
+ appId: inputRootDid,
778
+ mountPoint: inputMountPoint,
779
+ storeUrl,
780
+ startAllComponents: autoStartAllComponents,
781
+ }
782
+ ) => {
783
+ checkTerminalProxy('the command');
784
+
785
+ const defaultStoreUrl = storeUrl || process.env.COMPONENT_STORE_URL;
786
+
787
+ try {
788
+ const dir = process.cwd();
789
+
790
+ // get meta
791
+ let meta;
792
+ try {
793
+ meta = getBlockletMeta(dir, {
794
+ schemaOptions: { stripUnknown: false },
795
+ defaultStoreUrl: defaultStoreUrl ? wrapDefaultStoreUrl(defaultStoreUrl) : null,
796
+ });
797
+ } catch (err) {
798
+ printError(err.message);
799
+ if (err.message.includes("missing 'store'")) {
800
+ print('');
801
+ printInfo('Please confirm that blocklet.yml is filled in components[].source.store');
802
+ printInfo(
803
+ `You can also dynamically config COMPONENT_STORE_URL in environment or .env file. e.g.: ${chalk.cyan(
804
+ 'COMPONENT_STORE_URL="https://store.blocklet.dev"'
805
+ )}`
806
+ );
807
+ }
808
+ process.exit(1);
809
+ }
810
+ debug('blocklet meta', meta);
811
+
812
+ if (hasReservedKey(meta.environments)) {
813
+ printError('Blocklet key of environments can not start with `ABT_NODE_` or `BLOCKLET_`');
814
+ process.exit(1);
815
+ }
816
+
817
+ if (!devNode) {
818
+ await checkNodeRunning();
819
+ }
820
+
821
+ // rootDid
822
+ let rootDid;
823
+ if (inputRootDid) {
824
+ rootDid = inputRootDid;
825
+ } else if (!inputRootDid && process.env.BLOCKLET_DEV_APP_DID) {
826
+ printInfo(`Use appDid from env: ${chalk.cyan(process.env.BLOCKLET_DEV_APP_DID)}`);
827
+ rootDid = process.env.BLOCKLET_DEV_APP_DID;
828
+ }
829
+ if (rootDid) {
830
+ if (isValidDid(rootDid) === false) {
831
+ printError(`appDid is not valid: ${rootDid}`);
832
+ process.exit(1);
833
+ }
834
+
835
+ rootDid = toAddress(rootDid);
836
+ }
837
+
838
+ // mountPoint
839
+ let mountPoint;
840
+ if (inputMountPoint) {
841
+ mountPoint = inputMountPoint;
842
+ } else if (rootDid && !inputMountPoint && process.env.BLOCKLET_DEV_MOUNT_POINT) {
843
+ printInfo(`Use mountPoint from env: ${chalk.cyan(process.env.BLOCKLET_DEV_MOUNT_POINT)}`);
844
+ mountPoint = process.env.BLOCKLET_DEV_MOUNT_POINT;
845
+ } else if (!rootDid) {
846
+ mountPoint = '/';
847
+ }
848
+
849
+ printInfo('Try to dev blocklet from', chalk.cyan(dir));
850
+ print('');
851
+
852
+ const { node, publishEvent, getWsClient } = await getNode(devNode);
853
+ const nodeInfo = await node.getNodeInfo();
854
+ const socket = await getWsClient();
855
+
856
+ const params = {
857
+ dir,
858
+ meta,
859
+ autoOpen,
860
+ faucetHost,
861
+ faucetToken,
862
+ devNode,
863
+ e2eMode,
864
+ rootDid: rootDid || meta.did,
865
+ mountPoint,
866
+ node,
867
+ publishEvent,
868
+ getWsClient,
869
+ nodeInfo,
870
+ socket,
871
+ defaultStoreUrl,
872
+ autoStartAllComponents,
873
+ };
874
+
875
+ const util = getUtil(params);
876
+
877
+ node.onReady(async () => {
878
+ if (action === ACTIONS.INSTALL) {
879
+ print('');
880
+ printError(
881
+ `${chalk.cyan(`${getCLIBinaryName()} dev install`)} is not yet supported, please use ${chalk.cyan(
882
+ `${getCLIBinaryName()} dev`
883
+ )} instead`
884
+ );
885
+ print('');
886
+ process.exit(0);
887
+ }
888
+
889
+ if (action === ACTIONS.REMOVE) {
890
+ const app = await util.getApp();
891
+ if (!app) {
892
+ printError('Application does not exist');
893
+ process.exit(1);
894
+ }
895
+ if (rootDid) {
896
+ const component = await findComponentByIdV2(app, meta.did);
897
+ if (!component) {
898
+ printError('Component does not exist');
899
+ process.exit(1);
900
+ }
901
+ await util.deleteComponent({ checkDevMode: true });
902
+ } else {
903
+ await util.deleteApp({ checkDevMode: true });
904
+ }
905
+ process.exit(0);
906
+ }
907
+
908
+ if (action === ACTIONS.START) {
909
+ print('');
910
+ printError(
911
+ `${chalk.cyan(`${getCLIBinaryName()} dev start`)} is not yet supported, please use ${chalk.cyan(
912
+ `${getCLIBinaryName()} dev`
913
+ )} instead`
914
+ );
915
+ print('');
916
+ process.exit(0);
917
+ }
918
+
919
+ if (action === ACTIONS.RESET) {
920
+ if (rootDid) {
921
+ await util.resetComponent();
922
+ } else {
923
+ await util.resetApp();
924
+ }
925
+ process.exit(0);
926
+ }
927
+
928
+ if (action === ACTIONS.FAUCET) {
929
+ if (rootDid) {
930
+ await util.faucetComponent();
931
+ } else {
932
+ await util.faucetApp();
933
+ }
934
+ process.exit(0);
935
+ }
936
+
937
+ if (action === ACTIONS.STUDIO) {
938
+ const studioUtil = getUtil({ ...params, rootDid: meta.did });
939
+ await studioUtil.studio();
940
+
941
+ process.exit(0);
942
+ }
943
+
944
+ if (!(await util.getApp())) {
945
+ await util.installApp();
946
+ }
947
+
948
+ const { componentDids } = await util.installComponent();
949
+ if (isGatewayBlocklet(meta)) {
950
+ printInfo('Gateway blocklet detected, skip starting');
951
+ const [{ url: endpoint }] = await getBaseUrls(node, []);
952
+ printInfo(
953
+ `Please open ${chalk.cyan(
954
+ joinURL(endpoint, '/blocklets', rootDid || meta.did, '/components')
955
+ )} to start the blocklet`
956
+ );
957
+ process.exit(0);
958
+ }
959
+ await util.start({ ignoreCloseEvent: true, componentDids });
960
+ });
961
+ } catch (err) {
962
+ printError(err.message);
963
+ process.exit(1);
964
+ }
965
+ };
966
+
967
+ const getOption = (command, key) => {
968
+ if (get(command, `parent.${key}`) !== undefined) {
969
+ return get(command, `parent.${key}`);
970
+ }
971
+
972
+ return get(command, key);
973
+ };
974
+
975
+ const getOptions = (command) => ({
976
+ open: getOption(command, 'open'),
977
+ host: getOption(command, 'host'),
978
+ token: getOption(command, 'token'),
979
+ e2eMode: getOption(command, 'e2e'),
980
+ devNode: getOption(command, 'devNode'),
981
+ appId: getOption(command, 'appId') || getOption(command, 'appDid'),
982
+ mountPoint: getOption(command, 'mountPoint'),
983
+ studio: getOption(command, 'studio'),
984
+ storeUrl: getOption(command, 'storeUrl'),
985
+ startAllComponents: getOption(command, 'startAllComponents'),
986
+ });
987
+
988
+ module.exports = {
989
+ run: (command) => run(null, getOptions(command)),
990
+ install: (command) => run('install', getOptions(command)),
991
+ start: (command) => run('start', getOptions(command)),
992
+ remove: (command) => run('remove', getOptions(command)),
993
+ reset: (command) => run('reset', getOptions(command)),
994
+ faucet: (command) => run('faucet', getOptions(command)),
995
+ studio: (command) => run('studio', getOptions(command)),
996
+ waitForAnyEvents,
997
+ getAccessibleUrl,
998
+ checkNodeRunning,
999
+ getOption,
1000
+ };