@blocklet/cli 1.17.12 → 1.17.13-beta-20260512-004004-69bacba8
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 +1 -1
- package/lib/commands/blocklet/add.js +2 -2
- package/lib/commands/blocklet/bundle/bundle.js +2 -2
- package/lib/commands/blocklet/bundle/bundlers/blocklet.js +6 -6
- package/lib/commands/blocklet/bundle/bundlers/changelog.js +1 -1
- package/lib/commands/blocklet/bundle/bundlers/markdown.js +6 -5
- package/lib/commands/blocklet/bundle/bundlers/screenshots.js +1 -1
- package/lib/commands/blocklet/bundle/compact/index.js +1 -1
- package/lib/commands/blocklet/config.js +1 -1
- package/lib/commands/blocklet/connect.js +5 -4
- package/lib/commands/blocklet/deploy.js +1 -1
- package/lib/commands/blocklet/dev.js +9 -8
- package/lib/commands/blocklet/exec.js +4 -4
- package/lib/commands/blocklet/rotate.js +251 -0
- package/lib/commands/blocklet/upload.js +1 -1
- package/lib/commands/index.js +17 -0
- package/lib/commands/server/command.js +1 -1
- package/lib/commands/server/info.js +1 -2
- package/lib/commands/server/nginx.js +5 -5
- package/lib/commands/server/start.js +5 -5
- package/lib/node.js +1 -1
- package/lib/process/daemon.js +2 -2
- package/lib/util/blocklet/config.js +1 -1
- package/lib/util/clear-all-cache.js +2 -2
- package/lib/util/index.js +5 -4
- package/package.json +23 -23
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ const getSource = async (param, opts) => {
|
|
|
52
52
|
const { url, name, version } = parseComponent(param);
|
|
53
53
|
if (url) {
|
|
54
54
|
const componentMeta = await getMeta(url);
|
|
55
|
-
// bundleName
|
|
55
|
+
// bundleName must match the 'name' field in the blocklet's blocklet.yml
|
|
56
56
|
return { source: { url }, bundleName: componentMeta.name, bundleTitle: componentMeta.title || '' };
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -68,7 +68,7 @@ const getSource = async (param, opts) => {
|
|
|
68
68
|
|
|
69
69
|
const bundleMetaUrl = getMetaUrl(store, name, version);
|
|
70
70
|
const componentMeta = await getMeta(bundleMetaUrl);
|
|
71
|
-
// NOTICE:
|
|
71
|
+
// NOTICE: Installing by blocklet DID is also supported
|
|
72
72
|
if (componentMeta.name !== name && componentMeta.did !== name) {
|
|
73
73
|
printError(`Failed validate component meta: invalid name. Expected ${name}, Got: ${componentMeta.name}`);
|
|
74
74
|
process.exit(1);
|
|
@@ -61,7 +61,7 @@ const createBlockletBundle = async ({
|
|
|
61
61
|
compact = false,
|
|
62
62
|
dependenciesDepth = 9,
|
|
63
63
|
}) => {
|
|
64
|
-
//
|
|
64
|
+
// After adding glob support to meta.files, all glob-matched files must also be copied into the bundle
|
|
65
65
|
const metaFileList = meta.files || [];
|
|
66
66
|
const distDir = path.join(blockletDir, BLOCKLET_BUNDLE_FOLDER);
|
|
67
67
|
|
|
@@ -161,7 +161,7 @@ const createBlockletBundle = async ({
|
|
|
161
161
|
};
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
|
-
*
|
|
164
|
+
* Get all extraFiles. In theory, all .js files must be found here in order to subsequently resolve their dependencies.
|
|
165
165
|
* @param {Object} meta blocklet meta
|
|
166
166
|
* @returns Array
|
|
167
167
|
*/
|
|
@@ -11,7 +11,7 @@ class BlockletMdBundler extends MarkdownBundler {
|
|
|
11
11
|
* Creates an instance of BlockletMdBundler.
|
|
12
12
|
* @param {{
|
|
13
13
|
* blockletDir: string,
|
|
14
|
-
* blockletMdFileName: string, //
|
|
14
|
+
* blockletMdFileName: string, // Convention: the blocklet.md filename must be lowercase
|
|
15
15
|
* backupMdFileNames?: string[];
|
|
16
16
|
* required?: boolean = false;
|
|
17
17
|
* duplicateMdFileName?: string;
|
|
@@ -72,16 +72,16 @@ class BlockletMdBundler extends MarkdownBundler {
|
|
|
72
72
|
|
|
73
73
|
_findByBackupFileNames() {
|
|
74
74
|
for (const backupMdFileName of this.backupMdFileNames) {
|
|
75
|
-
//
|
|
75
|
+
// Try to find a backup file
|
|
76
76
|
const foundByBackupMdFileName = fg.sync(backupMdFileName, this.fastGlobOptions);
|
|
77
77
|
|
|
78
78
|
if (foundByBackupMdFileName.length === 1) {
|
|
79
|
-
//
|
|
79
|
+
// Exactly one backup file found
|
|
80
80
|
return foundByBackupMdFileName;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
if (foundByBackupMdFileName.length > 1) {
|
|
84
|
-
//
|
|
84
|
+
// Multiple backup files found — ambiguous, cannot determine which one to use
|
|
85
85
|
printError(
|
|
86
86
|
`Only one ${chalk.red(backupMdFileName)}(not case sensitive) can exist in ${chalk.cyan(this.blockletDir)}`
|
|
87
87
|
);
|
|
@@ -90,7 +90,7 @@ class BlockletMdBundler extends MarkdownBundler {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
if (this.required) {
|
|
93
|
-
//
|
|
93
|
+
// No backup file found either, but this file is required — abort the current task
|
|
94
94
|
printError(
|
|
95
95
|
`Either ${chalk.red(this.blockletMdFileName)} or ${this.backupMdFileNames
|
|
96
96
|
.map((backupMdFileName) => chalk.red(backupMdFileName))
|
|
@@ -106,7 +106,7 @@ class BlockletMdBundler extends MarkdownBundler {
|
|
|
106
106
|
const foundByDuplicateMdFileName = fg.sync(this.duplicateMdFileName, this.fastGlobOptions);
|
|
107
107
|
|
|
108
108
|
if (foundByDuplicateMdFileName.length) {
|
|
109
|
-
//
|
|
109
|
+
// The blank line is intentional: without it, the warning would appear on the same line as other output
|
|
110
110
|
print();
|
|
111
111
|
printWarning(
|
|
112
112
|
`File ${chalk.cyan(this.blockletMdFileName)} is currently in use, file ${chalk.yellow(
|
|
@@ -81,7 +81,7 @@ class ChangelogBundler {
|
|
|
81
81
|
const ast = fromMarkdown(fs.readFileSync(changelogAbsolutePath));
|
|
82
82
|
const children = ast?.children || [];
|
|
83
83
|
children.forEach((element) => {
|
|
84
|
-
//
|
|
84
|
+
// Only h1–h4 headings are supported
|
|
85
85
|
if (element.type === 'heading' && element.depth > 4) {
|
|
86
86
|
const line = element.position?.start.line || 0;
|
|
87
87
|
printError(`Just h1~h4 headings should be used in ${chalk.cyan(changelogAbsolutePath)} at line ${line}`);
|
|
@@ -48,7 +48,7 @@ class MarkdownBundler {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
*
|
|
51
|
-
* @description
|
|
51
|
+
* @description Extract local static assets from all blocklet.md files into the bundle's media folder
|
|
52
52
|
* @param {*} blockletDir
|
|
53
53
|
*/
|
|
54
54
|
async _bundle() {
|
|
@@ -63,7 +63,7 @@ class MarkdownBundler {
|
|
|
63
63
|
const referenceFile = astNode?.url || astNode?.attrs?.src || astNode?.attrs?.href;
|
|
64
64
|
const localUrl = this._getLocalUrl(referenceFile);
|
|
65
65
|
|
|
66
|
-
// FIXME:
|
|
66
|
+
// FIXME: Add a unit test for this using jest mock later
|
|
67
67
|
if (!fs.existsSync(localUrl)) {
|
|
68
68
|
printError(
|
|
69
69
|
`Referenced file ${chalk.red(referenceFile)} not found when bundling ${chalk.cyan(
|
|
@@ -73,7 +73,7 @@ class MarkdownBundler {
|
|
|
73
73
|
process.exit(1);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
//
|
|
76
|
+
// If the referenced path is a directory, report an error — directories are not valid in markdown references
|
|
77
77
|
if (fs.statSync(localUrl).isDirectory()) {
|
|
78
78
|
printError(
|
|
79
79
|
`Only files links are allowed in ${chalk.cyan(
|
|
@@ -109,12 +109,13 @@ class MarkdownBundler {
|
|
|
109
109
|
// eslint-disable-next-line no-await-in-loop
|
|
110
110
|
await this.writeFile(path.join(this.bundleDir, path.basename(markdownAbsolutePath)), toMarkdown(mdAstNode));
|
|
111
111
|
} else {
|
|
112
|
-
//
|
|
112
|
+
// Some assets have not yet had their hash computed or been copied into the bundle,
|
|
113
|
+
// so blocklet.md is not fully processed and its asset references cannot be rewritten yet.
|
|
113
114
|
// eslint-disable-next-line no-await-in-loop
|
|
114
115
|
await this._batchHash(cache);
|
|
115
116
|
// eslint-disable-next-line no-await-in-loop
|
|
116
117
|
await this._batchCopy(cache);
|
|
117
|
-
//
|
|
118
|
+
// Not finished yet — push back for reprocessing in the next iteration
|
|
118
119
|
markdownAbsolutePaths.push(markdownAbsolutePath);
|
|
119
120
|
}
|
|
120
121
|
}
|
|
@@ -62,7 +62,7 @@ exports.run = async ({
|
|
|
62
62
|
externals: inputExternals = [],
|
|
63
63
|
dependenciesDepth = 9,
|
|
64
64
|
}) => {
|
|
65
|
-
//
|
|
65
|
+
// Merge all external dependencies (deduplicated)
|
|
66
66
|
const externals = Array.from(new Set([...defaultExternals, ...inputExternals]));
|
|
67
67
|
|
|
68
68
|
// eslint-disable-next-line no-param-reassign
|
|
@@ -17,7 +17,7 @@ const whiteList = ['store', 'accessToken', 'registry', 'developerDid', 'name', '
|
|
|
17
17
|
function validate(key, value) {
|
|
18
18
|
const validatorMap = {
|
|
19
19
|
store: (v) => {
|
|
20
|
-
//
|
|
20
|
+
// Validate that the value is a valid URL using http or https, e.g. https://store.blocklet.dev/
|
|
21
21
|
if (/^((https?):\/\/)[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$/.test(v)) {
|
|
22
22
|
return true;
|
|
23
23
|
}
|
|
@@ -12,7 +12,8 @@ const { wrapSpinner } = require('../../ui');
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
*
|
|
15
|
-
* @description
|
|
15
|
+
* @description Prompt the user to confirm whether to overwrite the existing store URL.
|
|
16
|
+
* Pressing Enter or 'y' overwrites; any other input cancels.
|
|
16
17
|
* @see https://www.npmjs.com/package/inquirer
|
|
17
18
|
* @see https://github.com/SBoudrias/Inquirer.js/blob/0053e3f5694a4f75c4901512ab87e8906d1d7896/packages/inquirer/examples/pizza.js
|
|
18
19
|
* @default false
|
|
@@ -32,7 +33,7 @@ async function confirmOverwriteStoreUrl() {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const run = async (store, { profile }) => {
|
|
35
|
-
//
|
|
36
|
+
// Validate that the URL is a valid web URI (must use http or https)
|
|
36
37
|
if (!validUrl.isWebUri(store)) {
|
|
37
38
|
printError('Invalid store url:', store);
|
|
38
39
|
process.exit(1);
|
|
@@ -41,14 +42,14 @@ const run = async (store, { profile }) => {
|
|
|
41
42
|
try {
|
|
42
43
|
const storeUrl = await getStoreUrl(store);
|
|
43
44
|
|
|
44
|
-
//
|
|
45
|
+
// Load configuration
|
|
45
46
|
const config = new Config({
|
|
46
47
|
configFile: process.env.ABT_NODE_CONFIG_FILE,
|
|
47
48
|
section: profile === 'default' ? '' : profile,
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
const oldStoreUrl = config.get('store');
|
|
51
|
-
//
|
|
52
|
+
// If a store URL was already configured, it differs from the new one, and the user declined to overwrite, exit
|
|
52
53
|
if (!isUndefined(oldStoreUrl) && oldStoreUrl !== storeUrl && !(await confirmOverwriteStoreUrl())) {
|
|
53
54
|
process.exit(0);
|
|
54
55
|
}
|
|
@@ -115,7 +115,8 @@ const getAccessibleUrl = async (urls) => {
|
|
|
115
115
|
|
|
116
116
|
const ping = async (url) => {
|
|
117
117
|
try {
|
|
118
|
-
// FIXME:
|
|
118
|
+
// FIXME: When a Host IP is specified via ABT_NODE_HOST, HTTP-based reachability checks fail,
|
|
119
|
+
// so temporarily return true to bypass the check when running inside Docker.
|
|
119
120
|
if (isDocker()) {
|
|
120
121
|
return true;
|
|
121
122
|
}
|
|
@@ -136,7 +137,7 @@ const getAccessibleUrl = async (urls) => {
|
|
|
136
137
|
|
|
137
138
|
let resultUrl = urls.find((url) => isDidDomain(url));
|
|
138
139
|
if (!resultUrl) {
|
|
139
|
-
[resultUrl] = urls; //
|
|
140
|
+
[resultUrl] = urls; // No accessible URL found; fall back to the first one
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
print('');
|
|
@@ -192,7 +193,7 @@ const getBlockletMetaWithTimeout = (urls, options, timeout = 5000) => {
|
|
|
192
193
|
};
|
|
193
194
|
|
|
194
195
|
const findMountPointConflicts = async (meta, existedApp) => {
|
|
195
|
-
//
|
|
196
|
+
// Collect components to be installed, to detect mountPoint conflicts between them
|
|
196
197
|
let components = meta.components || [];
|
|
197
198
|
if (isEmpty(components)) {
|
|
198
199
|
const { title, name, did } = meta;
|
|
@@ -202,7 +203,7 @@ const findMountPointConflicts = async (meta, existedApp) => {
|
|
|
202
203
|
title: title || name || did,
|
|
203
204
|
});
|
|
204
205
|
}
|
|
205
|
-
//
|
|
206
|
+
// Already-installed components
|
|
206
207
|
components.unshift(
|
|
207
208
|
...(existedApp.children?.map((v) => ({
|
|
208
209
|
mountPoint: v.mountPoint,
|
|
@@ -210,9 +211,9 @@ const findMountPointConflicts = async (meta, existedApp) => {
|
|
|
210
211
|
title: v.meta.title || v.meta.name || v.meta.did,
|
|
211
212
|
})) || [])
|
|
212
213
|
);
|
|
213
|
-
//
|
|
214
|
+
// Ignore components that have no mountPoint defined
|
|
214
215
|
components = components.filter((x) => Boolean(x.mountPoint));
|
|
215
|
-
//
|
|
216
|
+
// Resolve the DID for each component that is pending installation
|
|
216
217
|
await pMap(
|
|
217
218
|
components.filter((v) => !v.did),
|
|
218
219
|
async (config) => {
|
|
@@ -227,12 +228,12 @@ const findMountPointConflicts = async (meta, existedApp) => {
|
|
|
227
228
|
}
|
|
228
229
|
}
|
|
229
230
|
);
|
|
230
|
-
//
|
|
231
|
+
// Ignore components whose DID could not be resolved
|
|
231
232
|
components = components.filter((x) => Boolean(x.did));
|
|
232
233
|
|
|
233
234
|
const conflictMsgs = [];
|
|
234
235
|
components.reduce((acc, child) => {
|
|
235
|
-
//
|
|
236
|
+
// Two different components are attempting to mount at the same mountPoint
|
|
236
237
|
if (acc[child.mountPoint] && acc[child.mountPoint].did !== child.did) {
|
|
237
238
|
conflictMsgs.push(
|
|
238
239
|
`mountPoint conflict: '${child.title}' attempted to mount on '${child.mountPoint}', but it is already occupied by '${acc[child.mountPoint].title}'`
|
|
@@ -35,7 +35,7 @@ const run = async (script, { appId: inputAppId, timeout } = {}) => {
|
|
|
35
35
|
appId = process.env.BLOCKLET_DEV_APP_DID;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// Validate appId
|
|
39
39
|
if (appId) {
|
|
40
40
|
if (isValidDid(appId) === false) {
|
|
41
41
|
printError(`${chalk.yellow('--app-id')} is not valid: ${appId}`);
|
|
@@ -48,7 +48,7 @@ const run = async (script, { appId: inputAppId, timeout } = {}) => {
|
|
|
48
48
|
|
|
49
49
|
printInfo(`Try to run script for blocklet(${chalk.yellow(appId)}) (timeout: ${timeoutMs}ms)`);
|
|
50
50
|
|
|
51
|
-
//
|
|
51
|
+
// Check that the script file exists
|
|
52
52
|
const scriptPath = path.join(appDir, script);
|
|
53
53
|
if (fs.existsSync(scriptPath) === false) {
|
|
54
54
|
printError(`Script ${chalk.cyan(scriptPath)} does not exist`);
|
|
@@ -56,7 +56,7 @@ const run = async (script, { appId: inputAppId, timeout } = {}) => {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
try {
|
|
59
|
-
//
|
|
59
|
+
// Try to read meta information from blocklet.yml
|
|
60
60
|
if (fs.existsSync(path.join(appDir, 'blocklet.yml'))) {
|
|
61
61
|
const meta = getBlockletMeta(appDir, { fix: false });
|
|
62
62
|
componentDid = meta?.did;
|
|
@@ -92,7 +92,7 @@ const run = async (script, { appId: inputAppId, timeout } = {}) => {
|
|
|
92
92
|
blocklet = await node.getBlocklet({ did: rootDid });
|
|
93
93
|
const nodeEnvironments = await node.states.node.getEnvironments();
|
|
94
94
|
|
|
95
|
-
//
|
|
95
|
+
// If componentDid is present, run the script for that component
|
|
96
96
|
if (componentDid) {
|
|
97
97
|
component = blocklet.children.find((x) => x.meta.did === componentDid);
|
|
98
98
|
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const { fromRandom, fromSecretKey } = require('@ocap/wallet');
|
|
4
|
+
const { isValid: isValidDid } = require('@arcblock/did');
|
|
5
|
+
const { getBlockletInfo } = require('@blocklet/meta/lib/info');
|
|
6
|
+
const { getBlockletChainInfo } = require('@blocklet/meta/lib/util');
|
|
7
|
+
const { getChainClient } = require('@abtnode/util/lib/get-chain-client');
|
|
8
|
+
const { MAIN_CHAIN_ENDPOINT } = require('@abtnode/constant');
|
|
9
|
+
|
|
10
|
+
const { printError, printInfo, printSuccess, printWarning, getCLIBinaryName } = require('../../util');
|
|
11
|
+
const { getNode } = require('../../node');
|
|
12
|
+
const { checkRunning } = require('../../manager');
|
|
13
|
+
|
|
14
|
+
const sleep = (ms) =>
|
|
15
|
+
new Promise((r) => {
|
|
16
|
+
setTimeout(r, ms);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const checkNodeRunning = async () => {
|
|
20
|
+
const isRunning = await checkRunning();
|
|
21
|
+
if (!isRunning) {
|
|
22
|
+
const startCommand = chalk.cyan(`${getCLIBinaryName()} server start`);
|
|
23
|
+
printError('Blocklet Server is not running, can not execute anything!');
|
|
24
|
+
printInfo(`To start Blocklet Server, use ${startCommand}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const waitForTx = async (client, hash, timeoutSec) => {
|
|
30
|
+
const deadline = Date.now() + timeoutSec * 1000;
|
|
31
|
+
let lastErr;
|
|
32
|
+
// Sequential polling is the intent here — every iteration must observe the
|
|
33
|
+
// chain state produced by the previous one's sleep, so parallelizing is
|
|
34
|
+
// semantically wrong. Disable the lint rule that bans await-in-loop.
|
|
35
|
+
/* eslint-disable no-await-in-loop */
|
|
36
|
+
while (Date.now() < deadline) {
|
|
37
|
+
try {
|
|
38
|
+
const { info } = await client.getTx({ hash });
|
|
39
|
+
if (info && info.code === 'OK') return;
|
|
40
|
+
if (info && info.code && info.code !== 'OK') {
|
|
41
|
+
throw new Error(`tx ${hash} rejected with code ${info.code}`);
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
lastErr = err;
|
|
45
|
+
}
|
|
46
|
+
await sleep(1000);
|
|
47
|
+
}
|
|
48
|
+
/* eslint-enable no-await-in-loop */
|
|
49
|
+
throw new Error(`timed out waiting for tx ${hash} (${timeoutSec}s)${lastErr ? ` — ${lastErr.message}` : ''}`);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const run = async ({
|
|
53
|
+
appDid = '',
|
|
54
|
+
newSk = '',
|
|
55
|
+
genNew = false,
|
|
56
|
+
yes = false,
|
|
57
|
+
skipConfirm = false,
|
|
58
|
+
dryRun = false,
|
|
59
|
+
skipChainMigrate = false,
|
|
60
|
+
skipConfigUpdate = false,
|
|
61
|
+
confirmTimeout = '60',
|
|
62
|
+
} = {}) => {
|
|
63
|
+
// Either subcommand `--skip-confirm` or global `-y/--yes` suppresses the
|
|
64
|
+
// prompt. Both end up in the merged options bag via parseOptions.
|
|
65
|
+
const noPrompt = !!(skipConfirm || yes);
|
|
66
|
+
try {
|
|
67
|
+
await checkNodeRunning();
|
|
68
|
+
|
|
69
|
+
if (!appDid || isValidDid(appDid) === false) {
|
|
70
|
+
printError(`--app-did is not a valid DID: ${appDid}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!newSk && !genNew) {
|
|
76
|
+
printError('Either --new-sk <hex> or --gen-new must be provided');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (newSk && genNew) {
|
|
81
|
+
printError('--new-sk and --gen-new are mutually exclusive');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (skipChainMigrate && skipConfigUpdate) {
|
|
86
|
+
printError('--skip-chain-migrate and --skip-config-update both set — nothing to do');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const timeoutSec = Number(confirmTimeout);
|
|
92
|
+
if (!Number.isFinite(timeoutSec) || timeoutSec <= 0) {
|
|
93
|
+
printError(`--confirm-timeout must be a positive number, got ${confirmTimeout}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { node } = await getNode();
|
|
99
|
+
node.onReady(async () => {
|
|
100
|
+
try {
|
|
101
|
+
const blocklet = await node.getBlocklet({ did: appDid });
|
|
102
|
+
if (!blocklet) {
|
|
103
|
+
printError(`Blocklet ${appDid} not found on this server`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const nodeInfo = await node.getNodeInfo();
|
|
109
|
+
const { wallet: oldWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
110
|
+
if (!oldWallet || !oldWallet.secretKey) {
|
|
111
|
+
printError('Cannot resolve old wallet — is BLOCKLET_APP_SK configured for this blocklet?');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Generate or accept new sk, keeping the old wallet's type so the
|
|
117
|
+
// address derivation stays in the same scheme (arcblock vs ethereum).
|
|
118
|
+
let resolvedNewSk = newSk;
|
|
119
|
+
if (genNew) {
|
|
120
|
+
const newW = fromRandom(oldWallet.type);
|
|
121
|
+
resolvedNewSk = newW.secretKey;
|
|
122
|
+
}
|
|
123
|
+
let newWallet;
|
|
124
|
+
try {
|
|
125
|
+
newWallet = fromSecretKey(resolvedNewSk, oldWallet.type);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
printError(`Cannot derive new wallet from sk: ${err.message}`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (newWallet.address === oldWallet.address) {
|
|
133
|
+
printError(`New sk derives the same address as the old one (${oldWallet.address}); refuse no-op`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Resolve chain endpoint (same logic auth/lib/server.js#migrateAppDid uses).
|
|
139
|
+
let chainHost = getBlockletChainInfo(blocklet).host;
|
|
140
|
+
if (!chainHost || chainHost === 'none') chainHost = MAIN_CHAIN_ENDPOINT;
|
|
141
|
+
|
|
142
|
+
// Show plan.
|
|
143
|
+
printInfo(`Rotation plan for blocklet ${chalk.cyan(blocklet.meta?.title || appDid)}`);
|
|
144
|
+
printInfo(` appDid (current): ${oldWallet.address}`);
|
|
145
|
+
printInfo(` appPid (meta DID): ${blocklet.appPid}`);
|
|
146
|
+
printInfo(` new appDid: ${newWallet.address}`);
|
|
147
|
+
printInfo(` chain endpoint: ${chainHost}`);
|
|
148
|
+
printInfo(' steps:');
|
|
149
|
+
printInfo(` 1) chain migrate: ${skipChainMigrate ? chalk.gray('SKIP') : chalk.green('YES')}`);
|
|
150
|
+
printInfo(` 2) local config: ${skipConfigUpdate ? chalk.gray('SKIP') : chalk.green('YES')}`);
|
|
151
|
+
printInfo(` 3) restart blocklet: ${chalk.yellow('MANUAL after success')}`);
|
|
152
|
+
|
|
153
|
+
if (genNew) {
|
|
154
|
+
// Print the generated sk early — caller must save it before the
|
|
155
|
+
// command exits.
|
|
156
|
+
printWarning('Generated new sk (SAVE IT NOW; will not be shown again):');
|
|
157
|
+
printWarning(` ${chalk.yellow(resolvedNewSk)}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (dryRun) {
|
|
161
|
+
printSuccess('dry run — no changes made');
|
|
162
|
+
process.exit(0);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!noPrompt) {
|
|
167
|
+
const { confirmed } = await inquirer.prompt([
|
|
168
|
+
{
|
|
169
|
+
type: 'confirm',
|
|
170
|
+
name: 'confirmed',
|
|
171
|
+
message: 'This rotates the blocklet appSk irreversibly on chain. Proceed?',
|
|
172
|
+
default: false,
|
|
173
|
+
},
|
|
174
|
+
]);
|
|
175
|
+
if (!confirmed) {
|
|
176
|
+
printInfo('aborted by user');
|
|
177
|
+
process.exit(0);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Step 1: on-chain AccountMigrateTx.
|
|
183
|
+
if (!skipChainMigrate) {
|
|
184
|
+
const client = getChainClient(chainHost);
|
|
185
|
+
|
|
186
|
+
const { state: newOnChain } = await client.getAccountState({
|
|
187
|
+
address: newWallet.address,
|
|
188
|
+
traceMigration: false,
|
|
189
|
+
});
|
|
190
|
+
if (newOnChain) {
|
|
191
|
+
printError(`new account ${newWallet.address} already exists on chain — abort`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const { state: oldOnChain } = await client.getAccountState({
|
|
196
|
+
address: oldWallet.address,
|
|
197
|
+
traceMigration: false,
|
|
198
|
+
});
|
|
199
|
+
if (oldOnChain && oldOnChain.migratedTo && oldOnChain.migratedTo.length > 0) {
|
|
200
|
+
printError(
|
|
201
|
+
`old account ${oldWallet.address} already migrated to ${JSON.stringify(
|
|
202
|
+
oldOnChain.migratedTo
|
|
203
|
+
)} — refuse to migrate again`
|
|
204
|
+
);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
printInfo('sending AccountMigrateTx ...');
|
|
210
|
+
const hash = await client.migrateAccount({ from: oldWallet, to: newWallet });
|
|
211
|
+
printInfo(` tx hash: ${chalk.cyan(hash)}`);
|
|
212
|
+
printInfo(` waiting up to ${timeoutSec}s for confirmation ...`);
|
|
213
|
+
await waitForTx(client, hash, timeoutSec);
|
|
214
|
+
printSuccess('chain migration confirmed');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Step 2: write BLOCKLET_APP_SK back to blocklet config.
|
|
218
|
+
if (!skipConfigUpdate) {
|
|
219
|
+
printInfo('updating BLOCKLET_APP_SK in blocklet config ...');
|
|
220
|
+
// NOTE: must use blocklet.appPid (= meta DID), NOT appDid — when sk
|
|
221
|
+
// changes, the appDid moves into alsoKnownAs and lookups by old
|
|
222
|
+
// appDid would fail. See env-config-manager.js:110-112.
|
|
223
|
+
await node.configBlocklet({
|
|
224
|
+
did: blocklet.appPid,
|
|
225
|
+
configs: [{ key: 'BLOCKLET_APP_SK', value: resolvedNewSk, secure: true, shared: false }],
|
|
226
|
+
skipHook: true,
|
|
227
|
+
skipDidDocument: false,
|
|
228
|
+
});
|
|
229
|
+
printSuccess('BLOCKLET_APP_SK updated. Blocklet must be restarted to pick up the new key.');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
printSuccess('Rotation complete.');
|
|
233
|
+
printWarning(
|
|
234
|
+
'This command did NOT sync passport VC / owner.connectedAccount. ' +
|
|
235
|
+
'If this blocklet has user-facing auth, follow up via the server UI ' +
|
|
236
|
+
'("Transfer App Owner" / "Rotate Key Pair") or run the corresponding ' +
|
|
237
|
+
'node APIs manually (see receive-transfer-app-owner.js:317+).'
|
|
238
|
+
);
|
|
239
|
+
process.exit(0);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
printError(`Rotation failed: ${err.message}`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
} catch (err) {
|
|
246
|
+
printError(err.message);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
exports.run = run;
|
|
@@ -40,7 +40,7 @@ exports.run = async (originalMetaFile, { accessToken, profile }) => {
|
|
|
40
40
|
realAccessToken = config.get('accessToken');
|
|
41
41
|
debug('read access token from config');
|
|
42
42
|
if (!realAccessToken) {
|
|
43
|
-
// TODO:
|
|
43
|
+
// TODO: Add logic to automatically obtain accessToken when it is missing
|
|
44
44
|
printError('accessToken is required to upload a blocklet');
|
|
45
45
|
process.exit(1);
|
|
46
46
|
}
|
package/lib/commands/index.js
CHANGED
|
@@ -48,6 +48,7 @@ const create = require('./blocklet/create');
|
|
|
48
48
|
const connect = require('./blocklet/connect');
|
|
49
49
|
const exec = require('./blocklet/exec');
|
|
50
50
|
const $debug = require('./blocklet/debug');
|
|
51
|
+
const rotate = require('./blocklet/rotate');
|
|
51
52
|
const add = require('./blocklet/add');
|
|
52
53
|
const remove = require('./blocklet/remove');
|
|
53
54
|
const cleanup = require('./blocklet/cleanup');
|
|
@@ -261,6 +262,22 @@ program
|
|
|
261
262
|
.description('Setting debug environment for blocklet')
|
|
262
263
|
.action(parseArgsAndOptions($debug.run));
|
|
263
264
|
|
|
265
|
+
program
|
|
266
|
+
.command('rotate')
|
|
267
|
+
.requiredOption('--app-did <did>', 'The appDid of the blocklet to rotate')
|
|
268
|
+
.option('--new-sk <hex>', 'New secret key in hex (e.g. 0x... or 64-byte hex)')
|
|
269
|
+
.option('--gen-new', 'Auto-generate a new random sk; prints the value once', false)
|
|
270
|
+
.option('--skip-chain-migrate', 'Skip on-chain AccountMigrateTx (local config only)', false)
|
|
271
|
+
.option('--skip-config-update', 'Skip local BLOCKLET_APP_SK update (chain migrate only)', false)
|
|
272
|
+
.option('--confirm-timeout <sec>', 'Seconds to wait for chain confirmation', '60')
|
|
273
|
+
.option('--skip-confirm', 'Skip interactive confirmation prompt (also honored via global -y/--yes)', false)
|
|
274
|
+
.option('--dry-run', 'Print plan only, do not execute', false)
|
|
275
|
+
.description(
|
|
276
|
+
'Rotate the BLOCKLET_APP_SK of a blocklet (on-chain AccountMigrate + local config update). ' +
|
|
277
|
+
'Does NOT sync passport VC / connectedAccount — use server UI for user-facing apps.'
|
|
278
|
+
)
|
|
279
|
+
.action(parseOptions(rotate.run));
|
|
280
|
+
|
|
264
281
|
program
|
|
265
282
|
.command('connect <store-url>')
|
|
266
283
|
.option('--profile <profile>', 'Your config profile', 'default')
|
|
@@ -34,7 +34,7 @@ module.exports = (parentCommand = '') => {
|
|
|
34
34
|
async (...args) => {
|
|
35
35
|
printVersionTip();
|
|
36
36
|
|
|
37
|
-
// @note:
|
|
37
|
+
// @note: The --yes flag cannot be reliably read via commander.js, so it is parsed directly from process.argv
|
|
38
38
|
const yes = process.argv.includes('--yes') || process.argv.includes('-y');
|
|
39
39
|
Object.assign(args[0], { yes });
|
|
40
40
|
|
|
@@ -15,7 +15,7 @@ const getDockerStatusLog = require('../../util/docker-status-log');
|
|
|
15
15
|
exports.run = ({ clipboard }) => {
|
|
16
16
|
// Clipboard is not accessible when on a linux tty
|
|
17
17
|
const copyToClipboard = process.platform === 'linux' && !process.env.DISPLAY ? false : clipboard;
|
|
18
|
-
//
|
|
18
|
+
// Accumulate printInfo output that will be copied to the clipboard
|
|
19
19
|
const toPlainText = (str) => stripAnsi(str);
|
|
20
20
|
let clipboardBuffer = `${getVersionInfo()}\n`;
|
|
21
21
|
const bufferedPrintInfo = (...args) => {
|
|
@@ -96,7 +96,6 @@ exports.run = ({ clipboard }) => {
|
|
|
96
96
|
? `${canUseFileSystemIsolation ? chalk.green('on(available)') : chalk.yellow('on(unavailable, version must be >= 21.6.0)')}`
|
|
97
97
|
: chalk.red('off')
|
|
98
98
|
);
|
|
99
|
-
// 判断是否是mac
|
|
100
99
|
|
|
101
100
|
getDockerStatusLog(bufferedPrintInfo, nodeInfo);
|
|
102
101
|
bufferedPrintInfo('Server domain status:', correct ? chalk.green('correct') : chalk.red('mismatch'));
|
|
@@ -330,15 +330,15 @@ function ensureNginxServiceNotManaged() {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
function setupNginxEnvironment() {
|
|
333
|
-
//
|
|
333
|
+
// Ensure nginx is installed
|
|
334
334
|
installNginxIfNeeded();
|
|
335
|
-
//
|
|
335
|
+
// Ensure nginx has the stream module
|
|
336
336
|
ensureNginxStreamModule();
|
|
337
|
-
//
|
|
337
|
+
// Ensure nginx has permission to bind low-numbered ports
|
|
338
338
|
configureNginxPortCapabilities();
|
|
339
|
-
//
|
|
339
|
+
// Ensure nginx is not managed by the system service manager
|
|
340
340
|
ensureNginxServiceNotManaged();
|
|
341
|
-
//
|
|
341
|
+
// Ensure nginx port configuration is correct
|
|
342
342
|
checkBlockletServerConfig();
|
|
343
343
|
|
|
344
344
|
print();
|
|
@@ -85,7 +85,7 @@ const ABT_NODE_BINARY_NAME = getCLIBinaryName();
|
|
|
85
85
|
const ABT_NODE_COMMAND_NAME = getCLICommandName();
|
|
86
86
|
|
|
87
87
|
const parseMemoryLimit = () => {
|
|
88
|
-
//
|
|
88
|
+
// Default to 30% of total memory; minimum 800 MB, maximum 4096 MB
|
|
89
89
|
const totalMemInMB = os.totalmem() / (1024 * 1024);
|
|
90
90
|
const memoryPercent = totalMemInMB * 0.3;
|
|
91
91
|
const calculatedLimit = Math.floor(Math.max(Math.min(memoryPercent, 4096), DAEMON_MAX_MEM_LIMIT_IN_MB));
|
|
@@ -425,7 +425,7 @@ const prepareInitialData = async (node, config, dataDir) => {
|
|
|
425
425
|
await fs.remove(dataDirBak);
|
|
426
426
|
} catch (error) {
|
|
427
427
|
await fs.move(dataDirBak, dataDir, { overwrite: true });
|
|
428
|
-
// FIXME:
|
|
428
|
+
// FIXME: An error here can prevent the node from starting, which may cause it to restart infinitely on EC2
|
|
429
429
|
throw error;
|
|
430
430
|
}
|
|
431
431
|
};
|
|
@@ -457,8 +457,8 @@ const exec = async ({ workingDir, config, dataDir, mode, updateDb, forceIntranet
|
|
|
457
457
|
|
|
458
458
|
const blockletMaxMemoryLimit = get(config, 'node.runtimeConfig.blockletMaxMemoryLimit', BLOCKLET_MAX_MEM_LIMIT_IN_MB);
|
|
459
459
|
|
|
460
|
-
//
|
|
461
|
-
//
|
|
460
|
+
// Ensure the log directory exists before starting pm2; without it pm2 may fail to start.
|
|
461
|
+
// Observed failure on Ubuntu 18.04.4 LTS inside Docker; macOS is unaffected — root cause unknown.
|
|
462
462
|
const logDir = process.env.ABT_NODE_LOG_DIR || getDaemonLogDir(dataDir);
|
|
463
463
|
fs.ensureDirSync(logDir);
|
|
464
464
|
|
|
@@ -792,7 +792,7 @@ const createStartLock = ({ lockFile, pid }) => fs.writeFileSync(lockFile, pid.to
|
|
|
792
792
|
|
|
793
793
|
const clearStartLock = ({ pid, lockFile, enforceLock }) => {
|
|
794
794
|
try {
|
|
795
|
-
//
|
|
795
|
+
// If autoInit is enabled, delete the lock file whenever it exists
|
|
796
796
|
if (!enforceLock && fs.existsSync(lockFile)) {
|
|
797
797
|
fs.removeSync(lockFile);
|
|
798
798
|
return;
|
package/lib/node.js
CHANGED
|
@@ -54,7 +54,7 @@ const readNodeConfig = async (dir) => {
|
|
|
54
54
|
}
|
|
55
55
|
} else {
|
|
56
56
|
printConfigError();
|
|
57
|
-
process.exit(1); // TODO:
|
|
57
|
+
process.exit(1); // TODO: Hate early-exit functions like this — extremely hard to debug
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
const configFile = await getConfigFile(dir);
|
package/lib/process/daemon.js
CHANGED
|
@@ -102,11 +102,11 @@ node.onReady(async () => {
|
|
|
102
102
|
try {
|
|
103
103
|
const initialized = await node.isInitialized();
|
|
104
104
|
if (initialized) {
|
|
105
|
-
//
|
|
105
|
+
// Regardless of whether the server previously crashed, always restart previously running components on server start (non-hot-reload)
|
|
106
106
|
await node.updateNodeStatus(SERVER_STATUS.RUNNING);
|
|
107
107
|
if (runningBefore) {
|
|
108
108
|
logger.info('> Blocklet Server is restarted from running before');
|
|
109
|
-
//
|
|
109
|
+
// If the server was running before, skip the rapid-restart check
|
|
110
110
|
ensureBlockletRunning.whenCycleCheck = true;
|
|
111
111
|
} else {
|
|
112
112
|
logger.info('> Blocklet Server is stopped from running before');
|
|
@@ -6,7 +6,7 @@ const { stopDockerRedis } = require('@abtnode/core/lib/util/docker/ensure-docker
|
|
|
6
6
|
const { CONFIG_FOLDER_NAME, CONFIG_FOLDER_NAME_OLD } = require('@abtnode/constant');
|
|
7
7
|
|
|
8
8
|
const clearAllCache = async (printSuccess, printError) => {
|
|
9
|
-
//
|
|
9
|
+
// Stop the Redis container in Docker
|
|
10
10
|
await stopDockerRedis();
|
|
11
11
|
try {
|
|
12
12
|
let nowConfigFolder = '';
|
|
@@ -27,7 +27,7 @@ const clearAllCache = async (printSuccess, printError) => {
|
|
|
27
27
|
forceType: 'sqlite',
|
|
28
28
|
}));
|
|
29
29
|
|
|
30
|
-
//
|
|
30
|
+
// Flush the SQLite adapter cache
|
|
31
31
|
await cache.flushAll();
|
|
32
32
|
}
|
|
33
33
|
} catch (error) {
|
package/lib/util/index.js
CHANGED
|
@@ -237,7 +237,8 @@ const startEventHub = async (logDir, maxMemoryRestart = BLOCKLET_MAX_MEM_LIMIT_I
|
|
|
237
237
|
try {
|
|
238
238
|
await wrapSpinner('Starting event hub...', async () => {
|
|
239
239
|
await pm2.startAsync({
|
|
240
|
-
//
|
|
240
|
+
// Due to a pm2 bug, child processes inherit the namespace and many other parameters,
|
|
241
|
+
// so the namespace here is intentionally set to 'blocklets' to be inherited by blocklet processes.
|
|
241
242
|
namespace: 'blocklets',
|
|
242
243
|
name: PROCESS_NAME_EVENT_HUB,
|
|
243
244
|
script: './lib/process/event-hub.js',
|
|
@@ -442,7 +443,7 @@ const isDaemonIpAccessible = async ({ ip, port, adminPath }) => {
|
|
|
442
443
|
}
|
|
443
444
|
};
|
|
444
445
|
|
|
445
|
-
//
|
|
446
|
+
// Retry 2 times, 3 total ping attempts
|
|
446
447
|
await pRetry(ping, { retries: 2 });
|
|
447
448
|
return true;
|
|
448
449
|
} catch (error) {
|
|
@@ -459,7 +460,7 @@ const getAccessibleIps = async (info, forceIntranet = false) => {
|
|
|
459
460
|
const port = getDaemonPort(nodeHttpPort, DEFAULT_HTTP_PORT);
|
|
460
461
|
const result = { internal: ips.internal };
|
|
461
462
|
|
|
462
|
-
//
|
|
463
|
+
// In a cloud environment, fully trust the retrieved IP information
|
|
463
464
|
if ((await cloud.isInCloud()) === true) {
|
|
464
465
|
result.external = ips.external;
|
|
465
466
|
return result;
|
|
@@ -546,7 +547,7 @@ const getDaemonAccessUrls = async ({ info, wallet, getBaseUrls, forceIntranet })
|
|
|
546
547
|
);
|
|
547
548
|
|
|
548
549
|
if (ips.external) {
|
|
549
|
-
accessibleIps.push(ips.external); //
|
|
550
|
+
accessibleIps.push(ips.external); // If public IP is accessible, it is the first element of accessibleIps
|
|
550
551
|
}
|
|
551
552
|
|
|
552
553
|
if (ips.internal) {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/cli",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.13-beta-20260512-004004-69bacba8",
|
|
4
4
|
"description": "Command line tools to manage Blocklet Server",
|
|
5
|
-
"homepage": "https://
|
|
5
|
+
"homepage": "https://github.com/ArcBlock/blocklet-server/tree/main/core/cli#readme",
|
|
6
6
|
"bin": {
|
|
7
7
|
"blocklet": "bin/blocklet.js"
|
|
8
8
|
},
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"bin"
|
|
21
21
|
],
|
|
22
22
|
"author": {
|
|
23
|
-
"name": "
|
|
24
|
-
"email": "
|
|
25
|
-
"url": "https://github.com/
|
|
23
|
+
"name": "ArcBlock",
|
|
24
|
+
"email": "blocklet@arcblock.io",
|
|
25
|
+
"url": "https://github.com/ArcBlock"
|
|
26
26
|
},
|
|
27
27
|
"license": "Apache-2.0",
|
|
28
28
|
"repository": {
|
|
@@ -33,28 +33,28 @@
|
|
|
33
33
|
"url": "https://github.com/ArcBlock/blocklet-server/issues"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@abtnode/blocklet-services": "1.17.
|
|
37
|
-
"@abtnode/constant": "1.17.
|
|
38
|
-
"@abtnode/core": "1.17.
|
|
39
|
-
"@abtnode/db-cache": "1.17.
|
|
40
|
-
"@abtnode/logger": "1.17.
|
|
41
|
-
"@abtnode/models": "1.17.
|
|
42
|
-
"@abtnode/router-provider": "1.17.
|
|
43
|
-
"@abtnode/util": "1.17.
|
|
44
|
-
"@abtnode/webapp": "1.17.
|
|
36
|
+
"@abtnode/blocklet-services": "1.17.13-beta-20260512-004004-69bacba8",
|
|
37
|
+
"@abtnode/constant": "1.17.13-beta-20260512-004004-69bacba8",
|
|
38
|
+
"@abtnode/core": "1.17.13-beta-20260512-004004-69bacba8",
|
|
39
|
+
"@abtnode/db-cache": "1.17.13-beta-20260512-004004-69bacba8",
|
|
40
|
+
"@abtnode/logger": "1.17.13-beta-20260512-004004-69bacba8",
|
|
41
|
+
"@abtnode/models": "1.17.13-beta-20260512-004004-69bacba8",
|
|
42
|
+
"@abtnode/router-provider": "1.17.13-beta-20260512-004004-69bacba8",
|
|
43
|
+
"@abtnode/util": "1.17.13-beta-20260512-004004-69bacba8",
|
|
44
|
+
"@abtnode/webapp": "1.17.13-beta-20260512-004004-69bacba8",
|
|
45
45
|
"@arcblock/did": "1.29.27",
|
|
46
46
|
"@arcblock/event-hub": "1.29.27",
|
|
47
47
|
"@arcblock/ipfs-only-hash": "^0.0.2",
|
|
48
48
|
"@arcblock/jwt": "1.29.27",
|
|
49
49
|
"@arcblock/ws": "1.29.27",
|
|
50
|
-
"@blocklet/constant": "1.17.
|
|
50
|
+
"@blocklet/constant": "1.17.13-beta-20260512-004004-69bacba8",
|
|
51
51
|
"@blocklet/error": "^0.3.5",
|
|
52
52
|
"@blocklet/form-collector": "^0.1.8",
|
|
53
|
-
"@blocklet/images": "1.17.
|
|
54
|
-
"@blocklet/meta": "1.17.
|
|
55
|
-
"@blocklet/resolver": "1.17.
|
|
56
|
-
"@blocklet/server-js": "1.17.
|
|
57
|
-
"@blocklet/store": "1.17.
|
|
53
|
+
"@blocklet/images": "1.17.13-beta-20260512-004004-69bacba8",
|
|
54
|
+
"@blocklet/meta": "1.17.13-beta-20260512-004004-69bacba8",
|
|
55
|
+
"@blocklet/resolver": "1.17.13-beta-20260512-004004-69bacba8",
|
|
56
|
+
"@blocklet/server-js": "1.17.13-beta-20260512-004004-69bacba8",
|
|
57
|
+
"@blocklet/store": "1.17.13-beta-20260512-004004-69bacba8",
|
|
58
58
|
"@blocklet/theme-builder": "^0.4.8",
|
|
59
59
|
"@ocap/client": "1.29.27",
|
|
60
60
|
"@ocap/mcrypto": "1.29.27",
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
"sqlite3": "^5.1.7",
|
|
137
137
|
"ssri": "^8.0.1",
|
|
138
138
|
"strip-ansi": "6.0.1",
|
|
139
|
-
"tar": "^
|
|
139
|
+
"tar": "^7.5.13",
|
|
140
140
|
"terminal-link": "^2.1.1",
|
|
141
141
|
"tweetnacl": "^1.0.3",
|
|
142
142
|
"tweetnacl-sealedbox-js": "^1.2.0",
|
|
@@ -151,9 +151,9 @@
|
|
|
151
151
|
"colors": "1.4.0"
|
|
152
152
|
},
|
|
153
153
|
"engines": {
|
|
154
|
-
"node": ">=
|
|
154
|
+
"node": ">=22"
|
|
155
155
|
},
|
|
156
|
-
"gitHead": "
|
|
156
|
+
"gitHead": "289f06c2f5a9390aa03cb942b29da0fc5bfdc745",
|
|
157
157
|
"devDependencies": {
|
|
158
158
|
"@types/fs-extra": "^11.0.4"
|
|
159
159
|
}
|