movehat 0.2.1 → 0.2.3

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 (176) hide show
  1. package/dist/__tests__/deployContract.test.js +56 -47
  2. package/dist/__tests__/deployContract.test.js.map +1 -1
  3. package/dist/__tests__/exports.test.d.ts +2 -0
  4. package/dist/__tests__/exports.test.d.ts.map +1 -0
  5. package/dist/__tests__/exports.test.js +30 -0
  6. package/dist/__tests__/exports.test.js.map +1 -0
  7. package/dist/__tests__/fixtures/sigint-deploy-harness.d.ts +4 -3
  8. package/dist/__tests__/fixtures/sigint-deploy-harness.d.ts.map +1 -1
  9. package/dist/__tests__/fixtures/sigint-deploy-harness.js +8 -7
  10. package/dist/__tests__/fixtures/sigint-deploy-harness.js.map +1 -1
  11. package/dist/__tests__/fork/api.test.js +5 -0
  12. package/dist/__tests__/fork/api.test.js.map +1 -1
  13. package/dist/__tests__/fork/api.timeout.test.d.ts +2 -0
  14. package/dist/__tests__/fork/api.timeout.test.d.ts.map +1 -0
  15. package/dist/__tests__/fork/api.timeout.test.js +98 -0
  16. package/dist/__tests__/fork/api.timeout.test.js.map +1 -0
  17. package/dist/cli.js +4 -0
  18. package/dist/cli.js.map +1 -1
  19. package/dist/commands/__tests__/compile.toml-mutation.test.d.ts +2 -0
  20. package/dist/commands/__tests__/compile.toml-mutation.test.d.ts.map +1 -0
  21. package/dist/commands/__tests__/compile.toml-mutation.test.js +69 -0
  22. package/dist/commands/__tests__/compile.toml-mutation.test.js.map +1 -0
  23. package/dist/commands/__tests__/init.test.js +73 -11
  24. package/dist/commands/__tests__/init.test.js.map +1 -1
  25. package/dist/commands/compile.d.ts.map +1 -1
  26. package/dist/commands/compile.js +19 -10
  27. package/dist/commands/compile.js.map +1 -1
  28. package/dist/commands/init.d.ts +22 -0
  29. package/dist/commands/init.d.ts.map +1 -1
  30. package/dist/commands/init.js +55 -6
  31. package/dist/commands/init.js.map +1 -1
  32. package/dist/commands/test.js +12 -19
  33. package/dist/commands/test.js.map +1 -1
  34. package/dist/core/AccountManager.d.ts.map +1 -1
  35. package/dist/core/AccountManager.js +14 -2
  36. package/dist/core/AccountManager.js.map +1 -1
  37. package/dist/core/Publisher.d.ts.map +1 -1
  38. package/dist/core/Publisher.js +72 -82
  39. package/dist/core/Publisher.js.map +1 -1
  40. package/dist/core/__tests__/AccountManager.global-state.test.d.ts +2 -0
  41. package/dist/core/__tests__/AccountManager.global-state.test.d.ts.map +1 -0
  42. package/dist/core/__tests__/AccountManager.global-state.test.js +69 -0
  43. package/dist/core/__tests__/AccountManager.global-state.test.js.map +1 -0
  44. package/dist/core/__tests__/movementProfile.test.d.ts +2 -0
  45. package/dist/core/__tests__/movementProfile.test.d.ts.map +1 -0
  46. package/dist/core/__tests__/movementProfile.test.js +112 -0
  47. package/dist/core/__tests__/movementProfile.test.js.map +1 -0
  48. package/dist/core/config.d.ts.map +1 -1
  49. package/dist/core/config.js +14 -10
  50. package/dist/core/config.js.map +1 -1
  51. package/dist/core/deployments.d.ts.map +1 -1
  52. package/dist/core/deployments.js +4 -2
  53. package/dist/core/deployments.js.map +1 -1
  54. package/dist/core/movementProfile.d.ts +55 -22
  55. package/dist/core/movementProfile.d.ts.map +1 -1
  56. package/dist/core/movementProfile.js +77 -99
  57. package/dist/core/movementProfile.js.map +1 -1
  58. package/dist/fork/__tests__/server.cors.test.d.ts +2 -0
  59. package/dist/fork/__tests__/server.cors.test.d.ts.map +1 -0
  60. package/dist/fork/__tests__/server.cors.test.js +79 -0
  61. package/dist/fork/__tests__/server.cors.test.js.map +1 -0
  62. package/dist/fork/api.d.ts +9 -1
  63. package/dist/fork/api.d.ts.map +1 -1
  64. package/dist/fork/api.js +37 -7
  65. package/dist/fork/api.js.map +1 -1
  66. package/dist/fork/manager.js +10 -10
  67. package/dist/fork/manager.js.map +1 -1
  68. package/dist/fork/server.d.ts +20 -1
  69. package/dist/fork/server.d.ts.map +1 -1
  70. package/dist/fork/server.js +40 -24
  71. package/dist/fork/server.js.map +1 -1
  72. package/dist/fork/test.d.ts.map +1 -1
  73. package/dist/fork/test.js +3 -2
  74. package/dist/fork/test.js.map +1 -1
  75. package/dist/harness/Harness.d.ts +6 -2
  76. package/dist/harness/Harness.d.ts.map +1 -1
  77. package/dist/harness/Harness.js +8 -2
  78. package/dist/harness/Harness.js.map +1 -1
  79. package/dist/harness/codeObject.d.ts.map +1 -1
  80. package/dist/harness/codeObject.js +41 -41
  81. package/dist/harness/codeObject.js.map +1 -1
  82. package/dist/harness/script.d.ts +3 -3
  83. package/dist/harness/script.d.ts.map +1 -1
  84. package/dist/harness/script.js +42 -35
  85. package/dist/harness/script.js.map +1 -1
  86. package/dist/helpers/__tests__/setupLocalTesting.fork-network.test.d.ts +2 -0
  87. package/dist/helpers/__tests__/setupLocalTesting.fork-network.test.d.ts.map +1 -0
  88. package/dist/helpers/__tests__/setupLocalTesting.fork-network.test.js +172 -0
  89. package/dist/helpers/__tests__/setupLocalTesting.fork-network.test.js.map +1 -0
  90. package/dist/helpers/setupLocalTesting.d.ts.map +1 -1
  91. package/dist/helpers/setupLocalTesting.js +31 -5
  92. package/dist/helpers/setupLocalTesting.js.map +1 -1
  93. package/dist/index.d.ts +1 -0
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/node/LocalNodeManager.d.ts +8 -0
  96. package/dist/node/LocalNodeManager.d.ts.map +1 -1
  97. package/dist/node/LocalNodeManager.js +70 -23
  98. package/dist/node/LocalNodeManager.js.map +1 -1
  99. package/dist/node/__tests__/LocalNodeManager.api-port.test.d.ts +2 -0
  100. package/dist/node/__tests__/LocalNodeManager.api-port.test.d.ts.map +1 -0
  101. package/dist/node/__tests__/LocalNodeManager.api-port.test.js +55 -0
  102. package/dist/node/__tests__/LocalNodeManager.api-port.test.js.map +1 -0
  103. package/dist/node/__tests__/LocalNodeManager.test.js +114 -14
  104. package/dist/node/__tests__/LocalNodeManager.test.js.map +1 -1
  105. package/dist/templates/move/Move.toml +1 -1
  106. package/dist/templates/move/sources/Counter.move +31 -4
  107. package/dist/templates/scripts/deploy-counter.ts +10 -0
  108. package/dist/types/config.d.ts +8 -1
  109. package/dist/types/config.d.ts.map +1 -1
  110. package/dist/ui/__tests__/logger.test.d.ts +2 -0
  111. package/dist/ui/__tests__/logger.test.d.ts.map +1 -0
  112. package/dist/ui/__tests__/logger.test.js +75 -0
  113. package/dist/ui/__tests__/logger.test.js.map +1 -0
  114. package/dist/ui/formatters.d.ts +0 -16
  115. package/dist/ui/formatters.d.ts.map +1 -1
  116. package/dist/ui/formatters.js +1 -1
  117. package/dist/ui/formatters.js.map +1 -1
  118. package/dist/ui/logger.d.ts +41 -0
  119. package/dist/ui/logger.d.ts.map +1 -1
  120. package/dist/ui/logger.js +49 -0
  121. package/dist/ui/logger.js.map +1 -1
  122. package/dist/ui/spinner.d.ts +25 -0
  123. package/dist/ui/spinner.d.ts.map +1 -1
  124. package/dist/ui/spinner.js +44 -0
  125. package/dist/ui/spinner.js.map +1 -1
  126. package/dist/utils/__tests__/childProcessAdapter.maxBuffer.test.d.ts +2 -0
  127. package/dist/utils/__tests__/childProcessAdapter.maxBuffer.test.d.ts.map +1 -0
  128. package/dist/utils/__tests__/childProcessAdapter.maxBuffer.test.js +43 -0
  129. package/dist/utils/__tests__/childProcessAdapter.maxBuffer.test.js.map +1 -0
  130. package/dist/utils/childProcessAdapter.d.ts +7 -0
  131. package/dist/utils/childProcessAdapter.d.ts.map +1 -1
  132. package/dist/utils/childProcessAdapter.js +20 -2
  133. package/dist/utils/childProcessAdapter.js.map +1 -1
  134. package/package.json +1 -1
  135. package/src/__tests__/deployContract.test.ts +59 -50
  136. package/src/__tests__/exports.test.ts +32 -0
  137. package/src/__tests__/fixtures/sigint-deploy-harness.ts +8 -7
  138. package/src/__tests__/fork/api.test.ts +5 -0
  139. package/src/__tests__/fork/api.timeout.test.ts +150 -0
  140. package/src/cli.ts +4 -0
  141. package/src/commands/__tests__/compile.toml-mutation.test.ts +77 -0
  142. package/src/commands/__tests__/init.test.ts +96 -11
  143. package/src/commands/compile.ts +24 -15
  144. package/src/commands/init.ts +77 -6
  145. package/src/commands/test.ts +12 -19
  146. package/src/core/AccountManager.ts +18 -1
  147. package/src/core/Publisher.ts +103 -107
  148. package/src/core/__tests__/AccountManager.global-state.test.ts +83 -0
  149. package/src/core/__tests__/movementProfile.test.ts +131 -0
  150. package/src/core/config.ts +18 -11
  151. package/src/core/deployments.ts +5 -4
  152. package/src/core/movementProfile.ts +75 -127
  153. package/src/fork/__tests__/server.cors.test.ts +101 -0
  154. package/src/fork/api.ts +69 -10
  155. package/src/fork/manager.ts +10 -10
  156. package/src/fork/server.ts +59 -24
  157. package/src/fork/test.ts +3 -2
  158. package/src/harness/Harness.ts +11 -2
  159. package/src/harness/codeObject.ts +45 -48
  160. package/src/harness/script.ts +47 -43
  161. package/src/helpers/__tests__/setupLocalTesting.fork-network.test.ts +212 -0
  162. package/src/helpers/setupLocalTesting.ts +39 -5
  163. package/src/index.ts +9 -1
  164. package/src/node/LocalNodeManager.ts +87 -26
  165. package/src/node/__tests__/LocalNodeManager.api-port.test.ts +62 -0
  166. package/src/node/__tests__/LocalNodeManager.test.ts +144 -17
  167. package/src/templates/move/Move.toml +1 -1
  168. package/src/templates/move/sources/Counter.move +31 -4
  169. package/src/templates/scripts/deploy-counter.ts +10 -0
  170. package/src/types/config.ts +8 -1
  171. package/src/ui/__tests__/logger.test.ts +89 -0
  172. package/src/ui/formatters.ts +1 -1
  173. package/src/ui/logger.ts +62 -0
  174. package/src/ui/spinner.ts +47 -0
  175. package/src/utils/__tests__/childProcessAdapter.maxBuffer.test.ts +51 -0
  176. package/src/utils/childProcessAdapter.ts +32 -2
@@ -1,6 +1,20 @@
1
1
  import http from 'http';
2
2
  import { URL } from 'url';
3
3
  import { ForkManager } from './manager.js';
4
+ import { logger } from '../ui/index.js';
5
+
6
+ export interface ForkServerOptions {
7
+ /**
8
+ * Origins allowed to make cross-origin requests. When unset (default),
9
+ * no `Access-Control-Allow-Origin` header is emitted — any browser
10
+ * cross-origin read is rejected by the user agent. Setting this to a
11
+ * non-empty list opts into echoing matching `Origin` request headers
12
+ * back. Wildcard `'*'` is intentionally NOT supported: cached fork
13
+ * state may include resources that should not be readable by every
14
+ * page in the dev's browser.
15
+ */
16
+ corsAllowOrigins?: readonly string[];
17
+ }
4
18
 
5
19
  /**
6
20
  * Fork Server - Serves fork data via Movement L1 RPC API
@@ -11,16 +25,38 @@ export class ForkServer {
11
25
  private forkManager: ForkManager;
12
26
  private port: number;
13
27
  private host: string;
28
+ private readonly corsAllowOrigins: ReadonlySet<string>;
14
29
 
15
30
  /**
16
31
  * @param host Interface to bind. Defaults to `127.0.0.1` so cached fork
17
32
  * state (which may include sensitive resources) is not exposed on the LAN.
18
33
  * Pass `'0.0.0.0'` only if you intentionally need to expose the server.
34
+ * @param options Optional CORS allowlist (see {@link ForkServerOptions}).
19
35
  */
20
- constructor(forkPath: string, port: number = 8080, host: string = '127.0.0.1') {
36
+ constructor(
37
+ forkPath: string,
38
+ port: number = 8080,
39
+ host: string = '127.0.0.1',
40
+ options: ForkServerOptions = {}
41
+ ) {
21
42
  this.forkManager = new ForkManager(forkPath);
22
43
  this.port = port;
23
44
  this.host = host;
45
+ this.corsAllowOrigins = new Set(options.corsAllowOrigins ?? []);
46
+ }
47
+
48
+ /**
49
+ * Set CORS headers for a request when the request's `Origin` is in
50
+ * the allowlist. No-op otherwise.
51
+ */
52
+ private applyCors(req: http.IncomingMessage, res: http.ServerResponse): void {
53
+ const origin = req.headers.origin;
54
+ if (typeof origin === 'string' && this.corsAllowOrigins.has(origin)) {
55
+ res.setHeader('Access-Control-Allow-Origin', origin);
56
+ res.setHeader('Vary', 'Origin');
57
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
58
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
59
+ }
24
60
  }
25
61
 
26
62
  /**
@@ -31,23 +67,21 @@ export class ForkServer {
31
67
  this.forkManager.load();
32
68
  const metadata = this.forkManager.getMetadata();
33
69
 
34
- console.log(`\nStarting Fork Server...`);
35
- console.log(` Network: ${metadata.network}`);
36
- console.log(` Chain ID: ${metadata.chainId}`);
37
- console.log(` Ledger Version: ${metadata.ledgerVersion}`);
38
- console.log(` Forked at: ${metadata.createdAt}`);
70
+ logger.newline();
71
+ logger.phase("Fork Server");
72
+ logger.kv("Network", metadata.network, 2);
73
+ logger.kv("Chain ID", String(metadata.chainId), 2);
74
+ logger.kv("Ledger Version", String(metadata.ledgerVersion), 2);
75
+ logger.kv("Forked at", metadata.createdAt, 2);
39
76
 
40
77
  this.server = http.createServer((req, res) => {
41
78
  this.handleRequest(req, res).catch((error) => {
42
79
  // Log full error server-side for diagnostics
43
- console.error(`Error handling request:`, error);
80
+ logger.error(`Error handling request: ${error instanceof Error ? error.message : String(error)}`);
44
81
 
45
82
  // Only send response if headers haven't been sent yet
46
83
  if (!res.headersSent) {
47
- // Add CORS headers (same as in handleRequest)
48
- res.setHeader('Access-Control-Allow-Origin', '*');
49
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
50
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
84
+ this.applyCors(req, res);
51
85
 
52
86
  // Send generic error response (no internal details exposed)
53
87
  this.sendJSON(res, 500, {
@@ -92,13 +126,15 @@ export class ForkServer {
92
126
  : isIpv6
93
127
  ? `[${this.host}]`
94
128
  : this.host;
95
- console.log(`\nFork Server listening on http://${displayHost}:${this.port}`);
96
- console.log(` Bound interface: ${this.host}`);
97
- console.log(` Ledger Info: http://${displayHost}:${this.port}/v1/`);
129
+ logger.newline();
130
+ logger.success(`Fork Server listening on http://${displayHost}:${this.port}`);
131
+ logger.kv("Bound interface", this.host, 2);
132
+ logger.kv("Ledger Info", `http://${displayHost}:${this.port}/v1/`, 2);
98
133
  if (this.host === '0.0.0.0') {
99
- console.warn(` Server is bound to 0.0.0.0 — fork state is reachable from the LAN.`);
134
+ logger.warning("Server is bound to 0.0.0.0 — fork state is reachable from the LAN.", 2);
100
135
  }
101
- console.log(`\nPress Ctrl+C to stop`);
136
+ logger.newline();
137
+ logger.info("Press Ctrl+C to stop");
102
138
  resolve();
103
139
  });
104
140
  });
@@ -111,7 +147,8 @@ export class ForkServer {
111
147
  return new Promise((resolve) => {
112
148
  if (this.server) {
113
149
  this.server.close(() => {
114
- console.log('\nFork Server stopped');
150
+ logger.newline();
151
+ logger.success("Fork Server stopped");
115
152
  resolve();
116
153
  });
117
154
  } else {
@@ -140,13 +177,11 @@ export class ForkServer {
140
177
  const url = new URL(req.url || '/', `http://localhost:${this.port}`);
141
178
  const pathname = url.pathname;
142
179
 
143
- // Log request
144
- console.log(`[${new Date().toISOString()}] ${req.method} ${pathname}`);
180
+ // Log request — plain so the fork-server access log retains its
181
+ // grep-friendly line shape (timestamp + method + path, no symbol).
182
+ logger.plain(`[${new Date().toISOString()}] ${req.method} ${pathname}`);
145
183
 
146
- // CORS headers
147
- res.setHeader('Access-Control-Allow-Origin', '*');
148
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
149
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
184
+ this.applyCors(req, res);
150
185
 
151
186
  // Handle OPTIONS for CORS preflight
152
187
  if (req.method === 'OPTIONS') {
@@ -187,7 +222,7 @@ export class ForkServer {
187
222
  }
188
223
  } catch (error) {
189
224
  // Log full error server-side for diagnostics
190
- console.error('Error handling request:', error);
225
+ logger.error(`Error handling request: ${error instanceof Error ? error.message : String(error)}`);
191
226
 
192
227
  // Send generic error to client (don't expose internal details)
193
228
  this.sendError(res, 500, 'Internal server error');
package/src/fork/test.ts CHANGED
@@ -2,6 +2,7 @@ import { join } from 'path';
2
2
  import { existsSync } from 'fs';
3
3
  import { runCli } from '../utils/runCli.js';
4
4
  import type { ChildProcessAdapter } from '../utils/childProcessAdapter.js';
5
+ import { logger } from '../ui/index.js';
5
6
 
6
7
  export interface SnapshotOptions {
7
8
  path?: string;
@@ -40,7 +41,7 @@ export async function snapshot(options: SnapshotOptions = {}): Promise<string> {
40
41
  const name = options.name || `snapshot-${Date.now()}`;
41
42
  const snapshotPath = options.path || join(process.cwd(), '.movehat', 'snapshots', name);
42
43
 
43
- console.log(`📸 Creating snapshot: ${name}...`);
44
+ logger.info(`Creating snapshot: ${name}...`);
44
45
 
45
46
  try {
46
47
  // Initialize fork/snapshot using aptos CLI.
@@ -68,7 +69,7 @@ export async function snapshot(options: SnapshotOptions = {}): Promise<string> {
68
69
  throw new Error('Snapshot directory was not created');
69
70
  }
70
71
 
71
- console.log(`Snapshot created at ${snapshotPath}`);
72
+ logger.success(`Snapshot created at ${snapshotPath}`, 2);
72
73
  return snapshotPath;
73
74
  } catch (error) {
74
75
  const msg = error instanceof Error ? error.message : String(error);
@@ -92,19 +92,28 @@ export class Harness {
92
92
  * and `runMoveScript` throw with a message pointing at `createLocal`.
93
93
  * `runViewFunction` works (read-only path).
94
94
  *
95
- * @param network - Network to fork (e.g. `"testnet"`).
95
+ * @param network - Network to fork. Built-ins: `"testnet"`, `"mainnet"`.
96
+ * Any other name requires `rpcUrl`.
96
97
  * @param apiKey - Optional Movement API key. When set, every upstream
97
98
  * request from the fork's `MovementApiClient` carries
98
99
  * `Authorization: Bearer <apiKey>`. Use for rate-limited public
99
100
  * endpoints or auth-gated nodes. The key stays in process memory
100
101
  * (not persisted to the fork's on-disk metadata).
102
+ * @param rpcUrl - Required when forking a non-built-in network.
103
+ * Ignored when a fork already exists on disk (the saved metadata's
104
+ * nodeUrl is reused).
101
105
  */
102
- static async createFork(network: string, apiKey?: string): Promise<Harness> {
106
+ static async createFork(
107
+ network: string,
108
+ apiKey?: string,
109
+ rpcUrl?: string
110
+ ): Promise<Harness> {
103
111
  const setupOpts: import("../types/config.js").LocalTestOptions = {
104
112
  mode: "fork",
105
113
  forkNetwork: network,
106
114
  };
107
115
  if (apiKey !== undefined) setupOpts.forkApiKey = apiKey;
116
+ if (rpcUrl !== undefined) setupOpts.forkRpcUrl = rpcUrl;
108
117
  const ctx = await setupLocalTesting(setupOpts);
109
118
  const init: HarnessInit = {
110
119
  mode: "fork",
@@ -1,6 +1,4 @@
1
- import { homedir } from "os";
2
- import { join } from "path";
3
- import { randomUUID } from "crypto";
1
+ import { PrivateKey, PrivateKeyVariants } from "@aptos-labs/ts-sdk";
4
2
  import type { MovehatRuntime } from "../types/runtime.js";
5
3
  import type {
6
4
  DeployCodeObjectOptions,
@@ -14,7 +12,7 @@ import {
14
12
  validateSafeName,
15
13
  type DeploymentInfo,
16
14
  } from "../core/deployments.js";
17
- import { validatePathSafety, validateProfileSafety } from "../core/shell.js";
15
+ import { validatePathSafety } from "../core/shell.js";
18
16
  import {
19
17
  CliExecutionError,
20
18
  ModuleAlreadyDeployedError,
@@ -22,12 +20,11 @@ import {
22
20
  } from "../errors.js";
23
21
  import { runCli } from "../utils/runCli.js";
24
22
  import { parseTxHash } from "../utils/parseCliOutput.js";
25
- import { logger } from "../ui/index.js";
23
+ import { logger, isVerbose } from "../ui/index.js";
26
24
  import {
27
- withYamlLock,
28
- addProfile,
29
- removeProfile,
30
- removeProfileSync,
25
+ writeTempKeyFile,
26
+ removeKeyFile,
27
+ removeKeyFileSyncBestEffort,
31
28
  ensureSignalHandler,
32
29
  cleanupCallbacks,
33
30
  } from "../core/movementProfile.js";
@@ -170,9 +167,7 @@ async function executeMovementMoveObject(
170
167
  }
171
168
 
172
169
  const dir = opts.packageDir || config.moveDir;
173
- const profile = `movehat-deploy-${randomUUID().slice(0, 8)}`;
174
170
  const safeDir = validatePathSafety(dir, "package directory");
175
- const safeProfile = validateProfileSafety(profile);
176
171
 
177
172
  logger.step(
178
173
  `${subcommand === "deploy-object" ? "Deploying" : "Upgrading"} module "${moduleName}" from ${dir}...`
@@ -211,32 +206,30 @@ async function executeMovementMoveObject(
211
206
  },
212
207
  { adapter: opts.adapter }
213
208
  );
214
- if (buildResult.stdout) console.log(buildResult.stdout.trim());
209
+ if (isVerbose() && buildResult.stdout) logger.info(buildResult.stdout.trim(), 2);
215
210
 
216
- // Strip `ed25519-priv-` prefix if present Movement CLI expects the
217
- // raw hex.
218
- let cleanPrivateKey = config.privateKey;
219
- if (cleanPrivateKey.startsWith("ed25519-priv-")) {
220
- cleanPrivateKey = cleanPrivateKey.replace("ed25519-priv-", "");
221
- }
211
+ // Format the private key into AIP-80 shape so the Movement CLI
212
+ // doesn't emit its raw-hex deprecation warning. `formatPrivateKey`
213
+ // is idempotent for already-prefixed inputs.
214
+ const formattedPrivateKey = PrivateKey.formatPrivateKey(
215
+ config.privateKey,
216
+ PrivateKeyVariants.Ed25519,
217
+ );
222
218
 
223
- const movementConfigPath = join(homedir(), ".aptos", "config.yaml");
219
+ // Pass the private key via a 0o600 temp file (--private-key-file)
220
+ // and the on-chain address via --sender-account. This avoids the
221
+ // CLI's profile-yaml lookup entirely — no CWD / HOME / .aptos /
222
+ // .movement dance, no CLI-variant dependency.
223
+ const keyFilePath = writeTempKeyFile(formattedPrivateKey);
224
224
 
225
- // Register SIGINT-safe sync cleanup BEFORE writing the key (same
226
- // pattern as Publisher closes bug #36).
225
+ // Register SIGINT-safe sync cleanup BEFORE invoking the CLI so
226
+ // the private key never persists on disk after an abnormal exit.
227
+ // The signal-handler path uses the best-effort variant because the
228
+ // event loop is dead and we cannot logger.warning.
227
229
  ensureSignalHandler();
228
- const syncCleanup = () => removeProfileSync(movementConfigPath, profile);
230
+ const syncCleanup = () => removeKeyFileSyncBestEffort(keyFilePath);
229
231
  cleanupCallbacks.add(syncCleanup);
230
232
 
231
- await withYamlLock(() =>
232
- addProfile(movementConfigPath, profile, {
233
- private_key: cleanPrivateKey,
234
- public_key: account.publicKey.toString(),
235
- account: deployerAddress,
236
- rest_url: config.rpc,
237
- })
238
- );
239
-
240
233
  let deployOut = "";
241
234
  try {
242
235
  logger.step(
@@ -258,8 +251,10 @@ async function executeMovementMoveObject(
258
251
  safeDir,
259
252
  "--url",
260
253
  config.rpc,
261
- "--profile",
262
- safeProfile,
254
+ "--private-key-file",
255
+ keyFilePath,
256
+ "--sender-account",
257
+ deployerAddress,
263
258
  "--assume-yes",
264
259
  ...includedArtifacts,
265
260
  ...namedAddrArgs,
@@ -270,21 +265,23 @@ async function executeMovementMoveObject(
270
265
  { adapter: opts.adapter }
271
266
  );
272
267
  deployOut = result.stdout;
273
- if (result.stdout) console.log(result.stdout.trim());
274
- if (result.stderr) console.error(result.stderr.trim());
268
+ // Both streams gated behind isVerbose(); see §9 — stream channel
269
+ // is not by itself a failure signal. Real failures throw via
270
+ // CliExecutionError and are surfaced from the catch below.
271
+ if (isVerbose() && result.stdout) logger.info(result.stdout.trim(), 2);
272
+ if (isVerbose() && result.stderr) logger.info(result.stderr.trim(), 2);
275
273
  } finally {
276
- // Best-effort profile removal. CRITICAL: catch + log instead of
277
- // re-throwing an await-in-finally that throws would clobber the
278
- // try block's success/error (the bug-#37 lesson from Publisher).
279
- await withYamlLock(() => removeProfile(movementConfigPath, profile)).catch(
280
- (err) => {
281
- const cleanupMsg = err instanceof Error ? err.message : String(err);
282
- logger.warning(
283
- `Failed to remove deploy profile "${profile}" from ${movementConfigPath}: ${cleanupMsg}. ` +
284
- `Run 'movement config delete-profile --profile ${profile}' to clean up manually.`
285
- );
286
- }
287
- );
274
+ // Unlink via the observable helper emit a warning if the file
275
+ // could not be removed AND still exists on disk (private key
276
+ // would persist silently otherwise). ENOENT and races are
277
+ // treated as benign success.
278
+ const cleanupErr = removeKeyFile(keyFilePath);
279
+ if (cleanupErr) {
280
+ logger.warning(
281
+ `Failed to remove temp key file '${keyFilePath}': ${cleanupErr.message}. ` +
282
+ `The file has mode 0o600 but should be removed manually: rm ${keyFilePath}`
283
+ );
284
+ }
288
285
  cleanupCallbacks.delete(syncCleanup);
289
286
  }
290
287
 
@@ -342,7 +339,7 @@ async function executeMovementMoveObject(
342
339
  throw error;
343
340
  }
344
341
  if (error instanceof CliExecutionError) {
345
- if (error.stdoutPreview) console.log(error.stdoutPreview);
342
+ if (error.stdoutPreview) logger.info(error.stdoutPreview, 2);
346
343
  logger.error(
347
344
  `Failed to ${subcommand === "deploy-object" ? "deploy" : "upgrade"} module: ${error.message}\n${error.stderr}`
348
345
  );
@@ -1,22 +1,20 @@
1
1
  import { existsSync } from "fs";
2
- import { homedir } from "os";
3
- import { extname, join } from "path";
4
- import { randomUUID } from "crypto";
2
+ import { extname } from "path";
3
+ import { PrivateKey, PrivateKeyVariants } from "@aptos-labs/ts-sdk";
5
4
  import type { MovehatRuntime } from "../types/runtime.js";
6
5
  import type {
7
6
  RunMoveScriptOptions,
8
7
  MoveScriptResult,
9
8
  } from "../types/harness.js";
10
- import { validatePathSafety, validateProfileSafety } from "../core/shell.js";
9
+ import { validatePathSafety } from "../core/shell.js";
11
10
  import { CliExecutionError } from "../errors.js";
12
11
  import { runCli } from "../utils/runCli.js";
13
12
  import { parseTxHash } from "../utils/parseCliOutput.js";
14
- import { logger } from "../ui/index.js";
13
+ import { logger, isVerbose } from "../ui/index.js";
15
14
  import {
16
- withYamlLock,
17
- addProfile,
18
- removeProfile,
19
- removeProfileSync,
15
+ writeTempKeyFile,
16
+ removeKeyFile,
17
+ removeKeyFileSyncBestEffort,
20
18
  ensureSignalHandler,
21
19
  cleanupCallbacks,
22
20
  } from "../core/movementProfile.js";
@@ -29,9 +27,9 @@ import {
29
27
  * - `.mv` compiled bytecode → `--compiled-script-path`
30
28
  *
31
29
  * Reuses Publisher's security model via the shared `movementProfile`
32
- * helpers: per-deploy unique profile, atomic 0o600 yaml writes under
33
- * the mutex, SIGINT-safe sync cleanup, `--profile` auth (key never
34
- * appears in `ps` output).
30
+ * helpers: per-invocation temp key file (0o600), SIGINT-safe sync
31
+ * cleanup, `--private-key-file` auth (key never appears in `ps`
32
+ * output or in the user's `~/.aptos/config.yaml`).
35
33
  *
36
34
  * Returns {@link MoveScriptResult}. `txHash` is guaranteed; `success`
37
35
  * and `vmStatus` are best-effort parsed from the CLI's Result JSON.
@@ -70,8 +68,6 @@ export async function runMoveScript(
70
68
  }
71
69
 
72
70
  const safeScriptPath = validatePathSafety(options.scriptPath, "script path");
73
- const profile = `movehat-script-${randomUUID().slice(0, 8)}`;
74
- const safeProfile = validateProfileSafety(profile);
75
71
 
76
72
  logger.step(
77
73
  `Running Move script '${options.scriptPath}' on ${config.network}...`
@@ -80,26 +76,28 @@ export async function runMoveScript(
80
76
  try {
81
77
  const deployerAddress = account.accountAddress.toString();
82
78
 
83
- let cleanPrivateKey = config.privateKey;
84
- if (cleanPrivateKey.startsWith("ed25519-priv-")) {
85
- cleanPrivateKey = cleanPrivateKey.replace("ed25519-priv-", "");
86
- }
79
+ // Format the private key into AIP-80 shape before writing to the
80
+ // temp key file. `formatPrivateKey` is idempotent for already-
81
+ // prefixed inputs.
82
+ const formattedPrivateKey = PrivateKey.formatPrivateKey(
83
+ config.privateKey,
84
+ PrivateKeyVariants.Ed25519,
85
+ );
87
86
 
88
- const movementConfigPath = join(homedir(), ".aptos", "config.yaml");
87
+ // Pass the private key via a 0o600 temp file (--private-key-file)
88
+ // and the on-chain address via --sender-account. Avoids the CLI's
89
+ // profile-yaml lookup chain entirely (no CWD / HOME / .aptos /
90
+ // .movement dance, no CLI-variant dependency).
91
+ const keyFilePath = writeTempKeyFile(formattedPrivateKey);
89
92
 
93
+ // SIGINT-safe sync cleanup BEFORE the CLI call so the private key
94
+ // never persists on disk after an abnormal exit. The signal-handler
95
+ // path uses the best-effort variant because the event loop is dead
96
+ // and we cannot logger.warning.
90
97
  ensureSignalHandler();
91
- const syncCleanup = () => removeProfileSync(movementConfigPath, profile);
98
+ const syncCleanup = () => removeKeyFileSyncBestEffort(keyFilePath);
92
99
  cleanupCallbacks.add(syncCleanup);
93
100
 
94
- await withYamlLock(() =>
95
- addProfile(movementConfigPath, profile, {
96
- private_key: cleanPrivateKey,
97
- public_key: account.publicKey.toString(),
98
- account: deployerAddress,
99
- rest_url: config.rpc,
100
- })
101
- );
102
-
103
101
  let scriptOut = "";
104
102
  try {
105
103
  const typeArgsFragment: string[] =
@@ -117,8 +115,10 @@ export async function runMoveScript(
117
115
  args: [
118
116
  "move",
119
117
  "run-script",
120
- "--profile",
121
- safeProfile,
118
+ "--private-key-file",
119
+ keyFilePath,
120
+ "--sender-account",
121
+ deployerAddress,
122
122
  "--url",
123
123
  config.rpc,
124
124
  "--assume-yes",
@@ -132,18 +132,22 @@ export async function runMoveScript(
132
132
  { adapter: options.adapter }
133
133
  );
134
134
  scriptOut = result.stdout;
135
- if (result.stdout) console.log(result.stdout.trim());
136
- if (result.stderr) console.error(result.stderr.trim());
135
+ // Both streams gated behind isVerbose(); Movement CLI uses
136
+ // stderr for progress messages too. Real failures throw via
137
+ // CliExecutionError and are surfaced from the catch below.
138
+ if (isVerbose() && result.stdout) logger.info(result.stdout.trim(), 2);
139
+ if (isVerbose() && result.stderr) logger.info(result.stderr.trim(), 2);
137
140
  } finally {
138
- await withYamlLock(() => removeProfile(movementConfigPath, profile)).catch(
139
- (err) => {
140
- const cleanupMsg = err instanceof Error ? err.message : String(err);
141
- logger.warning(
142
- `Failed to remove script profile "${profile}" from ${movementConfigPath}: ${cleanupMsg}. ` +
143
- `Run 'movement config delete-profile --profile ${profile}' to clean up manually.`
144
- );
145
- }
146
- );
141
+ // Observable cleanup emit a warning if the unlink failed and
142
+ // the file is still on disk (private key would persist silently
143
+ // otherwise).
144
+ const cleanupErr = removeKeyFile(keyFilePath);
145
+ if (cleanupErr) {
146
+ logger.warning(
147
+ `Failed to remove temp key file '${keyFilePath}': ${cleanupErr.message}. ` +
148
+ `The file has mode 0o600 but should be removed manually: rm ${keyFilePath}`
149
+ );
150
+ }
147
151
  cleanupCallbacks.delete(syncCleanup);
148
152
  }
149
153
 
@@ -166,7 +170,7 @@ export async function runMoveScript(
166
170
  return out;
167
171
  } catch (error) {
168
172
  if (error instanceof CliExecutionError) {
169
- if (error.stdoutPreview) console.log(error.stdoutPreview);
173
+ if (error.stdoutPreview) logger.info(error.stdoutPreview, 2);
170
174
  logger.error(`Failed to run Move script: ${error.message}\n${error.stderr}`);
171
175
  } else {
172
176
  const err = error instanceof Error ? error : new Error(String(error));