Skip to content

Commit 9607ca7

Browse files
authored
fix(cli): use fs.symlinkSync for paths with spaces, show version in status (#833)
* fix(cli): use fs.symlinkSync for paths with spaces, show version in status Replace shell `ln -fs` with Node.js fs.symlinkSync to handle paths containing spaces. Extract nttVersion() into shared version.ts and print CLI version in `ntt status` output. * style: run prettier on changed files * fix(cli): guard against malformed version file * fix(cli): improve version check and add end-to-end symlink test Use nttVersion() null check instead of string comparison in index.ts. Replace source-level symlink assertion with an end-to-end test that exercises createWorkTree in a temp repo with spaces in the path. * style: run prettier on test file
1 parent 2668455 commit 9607ca7

File tree

5 files changed

+89
-37
lines changed

5 files changed

+89
-37
lines changed

cli/src/__tests__/bugfix-regression.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { describe, expect, test, spyOn, beforeEach, afterEach } from "bun:test";
2+
import { execSync } from "child_process";
23
import fs from "fs";
4+
import os from "os";
35
import path from "path";
46
import { checkConfigErrors } from "../configErrors";
7+
import { createWorkTree } from "../tag";
58
import { validateChain } from "../validation";
69

710
const SRC_DIR = path.resolve(import.meta.dir, "..");
@@ -189,6 +192,41 @@ describe("Fix #14: sui/helpers.ts — execFileSync only, no execSync", () => {
189192
});
190193
});
191194

195+
// ─── tag.ts — symlink creation handles spaces in path ────────────────────────
196+
197+
describe("tag.ts — symlink creation with spaces in cwd", () => {
198+
test("createWorkTree creates overrides.json symlink to the source file", () => {
199+
const originalCwd = process.cwd();
200+
const tempRoot = fs.mkdtempSync(
201+
path.join(os.tmpdir(), "ntt-symlink-test-")
202+
);
203+
const repoDir = path.join(tempRoot, "dir with spaces");
204+
fs.mkdirSync(repoDir);
205+
206+
try {
207+
process.chdir(repoDir);
208+
execSync("git init -q");
209+
execSync("git config user.email [email protected]");
210+
execSync("git config user.name test");
211+
fs.writeFileSync("overrides.json", "{}\n");
212+
execSync("git add overrides.json");
213+
execSync("git -c commit.gpgsign=false commit -qm init");
214+
execSync("git tag v1.2.3+evm");
215+
216+
const worktree = createWorkTree("Evm", "1.2.3");
217+
const symlinkPath = path.resolve(worktree, "overrides.json");
218+
219+
expect(fs.lstatSync(symlinkPath).isSymbolicLink()).toBe(true);
220+
221+
const symlinkTarget = fs.readlinkSync(symlinkPath);
222+
expect(path.resolve(symlinkTarget)).toBe(path.resolve("overrides.json"));
223+
} finally {
224+
process.chdir(originalCwd);
225+
fs.rmSync(tempRoot, { recursive: true, force: true });
226+
}
227+
});
228+
});
229+
192230
// ─── Fix #11: config-mgmt.ts — no contradictory ?. + ! ──────────────────────
193231

194232
describe("Fix #11: config-mgmt.ts — no diff[k]?.push! pattern", () => {

cli/src/commands/status.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
getImmutables,
2424
getPdas,
2525
} from "../index";
26+
import { formatNttVersion } from "../version.js";
2627

2728
export function createStatusCommand(
2829
overrides: WormholeConfigOverrides<Network>
@@ -44,6 +45,8 @@ export function createStatusCommand(
4445
"Check the status with detailed output"
4546
),
4647
handler: async (argv: any) => {
48+
console.log(formatNttVersion());
49+
4750
const path = argv["path"];
4851
const verbose = argv["verbose"];
4952
// TODO: I don't like the variable names here

cli/src/index.ts

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import "./side-effects"; // doesn't quite work for silencing the bigint error message. why?
33
import type { Network } from "@wormhole-foundation/sdk-connect";
44
import type { WormholeConfigOverrides } from "@wormhole-foundation/sdk-connect";
5-
import fs from "fs";
65
import yargs from "yargs";
76
import { hideBin } from "yargs/helpers";
87
import { register as registerEvm } from "@wormhole-foundation/sdk-evm-ntt";
@@ -32,6 +31,7 @@ import {
3231
createUpgradeCommand,
3332
} from "./commands";
3433
import { loadOverrides } from "./overrides.js";
34+
import { formatNttVersion, nttVersion } from "./version.js";
3535

3636
// TODO: check if manager can mint the token in burning mode (on solana it's
3737
// simple. on evm we need to simulate with prank)
@@ -42,20 +42,8 @@ yargs(hideBin(process.argv))
4242
.scriptName("ntt")
4343
.version(
4444
(() => {
45-
const ver = nttVersion();
46-
if (!ver) {
47-
return "unknown";
48-
}
49-
const { version, commit, path, remote } = ver;
50-
const defaultPath = `${process.env.HOME}/.ntt-cli/.checkout`;
51-
const remoteString = remote.includes("wormhole-foundation")
52-
? ""
53-
: `${remote}@`;
54-
if (path === defaultPath) {
55-
return `ntt v${version} (${remoteString}${commit})`;
56-
} else {
57-
return `ntt v${version} (${remoteString}${commit}) from ${path}`;
58-
}
45+
if (!nttVersion()) return "unknown";
46+
return formatNttVersion();
5947
})()
6048
)
6149
// Commands (extracted to individual files in commands/)
@@ -80,22 +68,6 @@ yargs(hideBin(process.argv))
8068
.demandCommand()
8169
.parse();
8270

83-
function nttVersion(): {
84-
version: string;
85-
commit: string;
86-
path: string;
87-
remote: string;
88-
} | null {
89-
const nttDir = `${process.env.HOME}/.ntt-cli`;
90-
try {
91-
const versionFile = fs.readFileSync(`${nttDir}/version`).toString().trim();
92-
const [commit, installPath, version, remote] = versionFile.split("\n");
93-
return { version, commit, path: installPath, remote };
94-
} catch {
95-
return null;
96-
}
97-
}
98-
9971
// ── Re-exports for backward compatibility ────────────────────────────
10072
// Commands import from "../index" — keep these re-exports so nothing breaks.
10173

cli/src/tag.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Platform } from "@wormhole-foundation/sdk";
22
import { execSync } from "child_process";
33
import fs from "fs";
4+
import path from "path";
45
import { colors } from "./colors.js";
56
import { askForConfirmation } from "./prompts.js";
67

@@ -83,12 +84,14 @@ export function createWorkTree(platform: Platform, version: string): string {
8384

8485
// NOTE: we create this symlink whether or not the file exists.
8586
// this way, if it's created later, the symlink will be correct
86-
execSync(
87-
`ln -fs $(pwd)/overrides.json $(pwd)/${worktreeName}/overrides.json`,
88-
{
89-
stdio: "inherit",
90-
}
91-
);
87+
const target = path.resolve("overrides.json");
88+
const linkPath = path.resolve(worktreeName, "overrides.json");
89+
try {
90+
fs.unlinkSync(linkPath);
91+
} catch (e: any) {
92+
if (e.code !== "ENOENT") throw e;
93+
}
94+
fs.symlinkSync(target, linkPath);
9295

9396
console.log(
9497
colors.green(`Created worktree at ${worktreeName} from tag ${tag}`)

cli/src/version.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import fs from "fs";
2+
3+
export function nttVersion(): {
4+
version: string;
5+
commit: string;
6+
path: string;
7+
remote: string;
8+
} | null {
9+
const nttDir = `${process.env.HOME}/.ntt-cli`;
10+
try {
11+
const versionFile = fs.readFileSync(`${nttDir}/version`).toString().trim();
12+
const parts = versionFile.split("\n");
13+
if (parts.length < 4) return null;
14+
const [commit, installPath, version, remote] = parts;
15+
return { version, commit, path: installPath, remote };
16+
} catch {
17+
return null;
18+
}
19+
}
20+
21+
export function formatNttVersion(): string {
22+
const ver = nttVersion();
23+
if (!ver) {
24+
return "ntt version: unknown";
25+
}
26+
const { version, commit, path, remote } = ver;
27+
const defaultPath = `${process.env.HOME}/.ntt-cli/.checkout`;
28+
const remoteString = remote.includes("wormhole-foundation")
29+
? ""
30+
: `${remote}@`;
31+
if (path === defaultPath) {
32+
return `ntt v${version} (${remoteString}${commit})`;
33+
} else {
34+
return `ntt v${version} (${remoteString}${commit}) from ${path}`;
35+
}
36+
}

0 commit comments

Comments
 (0)