@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,435 @@
1
+ /* eslint-disable indent */
2
+ require('dotenv-flow').config({ silent: true, node_env: 'development' });
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const expandTilde = require('expand-tilde');
7
+ const tar = require('tar');
8
+ const FormData = require('form-data');
9
+ const chalk = require('chalk');
10
+ const slugify = require('slugify');
11
+ const { joinURL } = require('ufo');
12
+
13
+ const { isValid: isValidDid, toAddress } = require('@arcblock/did');
14
+ const Client = require('@abtnode/client');
15
+ const hashFiles = require('@abtnode/util/lib/hash-files');
16
+ const { default: axios } = require('axios');
17
+ const isPathPrefixEqual = require('@abtnode/util/lib/is-path-prefix-equal');
18
+ const getBlockletMeta = require('@blocklet/meta/lib/parse');
19
+ const validateBlockletEntry = require('@blocklet/meta/lib/entry');
20
+ const { hasMountPoint } = require('@blocklet/meta/lib/engine');
21
+ const { BLOCKLET_BUNDLE_FOLDER } = require('@blocklet/constant');
22
+ const hasReservedKey = require('@blocklet/meta/lib/has-reserved-key');
23
+ const urlPathFriendly = require('@blocklet/meta/lib/url-path-friendly').default;
24
+ const { ROLES } = require('@abtnode/constant');
25
+
26
+ const {
27
+ print,
28
+ printError,
29
+ printInfo,
30
+ printSuccess,
31
+ printWarning,
32
+ formatGQLError,
33
+ getCLIBinaryName,
34
+ } = require('../../util');
35
+ const { wrapSpinner } = require('../../ui');
36
+ const { getNode } = require('../../node');
37
+ const { checkRunning, deployManager } = require('../../manager');
38
+ const { version } = require('../../../package.json');
39
+ const debug = require('../../debug')('blocklet:deploy');
40
+
41
+ const { signWithAccessKey, validateAccessKey } = Client;
42
+ const { fileFilter, printDeployFileInfo } = deployManager;
43
+
44
+ const makeFormData = ({ tarFile: file, hasDiff, did, serverVersion, deleteSet, rootDid, mountPoint }) => {
45
+ let varFields = hasDiff
46
+ ? '$file: Upload!, $did: String, $diffVersion: String, $deleteSet: [String!]'
47
+ : '$file: Upload!';
48
+ let inputFields = hasDiff
49
+ ? 'file: $file, did: $did, diffVersion: $diffVersion, deleteSet: $deleteSet'
50
+ : 'file: $file';
51
+
52
+ varFields = `${varFields}, $rootDid: String, $mountPoint: String`;
53
+ inputFields = `${inputFields}, rootDid: $rootDid, mountPoint: $mountPoint`;
54
+
55
+ const variables = hasDiff
56
+ ? {
57
+ file: null,
58
+ did,
59
+ diffVersion: serverVersion,
60
+ deleteSet,
61
+ }
62
+ : {
63
+ file: null,
64
+ };
65
+
66
+ variables.rootDid = rootDid;
67
+ variables.mountPoint = mountPoint;
68
+
69
+ const apiName = 'installComponent';
70
+ const query = `
71
+ mutation (${varFields}) {
72
+ ${apiName}(input: { ${inputFields} } ) {
73
+ code
74
+ blocklet {
75
+ meta {
76
+ did
77
+ name
78
+ title
79
+ version
80
+ description
81
+ }
82
+ status
83
+ source
84
+ }
85
+ }
86
+ }
87
+ `;
88
+ const gql = {
89
+ query,
90
+ variables,
91
+ };
92
+ const map = {
93
+ file0: ['variables.file'],
94
+ };
95
+ const form = new FormData();
96
+ form.append('operations', JSON.stringify(gql));
97
+ form.append('map', JSON.stringify(map));
98
+ form.append('file0', fs.createReadStream(file));
99
+ return { form, apiName };
100
+ };
101
+
102
+ const deploy = async (dir, { endpoint, accessKey, accessSecret, appDid, appId, mountPoint: inputMountPoint }) => {
103
+ let bundleDir = dir;
104
+ if (fs.existsSync(path.join(dir, BLOCKLET_BUNDLE_FOLDER))) {
105
+ bundleDir = path.join(dir, BLOCKLET_BUNDLE_FOLDER);
106
+ }
107
+
108
+ const rootDid = appDid || appId;
109
+
110
+ const fullEndpoint = joinURL(endpoint, '/api/gql');
111
+ const client = new Client(fullEndpoint, `BlockletCLI/${version}`);
112
+ client.setAuthAccessKey({
113
+ accessKeyId: accessKey,
114
+ accessKeySecret: accessSecret,
115
+ });
116
+
117
+ // check mountPoint when deploy a component
118
+ const { blocklet: app } = await client.getBlocklet({
119
+ input: {
120
+ did: rootDid,
121
+ attachRuntimeInfo: false,
122
+ },
123
+ });
124
+
125
+ if (!app) {
126
+ printError(`App ${chalk.cyan(rootDid)} not found`);
127
+ process.exit(1);
128
+ }
129
+
130
+ if (new URL(endpoint).protocol === 'http:' && new URL(endpoint).hostname !== '127.0.0.1') {
131
+ printWarning(
132
+ `You are deploying to an insecure endpoint of ${chalk.cyan('http')} protocol, you should use ${chalk.cyan(
133
+ 'https'
134
+ )} to achieve better security.`
135
+ );
136
+ }
137
+
138
+ // get local blocklet meta
139
+ let localMeta;
140
+ try {
141
+ localMeta = getBlockletMeta(bundleDir);
142
+ } catch (error) {
143
+ debug('get blocklet error', error);
144
+ throw new Error(`Get blocklet meta failed: ${error.message}`);
145
+ }
146
+
147
+ printInfo(
148
+ `Try to deploy ${chalk.cyan(`${localMeta.title}@${localMeta.version}`)} from`,
149
+ `${chalk.cyan(bundleDir)} to ${chalk.cyan(app.meta.title)} in server ${chalk.cyan(endpoint)}.`
150
+ );
151
+
152
+ if (hasReservedKey(localMeta.environments)) {
153
+ throw new Error('Blocklet key of environments can not start with `ABT_NODE_` or `BLOCKLET_`');
154
+ }
155
+
156
+ // mountPoint
157
+ let mountPoint;
158
+ if (hasMountPoint(localMeta)) {
159
+ if (inputMountPoint) {
160
+ mountPoint = inputMountPoint;
161
+ printInfo(`Use mountPoint from input: ${chalk.cyan(inputMountPoint)}`);
162
+ } else if (process.env.BLOCKLET_DEPLOY_MOUNT_POINT) {
163
+ printInfo(`Use mountPoint from env: ${chalk.cyan(process.env.BLOCKLET_DEPLOY_MOUNT_POINT)}`);
164
+ mountPoint = process.env.BLOCKLET_DEPLOY_MOUNT_POINT;
165
+ } else {
166
+ const { name, title } = localMeta;
167
+ mountPoint = `/${urlPathFriendly(title) || urlPathFriendly(name)}`.toLowerCase();
168
+ printInfo(`Use default mountPoint: ${chalk.cyan(mountPoint)}`);
169
+ }
170
+ } else {
171
+ mountPoint = `/${localMeta.did}`;
172
+ printInfo(`Use fake mountPoint: ${chalk.cyan(mountPoint)}`);
173
+ }
174
+
175
+ // ensure entry file/folder exist
176
+ try {
177
+ await validateBlockletEntry(bundleDir, localMeta);
178
+ } catch (err) {
179
+ throw new Error(err.message);
180
+ }
181
+
182
+ // get hashFiles
183
+ const { files } = await hashFiles(bundleDir);
184
+
185
+ // check node_modules
186
+ const fileNames = Object.keys(files);
187
+ for (let i = 0; i < fileNames.length; i++) {
188
+ if (fileNames[i].includes('node_modules')) {
189
+ printError(`${chalk.bold('node_modules')} cannot be in the list of uploaded files`);
190
+ process.exit();
191
+ }
192
+ }
193
+
194
+ let blockletDiff;
195
+ try {
196
+ blockletDiff = await client.getBlockletDiff({
197
+ input: {
198
+ did: localMeta.did,
199
+ hashFiles: Object.entries(files).map(([file, hash]) => ({ file, hash })),
200
+ rootDid: rootDid || '',
201
+ },
202
+ });
203
+ } catch (error) {
204
+ throw new Error(`Blocklet deploy failed when fetching diff: ${formatGQLError(error)}`);
205
+ }
206
+
207
+ const {
208
+ addSet = [],
209
+ changeSet = [],
210
+ deleteSet = [],
211
+ hasBlocklet: hasDiff,
212
+ version: serverVersion,
213
+ } = blockletDiff.blockletDiff || {};
214
+ const diffList = hasDiff ? [...addSet, ...changeSet] : null;
215
+
216
+ // if no diff and mountPoint not changed, stop deploy
217
+ if (hasDiff && localMeta.version === serverVersion && !addSet.length && !changeSet.length && !deleteSet.length) {
218
+ let needNotDeploy = true;
219
+ const child = app.children.find((x) => x.meta.did === localMeta.did);
220
+ needNotDeploy = isPathPrefixEqual(mountPoint, child.mountPoint);
221
+
222
+ if (needNotDeploy) {
223
+ print(`${localMeta.title}@${localMeta.version} already exists in ${app.meta.title}`);
224
+ return;
225
+ }
226
+ }
227
+ printInfo(`Name: ${chalk.cyan(localMeta.name)}`);
228
+ printInfo(`DID: ${chalk.cyan(localMeta.did)}`);
229
+ printInfo(`Version: ${chalk.cyan(localMeta.version)}`);
230
+
231
+ if (hasDiff) {
232
+ printDeployFileInfo(addSet, changeSet, deleteSet);
233
+ } else {
234
+ printDeployFileInfo(Object.keys(files));
235
+ }
236
+
237
+ // build tarFile
238
+ const tarFile = path.join(process.cwd(), `${slugify(localMeta.name)}-${localMeta.version}.tgz`);
239
+
240
+ await tar.c(
241
+ {
242
+ file: tarFile,
243
+ cwd: bundleDir,
244
+ filter: (f) =>
245
+ !diffList ||
246
+ fileFilter(f.replace(/^\.\//, ''), {
247
+ diffList,
248
+ }),
249
+ },
250
+ ['.']
251
+ );
252
+
253
+ // do upload
254
+ const { form } = makeFormData({
255
+ tarFile,
256
+ hasDiff,
257
+ did: localMeta.did,
258
+ serverVersion,
259
+ deleteSet,
260
+ mountPoint,
261
+ rootDid,
262
+ });
263
+ try {
264
+ const timestamp = Date.now();
265
+ const timer = setInterval(() => {
266
+ // travis will close task if no output has been received in the last 10m0s
267
+ if (process.env.CI && process.env.TRAVIS) {
268
+ print('Uploading...');
269
+ }
270
+ }, 1000 * 500);
271
+ const res = await wrapSpinner(`Uploading ${localMeta.name}...`, () =>
272
+ axios({
273
+ url: fullEndpoint,
274
+ method: 'POST',
275
+ data: form,
276
+ headers: {
277
+ ...form.getHeaders(),
278
+ 'user-agent': `BlockletCLI/${version}`,
279
+ 'x-access-key-id': accessKey,
280
+ 'x-access-stamp': timestamp,
281
+ 'x-access-signature': signWithAccessKey({
282
+ accessKeyId: accessKey,
283
+ accessKeySecret: accessSecret,
284
+ message: `${timestamp}-${accessKey}`,
285
+ }),
286
+ },
287
+ timeout: 1000 * 60 * 30, // 30min
288
+ // max file size is limited by server, so here is infinity
289
+ maxContentLength: Number.POSITIVE_INFINITY,
290
+ maxBodyLength: Number.POSITIVE_INFINITY,
291
+ })); // prettier-ignore
292
+ clearInterval(timer);
293
+ if (Array.isArray(res.data.errors) && res.data.errors.length) {
294
+ const error = new Error('GraphQL Response Error');
295
+ error.errors = res.data.errors;
296
+ throw error;
297
+ }
298
+ if (fs.existsSync(tarFile)) {
299
+ fs.unlinkSync(tarFile);
300
+ }
301
+ printSuccess(
302
+ `${chalk.cyan(localMeta.title)}@${chalk.cyan(localMeta.version)} was successfully deployed to ${chalk.cyan(
303
+ app.meta.title
304
+ )} in server ${chalk.cyan(endpoint)}.`
305
+ );
306
+ } catch (error) {
307
+ if (fs.existsSync(tarFile)) {
308
+ fs.unlinkSync(tarFile);
309
+ }
310
+ throw new Error(`Blocklet deploy failed when uploading: ${formatGQLError(error)}`);
311
+ }
312
+ };
313
+
314
+ const getNodeAsync = async ({ dir }) => {
315
+ const { node } = await getNode({ dir });
316
+ return new Promise((resolve) => {
317
+ node.onReady(() => resolve({ node }));
318
+ });
319
+ };
320
+
321
+ const deployToLocal = async (dir, opts) => {
322
+ const isRunning = await checkRunning();
323
+ if (!isRunning) {
324
+ const startCommand = chalk.cyan(`${getCLIBinaryName()} start`);
325
+ printError('Blocklet Server is not running, can not deploy anything!');
326
+ printInfo(`To start Blocklet Server, use ${startCommand}`);
327
+ process.exit(1);
328
+ }
329
+
330
+ const { node } = await getNodeAsync({ dir: process.cwd() });
331
+
332
+ const nodeInfo = await node.getNodeInfo();
333
+ const endpoint = `http://127.0.0.1:${nodeInfo.port}`;
334
+ const { accessKeyId, accessKeySecret } = await node.createAccessKey({
335
+ remark: 'Local CLI',
336
+ passport: ROLES.ADMIN,
337
+ });
338
+ try {
339
+ await deploy(dir, {
340
+ endpoint,
341
+ accessKey: accessKeyId,
342
+ accessSecret: accessKeySecret,
343
+ ...opts,
344
+ });
345
+ await node.deleteAccessKey({ accessKeyId });
346
+ process.exit(0);
347
+ } catch (err) {
348
+ printError(err.message);
349
+ await node.deleteAccessKey({ accessKeyId });
350
+ process.exit(1);
351
+ }
352
+ };
353
+
354
+ const deployToRemote = async (dir, opts) => {
355
+ try {
356
+ await deploy(dir, opts);
357
+ process.exit(0);
358
+ } catch (err) {
359
+ printError(err.message);
360
+ process.exit(1);
361
+ }
362
+ };
363
+
364
+ const checkAppID = (opts) => {
365
+ if (!opts.appDid && !opts.appId) {
366
+ printError('appId must exist. Please use --app-id to specify appId');
367
+ printInfo('If you want to deploy this blocklet as an application, please install from your Blocklet Server');
368
+ printInfo(`See ${chalk.cyan('https://developer.blocklet.io/docs/d6e08589-278a-448a-92e1-1e5f8bd4cc70')}`);
369
+ process.exit(1);
370
+ }
371
+
372
+ if (opts.appId) {
373
+ if (!isValidDid(opts.appId)) {
374
+ printError('Invalid appId');
375
+ process.exit(1);
376
+ } else {
377
+ opts.appId = toAddress(opts.appId);
378
+ }
379
+ }
380
+
381
+ if (opts.appDid) {
382
+ if (!isValidDid(opts.appDid)) {
383
+ printError('Invalid appDid');
384
+ process.exit(1);
385
+ } else {
386
+ opts.appDid = toAddress(opts.appDid);
387
+ }
388
+ }
389
+ };
390
+
391
+ exports.run = (dirRaw, { endpoint, accessKey, accessSecret, ...opts }) => {
392
+ const dirExpanded = expandTilde(dirRaw);
393
+ const dir = [dirExpanded, path.join(process.cwd(), dirExpanded)].filter((x) => fs.existsSync(x)).pop();
394
+
395
+ if (!dir) {
396
+ printError(`Can not deploy blocklet from a non-existent directory ${chalk.red(dirRaw)}`);
397
+ process.exit(1);
398
+ }
399
+
400
+ if (!opts.appDid && !opts.appId) {
401
+ opts.appDid = process.env.BLOCKLET_DEPLOY_APP_DID || process.env.BLOCKLET_DEPLOY_APP_ID;
402
+ }
403
+
404
+ if (endpoint || accessKey || accessSecret) {
405
+ if (!endpoint) {
406
+ printError('Can not deploy to remote Blocklet Server with empty endpoint!');
407
+ process.exit(1);
408
+ }
409
+ try {
410
+ const { protocol } = new URL(endpoint);
411
+ if (!/https?/.test(protocol)) {
412
+ printError('Invalid endpoint protocol:', protocol.replace(':', ''));
413
+ process.exit(1);
414
+ }
415
+ } catch (error) {
416
+ printError('Invalid endpoint:', endpoint);
417
+ process.exit(1);
418
+ }
419
+ if (!accessKey) {
420
+ printError('Can not deploy to remote Blocklet Server with empty access-key!');
421
+ process.exit(1);
422
+ }
423
+ if (!accessSecret) {
424
+ printError('Can not deploy to remote Blocklet Server with empty access-secret!');
425
+ process.exit(1);
426
+ }
427
+ validateAccessKey({ accessKeyId: accessKey, accessKeySecret: accessSecret });
428
+
429
+ checkAppID(opts);
430
+ deployToRemote(dir, { endpoint, accessKey, accessSecret, ...opts });
431
+ } else {
432
+ checkAppID(opts);
433
+ deployToLocal(dir, opts);
434
+ }
435
+ };