movehat 0.2.2 → 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.
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +19 -10
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/test.js +12 -19
- package/dist/commands/test.js.map +1 -1
- package/dist/core/Publisher.d.ts.map +1 -1
- package/dist/core/Publisher.js +20 -14
- package/dist/core/Publisher.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +8 -5
- package/dist/core/config.js.map +1 -1
- package/dist/core/deployments.d.ts.map +1 -1
- package/dist/core/deployments.js +4 -2
- package/dist/core/deployments.js.map +1 -1
- package/dist/fork/manager.js +10 -10
- package/dist/fork/manager.js.map +1 -1
- package/dist/fork/server.d.ts.map +1 -1
- package/dist/fork/server.js +21 -15
- package/dist/fork/server.js.map +1 -1
- package/dist/fork/test.d.ts.map +1 -1
- package/dist/fork/test.js +3 -2
- package/dist/fork/test.js.map +1 -1
- package/dist/harness/codeObject.js +11 -8
- package/dist/harness/codeObject.js.map +1 -1
- package/dist/harness/script.d.ts.map +1 -1
- package/dist/harness/script.js +9 -6
- package/dist/harness/script.js.map +1 -1
- package/dist/helpers/setupLocalTesting.js +3 -3
- package/dist/helpers/setupLocalTesting.js.map +1 -1
- package/dist/node/LocalNodeManager.d.ts.map +1 -1
- package/dist/node/LocalNodeManager.js +60 -22
- package/dist/node/LocalNodeManager.js.map +1 -1
- package/dist/node/__tests__/LocalNodeManager.test.js +110 -11
- package/dist/node/__tests__/LocalNodeManager.test.js.map +1 -1
- package/dist/ui/__tests__/logger.test.d.ts +2 -0
- package/dist/ui/__tests__/logger.test.d.ts.map +1 -0
- package/dist/ui/__tests__/logger.test.js +75 -0
- package/dist/ui/__tests__/logger.test.js.map +1 -0
- package/dist/ui/formatters.d.ts +0 -16
- package/dist/ui/formatters.d.ts.map +1 -1
- package/dist/ui/formatters.js +1 -1
- package/dist/ui/formatters.js.map +1 -1
- package/dist/ui/logger.d.ts +41 -0
- package/dist/ui/logger.d.ts.map +1 -1
- package/dist/ui/logger.js +49 -0
- package/dist/ui/logger.js.map +1 -1
- package/dist/ui/spinner.d.ts +25 -0
- package/dist/ui/spinner.d.ts.map +1 -1
- package/dist/ui/spinner.js +44 -0
- package/dist/ui/spinner.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +4 -0
- package/src/commands/compile.ts +24 -15
- package/src/commands/test.ts +12 -19
- package/src/core/Publisher.ts +49 -34
- package/src/core/config.ts +9 -6
- package/src/core/deployments.ts +5 -4
- package/src/fork/manager.ts +10 -10
- package/src/fork/server.ts +21 -15
- package/src/fork/test.ts +3 -2
- package/src/harness/codeObject.ts +8 -5
- package/src/harness/script.ts +7 -4
- package/src/helpers/setupLocalTesting.ts +3 -3
- package/src/node/LocalNodeManager.ts +63 -24
- package/src/node/__tests__/LocalNodeManager.test.ts +140 -14
- package/src/ui/__tests__/logger.test.ts +89 -0
- package/src/ui/formatters.ts +1 -1
- package/src/ui/logger.ts +62 -0
- package/src/ui/spinner.ts +47 -0
package/dist/ui/spinner.d.ts
CHANGED
|
@@ -65,6 +65,31 @@ export declare const spinner: (options: SpinnerOptions) => Ora;
|
|
|
65
65
|
* );
|
|
66
66
|
*/
|
|
67
67
|
export declare const withSpinner: <T>(startText: string, task: () => Promise<T>, successText?: string, errorText?: string, indent?: number) => Promise<T>;
|
|
68
|
+
/**
|
|
69
|
+
* Execute async task with a spinner that updates its label with
|
|
70
|
+
* elapsed seconds while the task runs. Use for long-running phases
|
|
71
|
+
* (local node startup, publish + tx wait) where the user wants
|
|
72
|
+
* visible progress feedback in lieu of subprocess chatter.
|
|
73
|
+
*
|
|
74
|
+
* Pairs with the `§9` console-UX convention: any phase that
|
|
75
|
+
* empirically takes ≥3s in normal use should wrap its body in
|
|
76
|
+
* `withTimedSpinner` so the terminal never goes silent while work
|
|
77
|
+
* happens.
|
|
78
|
+
*
|
|
79
|
+
* @param label - Stable label shown next to the spinner (e.g. "Starting node")
|
|
80
|
+
* @param task - Async function to execute
|
|
81
|
+
* @param indent - Number of spaces to indent (default: 0)
|
|
82
|
+
* @returns Promise resolving to task result
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* await withTimedSpinner('Starting local node', async () => {
|
|
86
|
+
* await this.waitForReady(60_000);
|
|
87
|
+
* });
|
|
88
|
+
* // Renders: ⠋ Starting local node — 0.0s ... ⠼ Starting local node — 14.2s
|
|
89
|
+
* // On success: ✔ Starting local node (14.2s)
|
|
90
|
+
* // On error: ✖ <error.message>
|
|
91
|
+
*/
|
|
92
|
+
export declare const withTimedSpinner: <T>(label: string, task: () => Promise<T>, indent?: number) => Promise<T>;
|
|
68
93
|
/**
|
|
69
94
|
* Spinner chain for sequential operations
|
|
70
95
|
* Manages multiple spinners in sequence
|
package/dist/ui/spinner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../src/ui/spinner.ts"],"names":[],"mappings":"AAAA,OAAY,EAAE,KAAK,GAAG,EAAE,KAAK,OAAO,IAAI,UAAU,EAAE,MAAM,KAAK,CAAC;AAGhE;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEvG;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAChC,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,OAAO,GAAI,SAAS,cAAc,KAAG,GAcjD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,WAAW,GAAU,CAAC,EACjC,WAAW,MAAM,EACjB,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,EACtB,cAAc,MAAM,EACpB,YAAY,MAAM,EAClB,SAAQ,MAAU,KACjB,OAAO,CAAC,CAAC,CAYX,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAE1E;;OAEG;IACH,QAAQ,IAAI,IAAI,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,QAAO,YA0BrC,CAAC"}
|
|
1
|
+
{"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../src/ui/spinner.ts"],"names":[],"mappings":"AAAA,OAAY,EAAE,KAAK,GAAG,EAAE,KAAK,OAAO,IAAI,UAAU,EAAE,MAAM,KAAK,CAAC;AAGhE;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEvG;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAChC,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,OAAO,GAAI,SAAS,cAAc,KAAG,GAcjD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,WAAW,GAAU,CAAC,EACjC,WAAW,MAAM,EACjB,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,EACtB,cAAc,MAAM,EACpB,YAAY,MAAM,EAClB,SAAQ,MAAU,KACjB,OAAO,CAAC,CAAC,CAYX,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gBAAgB,GAAU,CAAC,EACtC,OAAO,MAAM,EACb,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,EACtB,SAAQ,MAAU,KACjB,OAAO,CAAC,CAAC,CAiBX,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAE1E;;OAEG;IACH,QAAQ,IAAI,IAAI,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,QAAO,YA0BrC,CAAC"}
|
package/dist/ui/spinner.js
CHANGED
|
@@ -72,6 +72,50 @@ export const withSpinner = async (startText, task, successText, errorText, inden
|
|
|
72
72
|
throw error;
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* Execute async task with a spinner that updates its label with
|
|
77
|
+
* elapsed seconds while the task runs. Use for long-running phases
|
|
78
|
+
* (local node startup, publish + tx wait) where the user wants
|
|
79
|
+
* visible progress feedback in lieu of subprocess chatter.
|
|
80
|
+
*
|
|
81
|
+
* Pairs with the `§9` console-UX convention: any phase that
|
|
82
|
+
* empirically takes ≥3s in normal use should wrap its body in
|
|
83
|
+
* `withTimedSpinner` so the terminal never goes silent while work
|
|
84
|
+
* happens.
|
|
85
|
+
*
|
|
86
|
+
* @param label - Stable label shown next to the spinner (e.g. "Starting node")
|
|
87
|
+
* @param task - Async function to execute
|
|
88
|
+
* @param indent - Number of spaces to indent (default: 0)
|
|
89
|
+
* @returns Promise resolving to task result
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* await withTimedSpinner('Starting local node', async () => {
|
|
93
|
+
* await this.waitForReady(60_000);
|
|
94
|
+
* });
|
|
95
|
+
* // Renders: ⠋ Starting local node — 0.0s ... ⠼ Starting local node — 14.2s
|
|
96
|
+
* // On success: ✔ Starting local node (14.2s)
|
|
97
|
+
* // On error: ✖ <error.message>
|
|
98
|
+
*/
|
|
99
|
+
export const withTimedSpinner = async (label, task, indent = 0) => {
|
|
100
|
+
const start = Date.now();
|
|
101
|
+
const spin = spinner({ text: `${label} — 0.0s`, indent });
|
|
102
|
+
const timer = setInterval(() => {
|
|
103
|
+
spin.text = `${label} — ${((Date.now() - start) / 1000).toFixed(1)}s`;
|
|
104
|
+
}, 500);
|
|
105
|
+
try {
|
|
106
|
+
const result = await task();
|
|
107
|
+
spin.succeed(`${label} (${((Date.now() - start) / 1000).toFixed(1)}s)`);
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
112
|
+
spin.fail(errMsg);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
clearInterval(timer);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
75
119
|
/**
|
|
76
120
|
* Create a sequential spinner chain
|
|
77
121
|
* Useful for multi-step processes like initialization
|
package/dist/ui/spinner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spinner.js","sourceRoot":"","sources":["../../src/ui/spinner.ts"],"names":[],"mappings":"AAAA,OAAO,GAA6C,MAAM,KAAK,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAqB7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,OAAuB,EAAO,EAAE;IACtD,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC;IAEzE,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAExC,MAAM,UAAU,GAAe;QAC7B,IAAI,EAAE,YAAY,GAAG,IAAI;QACzB,KAAK;QACL,OAAO;QACP,6DAA6D;QAC7D,SAAS,EAAE,cAAc,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;KAC7D,CAAC;IAEF,OAAO,GAAG,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,CAAC;AACjC,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAC9B,SAAiB,EACjB,IAAsB,EACtB,WAAoB,EACpB,SAAkB,EAClB,SAAiB,CAAC,EACN,EAAE;IACd,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;IAElD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;QAC/D,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,WAAW,MAAM,EAAE,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAkBF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAiB,EAAE;IACnD,IAAI,cAAc,GAAe,IAAI,CAAC;IAEtC,OAAO;QACL,KAAK,CAAC,GAAG,CACP,IAAY,EACZ,IAAsB,EACtB,SAAiB,CAAC;YAElB,cAAc,GAAG,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAC3C,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;gBAC5B,cAAc,CAAC,OAAO,EAAE,CAAC;gBACzB,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,cAAc,CAAC,IAAI,EAAE,CAAC;gBACtB,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QAED,QAAQ;YACN,IAAI,cAAc,EAAE,CAAC;gBACnB,cAAc,CAAC,IAAI,EAAE,CAAC;YACxB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"spinner.js","sourceRoot":"","sources":["../../src/ui/spinner.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -50,6 +50,7 @@ program
|
|
|
50
50
|
.version(version)
|
|
51
51
|
.option('--network <name>', 'Network to use (testnet, mainnet, local, etc.)')
|
|
52
52
|
.option('--redeploy', 'Force redeploy even if already deployed')
|
|
53
|
+
.option('-v, --verbose', 'Show subprocess output (movement node, aptos move) for debugging')
|
|
53
54
|
.hook('preAction', (thisCommand) => {
|
|
54
55
|
// Store network option in environment for commands to access
|
|
55
56
|
const options = thisCommand.opts();
|
|
@@ -59,6 +60,9 @@ program
|
|
|
59
60
|
if (options.redeploy) {
|
|
60
61
|
process.env.MH_CLI_REDEPLOY = 'true';
|
|
61
62
|
}
|
|
63
|
+
if (options.verbose) {
|
|
64
|
+
process.env.MOVEHAT_VERBOSE = '1';
|
|
65
|
+
}
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
program
|
package/src/commands/compile.ts
CHANGED
|
@@ -2,7 +2,8 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { loadUserConfig } from "../core/config.js";
|
|
4
4
|
import { validatePathSafety } from "../core/shell.js";
|
|
5
|
-
import { logger } from "../ui/index.js";
|
|
5
|
+
import { logger, isVerbose } from "../ui/index.js";
|
|
6
|
+
import { withSpinner } from "../ui/spinner.js";
|
|
6
7
|
import { runCli } from "../utils/runCli.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -195,25 +196,33 @@ async function runMovementBuild(
|
|
|
195
196
|
args: readonly string[],
|
|
196
197
|
cwd: string
|
|
197
198
|
): Promise<void> {
|
|
198
|
-
// Use throwOnNonZeroExit:false so we can
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
const result = await
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
199
|
+
// Use throwOnNonZeroExit:false so we can route stdout/stderr through
|
|
200
|
+
// the §9 verbosity gate ourselves. On failure we surface everything;
|
|
201
|
+
// on success the chatter is hidden unless isVerbose().
|
|
202
|
+
const result = await withSpinner("Compiling Move package", () =>
|
|
203
|
+
runCli(
|
|
204
|
+
{
|
|
205
|
+
command: "movement",
|
|
206
|
+
args,
|
|
207
|
+
cwd,
|
|
208
|
+
timeoutMs: 120000, // 2 minutes for git dependency downloads
|
|
209
|
+
},
|
|
210
|
+
{ throwOnNonZeroExit: false }
|
|
211
|
+
),
|
|
209
212
|
);
|
|
210
213
|
|
|
211
|
-
if (result.stdout) console.log(result.stdout.trim());
|
|
212
|
-
if (result.stderr) console.error(result.stderr.trim());
|
|
213
|
-
|
|
214
214
|
if (result.exitCode !== 0) {
|
|
215
|
+
// Build failed — show the user everything we have so they can debug.
|
|
216
|
+
if (result.stdout) logger.plain(result.stdout.trim());
|
|
217
|
+
if (result.stderr) logger.plain(result.stderr.trim());
|
|
215
218
|
throw new Error(`movement move build exited with code ${result.exitCode}`);
|
|
216
219
|
}
|
|
220
|
+
|
|
221
|
+
// Success path: route output through the verbosity gate.
|
|
222
|
+
if (isVerbose()) {
|
|
223
|
+
if (result.stdout) logger.info(result.stdout.trim(), 2);
|
|
224
|
+
if (result.stderr) logger.info(result.stderr.trim(), 2);
|
|
225
|
+
}
|
|
217
226
|
}
|
|
218
227
|
|
|
219
228
|
/**
|
package/src/commands/test.ts
CHANGED
|
@@ -97,8 +97,7 @@ async function showTestMenu(): Promise<TestType | undefined> {
|
|
|
97
97
|
*/
|
|
98
98
|
async function runMoveTestsOnly(filter?: string): Promise<void> {
|
|
99
99
|
logger.newline();
|
|
100
|
-
|
|
101
|
-
console.log(colors.muted("─".repeat(50)));
|
|
100
|
+
logger.phase("Move Unit Tests");
|
|
102
101
|
logger.newline();
|
|
103
102
|
|
|
104
103
|
try {
|
|
@@ -118,8 +117,7 @@ async function runMoveTestsOnly(filter?: string): Promise<void> {
|
|
|
118
117
|
*/
|
|
119
118
|
async function runTypeScriptTestsOnly(watch: boolean = false): Promise<void> {
|
|
120
119
|
logger.newline();
|
|
121
|
-
|
|
122
|
-
console.log(colors.muted("─".repeat(50)));
|
|
120
|
+
logger.phase("TypeScript Integration Tests");
|
|
123
121
|
|
|
124
122
|
if (!watch) {
|
|
125
123
|
logger.newline();
|
|
@@ -146,13 +144,11 @@ async function runTypeScriptTestsOnly(watch: boolean = false): Promise<void> {
|
|
|
146
144
|
*/
|
|
147
145
|
async function runAllTests(filter?: string): Promise<void> {
|
|
148
146
|
logger.newline();
|
|
149
|
-
|
|
150
|
-
console.log(colors.muted("═".repeat(50)));
|
|
147
|
+
logger.phase("Running All Tests");
|
|
151
148
|
|
|
152
149
|
// Section 1: Move Tests
|
|
153
150
|
logger.newline();
|
|
154
|
-
|
|
155
|
-
console.log(colors.muted("─".repeat(50)));
|
|
151
|
+
logger.phase("1. Move Unit Tests");
|
|
156
152
|
logger.newline();
|
|
157
153
|
|
|
158
154
|
try {
|
|
@@ -163,28 +159,25 @@ async function runAllTests(filter?: string): Promise<void> {
|
|
|
163
159
|
} catch (error) {
|
|
164
160
|
logger.newline();
|
|
165
161
|
logger.error("Move tests failed");
|
|
166
|
-
|
|
162
|
+
logger.divider();
|
|
167
163
|
process.exit(1);
|
|
168
164
|
}
|
|
169
165
|
|
|
170
166
|
// Section 2: TypeScript Tests
|
|
171
167
|
logger.newline();
|
|
172
|
-
|
|
173
|
-
logger.newline();
|
|
174
|
-
console.log(`${colors.brandBright("2.")} ${colors.bold("TypeScript Integration Tests")}`);
|
|
175
|
-
console.log(colors.muted("─".repeat(50)));
|
|
168
|
+
logger.phase("2. TypeScript Integration Tests");
|
|
176
169
|
logger.newline();
|
|
177
170
|
|
|
178
171
|
try {
|
|
179
172
|
await runTypeScriptTests(false);
|
|
180
173
|
logger.newline();
|
|
181
|
-
|
|
174
|
+
logger.divider();
|
|
182
175
|
logger.newline();
|
|
183
176
|
logger.success("All tests passed!");
|
|
184
177
|
logger.newline();
|
|
185
178
|
} catch (error) {
|
|
186
179
|
logger.newline();
|
|
187
|
-
|
|
180
|
+
logger.divider();
|
|
188
181
|
const message = error instanceof Error ? error.message : String(error);
|
|
189
182
|
logger.error(message);
|
|
190
183
|
process.exit(1);
|
|
@@ -198,8 +191,8 @@ async function runTypeScriptTests(watch: boolean = false): Promise<void> {
|
|
|
198
191
|
const testDir = join(process.cwd(), "tests");
|
|
199
192
|
|
|
200
193
|
if (!existsSync(testDir)) {
|
|
201
|
-
|
|
202
|
-
|
|
194
|
+
logger.plain(`${colors.muted(symbols.info)} No TypeScript tests found ${colors.muted("(tests/ directory not found)")}`);
|
|
195
|
+
logger.plain(` ${colors.muted("Skipping TypeScript tests...")}`);
|
|
203
196
|
logger.newline();
|
|
204
197
|
return;
|
|
205
198
|
}
|
|
@@ -208,7 +201,7 @@ async function runTypeScriptTests(watch: boolean = false): Promise<void> {
|
|
|
208
201
|
|
|
209
202
|
if (!existsSync(mochaPath)) {
|
|
210
203
|
logger.error("Mocha not found in project dependencies");
|
|
211
|
-
|
|
204
|
+
logger.plain(` ${colors.muted("Install it with:")} ${colors.info("npm install --save-dev mocha")}`);
|
|
212
205
|
throw new Error("Mocha not found");
|
|
213
206
|
}
|
|
214
207
|
|
|
@@ -232,7 +225,7 @@ async function runTypeScriptTests(watch: boolean = false): Promise<void> {
|
|
|
232
225
|
process.exit(1);
|
|
233
226
|
});
|
|
234
227
|
|
|
235
|
-
|
|
228
|
+
logger.plain(`${colors.info(symbols.info)} Watch mode active. Press Ctrl+C to exit.`);
|
|
236
229
|
logger.newline();
|
|
237
230
|
return;
|
|
238
231
|
}
|
package/src/core/Publisher.ts
CHANGED
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
import { validatePathSafety } from "./shell.js";
|
|
11
11
|
import { CliExecutionError, ModuleAlreadyDeployedError, PostPublishError } from "../errors.js";
|
|
12
12
|
import { runCli } from "../utils/runCli.js";
|
|
13
|
-
import { logger } from "../ui/index.js";
|
|
13
|
+
import { logger, isVerbose } from "../ui/index.js";
|
|
14
|
+
import { withSpinner } from "../ui/spinner.js";
|
|
14
15
|
import type { ChildProcessAdapter } from "../utils/childProcessAdapter.js";
|
|
15
16
|
import {
|
|
16
17
|
writeTempKeyFile,
|
|
@@ -122,19 +123,23 @@ export class Publisher {
|
|
|
122
123
|
: [];
|
|
123
124
|
|
|
124
125
|
// Build first with named addresses
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
const buildResult = await withSpinner(
|
|
127
|
+
"Building package",
|
|
128
|
+
() =>
|
|
129
|
+
runCli(
|
|
130
|
+
{
|
|
131
|
+
command: "movement",
|
|
132
|
+
args: ["move", "build", "--package-dir", safeDir, ...namedAddrArgs],
|
|
133
|
+
timeoutMs: 120000, // 2 minutes for git dependency downloads
|
|
134
|
+
},
|
|
135
|
+
{ adapter: this.deps.adapter }
|
|
136
|
+
),
|
|
133
137
|
);
|
|
134
|
-
if (
|
|
138
|
+
if (isVerbose() && buildResult.stdout) {
|
|
139
|
+
logger.info(buildResult.stdout.trim(), 2);
|
|
140
|
+
}
|
|
135
141
|
|
|
136
142
|
// Publish using direct parameters (avoid config file issues)
|
|
137
|
-
logger.step("Publishing to blockchain...");
|
|
138
143
|
|
|
139
144
|
// Format the private key into AIP-80 shape so the Movement CLI
|
|
140
145
|
// doesn't emit its raw-hex deprecation warning. `formatPrivateKey`
|
|
@@ -179,31 +184,41 @@ export class Publisher {
|
|
|
179
184
|
// stdout/stderr redaction still applies as defense in depth
|
|
180
185
|
// for any `ed25519-priv-…` substring that surfaces in CLI
|
|
181
186
|
// output (Movement CLI sometimes echoes the key on error).
|
|
182
|
-
const publishResult = await
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
187
|
+
const publishResult = await withSpinner(
|
|
188
|
+
"Publishing to blockchain",
|
|
189
|
+
() =>
|
|
190
|
+
runCli(
|
|
191
|
+
{
|
|
192
|
+
command: "movement",
|
|
193
|
+
args: [
|
|
194
|
+
"move",
|
|
195
|
+
"publish",
|
|
196
|
+
"--package-dir",
|
|
197
|
+
safeDir,
|
|
198
|
+
"--url",
|
|
199
|
+
config.rpc,
|
|
200
|
+
"--private-key-file",
|
|
201
|
+
keyFilePath,
|
|
202
|
+
"--sender-account",
|
|
203
|
+
deployerAddress,
|
|
204
|
+
"--assume-yes",
|
|
205
|
+
...namedAddrArgs,
|
|
206
|
+
],
|
|
207
|
+
timeoutMs: 120000, // 2 minutes for blockchain transactions
|
|
208
|
+
},
|
|
209
|
+
{ adapter: this.deps.adapter }
|
|
210
|
+
),
|
|
202
211
|
);
|
|
203
212
|
publishOut = publishResult.stdout;
|
|
204
213
|
publishErr = publishResult.stderr;
|
|
205
|
-
|
|
206
|
-
|
|
214
|
+
// Both stdout and stderr from the publish subprocess are gated
|
|
215
|
+
// behind isVerbose() — Movement CLI emits progress to both
|
|
216
|
+
// streams ("Compiling, may take a little while..."), so a
|
|
217
|
+
// visible stderr line is not by itself a failure signal. The
|
|
218
|
+
// surrounding withSpinner converts the runCli throw on real
|
|
219
|
+
// failure into the visible spinner.fail() output instead.
|
|
220
|
+
if (isVerbose() && publishOut) logger.info(publishOut.trim(), 2);
|
|
221
|
+
if (isVerbose() && publishErr) logger.info(publishErr.trim(), 2);
|
|
207
222
|
} finally {
|
|
208
223
|
// Unlink the temp key file via the observable cleanup helper.
|
|
209
224
|
// ENOENT and other already-gone outcomes are benign (null).
|
|
@@ -279,7 +294,7 @@ export class Publisher {
|
|
|
279
294
|
if (error instanceof CliExecutionError) {
|
|
280
295
|
// stdout/stderr are already redacted by runCli before reaching here,
|
|
281
296
|
// so this branch is safe to log verbatim.
|
|
282
|
-
if (error.stdoutPreview)
|
|
297
|
+
if (error.stdoutPreview) logger.info(error.stdoutPreview, 2);
|
|
283
298
|
logger.error(`Failed to publish module: ${error.message}\n${error.stderr}`);
|
|
284
299
|
} else {
|
|
285
300
|
// Preserve existing behaviour for non-CLI errors (filesystem write
|
package/src/core/config.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from "path";
|
|
|
3
3
|
import { existsSync, statSync } from "fs";
|
|
4
4
|
import { Account, Ed25519PrivateKey, PrivateKey, PrivateKeyVariants } from "@aptos-labs/ts-sdk";
|
|
5
5
|
import { MovehatConfig, MovehatUserConfig } from "../types/config.js";
|
|
6
|
+
import { logger } from "../ui/index.js";
|
|
6
7
|
|
|
7
8
|
interface ConfigCacheEntry {
|
|
8
9
|
mtimeMs: number;
|
|
@@ -132,7 +133,7 @@ export async function resolveNetworkConfig(
|
|
|
132
133
|
url: "https://testnet.movementnetwork.xyz/v1",
|
|
133
134
|
chainId: "testnet",
|
|
134
135
|
};
|
|
135
|
-
|
|
136
|
+
logger.info("testnet not found in config - using default Movement testnet configuration");
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
// Special case: Auto-generate config for local fork server
|
|
@@ -141,7 +142,7 @@ export async function resolveNetworkConfig(
|
|
|
141
142
|
url: "http://localhost:8080/v1",
|
|
142
143
|
chainId: "local",
|
|
143
144
|
};
|
|
144
|
-
|
|
145
|
+
logger.info("Local network not found in config - using default fork server configuration");
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
if (!networkConfig) {
|
|
@@ -187,8 +188,10 @@ export async function resolveNetworkConfig(
|
|
|
187
188
|
// 3. Deterministic = consistent test results
|
|
188
189
|
const testPrivateKey = "0x0000000000000000000000000000000000000000000000000000000000000001";
|
|
189
190
|
accounts = [testPrivateKey];
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
logger.newline();
|
|
192
|
+
logger.warning("[TESTNET] Using auto-generated test account (safe for testing only)");
|
|
193
|
+
logger.warning("[TESTNET] For mainnet, set PRIVATE_KEY in .env");
|
|
194
|
+
logger.newline();
|
|
192
195
|
} else {
|
|
193
196
|
// For any other network (especially mainnet), REQUIRE explicit configuration
|
|
194
197
|
// This prevents accidentally using the test key on production networks
|
|
@@ -273,8 +276,8 @@ function deriveAccountAddress(privateKeyHex: string | undefined): string {
|
|
|
273
276
|
// The private key may have come from several sources (network.accounts,
|
|
274
277
|
// global accounts, PRIVATE_KEY env, auto-generated testnet key). Keep
|
|
275
278
|
// the hint generic so it never points at the wrong source.
|
|
276
|
-
|
|
277
|
-
`
|
|
279
|
+
logger.warning(
|
|
280
|
+
`Could not derive account address from the resolved private key: ${
|
|
278
281
|
(err as Error).message
|
|
279
282
|
}. Verify the key configured for this network is a valid Ed25519 private key (with or without the "ed25519-priv-" prefix).`
|
|
280
283
|
);
|
package/src/core/deployments.ts
CHANGED
|
@@ -86,9 +86,9 @@ export function saveDeployment(deployment: DeploymentInfo): void {
|
|
|
86
86
|
`Deployment saved: deployments/${deployment.network}/${deployment.moduleName}.json`
|
|
87
87
|
);
|
|
88
88
|
} catch (error) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
90
|
+
logger.error(
|
|
91
|
+
`Failed to save deployment for ${deployment.moduleName} on ${deployment.network} at ${filePath}: ${msg}`
|
|
92
92
|
);
|
|
93
93
|
throw error;
|
|
94
94
|
}
|
|
@@ -110,7 +110,8 @@ export function loadDeployment(network: string, moduleName: string): DeploymentI
|
|
|
110
110
|
const content = readFileSync(filePath, "utf-8");
|
|
111
111
|
return JSON.parse(content) as DeploymentInfo;
|
|
112
112
|
} catch (error) {
|
|
113
|
-
|
|
113
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
114
|
+
logger.error(`Failed to load deployment for ${moduleName} on ${network}: ${msg}`);
|
|
114
115
|
return null;
|
|
115
116
|
}
|
|
116
117
|
}
|
package/src/fork/manager.ts
CHANGED
|
@@ -89,7 +89,7 @@ export class ForkManager {
|
|
|
89
89
|
|
|
90
90
|
this.storage.saveMetadata(this.metadata);
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
logger.success(`Fork initialized at ledger version ${ledgerInfo.ledger_version}`);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
@@ -123,7 +123,7 @@ export class ForkManager {
|
|
|
123
123
|
throw new Error('Fork not initialized. Call initialize() or load() first.');
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
logger.info(`Fetching account ${normalizedAddress} from network...`, 2);
|
|
127
127
|
const accountData = await this.apiClient.getAccount(normalizedAddress);
|
|
128
128
|
|
|
129
129
|
accountState = {
|
|
@@ -132,7 +132,7 @@ export class ForkManager {
|
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
this.storage.saveAccount(normalizedAddress, accountState);
|
|
135
|
-
|
|
135
|
+
logger.success(`Cached account ${normalizedAddress}`, 2);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
return accountState;
|
|
@@ -148,14 +148,14 @@ export class ForkManager {
|
|
|
148
148
|
throw new Error('Fork not initialized. Call initialize() or load() first.');
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
logger.info(`Fetching resource ${resourceType} for ${normalizedAddress}...`, 2);
|
|
152
152
|
|
|
153
153
|
try {
|
|
154
154
|
const resourceData = await this.apiClient.getAccountResource(normalizedAddress, resourceType);
|
|
155
155
|
resource = resourceData.data;
|
|
156
156
|
|
|
157
157
|
this.storage.saveResource(normalizedAddress, resourceType, resource);
|
|
158
|
-
|
|
158
|
+
logger.success(`Cached resource ${resourceType}`, 2);
|
|
159
159
|
} catch (error) {
|
|
160
160
|
const msg = error instanceof Error ? error.message : String(error);
|
|
161
161
|
if (msg.includes('404')) {
|
|
@@ -178,7 +178,7 @@ export class ForkManager {
|
|
|
178
178
|
throw new Error('Fork not initialized. Call initialize() or load() first.');
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
logger.info(`Fetching all resources for ${normalizedAddress}...`, 2);
|
|
182
182
|
const resourcesList = await this.apiClient.getAccountResources(normalizedAddress);
|
|
183
183
|
|
|
184
184
|
resources = {};
|
|
@@ -187,7 +187,7 @@ export class ForkManager {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
this.storage.saveAllResources(normalizedAddress, resources);
|
|
190
|
-
|
|
190
|
+
logger.success(`Cached ${Object.keys(resources).length} resources`, 2);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
return resources;
|
|
@@ -196,7 +196,7 @@ export class ForkManager {
|
|
|
196
196
|
async setResource(address: string, resourceType: string, data: unknown): Promise<void> {
|
|
197
197
|
const normalizedAddress = normalizeAddress(address);
|
|
198
198
|
this.storage.saveResource(normalizedAddress, resourceType, data);
|
|
199
|
-
|
|
199
|
+
logger.success(`Updated resource ${resourceType} for ${normalizedAddress}`, 2);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
/** Adds to the existing balance rather than replacing it. */
|
|
@@ -258,7 +258,7 @@ export class ForkManager {
|
|
|
258
258
|
this.storage.saveAccount(normalizedAddress, account);
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
logger.success(`Funded ${normalizedAddress} with ${amount} coins`, 2);
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
listAccounts(): string[] {
|
|
@@ -335,7 +335,7 @@ export class ForkManager {
|
|
|
335
335
|
};
|
|
336
336
|
|
|
337
337
|
this.storage.saveAccount(normalizedAddress, newAccount);
|
|
338
|
-
|
|
338
|
+
logger.success(`Created new account ${normalizedAddress}`, 2);
|
|
339
339
|
|
|
340
340
|
return newAccount;
|
|
341
341
|
}
|
package/src/fork/server.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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';
|
|
4
5
|
|
|
5
6
|
export interface ForkServerOptions {
|
|
6
7
|
/**
|
|
@@ -66,16 +67,17 @@ export class ForkServer {
|
|
|
66
67
|
this.forkManager.load();
|
|
67
68
|
const metadata = this.forkManager.getMetadata();
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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);
|
|
74
76
|
|
|
75
77
|
this.server = http.createServer((req, res) => {
|
|
76
78
|
this.handleRequest(req, res).catch((error) => {
|
|
77
79
|
// Log full error server-side for diagnostics
|
|
78
|
-
|
|
80
|
+
logger.error(`Error handling request: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
81
|
|
|
80
82
|
// Only send response if headers haven't been sent yet
|
|
81
83
|
if (!res.headersSent) {
|
|
@@ -124,13 +126,15 @@ export class ForkServer {
|
|
|
124
126
|
: isIpv6
|
|
125
127
|
? `[${this.host}]`
|
|
126
128
|
: this.host;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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);
|
|
130
133
|
if (this.host === '0.0.0.0') {
|
|
131
|
-
|
|
134
|
+
logger.warning("Server is bound to 0.0.0.0 — fork state is reachable from the LAN.", 2);
|
|
132
135
|
}
|
|
133
|
-
|
|
136
|
+
logger.newline();
|
|
137
|
+
logger.info("Press Ctrl+C to stop");
|
|
134
138
|
resolve();
|
|
135
139
|
});
|
|
136
140
|
});
|
|
@@ -143,7 +147,8 @@ export class ForkServer {
|
|
|
143
147
|
return new Promise((resolve) => {
|
|
144
148
|
if (this.server) {
|
|
145
149
|
this.server.close(() => {
|
|
146
|
-
|
|
150
|
+
logger.newline();
|
|
151
|
+
logger.success("Fork Server stopped");
|
|
147
152
|
resolve();
|
|
148
153
|
});
|
|
149
154
|
} else {
|
|
@@ -172,8 +177,9 @@ export class ForkServer {
|
|
|
172
177
|
const url = new URL(req.url || '/', `http://localhost:${this.port}`);
|
|
173
178
|
const pathname = url.pathname;
|
|
174
179
|
|
|
175
|
-
// Log request
|
|
176
|
-
|
|
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}`);
|
|
177
183
|
|
|
178
184
|
this.applyCors(req, res);
|
|
179
185
|
|
|
@@ -216,7 +222,7 @@ export class ForkServer {
|
|
|
216
222
|
}
|
|
217
223
|
} catch (error) {
|
|
218
224
|
// Log full error server-side for diagnostics
|
|
219
|
-
|
|
225
|
+
logger.error(`Error handling request: ${error instanceof Error ? error.message : String(error)}`);
|
|
220
226
|
|
|
221
227
|
// Send generic error to client (don't expose internal details)
|
|
222
228
|
this.sendError(res, 500, 'Internal server error');
|