movehat 0.0.1-alpha.0

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 (170) hide show
  1. package/README.md +236 -0
  2. package/bin/movehat.js +21 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +93 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/compile.d.ts +2 -0
  8. package/dist/commands/compile.d.ts.map +1 -0
  9. package/dist/commands/compile.js +71 -0
  10. package/dist/commands/compile.js.map +1 -0
  11. package/dist/commands/fork/create.d.ts +11 -0
  12. package/dist/commands/fork/create.d.ts.map +1 -0
  13. package/dist/commands/fork/create.js +56 -0
  14. package/dist/commands/fork/create.js.map +1 -0
  15. package/dist/commands/fork/fund.d.ts +12 -0
  16. package/dist/commands/fork/fund.d.ts.map +1 -0
  17. package/dist/commands/fork/fund.js +42 -0
  18. package/dist/commands/fork/fund.js.map +1 -0
  19. package/dist/commands/fork/list.d.ts +5 -0
  20. package/dist/commands/fork/list.d.ts.map +1 -0
  21. package/dist/commands/fork/list.js +61 -0
  22. package/dist/commands/fork/list.js.map +1 -0
  23. package/dist/commands/fork/serve.d.ts +10 -0
  24. package/dist/commands/fork/serve.d.ts.map +1 -0
  25. package/dist/commands/fork/serve.js +64 -0
  26. package/dist/commands/fork/serve.js.map +1 -0
  27. package/dist/commands/fork/view-resource.d.ts +11 -0
  28. package/dist/commands/fork/view-resource.d.ts.map +1 -0
  29. package/dist/commands/fork/view-resource.js +34 -0
  30. package/dist/commands/fork/view-resource.js.map +1 -0
  31. package/dist/commands/init.d.ts +2 -0
  32. package/dist/commands/init.d.ts.map +1 -0
  33. package/dist/commands/init.js +90 -0
  34. package/dist/commands/init.js.map +1 -0
  35. package/dist/commands/run.d.ts +2 -0
  36. package/dist/commands/run.d.ts.map +1 -0
  37. package/dist/commands/run.js +51 -0
  38. package/dist/commands/run.js.map +1 -0
  39. package/dist/commands/test.d.ts +2 -0
  40. package/dist/commands/test.d.ts.map +1 -0
  41. package/dist/commands/test.js +35 -0
  42. package/dist/commands/test.js.map +1 -0
  43. package/dist/core/config.d.ts +15 -0
  44. package/dist/core/config.d.ts.map +1 -0
  45. package/dist/core/config.js +121 -0
  46. package/dist/core/config.js.map +1 -0
  47. package/dist/core/contract.d.ts +20 -0
  48. package/dist/core/contract.d.ts.map +1 -0
  49. package/dist/core/contract.js +59 -0
  50. package/dist/core/contract.js.map +1 -0
  51. package/dist/core/deployments.d.ts +32 -0
  52. package/dist/core/deployments.d.ts.map +1 -0
  53. package/dist/core/deployments.js +122 -0
  54. package/dist/core/deployments.js.map +1 -0
  55. package/dist/core/shell.d.ts +25 -0
  56. package/dist/core/shell.d.ts.map +1 -0
  57. package/dist/core/shell.js +56 -0
  58. package/dist/core/shell.js.map +1 -0
  59. package/dist/errors.d.ts +12 -0
  60. package/dist/errors.d.ts.map +1 -0
  61. package/dist/errors.js +24 -0
  62. package/dist/errors.js.map +1 -0
  63. package/dist/fork/api.d.ts +33 -0
  64. package/dist/fork/api.d.ts.map +1 -0
  65. package/dist/fork/api.js +98 -0
  66. package/dist/fork/api.js.map +1 -0
  67. package/dist/fork/manager.d.ts +52 -0
  68. package/dist/fork/manager.d.ts.map +1 -0
  69. package/dist/fork/manager.js +221 -0
  70. package/dist/fork/manager.js.map +1 -0
  71. package/dist/fork/server.d.ts +55 -0
  72. package/dist/fork/server.d.ts.map +1 -0
  73. package/dist/fork/server.js +274 -0
  74. package/dist/fork/server.js.map +1 -0
  75. package/dist/fork/storage.d.ts +63 -0
  76. package/dist/fork/storage.d.ts.map +1 -0
  77. package/dist/fork/storage.js +183 -0
  78. package/dist/fork/storage.js.map +1 -0
  79. package/dist/fork/test.d.ts +75 -0
  80. package/dist/fork/test.d.ts.map +1 -0
  81. package/dist/fork/test.js +157 -0
  82. package/dist/fork/test.js.map +1 -0
  83. package/dist/helpers/assertions.d.ts +7 -0
  84. package/dist/helpers/assertions.d.ts.map +1 -0
  85. package/dist/helpers/assertions.js +17 -0
  86. package/dist/helpers/assertions.js.map +1 -0
  87. package/dist/helpers/banner.d.ts +3 -0
  88. package/dist/helpers/banner.d.ts.map +1 -0
  89. package/dist/helpers/banner.js +38 -0
  90. package/dist/helpers/banner.js.map +1 -0
  91. package/dist/helpers/index.d.ts +11 -0
  92. package/dist/helpers/index.d.ts.map +1 -0
  93. package/dist/helpers/index.js +7 -0
  94. package/dist/helpers/index.js.map +1 -0
  95. package/dist/helpers/setup.d.ts +10 -0
  96. package/dist/helpers/setup.d.ts.map +1 -0
  97. package/dist/helpers/setup.js +28 -0
  98. package/dist/helpers/setup.js.map +1 -0
  99. package/dist/index.d.ts +11 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +12 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/runtime.d.ts +26 -0
  104. package/dist/runtime.d.ts.map +1 -0
  105. package/dist/runtime.js +247 -0
  106. package/dist/runtime.js.map +1 -0
  107. package/dist/templates/.env.example +9 -0
  108. package/dist/templates/.mocharc.json +8 -0
  109. package/dist/templates/README.md +92 -0
  110. package/dist/templates/move/Counter.move +64 -0
  111. package/dist/templates/move/Move.toml +16 -0
  112. package/dist/templates/movehat.config.ts +37 -0
  113. package/dist/templates/package.json +24 -0
  114. package/dist/templates/scripts/deploy-counter.ts +48 -0
  115. package/dist/templates/tests/Counter.test.ts +75 -0
  116. package/dist/templates/tsconfig.json +15 -0
  117. package/dist/templates/types/movehat.d.ts +104 -0
  118. package/dist/types/config.d.ts +35 -0
  119. package/dist/types/config.d.ts.map +1 -0
  120. package/dist/types/config.js +2 -0
  121. package/dist/types/config.js.map +1 -0
  122. package/dist/types/fork.d.ts +37 -0
  123. package/dist/types/fork.d.ts.map +1 -0
  124. package/dist/types/fork.js +5 -0
  125. package/dist/types/fork.js.map +1 -0
  126. package/dist/types/runtime.d.ts +28 -0
  127. package/dist/types/runtime.d.ts.map +1 -0
  128. package/dist/types/runtime.js +2 -0
  129. package/dist/types/runtime.js.map +1 -0
  130. package/package.json +66 -0
  131. package/src/cli.ts +106 -0
  132. package/src/commands/compile.ts +84 -0
  133. package/src/commands/fork/create.ts +70 -0
  134. package/src/commands/fork/fund.ts +57 -0
  135. package/src/commands/fork/list.ts +67 -0
  136. package/src/commands/fork/serve.ts +77 -0
  137. package/src/commands/fork/view-resource.ts +46 -0
  138. package/src/commands/init.ts +150 -0
  139. package/src/commands/run.ts +59 -0
  140. package/src/commands/test.ts +42 -0
  141. package/src/core/config.ts +151 -0
  142. package/src/core/contract.ts +97 -0
  143. package/src/core/deployments.ts +164 -0
  144. package/src/core/shell.ts +66 -0
  145. package/src/errors.ts +21 -0
  146. package/src/fork/api.ts +117 -0
  147. package/src/fork/manager.ts +264 -0
  148. package/src/fork/server.ts +311 -0
  149. package/src/fork/storage.ts +224 -0
  150. package/src/fork/test.ts +195 -0
  151. package/src/helpers/assertions.ts +29 -0
  152. package/src/helpers/banner.ts +47 -0
  153. package/src/helpers/index.ts +26 -0
  154. package/src/helpers/setup.ts +49 -0
  155. package/src/index.ts +17 -0
  156. package/src/runtime.ts +322 -0
  157. package/src/templates/.env.example +9 -0
  158. package/src/templates/.mocharc.json +8 -0
  159. package/src/templates/README.md +92 -0
  160. package/src/templates/move/Counter.move +64 -0
  161. package/src/templates/move/Move.toml +16 -0
  162. package/src/templates/movehat.config.ts +37 -0
  163. package/src/templates/package.json +24 -0
  164. package/src/templates/scripts/deploy-counter.ts +48 -0
  165. package/src/templates/tests/Counter.test.ts +75 -0
  166. package/src/templates/tsconfig.json +15 -0
  167. package/src/templates/types/movehat.d.ts +104 -0
  168. package/src/types/config.ts +36 -0
  169. package/src/types/fork.ts +41 -0
  170. package/src/types/runtime.ts +49 -0
@@ -0,0 +1,67 @@
1
+ import { join } from 'path';
2
+ import { existsSync, readdirSync, statSync } from 'fs';
3
+ import { ForkStorage } from '../../fork/storage.js';
4
+
5
+ /**
6
+ * Fork list command: List all available forks
7
+ */
8
+ export default async function forkListCommand() {
9
+ try {
10
+ const forksDir = join(process.cwd(), '.movehat', 'forks');
11
+
12
+ if (!existsSync(forksDir)) {
13
+ console.log('\n📂 No forks found\n');
14
+ console.log('Create a fork with:');
15
+ console.log(' movehat fork create --network testnet\n');
16
+ return;
17
+ }
18
+
19
+ const entries = readdirSync(forksDir);
20
+ const forkDirs = entries.filter((entry) => {
21
+ const fullPath = join(forksDir, entry);
22
+ return statSync(fullPath).isDirectory();
23
+ });
24
+
25
+ if (forkDirs.length === 0) {
26
+ console.log('\n📂 No forks found\n');
27
+ return;
28
+ }
29
+
30
+ console.log(`\n📂 Found ${forkDirs.length} fork(s):\n`);
31
+
32
+ for (const forkDir of forkDirs) {
33
+ const forkPath = join(forksDir, forkDir);
34
+ const storage = new ForkStorage(forkPath);
35
+
36
+ try {
37
+ if (storage.exists()) {
38
+ const metadata = storage.loadMetadata();
39
+ const accounts = storage.listAccounts();
40
+
41
+ console.log(` ${forkDir}`);
42
+ console.log(` Path: ${forkPath}`);
43
+ console.log(` Network: ${metadata.network}`);
44
+ console.log(` Chain ID: ${metadata.chainId}`);
45
+ console.log(` Ledger Version: ${metadata.ledgerVersion}`);
46
+ console.log(` Cached Accounts: ${accounts.length}`);
47
+ console.log(` Created: ${new Date(metadata.createdAt).toLocaleString()}`);
48
+ console.log('');
49
+ } else {
50
+ console.log(` ${forkDir} (invalid - missing metadata)`);
51
+ console.log('');
52
+ }
53
+ } catch (error) {
54
+ console.log(` ${forkDir} (error reading metadata)`);
55
+ console.log('');
56
+ }
57
+ }
58
+
59
+ console.log('Usage:');
60
+ console.log(' movehat fork view-resource --fork <PATH> --account <ADDR> --resource <TYPE>');
61
+ console.log(' movehat fork fund --fork <PATH> --account <ADDR> --amount <AMOUNT>\n');
62
+
63
+ } catch (error: any) {
64
+ console.error(`\n❌ Error: ${error.message}\n`);
65
+ process.exit(1);
66
+ }
67
+ }
@@ -0,0 +1,77 @@
1
+ import { join } from 'path';
2
+ import { existsSync } from 'fs';
3
+ import { loadUserConfig } from '../../core/config.js';
4
+ import { ForkServer } from '../../fork/server.js';
5
+
6
+ interface ForkServeOptions {
7
+ fork?: string;
8
+ port?: number;
9
+ }
10
+
11
+ /**
12
+ * Fork serve command: Start a local RPC server serving the fork
13
+ */
14
+ export default async function forkServeCommand(options: ForkServeOptions): Promise<void> {
15
+ try {
16
+ // Determine fork path
17
+ let forkPath: string;
18
+
19
+ if (options.fork) {
20
+ // Use specified path
21
+ forkPath = options.fork;
22
+ } else {
23
+ // Use default fork path based on current network
24
+ const config = await loadUserConfig();
25
+ const networkName = process.env.MH_CLI_NETWORK || config.defaultNetwork || 'testnet';
26
+
27
+ // Lightweight validation: only check if network exists in config
28
+ // Don't validate accounts/keys since fork serve only reads data
29
+ if (!config.networks || !config.networks[networkName]) {
30
+ throw new Error(`Network "${networkName}" not found in config. Available networks: ${Object.keys(config.networks || {}).join(', ')}`);
31
+ }
32
+
33
+ forkPath = join(process.cwd(), '.movehat', 'forks', `${networkName}-fork`);
34
+ }
35
+
36
+ // Verify fork exists
37
+ if (!existsSync(join(forkPath, 'metadata.json'))) {
38
+ console.error(`\nError: Fork not found at ${forkPath}`);
39
+ console.error(`\nCreate a fork first with:`);
40
+ console.error(` movehat fork create --network <network> --name <name>`);
41
+ process.exit(1);
42
+ }
43
+
44
+ // Get port (already validated by Commander's parsePort in cli.ts)
45
+ const port = options.port ?? 8080;
46
+
47
+ // Create and start server
48
+ const server = new ForkServer(forkPath, port);
49
+
50
+ // Handle graceful shutdown (use 'once' to prevent duplicate shutdowns)
51
+ const shutdown = async () => {
52
+ console.log('\n\nShutting down...');
53
+ await server.stop();
54
+ process.exit(0);
55
+ };
56
+
57
+ // Use named handlers so we can remove them if needed
58
+ const sigintHandler = () => { void shutdown(); };
59
+ const sigtermHandler = () => { void shutdown(); };
60
+
61
+ process.once('SIGINT', sigintHandler);
62
+ process.once('SIGTERM', sigtermHandler);
63
+
64
+ try {
65
+ // Start server
66
+ await server.start();
67
+ } finally {
68
+ // Remove handlers in case server is stopped by other means
69
+ process.removeListener('SIGINT', sigintHandler);
70
+ process.removeListener('SIGTERM', sigtermHandler);
71
+ }
72
+
73
+ } catch (error: any) {
74
+ console.error(`\nError starting fork server:`, error.message);
75
+ process.exit(1);
76
+ }
77
+ }
@@ -0,0 +1,46 @@
1
+ import { join } from 'path';
2
+ import { ForkManager } from '../../fork/manager.js';
3
+
4
+ interface ForkViewResourceOptions {
5
+ fork?: string;
6
+ account: string;
7
+ resource: string;
8
+ }
9
+
10
+ /**
11
+ * Fork view-resource command: View a resource from the fork
12
+ */
13
+ export default async function forkViewResourceCommand(options: ForkViewResourceOptions) {
14
+ try {
15
+ if (!options.account) {
16
+ throw new Error('--account is required');
17
+ }
18
+
19
+ if (!options.resource) {
20
+ throw new Error('--resource is required');
21
+ }
22
+
23
+ // Determine fork path
24
+ const forkPath = options.fork || join(process.cwd(), '.movehat', 'forks', 'testnet-fork');
25
+
26
+ console.log(`\n🔍 Viewing resource from fork`);
27
+ console.log(` Fork: ${forkPath}`);
28
+ console.log(` Account: ${options.account}`);
29
+ console.log(` Resource: ${options.resource}\n`);
30
+
31
+ // Load fork
32
+ const forkManager = new ForkManager(forkPath);
33
+ forkManager.load();
34
+
35
+ // Get resource
36
+ const resource = await forkManager.getResource(options.account, options.resource);
37
+
38
+ // Display result
39
+ console.log(JSON.stringify(resource, null, 2));
40
+ console.log('');
41
+
42
+ } catch (error: any) {
43
+ console.error(`\n❌ Error: ${error.message}\n`);
44
+ process.exit(1);
45
+ }
46
+ }
@@ -0,0 +1,150 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import prompts from "prompts";
5
+ import { printMovehatBanner } from "../helpers/banner.js";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ export default async function initCommand(projectName?: string) {
11
+ // Show banner only on init command
12
+ printMovehatBanner();
13
+
14
+ // if name is not given
15
+ if (!projectName) {
16
+ const response = await prompts({
17
+ type: 'text',
18
+ name: 'projectName',
19
+ message: 'Project name:',
20
+ initial: 'first-project'
21
+ });
22
+
23
+ // If the user cancels (Ctrl+C), exit
24
+ if (!response.projectName) {
25
+ console.log('\n❌ Project initialization cancelled.');
26
+ process.exit(0);
27
+ }
28
+
29
+ projectName = response.projectName;
30
+ }
31
+
32
+ const targetDir = projectName!;
33
+ const projectPath = path.resolve(process.cwd(), targetDir);
34
+
35
+ console.log(`\nInitializing new Movehat project in ${projectPath}...`);
36
+
37
+ try {
38
+ await fs.mkdir(projectPath, { recursive: true });
39
+
40
+ const templatesDir = path.join(__dirname, "..", "templates");
41
+
42
+ console.log("📁 Creating project structure...");
43
+
44
+ await copyFile(
45
+ path.join(templatesDir, "package.json"),
46
+ path.join(projectPath, "package.json"),
47
+ { projectName: projectName! }
48
+ );
49
+
50
+ await copyFile(
51
+ path.join(templatesDir, "tsconfig.json"),
52
+ path.join(projectPath, "tsconfig.json")
53
+ );
54
+
55
+ await copyFile(
56
+ path.join(templatesDir, ".mocharc.json"),
57
+ path.join(projectPath, ".mocharc.json")
58
+ );
59
+
60
+ await copyFile(
61
+ path.join(templatesDir, "movehat.config.ts"),
62
+ path.join(projectPath, "movehat.config.ts")
63
+ );
64
+
65
+ await copyFile(
66
+ path.join(templatesDir, ".env.example"),
67
+ path.join(projectPath, ".env.example")
68
+ );
69
+
70
+ await copyFile(
71
+ path.join(templatesDir, ".gitignore"),
72
+ path.join(projectPath, ".gitignore")
73
+ );
74
+
75
+ await copyFile(
76
+ path.join(templatesDir, "README.md"),
77
+ path.join(projectPath, "README.md"),
78
+ { projectName: projectName! }
79
+ );
80
+
81
+ // 3. Copiar carpeta move/
82
+ console.log("📦 Setting up Move project...");
83
+ await copyDir(
84
+ path.join(templatesDir, "move"),
85
+ path.join(projectPath, "move")
86
+ );
87
+
88
+ // 4. Copiar scripts/
89
+ console.log("📜 Adding deployment scripts...");
90
+ await copyDir(
91
+ path.join(templatesDir, "scripts"),
92
+ path.join(projectPath, "scripts")
93
+ );
94
+
95
+ // 5. Copiar tests/
96
+ console.log("🧪 Adding test files...");
97
+ await copyDir(
98
+ path.join(templatesDir, "tests"),
99
+ path.join(projectPath, "tests")
100
+ );
101
+
102
+ console.log("\n✅ Project created successfully!\n");
103
+ console.log("📝 Next steps:\n");
104
+ console.log(` cd ${projectName}`);
105
+ console.log(` cp .env.example .env`);
106
+ console.log(` # Edit .env with your credentials`);
107
+ console.log(` npm install`);
108
+ console.log(` npx movehat compile`);
109
+ console.log(` npm test\n`);
110
+ } catch (error) {
111
+ console.error(`Failed to initialize project: ${error}`);
112
+ }
113
+ }
114
+
115
+ async function copyFile(
116
+ src: string,
117
+ dest: string,
118
+ replacements?: Record<string, string>
119
+ ) {
120
+ let content = await fs.readFile(src, "utf-8");
121
+
122
+ if (replacements) {
123
+ for (const [key, value] of Object.entries(replacements)) {
124
+ const regex = new RegExp(`{{${key}}}`, "g");
125
+ content = content.replace(regex, value);
126
+ }
127
+ }
128
+ await fs.writeFile(dest, content);
129
+ }
130
+
131
+ async function copyDir(src: string, dest: string) {
132
+ await fs.mkdir(dest, { recursive: true });
133
+ const entries = await fs.readdir(src, { withFileTypes: true });
134
+
135
+ for (const entry of entries) {
136
+ // Skip template development files
137
+ if (entry.name === 'types' || entry.name === '.vscode') {
138
+ continue;
139
+ }
140
+
141
+ const srcPath = path.join(src, entry.name);
142
+ const destPath = path.join(dest, entry.name);
143
+
144
+ if (entry.isDirectory()) {
145
+ await copyDir(srcPath, destPath);
146
+ } else {
147
+ await fs.copyFile(srcPath, destPath);
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,59 @@
1
+ import { spawn } from "child_process";
2
+ import { resolve, extname, dirname, join } from "path";
3
+ import { existsSync } from "fs";
4
+ import { fileURLToPath } from "url";
5
+
6
+ export default async function runCommand(scriptPath: string) {
7
+ if (!scriptPath) {
8
+ console.error("❌ Error: No script path provided");
9
+ console.error("Usage: movehat run <script-path> [--network <name>]");
10
+ console.error("Example: movehat run scripts/deploy-counter.ts --network testnet");
11
+ process.exit(1);
12
+ }
13
+
14
+ const fullPath = resolve(process.cwd(), scriptPath);
15
+
16
+ // Check if file exists
17
+ if (!existsSync(fullPath)) {
18
+ console.error(`❌ Script not found: ${scriptPath}`);
19
+ process.exit(1);
20
+ }
21
+
22
+ // Check if it's a TypeScript or JavaScript file
23
+ const ext = extname(fullPath);
24
+ if (![".ts", ".js", ".mjs"].includes(ext)) {
25
+ console.error(`❌ Unsupported file type: ${ext}`);
26
+ console.error("Supported extensions: .ts, .js, .mjs");
27
+ process.exit(1);
28
+ }
29
+
30
+ const network = process.env.MH_CLI_NETWORK;
31
+ console.log(`🚀 Running script: ${scriptPath}`);
32
+ if (network) {
33
+ console.log(` Network: ${network}`);
34
+ }
35
+ console.log();
36
+
37
+ // Find tsx from movehat's node_modules
38
+ const __filename = fileURLToPath(import.meta.url);
39
+ const __dirname = dirname(__filename);
40
+ const tsxPath = join(__dirname, "..", "..", "node_modules", ".bin", "tsx");
41
+
42
+ // Execute script with tsx (handles both .ts and .js files)
43
+ const child = spawn(tsxPath, [fullPath], {
44
+ stdio: "inherit",
45
+ env: {
46
+ ...process.env,
47
+ // MH_CLI_NETWORK is already set by the CLI hook
48
+ },
49
+ });
50
+
51
+ child.on("exit", (code) => {
52
+ process.exit(code || 0);
53
+ });
54
+
55
+ child.on("error", (error) => {
56
+ console.error(`❌ Failed to execute script: ${error.message}`);
57
+ process.exit(1);
58
+ });
59
+ }
@@ -0,0 +1,42 @@
1
+ import { spawn } from "child_process";
2
+ import { join } from "path";
3
+ import { existsSync } from "fs";
4
+
5
+ export default async function testCommand() {
6
+ const testDir = join(process.cwd(), "tests");
7
+
8
+ if (!existsSync(testDir)) {
9
+ console.error("❌ No tests directory found.");
10
+ console.error(" Create a 'tests' directory with your TypeScript test files.");
11
+ process.exit(1);
12
+ }
13
+
14
+ console.log("🧪 Running TypeScript tests with Mocha...\n");
15
+
16
+ // Find mocha from project's node_modules
17
+ const mochaPath = join(process.cwd(), "node_modules", ".bin", "mocha");
18
+
19
+ if (!existsSync(mochaPath)) {
20
+ console.error("❌ Mocha not found in project dependencies.");
21
+ console.error(" Install it with: npm install --save-dev mocha");
22
+ process.exit(1);
23
+ }
24
+
25
+ // Run mocha with TypeScript support
26
+ const child = spawn(mochaPath, [], {
27
+ stdio: "inherit",
28
+ env: {
29
+ ...process.env,
30
+ // Inherit network if set
31
+ },
32
+ });
33
+
34
+ child.on("exit", (code) => {
35
+ process.exit(code || 0);
36
+ });
37
+
38
+ child.on("error", (error) => {
39
+ console.error(`❌ Failed to run tests: ${error.message}`);
40
+ process.exit(1);
41
+ });
42
+ }
@@ -0,0 +1,151 @@
1
+ import { pathToFileURL } from "url";
2
+ import { join } from "path";
3
+ import { existsSync } from "fs";
4
+ import { MovehatConfig, MovehatUserConfig } from "../types/config.js";
5
+
6
+ /**
7
+ * Loads the user's movehat.config.js from the current working directory.
8
+ *
9
+ * @throws {Error} If the configuration file is not found or fails to load
10
+ * @security This function loads and executes code from the current working directory.
11
+ * It should only be called from trusted project directories.
12
+ */
13
+ export async function loadUserConfig(): Promise<MovehatUserConfig> {
14
+ const cwd = process.cwd();
15
+
16
+ // Try to find config file (.ts first, then .js)
17
+ const possiblePaths = [
18
+ join(cwd, "movehat.config.ts"),
19
+ join(cwd, "movehat.config.js"),
20
+ ];
21
+
22
+ let configPath: string | null = null;
23
+ for (const path of possiblePaths) {
24
+ if (existsSync(path)) {
25
+ configPath = path;
26
+ break;
27
+ }
28
+ }
29
+
30
+ if (!configPath) {
31
+ throw new Error(
32
+ "Configuration file not found. Expected 'movehat.config.ts' or 'movehat.config.js' in the current directory."
33
+ );
34
+ }
35
+
36
+ try {
37
+ let configModule;
38
+
39
+ if (configPath.endsWith('.ts')) {
40
+ // For TypeScript files, we need to use tsx's import system
41
+ // Register tsx loader for .ts files
42
+ const { register } = await import('tsx/esm/api');
43
+ const unregister = register();
44
+
45
+ try {
46
+ const configUrl = pathToFileURL(configPath).href;
47
+ configModule = await import(configUrl + '?t=' + Date.now());
48
+ } finally {
49
+ unregister();
50
+ }
51
+ } else {
52
+ // For .js files, use standard import
53
+ const configUrl = pathToFileURL(configPath).href;
54
+ configModule = await import(configUrl + '?t=' + Date.now());
55
+ }
56
+
57
+ const userConfig = configModule.default as MovehatUserConfig;
58
+
59
+ // Validate that networks are defined
60
+ if (!userConfig.networks || Object.keys(userConfig.networks).length === 0) {
61
+ throw new Error(
62
+ "No networks defined in configuration. Add at least one network in the 'networks' field."
63
+ );
64
+ }
65
+
66
+ return userConfig;
67
+ } catch (error) {
68
+ throw new Error(`Failed to load configuration file '${configPath}': ${error}`);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Resolve configuration for a specific network
74
+ * Merges global settings with network-specific settings
75
+ */
76
+ export async function resolveNetworkConfig(
77
+ userConfig: MovehatUserConfig,
78
+ networkName?: string
79
+ ): Promise<MovehatConfig> {
80
+ // Determine which network to use
81
+ const selectedNetwork =
82
+ networkName ||
83
+ process.env.MH_CLI_NETWORK ||
84
+ process.env.MH_DEFAULT_NETWORK ||
85
+ userConfig.defaultNetwork ||
86
+ "testnet";
87
+
88
+ // Check if network exists in config
89
+ const networkConfig = userConfig.networks[selectedNetwork];
90
+ if (!networkConfig) {
91
+ const availableNetworks = Object.keys(userConfig.networks).join(", ");
92
+ throw new Error(
93
+ `Network '${selectedNetwork}' not found in configuration.\nAvailable networks: ${availableNetworks}`
94
+ );
95
+ }
96
+
97
+ // Get accounts using Hardhat-style resolution:
98
+ // 1. Network-specific accounts (if defined)
99
+ // 2. Global accounts from config (if defined)
100
+ // 3. PRIVATE_KEY environment variable (Hardhat-style, no MH_ prefix)
101
+ // 4. Error if nothing found
102
+
103
+ let accounts: string[] = [];
104
+
105
+ // 1. Check network-specific accounts
106
+ if (networkConfig.accounts && networkConfig.accounts.length > 0) {
107
+ accounts = [...networkConfig.accounts].filter(Boolean);
108
+ }
109
+
110
+ // 2. If no network-specific accounts, use global accounts
111
+ if (accounts.length === 0 && userConfig.accounts && userConfig.accounts.length > 0) {
112
+ accounts = [...userConfig.accounts].filter(Boolean);
113
+ }
114
+
115
+ // 3. If still no accounts, check PRIVATE_KEY env var (Hardhat-style)
116
+ if (accounts.length === 0 && process.env.PRIVATE_KEY) {
117
+ accounts = [process.env.PRIVATE_KEY];
118
+ }
119
+
120
+ // 4. Validate we have at least one account
121
+ if (accounts.length === 0 || !accounts[0]) {
122
+ throw new Error(
123
+ `Network '${selectedNetwork}' has no accounts configured.\n` +
124
+ `Options:\n` +
125
+ ` 1. Set PRIVATE_KEY in your .env file (recommended)\n` +
126
+ ` 2. Add 'accounts: ["0x..."]' globally in movehat.config.ts\n` +
127
+ ` 3. Add 'accounts: ["0x..."]' to the '${selectedNetwork}' network config`
128
+ );
129
+ }
130
+
131
+ // Merge named addresses (network-specific overrides global)
132
+ const mergedNamedAddresses = {
133
+ ...(userConfig.namedAddresses || {}),
134
+ ...(networkConfig.namedAddresses || {}),
135
+ };
136
+
137
+ // Build resolved config
138
+ const resolvedConfig: MovehatConfig = {
139
+ network: selectedNetwork,
140
+ rpc: networkConfig.url,
141
+ privateKey: accounts[0],
142
+ allAccounts: accounts,
143
+ profile: networkConfig.profile || "default",
144
+ moveDir: userConfig.moveDir || "./move",
145
+ account: "", // Will be derived from privateKey in runtime
146
+ namedAddresses: mergedNamedAddresses,
147
+ networkConfig: networkConfig,
148
+ };
149
+
150
+ return resolvedConfig;
151
+ }
@@ -0,0 +1,97 @@
1
+ import {
2
+ Account,
3
+ Aptos,
4
+ type InputViewFunctionData,
5
+ type MoveFunctionId,
6
+ } from "@aptos-labs/ts-sdk";
7
+
8
+ export interface TransactionResult {
9
+ hash: string;
10
+ success: boolean;
11
+ vm_status: string;
12
+ }
13
+
14
+ export class MoveContract {
15
+ constructor(
16
+ private aptos: Aptos,
17
+ private moduleAddress: string,
18
+ private moduleName: string
19
+ ) {}
20
+
21
+ async call(
22
+ signer: Account,
23
+ functionName: string,
24
+ args: any[] = [],
25
+ typeArgs: string[] = []
26
+ ): Promise<TransactionResult> {
27
+ const functionFullName = `${this.moduleAddress}::${this.moduleName}::${functionName}`;
28
+
29
+ console.log(`📝 Calling ${functionFullName}...`);
30
+
31
+ const transaction = await this.aptos.transaction.build.simple({
32
+ sender: signer.accountAddress,
33
+ data: {
34
+ function: functionFullName as MoveFunctionId,
35
+ typeArguments: typeArgs,
36
+ functionArguments: args,
37
+ },
38
+ });
39
+
40
+ const signature = this.aptos.transaction.sign({
41
+ signer,
42
+ transaction,
43
+ });
44
+
45
+ const committedTxn = await this.aptos.transaction.submit.simple({
46
+ transaction,
47
+ senderAuthenticator: signature,
48
+ });
49
+
50
+ const response = await this.aptos.waitForTransaction({
51
+ transactionHash: committedTxn.hash,
52
+ });
53
+
54
+ console.log(
55
+ `✅ Transaction ${committedTxn.hash} committed with status: ${response.vm_status}\n`
56
+ );
57
+
58
+ return {
59
+ hash: committedTxn.hash,
60
+ success: response.success,
61
+ vm_status: response.vm_status,
62
+ };
63
+ }
64
+
65
+ async view<T = any>(
66
+ functionName: string,
67
+ args: any[] = [],
68
+ typeArgs: string[] = []
69
+ ): Promise<T> {
70
+ const functionFullName = `${this.moduleAddress}::${this.moduleName}::${functionName}`;
71
+
72
+ const payload: InputViewFunctionData = {
73
+ function: functionFullName as MoveFunctionId,
74
+ typeArguments: typeArgs,
75
+ functionArguments: args,
76
+ };
77
+
78
+ const result = await this.aptos.view({ payload });
79
+
80
+ return (result.length === 1 ? result[0] : result) as T;
81
+ }
82
+
83
+ getModuleId(): string {
84
+ return `${this.moduleAddress}::${this.moduleName}`;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Factory function to create a contract instance
90
+ */
91
+ export function getContract(
92
+ aptos: Aptos,
93
+ moduleAddress: string,
94
+ moduleName: string
95
+ ): MoveContract {
96
+ return new MoveContract(aptos, moduleAddress, moduleName);
97
+ }