movehat 0.2.7 → 0.2.8
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/dist/cli.js +1 -1
- package/dist/commands/fork/fund.js +3 -1
- package/dist/commands/fork/serve.js +10 -5
- package/dist/commands/test-move.js +6 -3
- package/dist/fork/api.d.ts +1 -1
- package/dist/fork/api.js +9 -4
- package/dist/fork/manager.d.ts +2 -2
- package/dist/fork/manager.js +4 -8
- package/dist/fork/storage.js +5 -4
- package/dist/fork/test.d.ts +1 -1
- package/dist/fork/validation.d.ts +9 -0
- package/dist/fork/validation.js +88 -0
- package/dist/harness/Harness.d.ts +3 -2
- package/dist/harness/Harness.js +4 -1
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/move-tests.js +8 -5
- package/dist/helpers/setupLocalTesting.d.ts +2 -2
- package/dist/helpers/setupLocalTesting.js +43 -20
- package/dist/helpers/version-check.js +4 -2
- package/dist/node/MoveliteManager.d.ts +18 -0
- package/dist/node/MoveliteManager.js +152 -0
- package/dist/node/NodeProvider.d.ts +9 -0
- package/dist/node/NodeProvider.js +1 -0
- package/dist/templates/.mocharc.json +1 -0
- package/dist/templates/tests/Counter.test.ts +6 -9
- package/dist/templates/tests/setup.ts +34 -0
- package/dist/types/config.d.ts +2 -0
- package/dist/types/fork.d.ts +24 -0
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -66,7 +66,7 @@ program
|
|
|
66
66
|
});
|
|
67
67
|
program
|
|
68
68
|
.command('compile')
|
|
69
|
-
.description('Compile Move smart contracts
|
|
69
|
+
.description('Compile Move smart contracts (auto-detects named addresses and updates Move.toml)')
|
|
70
70
|
.action(compileCommand);
|
|
71
71
|
program
|
|
72
72
|
.command('run <script>')
|
|
@@ -34,9 +34,11 @@ export default async function forkFundCommand(options) {
|
|
|
34
34
|
// Verify
|
|
35
35
|
const resourceType = `0x1::coin::CoinStore<${coinType}>`;
|
|
36
36
|
const coinStore = await forkManager.getResource(options.account, resourceType);
|
|
37
|
+
const { assertCoinStore } = await import('../../fork/validation.js');
|
|
38
|
+
const validated = assertCoinStore(coinStore);
|
|
37
39
|
logger.newline();
|
|
38
40
|
logger.success("Account funded successfully!");
|
|
39
|
-
logger.plain(` New balance: ${
|
|
41
|
+
logger.plain(` New balance: ${validated.coin.value}`);
|
|
40
42
|
logger.newline();
|
|
41
43
|
}
|
|
42
44
|
catch (error) {
|
|
@@ -2,6 +2,7 @@ import { join } from 'path';
|
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import { loadUserConfig } from '../../core/config.js';
|
|
4
4
|
import { ForkServer } from '../../fork/server.js';
|
|
5
|
+
import { logger } from '../../ui/index.js';
|
|
5
6
|
/**
|
|
6
7
|
* Fork serve command: Start a local RPC server serving the fork
|
|
7
8
|
*/
|
|
@@ -26,9 +27,11 @@ export default async function forkServeCommand(options) {
|
|
|
26
27
|
}
|
|
27
28
|
// Verify fork exists
|
|
28
29
|
if (!existsSync(join(forkPath, 'metadata.json'))) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
logger.newline();
|
|
31
|
+
logger.error(`Fork not found at ${forkPath}`);
|
|
32
|
+
logger.newline();
|
|
33
|
+
logger.error("Create a fork first with:");
|
|
34
|
+
logger.error(" movehat fork create --network <network> --name <name>");
|
|
32
35
|
process.exit(1);
|
|
33
36
|
}
|
|
34
37
|
// Get port (already validated by Commander's parsePort in cli.ts)
|
|
@@ -38,7 +41,8 @@ export default async function forkServeCommand(options) {
|
|
|
38
41
|
const server = new ForkServer(forkPath, port, host);
|
|
39
42
|
// Handle graceful shutdown (use 'once' to prevent duplicate shutdowns)
|
|
40
43
|
const shutdown = async () => {
|
|
41
|
-
|
|
44
|
+
logger.newline();
|
|
45
|
+
logger.step("Shutting down...");
|
|
42
46
|
await server.stop();
|
|
43
47
|
process.exit(0);
|
|
44
48
|
};
|
|
@@ -59,7 +63,8 @@ export default async function forkServeCommand(options) {
|
|
|
59
63
|
}
|
|
60
64
|
catch (error) {
|
|
61
65
|
const msg = error instanceof Error ? error.message : String(error);
|
|
62
|
-
|
|
66
|
+
logger.newline();
|
|
67
|
+
logger.error(`Error starting fork server: ${msg}`);
|
|
63
68
|
process.exit(1);
|
|
64
69
|
}
|
|
65
70
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { runMoveTests } from "../helpers/move-tests.js";
|
|
2
|
+
import { logger } from "../ui/index.js";
|
|
2
3
|
export default async function testMoveCommand(options = {}) {
|
|
3
4
|
try {
|
|
4
|
-
|
|
5
|
+
logger.step("Running Move unit tests...");
|
|
6
|
+
logger.newline();
|
|
5
7
|
await runMoveTests({
|
|
6
8
|
filter: options.filter,
|
|
7
9
|
ignoreWarnings: options.ignoreWarnings,
|
|
@@ -10,10 +12,11 @@ export default async function testMoveCommand(options = {}) {
|
|
|
10
12
|
process.exit(0);
|
|
11
13
|
}
|
|
12
14
|
catch (err) {
|
|
13
|
-
|
|
15
|
+
logger.newline();
|
|
16
|
+
logger.error("Move tests failed");
|
|
14
17
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15
18
|
if (msg) {
|
|
16
|
-
|
|
19
|
+
logger.error(` ${msg}`);
|
|
17
20
|
}
|
|
18
21
|
process.exit(1);
|
|
19
22
|
}
|
package/dist/fork/api.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export declare class MovementApiClient {
|
|
|
55
55
|
/**
|
|
56
56
|
* Get a specific account resource
|
|
57
57
|
*/
|
|
58
|
-
getAccountResource(address: string, resourceType: string): Promise<
|
|
58
|
+
getAccountResource(address: string, resourceType: string): Promise<AccountResource>;
|
|
59
59
|
/**
|
|
60
60
|
* Get all resources for an account
|
|
61
61
|
*/
|
package/dist/fork/api.js
CHANGED
|
@@ -2,6 +2,7 @@ import https from 'https';
|
|
|
2
2
|
import http from 'http';
|
|
3
3
|
import { URL } from 'url';
|
|
4
4
|
import { normalizeAddressShort } from '../utils/address.js';
|
|
5
|
+
import { assertLedgerInfo, assertAccountData, assertAccountResource, assertAccountResourceArray, } from './validation.js';
|
|
5
6
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
6
7
|
const DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
|
|
7
8
|
/**
|
|
@@ -191,14 +192,16 @@ export class MovementApiClient {
|
|
|
191
192
|
* Get ledger information
|
|
192
193
|
*/
|
|
193
194
|
async getLedgerInfo() {
|
|
194
|
-
|
|
195
|
+
const raw = await this.get(this.apiPath('/'));
|
|
196
|
+
return assertLedgerInfo(raw);
|
|
195
197
|
}
|
|
196
198
|
/**
|
|
197
199
|
* Get account information
|
|
198
200
|
*/
|
|
199
201
|
async getAccount(address) {
|
|
200
202
|
const normalizedAddress = normalizeAddressShort(address);
|
|
201
|
-
|
|
203
|
+
const raw = await this.get(this.apiPath(`/accounts/${normalizedAddress}`));
|
|
204
|
+
return assertAccountData(raw);
|
|
202
205
|
}
|
|
203
206
|
/**
|
|
204
207
|
* Get a specific account resource
|
|
@@ -207,14 +210,16 @@ export class MovementApiClient {
|
|
|
207
210
|
const normalizedAddress = normalizeAddressShort(address);
|
|
208
211
|
// URL encode the resource type
|
|
209
212
|
const encodedType = encodeURIComponent(resourceType);
|
|
210
|
-
|
|
213
|
+
const raw = await this.get(this.apiPath(`/accounts/${normalizedAddress}/resource/${encodedType}`));
|
|
214
|
+
return assertAccountResource(raw);
|
|
211
215
|
}
|
|
212
216
|
/**
|
|
213
217
|
* Get all resources for an account
|
|
214
218
|
*/
|
|
215
219
|
async getAccountResources(address) {
|
|
216
220
|
const normalizedAddress = normalizeAddressShort(address);
|
|
217
|
-
|
|
221
|
+
const raw = await this.get(this.apiPath(`/accounts/${normalizedAddress}/resources`));
|
|
222
|
+
return assertAccountResourceArray(raw);
|
|
218
223
|
}
|
|
219
224
|
/**
|
|
220
225
|
* Execute a Move view function via the upstream node's POST /v1/view.
|
package/dist/fork/manager.d.ts
CHANGED
|
@@ -36,8 +36,8 @@ export declare class ForkManager {
|
|
|
36
36
|
load(): void;
|
|
37
37
|
getMetadata(): ForkMetadata;
|
|
38
38
|
getAccount(address: string): Promise<AccountState>;
|
|
39
|
-
getResource(address: string, resourceType: string): Promise<
|
|
40
|
-
getAllResources(address: string): Promise<Record<string,
|
|
39
|
+
getResource(address: string, resourceType: string): Promise<unknown>;
|
|
40
|
+
getAllResources(address: string): Promise<Record<string, unknown>>;
|
|
41
41
|
/**
|
|
42
42
|
* Stateless passthrough of `POST /v1/view` to the upstream RPC.
|
|
43
43
|
*
|
package/dist/fork/manager.js
CHANGED
|
@@ -3,6 +3,7 @@ import { MovementApiClient } from './api.js';
|
|
|
3
3
|
import { ForkStorage } from './storage.js';
|
|
4
4
|
import { normalizeAddress } from '../utils/address.js';
|
|
5
5
|
import { logger } from '../ui/index.js';
|
|
6
|
+
import { assertCoinStore } from './validation.js';
|
|
6
7
|
/**
|
|
7
8
|
* Derive a deterministic 32-byte hex placeholder for the `authentication_key`
|
|
8
9
|
* of a fork-funded account. The real auth_key is `sha3_256(public_key || 0x00)`
|
|
@@ -182,17 +183,12 @@ export class ForkManager {
|
|
|
182
183
|
async fundAccount(address, amount, coinType = '0x1::aptos_coin::AptosCoin') {
|
|
183
184
|
const normalizedAddress = normalizeAddress(address);
|
|
184
185
|
const resourceType = `0x1::coin::CoinStore<${coinType}>`;
|
|
185
|
-
// Try to get existing coin store. The coin store is a CoinStore<T>
|
|
186
|
-
// resource whose `data` is Movement-side untyped JSON; we shape it
|
|
187
|
-
// locally as a structural object with `coin.value: string`.
|
|
188
|
-
// any: full CoinStore schema lives at the Movement REST boundary —
|
|
189
|
-
// proper validation deferred to the boundary-validation follow-up of #57.
|
|
190
186
|
let coinStore;
|
|
191
187
|
try {
|
|
192
|
-
|
|
188
|
+
const raw = await this.getResource(normalizedAddress, resourceType);
|
|
189
|
+
coinStore = assertCoinStore(raw);
|
|
193
190
|
}
|
|
194
191
|
catch (error) {
|
|
195
|
-
// Only catch "not found" errors, rethrow others (network, API, etc.)
|
|
196
192
|
const msg = error instanceof Error ? error.message : String(error);
|
|
197
193
|
if (!msg.includes('not found')) {
|
|
198
194
|
throw error;
|
|
@@ -220,7 +216,7 @@ export class ForkManager {
|
|
|
220
216
|
frozen: false,
|
|
221
217
|
};
|
|
222
218
|
}
|
|
223
|
-
const currentBalance = BigInt(coinStore.coin.value
|
|
219
|
+
const currentBalance = BigInt(coinStore.coin.value);
|
|
224
220
|
const newBalance = currentBalance + BigInt(amount);
|
|
225
221
|
coinStore.coin.value = newBalance.toString();
|
|
226
222
|
await this.setResource(normalizedAddress, resourceType, coinStore);
|
package/dist/fork/storage.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { isHexAddress } from '../utils/address.js';
|
|
4
|
+
import { assertForkMetadata, assertAccountStateRecord } from './validation.js';
|
|
4
5
|
/**
|
|
5
6
|
* Sanitize address to create a safe filename. Validates the address through
|
|
6
7
|
* the shared `isHexAddress` helper (length 1–64 hex chars, optional `0x`),
|
|
@@ -108,7 +109,7 @@ export class ForkStorage {
|
|
|
108
109
|
if (!existsSync(metadataPath)) {
|
|
109
110
|
throw new Error(`Fork metadata not found at ${metadataPath}`);
|
|
110
111
|
}
|
|
111
|
-
return readJsonFile(metadataPath, 'fork metadata');
|
|
112
|
+
return assertForkMetadata(readJsonFile(metadataPath, 'fork metadata'));
|
|
112
113
|
}
|
|
113
114
|
/**
|
|
114
115
|
* Get account state
|
|
@@ -118,7 +119,7 @@ export class ForkStorage {
|
|
|
118
119
|
if (!existsSync(accountsPath)) {
|
|
119
120
|
return null;
|
|
120
121
|
}
|
|
121
|
-
const accounts = readJsonFile(accountsPath, 'fork accounts');
|
|
122
|
+
const accounts = assertAccountStateRecord(readJsonFile(accountsPath, 'fork accounts'));
|
|
122
123
|
return accounts[address] || null;
|
|
123
124
|
}
|
|
124
125
|
/**
|
|
@@ -128,7 +129,7 @@ export class ForkStorage {
|
|
|
128
129
|
const accountsPath = join(this.forkPath, 'accounts.json');
|
|
129
130
|
let accounts = {};
|
|
130
131
|
if (existsSync(accountsPath)) {
|
|
131
|
-
accounts = readJsonFile(accountsPath, 'fork accounts');
|
|
132
|
+
accounts = assertAccountStateRecord(readJsonFile(accountsPath, 'fork accounts'));
|
|
132
133
|
}
|
|
133
134
|
accounts[address] = state;
|
|
134
135
|
writePrivateFile(accountsPath, JSON.stringify(accounts, null, 2));
|
|
@@ -193,7 +194,7 @@ export class ForkStorage {
|
|
|
193
194
|
if (!existsSync(accountsPath)) {
|
|
194
195
|
return [];
|
|
195
196
|
}
|
|
196
|
-
const accounts = readJsonFile(accountsPath, 'fork accounts');
|
|
197
|
+
const accounts = assertAccountStateRecord(readJsonFile(accountsPath, 'fork accounts'));
|
|
197
198
|
return Object.keys(accounts);
|
|
198
199
|
}
|
|
199
200
|
/**
|
package/dist/fork/test.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ export declare function getForkInfo(path: string): Promise<ForkInfo>;
|
|
|
59
59
|
*/
|
|
60
60
|
export declare function viewForkResource(sessionPath: string, account: string, resourceType: string, options?: {
|
|
61
61
|
adapter?: ChildProcessAdapter;
|
|
62
|
-
}): Promise<
|
|
62
|
+
}): Promise<unknown>;
|
|
63
63
|
/**
|
|
64
64
|
* Compare a resource between current network state and a fork
|
|
65
65
|
* Useful for verifying state changes after tests
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LedgerInfo, AccountData, AccountResource, ForkMetadata, AccountState, CoinStore } from "../types/fork.js";
|
|
2
|
+
export declare function assertLedgerInfo(v: unknown): LedgerInfo;
|
|
3
|
+
export declare function assertAccountData(v: unknown): AccountData;
|
|
4
|
+
export declare function assertAccountResource(v: unknown): AccountResource;
|
|
5
|
+
export declare function assertAccountResourceArray(v: unknown): AccountResource[];
|
|
6
|
+
export declare function assertForkMetadata(v: unknown): ForkMetadata;
|
|
7
|
+
export declare function assertAccountState(v: unknown): AccountState;
|
|
8
|
+
export declare function assertAccountStateRecord(v: unknown): Record<string, AccountState>;
|
|
9
|
+
export declare function assertCoinStore(v: unknown): CoinStore;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
function isObject(v) {
|
|
2
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
3
|
+
}
|
|
4
|
+
function hasString(o, key) {
|
|
5
|
+
return typeof o[key] === "string";
|
|
6
|
+
}
|
|
7
|
+
export function assertLedgerInfo(v) {
|
|
8
|
+
if (!isObject(v) ||
|
|
9
|
+
typeof v.chain_id !== "number" ||
|
|
10
|
+
!hasString(v, "epoch") ||
|
|
11
|
+
!hasString(v, "ledger_version") ||
|
|
12
|
+
!hasString(v, "oldest_ledger_version") ||
|
|
13
|
+
!hasString(v, "ledger_timestamp") ||
|
|
14
|
+
!hasString(v, "node_role") ||
|
|
15
|
+
!hasString(v, "oldest_block_height") ||
|
|
16
|
+
!hasString(v, "block_height")) {
|
|
17
|
+
throw new Error("Invalid LedgerInfo: missing or incorrectly typed fields");
|
|
18
|
+
}
|
|
19
|
+
return v;
|
|
20
|
+
}
|
|
21
|
+
export function assertAccountData(v) {
|
|
22
|
+
if (!isObject(v) ||
|
|
23
|
+
!hasString(v, "sequence_number") ||
|
|
24
|
+
!hasString(v, "authentication_key")) {
|
|
25
|
+
throw new Error("Invalid AccountData: expected object with 'sequence_number' and 'authentication_key' strings");
|
|
26
|
+
}
|
|
27
|
+
return v;
|
|
28
|
+
}
|
|
29
|
+
export function assertAccountResource(v) {
|
|
30
|
+
if (!isObject(v) || !hasString(v, "type") || !("data" in v)) {
|
|
31
|
+
throw new Error("Invalid AccountResource: expected object with 'type' string and 'data' field");
|
|
32
|
+
}
|
|
33
|
+
return v;
|
|
34
|
+
}
|
|
35
|
+
export function assertAccountResourceArray(v) {
|
|
36
|
+
if (!Array.isArray(v)) {
|
|
37
|
+
throw new Error("Invalid resources response: expected array");
|
|
38
|
+
}
|
|
39
|
+
for (let i = 0; i < v.length; i++) {
|
|
40
|
+
if (!isObject(v[i]) || !hasString(v[i], "type") || !("data" in v[i])) {
|
|
41
|
+
throw new Error(`Invalid AccountResource at index ${i}: expected object with 'type' and 'data'`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return v;
|
|
45
|
+
}
|
|
46
|
+
export function assertForkMetadata(v) {
|
|
47
|
+
if (!isObject(v) ||
|
|
48
|
+
!hasString(v, "network") ||
|
|
49
|
+
!hasString(v, "nodeUrl") ||
|
|
50
|
+
typeof v.chainId !== "number" ||
|
|
51
|
+
!hasString(v, "ledgerVersion") ||
|
|
52
|
+
!hasString(v, "timestamp") ||
|
|
53
|
+
!hasString(v, "epoch") ||
|
|
54
|
+
!hasString(v, "blockHeight") ||
|
|
55
|
+
!hasString(v, "createdAt")) {
|
|
56
|
+
throw new Error("Invalid ForkMetadata: missing or incorrectly typed fields");
|
|
57
|
+
}
|
|
58
|
+
return v;
|
|
59
|
+
}
|
|
60
|
+
export function assertAccountState(v) {
|
|
61
|
+
if (!isObject(v) ||
|
|
62
|
+
!hasString(v, "sequenceNumber") ||
|
|
63
|
+
!hasString(v, "authenticationKey")) {
|
|
64
|
+
throw new Error("Invalid AccountState: expected object with 'sequenceNumber' and 'authenticationKey' strings");
|
|
65
|
+
}
|
|
66
|
+
return v;
|
|
67
|
+
}
|
|
68
|
+
export function assertAccountStateRecord(v) {
|
|
69
|
+
if (!isObject(v)) {
|
|
70
|
+
throw new Error("Invalid account state record: expected object");
|
|
71
|
+
}
|
|
72
|
+
for (const [key, val] of Object.entries(v)) {
|
|
73
|
+
if (!isObject(val) ||
|
|
74
|
+
!hasString(val, "sequenceNumber") ||
|
|
75
|
+
!hasString(val, "authenticationKey")) {
|
|
76
|
+
throw new Error(`Invalid AccountState for key "${key}": missing 'sequenceNumber' or 'authenticationKey'`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return v;
|
|
80
|
+
}
|
|
81
|
+
export function assertCoinStore(v) {
|
|
82
|
+
if (!isObject(v) ||
|
|
83
|
+
!isObject(v.coin) ||
|
|
84
|
+
!hasString(v.coin, "value")) {
|
|
85
|
+
throw new Error("Invalid CoinStore: expected object with 'coin.value' string");
|
|
86
|
+
}
|
|
87
|
+
return v;
|
|
88
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Account } from "@aptos-labs/ts-sdk";
|
|
2
2
|
import type { MovehatRuntime } from "../types/runtime.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { NodeProvider } from "../node/NodeProvider.js";
|
|
4
4
|
import type { ForkServer } from "../fork/server.js";
|
|
5
5
|
import type { ForkManager } from "../fork/manager.js";
|
|
6
6
|
import type { LocalTestOptions } from "../types/config.js";
|
|
@@ -50,12 +50,13 @@ export declare class Harness {
|
|
|
50
50
|
*/
|
|
51
51
|
readonly accounts: Readonly<Record<string, Account>>;
|
|
52
52
|
/** @internal */
|
|
53
|
-
readonly localNode?:
|
|
53
|
+
readonly localNode?: NodeProvider;
|
|
54
54
|
/** @internal */
|
|
55
55
|
readonly forkServer?: ForkServer;
|
|
56
56
|
/** @internal */
|
|
57
57
|
readonly forkManager?: ForkManager;
|
|
58
58
|
private _poisoned;
|
|
59
|
+
private readonly ownsLocalNode;
|
|
59
60
|
private constructor();
|
|
60
61
|
/** True once `cleanup()` has been awaited at least once. */
|
|
61
62
|
get poisoned(): boolean;
|
package/dist/harness/Harness.js
CHANGED
|
@@ -54,10 +54,12 @@ export class Harness {
|
|
|
54
54
|
/** @internal */
|
|
55
55
|
forkManager;
|
|
56
56
|
_poisoned = false;
|
|
57
|
+
ownsLocalNode;
|
|
57
58
|
constructor(init) {
|
|
58
59
|
this.mode = init.mode;
|
|
59
60
|
this.runtime = init.runtime;
|
|
60
61
|
this.accounts = init.runtime.accountManager.getLabeledAccounts();
|
|
62
|
+
this.ownsLocalNode = init.ownsLocalNode ?? true;
|
|
61
63
|
if (init.localNode)
|
|
62
64
|
this.localNode = init.localNode;
|
|
63
65
|
if (init.forkServer)
|
|
@@ -81,6 +83,7 @@ export class Harness {
|
|
|
81
83
|
const init = {
|
|
82
84
|
mode: "local",
|
|
83
85
|
runtime: ctx.runtime,
|
|
86
|
+
ownsLocalNode: !options.localNode,
|
|
84
87
|
};
|
|
85
88
|
if (ctx.localNode)
|
|
86
89
|
init.localNode = ctx.localNode;
|
|
@@ -156,7 +159,7 @@ export class Harness {
|
|
|
156
159
|
if (this._poisoned)
|
|
157
160
|
return;
|
|
158
161
|
this._poisoned = true;
|
|
159
|
-
if (this.localNode) {
|
|
162
|
+
if (this.localNode && this.ownsLocalNode) {
|
|
160
163
|
await this.localNode.stop().catch(() => { });
|
|
161
164
|
}
|
|
162
165
|
if (this.forkServer) {
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { AccountManager } from "../core/AccountManager.js";
|
|
|
11
11
|
export type { StoredAccount } from "../core/AccountManager.js";
|
|
12
12
|
export { LocalNodeManager } from "../node/LocalNodeManager.js";
|
|
13
13
|
export type { LocalNodeOptions, LocalNodeInfo } from "../node/LocalNodeManager.js";
|
|
14
|
+
export { MoveliteManager, findMoveliteBinary } from "../node/MoveliteManager.js";
|
|
14
15
|
export { setupLocalTesting } from "./setupLocalTesting.js";
|
|
15
16
|
export type { LocalTestingContext } from "./setupLocalTesting.js";
|
|
16
17
|
export { setupTestFixture, setupMinimalFixture, } from "./testFixtures.js";
|
package/dist/helpers/index.js
CHANGED
|
@@ -6,5 +6,6 @@ export { saveDeployment, loadDeployment, getAllDeployments, getDeployedAddress,
|
|
|
6
6
|
export { snapshot, getForkInfo, viewForkResource, compareForkState, listSnapshots, } from "../fork/test.js";
|
|
7
7
|
export { AccountManager } from "../core/AccountManager.js";
|
|
8
8
|
export { LocalNodeManager } from "../node/LocalNodeManager.js";
|
|
9
|
+
export { MoveliteManager, findMoveliteBinary } from "../node/MoveliteManager.js";
|
|
9
10
|
export { setupLocalTesting } from "./setupLocalTesting.js";
|
|
10
11
|
export { setupTestFixture, setupMinimalFixture, } from "./testFixtures.js";
|
|
@@ -2,6 +2,7 @@ import { existsSync } from "fs";
|
|
|
2
2
|
import { resolve } from "path";
|
|
3
3
|
import { loadUserConfig } from "../core/config.js";
|
|
4
4
|
import { runCli } from "../utils/runCli.js";
|
|
5
|
+
import { logger } from "../ui/index.js";
|
|
5
6
|
/**
|
|
6
7
|
* Run Move unit tests using Movement CLI
|
|
7
8
|
* @param options Test options including filter, warnings, and skip behavior
|
|
@@ -12,8 +13,9 @@ export async function runMoveTests(options = {}) {
|
|
|
12
13
|
const moveDir = resolve(process.cwd(), userConfig.moveDir || "./move");
|
|
13
14
|
if (!existsSync(moveDir)) {
|
|
14
15
|
if (options.skipIfMissing) {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
logger.info("No Move directory found (./move not found)");
|
|
17
|
+
logger.plain(" Skipping Move tests...");
|
|
18
|
+
logger.newline();
|
|
17
19
|
return;
|
|
18
20
|
}
|
|
19
21
|
else {
|
|
@@ -42,12 +44,13 @@ export async function runMoveTests(options = {}) {
|
|
|
42
44
|
catch (error) {
|
|
43
45
|
// Spawn-time failure (ENOENT, etc.). The original code logged a
|
|
44
46
|
// Movement-CLI-install hint here; keep that.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
logger.error(`Failed to run Move tests: ${error.message}`);
|
|
48
|
+
logger.error(" Make sure Movement CLI is installed");
|
|
47
49
|
throw error;
|
|
48
50
|
}
|
|
49
51
|
if (result.exitCode === 0) {
|
|
50
|
-
|
|
52
|
+
logger.newline();
|
|
53
|
+
logger.success("Move tests passed");
|
|
51
54
|
return;
|
|
52
55
|
}
|
|
53
56
|
throw new Error("Move tests failed");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MovehatRuntime } from "../types/runtime.js";
|
|
2
2
|
import { ForkManager } from "../fork/manager.js";
|
|
3
3
|
import { ForkServer } from "../fork/server.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { NodeProvider } from "../node/NodeProvider.js";
|
|
5
5
|
import type { LocalTestOptions } from "../types/config.js";
|
|
6
6
|
/**
|
|
7
7
|
* Context returned by {@link setupLocalTesting}.
|
|
@@ -17,7 +17,7 @@ import type { LocalTestOptions } from "../types/config.js";
|
|
|
17
17
|
export interface LocalTestingContext {
|
|
18
18
|
runtime: MovehatRuntime;
|
|
19
19
|
/** @internal */
|
|
20
|
-
localNode?:
|
|
20
|
+
localNode?: NodeProvider;
|
|
21
21
|
/** @internal */
|
|
22
22
|
forkServer?: ForkServer;
|
|
23
23
|
/** @internal */
|
|
@@ -4,6 +4,7 @@ import { initRuntime } from "../runtime.js";
|
|
|
4
4
|
import { ForkManager } from "../fork/manager.js";
|
|
5
5
|
import { ForkServer } from "../fork/server.js";
|
|
6
6
|
import { LocalNodeManager } from "../node/LocalNodeManager.js";
|
|
7
|
+
import { MoveliteManager, findMoveliteBinary } from "../node/MoveliteManager.js";
|
|
7
8
|
import { AccountManager } from "../core/AccountManager.js";
|
|
8
9
|
import { logger } from "../ui/index.js";
|
|
9
10
|
const BUILTIN_FORK_RPCS = {
|
|
@@ -64,11 +65,13 @@ export async function setupLocalTesting(options = {}) {
|
|
|
64
65
|
logger.kv("Accounts", accountLabels.join(", "), 2);
|
|
65
66
|
logger.newline();
|
|
66
67
|
if (mode === 'local-node') {
|
|
67
|
-
const { runtime, localNode } = await setupWithLocalNode(options, accountLabels, autoFund, defaultBalance);
|
|
68
|
+
const { runtime, localNode, ownsNode } = await setupWithLocalNode(options, accountLabels, autoFund, defaultBalance);
|
|
68
69
|
return {
|
|
69
70
|
runtime,
|
|
70
71
|
localNode,
|
|
71
72
|
teardown: async () => {
|
|
73
|
+
if (!ownsNode)
|
|
74
|
+
return;
|
|
72
75
|
logger.newline();
|
|
73
76
|
logger.step("Stopping local testing environment...");
|
|
74
77
|
await localNode.stop();
|
|
@@ -97,21 +100,41 @@ export async function setupLocalTesting(options = {}) {
|
|
|
97
100
|
* Setup using local Movement node (full blockchain)
|
|
98
101
|
*/
|
|
99
102
|
async function setupWithLocalNode(options, accountLabels, autoFund, defaultBalance) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
103
|
+
let localNode;
|
|
104
|
+
let ownsNode;
|
|
105
|
+
let nodeInfo;
|
|
106
|
+
if (options.localNode) {
|
|
107
|
+
localNode = options.localNode;
|
|
108
|
+
ownsNode = false;
|
|
109
|
+
if (!localNode.isRunning()) {
|
|
110
|
+
throw new Error("localNode was provided but isRunning() is false. " +
|
|
111
|
+
"Start the node before passing it to setupLocalTesting.");
|
|
112
|
+
}
|
|
113
|
+
nodeInfo = localNode.getNodeInfo();
|
|
114
|
+
}
|
|
115
|
+
else if (options.useMovelite !== false && findMoveliteBinary()) {
|
|
116
|
+
localNode = new MoveliteManager(findMoveliteBinary());
|
|
117
|
+
nodeInfo = await localNode.start();
|
|
118
|
+
ownsNode = true;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const nodeTestDir = options.nodeTestDir || join(process.cwd(), ".movehat", "local-node");
|
|
122
|
+
const nodeForceRestart = options.nodeForceRestart !== false;
|
|
123
|
+
const nodeFaucetPort = options.nodeFaucetPort || 8081;
|
|
124
|
+
const nodeApiPort = options.nodeApiPort || 8080;
|
|
125
|
+
const nodeReadyPort = options.nodeReadyPort || 8070;
|
|
126
|
+
const nodeSilent = options.nodeSilent ?? false;
|
|
127
|
+
localNode = new LocalNodeManager({
|
|
128
|
+
testDir: nodeTestDir,
|
|
129
|
+
forceRestart: nodeForceRestart,
|
|
130
|
+
faucetPort: nodeFaucetPort,
|
|
131
|
+
apiPort: nodeApiPort,
|
|
132
|
+
readyPort: nodeReadyPort,
|
|
133
|
+
silent: nodeSilent,
|
|
134
|
+
});
|
|
135
|
+
ownsNode = true;
|
|
136
|
+
nodeInfo = await localNode.start();
|
|
137
|
+
}
|
|
115
138
|
// Once the node is up, every later step (account creation, funding,
|
|
116
139
|
// runtime init, autoDeploy) is fallible. If any of them throws we
|
|
117
140
|
// must stop the node we just started — otherwise the child process
|
|
@@ -188,12 +211,12 @@ async function setupWithLocalNode(options, accountLabels, autoFund, defaultBalan
|
|
|
188
211
|
logger.plain(` Accounts: ${Array.from(accountLabels).join(", ")}`);
|
|
189
212
|
logger.plain(` Balance per account: ${defaultBalance / 100_000_000} MOVE`);
|
|
190
213
|
logger.newline();
|
|
191
|
-
return { runtime, localNode };
|
|
214
|
+
return { runtime, localNode, ownsNode };
|
|
192
215
|
}
|
|
193
216
|
catch (error) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
217
|
+
if (ownsNode) {
|
|
218
|
+
await localNode.stop().catch(() => { });
|
|
219
|
+
}
|
|
197
220
|
throw error;
|
|
198
221
|
}
|
|
199
222
|
}
|
|
@@ -3,7 +3,7 @@ import { join } from "path";
|
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import { isNewerVersion } from "./semver-utils.js";
|
|
5
5
|
import { fetchLatestVersion } from "./npm-registry.js";
|
|
6
|
-
import { box, colors, formatCommand } from "../ui/index.js";
|
|
6
|
+
import { box, colors, formatCommand, logger } from "../ui/index.js";
|
|
7
7
|
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
|
8
8
|
const CACHE_DIR = join(homedir(), ".movehat");
|
|
9
9
|
const CACHE_FILE = join(CACHE_DIR, "version-cache.json");
|
|
@@ -72,7 +72,9 @@ export function checkForUpdates(currentVersion, packageName) {
|
|
|
72
72
|
const updateMessage = box(`${colors.brandBright('Update Available!')}\n\n` +
|
|
73
73
|
`${currentVersion} ${colors.dim('→')} ${colors.success(cache.latestVersion)}\n\n` +
|
|
74
74
|
`${formatCommand('movehat update')}`, { borderColor: 'warning', padding: 1 });
|
|
75
|
-
|
|
75
|
+
logger.newline();
|
|
76
|
+
logger.warning(updateMessage);
|
|
77
|
+
logger.newline();
|
|
76
78
|
}
|
|
77
79
|
// Update cache in background if needed (doesn't block)
|
|
78
80
|
if (!cache || Date.now() - cache.lastChecked > CACHE_DURATION) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Account } from "@aptos-labs/ts-sdk";
|
|
2
|
+
import type { LocalNodeInfo } from "./LocalNodeManager.js";
|
|
3
|
+
export declare class MoveliteManager {
|
|
4
|
+
private process;
|
|
5
|
+
private port;
|
|
6
|
+
private killed;
|
|
7
|
+
private readonly binaryPath;
|
|
8
|
+
constructor(binaryPath: string, port?: number);
|
|
9
|
+
start(): Promise<LocalNodeInfo>;
|
|
10
|
+
private waitForReady;
|
|
11
|
+
fundAccount(address: string, amount: number): Promise<void>;
|
|
12
|
+
fundAccounts(accounts: Account[], balance: number): Promise<void>;
|
|
13
|
+
stop(): Promise<void>;
|
|
14
|
+
private isPortInUse;
|
|
15
|
+
isRunning(): boolean;
|
|
16
|
+
getNodeInfo(): LocalNodeInfo;
|
|
17
|
+
}
|
|
18
|
+
export declare function findMoveliteBinary(): string | null;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { execSync, spawn } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { logger } from "../ui/index.js";
|
|
6
|
+
export class MoveliteManager {
|
|
7
|
+
process = null;
|
|
8
|
+
port;
|
|
9
|
+
killed = false;
|
|
10
|
+
binaryPath;
|
|
11
|
+
constructor(binaryPath, port = 8090) {
|
|
12
|
+
this.binaryPath = binaryPath;
|
|
13
|
+
this.port = port;
|
|
14
|
+
}
|
|
15
|
+
async start() {
|
|
16
|
+
const binary = this.binaryPath;
|
|
17
|
+
if (!binary) {
|
|
18
|
+
throw new Error("movelite binary not found");
|
|
19
|
+
}
|
|
20
|
+
if (await this.isPortInUse(this.port)) {
|
|
21
|
+
this.port = this.port + 1;
|
|
22
|
+
if (await this.isPortInUse(this.port)) {
|
|
23
|
+
throw new Error(`Ports ${this.port - 1} and ${this.port} are in use`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
logger.step("Starting movelite...");
|
|
27
|
+
this.process = spawn(binary, ["start", "--port", String(this.port), "--no-auth"], {
|
|
28
|
+
stdio: "pipe",
|
|
29
|
+
detached: false,
|
|
30
|
+
});
|
|
31
|
+
this.process.on("exit", () => {
|
|
32
|
+
this.process = null;
|
|
33
|
+
});
|
|
34
|
+
await this.waitForReady();
|
|
35
|
+
logger.success(`movelite ready on port ${this.port}`);
|
|
36
|
+
return this.getNodeInfo();
|
|
37
|
+
}
|
|
38
|
+
async waitForReady() {
|
|
39
|
+
const url = `http://127.0.0.1:${this.port}/v1`;
|
|
40
|
+
const timeout = 15_000;
|
|
41
|
+
const start = Date.now();
|
|
42
|
+
while (Date.now() - start < timeout) {
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(url);
|
|
45
|
+
if (res.ok)
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// not ready yet
|
|
50
|
+
}
|
|
51
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`movelite did not become ready within ${timeout}ms`);
|
|
54
|
+
}
|
|
55
|
+
async fundAccount(address, amount) {
|
|
56
|
+
const res = await fetch(`http://127.0.0.1:${this.port}/mint?address=${address}&amount=${amount}`, { method: "POST" });
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new Error(`Failed to fund account: ${res.status}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async fundAccounts(accounts, balance) {
|
|
62
|
+
for (const account of accounts) {
|
|
63
|
+
await this.fundAccount(account.accountAddress.toString(), balance);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async stop() {
|
|
67
|
+
if (!this.process || this.killed)
|
|
68
|
+
return;
|
|
69
|
+
this.killed = true;
|
|
70
|
+
this.process.kill("SIGTERM");
|
|
71
|
+
await new Promise((resolve) => {
|
|
72
|
+
const timer = setTimeout(() => {
|
|
73
|
+
if (this.process)
|
|
74
|
+
this.process.kill("SIGKILL");
|
|
75
|
+
resolve();
|
|
76
|
+
}, 5_000);
|
|
77
|
+
if (this.process) {
|
|
78
|
+
this.process.on("exit", () => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
resolve();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
resolve();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
this.process = null;
|
|
89
|
+
}
|
|
90
|
+
async isPortInUse(port) {
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch(`http://127.0.0.1:${port}/v1`);
|
|
93
|
+
return res.ok;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
isRunning() {
|
|
100
|
+
return this.process !== null && !this.killed;
|
|
101
|
+
}
|
|
102
|
+
getNodeInfo() {
|
|
103
|
+
return {
|
|
104
|
+
rpcUrl: `http://127.0.0.1:${this.port}`,
|
|
105
|
+
faucetUrl: `http://127.0.0.1:${this.port}`,
|
|
106
|
+
readyUrl: `http://127.0.0.1:${this.port}/v1`,
|
|
107
|
+
testDir: "",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function findMoveliteBinary() {
|
|
112
|
+
if (process.env.MOVELITE_PATH) {
|
|
113
|
+
return existsSync(process.env.MOVELITE_PATH)
|
|
114
|
+
? process.env.MOVELITE_PATH
|
|
115
|
+
: null;
|
|
116
|
+
}
|
|
117
|
+
const platforms = {
|
|
118
|
+
"darwin-arm64": "movelite-darwin-arm64",
|
|
119
|
+
"darwin-x64": "movelite-darwin-x64",
|
|
120
|
+
"linux-x64": "movelite-linux-x64",
|
|
121
|
+
"linux-arm64": "movelite-linux-arm64",
|
|
122
|
+
};
|
|
123
|
+
const key = `${process.platform}-${process.arch}`;
|
|
124
|
+
const pkg = platforms[key];
|
|
125
|
+
if (pkg) {
|
|
126
|
+
try {
|
|
127
|
+
// Resolve through the `movelite` shim (movehat's direct dependency),
|
|
128
|
+
// then resolve the platform package from movelite's own context. The
|
|
129
|
+
// platform package is movelite's dependency, not movehat's, so a direct
|
|
130
|
+
// resolve from here fails under pnpm/yarn strict layouts.
|
|
131
|
+
const req = createRequire(import.meta.url);
|
|
132
|
+
const movelitePkg = req.resolve("movelite/package.json");
|
|
133
|
+
const moveliteReq = createRequire(movelitePkg);
|
|
134
|
+
const pkgPath = moveliteReq.resolve(`${pkg}/package.json`);
|
|
135
|
+
const binPath = join(pkgPath, "..", "bin", "movelite");
|
|
136
|
+
if (existsSync(binPath))
|
|
137
|
+
return binPath;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// package not installed
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const found = execSync("which movelite", { encoding: "utf-8" }).trim();
|
|
145
|
+
if (found && existsSync(found))
|
|
146
|
+
return found;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// not in PATH
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Account } from "@aptos-labs/ts-sdk";
|
|
2
|
+
import type { LocalNodeInfo } from "./LocalNodeManager.js";
|
|
3
|
+
export interface NodeProvider {
|
|
4
|
+
start(): Promise<LocalNodeInfo>;
|
|
5
|
+
stop(): Promise<void>;
|
|
6
|
+
isRunning(): boolean;
|
|
7
|
+
getNodeInfo(): LocalNodeInfo;
|
|
8
|
+
fundAccounts(accounts: Account[], balance: number): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { describe, it, before, after } from "mocha";
|
|
3
3
|
import { expect } from "chai";
|
|
4
4
|
import { Harness } from "movehat";
|
|
5
|
+
import { getSharedNode } from "./setup.js";
|
|
5
6
|
|
|
6
7
|
describe("Counter Contract", () => {
|
|
7
8
|
let harness;
|
|
@@ -9,16 +10,12 @@ describe("Counter Contract", () => {
|
|
|
9
10
|
let deployer, alice, bob;
|
|
10
11
|
|
|
11
12
|
before(async function () {
|
|
12
|
-
this.timeout(60000);
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// accounts from the local faucet, and (via autoDeploy) builds +
|
|
17
|
-
// publishes the named modules so they're ready to use.
|
|
18
|
-
//
|
|
19
|
-
// The setupTestFixture helper still works if you prefer the older
|
|
20
|
-
// pattern; see `import { setupTestFixture } from "movehat/helpers"`.
|
|
13
|
+
this.timeout(60000);
|
|
14
|
+
|
|
15
|
+
// Reuses the shared Movement node started by root hooks (tests/setup.ts).
|
|
16
|
+
// Each spec still gets its own accounts + deployments for isolation.
|
|
21
17
|
harness = await Harness.createLocal({
|
|
18
|
+
localNode: getSharedNode(),
|
|
22
19
|
accountLabels: ["deployer", "alice", "bob"],
|
|
23
20
|
autoDeploy: ["counter"],
|
|
24
21
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @ts-nocheck - This is a template file, dependencies are installed in user projects
|
|
2
|
+
import { LocalNodeManager } from "movehat/helpers";
|
|
3
|
+
|
|
4
|
+
let sharedNode;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns the shared local Movement node started by root hooks.
|
|
8
|
+
* Pass the result as `{ localNode: getSharedNode() }` to
|
|
9
|
+
* Harness.createLocal() or setupTestFixture().
|
|
10
|
+
*/
|
|
11
|
+
export function getSharedNode() {
|
|
12
|
+
if (!sharedNode || !sharedNode.isRunning()) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"Shared node not available. " +
|
|
15
|
+
'Ensure tests/setup.ts is listed in .mocharc.json under "require".'
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return sharedNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const mochaHooks = {
|
|
22
|
+
async beforeAll() {
|
|
23
|
+
this.timeout(60000);
|
|
24
|
+
sharedNode = new LocalNodeManager({ forceRestart: true });
|
|
25
|
+
await sharedNode.start();
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async afterAll() {
|
|
29
|
+
if (sharedNode) {
|
|
30
|
+
await sharedNode.stop();
|
|
31
|
+
sharedNode = undefined;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
package/dist/types/config.d.ts
CHANGED
|
@@ -41,6 +41,8 @@ export type LocalTestingMode = 'local-node' | 'fork';
|
|
|
41
41
|
*/
|
|
42
42
|
export interface LocalTestOptions {
|
|
43
43
|
mode?: LocalTestingMode;
|
|
44
|
+
localNode?: import("../node/LocalNodeManager.js").LocalNodeManager;
|
|
45
|
+
useMovelite?: boolean;
|
|
44
46
|
nodeTestDir?: string;
|
|
45
47
|
nodeForceRestart?: boolean;
|
|
46
48
|
nodeFaucetPort?: number;
|
package/dist/types/fork.d.ts
CHANGED
|
@@ -40,3 +40,27 @@ export interface AccountResource {
|
|
|
40
40
|
*/
|
|
41
41
|
data: unknown;
|
|
42
42
|
}
|
|
43
|
+
export interface CoinStore {
|
|
44
|
+
coin: {
|
|
45
|
+
value: string;
|
|
46
|
+
};
|
|
47
|
+
deposit_events: {
|
|
48
|
+
counter: string;
|
|
49
|
+
guid: {
|
|
50
|
+
id: {
|
|
51
|
+
addr: string;
|
|
52
|
+
creation_num: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
withdraw_events: {
|
|
57
|
+
counter: string;
|
|
58
|
+
guid: {
|
|
59
|
+
id: {
|
|
60
|
+
addr: string;
|
|
61
|
+
creation_num: string;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
frozen: boolean;
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "movehat",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Hardhat-like development framework for Movement L1 smart contracts",
|
|
6
6
|
"bin": {
|
|
@@ -70,6 +70,9 @@
|
|
|
70
70
|
"prompts": "^2.4.2",
|
|
71
71
|
"tsx": "^4.7.0"
|
|
72
72
|
},
|
|
73
|
+
"optionalDependencies": {
|
|
74
|
+
"movelite": "^0.1.0"
|
|
75
|
+
},
|
|
73
76
|
"devDependencies": {
|
|
74
77
|
"@types/js-yaml": "^4.0.9",
|
|
75
78
|
"@types/node": "^24.10.1",
|