movehat 0.2.2 → 0.2.4

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 (74) hide show
  1. package/dist/cli.js +4 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/compile.d.ts.map +1 -1
  4. package/dist/commands/compile.js +19 -10
  5. package/dist/commands/compile.js.map +1 -1
  6. package/dist/commands/test.js +12 -19
  7. package/dist/commands/test.js.map +1 -1
  8. package/dist/core/Publisher.d.ts.map +1 -1
  9. package/dist/core/Publisher.js +20 -14
  10. package/dist/core/Publisher.js.map +1 -1
  11. package/dist/core/config.d.ts.map +1 -1
  12. package/dist/core/config.js +8 -5
  13. package/dist/core/config.js.map +1 -1
  14. package/dist/core/deployments.d.ts.map +1 -1
  15. package/dist/core/deployments.js +4 -2
  16. package/dist/core/deployments.js.map +1 -1
  17. package/dist/fork/manager.d.ts +1 -1
  18. package/dist/fork/manager.js +11 -11
  19. package/dist/fork/manager.js.map +1 -1
  20. package/dist/fork/server.d.ts.map +1 -1
  21. package/dist/fork/server.js +21 -15
  22. package/dist/fork/server.js.map +1 -1
  23. package/dist/fork/test.d.ts.map +1 -1
  24. package/dist/fork/test.js +3 -2
  25. package/dist/fork/test.js.map +1 -1
  26. package/dist/harness/codeObject.js +11 -8
  27. package/dist/harness/codeObject.js.map +1 -1
  28. package/dist/harness/script.d.ts.map +1 -1
  29. package/dist/harness/script.js +9 -6
  30. package/dist/harness/script.js.map +1 -1
  31. package/dist/helpers/setupLocalTesting.js +5 -5
  32. package/dist/helpers/setupLocalTesting.js.map +1 -1
  33. package/dist/node/LocalNodeManager.d.ts +1 -1
  34. package/dist/node/LocalNodeManager.d.ts.map +1 -1
  35. package/dist/node/LocalNodeManager.js +61 -23
  36. package/dist/node/LocalNodeManager.js.map +1 -1
  37. package/dist/node/__tests__/LocalNodeManager.test.js +110 -11
  38. package/dist/node/__tests__/LocalNodeManager.test.js.map +1 -1
  39. package/dist/ui/__tests__/logger.test.d.ts +2 -0
  40. package/dist/ui/__tests__/logger.test.d.ts.map +1 -0
  41. package/dist/ui/__tests__/logger.test.js +75 -0
  42. package/dist/ui/__tests__/logger.test.js.map +1 -0
  43. package/dist/ui/formatters.d.ts +0 -16
  44. package/dist/ui/formatters.d.ts.map +1 -1
  45. package/dist/ui/formatters.js +1 -1
  46. package/dist/ui/formatters.js.map +1 -1
  47. package/dist/ui/logger.d.ts +41 -0
  48. package/dist/ui/logger.d.ts.map +1 -1
  49. package/dist/ui/logger.js +49 -0
  50. package/dist/ui/logger.js.map +1 -1
  51. package/dist/ui/spinner.d.ts +25 -0
  52. package/dist/ui/spinner.d.ts.map +1 -1
  53. package/dist/ui/spinner.js +44 -0
  54. package/dist/ui/spinner.js.map +1 -1
  55. package/package.json +1 -1
  56. package/src/cli.ts +4 -0
  57. package/src/commands/compile.ts +24 -15
  58. package/src/commands/test.ts +12 -19
  59. package/src/core/Publisher.ts +49 -34
  60. package/src/core/config.ts +9 -6
  61. package/src/core/deployments.ts +5 -4
  62. package/src/fork/manager.ts +11 -11
  63. package/src/fork/server.ts +21 -15
  64. package/src/fork/test.ts +3 -2
  65. package/src/harness/codeObject.ts +8 -5
  66. package/src/harness/script.ts +7 -4
  67. package/src/helpers/setupLocalTesting.ts +5 -5
  68. package/src/node/LocalNodeManager.ts +64 -25
  69. package/src/node/__tests__/LocalNodeManager.test.ts +140 -14
  70. package/src/types/config.ts +1 -1
  71. package/src/ui/__tests__/logger.test.ts +89 -0
  72. package/src/ui/formatters.ts +1 -1
  73. package/src/ui/logger.ts +62 -0
  74. package/src/ui/spinner.ts +47 -0
@@ -77,7 +77,7 @@ export interface LocalTestOptions {
77
77
 
78
78
  // Common options (both modes)
79
79
  autoFund?: boolean; // Auto-fund accounts (default: true)
80
- defaultBalance?: number; // Default balance in octas (default: 100_000_000 = 100 APT)
80
+ defaultBalance?: number; // Default balance in octas (default: 100_000_000 = 1 MOVE)
81
81
  autoDeploy?: readonly string[]; // Modules to auto-deploy (accepts readonly arrays)
82
82
  accountLabels?: readonly string[]; // Labels for pre-generated accounts (default: ['deployer', 'alice', 'bob'])
83
83
  }
@@ -0,0 +1,89 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ import { configureLogger, isVerbose, divider, phase } from "../logger.js";
4
+
5
+ describe("logger — verbosity", () => {
6
+ let originalEnv: string | undefined;
7
+
8
+ beforeEach(() => {
9
+ originalEnv = process.env.MOVEHAT_VERBOSE;
10
+ // Start each test from a clean slate so prior runs cannot leak.
11
+ delete process.env.MOVEHAT_VERBOSE;
12
+ configureLogger({ verbosity: "normal" });
13
+ });
14
+
15
+ afterEach(() => {
16
+ if (originalEnv === undefined) {
17
+ delete process.env.MOVEHAT_VERBOSE;
18
+ } else {
19
+ process.env.MOVEHAT_VERBOSE = originalEnv;
20
+ }
21
+ configureLogger({ verbosity: "normal" });
22
+ });
23
+
24
+ it("defaults to non-verbose when MOVEHAT_VERBOSE is unset and config is normal", () => {
25
+ expect(isVerbose()).toBe(false);
26
+ });
27
+
28
+ it("returns true when MOVEHAT_VERBOSE=1 (env-driven path)", () => {
29
+ process.env.MOVEHAT_VERBOSE = "1";
30
+ expect(isVerbose()).toBe(true);
31
+ });
32
+
33
+ it("returns true when configureLogger({ verbosity: 'verbose' }) is set in-process", () => {
34
+ configureLogger({ verbosity: "verbose" });
35
+ expect(isVerbose()).toBe(true);
36
+ });
37
+
38
+ it("env var wins even when in-process config is normal (allows shell-script callers)", () => {
39
+ configureLogger({ verbosity: "normal" });
40
+ process.env.MOVEHAT_VERBOSE = "1";
41
+ expect(isVerbose()).toBe(true);
42
+ });
43
+
44
+ it("non-'1' MOVEHAT_VERBOSE values do not enable verbose mode", () => {
45
+ process.env.MOVEHAT_VERBOSE = "true";
46
+ expect(isVerbose()).toBe(false);
47
+ process.env.MOVEHAT_VERBOSE = "0";
48
+ expect(isVerbose()).toBe(false);
49
+ });
50
+ });
51
+
52
+ describe("logger.phase / logger.divider", () => {
53
+ let logSpy: ReturnType<typeof vi.spyOn>;
54
+
55
+ beforeEach(() => {
56
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
57
+ configureLogger({ silent: false });
58
+ });
59
+
60
+ afterEach(() => {
61
+ vi.restoreAllMocks();
62
+ });
63
+
64
+ it("phase prints three lines: top rule, indented title, bottom rule", () => {
65
+ phase("Local Movement node");
66
+ expect(logSpy).toHaveBeenCalledTimes(3);
67
+
68
+ const calls = logSpy.mock.calls.map((c: unknown[]) => String(c[0]));
69
+ // The title line carries the supplied text after the two-space indent.
70
+ expect(calls[1]).toContain("Local Movement node");
71
+ // Top and bottom rules should be identical (same width, same color).
72
+ expect(calls[0]).toBe(calls[2]);
73
+ });
74
+
75
+ it("divider prints a single muted rule line", () => {
76
+ divider();
77
+ expect(logSpy).toHaveBeenCalledTimes(1);
78
+ const line = String(logSpy.mock.calls[0]?.[0] ?? "");
79
+ // The rule character is `━` (BOX DRAWINGS HEAVY HORIZONTAL).
80
+ expect(line).toMatch(/━+/);
81
+ });
82
+
83
+ it("phase and divider are silenced when configureLogger({ silent: true })", () => {
84
+ configureLogger({ silent: true });
85
+ phase("hidden phase");
86
+ divider();
87
+ expect(logSpy).not.toHaveBeenCalled();
88
+ });
89
+ });
@@ -176,7 +176,7 @@ export const indent = (text: string, spaces: number): string => {
176
176
  * console.log(divider(40, '='));
177
177
  * // ========================================
178
178
  */
179
- export const divider = (
179
+ const divider = (
180
180
  width: number = 60,
181
181
  char: string = symbols.line
182
182
  ): string => {
package/src/ui/logger.ts CHANGED
@@ -6,6 +6,14 @@ import { coloredSymbol, symbols } from './symbols.js';
6
6
  */
7
7
  export type LogLevel = 'info' | 'success' | 'error' | 'warning' | 'debug';
8
8
 
9
+ /**
10
+ * Verbosity level for subprocess output and gray-prefixed chatter.
11
+ * - `quiet` — reserved; currently behaves like `normal`
12
+ * - `normal` — default; system logs only, subprocess chatter hidden
13
+ * - `verbose` — surface subprocess stdout with a muted gray `›` prefix
14
+ */
15
+ export type Verbosity = 'quiet' | 'normal' | 'verbose';
16
+
9
17
  /**
10
18
  * Logger configuration options
11
19
  */
@@ -16,6 +24,8 @@ export interface LoggerConfig {
16
24
  level?: LogLevel;
17
25
  /** Include timestamps in log messages */
18
26
  timestamp?: boolean;
27
+ /** Verbosity level for subprocess output */
28
+ verbosity?: Verbosity;
19
29
  }
20
30
 
21
31
  /**
@@ -25,8 +35,18 @@ let config: LoggerConfig = {
25
35
  silent: false,
26
36
  level: 'info',
27
37
  timestamp: false,
38
+ verbosity: process.env.MOVEHAT_VERBOSE === '1' ? 'verbose' : 'normal',
28
39
  };
29
40
 
41
+ /**
42
+ * Whether subprocess chatter should reach the user's terminal.
43
+ * Honors both the in-process config (set by the `-v` CLI flag's
44
+ * preAction hook) and the `MOVEHAT_VERBOSE=1` env var (which lets
45
+ * callers opt in before the CLI parses args, e.g. in shell scripts).
46
+ */
47
+ export const isVerbose = (): boolean =>
48
+ config.verbosity === 'verbose' || process.env.MOVEHAT_VERBOSE === '1';
49
+
30
50
  /**
31
51
  * Configure logger globally
32
52
  *
@@ -186,6 +206,45 @@ export const section = (title: string): void => {
186
206
  console.log(`\n${colors.brandBright(title)}`);
187
207
  };
188
208
 
209
+ /**
210
+ * Width of the `━` rule used by `phase` and `divider`. Matched to a
211
+ * comfortable terminal width that fits in a side-by-side dev layout.
212
+ */
213
+ const PHASE_RULE_WIDTH = 52;
214
+
215
+ /**
216
+ * Single muted horizontal rule. Use to close out a phase or to
217
+ * visually separate output sections.
218
+ */
219
+ export const divider = (): void => {
220
+ if (config.silent) return;
221
+ console.log(colors.muted('━'.repeat(PHASE_RULE_WIDTH)));
222
+ };
223
+
224
+ /**
225
+ * Phase header — renders a muted top rule, a bold brand-colored title
226
+ * indented two spaces, and a muted bottom rule. Use at top-level phase
227
+ * boundaries (local node start, deploy flow, test orchestrator
228
+ * sections) so the user can visually anchor where one phase ends and
229
+ * the next begins.
230
+ *
231
+ * @param title - Phase title (e.g. "Local Movement node")
232
+ *
233
+ * @example
234
+ * logger.phase('Local Movement node');
235
+ * // Renders:
236
+ * // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
237
+ * // Local Movement node
238
+ * // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
239
+ */
240
+ export const phase = (title: string): void => {
241
+ if (config.silent) return;
242
+ const rule = colors.muted('━'.repeat(PHASE_RULE_WIDTH));
243
+ console.log(rule);
244
+ console.log(` ${colors.brandBright(title)}`);
245
+ console.log(rule);
246
+ };
247
+
189
248
  /**
190
249
  * Key-value pair
191
250
  * Use for displaying structured data
@@ -235,6 +294,7 @@ export const item = (text: string, indent: number = 0): void => {
235
294
  */
236
295
  export const logger = {
237
296
  configure: configureLogger,
297
+ isVerbose,
238
298
  info,
239
299
  success,
240
300
  error,
@@ -243,6 +303,8 @@ export const logger = {
243
303
  plain,
244
304
  newline,
245
305
  section,
306
+ phase,
307
+ divider,
246
308
  kv,
247
309
  item,
248
310
  };
package/src/ui/spinner.ts CHANGED
@@ -103,6 +103,53 @@ export const withSpinner = async <T>(
103
103
  }
104
104
  };
105
105
 
106
+ /**
107
+ * Execute async task with a spinner that updates its label with
108
+ * elapsed seconds while the task runs. Use for long-running phases
109
+ * (local node startup, publish + tx wait) where the user wants
110
+ * visible progress feedback in lieu of subprocess chatter.
111
+ *
112
+ * Pairs with the `§9` console-UX convention: any phase that
113
+ * empirically takes ≥3s in normal use should wrap its body in
114
+ * `withTimedSpinner` so the terminal never goes silent while work
115
+ * happens.
116
+ *
117
+ * @param label - Stable label shown next to the spinner (e.g. "Starting node")
118
+ * @param task - Async function to execute
119
+ * @param indent - Number of spaces to indent (default: 0)
120
+ * @returns Promise resolving to task result
121
+ *
122
+ * @example
123
+ * await withTimedSpinner('Starting local node', async () => {
124
+ * await this.waitForReady(60_000);
125
+ * });
126
+ * // Renders: ⠋ Starting local node — 0.0s ... ⠼ Starting local node — 14.2s
127
+ * // On success: ✔ Starting local node (14.2s)
128
+ * // On error: ✖ <error.message>
129
+ */
130
+ export const withTimedSpinner = async <T>(
131
+ label: string,
132
+ task: () => Promise<T>,
133
+ indent: number = 0
134
+ ): Promise<T> => {
135
+ const start = Date.now();
136
+ const spin = spinner({ text: `${label} — 0.0s`, indent });
137
+ const timer = setInterval(() => {
138
+ spin.text = `${label} — ${((Date.now() - start) / 1000).toFixed(1)}s`;
139
+ }, 500);
140
+ try {
141
+ const result = await task();
142
+ spin.succeed(`${label} (${((Date.now() - start) / 1000).toFixed(1)}s)`);
143
+ return result;
144
+ } catch (error) {
145
+ const errMsg = error instanceof Error ? error.message : String(error);
146
+ spin.fail(errMsg);
147
+ throw error;
148
+ } finally {
149
+ clearInterval(timer);
150
+ }
151
+ };
152
+
106
153
  /**
107
154
  * Spinner chain for sequential operations
108
155
  * Manages multiple spinners in sequence