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
|
@@ -13,7 +13,8 @@ vi.mock("../../helpers/banner.js", () => ({
|
|
|
13
13
|
printMovehatBanner: () => undefined,
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const initModule = await import("../init.js");
|
|
17
|
+
const { default: initCommand, resolveProjectNames, InvalidProjectNameError } = initModule;
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Strategy: real tmpdir + real fs ops (matches §6.1's example-as-canonical
|
|
@@ -28,10 +29,38 @@ const { default: initCommand } = await import("../init.js");
|
|
|
28
29
|
* to `src/templates/` cleanly. No bundling step required.
|
|
29
30
|
*/
|
|
30
31
|
|
|
32
|
+
describe("resolveProjectNames", () => {
|
|
33
|
+
const cases: Array<[
|
|
34
|
+
string,
|
|
35
|
+
{ dirName: string; npmName: string; moveName: string; sanitized: boolean }
|
|
36
|
+
]> = [
|
|
37
|
+
["my_project", { dirName: "my_project", npmName: "my_project", moveName: "my_project", sanitized: false }],
|
|
38
|
+
["my-project", { dirName: "my-project", npmName: "my-project", moveName: "my_project", sanitized: true }],
|
|
39
|
+
["/tmp/my-project", { dirName: "/tmp/my-project", npmName: "my-project", moveName: "my_project", sanitized: true }],
|
|
40
|
+
["123abc", { dirName: "123abc", npmName: "123abc", moveName: "pkg_123abc", sanitized: true }],
|
|
41
|
+
["_underscore", { dirName: "_underscore", npmName: "_underscore", moveName: "_underscore", sanitized: false }],
|
|
42
|
+
["UPPER", { dirName: "UPPER", npmName: "UPPER", moveName: "UPPER", sanitized: false }],
|
|
43
|
+
[" spaced ", { dirName: "spaced", npmName: "spaced", moveName: "spaced", sanitized: false }],
|
|
44
|
+
["with spaces", { dirName: "with spaces", npmName: "with spaces", moveName: "with_spaces", sanitized: true }],
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
it.each(cases)("derives names for %j", (input, expected) => {
|
|
48
|
+
expect(resolveProjectNames(input)).toEqual(expected);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it.each(["", " ", ".", "..", "/", "////"])(
|
|
52
|
+
"rejects %j as an invalid project name",
|
|
53
|
+
(input) => {
|
|
54
|
+
expect(() => resolveProjectNames(input)).toThrow(InvalidProjectNameError);
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
31
59
|
describe("initCommand", () => {
|
|
32
60
|
let tmpParent: string;
|
|
33
61
|
let origCwd: string;
|
|
34
62
|
let exitSpy: ReturnType<typeof vi.spyOn>;
|
|
63
|
+
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
35
64
|
|
|
36
65
|
beforeEach(() => {
|
|
37
66
|
promptsMock.mockReset();
|
|
@@ -48,6 +77,7 @@ describe("initCommand", () => {
|
|
|
48
77
|
}) as never);
|
|
49
78
|
vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
50
79
|
vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
80
|
+
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
51
81
|
});
|
|
52
82
|
|
|
53
83
|
afterEach(() => {
|
|
@@ -59,9 +89,9 @@ describe("initCommand", () => {
|
|
|
59
89
|
});
|
|
60
90
|
|
|
61
91
|
it("happy path: scaffolds a project from the canonical template", async () => {
|
|
62
|
-
await initCommand("
|
|
92
|
+
await initCommand("my_test_project");
|
|
63
93
|
|
|
64
|
-
const target = join(tmpParent, "
|
|
94
|
+
const target = join(tmpParent, "my_test_project");
|
|
65
95
|
expect(existsSync(target)).toBe(true);
|
|
66
96
|
// Canonical files at the project root.
|
|
67
97
|
expect(existsSync(join(target, "package.json"))).toBe(true);
|
|
@@ -78,32 +108,37 @@ describe("initCommand", () => {
|
|
|
78
108
|
// Excluded template-development directory.
|
|
79
109
|
expect(existsSync(join(target, "types"))).toBe(false);
|
|
80
110
|
expect(existsSync(join(target, ".vscode"))).toBe(false);
|
|
111
|
+
// Move.toml gets a valid Move identifier.
|
|
112
|
+
const moveToml = readFileSync(join(target, "move", "Move.toml"), "utf-8");
|
|
113
|
+
expect(moveToml).toContain('name = "my_test_project"');
|
|
114
|
+
expect(moveToml).not.toContain("{{movePackageName}}");
|
|
115
|
+
expect(moveToml).not.toContain("{{projectName}}");
|
|
81
116
|
});
|
|
82
117
|
|
|
83
118
|
it("substitutes {{projectName}} placeholders inside template files", async () => {
|
|
84
|
-
await initCommand("
|
|
119
|
+
await initCommand("substituted_project");
|
|
85
120
|
|
|
86
121
|
const pkg = readFileSync(
|
|
87
|
-
join(tmpParent, "
|
|
122
|
+
join(tmpParent, "substituted_project", "package.json"),
|
|
88
123
|
"utf-8"
|
|
89
124
|
);
|
|
90
|
-
expect(pkg).toContain("
|
|
125
|
+
expect(pkg).toContain("substituted_project");
|
|
91
126
|
expect(pkg).not.toContain("{{projectName}}");
|
|
92
127
|
|
|
93
128
|
const readme = readFileSync(
|
|
94
|
-
join(tmpParent, "
|
|
129
|
+
join(tmpParent, "substituted_project", "README.md"),
|
|
95
130
|
"utf-8"
|
|
96
131
|
);
|
|
97
|
-
expect(readme).toContain("
|
|
132
|
+
expect(readme).toContain("substituted_project");
|
|
98
133
|
});
|
|
99
134
|
|
|
100
135
|
it("prompts for project name when none is provided", async () => {
|
|
101
|
-
promptsMock.mockResolvedValueOnce({ projectName: "
|
|
136
|
+
promptsMock.mockResolvedValueOnce({ projectName: "from_prompt" });
|
|
102
137
|
|
|
103
138
|
await initCommand();
|
|
104
139
|
|
|
105
140
|
expect(promptsMock).toHaveBeenCalledTimes(1);
|
|
106
|
-
expect(existsSync(join(tmpParent, "
|
|
141
|
+
expect(existsSync(join(tmpParent, "from_prompt"))).toBe(true);
|
|
107
142
|
});
|
|
108
143
|
|
|
109
144
|
it("exits 0 when the user Ctrl+Cs the prompt (no project created)", async () => {
|
|
@@ -116,10 +151,60 @@ describe("initCommand", () => {
|
|
|
116
151
|
it("exits 1 when the template copy step hits an unwriteable target", async () => {
|
|
117
152
|
// Plant a directory where the command will try to write package.json
|
|
118
153
|
// as a file — fs.writeFile rejects with EISDIR.
|
|
119
|
-
const targetName = "
|
|
154
|
+
const targetName = "blocked_project";
|
|
120
155
|
mkdirSync(join(tmpParent, targetName, "package.json"), { recursive: true });
|
|
121
156
|
|
|
122
157
|
await expect(initCommand(targetName)).rejects.toThrow("__test_exit_1__");
|
|
123
158
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
124
159
|
});
|
|
160
|
+
|
|
161
|
+
it("sanitizes Move.toml for hyphenated names but keeps package.json original", async () => {
|
|
162
|
+
await initCommand("my-project");
|
|
163
|
+
|
|
164
|
+
const target = join(tmpParent, "my-project");
|
|
165
|
+
expect(existsSync(target)).toBe(true);
|
|
166
|
+
// package.json keeps the original (npm allows hyphens).
|
|
167
|
+
const pkg = readFileSync(join(target, "package.json"), "utf-8");
|
|
168
|
+
expect(pkg).toContain('"my-project"');
|
|
169
|
+
// Move.toml gets the sanitized identifier.
|
|
170
|
+
const moveToml = readFileSync(join(target, "move", "Move.toml"), "utf-8");
|
|
171
|
+
expect(moveToml).toContain('name = "my_project"');
|
|
172
|
+
expect(moveToml).not.toContain('"my-project"');
|
|
173
|
+
// Warning was emitted.
|
|
174
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
175
|
+
const warningText = warnSpy.mock.calls.flat().join(" ");
|
|
176
|
+
expect(warningText).toContain("my-project");
|
|
177
|
+
expect(warningText).toContain("my_project");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("with a path argument, creates the dir at the full path and sanitizes Move.toml", async () => {
|
|
181
|
+
const nestedPath = join(tmpParent, "nested", "sub-project");
|
|
182
|
+
|
|
183
|
+
await initCommand(nestedPath);
|
|
184
|
+
|
|
185
|
+
expect(existsSync(nestedPath)).toBe(true);
|
|
186
|
+
const moveToml = readFileSync(join(nestedPath, "move", "Move.toml"), "utf-8");
|
|
187
|
+
expect(moveToml).toContain('name = "sub_project"');
|
|
188
|
+
const pkg = readFileSync(join(nestedPath, "package.json"), "utf-8");
|
|
189
|
+
expect(pkg).toContain('"sub-project"');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("prefixes Move.toml package name with pkg_ for names that start with a digit", async () => {
|
|
193
|
+
await initCommand("123abc");
|
|
194
|
+
|
|
195
|
+
const moveToml = readFileSync(
|
|
196
|
+
join(tmpParent, "123abc", "move", "Move.toml"),
|
|
197
|
+
"utf-8"
|
|
198
|
+
);
|
|
199
|
+
expect(moveToml).toContain('name = "pkg_123abc"');
|
|
200
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it.each([".", "..", "/", " "])(
|
|
204
|
+
"rejects %j as an invalid project name and exits 1",
|
|
205
|
+
async (input) => {
|
|
206
|
+
await expect(initCommand(input)).rejects.toThrow("__test_exit_1__");
|
|
207
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
208
|
+
}
|
|
209
|
+
);
|
|
125
210
|
});
|
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/init.ts
CHANGED
|
@@ -8,6 +8,60 @@ import { logger, createSpinnerChain, formatCommand } from "../ui/index.js";
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = path.dirname(__filename);
|
|
10
10
|
|
|
11
|
+
export class InvalidProjectNameError extends Error {
|
|
12
|
+
constructor(message: string) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "InvalidProjectNameError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ProjectNames {
|
|
19
|
+
/** User input as-is (after trim) — used for the filesystem target. */
|
|
20
|
+
dirName: string;
|
|
21
|
+
/** Basename of the resolved path — used for package.json + README.md. */
|
|
22
|
+
npmName: string;
|
|
23
|
+
/** Valid Move identifier — used for Move.toml. */
|
|
24
|
+
moveName: string;
|
|
25
|
+
/** True when moveName had to diverge from npmName to satisfy Move identifier rules. */
|
|
26
|
+
sanitized: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Derive filesystem, npm, and Move identifier names from a single user input.
|
|
31
|
+
*
|
|
32
|
+
* Move identifiers must match `[a-zA-Z_][a-zA-Z0-9_]*`. npm package names are
|
|
33
|
+
* looser (hyphens allowed). The filesystem accepts almost anything. Rather
|
|
34
|
+
* than reject inputs that would compile fine for npm but not for Move, we
|
|
35
|
+
* sanitize the Move identifier and leave the npm name + dir unchanged.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveProjectNames(input: string): ProjectNames {
|
|
38
|
+
const trimmed = input.trim();
|
|
39
|
+
if (
|
|
40
|
+
trimmed === "" ||
|
|
41
|
+
trimmed === "." ||
|
|
42
|
+
trimmed === ".." ||
|
|
43
|
+
/^\/+$/.test(trimmed)
|
|
44
|
+
) {
|
|
45
|
+
throw new InvalidProjectNameError(
|
|
46
|
+
`Project name '${input}' must include at least one character besides path separators.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const dirName = trimmed;
|
|
51
|
+
const npmName = path.basename(path.resolve(trimmed));
|
|
52
|
+
let moveName = npmName.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
53
|
+
if (!/^[a-zA-Z_]/.test(moveName)) {
|
|
54
|
+
moveName = `pkg_${moveName}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
dirName,
|
|
59
|
+
npmName,
|
|
60
|
+
moveName,
|
|
61
|
+
sanitized: moveName !== npmName,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
11
65
|
/**
|
|
12
66
|
* Initialize a new Movehat project with template files
|
|
13
67
|
*
|
|
@@ -49,8 +103,25 @@ export default async function initCommand(projectName?: string) {
|
|
|
49
103
|
projectName = response.projectName;
|
|
50
104
|
}
|
|
51
105
|
|
|
52
|
-
|
|
53
|
-
|
|
106
|
+
let names: ProjectNames;
|
|
107
|
+
try {
|
|
108
|
+
names = resolveProjectNames(projectName!);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error instanceof InvalidProjectNameError) {
|
|
111
|
+
logger.error(error.message);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { dirName, npmName, moveName, sanitized } = names;
|
|
118
|
+
const projectPath = path.resolve(process.cwd(), dirName);
|
|
119
|
+
|
|
120
|
+
if (sanitized) {
|
|
121
|
+
logger.warning(
|
|
122
|
+
`'${npmName}' is not a valid Move identifier; using '${moveName}' for move/Move.toml. (package.json keeps '${npmName}'.)`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
54
125
|
|
|
55
126
|
logger.newline();
|
|
56
127
|
logger.info(`Initializing new Movehat project in ${projectPath}...`);
|
|
@@ -67,7 +138,7 @@ export default async function initCommand(projectName?: string) {
|
|
|
67
138
|
await copyFile(
|
|
68
139
|
path.join(templatesDir, "package.json"),
|
|
69
140
|
path.join(projectPath, "package.json"),
|
|
70
|
-
{ projectName:
|
|
141
|
+
{ projectName: npmName }
|
|
71
142
|
);
|
|
72
143
|
|
|
73
144
|
await copyFile(
|
|
@@ -98,7 +169,7 @@ export default async function initCommand(projectName?: string) {
|
|
|
98
169
|
await copyFile(
|
|
99
170
|
path.join(templatesDir, "README.md"),
|
|
100
171
|
path.join(projectPath, "README.md"),
|
|
101
|
-
{ projectName:
|
|
172
|
+
{ projectName: npmName }
|
|
102
173
|
);
|
|
103
174
|
});
|
|
104
175
|
|
|
@@ -107,7 +178,7 @@ export default async function initCommand(projectName?: string) {
|
|
|
107
178
|
await copyDir(
|
|
108
179
|
path.join(templatesDir, "move"),
|
|
109
180
|
path.join(projectPath, "move"),
|
|
110
|
-
{ projectName:
|
|
181
|
+
{ projectName: npmName, movePackageName: moveName }
|
|
111
182
|
);
|
|
112
183
|
});
|
|
113
184
|
|
|
@@ -136,7 +207,7 @@ export default async function initCommand(projectName?: string) {
|
|
|
136
207
|
|
|
137
208
|
// Next steps
|
|
138
209
|
logger.section('Next steps');
|
|
139
|
-
logger.item(formatCommand(`cd ${
|
|
210
|
+
logger.item(formatCommand(`cd ${dirName}`), 2);
|
|
140
211
|
logger.item(formatCommand('cp .env.example .env'), 2);
|
|
141
212
|
logger.plain(' # Edit .env with your credentials');
|
|
142
213
|
logger.item(formatCommand('npm install'), 2);
|
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
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Account,
|
|
3
3
|
Ed25519PrivateKey,
|
|
4
|
+
PrivateKey,
|
|
5
|
+
PrivateKeyVariants,
|
|
4
6
|
} from "@aptos-labs/ts-sdk";
|
|
5
7
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
6
8
|
import { join } from "path";
|
|
@@ -25,10 +27,18 @@ interface AccountPool {
|
|
|
25
27
|
* Provides a pool of reusable test accounts with labels for better test readability.
|
|
26
28
|
*/
|
|
27
29
|
export class AccountManager {
|
|
30
|
+
// Class-static maps: shared across every consumer in the same Node
|
|
31
|
+
// process (e.g. two Harness instances). Re-using a label across
|
|
32
|
+
// "sessions" overwrites the labelMap entry. Documented + fixed by
|
|
33
|
+
// contract in `AccountManager.global-state.test.ts` (audit F8).
|
|
28
34
|
private static pool: Map<string, Account> = new Map(); // address → Account
|
|
29
35
|
private static privateKeys: Map<string, string> = new Map(); // address → privateKey hex
|
|
30
36
|
private static labelMap: Map<string, string> = new Map(); // label → address
|
|
31
37
|
private static poolLoaded = false;
|
|
38
|
+
// `defaultPoolPath` is captured ONCE at module-import time. A later
|
|
39
|
+
// `process.chdir(...)` does NOT redirect the save destination — pass
|
|
40
|
+
// an explicit `poolPath` to `saveAccountPool`/`loadAccountPool` when
|
|
41
|
+
// per-test isolation matters. See audit finding F8.
|
|
32
42
|
private static defaultPoolPath = join(process.cwd(), ".movehat", "accounts");
|
|
33
43
|
|
|
34
44
|
/**
|
|
@@ -131,7 +141,14 @@ export class AccountManager {
|
|
|
131
141
|
* const account = AccountManager.loadAccountFromPrivateKey("0xabc123...");
|
|
132
142
|
*/
|
|
133
143
|
static loadAccountFromPrivateKey(privateKeyHex: string): Account {
|
|
134
|
-
|
|
144
|
+
// Format into AIP-80 shape (`ed25519-priv-0x…`) before constructing
|
|
145
|
+
// the SDK type. Without this, raw-hex inputs trigger a noisy
|
|
146
|
+
// deprecation warning from `@aptos-labs/ts-sdk` on every call.
|
|
147
|
+
const formatted = PrivateKey.formatPrivateKey(
|
|
148
|
+
privateKeyHex,
|
|
149
|
+
PrivateKeyVariants.Ed25519,
|
|
150
|
+
);
|
|
151
|
+
const privateKey = new Ed25519PrivateKey(formatted);
|
|
135
152
|
const account = Account.fromPrivateKey({ privateKey });
|
|
136
153
|
const address = account.accountAddress.toString();
|
|
137
154
|
|