Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion etc/Dependency Report.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ This is a list of all modules leaked by Vortex to extensions. Any module listed
| tailwindcss | 4.2.2 |
| tmp | 0.1.0 |
| tough-cookie | 6.0.1 |
| turbowalk | 3.1.1 |
| universal-analytics | 0.4.23 |
| uuid | 3.4.0 |
| vortex-parse-ini | 0.4.0 |
Expand Down
2 changes: 1 addition & 1 deletion extensions/collections/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"shortid": "catalog:",
"tmp": "catalog:",
"ts-v-gen": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"typescript": "catalog:",
"vortex-api": "workspace:*"
}
Expand Down
2 changes: 1 addition & 1 deletion extensions/extension-dashlet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"react-i18next": "catalog:",
"redux": "catalog:",
"redux-thunk": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"typescript": "catalog:",
"vortex-api": "workspace:*"
}
Expand Down
2 changes: 1 addition & 1 deletion extensions/gamebryo-savegame-management/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"redux": "catalog:",
"redux-act": "catalog:",
"redux-thunk": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"vortex-api": "workspace:*",
"vortex-parse-ini": "catalog:"
},
Expand Down
2 changes: 1 addition & 1 deletion extensions/games/game-stardewvalley/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"redux-act": "catalog:",
"relaxed-json": "catalog:",
"semver": "catalog:",
"turbowalk": "catalog:"
"turbowalk": "workspace:*"
},
"devDependencies": {
"@nexusmods/nexus-api": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion extensions/gamestore-origin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@types/libxmljs": "catalog:",
"bluebird": "catalog:",
"copyfiles": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"typescript": "catalog:",
"vortex-api": "workspace:*",
"winapi-bindings": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion extensions/gamestore-xbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"copyfiles": "catalog:",
"redux": "catalog:",
"redux-thunk": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"typescript": "catalog:",
"vortex-api": "workspace:*",
"winapi-bindings": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion extensions/mod-content/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"react": "catalog:",
"react-bootstrap": "catalog:",
"redux": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"vortex-api": "workspace:*"
}
}
2 changes: 1 addition & 1 deletion extensions/mod-dependency-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"reselect": "catalog:",
"semver": "catalog:",
"shortid": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"vortex-api": "workspace:*"
}
}
2 changes: 1 addition & 1 deletion extensions/mod-report/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"bs58": "catalog:",
"copyfiles": "catalog:",
"electron": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"typescript": "catalog:",
"vortex-api": "workspace:*"
}
Expand Down
2 changes: 1 addition & 1 deletion extensions/new-file-monitor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"bluebird": "catalog:",
"copyfiles": "catalog:",
"redux": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"typescript": "catalog:",
"vortex-api": "workspace:*"
}
Expand Down
2 changes: 1 addition & 1 deletion extensions/script-extender-error-check/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"i18next": "catalog:",
"react": "catalog:",
"react-bootstrap": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"vortex-api": "workspace:*"
}
}
2 changes: 1 addition & 1 deletion extensions/test-setup/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"copyfiles": "catalog:",
"i18next": "catalog:",
"react-bootstrap": "catalog:",
"turbowalk": "catalog:",
"turbowalk": "workspace:*",
"vortex-api": "workspace:*",
"winapi-bindings": "catalog:"
}
Expand Down
14 changes: 0 additions & 14 deletions flatpak/generated-sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -350,20 +350,6 @@
"dest-filename": "7c1b6f1d6437f2238be51316de823b0fbd63e4c0",
"dest": "flatpak-node/yarn-mirror"
},
{
"type": "file",
"url": "https://codeload.github.com/Nexus-Mods/node-turbowalk/tar.gz/314f2cdb904a9a075c35261e8a1de10b0af20295",
"sha256": "c2be72ccc8122002afa064561db4b0d22f39a960e75ddc2c5e3272c399838dc6",
"dest-filename": "314f2cdb904a9a075c35261e8a1de10b0af20295",
"dest": "flatpak-node/yarn-mirror"
},
{
"type": "file",
"url": "https://codeload.github.com/Nexus-Mods/node-turbowalk/tar.gz/cf4238a4d192763c8133500ce9b366a71b262a33",
"sha256": "85c82bb0151739fec38aa48eb276987ac62a7a0984283d1b3b457019de7f6574",
"dest-filename": "cf4238a4d192763c8133500ce9b366a71b262a33",
"dest": "flatpak-node/yarn-mirror"
},
{
"type": "file",
"url": "https://codeload.github.com/Nexus-Mods/node-vortexmt/tar.gz/5251ea012ce856742aeaf73a583073497aff773a",
Expand Down
14 changes: 14 additions & 0 deletions packages/turbowalk/package.json
Comment thread
halgari marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "turbowalk",
"version": "4.0.0",
"description": "Fast directory walking using koffi FFI on Windows, fs on Linux",
"main": "src/index.ts",
"types": "src/index.ts",
"private": true,
"type": "commonjs",
"devDependencies": {
"bluebird": "catalog:",
"@types/bluebird": "catalog:",
"koffi": "^2.9.0"
}
}
114 changes: 114 additions & 0 deletions packages/turbowalk/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";

import turbowalk, { type IEntry } from "./index";

let tmpDir: string;

beforeAll(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "turbowalk-test-"));

// Create a directory tree:
// root/
// file1.txt (10 bytes)
// file2.dat (20 bytes)
// .hidden (0 bytes)
// subdir/
// nested.txt (5 bytes)
// emptydir/
// .hiddendir/
// inside.txt (1 byte)
fs.writeFileSync(path.join(tmpDir, "file1.txt"), "0123456789");
fs.writeFileSync(path.join(tmpDir, "file2.dat"), "01234567890123456789");
fs.writeFileSync(path.join(tmpDir, ".hidden"), "");
fs.mkdirSync(path.join(tmpDir, "subdir"));
fs.writeFileSync(path.join(tmpDir, "subdir", "nested.txt"), "hello");
fs.mkdirSync(path.join(tmpDir, "emptydir"));
fs.mkdirSync(path.join(tmpDir, ".hiddendir"));
fs.writeFileSync(path.join(tmpDir, ".hiddendir", "inside.txt"), "x");
});

afterAll(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});

function collect(dir: string, options?: Parameters<typeof turbowalk>[2]): Promise<IEntry[]> {
const all: IEntry[] = [];
return turbowalk(dir, (entries) => { all.push(...entries); }, options).then(() => all);
}

describe("turbowalk", () => {
it("walks all files and directories recursively", async () => {
const entries = await collect(tmpDir, { skipHidden: false });
const names = entries.map((e) => path.basename(e.filePath)).sort();
expect(names).toEqual([
".hidden", ".hiddendir", "emptydir", "file1.txt", "file2.dat",
"inside.txt", "nested.txt", "subdir",
]);
});

it("skips hidden files and directories by default", async () => {
const entries = await collect(tmpDir);
const names = entries.map((e) => path.basename(e.filePath)).sort();
// .hidden, .hiddendir, and inside.txt (inside .hiddendir) should be excluded
expect(names).toEqual(["emptydir", "file1.txt", "file2.dat", "nested.txt", "subdir"]);
});

it("reports correct isDirectory flag", async () => {
const entries = await collect(tmpDir, { skipHidden: false });
const dirs = entries.filter((e) => e.isDirectory).map((e) => path.basename(e.filePath)).sort();
const files = entries.filter((e) => !e.isDirectory).map((e) => path.basename(e.filePath)).sort();
expect(dirs).toEqual([".hiddendir", "emptydir", "subdir"]);
expect(files).toEqual([".hidden", "file1.txt", "file2.dat", "inside.txt", "nested.txt"]);
});

it("reports correct file sizes", async () => {
const entries = await collect(tmpDir, { skipHidden: false });
const file1 = entries.find((e) => path.basename(e.filePath) === "file1.txt");
const file2 = entries.find((e) => path.basename(e.filePath) === "file2.dat");
expect(file1?.size).toBe(10);
expect(file2?.size).toBe(20);
});

it("reports mtime as unix seconds", async () => {
const entries = await collect(tmpDir, { skipHidden: false });
const file1 = entries.find((e) => path.basename(e.filePath) === "file1.txt");
expect(file1?.mtime).toBeGreaterThan(0);
// Should be within a few seconds of now
const now = Math.floor(Date.now() / 1000);
expect(file1!.mtime).toBeGreaterThan(now - 60);
expect(file1!.mtime).toBeLessThanOrEqual(now + 1);
});

it("respects recurse: false", async () => {
const entries = await collect(tmpDir, { recurse: false, skipHidden: false });
const names = entries.map((e) => path.basename(e.filePath)).sort();
// Should only include top-level entries, not nested.txt or inside.txt
expect(names).toEqual([".hidden", ".hiddendir", "emptydir", "file1.txt", "file2.dat", "subdir"]);
});

it("handles non-existent directory gracefully", async () => {
const entries = await collect(path.join(tmpDir, "nonexistent"));
expect(entries).toEqual([]);
});

it("calls progress callback with batched entries", async () => {
const calls: number[] = [];
await turbowalk(tmpDir, (entries) => { calls.push(entries.length); }, { skipHidden: false });
// At least one call should have been made
expect(calls.length).toBeGreaterThan(0);
// Total entries across all calls should match
const total = calls.reduce((a, b) => a + b, 0);
expect(total).toBe(8); // 5 files + 3 dirs
});

it("returns full absolute paths", async () => {
const entries = await collect(tmpDir, { skipHidden: false });
for (const entry of entries) {
expect(path.isAbsolute(entry.filePath)).toBe(true);
expect(entry.filePath.startsWith(tmpDir)).toBe(true);
}
});
});
Loading
Loading