@0xobelisk/sui-cli 1.2.0-pre.12 → 1.2.0-pre.121

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 (40) hide show
  1. package/README.md +7 -7
  2. package/dist/dubhe.js +152 -51
  3. package/dist/dubhe.js.map +1 -1
  4. package/package.json +31 -19
  5. package/src/commands/build.ts +61 -18
  6. package/src/commands/call.ts +83 -83
  7. package/src/commands/checkBalance.ts +27 -12
  8. package/src/commands/convertJson.ts +85 -0
  9. package/src/commands/doctor.ts +1515 -0
  10. package/src/commands/faucet.ts +20 -10
  11. package/src/commands/generate.ts +61 -0
  12. package/src/commands/generateKey.ts +3 -2
  13. package/src/commands/index.ts +20 -11
  14. package/src/commands/info.ts +61 -0
  15. package/src/commands/loadMetadata.ts +68 -0
  16. package/src/commands/localnode.ts +22 -6
  17. package/src/commands/publish.ts +55 -7
  18. package/src/commands/query.ts +101 -101
  19. package/src/commands/shell.ts +208 -0
  20. package/src/commands/{configStore.ts → storeConfig.ts} +13 -5
  21. package/src/commands/switchEnv.ts +33 -0
  22. package/src/commands/test.ts +143 -31
  23. package/src/commands/upgrade.ts +46 -6
  24. package/src/commands/wait.ts +333 -22
  25. package/src/commands/watch.ts +9 -8
  26. package/src/dubhe.ts +12 -4
  27. package/src/utils/axios-downloader.ts +116 -0
  28. package/src/utils/callHandler.ts +118 -118
  29. package/src/utils/checkBalance.ts +6 -2
  30. package/src/utils/constants.ts +9 -0
  31. package/src/utils/generateAccount.ts +1 -1
  32. package/src/utils/index.ts +4 -3
  33. package/src/utils/metadataHandler.ts +17 -0
  34. package/src/utils/publishHandler.ts +408 -289
  35. package/src/utils/queryStorage.ts +141 -141
  36. package/src/utils/startNode.ts +115 -16
  37. package/src/utils/storeConfig.ts +50 -10
  38. package/src/utils/upgradeHandler.ts +218 -85
  39. package/src/utils/utils.ts +1041 -63
  40. package/src/commands/schemagen.ts +0 -40
@@ -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
- type Options = {
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'?: string;
35
+ 'gas-limit': string;
36
+ list?: boolean;
9
37
  };
10
38
 
11
- const commandModule: CommandModule<Options, Options> = {
12
- command: 'test',
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.options({
18
- 'config-path': {
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
- desc: 'Set the gas limit for the test',
30
- default: '100000000'
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({ 'config-path': configPath, test, 'gas-limit': gasLimit }) {
36
- // Start an internal anvil process if no world address is provided
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
- const path = process.cwd();
41
- const projectPath = `${path}/src/${dubheConfig.name}`;
42
- const command = `sui move test --path ${projectPath} ${
43
- test ? ` --test ${test}` : ''
44
- } --gas-limit ${gasLimit}`;
45
- execSync(command, { stdio: 'inherit', encoding: 'utf-8' });
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.exit(0);
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
 
@@ -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: 'localnet',
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({ network, 'config-path': configPath }) {
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
- await upgradeHandler(dubheConfig, dubheConfig.name, network);
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
- process.exit(1);
78
+ handlerExit(1);
39
79
  }
40
- process.exit(0);
80
+ handlerExit();
41
81
  }
42
82
  };
43
83
 
@@ -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: string;
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: 180000
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
- await waitOn({
43
- resources: [options.url],
44
- timeout: options.timeout,
45
- interval: options.interval,
46
- validateStatus: (status: number) => status === 200
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
- spinner.succeed(chalk.green('Service is ready!'));
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
  };