movehat 0.2.7 → 0.2.9

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.
@@ -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 { LocalNodeManager } from "../node/LocalNodeManager.js";
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?: LocalNodeManager;
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;
@@ -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) {
@@ -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";
@@ -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
- console.log("No Move directory found (./move not found)");
16
- console.log(" Skipping Move tests...\n");
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
- console.error(`Failed to run Move tests: ${error.message}`);
46
- console.error(" Make sure Movement CLI is installed");
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
- console.log("\n✓ Move tests passed");
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 { LocalNodeManager } from "../node/LocalNodeManager.js";
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?: LocalNodeManager;
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();
@@ -93,25 +96,56 @@ export async function setupLocalTesting(options = {}) {
93
96
  };
94
97
  }
95
98
  }
99
+ /**
100
+ * Resolve whether to prefer movelite. Explicit `useMovelite` wins; otherwise
101
+ * the `MOVEHAT_USE_MOVELITE` env var acts as an override (`0` disables, `1`
102
+ * enables); default is enabled. Lets tooling force a backend without editing
103
+ * test code.
104
+ */
105
+ function resolveUseMovelite(useMovelite) {
106
+ if (useMovelite !== undefined)
107
+ return useMovelite;
108
+ return process.env.MOVEHAT_USE_MOVELITE !== "0";
109
+ }
96
110
  /**
97
111
  * Setup using local Movement node (full blockchain)
98
112
  */
99
113
  async function setupWithLocalNode(options, accountLabels, autoFund, defaultBalance) {
100
- const nodeTestDir = options.nodeTestDir || join(process.cwd(), ".movehat", "local-node");
101
- const nodeForceRestart = options.nodeForceRestart !== false;
102
- const nodeFaucetPort = options.nodeFaucetPort || 8081;
103
- const nodeApiPort = options.nodeApiPort || 8080;
104
- const nodeReadyPort = options.nodeReadyPort || 8070;
105
- const nodeSilent = options.nodeSilent ?? false;
106
- const localNode = new LocalNodeManager({
107
- testDir: nodeTestDir,
108
- forceRestart: nodeForceRestart,
109
- faucetPort: nodeFaucetPort,
110
- apiPort: nodeApiPort,
111
- readyPort: nodeReadyPort,
112
- silent: nodeSilent,
113
- });
114
- const nodeInfo = await localNode.start();
114
+ let localNode;
115
+ let ownsNode;
116
+ let nodeInfo;
117
+ if (options.localNode) {
118
+ localNode = options.localNode;
119
+ ownsNode = false;
120
+ if (!localNode.isRunning()) {
121
+ throw new Error("localNode was provided but isRunning() is false. " +
122
+ "Start the node before passing it to setupLocalTesting.");
123
+ }
124
+ nodeInfo = localNode.getNodeInfo();
125
+ }
126
+ else if (resolveUseMovelite(options.useMovelite) && findMoveliteBinary()) {
127
+ localNode = new MoveliteManager(findMoveliteBinary());
128
+ nodeInfo = await localNode.start();
129
+ ownsNode = true;
130
+ }
131
+ else {
132
+ const nodeTestDir = options.nodeTestDir || join(process.cwd(), ".movehat", "local-node");
133
+ const nodeForceRestart = options.nodeForceRestart !== false;
134
+ const nodeFaucetPort = options.nodeFaucetPort || 8081;
135
+ const nodeApiPort = options.nodeApiPort || 8080;
136
+ const nodeReadyPort = options.nodeReadyPort || 8070;
137
+ const nodeSilent = options.nodeSilent ?? false;
138
+ localNode = new LocalNodeManager({
139
+ testDir: nodeTestDir,
140
+ forceRestart: nodeForceRestart,
141
+ faucetPort: nodeFaucetPort,
142
+ apiPort: nodeApiPort,
143
+ readyPort: nodeReadyPort,
144
+ silent: nodeSilent,
145
+ });
146
+ ownsNode = true;
147
+ nodeInfo = await localNode.start();
148
+ }
115
149
  // Once the node is up, every later step (account creation, funding,
116
150
  // runtime init, autoDeploy) is fallible. If any of them throws we
117
151
  // must stop the node we just started — otherwise the child process
@@ -156,11 +190,14 @@ async function setupWithLocalNode(options, accountLabels, autoFund, defaultBalan
156
190
  logger.step(`Auto-deploying ${options.autoDeploy.length} module(s)...`);
157
191
  const previousRedeploy = process.env.MH_CLI_REDEPLOY;
158
192
  process.env.MH_CLI_REDEPLOY = 'true';
193
+ // movelite's REST responses can't drive the Movement CLI publish flow,
194
+ // so deploy through the TypeScript SDK when it is the spawned backend.
195
+ const sdkPublish = localNode instanceof MoveliteManager;
159
196
  try {
160
197
  for (const moduleName of options.autoDeploy) {
161
198
  try {
162
199
  logger.plain(` Deploying ${moduleName}...`);
163
- await runtime.deployContract(moduleName);
200
+ await runtime.deployContract(moduleName, { sdkPublish });
164
201
  logger.success(`${moduleName} deployed`, 2);
165
202
  }
166
203
  catch (error) {
@@ -188,12 +225,12 @@ async function setupWithLocalNode(options, accountLabels, autoFund, defaultBalan
188
225
  logger.plain(` Accounts: ${Array.from(accountLabels).join(", ")}`);
189
226
  logger.plain(` Balance per account: ${defaultBalance / 100_000_000} MOVE`);
190
227
  logger.newline();
191
- return { runtime, localNode };
228
+ return { runtime, localNode, ownsNode };
192
229
  }
193
230
  catch (error) {
194
- // Best-effort cleanup. Swallow the stop() error so the original
195
- // setup failure surfaces unchanged.
196
- await localNode.stop().catch(() => { });
231
+ if (ownsNode) {
232
+ await localNode.stop().catch(() => { });
233
+ }
197
234
  throw error;
198
235
  }
199
236
  }
@@ -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
- console.error('\n' + updateMessage + '\n');
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 {};
package/dist/runtime.js CHANGED
@@ -64,6 +64,8 @@ export async function initRuntime(options = {}) {
64
64
  config,
65
65
  account,
66
66
  packageDir: options?.packageDir,
67
+ sdkPublish: options?.sdkPublish,
68
+ aptos,
67
69
  });
68
70
  };
69
71
  const getDeployment = (moduleName) => {
@@ -2,6 +2,7 @@
2
2
  "node-option": ["import=tsx"],
3
3
  "extensions": ["ts"],
4
4
  "spec": ["tests/**/*.test.ts"],
5
+ "require": ["tests/setup.ts"],
5
6
  "timeout": 30000,
6
7
  "color": true,
7
8
  "reporter": "spec"
@@ -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); // Allow time for local node startup + deployment
13
-
14
- // Hardhat-style Harness the primary public API.
15
- // createLocal spins up a Movement local node, funds the labeled
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
+ };
@@ -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;