movehat 0.2.0 → 0.2.2
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/README.md +132 -279
- 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 +7 -2
- 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/__tests__/harness/Harness.proxy.test.js +7 -11
- package/dist/__tests__/harness/Harness.proxy.test.js.map +1 -1
- package/dist/__tests__/harness/codeObject.deploy.test.js +1 -1
- package/dist/__tests__/harness/codeObject.deploy.test.js.map +1 -1
- package/dist/__tests__/harness/view.test.js +3 -3
- 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/__tests__/run.test.js +3 -3
- package/dist/commands/__tests__/run.test.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/core/AccountManager.d.ts +0 -3
- package/dist/core/AccountManager.d.ts.map +1 -1
- package/dist/core/AccountManager.js +14 -7
- package/dist/core/AccountManager.js.map +1 -1
- package/dist/core/Publisher.d.ts +0 -5
- package/dist/core/Publisher.d.ts.map +1 -1
- package/dist/core/Publisher.js +52 -76
- 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.js +6 -5
- package/dist/core/config.js.map +1 -1
- package/dist/core/contract.d.ts +0 -3
- package/dist/core/contract.d.ts.map +1 -1
- package/dist/core/contract.js +0 -3
- package/dist/core/contract.js.map +1 -1
- package/dist/core/deployments.d.ts +0 -6
- package/dist/core/deployments.d.ts.map +1 -1
- package/dist/core/deployments.js +0 -12
- 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__/manager.test.js +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.d.ts +1 -21
- package/dist/fork/manager.d.ts.map +1 -1
- package/dist/fork/manager.js +1 -41
- 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 +19 -9
- package/dist/fork/server.js.map +1 -1
- package/dist/fork/test.d.ts +0 -1
- package/dist/fork/test.d.ts.map +1 -1
- package/dist/fork/test.js.map +1 -1
- package/dist/harness/Harness.d.ts +11 -13
- package/dist/harness/Harness.d.ts.map +1 -1
- package/dist/harness/Harness.js +13 -13
- package/dist/harness/Harness.js.map +1 -1
- package/dist/harness/codeObject.d.ts.map +1 -1
- package/dist/harness/codeObject.js +31 -38
- 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 +33 -29
- 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 +1 -2
- package/dist/helpers/setupLocalTesting.d.ts.map +1 -1
- package/dist/helpers/setupLocalTesting.js +28 -2
- 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/index.js +0 -1
- package/dist/index.js.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 +10 -1
- 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 +4 -3
- package/dist/node/__tests__/LocalNodeManager.test.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +1 -3
- package/dist/runtime.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 +11 -1
- package/dist/templates/tests/Counter.test.ts +2 -2
- package/dist/types/config.d.ts +8 -1
- package/dist/types/config.d.ts.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/address.d.ts +0 -4
- package/dist/utils/address.d.ts.map +1 -1
- package/dist/utils/address.js +0 -4
- package/dist/utils/address.js.map +1 -1
- package/dist/utils/childProcessAdapter.d.ts +7 -0
- package/dist/utils/childProcessAdapter.d.ts.map +1 -1
- package/dist/utils/childProcessAdapter.js +23 -6
- package/dist/utils/childProcessAdapter.js.map +1 -1
- package/package.json +2 -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 +7 -2
- package/src/__tests__/fork/api.timeout.test.ts +150 -0
- package/src/__tests__/harness/Harness.proxy.test.ts +7 -11
- package/src/__tests__/harness/codeObject.deploy.test.ts +1 -1
- package/src/__tests__/harness/view.test.ts +3 -3
- package/src/commands/__tests__/compile.toml-mutation.test.ts +77 -0
- package/src/commands/__tests__/init.test.ts +96 -11
- package/src/commands/__tests__/run.test.ts +3 -3
- package/src/commands/init.ts +77 -6
- package/src/core/AccountManager.ts +18 -13
- package/src/core/Publisher.ts +58 -85
- 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 +9 -5
- package/src/core/contract.ts +0 -3
- package/src/core/deployments.ts +0 -12
- package/src/core/movementProfile.ts +75 -127
- package/src/fork/__tests__/manager.test.ts +1 -1
- package/src/fork/__tests__/server.cors.test.ts +101 -0
- package/src/fork/api.ts +69 -10
- package/src/fork/manager.ts +1 -41
- package/src/fork/server.ts +38 -9
- package/src/fork/test.ts +0 -1
- package/src/harness/Harness.ts +16 -13
- package/src/harness/codeObject.ts +38 -48
- package/src/harness/script.ts +40 -39
- package/src/helpers/__tests__/setupLocalTesting.fork-network.test.ts +212 -0
- package/src/helpers/setupLocalTesting.ts +37 -4
- package/src/index.ts +9 -2
- package/src/node/LocalNodeManager.ts +24 -2
- package/src/node/__tests__/LocalNodeManager.api-port.test.ts +62 -0
- package/src/node/__tests__/LocalNodeManager.test.ts +5 -4
- package/src/runtime.ts +1 -3
- package/src/templates/move/Move.toml +1 -1
- package/src/templates/move/sources/Counter.move +31 -4
- package/src/templates/scripts/deploy-counter.ts +11 -1
- package/src/templates/tests/Counter.test.ts +2 -2
- package/src/types/config.ts +8 -1
- package/src/types/runtime.ts +2 -2
- package/src/utils/__tests__/childProcessAdapter.maxBuffer.test.ts +51 -0
- package/src/utils/address.ts +0 -4
- package/src/utils/childProcessAdapter.ts +35 -6
|
@@ -35,6 +35,13 @@ export interface RunInput {
|
|
|
35
35
|
* Default: `false`.
|
|
36
36
|
*/
|
|
37
37
|
inheritStdio?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Maximum combined bytes (stdout + stderr) the captured Buffers may
|
|
40
|
+
* grow to before the child is killed and the promise rejects. Defaults
|
|
41
|
+
* to 64 MiB. Set to `Infinity` to disable. Ignored when
|
|
42
|
+
* `inheritStdio` is `true` (no buffering happens).
|
|
43
|
+
*/
|
|
44
|
+
maxBuffer?: number;
|
|
38
45
|
}
|
|
39
46
|
export interface RunResult {
|
|
40
47
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"childProcessAdapter.d.ts","sourceRoot":"","sources":["../../src/utils/childProcessAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACzC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CAAC;CAC1C;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"childProcessAdapter.d.ts","sourceRoot":"","sources":["../../src/utils/childProcessAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACzC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CAAC;CAC1C;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;IACvC;;;;OAIG;IACH,MAAM,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CACzE;AA6ID,eAAO,MAAM,0BAA0B,EAAE,mBAAsD,CAAC"}
|
|
@@ -1,13 +1,13 @@
|
|
|
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) {
|
|
5
6
|
// Skip the default 5-minute timeout when the caller wires stdio
|
|
6
7
|
// through to the terminal — interactive sessions (mocha, tsx scripts,
|
|
7
|
-
// `movement move test`, `pnpm install`) routinely exceed 5 minutes
|
|
8
|
-
// SIGTERM'ing them silently
|
|
9
|
-
//
|
|
10
|
-
// is always honored, regardless of `inheritStdio`.
|
|
8
|
+
// `movement move test`, `pnpm install`) routinely exceed 5 minutes
|
|
9
|
+
// and SIGTERM'ing them silently would be a regression. An explicit
|
|
10
|
+
// `timeoutMs` is always honored, regardless of `inheritStdio`.
|
|
11
11
|
const timeoutMs = input.timeoutMs ?? (input.inheritStdio ? undefined : DEFAULT_TIMEOUT_MS);
|
|
12
12
|
return new Promise((resolve, reject) => {
|
|
13
13
|
const child = spawn(input.command, [...input.args], {
|
|
@@ -17,9 +17,26 @@ class DefaultChildProcessAdapter {
|
|
|
17
17
|
});
|
|
18
18
|
const stdoutChunks = [];
|
|
19
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
|
+
};
|
|
20
37
|
// Streams are null when stdio is 'inherit'; the `?.` covers that.
|
|
21
|
-
child.stdout?.on('data', (
|
|
22
|
-
child.stderr?.on('data', (
|
|
38
|
+
child.stdout?.on('data', onChunk(stdoutChunks));
|
|
39
|
+
child.stderr?.on('data', onChunk(stderrChunks));
|
|
23
40
|
let timeoutHandle;
|
|
24
41
|
const clearTimer = () => {
|
|
25
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "movehat",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Hardhat-like development framework for Movement L1 smart contracts",
|
|
6
6
|
"bin": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
28
28
|
"docs:api": "typedoc && node scripts/postprocess-typedoc.mjs",
|
|
29
29
|
"bench": "cd ../../examples/counter-example && tsx ../../packages/movehat/bench/fork.bench.ts",
|
|
30
|
+
"prepack": "cp ../../README.md README.md",
|
|
30
31
|
"prepublishOnly": "npm run build"
|
|
31
32
|
},
|
|
32
33
|
"files": [
|
|
@@ -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 {
|
|
@@ -3,8 +3,8 @@ import type { ClientRequest, IncomingMessage } from "node:http";
|
|
|
3
3
|
import { EventEmitter } from "node:events";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Tests for `MovementApiClient` Authorization header injection
|
|
7
|
-
*
|
|
6
|
+
* Tests for `MovementApiClient` Authorization header injection —
|
|
7
|
+
* verifies the apiKey threading from `Harness.createFork`.
|
|
8
8
|
*
|
|
9
9
|
* Strategy: `vi.mock` `node:https` and `node:http` so the test
|
|
10
10
|
* captures the request options passed to `client.get(url, options, cb)`
|
|
@@ -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
|
+
});
|
|
@@ -4,17 +4,15 @@ import { Harness, HarnessDisposedError } from "../../harness/index.js";
|
|
|
4
4
|
import { setupHarnessTestFixture, type HarnessTestFixture } from "./_fixture.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Proxy poisoning is the load-bearing safety guarantee of
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* lock that contract.
|
|
7
|
+
* Proxy poisoning is the load-bearing safety guarantee of the Harness:
|
|
8
|
+
* once cleaned up, any further deploy / view / script / upgrade call
|
|
9
|
+
* must throw `HarnessDisposedError` synchronously on property access —
|
|
10
|
+
* not after the awaited method body. These tests lock that contract.
|
|
12
11
|
*
|
|
13
12
|
* Uses `Harness.createLive(network)` because it does not spawn a real
|
|
14
13
|
* Movement node — `initRuntime` only constructs the SDK client (no RPC
|
|
15
14
|
* round-trip) from the fixture config. `createLocal` / `createFork`
|
|
16
|
-
* runtime tests live in the
|
|
17
|
-
* node is acceptable.
|
|
15
|
+
* runtime tests live in the integration suite.
|
|
18
16
|
*/
|
|
19
17
|
describe("Harness — proxy poisoning", () => {
|
|
20
18
|
let fixture: HarnessTestFixture;
|
|
@@ -93,10 +91,8 @@ describe("Harness — proxy poisoning", () => {
|
|
|
93
91
|
expect(harness.runtime).toBeDefined();
|
|
94
92
|
});
|
|
95
93
|
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
// now have dedicated test suites (codeObject.deploy/upgrade.test.ts,
|
|
99
|
-
// view.test.ts, script.test.ts).
|
|
94
|
+
// Dedicated suites exist for each of the four methods
|
|
95
|
+
// (codeObject.deploy/upgrade.test.ts, view.test.ts, script.test.ts).
|
|
100
96
|
|
|
101
97
|
it("await harness.someAsyncMethod() pattern: post-cleanup throw happens before await", async () => {
|
|
102
98
|
const harness = await Harness.createLive("testnet");
|
|
@@ -304,7 +304,7 @@ describe("Harness.deployCodeObject", () => {
|
|
|
304
304
|
}
|
|
305
305
|
});
|
|
306
306
|
|
|
307
|
-
it("fork-mode harness throws synchronously before any CLI call (
|
|
307
|
+
it("fork-mode harness throws synchronously before any CLI call (createLocal/createFork covered by the integration suite)", async () => {
|
|
308
308
|
// This case can't be tested with a real createFork (it spawns a fork
|
|
309
309
|
// server). Instead, prove that the disposed-instance path also fires
|
|
310
310
|
// synchronously: a poisoned harness throws HarnessDisposedError on
|
|
@@ -7,9 +7,9 @@ import { setupHarnessTestFixture, type HarnessTestFixture } from "./_fixture.js"
|
|
|
7
7
|
* Tests for `Harness.runViewFunction` — the SDK delegation path.
|
|
8
8
|
*
|
|
9
9
|
* The Aptos SDK isn't behind an injectable adapter, so we monkey-patch
|
|
10
|
-
* `harness.runtime.aptos.view` after `createLive`.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* `harness.runtime.aptos.view` after `createLive`. Acceptable here
|
|
11
|
+
* because the function under test is a 6-line wrapper that only
|
|
12
|
+
* forwards options.
|
|
13
13
|
*/
|
|
14
14
|
describe("Harness.runViewFunction", () => {
|
|
15
15
|
let fixture: HarnessTestFixture;
|