@0xobelisk/sui-cli 1.2.0-pre.12 → 1.2.0-pre.120
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 +7 -7
- package/dist/dubhe.js +152 -51
- package/dist/dubhe.js.map +1 -1
- package/package.json +31 -19
- package/src/commands/build.ts +61 -18
- package/src/commands/call.ts +83 -83
- package/src/commands/checkBalance.ts +27 -12
- package/src/commands/convertJson.ts +84 -0
- package/src/commands/doctor.ts +1515 -0
- package/src/commands/faucet.ts +20 -10
- package/src/commands/generate.ts +61 -0
- package/src/commands/generateKey.ts +3 -2
- package/src/commands/index.ts +20 -11
- package/src/commands/info.ts +61 -0
- package/src/commands/loadMetadata.ts +68 -0
- package/src/commands/localnode.ts +22 -6
- package/src/commands/publish.ts +55 -7
- package/src/commands/query.ts +101 -101
- package/src/commands/shell.ts +208 -0
- package/src/commands/{configStore.ts → storeConfig.ts} +13 -5
- package/src/commands/switchEnv.ts +33 -0
- package/src/commands/test.ts +143 -31
- package/src/commands/upgrade.ts +46 -6
- package/src/commands/wait.ts +333 -22
- package/src/commands/watch.ts +9 -8
- package/src/dubhe.ts +12 -4
- package/src/utils/axios-downloader.ts +116 -0
- package/src/utils/callHandler.ts +118 -118
- package/src/utils/checkBalance.ts +6 -2
- package/src/utils/constants.ts +9 -0
- package/src/utils/generateAccount.ts +1 -1
- package/src/utils/index.ts +4 -3
- package/src/utils/metadataHandler.ts +17 -0
- package/src/utils/publishHandler.ts +404 -289
- package/src/utils/queryStorage.ts +141 -141
- package/src/utils/startNode.ts +115 -16
- package/src/utils/storeConfig.ts +50 -10
- package/src/utils/upgradeHandler.ts +210 -86
- package/src/utils/utils.ts +1025 -63
- package/src/commands/schemagen.ts +0 -40
package/src/commands/test.ts
CHANGED
|
@@ -1,51 +1,163 @@
|
|
|
1
1
|
import type { CommandModule } from 'yargs';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
2
|
+
import { execFileSync, execSync } from 'child_process';
|
|
3
3
|
import { DubheConfig, loadConfig } from '@0xobelisk/sui-common';
|
|
4
|
+
import { handlerExit } from './shell';
|
|
5
|
+
import { lintSystemGuards, formatLintWarnings } from '../utils';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Returns the active Sui client environment (e.g. "localnet", "testnet").
|
|
9
|
+
* Falls back to "testnet" if the command fails.
|
|
10
|
+
*/
|
|
11
|
+
function getActiveSuiEnv(): string {
|
|
12
|
+
try {
|
|
13
|
+
return execSync('sui client active-env', { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
14
|
+
} catch {
|
|
15
|
+
return 'testnet';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type RunMoveTestOptions = {
|
|
20
|
+
/** Substring matched against each test's fully qualified name (`addr::module::fun`). */
|
|
21
|
+
filter?: string;
|
|
22
|
+
'gas-limit'?: string;
|
|
23
|
+
/** Same as `gas-limit` (programmatic API). */
|
|
24
|
+
gasLimit?: string;
|
|
25
|
+
buildEnv?: string;
|
|
26
|
+
/** When true, passes `-l` to list tests instead of running them. */
|
|
27
|
+
list?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type CliOptions = {
|
|
6
31
|
'config-path': string;
|
|
32
|
+
/** Positional from `test [filter]` */
|
|
33
|
+
filter?: string;
|
|
7
34
|
test?: string;
|
|
8
|
-
'gas-limit'
|
|
35
|
+
'gas-limit': string;
|
|
36
|
+
list?: boolean;
|
|
9
37
|
};
|
|
10
38
|
|
|
11
|
-
|
|
12
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Builds argv for `sui move test` (argument array — no shell interpolation).
|
|
41
|
+
*
|
|
42
|
+
* Sui expects an optional filter as a **positional** argument at the end of the command.
|
|
43
|
+
* The `--test` flag on `sui move test` is unrelated (it enables compiling the `tests/` tree).
|
|
44
|
+
*
|
|
45
|
+
* @see `sui move test --help`
|
|
46
|
+
*/
|
|
47
|
+
export function buildSuiMoveTestArgv(options: {
|
|
48
|
+
projectPath: string;
|
|
49
|
+
gasLimit: string;
|
|
50
|
+
buildEnv?: string;
|
|
51
|
+
filter?: string;
|
|
52
|
+
list?: boolean;
|
|
53
|
+
}): string[] {
|
|
54
|
+
const args = ['move', 'test'];
|
|
55
|
+
if (options.buildEnv) {
|
|
56
|
+
args.push('--build-env', options.buildEnv);
|
|
57
|
+
}
|
|
58
|
+
args.push('--path', options.projectPath);
|
|
59
|
+
if (options.list) {
|
|
60
|
+
args.push('-l');
|
|
61
|
+
}
|
|
62
|
+
args.push('--gas-limit', options.gasLimit);
|
|
63
|
+
if (options.filter && !options.list) {
|
|
64
|
+
args.push(options.filter);
|
|
65
|
+
}
|
|
66
|
+
return args;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Core Move test runner for Dubhe contracts.
|
|
71
|
+
* Runs `sui move test` against the package at `src/<dubheConfig.name>`.
|
|
72
|
+
*
|
|
73
|
+
* Move unit tests compile packages locally — no network or published address required.
|
|
74
|
+
*/
|
|
75
|
+
export async function testHandler(
|
|
76
|
+
dubheConfig: DubheConfig,
|
|
77
|
+
options: RunMoveTestOptions = {}
|
|
78
|
+
): Promise<string> {
|
|
79
|
+
const gasLimit = options['gas-limit'] ?? options.gasLimit ?? '100000000';
|
|
80
|
+
const cwd = process.cwd();
|
|
81
|
+
const projectPath = `${cwd}/src/${dubheConfig.name}`;
|
|
82
|
+
const argv = buildSuiMoveTestArgv({
|
|
83
|
+
projectPath,
|
|
84
|
+
gasLimit,
|
|
85
|
+
buildEnv: options.buildEnv,
|
|
86
|
+
filter: options.filter,
|
|
87
|
+
list: options.list
|
|
88
|
+
});
|
|
89
|
+
return execFileSync('sui', argv, { stdio: 'pipe', encoding: 'utf-8' });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveTestFilter(argv: { filter?: string; test?: string }): string | undefined {
|
|
93
|
+
return argv.filter ?? argv.test;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const commandModule: CommandModule<CliOptions, CliOptions> = {
|
|
97
|
+
command: 'test [filter]',
|
|
13
98
|
|
|
14
|
-
describe: 'Run tests in Dubhe contracts',
|
|
99
|
+
describe: 'Run Move unit tests in Dubhe contracts',
|
|
15
100
|
|
|
16
101
|
builder(yargs) {
|
|
17
|
-
return yargs
|
|
18
|
-
'
|
|
19
|
-
type: 'string',
|
|
20
|
-
default: 'dubhe.config.ts',
|
|
21
|
-
description: 'Options to pass to forge test'
|
|
22
|
-
},
|
|
23
|
-
test: {
|
|
24
|
-
type: 'string',
|
|
25
|
-
desc: 'Run a specific test'
|
|
26
|
-
},
|
|
27
|
-
'gas-limit': {
|
|
102
|
+
return yargs
|
|
103
|
+
.positional('filter', {
|
|
28
104
|
type: 'string',
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
105
|
+
describe:
|
|
106
|
+
'Substring of fully qualified test name (see `sui move test --help`); optional when using --list'
|
|
107
|
+
})
|
|
108
|
+
.options({
|
|
109
|
+
'config-path': {
|
|
110
|
+
type: 'string',
|
|
111
|
+
default: 'dubhe.config.ts',
|
|
112
|
+
description: 'Path to the Dubhe config file'
|
|
113
|
+
},
|
|
114
|
+
test: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
describe: 'Same as positional [filter] (kept for backward compatibility)'
|
|
117
|
+
},
|
|
118
|
+
'gas-limit': {
|
|
119
|
+
type: 'string',
|
|
120
|
+
desc: 'Set the gas limit for the test',
|
|
121
|
+
default: '100000000'
|
|
122
|
+
},
|
|
123
|
+
list: {
|
|
124
|
+
type: 'boolean',
|
|
125
|
+
default: false,
|
|
126
|
+
describe: 'List all Move unit tests (`sui move test -l`)'
|
|
127
|
+
}
|
|
128
|
+
});
|
|
33
129
|
},
|
|
34
130
|
|
|
35
|
-
async handler(
|
|
36
|
-
|
|
131
|
+
async handler(argv) {
|
|
132
|
+
const { 'config-path': configPath, 'gas-limit': gasLimit, list } = argv;
|
|
133
|
+
const filter = resolveTestFilter(argv);
|
|
134
|
+
|
|
37
135
|
try {
|
|
38
|
-
console.log('🚀 Running move test');
|
|
136
|
+
console.log(list ? '🚀 Listing Move unit tests' : '🚀 Running move test');
|
|
39
137
|
const dubheConfig = (await loadConfig(configPath)) as DubheConfig;
|
|
40
|
-
|
|
41
|
-
const projectPath = `${
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
138
|
+
|
|
139
|
+
const projectPath = `${process.cwd()}/src/${dubheConfig.name}`;
|
|
140
|
+
const lintResults = lintSystemGuards(projectPath);
|
|
141
|
+
const warnings = formatLintWarnings(lintResults);
|
|
142
|
+
if (warnings) process.stdout.write(warnings);
|
|
143
|
+
|
|
144
|
+
const activeEnv = getActiveSuiEnv();
|
|
145
|
+
const buildEnv = activeEnv === 'localnet' || activeEnv === 'devnet' ? 'testnet' : undefined;
|
|
146
|
+
|
|
147
|
+
const output = await testHandler(dubheConfig, {
|
|
148
|
+
filter,
|
|
149
|
+
'gas-limit': gasLimit,
|
|
150
|
+
buildEnv,
|
|
151
|
+
list
|
|
152
|
+
});
|
|
153
|
+
if (output) process.stdout.write(output);
|
|
46
154
|
} catch (error: any) {
|
|
47
|
-
process.
|
|
155
|
+
if (error.stdout) process.stdout.write(error.stdout);
|
|
156
|
+
if (error.stderr) process.stderr.write(error.stderr);
|
|
157
|
+
if (!error.stdout && !error.stderr && error.message) process.stderr.write(error.message);
|
|
158
|
+
handlerExit(1);
|
|
48
159
|
}
|
|
160
|
+
handlerExit();
|
|
49
161
|
}
|
|
50
162
|
};
|
|
51
163
|
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -2,10 +2,16 @@ import type { CommandModule } from 'yargs';
|
|
|
2
2
|
import { logError } from '../utils/errors';
|
|
3
3
|
import { upgradeHandler } from '../utils/upgradeHandler';
|
|
4
4
|
import { DubheConfig, loadConfig } from '@0xobelisk/sui-common';
|
|
5
|
+
import { handlerExit } from './shell';
|
|
6
|
+
import { getDefaultNetwork, lintSystemGuards, formatLintWarnings, confirm } from '../utils';
|
|
7
|
+
import { join as pathJoin } from 'path';
|
|
8
|
+
import chalk from 'chalk';
|
|
5
9
|
|
|
6
10
|
type Options = {
|
|
7
11
|
network: any;
|
|
8
12
|
'config-path': string;
|
|
13
|
+
'bump-version': boolean;
|
|
14
|
+
'rpc-url'?: string;
|
|
9
15
|
};
|
|
10
16
|
|
|
11
17
|
const commandModule: CommandModule<Options, Options> = {
|
|
@@ -17,27 +23,61 @@ const commandModule: CommandModule<Options, Options> = {
|
|
|
17
23
|
return yargs.options({
|
|
18
24
|
network: {
|
|
19
25
|
type: 'string',
|
|
20
|
-
choices: ['mainnet', 'testnet', 'devnet', 'localnet'],
|
|
21
|
-
default: '
|
|
26
|
+
choices: ['mainnet', 'testnet', 'devnet', 'localnet', 'default'],
|
|
27
|
+
default: 'default',
|
|
22
28
|
desc: 'Network of the node (mainnet/testnet/devnet/localnet)'
|
|
23
29
|
},
|
|
24
30
|
'config-path': {
|
|
25
31
|
type: 'string',
|
|
26
32
|
default: 'dubhe.config.ts',
|
|
27
33
|
decs: 'Path to the config file'
|
|
34
|
+
},
|
|
35
|
+
'bump-version': {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
default: false,
|
|
38
|
+
desc: 'Force a version bump even when no new resources were added (use for breaking logic changes or security fixes that must invalidate old clients)'
|
|
39
|
+
},
|
|
40
|
+
'rpc-url': {
|
|
41
|
+
type: 'string',
|
|
42
|
+
desc: 'Custom RPC endpoint URL (overrides the default for the selected network)'
|
|
28
43
|
}
|
|
29
44
|
});
|
|
30
45
|
},
|
|
31
46
|
|
|
32
|
-
async handler({
|
|
47
|
+
async handler({
|
|
48
|
+
network,
|
|
49
|
+
'config-path': configPath,
|
|
50
|
+
'bump-version': bumpVersion,
|
|
51
|
+
'rpc-url': rpcUrl
|
|
52
|
+
}) {
|
|
33
53
|
try {
|
|
54
|
+
if (network == 'default') {
|
|
55
|
+
network = await getDefaultNetwork();
|
|
56
|
+
console.log(chalk.yellow(`Use default network: [${network}]`));
|
|
57
|
+
}
|
|
34
58
|
const dubheConfig = (await loadConfig(configPath)) as DubheConfig;
|
|
35
|
-
|
|
59
|
+
|
|
60
|
+
const projectPath = pathJoin(process.cwd(), 'src', dubheConfig.name);
|
|
61
|
+
const lintResults = lintSystemGuards(projectPath);
|
|
62
|
+
if (lintResults.length > 0) {
|
|
63
|
+
process.stdout.write(formatLintWarnings(lintResults));
|
|
64
|
+
const proceed = await confirm(
|
|
65
|
+
'Some entry functions are missing ensure_latest_version. Proceed with upgrade anyway?'
|
|
66
|
+
);
|
|
67
|
+
if (!proceed) {
|
|
68
|
+
console.log(chalk.red('Upgrade cancelled.'));
|
|
69
|
+
handlerExit(1);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const fullnodeUrls = rpcUrl ? [rpcUrl] : undefined;
|
|
75
|
+
await upgradeHandler(dubheConfig, dubheConfig.name, network, bumpVersion, fullnodeUrls);
|
|
36
76
|
} catch (error: any) {
|
|
37
77
|
logError(error);
|
|
38
|
-
|
|
78
|
+
handlerExit(1);
|
|
39
79
|
}
|
|
40
|
-
|
|
80
|
+
handlerExit();
|
|
41
81
|
}
|
|
42
82
|
};
|
|
43
83
|
|
package/src/commands/wait.ts
CHANGED
|
@@ -2,56 +2,367 @@ import type { CommandModule } from 'yargs';
|
|
|
2
2
|
import waitOn from 'wait-on';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
+
import net from 'net';
|
|
6
|
+
import { handlerExit } from './shell';
|
|
5
7
|
|
|
6
8
|
interface WaitOptions {
|
|
7
|
-
url
|
|
9
|
+
url?: string;
|
|
10
|
+
localnet?: boolean;
|
|
11
|
+
'local-database'?: boolean;
|
|
12
|
+
'local-node'?: boolean;
|
|
13
|
+
'local-indexer'?: boolean;
|
|
8
14
|
timeout: number;
|
|
9
15
|
interval: number;
|
|
10
16
|
}
|
|
11
17
|
|
|
18
|
+
async function withoutProxy<T>(fn: () => Promise<T>): Promise<T> {
|
|
19
|
+
const originalProxy = {
|
|
20
|
+
HTTP_PROXY: process.env.HTTP_PROXY,
|
|
21
|
+
HTTPS_PROXY: process.env.HTTPS_PROXY,
|
|
22
|
+
http_proxy: process.env.http_proxy,
|
|
23
|
+
https_proxy: process.env.https_proxy,
|
|
24
|
+
NO_PROXY: process.env.NO_PROXY,
|
|
25
|
+
no_proxy: process.env.no_proxy
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
delete process.env.HTTP_PROXY;
|
|
29
|
+
delete process.env.HTTPS_PROXY;
|
|
30
|
+
delete process.env.http_proxy;
|
|
31
|
+
delete process.env.https_proxy;
|
|
32
|
+
process.env.NO_PROXY = '127.0.0.1,localhost,*.local';
|
|
33
|
+
process.env.no_proxy = '127.0.0.1,localhost,*.local';
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
return await fn();
|
|
37
|
+
} finally {
|
|
38
|
+
Object.keys(originalProxy).forEach((key) => {
|
|
39
|
+
const value = originalProxy[key as keyof typeof originalProxy];
|
|
40
|
+
if (value !== undefined) {
|
|
41
|
+
process.env[key] = value;
|
|
42
|
+
} else {
|
|
43
|
+
delete process.env[key];
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check if PostgreSQL port is occupied (service is running)
|
|
50
|
+
async function checkPostgreSQLRunning(): Promise<boolean> {
|
|
51
|
+
return checkPortRunning(5432);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Generic port checking function
|
|
55
|
+
async function checkPortRunning(port: number, host: string = '127.0.0.1'): Promise<boolean> {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
const socket = new net.Socket();
|
|
58
|
+
let isConnected = false;
|
|
59
|
+
|
|
60
|
+
// Set timeout for connection attempt
|
|
61
|
+
const timeout = setTimeout(() => {
|
|
62
|
+
socket.destroy();
|
|
63
|
+
if (!isConnected) {
|
|
64
|
+
resolve(false);
|
|
65
|
+
}
|
|
66
|
+
}, 2000);
|
|
67
|
+
|
|
68
|
+
socket.connect(port, host, () => {
|
|
69
|
+
isConnected = true;
|
|
70
|
+
clearTimeout(timeout);
|
|
71
|
+
socket.destroy();
|
|
72
|
+
resolve(true); // Port is occupied, service is running
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
socket.on('error', () => {
|
|
76
|
+
clearTimeout(timeout);
|
|
77
|
+
if (!isConnected) {
|
|
78
|
+
resolve(false); // Connection failed, service not running
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check indexer health endpoint
|
|
85
|
+
async function checkIndexerHealth(): Promise<boolean> {
|
|
86
|
+
try {
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
89
|
+
|
|
90
|
+
const response = await fetch('http://127.0.0.1:8080/health', {
|
|
91
|
+
signal: controller.signal,
|
|
92
|
+
headers: {
|
|
93
|
+
Accept: 'application/json'
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
return response.status === 200;
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Wait for all localnet services with custom checks
|
|
105
|
+
async function waitForLocalnetServices(options: WaitOptions): Promise<void> {
|
|
106
|
+
const spinner = ora({
|
|
107
|
+
text: 'Waiting for dubhe localnet services...',
|
|
108
|
+
color: 'cyan'
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
spinner.start();
|
|
112
|
+
|
|
113
|
+
const startTime = Date.now();
|
|
114
|
+
|
|
115
|
+
while (Date.now() - startTime < options.timeout) {
|
|
116
|
+
try {
|
|
117
|
+
// Check HTTP services using wait-on (excluding 9000 port)
|
|
118
|
+
await withoutProxy(() =>
|
|
119
|
+
waitOn({
|
|
120
|
+
resources: [
|
|
121
|
+
'http://127.0.0.1:9123', // Sui faucet
|
|
122
|
+
'http://127.0.0.1:4000' // GraphQL server
|
|
123
|
+
],
|
|
124
|
+
timeout: options.interval,
|
|
125
|
+
interval: 500,
|
|
126
|
+
validateStatus: (status: number) => status === 200
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Check PostgreSQL separately
|
|
131
|
+
const postgresRunning = await checkPostgreSQLRunning();
|
|
132
|
+
|
|
133
|
+
if (postgresRunning) {
|
|
134
|
+
spinner.succeed(chalk.green('All dubhe localnet services are ready!'));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
} catch (_error) {
|
|
138
|
+
// Continue waiting...
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Wait before next check
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, options.interval));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Timeout reached
|
|
146
|
+
throw new Error('Timeout waiting for services');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Wait for local database
|
|
150
|
+
async function waitForLocalDatabase(options: WaitOptions): Promise<void> {
|
|
151
|
+
const spinner = ora({
|
|
152
|
+
text: 'Waiting for local database...',
|
|
153
|
+
color: 'cyan'
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
spinner.start();
|
|
157
|
+
|
|
158
|
+
const startTime = Date.now();
|
|
159
|
+
|
|
160
|
+
while (Date.now() - startTime < options.timeout) {
|
|
161
|
+
const isRunning = await checkPostgreSQLRunning();
|
|
162
|
+
|
|
163
|
+
if (isRunning) {
|
|
164
|
+
spinner.succeed(chalk.green('Local database is ready!'));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Wait before next check
|
|
169
|
+
await new Promise((resolve) => setTimeout(resolve, options.interval));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Timeout reached
|
|
173
|
+
throw new Error('Timeout waiting for local database');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Wait for local Sui node
|
|
177
|
+
async function waitForLocalNode(options: WaitOptions): Promise<void> {
|
|
178
|
+
const spinner = ora({
|
|
179
|
+
text: 'Waiting for local Sui node...',
|
|
180
|
+
color: 'cyan'
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
spinner.start();
|
|
184
|
+
|
|
185
|
+
const startTime = Date.now();
|
|
186
|
+
|
|
187
|
+
while (Date.now() - startTime < options.timeout) {
|
|
188
|
+
const isRunning = await checkPortRunning(9123);
|
|
189
|
+
|
|
190
|
+
if (isRunning) {
|
|
191
|
+
spinner.succeed(chalk.green('Local Sui node is ready!'));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Wait before next check
|
|
196
|
+
await new Promise((resolve) => setTimeout(resolve, options.interval));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Timeout reached
|
|
200
|
+
throw new Error('Timeout waiting for local Sui node');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Wait for local indexer
|
|
204
|
+
async function waitForLocalIndexer(options: WaitOptions): Promise<void> {
|
|
205
|
+
const spinner = ora({
|
|
206
|
+
text: 'Waiting for local indexer...',
|
|
207
|
+
color: 'cyan'
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
spinner.start();
|
|
211
|
+
|
|
212
|
+
const startTime = Date.now();
|
|
213
|
+
|
|
214
|
+
while (Date.now() - startTime < options.timeout) {
|
|
215
|
+
const isRunning = await checkIndexerHealth();
|
|
216
|
+
|
|
217
|
+
if (isRunning) {
|
|
218
|
+
spinner.succeed(chalk.green('Local indexer is ready!'));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Wait before next check
|
|
223
|
+
await new Promise((resolve) => setTimeout(resolve, options.interval));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Timeout reached
|
|
227
|
+
throw new Error('Timeout waiting for local indexer');
|
|
228
|
+
}
|
|
229
|
+
|
|
12
230
|
const commandModule: CommandModule = {
|
|
13
231
|
command: 'wait',
|
|
14
|
-
describe: 'Wait for service to be ready',
|
|
232
|
+
describe: 'Wait for service(s) to be ready',
|
|
15
233
|
builder(yargs) {
|
|
16
234
|
return yargs
|
|
17
235
|
.option('url', {
|
|
18
236
|
type: 'string',
|
|
19
|
-
description: 'URL to wait for'
|
|
237
|
+
description: 'URL to wait for (single service)'
|
|
238
|
+
})
|
|
239
|
+
.option('localnet', {
|
|
240
|
+
type: 'boolean',
|
|
241
|
+
description:
|
|
242
|
+
'Wait for all dubhe localnet services (sui localnode:9000&9123, postgres:5432, graphql:4000)',
|
|
243
|
+
default: false
|
|
244
|
+
})
|
|
245
|
+
.option('local-database', {
|
|
246
|
+
type: 'boolean',
|
|
247
|
+
description: 'Wait for local database (PostgreSQL on port 5432)',
|
|
248
|
+
default: false
|
|
249
|
+
})
|
|
250
|
+
.option('local-node', {
|
|
251
|
+
type: 'boolean',
|
|
252
|
+
description: 'Wait for local Sui node (port 9123)',
|
|
253
|
+
default: false
|
|
254
|
+
})
|
|
255
|
+
.option('local-indexer', {
|
|
256
|
+
type: 'boolean',
|
|
257
|
+
description: 'Wait for local indexer (health check at http://127.0.0.1:8080/health)',
|
|
258
|
+
default: false
|
|
20
259
|
})
|
|
21
260
|
.option('timeout', {
|
|
22
261
|
type: 'number',
|
|
23
262
|
description: 'Timeout (in milliseconds)',
|
|
24
|
-
default:
|
|
263
|
+
default: 24 * 60 * 60 * 1000 // 24 hours, effectively no timeout
|
|
25
264
|
})
|
|
26
265
|
.option('interval', {
|
|
27
266
|
type: 'number',
|
|
28
267
|
description: 'Check interval (in milliseconds)',
|
|
29
268
|
default: 1000
|
|
269
|
+
})
|
|
270
|
+
.check((argv) => {
|
|
271
|
+
const hasUrl = !!argv.url;
|
|
272
|
+
const hasLocalnet = !!argv.localnet;
|
|
273
|
+
const hasLocalDatabase = !!argv['local-database'];
|
|
274
|
+
const hasLocalNode = !!argv['local-node'];
|
|
275
|
+
const hasLocalIndexer = !!argv['local-indexer'];
|
|
276
|
+
|
|
277
|
+
const optionCount = [
|
|
278
|
+
hasUrl,
|
|
279
|
+
hasLocalnet,
|
|
280
|
+
hasLocalDatabase,
|
|
281
|
+
hasLocalNode,
|
|
282
|
+
hasLocalIndexer
|
|
283
|
+
].filter(Boolean).length;
|
|
284
|
+
|
|
285
|
+
if (optionCount === 0) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
'Please provide at least one option: --url, --localnet, --local-database, --local-node, or --local-indexer'
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (hasUrl && optionCount > 1) {
|
|
292
|
+
throw new Error('Cannot use --url together with other options');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (hasLocalnet && (hasLocalDatabase || hasLocalNode || hasLocalIndexer)) {
|
|
296
|
+
throw new Error('Cannot use --localnet together with individual service options');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return true;
|
|
30
300
|
});
|
|
31
301
|
},
|
|
32
302
|
async handler(argv) {
|
|
33
303
|
const options = argv as unknown as WaitOptions;
|
|
34
|
-
const spinner = ora({
|
|
35
|
-
text: `Waiting for service to start ${chalk.cyan(options.url)}...`,
|
|
36
|
-
color: 'cyan'
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
spinner.start();
|
|
40
304
|
|
|
41
305
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
306
|
+
if (options.localnet) {
|
|
307
|
+
await waitForLocalnetServices(options);
|
|
308
|
+
} else if (options['local-database']) {
|
|
309
|
+
await waitForLocalDatabase(options);
|
|
310
|
+
} else if (options['local-node']) {
|
|
311
|
+
await waitForLocalNode(options);
|
|
312
|
+
} else if (options['local-indexer']) {
|
|
313
|
+
await waitForLocalIndexer(options);
|
|
314
|
+
} else {
|
|
315
|
+
// Single URL mode - use original wait-on logic
|
|
316
|
+
const spinner = ora({
|
|
317
|
+
text: `Waiting for ${options.url}...`,
|
|
318
|
+
color: 'cyan'
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
spinner.start();
|
|
322
|
+
|
|
323
|
+
await withoutProxy(() =>
|
|
324
|
+
waitOn({
|
|
325
|
+
resources: [options.url!],
|
|
326
|
+
timeout: options.timeout,
|
|
327
|
+
interval: options.interval,
|
|
328
|
+
validateStatus: (status: number) => status === 200
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
spinner.succeed(chalk.green('Service is ready!'));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
handlerExit();
|
|
336
|
+
} catch (_error) {
|
|
337
|
+
const spinner = ora();
|
|
338
|
+
|
|
339
|
+
let errorMessage = 'Timeout waiting for service';
|
|
340
|
+
let helpMessage = 'Please make sure the service is running...';
|
|
341
|
+
|
|
342
|
+
if (options.localnet) {
|
|
343
|
+
errorMessage = 'Timeout waiting for dubhe localnet services';
|
|
344
|
+
helpMessage =
|
|
345
|
+
'Please make sure all required services are running:\n' +
|
|
346
|
+
'- Sui localnode on port 9000\n' +
|
|
347
|
+
'- Sui faucet on port 9123\n' +
|
|
348
|
+
'- PostgreSQL database on port 5432\n' +
|
|
349
|
+
'- Dubhe GraphQL server on port 4000';
|
|
350
|
+
} else if (options['local-database']) {
|
|
351
|
+
errorMessage = 'Timeout waiting for local database';
|
|
352
|
+
helpMessage = 'Please make sure PostgreSQL is running on port 5432';
|
|
353
|
+
} else if (options['local-node']) {
|
|
354
|
+
errorMessage = 'Timeout waiting for local Sui node';
|
|
355
|
+
helpMessage = 'Please make sure Sui localnode is running on port 9123';
|
|
356
|
+
} else if (options['local-indexer']) {
|
|
357
|
+
errorMessage = 'Timeout waiting for local indexer';
|
|
358
|
+
helpMessage =
|
|
359
|
+
'Please make sure indexer is running and health endpoint is available at http://127.0.0.1:8080/health';
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
spinner.fail(chalk.red(errorMessage));
|
|
363
|
+
console.error(chalk.yellow(helpMessage));
|
|
48
364
|
|
|
49
|
-
|
|
50
|
-
process.exit(0);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
spinner.fail(chalk.red('Timeout waiting for service'));
|
|
53
|
-
console.error(chalk.yellow('Please make sure the service is running...'));
|
|
54
|
-
process.exit(1);
|
|
365
|
+
handlerExit(1);
|
|
55
366
|
}
|
|
56
367
|
}
|
|
57
368
|
};
|