Files
wehub-resource-sync 7088f49b01
Build dist / build (push) Has been cancelled
CI / test (18.x) (push) Has been cancelled
CI / test (20.x) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:33 +08:00

556 lines
19 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from "node:test";
import assert from "node:assert/strict";
import { fileURLToPath } from "node:url";
import { spawnSync } from "node:child_process";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { readFileSync } from "node:fs";
function stripAnsi(text) {
return text
.replace(
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nq-uy=><]/g,
"",
)
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
}
function skipIfSpawnBlocked(result, t) {
if (result.error?.code === "EPERM") {
t.skip("spawnSync is blocked by sandbox policy in this environment");
return true;
}
return false;
}
test("CLI renders expected output for a basic transcript", async (t) => {
const fixturePath = fileURLToPath(
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
);
const expectedPath = fileURLToPath(
new URL("./fixtures/expected/render-basic.txt", import.meta.url),
);
const expected = readFileSync(expectedPath, "utf8").trimEnd();
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
// Use a fixed 3-level path for deterministic test output
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
await import("node:fs/promises").then((fs) =>
fs.mkdir(projectDir, { recursive: true }),
);
try {
const stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
});
const result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.error, undefined, result.error?.message);
assert.equal(result.status, 0, result.stderr || "non-zero exit");
const normalized = stripAnsi(result.stdout)
.replace(/\u00A0/g, " ")
.trimEnd();
if (process.env.UPDATE_SNAPSHOTS === "1") {
await writeFile(expectedPath, normalized + "\n", "utf8");
return;
}
assert.equal(normalized, expected);
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test("CLI renders added_dirs basenames on the project line", async (t) => {
const fixturePath = fileURLToPath(
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
);
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
const addedDirA = path.join(homeDir, "dev", "apps", "lib-foo");
const addedDirB = path.join(homeDir, "dev", "apps", "some-other-repo");
await import("node:fs/promises").then((fs) =>
Promise.all([
fs.mkdir(projectDir, { recursive: true }),
fs.mkdir(addedDirA, { recursive: true }),
fs.mkdir(addedDirB, { recursive: true }),
]),
);
try {
const stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
workspace: {
current_dir: projectDir,
added_dirs: [addedDirA, addedDirB],
},
});
const result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.error, undefined, result.error?.message);
assert.equal(result.status, 0, result.stderr || "non-zero exit");
const firstLine = stripAnsi(result.stdout).split("\n")[0];
assert.match(firstLine, /\+lib-foo/);
assert.match(firstLine, /\+some-other-repo/);
assert.ok(
firstLine.indexOf("my-project") < firstLine.indexOf("+lib-foo"),
"added dirs should come after the project name",
);
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test("CLI omits added dirs section when array is empty", async (t) => {
const fixturePath = fileURLToPath(
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
);
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
await import("node:fs/promises").then((fs) =>
fs.mkdir(projectDir, { recursive: true }),
);
try {
const stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
workspace: { current_dir: projectDir, added_dirs: [] },
});
const result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.status, 0, result.stderr || "non-zero exit");
const firstLine = stripAnsi(result.stdout).split("\n")[0];
assert.doesNotMatch(firstLine, /\+/);
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test("CLI tolerates added_dirs: null without crashing", async (t) => {
const fixturePath = fileURLToPath(
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
);
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
await import("node:fs/promises").then((fs) =>
fs.mkdir(projectDir, { recursive: true }),
);
try {
const stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
workspace: { added_dirs: null },
});
const result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.error, undefined, result.error?.message);
assert.equal(result.status, 0, result.stderr || "non-zero exit");
const firstLine = stripAnsi(result.stdout).split("\n")[0];
assert.doesNotMatch(firstLine, /\+/);
assert.doesNotMatch(stripAnsi(result.stdout), /Added dirs:/);
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test("CLI ignores non-string and post-sanitize-empty added_dirs entries", async (t) => {
const fixturePath = fileURLToPath(
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
);
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
const validA = path.join(homeDir, "dev", "apps", "valid-one");
const validB = path.join(homeDir, "dev", "apps", "valid-two");
await import("node:fs/promises").then((fs) =>
Promise.all([
fs.mkdir(projectDir, { recursive: true }),
fs.mkdir(validA, { recursive: true }),
fs.mkdir(validB, { recursive: true }),
]),
);
try {
const stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
workspace: {
added_dirs: [validA, null, 42, "", { foo: 1 }, "", validB],
},
});
const result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.error, undefined, result.error?.message);
assert.equal(result.status, 0, result.stderr || "non-zero exit");
const firstLine = stripAnsi(result.stdout).split("\n")[0];
assert.match(firstLine, /\+valid-one/);
assert.match(firstLine, /\+valid-two/);
const plusCount = (firstLine.match(/\+valid-/g) || []).length;
assert.equal(plusCount, 2, "only the two valid basenames should render");
assert.doesNotMatch(firstLine, /\+ /, "no bare '+' from control-char-only basename");
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test("CLI caps inline added_dirs at 5 with overflow indicator", async (t) => {
const fixturePath = fileURLToPath(
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
);
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
const dirs = Array.from({ length: 7 }, (_, i) =>
path.join(homeDir, "dev", "apps", `dir-${i + 1}`),
);
await import("node:fs/promises").then((fs) =>
Promise.all([
fs.mkdir(projectDir, { recursive: true }),
...dirs.map((d) => fs.mkdir(d, { recursive: true })),
]),
);
try {
const stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
workspace: { added_dirs: dirs },
});
const result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.status, 0, result.stderr || "non-zero exit");
const firstLine = stripAnsi(result.stdout).split("\n")[0];
for (let i = 1; i <= 5; i++) {
assert.match(firstLine, new RegExp(`\\+dir-${i}\\b`));
}
assert.doesNotMatch(firstLine, /\+dir-6\b/);
assert.doesNotMatch(firstLine, /\+dir-7\b/);
assert.match(firstLine, /\+2 more/);
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test("CLI truncates long inline added_dirs basenames", async (t) => {
const fixturePath = fileURLToPath(
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
);
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
const longName = "a".repeat(40);
const longDir = path.join(homeDir, "dev", "apps", longName);
await import("node:fs/promises").then((fs) =>
Promise.all([
fs.mkdir(projectDir, { recursive: true }),
fs.mkdir(longDir, { recursive: true }),
]),
);
try {
const stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
workspace: { added_dirs: [longDir] },
});
const result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.status, 0, result.stderr || "non-zero exit");
const firstLine = stripAnsi(result.stdout).split("\n")[0];
assert.match(firstLine, /\+a+…/, "long basename should be truncated with ellipsis");
assert.doesNotMatch(firstLine, new RegExp(`\\+${longName}`));
const m = firstLine.match(/\+(a+…)/);
assert.ok(m && m[1].length <= 24, `truncated name should be ≤24 chars, got ${m && m[1].length}`);
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
async function writeHudConfig(homeDir, config) {
const fs = await import("node:fs/promises");
const dir = path.join(homeDir, ".claude", "plugins", "claude-hud");
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(path.join(dir, "config.json"), JSON.stringify(config), "utf8");
}
test("CLI renders line layout 'Added dirs:' on a separate line", async (t) => {
const fixturePath = fileURLToPath(
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
);
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
const dirA = path.join(homeDir, "dev", "apps", "shared-utils");
const dirB = path.join(homeDir, "dev", "apps", "mobile-app");
await import("node:fs/promises").then((fs) =>
Promise.all([
fs.mkdir(projectDir, { recursive: true }),
fs.mkdir(dirA, { recursive: true }),
fs.mkdir(dirB, { recursive: true }),
]),
);
await writeHudConfig(homeDir, { display: { addedDirsLayout: "line" } });
try {
const stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
workspace: { added_dirs: [dirA, dirB] },
});
const result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.status, 0, result.stderr || "non-zero exit");
const lines = stripAnsi(result.stdout).split("\n");
assert.doesNotMatch(lines[0], /\+shared-utils|\+mobile-app/, "inline + prefix should not appear in line mode");
const dirsLine = lines.find((l) => l.includes("Added dirs:"));
assert.ok(dirsLine, `expected an 'Added dirs:' line, got:\n${result.stdout}`);
assert.match(dirsLine, /shared-utils/);
assert.match(dirsLine, /mobile-app/);
assert.match(dirsLine, /shared-utils,\s*mobile-app/);
assert.doesNotMatch(dirsLine, /\bmore\b/, "two dirs should not trigger overflow");
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test("CLI renders inline added_dirs even when showProject is false", async (t) => {
const fixturePath = fileURLToPath(
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
);
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
const addedDir = path.join(homeDir, "dev", "apps", "lib-foo");
await import("node:fs/promises").then((fs) =>
Promise.all([
fs.mkdir(projectDir, { recursive: true }),
fs.mkdir(addedDir, { recursive: true }),
]),
);
await writeHudConfig(homeDir, { display: { showProject: false } });
try {
const stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
workspace: { added_dirs: [addedDir] },
});
const result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.status, 0, result.stderr || "non-zero exit");
const firstLine = stripAnsi(result.stdout).split("\n")[0];
assert.match(firstLine, /\[Opus\]/, "model bracket should still render (sanity)");
assert.doesNotMatch(firstLine, /my-project/, "project name should be hidden");
assert.match(firstLine, /\+lib-foo/, "added dirs should still render");
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test("CLI applies caps in line layout (overflow + truncation)", async (t) => {
const fixturePath = fileURLToPath(
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
);
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
const dirs = Array.from({ length: 7 }, (_, i) =>
path.join(homeDir, "dev", "apps", `dir-${i + 1}`),
);
const longName = "b".repeat(40);
const longDir = path.join(homeDir, "dev", "apps", longName);
await import("node:fs/promises").then((fs) =>
Promise.all([
fs.mkdir(projectDir, { recursive: true }),
fs.mkdir(longDir, { recursive: true }),
...dirs.map((d) => fs.mkdir(d, { recursive: true })),
]),
);
await writeHudConfig(homeDir, { display: { addedDirsLayout: "line" } });
try {
let stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
workspace: { added_dirs: dirs },
});
let result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.status, 0, result.stderr || "non-zero exit");
let dirsLine = stripAnsi(result.stdout).split("\n").find((l) => l.includes("Added dirs:"));
assert.ok(dirsLine, `expected 'Added dirs:' line, got:\n${result.stdout}`);
assert.match(dirsLine, /dir-1/);
assert.match(dirsLine, /dir-5/);
assert.doesNotMatch(dirsLine, /dir-6/);
assert.doesNotMatch(dirsLine, /dir-7/);
assert.match(dirsLine, /\+2 more/);
stdin = JSON.stringify({
model: { display_name: "Opus" },
context_window: {
context_window_size: 200000,
current_usage: { input_tokens: 45000 },
},
transcript_path: fixturePath,
cwd: projectDir,
workspace: { added_dirs: [longDir] },
});
result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: stdin,
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.status, 0, result.stderr || "non-zero exit");
dirsLine = stripAnsi(result.stdout).split("\n").find((l) => l.includes("Added dirs:"));
assert.ok(dirsLine, `expected 'Added dirs:' line, got:\n${result.stdout}`);
assert.match(dirsLine, /b+…/);
assert.doesNotMatch(dirsLine, new RegExp(longName));
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test("CLI prints initializing message on empty stdin", async (t) => {
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
try {
const result = spawnSync("node", ["dist/index.js"], {
cwd: path.resolve(process.cwd()),
input: "",
encoding: "utf8",
env: { ...process.env, HOME: homeDir, LANG: "C" },
});
if (skipIfSpawnBlocked(result, t)) return;
assert.equal(result.error, undefined, result.error?.message);
assert.equal(result.status, 0, result.stderr || "non-zero exit");
const normalized = stripAnsi(result.stdout)
.replace(/\u00A0/g, " ")
.trimEnd();
assert.ok(
normalized.startsWith("[claude-hud] Initializing..."),
`unexpected output: ${normalized}`,
);
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});