movehat 0.0.8-alpha.0 → 0.0.10-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +32 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/test-move.d.ts +7 -0
  5. package/dist/commands/test-move.d.ts.map +1 -0
  6. package/dist/commands/test-move.js +20 -0
  7. package/dist/commands/test-move.js.map +1 -0
  8. package/dist/commands/test.d.ts +8 -1
  9. package/dist/commands/test.d.ts.map +1 -1
  10. package/dist/commands/test.js +90 -24
  11. package/dist/commands/test.js.map +1 -1
  12. package/dist/commands/update.d.ts +5 -0
  13. package/dist/commands/update.d.ts.map +1 -0
  14. package/dist/commands/update.js +121 -0
  15. package/dist/commands/update.js.map +1 -0
  16. package/dist/helpers/move-tests.d.ts +13 -0
  17. package/dist/helpers/move-tests.d.ts.map +1 -0
  18. package/dist/helpers/move-tests.js +54 -0
  19. package/dist/helpers/move-tests.js.map +1 -0
  20. package/dist/helpers/npm-registry.d.ts +19 -0
  21. package/dist/helpers/npm-registry.d.ts.map +1 -0
  22. package/dist/helpers/npm-registry.js +47 -0
  23. package/dist/helpers/npm-registry.js.map +1 -0
  24. package/dist/helpers/semver-utils.d.ts +7 -0
  25. package/dist/helpers/semver-utils.d.ts.map +1 -0
  26. package/dist/helpers/semver-utils.js +47 -0
  27. package/dist/helpers/semver-utils.js.map +1 -0
  28. package/dist/helpers/version-check.d.ts +6 -0
  29. package/dist/helpers/version-check.d.ts.map +1 -0
  30. package/dist/helpers/version-check.js +85 -0
  31. package/dist/helpers/version-check.js.map +1 -0
  32. package/dist/templates/README.md +20 -6
  33. package/dist/templates/move/Move.toml +3 -2
  34. package/dist/templates/package.json +4 -2
  35. package/package.json +1 -1
  36. package/src/cli.ts +37 -2
  37. package/src/commands/test-move.ts +26 -0
  38. package/src/commands/test.ts +106 -27
  39. package/src/commands/update.ts +148 -0
  40. package/src/helpers/move-tests.ts +68 -0
  41. package/src/helpers/npm-registry.ts +71 -0
  42. package/src/helpers/semver-utils.ts +53 -0
  43. package/src/helpers/version-check.ts +97 -0
  44. package/src/templates/README.md +20 -6
  45. package/src/templates/move/Move.toml +3 -2
  46. package/src/templates/package.json +4 -2
@@ -0,0 +1,68 @@
1
+ import { spawn } from "child_process";
2
+ import { existsSync } from "fs";
3
+ import { resolve } from "path";
4
+ import { loadUserConfig } from "../core/config.js";
5
+
6
+ interface RunMoveTestsOptions {
7
+ filter?: string;
8
+ ignoreWarnings?: boolean;
9
+ skipIfMissing?: boolean; // If true, skip gracefully when Move dir missing (for orchestrated tests)
10
+ }
11
+
12
+ /**
13
+ * Run Move unit tests using Movement CLI
14
+ * @param options Test options including filter, warnings, and skip behavior
15
+ * @returns Promise that resolves when tests complete successfully
16
+ */
17
+ export async function runMoveTests(options: RunMoveTestsOptions = {}): Promise<void> {
18
+ const userConfig = await loadUserConfig();
19
+ const moveDir = resolve(process.cwd(), userConfig.moveDir || "./move");
20
+
21
+ if (!existsSync(moveDir)) {
22
+ if (options.skipIfMissing) {
23
+ console.log("⊘ No Move directory found (./move not found)");
24
+ console.log(" Skipping Move tests...\n");
25
+ return;
26
+ } else {
27
+ throw new Error(
28
+ `Move directory not found: ${moveDir}\n` +
29
+ ` Update movehat.config.ts -> moveDir`
30
+ );
31
+ }
32
+ }
33
+
34
+ const args = ["move", "test", "--package-dir", moveDir];
35
+
36
+ // Add dev flag for auto-detected addresses
37
+ args.push("--dev");
38
+
39
+ if (options.filter) {
40
+ args.push("--filter", options.filter);
41
+ }
42
+
43
+ if (options.ignoreWarnings) {
44
+ args.push("--ignore-compile-warnings");
45
+ }
46
+
47
+ return new Promise<void>((resolve, reject) => {
48
+ const child = spawn("movement", args, {
49
+ stdio: "inherit",
50
+ cwd: process.cwd(),
51
+ });
52
+
53
+ child.on("exit", (code) => {
54
+ if (code === 0) {
55
+ console.log("\n✓ Move tests passed");
56
+ resolve();
57
+ } else {
58
+ reject(new Error("Move tests failed"));
59
+ }
60
+ });
61
+
62
+ child.on("error", (error) => {
63
+ console.error(`Failed to run Move tests: ${error.message}`);
64
+ console.error(" Make sure Movement CLI is installed");
65
+ reject(error);
66
+ });
67
+ });
68
+ }
@@ -0,0 +1,71 @@
1
+ export interface NpmRegistryResponse {
2
+ "dist-tags": {
3
+ latest: string;
4
+ [tag: string]: string;
5
+ };
6
+ versions: Record<string, unknown>;
7
+ }
8
+
9
+ export interface FetchOptions {
10
+ timeout?: number; // Timeout in milliseconds
11
+ throwOnError?: boolean; // If true, throw errors; if false, return null on error
12
+ }
13
+
14
+ /**
15
+ * Fetch latest version from npm registry
16
+ * @param packageName - The npm package name
17
+ * @param options - Fetch options (timeout, error handling)
18
+ * @returns Latest version string, or null if failed and throwOnError is false
19
+ */
20
+ export async function fetchLatestVersion(
21
+ packageName: string,
22
+ options: FetchOptions = {}
23
+ ): Promise<string | null> {
24
+ const { timeout = 0, throwOnError = false } = options;
25
+
26
+ try {
27
+ const controller = timeout > 0 ? new AbortController() : undefined;
28
+ const timeoutId = timeout > 0 && controller
29
+ ? setTimeout(() => controller.abort(), timeout)
30
+ : undefined;
31
+
32
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`, {
33
+ signal: controller?.signal,
34
+ });
35
+
36
+ if (timeoutId) clearTimeout(timeoutId);
37
+
38
+ if (!response.ok) {
39
+ const error = new Error(`Failed to fetch package info: ${response.statusText}`);
40
+ if (throwOnError) throw error;
41
+ return null;
42
+ }
43
+
44
+ const data = (await response.json()) as NpmRegistryResponse;
45
+
46
+ // Defensive validation of registry response structure
47
+ if (
48
+ !data ||
49
+ typeof data !== "object" ||
50
+ !data["dist-tags"] ||
51
+ typeof data["dist-tags"] !== "object" ||
52
+ typeof data["dist-tags"].latest !== "string"
53
+ ) {
54
+ const error = new Error(
55
+ `Invalid npm registry response: missing or malformed "dist-tags.latest" field`
56
+ );
57
+ if (throwOnError) throw error;
58
+ return null;
59
+ }
60
+
61
+ return data["dist-tags"].latest;
62
+ } catch (error) {
63
+ if (throwOnError) {
64
+ throw new Error(
65
+ `Failed to check for updates: ${error instanceof Error ? error.message : String(error)}`
66
+ );
67
+ }
68
+ // Silently fail - don't interrupt user's workflow
69
+ return null;
70
+ }
71
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Compare two semver versions
3
+ * Returns true if newVersion > currentVersion
4
+ * Handles variable-length versions (1.2, 1.2.3, 1.2.3.4, etc.)
5
+ */
6
+ export function isNewerVersion(currentVersion: string, newVersion: string): boolean {
7
+ // Remove any pre-release tags (e.g., -alpha.0, -beta.1)
8
+ const cleanCurrent = currentVersion.split("-")[0];
9
+ const cleanNew = newVersion.split("-")[0];
10
+
11
+ // Split and validate numeric parts
12
+ const currentParts = cleanCurrent.split(".").map((part) => {
13
+ const num = Number(part);
14
+ if (isNaN(num) || !part.trim()) {
15
+ throw new Error(`Invalid version format: ${currentVersion}`);
16
+ }
17
+ return num;
18
+ });
19
+
20
+ const newerParts = cleanNew.split(".").map((part) => {
21
+ const num = Number(part);
22
+ if (isNaN(num) || !part.trim()) {
23
+ throw new Error(`Invalid version format: ${newVersion}`);
24
+ }
25
+ return num;
26
+ });
27
+
28
+ // Compare up to the maximum length, treating missing parts as 0
29
+ const maxLength = Math.max(currentParts.length, newerParts.length);
30
+
31
+ for (let i = 0; i < maxLength; i++) {
32
+ const currentPart = currentParts[i] || 0;
33
+ const newerPart = newerParts[i] || 0;
34
+
35
+ if (newerPart > currentPart) return true;
36
+ if (newerPart < currentPart) return false;
37
+ }
38
+
39
+ // If base versions are equal, check pre-release tags
40
+ // A version with no pre-release tag is considered newer than one with a tag
41
+ const currentHasPrerelease = currentVersion.includes("-");
42
+ const newHasPrerelease = newVersion.includes("-");
43
+
44
+ if (!currentHasPrerelease && newHasPrerelease) {
45
+ return false; // Current stable is newer than new pre-release
46
+ }
47
+
48
+ if (currentHasPrerelease && !newHasPrerelease) {
49
+ return true; // New stable is newer than current pre-release
50
+ }
51
+
52
+ return false;
53
+ }
@@ -0,0 +1,97 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
2
+ import { join } from "path";
3
+ import { homedir } from "os";
4
+ import { isNewerVersion } from "./semver-utils.js";
5
+ import { fetchLatestVersion } from "./npm-registry.js";
6
+
7
+ interface VersionCache {
8
+ lastChecked: number;
9
+ latestVersion: string;
10
+ }
11
+
12
+ const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
13
+ const CACHE_DIR = join(homedir(), ".movehat");
14
+ const CACHE_FILE = join(CACHE_DIR, "version-cache.json");
15
+
16
+ /**
17
+ * Read version from cache
18
+ */
19
+ function readCache(): VersionCache | null {
20
+ try {
21
+ if (!existsSync(CACHE_FILE)) {
22
+ return null;
23
+ }
24
+
25
+ const cacheContent = readFileSync(CACHE_FILE, "utf-8");
26
+ return JSON.parse(cacheContent) as VersionCache;
27
+ } catch (error) {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Write version to cache
34
+ */
35
+ function writeCache(latestVersion: string): void {
36
+ try {
37
+ if (!existsSync(CACHE_DIR)) {
38
+ mkdirSync(CACHE_DIR, { recursive: true });
39
+ }
40
+
41
+ const cache: VersionCache = {
42
+ lastChecked: Date.now(),
43
+ latestVersion,
44
+ };
45
+
46
+ writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
47
+ } catch (error) {
48
+ // Silently fail - don't interrupt user's workflow
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Check if a newer version is available and notify the user
54
+ * This runs synchronously using cache, and updates cache in background
55
+ */
56
+ export function checkForUpdates(currentVersion: string, packageName: string): void {
57
+ try {
58
+ const cache = readCache();
59
+ let shouldNotify = false;
60
+
61
+ // Check cache synchronously for immediate notification
62
+ if (cache && isNewerVersion(currentVersion, cache.latestVersion)) {
63
+ shouldNotify = true;
64
+ }
65
+
66
+ // Display notification immediately if cache says there's an update
67
+ if (shouldNotify && cache) {
68
+ const updateMessage =
69
+ "\n" +
70
+ "┌" + "─".repeat(60) + "┐\n" +
71
+ `│ Update available: ${currentVersion} → ${cache.latestVersion}`.padEnd(61) + "│\n" +
72
+ `│ Run: movehat update`.padEnd(61) + "│\n" +
73
+ "└" + "─".repeat(60) + "┘\n";
74
+
75
+ console.error(updateMessage);
76
+ }
77
+
78
+ // Update cache in background if needed (doesn't block)
79
+ if (!cache || Date.now() - cache.lastChecked > CACHE_DURATION) {
80
+ setImmediate(async () => {
81
+ try {
82
+ const latestVersion = await fetchLatestVersion(packageName, {
83
+ timeout: 2000,
84
+ throwOnError: false,
85
+ });
86
+ if (latestVersion) {
87
+ writeCache(latestVersion);
88
+ }
89
+ } catch (error) {
90
+ // Silently fail
91
+ }
92
+ });
93
+ }
94
+ } catch (error) {
95
+ // Silently fail - never interrupt user's workflow
96
+ }
97
+ }
@@ -12,7 +12,7 @@ A Move smart contract project built with Movehat.
12
12
 
13
13
  Verify: `movement --version`
14
14
 
15
- **⚠️ Important:** Without Movement CLI, compilation will fail!
15
+ **IMPORTANT:** Without Movement CLI, compilation will fail!
16
16
 
17
17
  ## Getting Started
18
18
 
@@ -52,11 +52,25 @@ npm run compile
52
52
  npm test
53
53
  ```
54
54
 
55
- **How it works:**
56
- - Tests use **Transaction Simulation** - no real blockchain required
57
- - Runs instantly without gas costs
58
- - Uses Movement testnet by default with auto-generated test accounts
59
- - Perfect for TDD and CI/CD workflows
55
+ **Two types of tests available:**
56
+
57
+ 1. **Move Unit Tests** (`tests/Counter.move` lines 50-63)
58
+ - Written in Move with `#[test]` annotations
59
+ - Test internal logic and business rules
60
+ - Ultra-fast execution (milliseconds)
61
+ - Run with: `npm run test:move`
62
+
63
+ 2. **TypeScript Integration Tests** (`tests/Counter.test.ts`)
64
+ - Written in TypeScript using Transaction Simulation
65
+ - Test end-to-end flows and SDK integration
66
+ - No blockchain or gas costs required
67
+ - Run with: `npm run test:ts`
68
+
69
+ **Commands:**
70
+ - `npm test` - Runs both Move + TypeScript tests
71
+ - `npm run test:move` - Only Move unit tests (fast)
72
+ - `npm run test:ts` - Only TypeScript integration tests
73
+ - `npm run test:watch` - TypeScript tests in watch mode
60
74
 
61
75
  ### 5. Deploy (optional)
62
76
 
@@ -7,8 +7,9 @@ authors = []
7
7
  counter = "_"
8
8
 
9
9
  [dev-addresses]
10
- # Dev addresses are auto-detected by movehat during compilation
11
- # You can also add them manually here if needed
10
+ # Dev addresses for testing (used with movement move test --dev)
11
+ # These addresses are temporary and used for local development/testing only
12
+ counter = "0xcafe"
12
13
 
13
14
  [dependencies.AptosFramework]
14
15
  git = "https://github.com/movementlabsxyz/aptos-core.git"
@@ -3,8 +3,10 @@
3
3
  "version": "1.0.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
- "test": "mocha",
7
- "test:watch": "mocha --watch",
6
+ "test": "movehat test",
7
+ "test:move": "movehat test:move",
8
+ "test:ts": "movehat test:ts",
9
+ "test:watch": "movehat test:ts --watch",
8
10
  "deploy": "movehat run scripts/deploy-counter.ts"
9
11
  },
10
12
  "dependencies": {