@0xobelisk/sui-cli 1.2.0-pre.3 → 1.2.0-pre.34

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.
@@ -6,17 +6,19 @@ import { FsIibError } from './errors';
6
6
  import * as fs from 'fs';
7
7
  import chalk from 'chalk';
8
8
  import { spawn } from 'child_process';
9
- import { Dubhe, NetworkType, SuiMoveNormalizedModules } from '@0xobelisk/sui-client';
9
+ import { Dubhe, NetworkType, SuiMoveNormalizedModules, loadMetadata } from '@0xobelisk/sui-client';
10
10
  import { DubheCliError } from './errors';
11
+ import packageJson from '../../package.json';
12
+ import { Component, MoveType, EmptyComponent, DubheConfig } from '@0xobelisk/sui-common';
11
13
 
12
14
  export type DeploymentJsonType = {
13
15
  projectName: string;
14
16
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet';
15
17
  packageId: string;
16
- schemaId: string;
18
+ dappHub: string;
17
19
  upgradeCap: string;
18
20
  version: number;
19
- schemas: Record<string, string>;
21
+ components: Record<string, Component | MoveType | EmptyComponent>;
20
22
  };
21
23
 
22
24
  export function validatePrivateKey(privateKey: string): false | string {
@@ -75,43 +77,43 @@ export async function getDeploymentJson(
75
77
  }
76
78
  }
77
79
 
78
- export async function getDeploymentSchemaId(projectPath: string, network: string): Promise<string> {
80
+ export async function getDeploymentDappHub(projectPath: string, network: string): Promise<string> {
79
81
  try {
80
82
  const data = await fsAsync.readFile(
81
83
  `${projectPath}/.history/sui_${network}/latest.json`,
82
84
  'utf8'
83
85
  );
84
86
  const deployment = JSON.parse(data) as DeploymentJsonType;
85
- return deployment.schemaId;
87
+ return deployment.dappHub;
86
88
  } catch (error) {
87
89
  return '';
88
90
  }
89
91
  }
90
92
 
91
- export async function getDubheSchemaId(network: string) {
93
+ export async function getDubheDappHub(network: string) {
92
94
  const path = process.cwd();
93
- const contractPath = `${path}/contracts/dubhe-framework`;
95
+ const contractPath = `${path}/src/dubhe`;
94
96
 
95
97
  switch (network) {
96
98
  case 'mainnet':
97
- return await getDeploymentSchemaId(contractPath, 'mainnet');
99
+ return await getDeploymentDappHub(contractPath, 'mainnet');
98
100
  case 'testnet':
99
- return '0xa565cbb3641fff8f7e8ef384b215808db5f1837aa72c1cca1803b5d973699aac';
101
+ return await getDeploymentDappHub(contractPath, 'testnet');
100
102
  case 'devnet':
101
- return await getDeploymentSchemaId(contractPath, 'devnet');
103
+ return await getDeploymentDappHub(contractPath, 'devnet');
102
104
  case 'localnet':
103
- return await getDeploymentSchemaId(contractPath, 'localnet');
105
+ return await getDeploymentDappHub(contractPath, 'localnet');
104
106
  default:
105
107
  throw new Error(`Invalid network: ${network}`);
106
108
  }
107
109
  }
108
110
 
109
- export async function getOnchainSchemas(
111
+ export async function getOnchainComponents(
110
112
  projectPath: string,
111
113
  network: string
112
- ): Promise<Record<string, string>> {
114
+ ): Promise<Record<string, Component | MoveType | EmptyComponent>> {
113
115
  const deployment = await getDeploymentJson(projectPath, network);
114
- return deployment.schemas;
116
+ return deployment.components;
115
117
  }
116
118
 
117
119
  export async function getVersion(projectPath: string, network: string): Promise<number> {
@@ -132,9 +134,9 @@ export async function getOldPackageId(projectPath: string, network: string): Pro
132
134
  return deployment.packageId;
133
135
  }
134
136
 
135
- export async function getSchemaId(projectPath: string, network: string): Promise<string> {
137
+ export async function getDappHub(projectPath: string, network: string): Promise<string> {
136
138
  const deployment = await getDeploymentJson(projectPath, network);
137
- return deployment.schemaId;
139
+ return deployment.dappHub;
138
140
  }
139
141
 
140
142
  export async function getUpgradeCap(projectPath: string, network: string): Promise<string> {
@@ -142,34 +144,62 @@ export async function getUpgradeCap(projectPath: string, network: string): Promi
142
144
  return deployment.upgradeCap;
143
145
  }
144
146
 
145
- export function saveContractData(
147
+ export async function saveContractData(
146
148
  projectName: string,
147
149
  network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
148
150
  packageId: string,
149
- schemaId: string,
151
+ dappHub: string,
150
152
  upgradeCap: string,
151
153
  version: number,
152
- schemas: Record<string, string>
154
+ components: Record<string, Component | MoveType | EmptyComponent>
153
155
  ) {
154
156
  const DeploymentData: DeploymentJsonType = {
155
157
  projectName,
156
158
  network,
157
159
  packageId,
158
- schemaId,
159
- schemas,
160
+ dappHub,
161
+ components,
160
162
  upgradeCap,
161
163
  version
162
164
  };
163
165
 
164
166
  const path = process.cwd();
165
167
  const storeDeploymentData = JSON.stringify(DeploymentData, null, 2);
166
- writeOutput(
168
+ await writeOutput(
167
169
  storeDeploymentData,
168
- `${path}/contracts/${projectName}/.history/sui_${network}/latest.json`,
170
+ `${path}/src/${projectName}/.history/sui_${network}/latest.json`,
169
171
  'Update deploy log'
170
172
  );
171
173
  }
172
174
 
175
+ export async function saveMetadata(
176
+ projectName: string,
177
+ network: 'mainnet' | 'testnet' | 'devnet' | 'localnet',
178
+ packageId: string
179
+ ) {
180
+ const path = process.cwd();
181
+
182
+ // Save metadata files
183
+ try {
184
+ const metadata = await loadMetadata(network, packageId);
185
+ if (metadata) {
186
+ const metadataJson = JSON.stringify(metadata, null, 2);
187
+
188
+ // Save packageId-specific metadata file
189
+ await writeOutput(
190
+ metadataJson,
191
+ `${path}/src/${projectName}/.history/sui_${network}/${packageId}.json`,
192
+ 'Save package metadata'
193
+ );
194
+
195
+ // Save latest metadata.json
196
+ await writeOutput(metadataJson, `${path}/metadata.json`, 'Save latest metadata');
197
+ }
198
+ } catch (error) {
199
+ console.warn(chalk.yellow(`Warning: Failed to save metadata: ${error}`));
200
+ }
201
+ }
202
+
173
203
  export async function writeOutput(
174
204
  output: string,
175
205
  fullOutputPath: string,
@@ -186,11 +216,11 @@ export async function writeOutput(
186
216
  function getDubheDependency(network: 'mainnet' | 'testnet' | 'devnet' | 'localnet'): string {
187
217
  switch (network) {
188
218
  case 'localnet':
189
- return 'Dubhe = { local = "../dubhe-framework" }';
219
+ return 'Dubhe = { local = "../dubhe" }';
190
220
  case 'testnet':
191
- return 'Dubhe = { git = "https://github.com/0xobelisk/dubhe-framework.git", subdir = "contracts/dubhe", rev = "develop" }';
221
+ return `Dubhe = { git = "https://github.com/0xobelisk/dubhe-wip.git", subdir = "packages/sui-framework/src/dubhe", rev = "v${packageJson.version}" }`;
192
222
  case 'mainnet':
193
- return 'Dubhe = { git = "https://github.com/0xobelisk/dubhe-framework.git", subdir = "contracts/dubhe", rev = "develop" }';
223
+ return `Dubhe = { git = "https://github.com/0xobelisk/dubhe-wip.git", subdir = "packages/sui-framework/src/dubhe", rev = "v${packageJson.version}" }`;
194
224
  default:
195
225
  throw new Error(`Unsupported network: ${network}`);
196
226
  }
@@ -206,32 +236,147 @@ export async function updateDubheDependency(
206
236
  fs.writeFileSync(filePath, updatedContent, 'utf-8');
207
237
  console.log(`Updated Dubhe dependency in ${filePath} for ${network}.`);
208
238
  }
239
+
240
+ async function checkRpcAvailability(rpcUrl: string): Promise<boolean> {
241
+ try {
242
+ const response = await fetch(rpcUrl, {
243
+ method: 'POST',
244
+ headers: {
245
+ 'Content-Type': 'application/json'
246
+ },
247
+ body: JSON.stringify({
248
+ jsonrpc: '2.0',
249
+ id: 1,
250
+ method: 'sui_getLatestCheckpointSequenceNumber',
251
+ params: []
252
+ })
253
+ });
254
+
255
+ if (!response.ok) {
256
+ return false;
257
+ }
258
+
259
+ const data = await response.json();
260
+ return !data.error;
261
+ } catch (error) {
262
+ return false;
263
+ }
264
+ }
265
+
266
+ export async function addEnv(
267
+ network: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
268
+ ): Promise<void> {
269
+ const rpcMap = {
270
+ localnet: 'http://127.0.0.1:9000',
271
+ devnet: 'https://fullnode.devnet.sui.io:443/',
272
+ testnet: 'https://fullnode.testnet.sui.io:443/',
273
+ mainnet: 'https://fullnode.mainnet.sui.io:443/'
274
+ };
275
+
276
+ const rpcUrl = rpcMap[network];
277
+
278
+ // Check RPC availability first
279
+ const isRpcAvailable = await checkRpcAvailability(rpcUrl);
280
+ if (!isRpcAvailable) {
281
+ throw new Error(
282
+ `RPC endpoint ${rpcUrl} is not available. Please check your network connection or try again later.`
283
+ );
284
+ }
285
+
286
+ return new Promise<void>((resolve, reject) => {
287
+ let errorOutput = '';
288
+ let stdoutOutput = '';
289
+
290
+ const suiProcess = spawn(
291
+ 'sui',
292
+ ['client', 'new-env', '--alias', network, '--rpc', rpcMap[network]],
293
+ {
294
+ env: { ...process.env },
295
+ stdio: 'pipe'
296
+ }
297
+ );
298
+
299
+ // Capture standard output
300
+ suiProcess.stdout.on('data', (data) => {
301
+ stdoutOutput += data.toString();
302
+ });
303
+
304
+ // Capture error output
305
+ suiProcess.stderr.on('data', (data) => {
306
+ errorOutput += data.toString();
307
+ });
308
+
309
+ // Handle process errors (e.g., command not found)
310
+ suiProcess.on('error', (error) => {
311
+ console.error(chalk.red(`\n❌ Failed to execute sui command: ${error.message}`));
312
+ reject(new Error(`Failed to execute sui command: ${error.message}`));
313
+ });
314
+
315
+ // Handle process exit
316
+ suiProcess.on('exit', (code) => {
317
+ // Check if "already exists" message is present
318
+ if (errorOutput.includes('already exists') || stdoutOutput.includes('already exists')) {
319
+ console.log(chalk.yellow(`Environment ${network} already exists, proceeding...`));
320
+ resolve();
321
+ return;
322
+ }
323
+
324
+ if (code === 0) {
325
+ console.log(chalk.green(`Successfully added environment ${network}`));
326
+ resolve();
327
+ } else {
328
+ const finalError = errorOutput || stdoutOutput || `Process exited with code ${code}`;
329
+ console.error(chalk.red(`\n❌ Failed to add environment ${network}`));
330
+ console.error(chalk.red(` └─ ${finalError.trim()}`));
331
+ reject(new Error(finalError));
332
+ }
333
+ });
334
+ });
335
+ }
336
+
209
337
  export async function switchEnv(network: 'mainnet' | 'testnet' | 'devnet' | 'localnet') {
210
338
  try {
339
+ // First, try to add the environment
340
+ await addEnv(network);
341
+
342
+ // Then switch to the specified environment
211
343
  return new Promise<void>((resolve, reject) => {
344
+ let errorOutput = '';
345
+ let stdoutOutput = '';
346
+
212
347
  const suiProcess = spawn('sui', ['client', 'switch', '--env', network], {
213
348
  env: { ...process.env },
214
349
  stdio: 'pipe'
215
350
  });
216
351
 
352
+ suiProcess.stdout.on('data', (data) => {
353
+ stdoutOutput += data.toString();
354
+ });
355
+
356
+ suiProcess.stderr.on('data', (data) => {
357
+ errorOutput += data.toString();
358
+ });
359
+
217
360
  suiProcess.on('error', (error) => {
218
- console.error(chalk.red('\n❌ Failed to Switch Env'));
219
- console.error(chalk.red(` Error: ${error.message}`));
220
- reject(error); // Reject promise on error
361
+ console.error(chalk.red(`\n❌ Failed to execute sui command: ${error.message}`));
362
+ reject(new Error(`Failed to execute sui command: ${error.message}`));
221
363
  });
222
364
 
223
365
  suiProcess.on('exit', (code) => {
224
- if (code !== 0) {
225
- console.error(chalk.red(`\n❌ Process exited with code: ${code}`));
226
- reject(new Error(`Process exited with code: ${code}`));
366
+ if (code === 0) {
367
+ console.log(chalk.green(`Successfully switched to environment ${network}`));
368
+ resolve();
227
369
  } else {
228
- resolve(); // Resolve promise on successful exit
370
+ const finalError = errorOutput || stdoutOutput || `Process exited with code ${code}`;
371
+ console.error(chalk.red(`\n❌ Failed to switch to environment ${network}`));
372
+ console.error(chalk.red(` └─ ${finalError.trim()}`));
373
+ reject(new Error(finalError));
229
374
  }
230
375
  });
231
376
  });
232
377
  } catch (error) {
233
- console.error(chalk.red('\n❌ Failed to Switch Env'));
234
- console.error(chalk.red(` └─ Error: ${error}`));
378
+ // Re-throw the error for the caller to handle
379
+ throw error;
235
380
  }
236
381
  }
237
382
 
@@ -271,3 +416,114 @@ export function initializeDubhe({
271
416
  metadata
272
417
  });
273
418
  }
419
+
420
+ export function generateConfigJson(config: DubheConfig): string {
421
+ const components = Object.entries(config.components).map(([name, component]) => {
422
+ if (typeof component === 'string') {
423
+ return {
424
+ [name]: {
425
+ fields: [
426
+ { entity_id: 'address' },
427
+ { value: component }
428
+ ],
429
+ keys: ['entity_id']
430
+ }
431
+ };
432
+ }
433
+
434
+ if (Object.keys(component as object).length === 0) {
435
+ return {
436
+ [name]: {
437
+ fields: [
438
+ { entity_id: 'address' }
439
+ ],
440
+ keys: ['entity_id']
441
+ }
442
+ };
443
+ }
444
+
445
+ const fields = (component as any).fields || {};
446
+ const keys = (component as any).keys || ['entity_id'];
447
+
448
+ // ensure entity_id field exists
449
+ if (!fields.entity_id && keys.includes('entity_id')) {
450
+ fields.entity_id = 'address';
451
+ }
452
+
453
+ return {
454
+ [name]: {
455
+ fields: Object.entries(fields).map(([fieldName, fieldType]) => ({
456
+ [fieldName]: fieldType
457
+ })),
458
+ keys: keys
459
+ }
460
+ };
461
+ });
462
+
463
+ const resources = Object.entries(config.resources).map(([name, resource]) => {
464
+ if (typeof resource === 'string') {
465
+ return {
466
+ [name]: {
467
+ fields: [
468
+ { value: resource }
469
+ ],
470
+ keys: []
471
+ }
472
+ };
473
+ }
474
+
475
+ if (Object.keys(resource as object).length === 0) {
476
+ return {
477
+ [name]: {
478
+ fields: [],
479
+ keys: []
480
+ }
481
+ };
482
+ }
483
+
484
+ const fields = (resource as any).fields || {};
485
+ const keys = (resource as any).keys || [];
486
+
487
+ return {
488
+ [name]: {
489
+ fields: Object.entries(fields).map(([fieldName, fieldType]) => ({
490
+ [fieldName]: fieldType
491
+ })),
492
+ keys: keys
493
+ }
494
+ };
495
+ });
496
+
497
+ // handle enums
498
+ const enums = Object.entries(config.enums || {}).map(([name, enumFields]) => {
499
+ // Sort enum values by first letter
500
+ let sortedFields = enumFields.sort((a, b) => a.localeCompare(b)).map((value, index) => ({
501
+ [index]: value
502
+ }));
503
+
504
+ return {
505
+ [name]: {
506
+ fields: sortedFields
507
+ }
508
+ };
509
+ });
510
+
511
+ return JSON.stringify({
512
+ components,
513
+ resources,
514
+ enums
515
+ }, null, 2);
516
+ }
517
+
518
+ /**
519
+ * Updates the dubhe address in Move.toml file
520
+ * @param path - Directory path containing Move.toml file
521
+ * @param packageAddress - New dubhe package address to set
522
+ */
523
+ export function updateMoveTomlAddress(path: string, packageAddress: string) {
524
+ const moveTomlPath = `${path}/Move.toml`;
525
+ const moveTomlContent = fs.readFileSync(moveTomlPath, 'utf-8');
526
+ // Use regex to match any dubhe address, not just "0x0"
527
+ const updatedContent = moveTomlContent.replace(/dubhe\s*=\s*"[^"]*"/, `dubhe = "${packageAddress}"`);
528
+ fs.writeFileSync(moveTomlPath, updatedContent, 'utf-8');
529
+ }