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 { createWatcherClient } from "./watcher-client.js";
|
||||
import { createFdOps } from "./fd.js";
|
||||
import { createFsCallbacks } from "./callback.js";
|
||||
import { realpath, realpathSync } from "./realpath.js";
|
||||
import { constants } from "./constants.js";
|
||||
import { registerReadTransform, removeReadTransform, resolvePath } from "./transforms.js";
|
||||
import { wsClient } from "../ws-client.js";
|
||||
@@ -18,10 +20,13 @@ const fsSync = createFsSync(metadataCache, contentCache, transport);
|
||||
const fsWatch = createFsWatch(transport);
|
||||
const watcherClient = createWatcherClient(metadataCache, contentCache, fsWatch, wsClient);
|
||||
const fdOps = createFdOps(metadataCache, contentCache, transport);
|
||||
const fsCallbacks = createFsCallbacks(fsPromises);
|
||||
|
||||
export const fsShim = {
|
||||
promises: fsPromises,
|
||||
|
||||
...fsCallbacks,
|
||||
|
||||
existsSync: fsSync.existsSync,
|
||||
readFileSync: fsSync.readFileSync,
|
||||
writeFileSync: fsSync.writeFileSync,
|
||||
@@ -29,6 +34,18 @@ export const fsShim = {
|
||||
accessSync: fsSync.accessSync,
|
||||
statSync: fsSync.statSync,
|
||||
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,
|
||||
openSync: fdOps.openSync,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { markLocalOp } from "./echo-guard.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";
|
||||
|
||||
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) {
|
||||
const hasInCache = isInputCachePath(path) && inputCacheGet(path) !== null;
|
||||
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);
|
||||
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() {
|
||||
installGlobalAlias();
|
||||
installProcess();
|
||||
installBuffer();
|
||||
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 = {
|
||||
platform: "linux",
|
||||
version: "v18.18.0",
|
||||
versions: {
|
||||
electron: "28.2.3",
|
||||
node: "18.18.0",
|
||||
|
||||
@@ -11,6 +11,9 @@ import * as netShim from "./node/net.js";
|
||||
import * as httpShim from "./node/http.js";
|
||||
import * as zlibShim from "./node/zlib.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";
|
||||
|
||||
const rawRegistry = {
|
||||
@@ -29,6 +32,9 @@ const rawRegistry = {
|
||||
https: httpShim,
|
||||
zlib: zlibShim,
|
||||
util: utilShim,
|
||||
constants: constantsShim,
|
||||
assert: assertShim,
|
||||
stream: streamShim,
|
||||
};
|
||||
|
||||
const shimRegistry = {};
|
||||
|
||||
Reference in New Issue
Block a user