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.
- package/dist/__tests__/deployContract.test.js +56 -47
- package/dist/__tests__/deployContract.test.js.map +1 -1
- package/dist/__tests__/exports.test.d.ts +2 -0
- package/dist/__tests__/exports.test.d.ts.map +1 -0
- package/dist/__tests__/exports.test.js +30 -0
- package/dist/__tests__/exports.test.js.map +1 -0
- package/dist/__tests__/fixtures/sigint-deploy-harness.d.ts +4 -3
- package/dist/__tests__/fixtures/sigint-deploy-harness.d.ts.map +1 -1
- package/dist/__tests__/fixtures/sigint-deploy-harness.js +8 -7
- package/dist/__tests__/fixtures/sigint-deploy-harness.js.map +1 -1
- package/dist/__tests__/fork/api.test.js +5 -0
- package/dist/__tests__/fork/api.test.js.map +1 -1
- package/dist/__tests__/fork/api.timeout.test.d.ts +2 -0
- package/dist/__tests__/fork/api.timeout.test.d.ts.map +1 -0
- package/dist/__tests__/fork/api.timeout.test.js +98 -0
- package/dist/__tests__/fork/api.timeout.test.js.map +1 -0
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/__tests__/compile.toml-mutation.test.d.ts +2 -0
- package/dist/commands/__tests__/compile.toml-mutation.test.d.ts.map +1 -0
- package/dist/commands/__tests__/compile.toml-mutation.test.js +69 -0
- package/dist/commands/__tests__/compile.toml-mutation.test.js.map +1 -0
- package/dist/commands/__tests__/init.test.js +73 -11
- package/dist/commands/__tests__/init.test.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/init.d.ts +22 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +55 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/test.js +12 -19
- package/dist/commands/test.js.map +1 -1
- package/dist/core/AccountManager.d.ts.map +1 -1
- package/dist/core/AccountManager.js +14 -2
- package/dist/core/AccountManager.js.map +1 -1
- package/dist/core/Publisher.d.ts.map +1 -1
- package/dist/core/Publisher.js +72 -82
- package/dist/core/Publisher.js.map +1 -1
- package/dist/core/__tests__/AccountManager.global-state.test.d.ts +2 -0
- package/dist/core/__tests__/AccountManager.global-state.test.d.ts.map +1 -0
- package/dist/core/__tests__/AccountManager.global-state.test.js +69 -0
- package/dist/core/__tests__/AccountManager.global-state.test.js.map +1 -0
- package/dist/core/__tests__/movementProfile.test.d.ts +2 -0
- package/dist/core/__tests__/movementProfile.test.d.ts.map +1 -0
- package/dist/core/__tests__/movementProfile.test.js +112 -0
- package/dist/core/__tests__/movementProfile.test.js.map +1 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +14 -10
- 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/core/movementProfile.d.ts +55 -22
- package/dist/core/movementProfile.d.ts.map +1 -1
- package/dist/core/movementProfile.js +77 -99
- package/dist/core/movementProfile.js.map +1 -1
- package/dist/fork/__tests__/server.cors.test.d.ts +2 -0
- package/dist/fork/__tests__/server.cors.test.d.ts.map +1 -0
- package/dist/fork/__tests__/server.cors.test.js +79 -0
- package/dist/fork/__tests__/server.cors.test.js.map +1 -0
- package/dist/fork/api.d.ts +9 -1
- package/dist/fork/api.d.ts.map +1 -1
- package/dist/fork/api.js +37 -7
- package/dist/fork/api.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 +20 -1
- package/dist/fork/server.d.ts.map +1 -1
- package/dist/fork/server.js +40 -24
- 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/Harness.d.ts +6 -2
- package/dist/harness/Harness.d.ts.map +1 -1
- package/dist/harness/Harness.js +8 -2
- package/dist/harness/Harness.js.map +1 -1
- package/dist/harness/codeObject.d.ts.map +1 -1
- package/dist/harness/codeObject.js +41 -41
- package/dist/harness/codeObject.js.map +1 -1
- package/dist/harness/script.d.ts +3 -3
- package/dist/harness/script.d.ts.map +1 -1
- package/dist/harness/script.js +42 -35
- package/dist/harness/script.js.map +1 -1
- package/dist/helpers/__tests__/setupLocalTesting.fork-network.test.d.ts +2 -0
- package/dist/helpers/__tests__/setupLocalTesting.fork-network.test.d.ts.map +1 -0
- package/dist/helpers/__tests__/setupLocalTesting.fork-network.test.js +172 -0
- package/dist/helpers/__tests__/setupLocalTesting.fork-network.test.js.map +1 -0
- package/dist/helpers/setupLocalTesting.d.ts.map +1 -1
- package/dist/helpers/setupLocalTesting.js +31 -5
- package/dist/helpers/setupLocalTesting.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/node/LocalNodeManager.d.ts +8 -0
- package/dist/node/LocalNodeManager.d.ts.map +1 -1
- package/dist/node/LocalNodeManager.js +70 -23
- package/dist/node/LocalNodeManager.js.map +1 -1
- package/dist/node/__tests__/LocalNodeManager.api-port.test.d.ts +2 -0
- package/dist/node/__tests__/LocalNodeManager.api-port.test.d.ts.map +1 -0
- package/dist/node/__tests__/LocalNodeManager.api-port.test.js +55 -0
- package/dist/node/__tests__/LocalNodeManager.api-port.test.js.map +1 -0
- package/dist/node/__tests__/LocalNodeManager.test.js +114 -14
- package/dist/node/__tests__/LocalNodeManager.test.js.map +1 -1
- package/dist/templates/move/Move.toml +1 -1
- package/dist/templates/move/sources/Counter.move +31 -4
- package/dist/templates/scripts/deploy-counter.ts +10 -0
- package/dist/types/config.d.ts +8 -1
- package/dist/types/config.d.ts.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/dist/utils/__tests__/childProcessAdapter.maxBuffer.test.d.ts +2 -0
- package/dist/utils/__tests__/childProcessAdapter.maxBuffer.test.d.ts.map +1 -0
- package/dist/utils/__tests__/childProcessAdapter.maxBuffer.test.js +43 -0
- package/dist/utils/__tests__/childProcessAdapter.maxBuffer.test.js.map +1 -0
- package/dist/utils/childProcessAdapter.d.ts +7 -0
- package/dist/utils/childProcessAdapter.d.ts.map +1 -1
- package/dist/utils/childProcessAdapter.js +20 -2
- package/dist/utils/childProcessAdapter.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/deployContract.test.ts +59 -50
- package/src/__tests__/exports.test.ts +32 -0
- package/src/__tests__/fixtures/sigint-deploy-harness.ts +8 -7
- package/src/__tests__/fork/api.test.ts +5 -0
- package/src/__tests__/fork/api.timeout.test.ts +150 -0
- package/src/cli.ts +4 -0
- package/src/commands/__tests__/compile.toml-mutation.test.ts +77 -0
- package/src/commands/__tests__/init.test.ts +96 -11
- package/src/commands/compile.ts +24 -15
- package/src/commands/init.ts +77 -6
- package/src/commands/test.ts +12 -19
- package/src/core/AccountManager.ts +18 -1
- package/src/core/Publisher.ts +103 -107
- package/src/core/__tests__/AccountManager.global-state.test.ts +83 -0
- package/src/core/__tests__/movementProfile.test.ts +131 -0
- package/src/core/config.ts +18 -11
- package/src/core/deployments.ts +5 -4
- package/src/core/movementProfile.ts +75 -127
- package/src/fork/__tests__/server.cors.test.ts +101 -0
- package/src/fork/api.ts +69 -10
- package/src/fork/manager.ts +10 -10
- package/src/fork/server.ts +59 -24
- package/src/fork/test.ts +3 -2
- package/src/harness/Harness.ts +11 -2
- package/src/harness/codeObject.ts +45 -48
- package/src/harness/script.ts +47 -43
- package/src/helpers/__tests__/setupLocalTesting.fork-network.test.ts +212 -0
- package/src/helpers/setupLocalTesting.ts +39 -5
- package/src/index.ts +9 -1
- package/src/node/LocalNodeManager.ts +87 -26
- package/src/node/__tests__/LocalNodeManager.api-port.test.ts +62 -0
- package/src/node/__tests__/LocalNodeManager.test.ts +144 -17
- package/src/templates/move/Move.toml +1 -1
- package/src/templates/move/sources/Counter.move +31 -4
- package/src/templates/scripts/deploy-counter.ts +10 -0
- package/src/types/config.ts +8 -1
- 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/src/utils/__tests__/childProcessAdapter.maxBuffer.test.ts +51 -0
- package/src/utils/childProcessAdapter.ts +32 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
const DEFAULT_MAX_BUFFER = 64 * 1024 * 1024;
|
|
2
3
|
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
3
4
|
class DefaultChildProcessAdapter {
|
|
4
5
|
run(input) {
|
|
@@ -16,9 +17,26 @@ class DefaultChildProcessAdapter {
|
|
|
16
17
|
});
|
|
17
18
|
const stdoutChunks = [];
|
|
18
19
|
const stderrChunks = [];
|
|
20
|
+
let totalBytes = 0;
|
|
21
|
+
let overflowed = false;
|
|
22
|
+
const maxBuffer = input.maxBuffer ?? DEFAULT_MAX_BUFFER;
|
|
23
|
+
const onChunk = (chunks) => (chunk) => {
|
|
24
|
+
if (overflowed)
|
|
25
|
+
return;
|
|
26
|
+
totalBytes += chunk.length;
|
|
27
|
+
if (totalBytes > maxBuffer) {
|
|
28
|
+
overflowed = true;
|
|
29
|
+
clearTimer();
|
|
30
|
+
input.signal?.removeEventListener('abort', onAbort);
|
|
31
|
+
child.kill('SIGTERM');
|
|
32
|
+
reject(new Error(`Command output exceeded maxBuffer (${maxBuffer} bytes): ${input.command}`));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
chunks.push(chunk);
|
|
36
|
+
};
|
|
19
37
|
// Streams are null when stdio is 'inherit'; the `?.` covers that.
|
|
20
|
-
child.stdout?.on('data', (
|
|
21
|
-
child.stderr?.on('data', (
|
|
38
|
+
child.stdout?.on('data', onChunk(stdoutChunks));
|
|
39
|
+
child.stderr?.on('data', onChunk(stderrChunks));
|
|
22
40
|
let timeoutHandle;
|
|
23
41
|
const clearTimer = () => {
|
|
24
42
|
if (timeoutHandle)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"childProcessAdapter.js","sourceRoot":"","sources":["../../src/utils/childProcessAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"childProcessAdapter.js","sourceRoot":"","sources":["../../src/utils/childProcessAdapter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -149,19 +149,19 @@ version = "0.0.1"
|
|
|
149
149
|
expect(existsSync(join(tmpHome, ".aptos", "config.yaml"))).toBe(false);
|
|
150
150
|
});
|
|
151
151
|
|
|
152
|
-
it("two concurrent deploys
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
// Seed
|
|
152
|
+
it("two concurrent deploys use distinct temp key files and never touch ~/.aptos/config.yaml", async () => {
|
|
153
|
+
// Each deploy writes its private key to a UUID-named temp file
|
|
154
|
+
// (see `core/movementProfile.ts:writeTempKeyFile`) and passes
|
|
155
|
+
// `--private-key-file <path>` to Movement CLI. Concurrent deploys
|
|
156
|
+
// have no shared state — no profile YAML, no mutex, no race.
|
|
157
|
+
// Asserts:
|
|
158
|
+
// 1. Each invocation records a DISTINCT --private-key-file path.
|
|
159
|
+
// 2. After both deploys finish, neither temp key file remains on
|
|
160
|
+
// disk (cleanup ran on both happy paths).
|
|
161
|
+
// 3. ~/.aptos/config.yaml is byte-identical to what was on disk
|
|
162
|
+
// before — the new flow doesn't touch the user's CLI config.
|
|
163
|
+
|
|
164
|
+
// Seed an unrelated user profile that MUST survive untouched.
|
|
165
165
|
const aptosDir = join(tmpHome, ".aptos");
|
|
166
166
|
mkdirSync(aptosDir, { recursive: true });
|
|
167
167
|
const preExisting = {
|
|
@@ -176,10 +176,11 @@ version = "0.0.1"
|
|
|
176
176
|
};
|
|
177
177
|
const configPath = join(aptosDir, "config.yaml");
|
|
178
178
|
writeFileSync(configPath, yaml.dump(preExisting), { mode: 0o600 });
|
|
179
|
+
const initialConfigBytes = readFileSync(configPath, "utf8");
|
|
179
180
|
|
|
180
|
-
// Set up two Publisher instances with fake adapters that record
|
|
181
|
-
//
|
|
182
|
-
// critical sections overlap.
|
|
181
|
+
// Set up two Publisher instances with fake adapters that record
|
|
182
|
+
// their --private-key-file argument and inject a small delay on
|
|
183
|
+
// publish so the critical sections overlap.
|
|
183
184
|
function makeDelayedAdapter(label: string): {
|
|
184
185
|
adapter: ChildProcessAdapter;
|
|
185
186
|
captured: { publishCall?: RunInput };
|
|
@@ -234,38 +235,41 @@ version = "0.0.1"
|
|
|
234
235
|
}),
|
|
235
236
|
]);
|
|
236
237
|
|
|
237
|
-
// Both publish calls captured distinct --
|
|
238
|
+
// Both publish calls captured distinct --private-key-file args.
|
|
238
239
|
const argsA = a.captured.publishCall!.args;
|
|
239
240
|
const argsB = b.captured.publishCall!.args;
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
expect(
|
|
243
|
-
expect(
|
|
244
|
-
expect(
|
|
245
|
-
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
expect(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
);
|
|
241
|
+
const keyFileArgA = argsA[argsA.indexOf("--private-key-file") + 1] as string;
|
|
242
|
+
const keyFileArgB = argsB[argsB.indexOf("--private-key-file") + 1] as string;
|
|
243
|
+
expect(keyFileArgA).toMatch(/movehat-key-/);
|
|
244
|
+
expect(keyFileArgB).toMatch(/movehat-key-/);
|
|
245
|
+
expect(keyFileArgA).not.toBe(keyFileArgB);
|
|
246
|
+
|
|
247
|
+
// Cleanup ran on both — neither temp file persists after the
|
|
248
|
+
// deploys finished normally.
|
|
249
|
+
expect(existsSync(keyFileArgA)).toBe(false);
|
|
250
|
+
expect(existsSync(keyFileArgB)).toBe(false);
|
|
251
|
+
|
|
252
|
+
// ~/.aptos/config.yaml byte-identical to pre-deploy state — the new
|
|
253
|
+
// flow never touches the user's CLI config.
|
|
254
|
+
expect(readFileSync(configPath, "utf8")).toBe(initialConfigBytes);
|
|
254
255
|
});
|
|
255
256
|
|
|
256
|
-
it("SIGINT mid-deploy
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
257
|
+
it("SIGINT mid-deploy unlinks the temp key file", async () => {
|
|
258
|
+
// Without sync SIGINT cleanup, the temp private-key file written
|
|
259
|
+
// by `writeTempKeyFile` would persist on disk after an abnormal
|
|
260
|
+
// exit (chmod 0o600 prevents other users from reading it, but
|
|
261
|
+
// forensic recovery from /tmp is still possible). The sync signal
|
|
262
|
+
// handler runs synchronously before process.exit and unlinks
|
|
263
|
+
// every active deploy's key file.
|
|
262
264
|
//
|
|
263
265
|
// This test spawns a child process running a harness that drives
|
|
264
266
|
// Publisher.deploy() with a 3-second-delayed publish, then sends
|
|
265
267
|
// SIGINT mid-flight. Vitest's own process is unaffected because
|
|
266
268
|
// the SIGINT goes to the child.
|
|
267
269
|
|
|
268
|
-
// Seed an unrelated user profile that MUST
|
|
270
|
+
// Seed an unrelated user profile that MUST be left untouched —
|
|
271
|
+
// the new flow doesn't read or write ~/.aptos/config.yaml at all,
|
|
272
|
+
// so this is an invariant check.
|
|
269
273
|
const aptosDir = join(tmpHome, ".aptos");
|
|
270
274
|
mkdirSync(aptosDir, { recursive: true });
|
|
271
275
|
const configPath = join(aptosDir, "config.yaml");
|
|
@@ -283,6 +287,7 @@ version = "0.0.1"
|
|
|
283
287
|
}),
|
|
284
288
|
{ mode: 0o600 }
|
|
285
289
|
);
|
|
290
|
+
const initialConfigBytes = readFileSync(configPath, "utf8");
|
|
286
291
|
|
|
287
292
|
const harnessPath = join(__dirname, "fixtures", "sigint-deploy-harness.ts");
|
|
288
293
|
// Resolve tsx's CLI binary by absolute path — the test's tmp cwd has
|
|
@@ -302,8 +307,8 @@ version = "0.0.1"
|
|
|
302
307
|
}
|
|
303
308
|
);
|
|
304
309
|
|
|
305
|
-
// Wait for the harness to announce its
|
|
306
|
-
// (it writes a JSON line `{"
|
|
310
|
+
// Wait for the harness to announce its temp key file path via stdout
|
|
311
|
+
// (it writes a JSON line `{"keyFile":"/tmp/movehat-key-XXXX"}` just
|
|
307
312
|
// before entering the slow publish step).
|
|
308
313
|
let announced: string | undefined;
|
|
309
314
|
let stdoutBuf = "";
|
|
@@ -315,7 +320,7 @@ version = "0.0.1"
|
|
|
315
320
|
if (!trimmed.startsWith("{")) continue;
|
|
316
321
|
try {
|
|
317
322
|
const parsed = JSON.parse(trimmed);
|
|
318
|
-
if (typeof parsed.
|
|
323
|
+
if (typeof parsed.keyFile === "string") announced = parsed.keyFile;
|
|
319
324
|
} catch {
|
|
320
325
|
/* not a JSON line we care about */
|
|
321
326
|
}
|
|
@@ -333,12 +338,17 @@ version = "0.0.1"
|
|
|
333
338
|
if (!announced) {
|
|
334
339
|
child.kill("SIGKILL");
|
|
335
340
|
throw new Error(
|
|
336
|
-
`harness never announced
|
|
341
|
+
`harness never announced keyFile in 8s.\n` +
|
|
337
342
|
`stdout so far:\n${stdoutBuf}\n---\nstderr so far:\n${stderrBuf}`
|
|
338
343
|
);
|
|
339
344
|
}
|
|
340
345
|
|
|
341
|
-
expect(announced).toMatch(
|
|
346
|
+
expect(announced).toMatch(/movehat-key-/);
|
|
347
|
+
// The temp key file is present on disk while the harness is in
|
|
348
|
+
// the middle of the slow publish (the JSON announcement is emitted
|
|
349
|
+
// right before the simulated 3s wait, and the file is unlinked
|
|
350
|
+
// only on cleanup).
|
|
351
|
+
expect(existsSync(announced)).toBe(true);
|
|
342
352
|
|
|
343
353
|
// Deliver SIGINT mid-publish and wait for the harness to exit.
|
|
344
354
|
child.kill("SIGINT");
|
|
@@ -347,13 +357,12 @@ version = "0.0.1"
|
|
|
347
357
|
});
|
|
348
358
|
expect(exitCode).toBe(130);
|
|
349
359
|
|
|
350
|
-
// The
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
expect(
|
|
356
|
-
expect(finalYaml.profiles.user_main.private_key).toBe("0x" + "a".repeat(64));
|
|
360
|
+
// The temp key file has been unlinked by the SIGINT handler.
|
|
361
|
+
expect(existsSync(announced)).toBe(false);
|
|
362
|
+
|
|
363
|
+
// The user's ~/.aptos/config.yaml is byte-identical to pre-deploy
|
|
364
|
+
// — the new flow never touches it.
|
|
365
|
+
expect(readFileSync(configPath, "utf8")).toBe(initialConfigBytes);
|
|
357
366
|
}, 15000);
|
|
358
367
|
|
|
359
368
|
it("does not mutate Move.toml during deploy (#38)", async () => {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import * as publicSurface from "../index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Locks the public export surface of `movehat`. Adding a new symbol
|
|
6
|
+
* is a deliberate API change; removing one is a breaking change.
|
|
7
|
+
* Update this list (and the CHANGELOG) when the surface evolves.
|
|
8
|
+
*/
|
|
9
|
+
const EXPECTED_RUNTIME_EXPORTS = [
|
|
10
|
+
"Harness",
|
|
11
|
+
"HarnessDisposedError",
|
|
12
|
+
"ForkManager",
|
|
13
|
+
"MovementApiClient",
|
|
14
|
+
"ForkStorage",
|
|
15
|
+
"ForkServer",
|
|
16
|
+
"ModuleAlreadyDeployedError",
|
|
17
|
+
"PostPublishError",
|
|
18
|
+
"initRuntime",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
describe("public export surface (movehat root)", () => {
|
|
22
|
+
it.each(EXPECTED_RUNTIME_EXPORTS)("exports %s as a runtime value", (name) => {
|
|
23
|
+
expect(publicSurface[name as keyof typeof publicSurface]).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Type-only exports cannot be probed at runtime; the assertion is
|
|
27
|
+
// that the module imports successfully (failing types-only export
|
|
28
|
+
// would surface as a TS error in `pnpm check:example`).
|
|
29
|
+
it("imports without errors", () => {
|
|
30
|
+
expect(typeof publicSurface).toBe("object");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* send SIGINT mid-flight.
|
|
10
10
|
* 2. Drive `Publisher.deploy()` against the fake adapter using a
|
|
11
11
|
* synthetic MovehatConfig + Account read from env vars.
|
|
12
|
-
* 3. Write the
|
|
13
|
-
*
|
|
14
|
-
* parent test knows which
|
|
12
|
+
* 3. Write the temp key file path to stdout as JSON
|
|
13
|
+
* (`{"keyFile": "/tmp/movehat-key-<uuid>"}`) before the slow
|
|
14
|
+
* publish so the parent test knows which file to look for after
|
|
15
|
+
* SIGINT.
|
|
15
16
|
* 4. If the deploy completes naturally (test failure case), exit 0.
|
|
16
17
|
* 5. When SIGINT arrives, Publisher's signal handler runs synchronous
|
|
17
18
|
* cleanup and `setImmediate(() => process.exit(130))`.
|
|
@@ -61,10 +62,10 @@ async function main() {
|
|
|
61
62
|
return { exitCode: 0, stdout: "built", stderr: "" };
|
|
62
63
|
}
|
|
63
64
|
if (input.args[1] === "publish") {
|
|
64
|
-
// Surface the
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
process.stdout.write(JSON.stringify({
|
|
65
|
+
// Surface the temp key file path to the parent BEFORE blocking.
|
|
66
|
+
const keyFileIdx = input.args.indexOf("--private-key-file");
|
|
67
|
+
const keyFile = keyFileIdx >= 0 ? input.args[keyFileIdx + 1] : "";
|
|
68
|
+
process.stdout.write(JSON.stringify({ keyFile }) + "\n");
|
|
68
69
|
// Hold long enough for the parent to deliver SIGINT.
|
|
69
70
|
await new Promise((r) => setTimeout(r, 3000));
|
|
70
71
|
return {
|
|
@@ -77,6 +77,11 @@ function setupGetCapture(): {
|
|
|
77
77
|
|
|
78
78
|
const fakeReq = new EventEmitter() as unknown as ClientRequest;
|
|
79
79
|
(fakeReq as unknown as { end: () => void }).end = () => {};
|
|
80
|
+
// F3: api.ts now installs a setTimeout on the request and may call
|
|
81
|
+
// destroy() on overflow / timeout. Stub both so this happy-path
|
|
82
|
+
// capture mock still satisfies the new contract.
|
|
83
|
+
(fakeReq as unknown as { setTimeout: (ms: number, cb?: () => void) => void }).setTimeout = () => {};
|
|
84
|
+
(fakeReq as unknown as { destroy: () => void }).destroy = () => {};
|
|
80
85
|
|
|
81
86
|
if (callback) {
|
|
82
87
|
const body = JSON.stringify({
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { ClientRequest, IncomingMessage } from "node:http";
|
|
3
|
+
import { EventEmitter } from "node:events";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* F3 — MovementApiClient must bound responses by time AND bytes.
|
|
7
|
+
*
|
|
8
|
+
* Without these guards a malicious / hung upstream can:
|
|
9
|
+
* - leak the request promise forever (never emits 'end'), or
|
|
10
|
+
* - exhaust heap by pushing unbounded `data` chunks.
|
|
11
|
+
*
|
|
12
|
+
* Strategy mirrors src/__tests__/fork/api.test.ts: vi.mock node:http
|
|
13
|
+
* and node:https, intercept `client.get(url, options, cb)`, and feed
|
|
14
|
+
* a controllable `IncomingMessage` to the callback. The fake
|
|
15
|
+
* `ClientRequest` exposes `setTimeout`, `destroy`, and emits 'error' /
|
|
16
|
+
* 'timeout' so we can drive the failure modes from the test.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
interface FakeReq extends EventEmitter {
|
|
20
|
+
end(): void;
|
|
21
|
+
destroy(err?: Error): void;
|
|
22
|
+
setTimeout(ms: number, cb?: () => void): void;
|
|
23
|
+
destroyed: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const httpsGet = vi.fn();
|
|
27
|
+
const httpGet = vi.fn();
|
|
28
|
+
|
|
29
|
+
vi.mock("https", () => ({ default: { get: httpsGet }, get: httpsGet }));
|
|
30
|
+
vi.mock("http", () => ({ default: { get: httpGet }, get: httpGet }));
|
|
31
|
+
|
|
32
|
+
function makeFakeReq(): FakeReq {
|
|
33
|
+
const req = new EventEmitter() as FakeReq;
|
|
34
|
+
req.destroyed = false;
|
|
35
|
+
let timeoutHandle: NodeJS.Timeout | undefined;
|
|
36
|
+
req.end = () => {};
|
|
37
|
+
req.destroy = (err?: Error) => {
|
|
38
|
+
req.destroyed = true;
|
|
39
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
40
|
+
setImmediate(() => req.emit("error", err ?? new Error("destroyed")));
|
|
41
|
+
};
|
|
42
|
+
req.setTimeout = (ms: number, cb?: () => void) => {
|
|
43
|
+
timeoutHandle = setTimeout(() => {
|
|
44
|
+
req.emit("timeout");
|
|
45
|
+
if (cb) cb();
|
|
46
|
+
}, ms);
|
|
47
|
+
// Don't keep the event loop alive — Node sets this itself but the
|
|
48
|
+
// mock has no native socket to inherit from.
|
|
49
|
+
timeoutHandle.unref?.();
|
|
50
|
+
};
|
|
51
|
+
return req;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function makeUnresolvableResponse(): IncomingMessage {
|
|
55
|
+
const res = new EventEmitter() as unknown as IncomingMessage;
|
|
56
|
+
(res as unknown as { statusCode: number }).statusCode = 200;
|
|
57
|
+
return res;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function makeStreamingResponse(
|
|
61
|
+
bytesPerChunk: number,
|
|
62
|
+
chunks: number
|
|
63
|
+
): IncomingMessage {
|
|
64
|
+
const res = new EventEmitter() as unknown as IncomingMessage;
|
|
65
|
+
(res as unknown as { statusCode: number }).statusCode = 200;
|
|
66
|
+
setImmediate(() => {
|
|
67
|
+
let i = 0;
|
|
68
|
+
const pump = () => {
|
|
69
|
+
if (i >= chunks) {
|
|
70
|
+
(res as unknown as EventEmitter).emit("end");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
(res as unknown as EventEmitter).emit(
|
|
74
|
+
"data",
|
|
75
|
+
Buffer.alloc(bytesPerChunk, 0x61)
|
|
76
|
+
);
|
|
77
|
+
i++;
|
|
78
|
+
setImmediate(pump);
|
|
79
|
+
};
|
|
80
|
+
pump();
|
|
81
|
+
});
|
|
82
|
+
return res;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe("F3 — MovementApiClient timeouts and byte cap", () => {
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
httpsGet.mockReset();
|
|
88
|
+
httpGet.mockReset();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
vi.restoreAllMocks();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("rejects with a timeout error when the upstream never responds", async () => {
|
|
96
|
+
const fakeReq = makeFakeReq();
|
|
97
|
+
httpGet.mockImplementation(
|
|
98
|
+
(
|
|
99
|
+
_url: string,
|
|
100
|
+
options: unknown,
|
|
101
|
+
cb?: (res: IncomingMessage) => void
|
|
102
|
+
) => {
|
|
103
|
+
const callback =
|
|
104
|
+
typeof options === "function"
|
|
105
|
+
? (options as (r: IncomingMessage) => void)
|
|
106
|
+
: cb;
|
|
107
|
+
if (callback) callback(makeUnresolvableResponse());
|
|
108
|
+
return fakeReq as unknown as ClientRequest;
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const { MovementApiClient } = await import("../../fork/api.js");
|
|
113
|
+
const client = new MovementApiClient("http://hung.example/v1", undefined, {
|
|
114
|
+
timeoutMs: 25,
|
|
115
|
+
maxBytes: 1024 * 1024,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await expect(client.getLedgerInfo()).rejects.toThrow(/timed out|timeout/i);
|
|
119
|
+
expect(fakeReq.destroyed).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("rejects and destroys the request when the response exceeds maxBytes", async () => {
|
|
123
|
+
const fakeReq = makeFakeReq();
|
|
124
|
+
httpGet.mockImplementation(
|
|
125
|
+
(
|
|
126
|
+
_url: string,
|
|
127
|
+
options: unknown,
|
|
128
|
+
cb?: (res: IncomingMessage) => void
|
|
129
|
+
) => {
|
|
130
|
+
const callback =
|
|
131
|
+
typeof options === "function"
|
|
132
|
+
? (options as (r: IncomingMessage) => void)
|
|
133
|
+
: cb;
|
|
134
|
+
if (callback) callback(makeStreamingResponse(2048, 100));
|
|
135
|
+
return fakeReq as unknown as ClientRequest;
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const { MovementApiClient } = await import("../../fork/api.js");
|
|
140
|
+
const client = new MovementApiClient("http://big.example/v1", undefined, {
|
|
141
|
+
timeoutMs: 5000,
|
|
142
|
+
maxBytes: 4096,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await expect(client.getLedgerInfo()).rejects.toThrow(
|
|
146
|
+
/maxBytes|too large|exceeded/i
|
|
147
|
+
);
|
|
148
|
+
expect(fakeReq.destroyed).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
});
|
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
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { vol, fs as memfsFs } from "memfs";
|
|
3
|
+
|
|
4
|
+
vi.mock("fs", () => {
|
|
5
|
+
return {
|
|
6
|
+
default: memfsFs,
|
|
7
|
+
...memfsFs,
|
|
8
|
+
};
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
import { updateMoveToml } from "../compile.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* F7 — `movehat compile` mutates `Move.toml` when it detects a named
|
|
15
|
+
* address that isn't declared yet. This test pins down the current
|
|
16
|
+
* behavior so a future change (introducing a `--write` flag, moving
|
|
17
|
+
* the mutation to a dedicated `movehat update-toml` command, etc.)
|
|
18
|
+
* MUST update this test alongside the source change.
|
|
19
|
+
*
|
|
20
|
+
* Audit finding F7 is marked "no concluyente — requires product
|
|
21
|
+
* decision": the mutation may be the right UX default for the
|
|
22
|
+
* Hardhat-style workflow, but the asymmetry (compile reading + writing)
|
|
23
|
+
* deserves to be a deliberate choice. See ROADMAP follow-up.
|
|
24
|
+
*/
|
|
25
|
+
describe("F7 — updateMoveToml mutates Move.toml on disk (current behavior captured)", () => {
|
|
26
|
+
beforeEach(() => vol.reset());
|
|
27
|
+
afterEach(() => vol.reset());
|
|
28
|
+
|
|
29
|
+
it("writes new named addresses into [addresses] and [dev-addresses] when missing", () => {
|
|
30
|
+
const before = `[package]
|
|
31
|
+
name = "captured"
|
|
32
|
+
version = "1.0.0"
|
|
33
|
+
|
|
34
|
+
[addresses]
|
|
35
|
+
existing = "_"
|
|
36
|
+
|
|
37
|
+
[dev-addresses]
|
|
38
|
+
existing = "0xcafe"
|
|
39
|
+
|
|
40
|
+
[dependencies]
|
|
41
|
+
`;
|
|
42
|
+
vol.fromJSON({ "/move/Move.toml": before });
|
|
43
|
+
|
|
44
|
+
const detected = new Set(["existing", "audit_module"]);
|
|
45
|
+
const added = updateMoveToml("/move", detected);
|
|
46
|
+
|
|
47
|
+
expect(added).toEqual(["audit_module"]);
|
|
48
|
+
|
|
49
|
+
// Source-of-truth assertion: the on-disk file CHANGED. F7 is the
|
|
50
|
+
// observation that a "compile" verb has a side effect on the user's
|
|
51
|
+
// working tree. The behavior is captured here so an unintended
|
|
52
|
+
// regression (or a deliberate revisit) cannot land silently.
|
|
53
|
+
const after = vol.readFileSync("/move/Move.toml", "utf-8") as string;
|
|
54
|
+
expect(after).not.toBe(before);
|
|
55
|
+
expect(after).toContain("audit_module = \"_\"");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns an empty list and leaves Move.toml byte-identical when all named addresses are already declared", () => {
|
|
59
|
+
const before = `[package]
|
|
60
|
+
name = "captured"
|
|
61
|
+
|
|
62
|
+
[addresses]
|
|
63
|
+
existing = "_"
|
|
64
|
+
|
|
65
|
+
[dev-addresses]
|
|
66
|
+
existing = "0xcafe"
|
|
67
|
+
`;
|
|
68
|
+
vol.fromJSON({ "/move/Move.toml": before });
|
|
69
|
+
|
|
70
|
+
const added = updateMoveToml("/move", new Set(["existing"]));
|
|
71
|
+
expect(added).toEqual([]);
|
|
72
|
+
// No mutation in the no-op path — the asymmetry only triggers when
|
|
73
|
+
// detected ⊄ declared. Useful boundary for the F7 product decision.
|
|
74
|
+
const after = vol.readFileSync("/move/Move.toml", "utf-8") as string;
|
|
75
|
+
expect(after).toBe(before);
|
|
76
|
+
});
|
|
77
|
+
});
|