[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,617 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const {
collectLocalTreeEntries,
MAX_LOCAL_TREE_DIRECTORIES,
MAX_LOCAL_TREE_ENTRIES,
WINDOWS_ATTRIB_TIMEOUT_MS,
parseAttribOutput,
listWindowsHiddenBasenames,
statLocal,
lstatLocal,
deleteLocalFile,
} = require("./localFsBridge.cjs");
test("local tree traversal defaults match the remote traversal safety budget", () => {
assert.equal(MAX_LOCAL_TREE_DIRECTORIES, 50_000);
assert.equal(MAX_LOCAL_TREE_ENTRIES, 200_000);
});
test("expected symlink delete refuses a replacement file", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-delete-type-"));
const target = path.join(root, "target");
t.after(async () => fs.promises.rm(root, { recursive: true, force: true }));
await fs.promises.writeFile(target, "new owner");
await assert.rejects(
() => deleteLocalFile(null, { path: target, expectedType: "symlink" }),
(error) => {
assert.equal(error.code, "ESTALE");
return true;
},
);
assert.equal(await fs.promises.readFile(target, "utf8"), "new owner");
});
test("parseAttribOutput returns an empty set for empty input", () => {
assert.equal(parseAttribOutput("").size, 0);
assert.equal(parseAttribOutput("\r\n\r\n").size, 0);
});
test("parseAttribOutput captures basenames of files with the H flag", () => {
const stdout = [
"A C:\\Users\\foo\\public.txt",
" H C:\\Users\\foo\\.secret",
"A H R C:\\Users\\foo\\hidden-readonly.exe",
"A C:\\Users\\foo\\another.log",
].join("\r\n");
const hidden = parseAttribOutput(stdout);
assert.deepEqual(
[...hidden].sort(),
[".secret", "hidden-readonly.exe"].sort(),
);
});
test("parseAttribOutput ignores the trailing [DIR] marker on some Windows versions", () => {
const stdout = [
" H C:\\data\\node_modules [DIR]",
" H C:\\data\\.git [DIR]",
"A C:\\data\\README.md",
].join("\r\n");
const hidden = parseAttribOutput(stdout);
assert.deepEqual([...hidden].sort(), [".git", "node_modules"].sort());
});
test("parseAttribOutput preserves filenames that legitimately end with bracketed suffixes", () => {
// Regression: a prior version stripped ANY trailing bracketed suffix
// via /\s+\[[^\]]+\]\s*$/, truncating "Notes [old]" to "Notes".
// Only the literal [DIR] marker that attrib emits with /d is a parser
// artifact; user-facing filenames with brackets must survive intact so
// hiddenSet.has(entry.name) still matches the actual readdir entry.
const stdout = [
" H C:\\data\\Notes [old]",
" H C:\\data\\Draft [v2].md",
" H C:\\data\\archived [2024]",
" H C:\\data\\node_modules [DIR]",
].join("\r\n");
const hidden = parseAttribOutput(stdout);
assert.deepEqual(
[...hidden].sort(),
["Draft [v2].md", "Notes [old]", "archived [2024]", "node_modules"].sort(),
);
});
test("parseAttribOutput handles UNC paths", () => {
const stdout = [
" H \\\\fileserver\\share\\secret.cfg",
"A \\\\fileserver\\share\\public.cfg",
].join("\r\n");
const hidden = parseAttribOutput(stdout);
assert.deepEqual([...hidden], ["secret.cfg"]);
});
test("parseAttribOutput skips malformed lines", () => {
const stdout = [
"Parameter format not correct",
"",
" H C:\\good\\hidden.txt",
"File not found",
" H not-a-windows-path.txt",
].join("\r\n");
const hidden = parseAttribOutput(stdout);
assert.deepEqual([...hidden], ["hidden.txt"]);
});
test("listWindowsHiddenBasenames returns an empty set on non-Windows without spawning anything", async () => {
// Running this test file is only meaningful on a non-Windows host for this
// assertion. On Windows CI we skip the subprocess-free guarantee.
if (process.platform === "win32") return;
const result = await listWindowsHiddenBasenames("/tmp");
assert.ok(result instanceof Set);
assert.equal(result.size, 0);
});
test("listWindowsHiddenBasenames invokes attrib.exe with /d so hidden directories aren't omitted", async () => {
// Regression: without `/d`, `attrib <dir>\*` treats the wildcard as
// file-centric and hidden directories (node_modules, .git, …) never
// reach parseAttribOutput — the SFTP browser then shows them as
// not-hidden, a behavior regression from the per-file implementation.
const Module = require("node:module");
const realChildProcess = require("node:child_process");
const originalLoad = Module._load;
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
let capturedArgs = null;
let capturedExecutable = null;
let capturedOptions = null;
Module._load = function patchedLoad(request, parent, isMain) {
if (request === "node:child_process") {
return {
...realChildProcess,
execFile: (executable, args, options, cb) => {
capturedExecutable = executable;
capturedArgs = args;
capturedOptions = options;
cb(null, { stdout: "", stderr: "" });
},
};
}
return originalLoad.call(this, request, parent, isMain);
};
Object.defineProperty(process, "platform", {
value: "win32",
writable: true,
configurable: true,
});
const bridgePath = require.resolve("./localFsBridge.cjs");
delete require.cache[bridgePath];
try {
const { listWindowsHiddenBasenames: fn } = require("./localFsBridge.cjs");
await fn("C:\\fixture");
} finally {
Module._load = originalLoad;
Object.defineProperty(process, "platform", originalPlatform);
delete require.cache[bridgePath];
}
assert.equal(capturedExecutable, "attrib.exe");
assert.ok(
Array.isArray(capturedArgs) && capturedArgs.includes("/d"),
`expected /d in attrib args so hidden directories are included, got ${JSON.stringify(capturedArgs)}`,
);
assert.equal(capturedOptions.timeout, WINDOWS_ATTRIB_TIMEOUT_MS);
assert.equal(capturedOptions.maxBuffer, 64 * 1024 * 1024);
});
test("collectLocalTreeEntries preserves empty directories in selected folders", async () => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-"));
const selected = path.join(root, "project");
await fs.promises.mkdir(path.join(selected, "empty"), { recursive: true });
await fs.promises.mkdir(path.join(selected, "src"), { recursive: true });
await fs.promises.writeFile(path.join(selected, "src", "main.txt"), "hello");
try {
const entries = await collectLocalTreeEntries(selected);
const summary = entries.map((entry) => ({
relativePath: entry.relativePath,
type: entry.type,
}));
assert.deepEqual(summary, [
{ relativePath: "project", type: "directory" },
{ relativePath: "project/empty", type: "directory" },
{ relativePath: "project/src", type: "directory" },
{ relativePath: "project/src/main.txt", type: "file" },
]);
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("collectLocalTreeEntries reports progressive file counts during scan", async () => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-progress-"));
const selected = path.join(root, "project");
await fs.promises.mkdir(path.join(selected, "src"), { recursive: true });
await fs.promises.writeFile(path.join(selected, "readme.txt"), "hi");
await fs.promises.writeFile(path.join(selected, "src", "main.txt"), "hello");
const progress = [];
try {
const entries = await collectLocalTreeEntries(selected, {}, (stats) => {
progress.push({ ...stats });
});
assert.equal(entries.filter((entry) => entry.type === "file").length, 2);
assert.ok(progress.length >= 1, "expected at least one progress event");
const final = progress[progress.length - 1];
assert.equal(final.fileCount, 2);
assert.equal(final.directoryCount, 2);
assert.equal(final.entryCount, 4);
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("collectLocalTreeEntries aborts when cancelled mid-scan", async () => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-cancel-"));
const selected = path.join(root, "project");
await fs.promises.mkdir(path.join(selected, "a"), { recursive: true });
await fs.promises.mkdir(path.join(selected, "b"), { recursive: true });
await fs.promises.writeFile(path.join(selected, "a", "1.txt"), "1");
await fs.promises.writeFile(path.join(selected, "b", "2.txt"), "2");
let cancel = false;
try {
await assert.rejects(
() => collectLocalTreeEntries(
selected,
{},
() => {
// Cancel as soon as progress starts flowing.
cancel = true;
},
() => cancel,
),
/cancelled/i,
);
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("collectLocalTreeEntries bounds and parallelizes metadata reads within a directory", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-parallel-"));
const selected = path.join(root, "project");
await fs.promises.mkdir(selected, { recursive: true });
await Promise.all(Array.from({ length: 64 }, (_, index) => (
fs.promises.writeFile(path.join(selected, `file-${String(index).padStart(3, "0")}.txt`), "x")
)));
const originalLstat = fs.promises.lstat;
let active = 0;
let maxActive = 0;
fs.promises.lstat = async (...args) => {
active += 1;
maxActive = Math.max(maxActive, active);
try {
await new Promise((resolve) => setImmediate(resolve));
return await originalLstat.apply(fs.promises, args);
} finally {
active -= 1;
}
};
t.after(async () => {
fs.promises.lstat = originalLstat;
await fs.promises.rm(root, { recursive: true, force: true });
});
const entries = await collectLocalTreeEntries(selected);
assert.equal(entries.length, 65);
assert.ok(maxActive > 1, `expected concurrent lstat calls, got ${maxActive}`);
assert.ok(maxActive <= 32, `metadata concurrency must stay bounded, got ${maxActive}`);
});
test("collectLocalTreeEntries skips a child that disappears during inspection", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-vanished-"));
const selected = path.join(root, "project");
await fs.promises.mkdir(selected, { recursive: true });
await fs.promises.writeFile(path.join(selected, "stable.txt"), "stable");
await fs.promises.writeFile(path.join(selected, "vanished.txt"), "vanished");
const originalLstat = fs.promises.lstat;
fs.promises.lstat = async (candidate, ...args) => {
if (candidate === path.join(selected, "vanished.txt")) {
const error = new Error("file disappeared");
error.code = "ENOENT";
throw error;
}
return originalLstat.call(fs.promises, candidate, ...args);
};
t.after(async () => {
fs.promises.lstat = originalLstat;
await fs.promises.rm(root, { recursive: true, force: true });
});
const entries = await collectLocalTreeEntries(selected);
assert.deepEqual(entries.map((entry) => entry.relativePath), [
"project",
"project/stable.txt",
]);
});
test("collectLocalTreeEntries does not follow a directory symlink cycle", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-cycle-"));
const selected = path.join(root, "project");
const nested = path.join(selected, "nested");
await fs.promises.mkdir(nested, { recursive: true });
try {
await fs.promises.symlink(selected, path.join(nested, "back-to-project"), process.platform === "win32" ? "junction" : "dir");
} catch (error) {
await fs.promises.rm(root, { recursive: true, force: true });
t.skip(`symlink creation unavailable: ${error instanceof Error ? error.message : String(error)}`);
return;
}
try {
const entries = await Promise.race([
collectLocalTreeEntries(selected),
new Promise((_, reject) => setTimeout(() => reject(new Error("local tree scan followed a symlink cycle")), 500)),
]);
const loopRows = entries.filter((entry) => entry.relativePath.endsWith("back-to-project"));
assert.equal(loopRows.length, 0, "cyclic directory links must be skipped instead of uploaded as files");
assert.ok(entries.length <= 3, `cycle scan must stay bounded, got ${entries.length} rows`);
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("collectLocalTreeEntries skips a symlink back to a non-root ancestor", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-nested-cycle-"));
const selected = path.join(root, "project");
const nested = path.join(selected, "a", "b");
await fs.promises.mkdir(nested, { recursive: true });
await fs.promises.writeFile(path.join(selected, "a", "keep.txt"), "keep");
try {
await fs.promises.symlink(
path.join(selected, "a"),
path.join(nested, "back"),
process.platform === "win32" ? "junction" : "dir",
);
} catch (error) {
await fs.promises.rm(root, { recursive: true, force: true });
t.skip(`symlink creation unavailable: ${error instanceof Error ? error.message : String(error)}`);
return;
}
try {
const entries = await Promise.race([
collectLocalTreeEntries(selected),
new Promise((_, reject) => setTimeout(() => reject(new Error("local tree scan followed a nested ancestor symlink cycle")), 500)),
]);
const loopRows = entries.filter((entry) => entry.relativePath.includes("/back/"));
assert.equal(loopRows.length, 0, "symlink to a non-root ancestor must not duplicate the ancestor subtree");
assert.equal(
entries.filter((entry) => entry.relativePath.endsWith("/back")).length,
0,
"cyclic directory links must be skipped instead of uploaded as files",
);
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("collectLocalTreeEntries counts a cyclic directory alias against the global budget", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-cycle-budget-"));
const selected = path.join(root, "project");
const nested = path.join(selected, "nested");
await fs.promises.mkdir(nested, { recursive: true });
try {
await fs.promises.symlink(selected, path.join(nested, "back"), process.platform === "win32" ? "junction" : "dir");
} catch (error) {
await fs.promises.rm(root, { recursive: true, force: true });
t.skip(`symlink creation unavailable: ${error instanceof Error ? error.message : String(error)}`);
return;
}
try {
await assert.rejects(
collectLocalTreeEntries(selected, { maxDirectories: 2, maxEntries: 10 }),
/Local directory traversal directory limit exceeded \(2\).*smaller folder/i,
);
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("collectLocalTreeEntries bounds non-cyclic directory symlink fan-out", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-fanout-"));
const selected = path.join(root, "project");
const shared = path.join(root, "shared");
await fs.promises.mkdir(selected, { recursive: true });
await fs.promises.mkdir(shared, { recursive: true });
await fs.promises.writeFile(path.join(shared, "payload.txt"), "payload");
try {
for (let index = 0; index < 4; index += 1) {
await fs.promises.symlink(
shared,
path.join(selected, `alias-${index}`),
process.platform === "win32" ? "junction" : "dir",
);
}
} catch (error) {
await fs.promises.rm(root, { recursive: true, force: true });
t.skip(`symlink creation unavailable: ${error instanceof Error ? error.message : String(error)}`);
return;
}
try {
await assert.rejects(
collectLocalTreeEntries(selected, { maxDirectories: 4, maxEntries: 20 }),
/Local directory traversal directory limit exceeded \(4\).*smaller folder/i,
);
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("collectLocalTreeEntries accepts exact directory and entry budget boundaries", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-budget-edge-"));
const selected = path.join(root, "project");
await fs.promises.mkdir(path.join(selected, "nested"), { recursive: true });
await fs.promises.writeFile(path.join(selected, "one.txt"), "1");
await fs.promises.writeFile(path.join(selected, "nested", "two.txt"), "2");
t.after(() => fs.promises.rm(root, { recursive: true, force: true }));
const entries = await collectLocalTreeEntries(selected, {
maxDirectories: 2,
maxEntries: 3,
});
assert.deepEqual(entries.map((entry) => entry.relativePath), [
"project",
"project/nested",
"project/one.txt",
"project/nested/two.txt",
]);
});
test("collectLocalTreeEntries rejects one entry beyond the global entry budget", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-entry-budget-"));
const selected = path.join(root, "project");
await fs.promises.mkdir(selected, { recursive: true });
await Promise.all([
fs.promises.writeFile(path.join(selected, "one.txt"), "1"),
fs.promises.writeFile(path.join(selected, "two.txt"), "2"),
fs.promises.writeFile(path.join(selected, "three.txt"), "3"),
]);
t.after(() => fs.promises.rm(root, { recursive: true, force: true }));
await assert.rejects(
collectLocalTreeEntries(selected, { maxDirectories: 1, maxEntries: 2 }),
/Local directory traversal entry limit exceeded \(2\).*smaller folder/i,
);
});
test("collectLocalTreeEntries follows a non-cyclic directory symlink for folder upload", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-directory-link-"));
const selected = path.join(root, "project");
const target = path.join(root, "shared-assets");
const link = path.join(selected, "assets");
await fs.promises.mkdir(selected, { recursive: true });
await fs.promises.mkdir(target, { recursive: true });
await fs.promises.writeFile(path.join(target, "logo.txt"), "logo");
try {
await fs.promises.symlink(target, link, process.platform === "win32" ? "junction" : "dir");
} catch (error) {
await fs.promises.rm(root, { recursive: true, force: true });
t.skip(`symlink creation unavailable: ${error instanceof Error ? error.message : String(error)}`);
return;
}
try {
const entries = await collectLocalTreeEntries(selected);
const linkedDirectory = entries.find((entry) => entry.relativePath === "project/assets");
const linkedFile = entries.find((entry) => entry.relativePath === "project/assets/logo.txt");
assert.equal(linkedDirectory?.type, "directory");
assert.equal(linkedFile?.type, "file");
assert.equal(linkedFile?.localPath, path.join(link, "logo.txt"));
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("statLocal follows symlinks and reports target size for resume sizing", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-stat-local-follow-"));
const target = path.join(root, "outside.bin");
const link = path.join(root, "tool.sh");
await fs.promises.writeFile(target, "target-bytes-longer");
try {
await fs.promises.symlink(target, link, "file");
} catch (error) {
await fs.promises.rm(root, { recursive: true, force: true });
t.skip(`symlink creation unavailable: ${error instanceof Error ? error.message : String(error)}`);
return;
}
try {
const linkStat = await fs.promises.lstat(link);
const targetStat = await fs.promises.stat(target);
assert.notEqual(linkStat.size, targetStat.size, "fixture must distinguish link metadata from target");
const result = await statLocal(null, { path: link });
assert.equal(result.type, "file");
assert.equal(result.size, targetStat.size);
assert.equal(result.lastModified, targetStat.mtime.getTime());
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("lstatLocal classifies symlinks without following the target", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-lstat-local-link-"));
const target = path.join(root, "outside.bin");
const link = path.join(root, "tool.sh");
await fs.promises.writeFile(target, "target-bytes-longer");
try {
await fs.promises.symlink(target, link, "file");
} catch (error) {
await fs.promises.rm(root, { recursive: true, force: true });
t.skip(`symlink creation unavailable: ${error instanceof Error ? error.message : String(error)}`);
return;
}
try {
const linkStat = await fs.promises.lstat(link);
const targetStat = await fs.promises.stat(target);
assert.notEqual(linkStat.size, targetStat.size, "fixture must distinguish link metadata from target");
const result = await lstatLocal(null, { path: link });
assert.equal(result.type, "symlink");
assert.equal(result.size, linkStat.size);
assert.equal(result.lastModified, linkStat.mtime.getTime());
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("collectLocalTreeEntries skips realpath for ordinary files and directories", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-realpath-"));
const selected = path.join(root, "project");
await fs.promises.mkdir(path.join(selected, "src"), { recursive: true });
await fs.promises.writeFile(path.join(selected, "src", "index.txt"), "ok");
const originalRealpath = fs.promises.realpath;
let realpathCalls = 0;
fs.promises.realpath = async (...args) => {
realpathCalls += 1;
return originalRealpath.apply(fs.promises, args);
};
t.after(async () => {
fs.promises.realpath = originalRealpath;
await fs.promises.rm(root, { recursive: true, force: true });
});
const entries = await collectLocalTreeEntries(selected);
assert.equal(entries.some((entry) => entry.relativePath === "project/src/index.txt"), true);
assert.equal(realpathCalls, 1, "only the selected root should be realpath'd");
});
test("collectLocalTreeEntries uses the target metadata for a leaf file symlink", async (t) => {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-upload-tree-file-link-"));
const selected = path.join(root, "project");
const target = path.join(root, "target.bin");
const link = path.join(selected, "linked.bin");
await fs.promises.mkdir(selected, { recursive: true });
await fs.promises.writeFile(target, "target-file-contents");
try {
await fs.promises.symlink(target, link, "file");
} catch (error) {
await fs.promises.rm(root, { recursive: true, force: true });
t.skip(`symlink creation unavailable: ${error instanceof Error ? error.message : String(error)}`);
return;
}
try {
const targetStat = await fs.promises.stat(target);
const linkStat = await fs.promises.lstat(link);
assert.notEqual(linkStat.size, targetStat.size, "fixture must distinguish link metadata from target metadata");
const entries = await collectLocalTreeEntries(selected);
const linked = entries.find((entry) => entry.relativePath === "project/linked.bin");
assert.equal(linked?.type, "file");
assert.equal(linked?.size, targetStat.size);
assert.equal(linked?.lastModified, targetStat.mtime.getTime());
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});
test("readLocalFile returns only the trailing maxBytes when requested", async () => {
const { readLocalFile } = require("./localFsBridge.cjs");
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "netcatty-local-read-"));
const filePath = path.join(root, "hist.txt");
const body = "AAAA\nBBBB\nCCCC\nDDDD\n";
await fs.promises.writeFile(filePath, body);
try {
const full = await readLocalFile(null, { path: filePath });
assert.equal(Buffer.from(full).toString("utf8"), body);
const tailed = await readLocalFile(null, { path: filePath, maxBytes: 10 });
assert.equal(Buffer.from(tailed).toString("utf8"), body.slice(-10));
} finally {
await fs.promises.rm(root, { recursive: true, force: true });
}
});