mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
expand shim coverage, additional fs shims, add light stream shim
This commit is contained in:
34
packages/shim/src/fs/callback.js
Normal file
34
packages/shim/src/fs/callback.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const CALLBACK_METHODS = [
|
||||||
|
"stat",
|
||||||
|
"lstat",
|
||||||
|
"readdir",
|
||||||
|
"readFile",
|
||||||
|
"writeFile",
|
||||||
|
"appendFile",
|
||||||
|
"unlink",
|
||||||
|
"rename",
|
||||||
|
"mkdir",
|
||||||
|
"rmdir",
|
||||||
|
"rm",
|
||||||
|
"copyFile",
|
||||||
|
"access",
|
||||||
|
"utimes",
|
||||||
|
"chmod",
|
||||||
|
];
|
||||||
|
|
||||||
|
export function createFsCallbacks(fsPromises) {
|
||||||
|
const callbacks = {};
|
||||||
|
|
||||||
|
for (const name of CALLBACK_METHODS) {
|
||||||
|
callbacks[name] = function (...args) {
|
||||||
|
const callback = args.pop();
|
||||||
|
|
||||||
|
fsPromises[name](...args).then(
|
||||||
|
(result) => callback(null, result),
|
||||||
|
(err) => callback(err),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return callbacks;
|
||||||
|
}
|
||||||
47
packages/shim/src/fs/callback.test.js
Normal file
47
packages/shim/src/fs/callback.test.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { createFsCallbacks } from "./callback.js";
|
||||||
|
|
||||||
|
describe("fs callbacks", () => {
|
||||||
|
it("resolves the promise result through the callback", async () => {
|
||||||
|
const fakePromises = { readFile: async (p) => `data:${p}` };
|
||||||
|
const cb = createFsCallbacks(fakePromises);
|
||||||
|
|
||||||
|
const result = await new Promise((resolve) =>
|
||||||
|
cb.readFile("/x", (err, data) => resolve([err, data])),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual([null, "data:/x"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes a rejection to the callback as the error argument", async () => {
|
||||||
|
const boom = new Error("nope");
|
||||||
|
const fakePromises = {
|
||||||
|
stat: async () => {
|
||||||
|
throw boom;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const cb = createFsCallbacks(fakePromises);
|
||||||
|
|
||||||
|
const result = await new Promise((resolve) =>
|
||||||
|
cb.stat("/x", (err) => resolve(err)),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(boom);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("forwards the arguments that precede the callback", async () => {
|
||||||
|
let received = null;
|
||||||
|
const fakePromises = {
|
||||||
|
mkdir: async (p, opts) => {
|
||||||
|
received = [p, opts];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const cb = createFsCallbacks(fakePromises);
|
||||||
|
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
cb.mkdir("/d", { recursive: true }, () => resolve()),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(received).toEqual(["/d", { recursive: true }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,6 +6,8 @@ import { createFsSync } from "./sync.js";
|
|||||||
import { createFsWatch } from "./watch.js";
|
import { createFsWatch } from "./watch.js";
|
||||||
import { createWatcherClient } from "./watcher-client.js";
|
import { createWatcherClient } from "./watcher-client.js";
|
||||||
import { createFdOps } from "./fd.js";
|
import { createFdOps } from "./fd.js";
|
||||||
|
import { createFsCallbacks } from "./callback.js";
|
||||||
|
import { realpath, realpathSync } from "./realpath.js";
|
||||||
import { constants } from "./constants.js";
|
import { constants } from "./constants.js";
|
||||||
import { registerReadTransform, removeReadTransform, resolvePath } from "./transforms.js";
|
import { registerReadTransform, removeReadTransform, resolvePath } from "./transforms.js";
|
||||||
import { wsClient } from "../ws-client.js";
|
import { wsClient } from "../ws-client.js";
|
||||||
@@ -18,10 +20,13 @@ const fsSync = createFsSync(metadataCache, contentCache, transport);
|
|||||||
const fsWatch = createFsWatch(transport);
|
const fsWatch = createFsWatch(transport);
|
||||||
const watcherClient = createWatcherClient(metadataCache, contentCache, fsWatch, wsClient);
|
const watcherClient = createWatcherClient(metadataCache, contentCache, fsWatch, wsClient);
|
||||||
const fdOps = createFdOps(metadataCache, contentCache, transport);
|
const fdOps = createFdOps(metadataCache, contentCache, transport);
|
||||||
|
const fsCallbacks = createFsCallbacks(fsPromises);
|
||||||
|
|
||||||
export const fsShim = {
|
export const fsShim = {
|
||||||
promises: fsPromises,
|
promises: fsPromises,
|
||||||
|
|
||||||
|
...fsCallbacks,
|
||||||
|
|
||||||
existsSync: fsSync.existsSync,
|
existsSync: fsSync.existsSync,
|
||||||
readFileSync: fsSync.readFileSync,
|
readFileSync: fsSync.readFileSync,
|
||||||
writeFileSync: fsSync.writeFileSync,
|
writeFileSync: fsSync.writeFileSync,
|
||||||
@@ -29,6 +34,18 @@ export const fsShim = {
|
|||||||
accessSync: fsSync.accessSync,
|
accessSync: fsSync.accessSync,
|
||||||
statSync: fsSync.statSync,
|
statSync: fsSync.statSync,
|
||||||
readdirSync: fsSync.readdirSync,
|
readdirSync: fsSync.readdirSync,
|
||||||
|
lstatSync: fsSync.lstatSync,
|
||||||
|
mkdirSync: fsSync.mkdirSync,
|
||||||
|
rmdirSync: fsSync.rmdirSync,
|
||||||
|
rmSync: fsSync.rmSync,
|
||||||
|
renameSync: fsSync.renameSync,
|
||||||
|
copyFileSync: fsSync.copyFileSync,
|
||||||
|
appendFileSync: fsSync.appendFileSync,
|
||||||
|
utimesSync: fsSync.utimesSync,
|
||||||
|
chmodSync: fsSync.chmodSync,
|
||||||
|
|
||||||
|
realpath,
|
||||||
|
realpathSync,
|
||||||
|
|
||||||
open: fdOps.open,
|
open: fdOps.open,
|
||||||
openSync: fdOps.openSync,
|
openSync: fdOps.openSync,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { markLocalOp } from "./echo-guard.js";
|
import { markLocalOp } from "./echo-guard.js";
|
||||||
import { isInputCachePath, inputCacheGet } from "./input-cache.js";
|
import { isInputCachePath, inputCacheGet } from "./input-cache.js";
|
||||||
import { applyReadTransform, applyWriteTransform, resolvePath } from "./transforms.js";
|
import {
|
||||||
|
applyReadTransform,
|
||||||
|
applyWriteTransform,
|
||||||
|
resolvePath,
|
||||||
|
} from "./transforms.js";
|
||||||
import { hasVirtualFile, getVirtualFile } from "./virtual-files.js";
|
import { hasVirtualFile, getVirtualFile } from "./virtual-files.js";
|
||||||
|
|
||||||
export function createFsPromises(metadataCache, contentCache, transport) {
|
export function createFsPromises(metadataCache, contentCache, transport) {
|
||||||
@@ -270,6 +274,10 @@ export function createFsPromises(metadataCache, contentCache, transport) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async chmod() {
|
||||||
|
// No permission bits in the vault FS. No-op.
|
||||||
|
},
|
||||||
|
|
||||||
async open(path, flags) {
|
async open(path, flags) {
|
||||||
const hasInCache = isInputCachePath(path) && inputCacheGet(path) !== null;
|
const hasInCache = isInputCachePath(path) && inputCacheGet(path) !== null;
|
||||||
const resolved = resolvePath(path);
|
const resolved = resolvePath(path);
|
||||||
|
|||||||
12
packages/shim/src/fs/realpath.js
Normal file
12
packages/shim/src/fs/realpath.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export function realpathSync(path) {
|
||||||
|
return typeof path === "string" ? path : String(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function realpath(path, options, callback) {
|
||||||
|
const cb = typeof options === "function" ? options : callback;
|
||||||
|
|
||||||
|
queueMicrotask(() => cb(null, realpathSync(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
realpath.native = realpath;
|
||||||
|
realpathSync.native = realpathSync;
|
||||||
20
packages/shim/src/fs/realpath.test.js
Normal file
20
packages/shim/src/fs/realpath.test.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { realpath } from "./realpath.js";
|
||||||
|
|
||||||
|
describe("fs realpath shim", () => {
|
||||||
|
it("realpath invokes the callback with the path", async () => {
|
||||||
|
const result = await new Promise((resolve) =>
|
||||||
|
realpath("/a/b.md", (err, p) => resolve(p)),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe("/a/b.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("realpath accepts an options argument before the callback", async () => {
|
||||||
|
const result = await new Promise((resolve) =>
|
||||||
|
realpath("/a/b.md", "utf8", (err, p) => resolve(p)),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe("/a/b.md");
|
||||||
|
});
|
||||||
|
});
|
||||||
128
packages/shim/src/fs/sync-mutations.test.js
Normal file
128
packages/shim/src/fs/sync-mutations.test.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { describe, it, expect, vi } from "vitest";
|
||||||
|
import { createFsSync } from "./sync.js";
|
||||||
|
import { resolvePath } from "./transforms.js";
|
||||||
|
|
||||||
|
function makeDeps() {
|
||||||
|
const store = new Map();
|
||||||
|
|
||||||
|
const metadataCache = {
|
||||||
|
has: (p) => store.has(p),
|
||||||
|
get: (p) => (store.has(p) ? store.get(p) : null),
|
||||||
|
set: (p, m) => store.set(p, m),
|
||||||
|
delete: (p) => store.delete(p),
|
||||||
|
rename: (a, b) => {
|
||||||
|
if (store.has(a)) {
|
||||||
|
store.set(b, store.get(a));
|
||||||
|
store.delete(a);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toStat: (p) =>
|
||||||
|
store.has(p)
|
||||||
|
? {
|
||||||
|
type: store.get(p).type,
|
||||||
|
isDirectory: () => store.get(p).type === "directory",
|
||||||
|
isFile: () => store.get(p).type === "file",
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
readdir: () => [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const contentCache = {
|
||||||
|
get: () => null,
|
||||||
|
set: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
invalidate: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const transport = {
|
||||||
|
mkdir: vi.fn(async () => {}),
|
||||||
|
rmdir: vi.fn(async () => {}),
|
||||||
|
rm: vi.fn(async () => {}),
|
||||||
|
rename: vi.fn(async () => {}),
|
||||||
|
copyFile: vi.fn(async () => {}),
|
||||||
|
appendFile: vi.fn(async () => {}),
|
||||||
|
utimes: vi.fn(async () => {}),
|
||||||
|
stat: vi.fn(async () => ({ type: "file", size: 1 })),
|
||||||
|
};
|
||||||
|
|
||||||
|
return { metadataCache, contentCache, transport, store };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("sync fs mutations", () => {
|
||||||
|
it("lstatSync mirrors statSync", () => {
|
||||||
|
const deps = makeDeps();
|
||||||
|
const fs = createFsSync(deps.metadataCache, deps.contentCache, deps.transport);
|
||||||
|
deps.store.set(resolvePath("dir"), { type: "directory" });
|
||||||
|
|
||||||
|
expect(fs.lstatSync("dir").isDirectory()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("mkdirSync updates the cache and fires the transport", () => {
|
||||||
|
const deps = makeDeps();
|
||||||
|
const fs = createFsSync(deps.metadataCache, deps.contentCache, deps.transport);
|
||||||
|
|
||||||
|
fs.mkdirSync("newdir", { recursive: true });
|
||||||
|
|
||||||
|
expect(deps.store.get("newdir")).toEqual({ type: "directory" });
|
||||||
|
expect(deps.transport.mkdir).toHaveBeenCalledWith("newdir", true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rmSync deletes from the cache and fires the transport", () => {
|
||||||
|
const deps = makeDeps();
|
||||||
|
const fs = createFsSync(deps.metadataCache, deps.contentCache, deps.transport);
|
||||||
|
const key = resolvePath("gone.md");
|
||||||
|
deps.store.set(key, { type: "file" });
|
||||||
|
|
||||||
|
fs.rmSync("gone.md", { recursive: true });
|
||||||
|
|
||||||
|
expect(deps.store.has(key)).toBe(false);
|
||||||
|
expect(deps.transport.rm).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renameSync moves cache metadata and fires the transport", () => {
|
||||||
|
const deps = makeDeps();
|
||||||
|
const fs = createFsSync(deps.metadataCache, deps.contentCache, deps.transport);
|
||||||
|
const from = resolvePath("a.md");
|
||||||
|
const to = resolvePath("b.md");
|
||||||
|
deps.store.set(from, { type: "file", size: 2 });
|
||||||
|
|
||||||
|
fs.renameSync("a.md", "b.md");
|
||||||
|
|
||||||
|
expect(deps.store.has(from)).toBe(false);
|
||||||
|
expect(deps.store.get(to)).toEqual({ type: "file", size: 2 });
|
||||||
|
expect(deps.transport.rename).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("copyFileSync optimistically mirrors source metadata and fires the transport", () => {
|
||||||
|
const deps = makeDeps();
|
||||||
|
const fs = createFsSync(deps.metadataCache, deps.contentCache, deps.transport);
|
||||||
|
const srcKey = resolvePath("src.md");
|
||||||
|
const destKey = resolvePath("dest.md");
|
||||||
|
deps.store.set(srcKey, { type: "file", size: 9 });
|
||||||
|
|
||||||
|
fs.copyFileSync("src.md", "dest.md");
|
||||||
|
|
||||||
|
expect(deps.store.get(destKey)).toEqual({ type: "file", size: 9 });
|
||||||
|
expect(deps.transport.copyFile).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("utimesSync sets mtime and fires the transport", () => {
|
||||||
|
const deps = makeDeps();
|
||||||
|
const fs = createFsSync(deps.metadataCache, deps.contentCache, deps.transport);
|
||||||
|
const key = resolvePath("note.md");
|
||||||
|
deps.store.set(key, { type: "file", mtime: 0 });
|
||||||
|
|
||||||
|
fs.utimesSync("note.md", 111, 222);
|
||||||
|
|
||||||
|
expect(deps.store.get(key).mtime).toBe(222);
|
||||||
|
expect(deps.transport.utimes).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("chmodSync is a no-op that does not throw", () => {
|
||||||
|
const deps = makeDeps();
|
||||||
|
const fs = createFsSync(deps.metadataCache, deps.contentCache, deps.transport);
|
||||||
|
|
||||||
|
expect(() => fs.chmodSync("note.md", 0o644)).not.toThrow();
|
||||||
|
expect(fs.chmodSync("note.md", 0o644)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -180,5 +180,147 @@ export function createFsSync(metadataCache, contentCache, transport) {
|
|||||||
const entries = metadataCache.readdir(path);
|
const entries = metadataCache.readdir(path);
|
||||||
return entries.map((e) => e.name);
|
return entries.map((e) => e.name);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
lstatSync(path) {
|
||||||
|
// No symlinks in our context.
|
||||||
|
return this.statSync(path);
|
||||||
|
},
|
||||||
|
|
||||||
|
mkdirSync(path, options) {
|
||||||
|
const recursive =
|
||||||
|
typeof options === "object" ? !!options.recursive : !!options;
|
||||||
|
|
||||||
|
markLocalOp(path);
|
||||||
|
metadataCache.set(path, { type: "directory" });
|
||||||
|
|
||||||
|
transport.mkdir(path, recursive).catch((e) => {
|
||||||
|
console.error("[shim:fs] mkdirSync background create failed:", path, e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
rmdirSync(path) {
|
||||||
|
markLocalOp(path);
|
||||||
|
metadataCache.delete(path);
|
||||||
|
|
||||||
|
transport.rmdir(path).catch((e) => {
|
||||||
|
console.error("[shim:fs] rmdirSync background remove failed:", path, e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
rmSync(path, options) {
|
||||||
|
const recursive =
|
||||||
|
typeof options === "object" ? !!options.recursive : false;
|
||||||
|
|
||||||
|
const resolved = resolvePath(path);
|
||||||
|
|
||||||
|
markLocalOp(resolved);
|
||||||
|
metadataCache.delete(resolved);
|
||||||
|
contentCache.delete(resolved);
|
||||||
|
|
||||||
|
transport.rm(resolved, recursive).catch((e) => {
|
||||||
|
console.error(
|
||||||
|
"[shim:fs] rmSync background remove failed:",
|
||||||
|
resolved,
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renameSync(oldPath, newPath) {
|
||||||
|
const resolvedOld = resolvePath(oldPath);
|
||||||
|
const resolvedNew = resolvePath(newPath);
|
||||||
|
|
||||||
|
markLocalOp(resolvedOld);
|
||||||
|
markLocalOp(resolvedNew);
|
||||||
|
const content = contentCache.get(resolvedOld);
|
||||||
|
|
||||||
|
if (content !== null) {
|
||||||
|
contentCache.set(resolvedNew, content);
|
||||||
|
contentCache.delete(resolvedOld);
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataCache.rename(resolvedOld, resolvedNew);
|
||||||
|
|
||||||
|
transport.rename(resolvedOld, resolvedNew).catch((e) => {
|
||||||
|
console.error(
|
||||||
|
"[shim:fs] renameSync background rename failed:",
|
||||||
|
resolvedOld,
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
copyFileSync(src, dest) {
|
||||||
|
const resolvedSrc = resolvePath(src);
|
||||||
|
const resolvedDest = resolvePath(dest);
|
||||||
|
|
||||||
|
markLocalOp(resolvedDest);
|
||||||
|
|
||||||
|
// Optimistically mirror the source so a sync read right after sees it.
|
||||||
|
const content = contentCache.get(resolvedSrc);
|
||||||
|
|
||||||
|
if (content !== null) {
|
||||||
|
contentCache.set(resolvedDest, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
const srcMeta = metadataCache.get(resolvedSrc);
|
||||||
|
|
||||||
|
if (srcMeta) {
|
||||||
|
metadataCache.set(resolvedDest, { ...srcMeta });
|
||||||
|
}
|
||||||
|
|
||||||
|
transport
|
||||||
|
.copyFile(src, resolvedDest)
|
||||||
|
.then(() => transport.stat(resolvedDest))
|
||||||
|
.then((meta) => metadataCache.set(resolvedDest, meta))
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(
|
||||||
|
"[shim:fs] copyFileSync background copy failed:",
|
||||||
|
resolvedDest,
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
appendFileSync(path, data) {
|
||||||
|
const resolved = resolvePath(path);
|
||||||
|
|
||||||
|
markLocalOp(resolved);
|
||||||
|
contentCache.invalidate(resolved);
|
||||||
|
|
||||||
|
transport
|
||||||
|
.appendFile(resolved, data)
|
||||||
|
.then(() => transport.stat(resolved))
|
||||||
|
.then((meta) => metadataCache.set(resolved, meta))
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(
|
||||||
|
"[shim:fs] appendFileSync background append failed:",
|
||||||
|
resolved,
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
utimesSync(path, atime, mtime) {
|
||||||
|
const resolved = resolvePath(path);
|
||||||
|
const meta = metadataCache.get(resolved);
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
meta.mtime = typeof mtime === "number" ? mtime : mtime.getTime();
|
||||||
|
metadataCache.set(resolved, meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
transport.utimes(resolved, atime, mtime).catch((e) => {
|
||||||
|
console.error(
|
||||||
|
"[shim:fs] utimesSync background utimes failed:",
|
||||||
|
resolved,
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
chmodSync() {
|
||||||
|
// The vault FS does not model permission bits. No-op.
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,7 +228,12 @@ function installContextMenuFix() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function installGlobalAlias() {
|
||||||
|
window.global = window;
|
||||||
|
}
|
||||||
|
|
||||||
export function installGlobals() {
|
export function installGlobals() {
|
||||||
|
installGlobalAlias();
|
||||||
installProcess();
|
installProcess();
|
||||||
installBuffer();
|
installBuffer();
|
||||||
installFetchShim();
|
installFetchShim();
|
||||||
|
|||||||
82
packages/shim/src/node/assert.js
Normal file
82
packages/shim/src/node/assert.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
class AssertionError extends Error {
|
||||||
|
constructor(message) {
|
||||||
|
super(message || "Assertion failed");
|
||||||
|
this.name = "AssertionError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert(value, message) {
|
||||||
|
if (!value) {
|
||||||
|
throw new AssertionError(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.AssertionError = AssertionError;
|
||||||
|
assert.ok = assert;
|
||||||
|
assert.strict = assert;
|
||||||
|
|
||||||
|
assert.fail = function (message) {
|
||||||
|
throw new AssertionError(message || "Failed");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.equal = function (actual, expected, message) {
|
||||||
|
if (actual != expected) {
|
||||||
|
throw new AssertionError(message || `${actual} == ${expected}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.notEqual = function (actual, expected, message) {
|
||||||
|
if (actual == expected) {
|
||||||
|
throw new AssertionError(message || `${actual} != ${expected}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.strictEqual = function (actual, expected, message) {
|
||||||
|
if (actual !== expected) {
|
||||||
|
throw new AssertionError(message || `${actual} === ${expected}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.notStrictEqual = function (actual, expected, message) {
|
||||||
|
if (actual === expected) {
|
||||||
|
throw new AssertionError(message || `${actual} !== ${expected}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.deepEqual = function (actual, expected, message) {
|
||||||
|
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
||||||
|
throw new AssertionError(message || "deepEqual");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.deepStrictEqual = assert.deepEqual;
|
||||||
|
|
||||||
|
assert.throws = function (fn, message) {
|
||||||
|
let threw = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
} catch {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!threw) {
|
||||||
|
throw new AssertionError(message || "Missing expected exception");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.doesNotThrow = function (fn, message) {
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
} catch (e) {
|
||||||
|
throw new AssertionError(message || `Got unwanted exception: ${e.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.ifError = function (value) {
|
||||||
|
if (value) {
|
||||||
|
throw new AssertionError(`ifError got unwanted exception: ${value}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const assertShim = assert;
|
||||||
30
packages/shim/src/node/assert.test.js
Normal file
30
packages/shim/src/node/assert.test.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { assertShim as assert } from "./assert.js";
|
||||||
|
|
||||||
|
describe("assert shim", () => {
|
||||||
|
it("is callable and throws on a falsy value", () => {
|
||||||
|
expect(() => assert(false)).toThrow();
|
||||||
|
expect(() => assert(true)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("equal throws on mismatch and passes on loose match", () => {
|
||||||
|
expect(() => assert.equal(1, 2)).toThrow();
|
||||||
|
expect(() => assert.equal(1, 1)).not.toThrow();
|
||||||
|
expect(() => assert.equal(1, "1")).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("strictEqual distinguishes type", () => {
|
||||||
|
expect(() => assert.strictEqual(1, "1")).toThrow();
|
||||||
|
expect(() => assert.strictEqual(1, 1)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws() verifies that a function threw", () => {
|
||||||
|
expect(() =>
|
||||||
|
assert.throws(() => {
|
||||||
|
throw new Error("x");
|
||||||
|
}),
|
||||||
|
).not.toThrow();
|
||||||
|
|
||||||
|
expect(() => assert.throws(() => {})).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
50
packages/shim/src/node/constants.js
Normal file
50
packages/shim/src/node/constants.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Linux constant values, to match the platform the process shim reports.
|
||||||
|
// O_SYMLINK and other macOS/BSD flags are omitted so feature checks treat platform as linux
|
||||||
|
|
||||||
|
export const constantsShim = {
|
||||||
|
// File access checks (fs.access mode).
|
||||||
|
F_OK: 0,
|
||||||
|
X_OK: 1,
|
||||||
|
W_OK: 2,
|
||||||
|
R_OK: 4,
|
||||||
|
|
||||||
|
// open() flags.
|
||||||
|
O_RDONLY: 0,
|
||||||
|
O_WRONLY: 1,
|
||||||
|
O_RDWR: 2,
|
||||||
|
O_CREAT: 64,
|
||||||
|
O_EXCL: 128,
|
||||||
|
O_NOCTTY: 256,
|
||||||
|
O_TRUNC: 512,
|
||||||
|
O_APPEND: 1024,
|
||||||
|
O_DIRECTORY: 65536,
|
||||||
|
O_NOATIME: 262144,
|
||||||
|
O_NOFOLLOW: 131072,
|
||||||
|
O_SYNC: 1052672,
|
||||||
|
O_DSYNC: 4096,
|
||||||
|
O_NONBLOCK: 2048,
|
||||||
|
|
||||||
|
// File type bits (st_mode & S_IFMT).
|
||||||
|
S_IFMT: 61440,
|
||||||
|
S_IFREG: 32768,
|
||||||
|
S_IFDIR: 16384,
|
||||||
|
S_IFCHR: 8192,
|
||||||
|
S_IFBLK: 24576,
|
||||||
|
S_IFIFO: 4096,
|
||||||
|
S_IFLNK: 40960,
|
||||||
|
S_IFSOCK: 49152,
|
||||||
|
|
||||||
|
// Permission bits.
|
||||||
|
S_IRWXU: 448,
|
||||||
|
S_IRUSR: 256,
|
||||||
|
S_IWUSR: 128,
|
||||||
|
S_IXUSR: 64,
|
||||||
|
S_IRWXG: 56,
|
||||||
|
S_IRGRP: 32,
|
||||||
|
S_IWGRP: 16,
|
||||||
|
S_IXGRP: 8,
|
||||||
|
S_IRWXO: 7,
|
||||||
|
S_IROTH: 4,
|
||||||
|
S_IWOTH: 2,
|
||||||
|
S_IXOTH: 1,
|
||||||
|
};
|
||||||
85
packages/shim/src/node/stream.js
Normal file
85
packages/shim/src/node/stream.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { EventEmitter } from "./events.js";
|
||||||
|
|
||||||
|
let warned = false;
|
||||||
|
|
||||||
|
function warnNoDataFlow(method) {
|
||||||
|
if (warned) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
warned = true;
|
||||||
|
console.warn(
|
||||||
|
`[shim:stream] ${method}() called, but stream data flow is not implemented. ` +
|
||||||
|
"This plugin needs the full stream shim.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Stream extends EventEmitter {
|
||||||
|
pipe(destination) {
|
||||||
|
warnNoDataFlow("pipe");
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Readable extends Stream {
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
this.readable = true;
|
||||||
|
this._readableState = { options: options || {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
read() {
|
||||||
|
warnNoDataFlow("read");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
push() {
|
||||||
|
warnNoDataFlow("push");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_read() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Writable extends Stream {
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
this.writable = true;
|
||||||
|
this._writableState = { options: options || {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
write() {
|
||||||
|
warnNoDataFlow("write");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
warnNoDataFlow("end");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
_write() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Duplex extends Readable {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
this.writable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
write() {
|
||||||
|
warnNoDataFlow("write");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
warnNoDataFlow("end");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Transform extends Duplex {
|
||||||
|
_transform() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PassThrough extends Transform {}
|
||||||
16
packages/shim/src/node/stream.test.js
Normal file
16
packages/shim/src/node/stream.test.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { describe, it, expect, vi } from "vitest";
|
||||||
|
import { Readable, Writable } from "./stream.js";
|
||||||
|
|
||||||
|
describe("stream shim", () => {
|
||||||
|
it("warns once when data-flow methods are used", () => {
|
||||||
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
|
|
||||||
|
new Readable().read();
|
||||||
|
new Writable().write("x");
|
||||||
|
|
||||||
|
expect(warn).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warn.mock.calls[0][0]).toContain("[shim:stream]");
|
||||||
|
|
||||||
|
warn.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export const processShim = {
|
export const processShim = {
|
||||||
platform: "linux",
|
platform: "linux",
|
||||||
|
version: "v18.18.0",
|
||||||
versions: {
|
versions: {
|
||||||
electron: "28.2.3",
|
electron: "28.2.3",
|
||||||
node: "18.18.0",
|
node: "18.18.0",
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import * as netShim from "./node/net.js";
|
|||||||
import * as httpShim from "./node/http.js";
|
import * as httpShim from "./node/http.js";
|
||||||
import * as zlibShim from "./node/zlib.js";
|
import * as zlibShim from "./node/zlib.js";
|
||||||
import * as utilShim from "./node/util.js";
|
import * as utilShim from "./node/util.js";
|
||||||
|
import { constantsShim } from "./node/constants.js";
|
||||||
|
import { assertShim } from "./node/assert.js";
|
||||||
|
import * as streamShim from "./node/stream.js";
|
||||||
import { wrapWithProxy, installDebugHelpers } from "./debug.js";
|
import { wrapWithProxy, installDebugHelpers } from "./debug.js";
|
||||||
|
|
||||||
const rawRegistry = {
|
const rawRegistry = {
|
||||||
@@ -29,6 +32,9 @@ const rawRegistry = {
|
|||||||
https: httpShim,
|
https: httpShim,
|
||||||
zlib: zlibShim,
|
zlib: zlibShim,
|
||||||
util: utilShim,
|
util: utilShim,
|
||||||
|
constants: constantsShim,
|
||||||
|
assert: assertShim,
|
||||||
|
stream: streamShim,
|
||||||
};
|
};
|
||||||
|
|
||||||
const shimRegistry = {};
|
const shimRegistry = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user