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.
- package/README.md +1 -1
- package/dist/cli.js +32 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/test-move.d.ts +7 -0
- package/dist/commands/test-move.d.ts.map +1 -0
- package/dist/commands/test-move.js +20 -0
- package/dist/commands/test-move.js.map +1 -0
- package/dist/commands/test.d.ts +8 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +90 -24
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/update.d.ts +5 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +121 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/helpers/move-tests.d.ts +13 -0
- package/dist/helpers/move-tests.d.ts.map +1 -0
- package/dist/helpers/move-tests.js +54 -0
- package/dist/helpers/move-tests.js.map +1 -0
- package/dist/helpers/npm-registry.d.ts +19 -0
- package/dist/helpers/npm-registry.d.ts.map +1 -0
- package/dist/helpers/npm-registry.js +47 -0
- package/dist/helpers/npm-registry.js.map +1 -0
- package/dist/helpers/semver-utils.d.ts +7 -0
- package/dist/helpers/semver-utils.d.ts.map +1 -0
- package/dist/helpers/semver-utils.js +47 -0
- package/dist/helpers/semver-utils.js.map +1 -0
- package/dist/helpers/version-check.d.ts +6 -0
- package/dist/helpers/version-check.d.ts.map +1 -0
- package/dist/helpers/version-check.js +85 -0
- package/dist/helpers/version-check.js.map +1 -0
- package/dist/templates/README.md +20 -6
- package/dist/templates/move/Move.toml +3 -2
- package/dist/templates/package.json +4 -2
- package/package.json +1 -1
- package/src/cli.ts +37 -2
- package/src/commands/test-move.ts +26 -0
- package/src/commands/test.ts +106 -27
- package/src/commands/update.ts +148 -0
- package/src/helpers/move-tests.ts +68 -0
- package/src/helpers/npm-registry.ts +71 -0
- package/src/helpers/semver-utils.ts +53 -0
- package/src/helpers/version-check.ts +97 -0
- package/src/templates/README.md +20 -6
- package/src/templates/move/Move.toml +3 -2
- package/src/templates/package.json +4 -2
|
@@ -0,0 +1,47 @@
|
|
|
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, newVersion) {
|
|
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
|
+
// Split and validate numeric parts
|
|
11
|
+
const currentParts = cleanCurrent.split(".").map((part) => {
|
|
12
|
+
const num = Number(part);
|
|
13
|
+
if (isNaN(num) || !part.trim()) {
|
|
14
|
+
throw new Error(`Invalid version format: ${currentVersion}`);
|
|
15
|
+
}
|
|
16
|
+
return num;
|
|
17
|
+
});
|
|
18
|
+
const newerParts = cleanNew.split(".").map((part) => {
|
|
19
|
+
const num = Number(part);
|
|
20
|
+
if (isNaN(num) || !part.trim()) {
|
|
21
|
+
throw new Error(`Invalid version format: ${newVersion}`);
|
|
22
|
+
}
|
|
23
|
+
return num;
|
|
24
|
+
});
|
|
25
|
+
// Compare up to the maximum length, treating missing parts as 0
|
|
26
|
+
const maxLength = Math.max(currentParts.length, newerParts.length);
|
|
27
|
+
for (let i = 0; i < maxLength; i++) {
|
|
28
|
+
const currentPart = currentParts[i] || 0;
|
|
29
|
+
const newerPart = newerParts[i] || 0;
|
|
30
|
+
if (newerPart > currentPart)
|
|
31
|
+
return true;
|
|
32
|
+
if (newerPart < currentPart)
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
// If base versions are equal, check pre-release tags
|
|
36
|
+
// A version with no pre-release tag is considered newer than one with a tag
|
|
37
|
+
const currentHasPrerelease = currentVersion.includes("-");
|
|
38
|
+
const newHasPrerelease = newVersion.includes("-");
|
|
39
|
+
if (!currentHasPrerelease && newHasPrerelease) {
|
|
40
|
+
return false; // Current stable is newer than new pre-release
|
|
41
|
+
}
|
|
42
|
+
if (currentHasPrerelease && !newHasPrerelease) {
|
|
43
|
+
return true; // New stable is newer than current pre-release
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=semver-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semver-utils.js","sourceRoot":"","sources":["../../src/helpers/semver-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,cAAsB,EAAE,UAAkB;IACvE,wDAAwD;IACxD,MAAM,YAAY,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAE1C,mCAAmC;IACnC,MAAM,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,2BAA2B,cAAc,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAClD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,2BAA2B,UAAU,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CAAC;IAEH,gEAAgE;IAChE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAEnE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,WAAW,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAErC,IAAI,SAAS,GAAG,WAAW;YAAE,OAAO,IAAI,CAAC;QACzC,IAAI,SAAS,GAAG,WAAW;YAAE,OAAO,KAAK,CAAC;IAC5C,CAAC;IAED,qDAAqD;IACrD,4EAA4E;IAC5E,MAAM,oBAAoB,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1D,MAAM,gBAAgB,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAElD,IAAI,CAAC,oBAAoB,IAAI,gBAAgB,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC,CAAC,+CAA+C;IAC/D,CAAC;IAED,IAAI,oBAAoB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9C,OAAO,IAAI,CAAC,CAAC,+CAA+C;IAC9D,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a newer version is available and notify the user
|
|
3
|
+
* This runs synchronously using cache, and updates cache in background
|
|
4
|
+
*/
|
|
5
|
+
export declare function checkForUpdates(currentVersion: string, packageName: string): void;
|
|
6
|
+
//# sourceMappingURL=version-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../src/helpers/version-check.ts"],"names":[],"mappings":"AAmDA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAyCjF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
|
7
|
+
const CACHE_DIR = join(homedir(), ".movehat");
|
|
8
|
+
const CACHE_FILE = join(CACHE_DIR, "version-cache.json");
|
|
9
|
+
/**
|
|
10
|
+
* Read version from cache
|
|
11
|
+
*/
|
|
12
|
+
function readCache() {
|
|
13
|
+
try {
|
|
14
|
+
if (!existsSync(CACHE_FILE)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const cacheContent = readFileSync(CACHE_FILE, "utf-8");
|
|
18
|
+
return JSON.parse(cacheContent);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Write version to cache
|
|
26
|
+
*/
|
|
27
|
+
function writeCache(latestVersion) {
|
|
28
|
+
try {
|
|
29
|
+
if (!existsSync(CACHE_DIR)) {
|
|
30
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
const cache = {
|
|
33
|
+
lastChecked: Date.now(),
|
|
34
|
+
latestVersion,
|
|
35
|
+
};
|
|
36
|
+
writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
// Silently fail - don't interrupt user's workflow
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if a newer version is available and notify the user
|
|
44
|
+
* This runs synchronously using cache, and updates cache in background
|
|
45
|
+
*/
|
|
46
|
+
export function checkForUpdates(currentVersion, packageName) {
|
|
47
|
+
try {
|
|
48
|
+
const cache = readCache();
|
|
49
|
+
let shouldNotify = false;
|
|
50
|
+
// Check cache synchronously for immediate notification
|
|
51
|
+
if (cache && isNewerVersion(currentVersion, cache.latestVersion)) {
|
|
52
|
+
shouldNotify = true;
|
|
53
|
+
}
|
|
54
|
+
// Display notification immediately if cache says there's an update
|
|
55
|
+
if (shouldNotify && cache) {
|
|
56
|
+
const updateMessage = "\n" +
|
|
57
|
+
"┌" + "─".repeat(60) + "┐\n" +
|
|
58
|
+
`│ Update available: ${currentVersion} → ${cache.latestVersion}`.padEnd(61) + "│\n" +
|
|
59
|
+
`│ Run: movehat update`.padEnd(61) + "│\n" +
|
|
60
|
+
"└" + "─".repeat(60) + "┘\n";
|
|
61
|
+
console.error(updateMessage);
|
|
62
|
+
}
|
|
63
|
+
// Update cache in background if needed (doesn't block)
|
|
64
|
+
if (!cache || Date.now() - cache.lastChecked > CACHE_DURATION) {
|
|
65
|
+
setImmediate(async () => {
|
|
66
|
+
try {
|
|
67
|
+
const latestVersion = await fetchLatestVersion(packageName, {
|
|
68
|
+
timeout: 2000,
|
|
69
|
+
throwOnError: false,
|
|
70
|
+
});
|
|
71
|
+
if (latestVersion) {
|
|
72
|
+
writeCache(latestVersion);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// Silently fail
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// Silently fail - never interrupt user's workflow
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=version-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/helpers/version-check.ts"],"names":[],"mappings":"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"}
|
package/dist/templates/README.md
CHANGED
|
@@ -12,7 +12,7 @@ A Move smart contract project built with Movehat.
|
|
|
12
12
|
|
|
13
13
|
Verify: `movement --version`
|
|
14
14
|
|
|
15
|
-
|
|
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
|
-
**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
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
|
|
11
|
-
#
|
|
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": "
|
|
7
|
-
"test:
|
|
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": {
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -4,19 +4,23 @@ import { readFileSync } from 'fs';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { dirname, join } from 'path';
|
|
6
6
|
import testCommand from './commands/test.js';
|
|
7
|
+
import testMoveCommand from './commands/test-move.js';
|
|
7
8
|
import compileCommand from './commands/compile.js';
|
|
8
9
|
import initCommand from './commands/init.js';
|
|
9
10
|
import runCommand from './commands/run.js';
|
|
11
|
+
import updateCommand from './commands/update.js';
|
|
10
12
|
import forkCreateCommand from './commands/fork/create.js';
|
|
11
13
|
import forkViewResourceCommand from './commands/fork/view-resource.js';
|
|
12
14
|
import forkFundCommand from './commands/fork/fund.js';
|
|
13
15
|
import forkListCommand from './commands/fork/list.js';
|
|
14
16
|
import forkServeCommand from './commands/fork/serve.js';
|
|
17
|
+
import { checkForUpdates } from './helpers/version-check.js';
|
|
15
18
|
|
|
16
19
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
20
|
const __dirname = dirname(__filename);
|
|
18
21
|
const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
|
|
19
22
|
const version = packageJson.version;
|
|
23
|
+
const packageName = packageJson.name;
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* Parse and validate port number
|
|
@@ -29,6 +33,15 @@ function parsePort(value: string): number {
|
|
|
29
33
|
return port;
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
// Check for updates at startup (skip if running update command or help)
|
|
37
|
+
const args = process.argv.slice(2);
|
|
38
|
+
const isUpdateCommand = args.includes('update');
|
|
39
|
+
const isHelpOnly = args.length === 0 || args.includes('-h') || args.includes('--help');
|
|
40
|
+
|
|
41
|
+
if (!isUpdateCommand && !isHelpOnly) {
|
|
42
|
+
checkForUpdates(version, packageName);
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
const program = new Command();
|
|
33
46
|
|
|
34
47
|
program
|
|
@@ -67,8 +80,30 @@ program
|
|
|
67
80
|
|
|
68
81
|
program
|
|
69
82
|
.command('test')
|
|
70
|
-
.description('Run
|
|
71
|
-
.
|
|
83
|
+
.description('Run all tests (Move + TypeScript)')
|
|
84
|
+
.option('--move-only', 'Run only Move unit tests')
|
|
85
|
+
.option('--ts-only', 'Run only TypeScript integration tests')
|
|
86
|
+
.option('--watch', 'Run TypeScript tests in watch mode (implies --ts-only)')
|
|
87
|
+
.option('--filter <pattern>', 'Filter Move tests by name pattern (Move tests only)')
|
|
88
|
+
.action((options) => testCommand(options));
|
|
89
|
+
|
|
90
|
+
program
|
|
91
|
+
.command('test:move')
|
|
92
|
+
.description('Run Move unit tests')
|
|
93
|
+
.option('--filter <pattern>', 'Filter tests by name pattern')
|
|
94
|
+
.option('--ignore-warnings', 'Ignore compilation warnings')
|
|
95
|
+
.action((options) => testMoveCommand(options));
|
|
96
|
+
|
|
97
|
+
program
|
|
98
|
+
.command('test:ts')
|
|
99
|
+
.description('Run TypeScript integration tests')
|
|
100
|
+
.option('--watch', 'Run tests in watch mode')
|
|
101
|
+
.action((options) => testCommand({ tsOnly: true, watch: options.watch }));
|
|
102
|
+
|
|
103
|
+
program
|
|
104
|
+
.command('update')
|
|
105
|
+
.description('Check for updates and upgrade to the latest version')
|
|
106
|
+
.action(() => updateCommand());
|
|
72
107
|
|
|
73
108
|
// Fork commands
|
|
74
109
|
const fork = program
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { runMoveTests } from "../helpers/move-tests.js";
|
|
2
|
+
|
|
3
|
+
interface TestMoveOptions {
|
|
4
|
+
filter?: string;
|
|
5
|
+
ignoreWarnings?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default async function testMoveCommand(options: TestMoveOptions = {}) {
|
|
9
|
+
try {
|
|
10
|
+
console.log("Running Move unit tests...\n");
|
|
11
|
+
|
|
12
|
+
await runMoveTests({
|
|
13
|
+
filter: options.filter,
|
|
14
|
+
ignoreWarnings: options.ignoreWarnings,
|
|
15
|
+
skipIfMissing: false, // Fail if no Move directory (standalone command mode)
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
process.exit(0);
|
|
19
|
+
} catch (err: any) {
|
|
20
|
+
console.error("\n✗ Move tests failed");
|
|
21
|
+
if (err.message) {
|
|
22
|
+
console.error(` ${err.message}`);
|
|
23
|
+
}
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/commands/test.ts
CHANGED
|
@@ -1,42 +1,121 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
import { join } from "path";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
3
|
import { existsSync } from "fs";
|
|
4
|
+
import { runMoveTests } from "../helpers/move-tests.js";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
interface TestOptions {
|
|
7
|
+
moveOnly?: boolean;
|
|
8
|
+
tsOnly?: boolean;
|
|
9
|
+
watch?: boolean;
|
|
10
|
+
filter?: string;
|
|
11
|
+
}
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
export default async function testCommand(options: TestOptions = {}) {
|
|
14
|
+
// Handle move-only flag
|
|
15
|
+
if (options.moveOnly) {
|
|
16
|
+
if (options.watch) {
|
|
17
|
+
console.error("ERROR: --watch flag is not supported with --move-only");
|
|
18
|
+
console.error(" Watch mode only works with TypeScript tests");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
return runMoveTestsSync(options.filter);
|
|
12
22
|
}
|
|
13
23
|
|
|
14
|
-
|
|
24
|
+
// Handle ts-only flag or watch flag (watch implies ts-only)
|
|
25
|
+
if (options.tsOnly || options.watch) {
|
|
26
|
+
return runTypeScriptTests(options.watch);
|
|
27
|
+
}
|
|
15
28
|
|
|
16
|
-
//
|
|
17
|
-
|
|
29
|
+
// Default: Run both Move and TypeScript tests (no watch mode)
|
|
30
|
+
console.log("Running all tests...\n");
|
|
31
|
+
console.log("=" + "=".repeat(60) + "\n");
|
|
18
32
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
33
|
+
// First run Move tests (fail fast)
|
|
34
|
+
try {
|
|
35
|
+
await runMoveTestsSync();
|
|
36
|
+
console.log("\n" + "=" + "=".repeat(60) + "\n");
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error("\n✗ Move tests failed");
|
|
39
|
+
console.log("\n" + "=" + "=".repeat(60));
|
|
22
40
|
process.exit(1);
|
|
23
41
|
}
|
|
24
42
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
// Then run TypeScript tests (never in watch mode for "all tests")
|
|
44
|
+
try {
|
|
45
|
+
await runTypeScriptTests(false);
|
|
46
|
+
console.log("\n" + "=" + "=".repeat(60));
|
|
47
|
+
console.log("\n✓ All tests passed!\n");
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("\n" + "=" + "=".repeat(60));
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
console.error(`\n${message}\n`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runMoveTestsSync(filter?: string): Promise<void> {
|
|
57
|
+
console.log("1. Move Unit Tests");
|
|
58
|
+
console.log("-" + "-".repeat(60) + "\n");
|
|
33
59
|
|
|
34
|
-
|
|
35
|
-
|
|
60
|
+
return runMoveTests({
|
|
61
|
+
filter,
|
|
62
|
+
skipIfMissing: true, // Gracefully skip if no Move directory (orchestrated test mode)
|
|
36
63
|
});
|
|
64
|
+
}
|
|
37
65
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
function runTypeScriptTests(watch: boolean = false): Promise<void> {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
console.log("2. TypeScript Integration Tests");
|
|
69
|
+
console.log("-" + "-".repeat(60) + "\n");
|
|
70
|
+
|
|
71
|
+
const testDir = join(process.cwd(), "tests");
|
|
72
|
+
|
|
73
|
+
if (!existsSync(testDir)) {
|
|
74
|
+
console.log("⊘ No TypeScript tests found (tests directory not found)");
|
|
75
|
+
console.log(" Skipping TypeScript tests...\n");
|
|
76
|
+
resolve();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const mochaPath = join(process.cwd(), "node_modules", ".bin", "mocha");
|
|
81
|
+
|
|
82
|
+
if (!existsSync(mochaPath)) {
|
|
83
|
+
console.error("✗ Mocha not found in project dependencies.");
|
|
84
|
+
console.error(" Install it with: npm install --save-dev mocha");
|
|
85
|
+
reject(new Error("Mocha not found"));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const args = watch ? ["--watch"] : [];
|
|
90
|
+
|
|
91
|
+
const child = spawn(mochaPath, args, {
|
|
92
|
+
stdio: "inherit",
|
|
93
|
+
env: {
|
|
94
|
+
...process.env,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// In watch mode, Mocha never exits, so resolve immediately
|
|
99
|
+
if (watch) {
|
|
100
|
+
console.log("Watch mode active. Press Ctrl+C to exit.\n");
|
|
101
|
+
resolve();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Non-watch mode: wait for exit
|
|
106
|
+
child.on("exit", (code) => {
|
|
107
|
+
if (code === 0) {
|
|
108
|
+
console.log("\n✓ TypeScript tests passed");
|
|
109
|
+
resolve();
|
|
110
|
+
} else {
|
|
111
|
+
const exitCode = typeof code === "number" ? code : 1;
|
|
112
|
+
reject(new Error(`TypeScript tests failed with exit code ${exitCode}`));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
child.on("error", (error) => {
|
|
117
|
+
console.error(`Failed to run TypeScript tests: ${error.message}`);
|
|
118
|
+
reject(error);
|
|
119
|
+
});
|
|
41
120
|
});
|
|
42
|
-
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { join, dirname, resolve } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { isNewerVersion } from "../helpers/semver-utils.js";
|
|
7
|
+
import { fetchLatestVersion } from "../helpers/npm-registry.js";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
interface PackageJson {
|
|
13
|
+
name: string;
|
|
14
|
+
version: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Detect which package manager to use for update
|
|
19
|
+
* Searches for lockfiles upward from cwd, checks user agent, or falls back to defaults
|
|
20
|
+
*/
|
|
21
|
+
function detectPackageManager(): "yarn" | "npm" | "pnpm" {
|
|
22
|
+
// First, try to detect from lockfiles by searching upward
|
|
23
|
+
let currentDir = process.cwd();
|
|
24
|
+
const root = resolve("/");
|
|
25
|
+
|
|
26
|
+
while (currentDir !== root) {
|
|
27
|
+
if (existsSync(join(currentDir, "pnpm-lock.yaml"))) {
|
|
28
|
+
return "pnpm";
|
|
29
|
+
}
|
|
30
|
+
if (existsSync(join(currentDir, "yarn.lock"))) {
|
|
31
|
+
return "yarn";
|
|
32
|
+
}
|
|
33
|
+
if (
|
|
34
|
+
existsSync(join(currentDir, "package-lock.json")) ||
|
|
35
|
+
existsSync(join(currentDir, "npm-shrinkwrap.json"))
|
|
36
|
+
) {
|
|
37
|
+
return "npm";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const parentDir = dirname(currentDir);
|
|
41
|
+
if (parentDir === currentDir) break; // Reached root
|
|
42
|
+
currentDir = parentDir;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// No lockfile found, check user agent environment variables
|
|
46
|
+
const userAgent =
|
|
47
|
+
process.env.npm_config_user_agent || process.env.npm_execpath || "";
|
|
48
|
+
|
|
49
|
+
if (userAgent.includes("pnpm")) {
|
|
50
|
+
return "pnpm";
|
|
51
|
+
}
|
|
52
|
+
if (userAgent.includes("yarn")) {
|
|
53
|
+
return "yarn";
|
|
54
|
+
}
|
|
55
|
+
if (userAgent.includes("npm")) {
|
|
56
|
+
return "npm";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Default fallback to npm for global installs
|
|
60
|
+
return "npm";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Update command - checks for updates and installs the latest version
|
|
65
|
+
*/
|
|
66
|
+
export default async function updateCommand() {
|
|
67
|
+
try {
|
|
68
|
+
console.log("Checking for updates...\n");
|
|
69
|
+
|
|
70
|
+
// Read current version from package.json
|
|
71
|
+
const packageJsonPath = join(__dirname, "../../package.json");
|
|
72
|
+
const packageJson: PackageJson = JSON.parse(
|
|
73
|
+
readFileSync(packageJsonPath, "utf-8")
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const currentVersion = packageJson.version;
|
|
77
|
+
const packageName = packageJson.name;
|
|
78
|
+
|
|
79
|
+
console.log(`Current version: ${currentVersion}`);
|
|
80
|
+
|
|
81
|
+
// Fetch latest version from npm
|
|
82
|
+
const latestVersion = await fetchLatestVersion(packageName, {
|
|
83
|
+
throwOnError: true,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!latestVersion) {
|
|
87
|
+
console.error("Failed to fetch latest version from npm registry");
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(`Latest version: ${latestVersion}\n`);
|
|
92
|
+
|
|
93
|
+
// Compare versions
|
|
94
|
+
if (!isNewerVersion(currentVersion, latestVersion)) {
|
|
95
|
+
console.log("✓ You are already using the latest version!");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(`New version available: ${currentVersion} -> ${latestVersion}`);
|
|
100
|
+
console.log("\nUpdating movehat...\n");
|
|
101
|
+
|
|
102
|
+
// Detect package manager
|
|
103
|
+
const packageManager = detectPackageManager();
|
|
104
|
+
|
|
105
|
+
// Build update command based on package manager
|
|
106
|
+
let updateArgs: string[];
|
|
107
|
+
switch (packageManager) {
|
|
108
|
+
case "yarn":
|
|
109
|
+
updateArgs = ["global", "upgrade", packageName];
|
|
110
|
+
break;
|
|
111
|
+
case "pnpm":
|
|
112
|
+
updateArgs = ["add", "-g", `${packageName}@latest`];
|
|
113
|
+
break;
|
|
114
|
+
case "npm":
|
|
115
|
+
default:
|
|
116
|
+
updateArgs = ["update", "-g", packageName];
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Execute update
|
|
121
|
+
// Use home directory as cwd to avoid packageManager conflicts from local package.json
|
|
122
|
+
const child = spawn(packageManager, updateArgs, {
|
|
123
|
+
stdio: "inherit",
|
|
124
|
+
cwd: homedir() || process.cwd(),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
child.on("exit", (code) => {
|
|
128
|
+
if (code === 0) {
|
|
129
|
+
console.log(`\n✓ Successfully updated to version ${latestVersion}!`);
|
|
130
|
+
process.exit(0);
|
|
131
|
+
} else {
|
|
132
|
+
console.error("\n✗ Update failed");
|
|
133
|
+
console.error(` Try manually: ${packageManager} ${updateArgs.join(" ")}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
child.on("error", (error) => {
|
|
139
|
+
console.error(`Failed to update: ${error.message}`);
|
|
140
|
+
console.error(` Try manually: ${packageManager} ${updateArgs.join(" ")}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
});
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
145
|
+
console.error(`Error: ${message}`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|