larry babby and threejs for glsl

This commit is contained in:
Sam
2024-06-24 21:24:00 +12:00
parent 87d5dc634d
commit 907ebae4c0
6474 changed files with 1279596 additions and 8 deletions

21
webGl/my-threejs-test/node_modules/@parcel/fs/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-present Devon Govett
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,16 @@
import type {FileSystem} from './lib/types';
import type WorkerFarm from '@parcel/workers';
export * from './lib/types';
export const NodeFS: {
new (): FileSystem;
};
export const MemoryFS: {
new (farm: WorkerFarm): FileSystem;
};
export const OverlayFS: {
new (writable: FileSystem, readable: FileSystem): FileSystem;
};

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,91 @@
import type { FilePath } from "@parcel/types";
import type { Readable, Writable } from "stream";
import type { Event, Options as WatcherOptions, AsyncSubscription } from "@parcel/watcher";
export type FileOptions = {
mode?: number;
};
export type ReaddirOptions = {
withFileTypes?: false;
} | {
withFileTypes: true;
};
export interface Stats {
dev: number;
ino: number;
mode: number;
nlink: number;
uid: number;
gid: number;
rdev: number;
size: number;
blksize: number;
blocks: number;
atimeMs: number;
mtimeMs: number;
ctimeMs: number;
birthtimeMs: number;
atime: Date;
mtime: Date;
ctime: Date;
birthtime: Date;
isFile(): boolean;
isDirectory(): boolean;
isBlockDevice(): boolean;
isCharacterDevice(): boolean;
isSymbolicLink(): boolean;
isFIFO(): boolean;
isSocket(): boolean;
}
export type Encoding = "hex" | "utf8" | "utf-8" | "ascii" | "binary" | "base64" | "ucs2" | "ucs-2" | "utf16le" | "latin1";
export interface FileSystem {
readFile(filePath: FilePath): Promise<Buffer>;
readFile(filePath: FilePath, encoding: Encoding): Promise<string>;
readFileSync(filePath: FilePath): Buffer;
readFileSync(filePath: FilePath, encoding: Encoding): string;
writeFile(filePath: FilePath, contents: Buffer | string, options: FileOptions | null | undefined): Promise<void>;
copyFile(source: FilePath, destination: FilePath, flags?: number): Promise<void>;
stat(filePath: FilePath): Promise<Stats>;
statSync(filePath: FilePath): Stats;
readdir(path: FilePath, opts?: {
withFileTypes?: false;
}): Promise<FilePath[]>;
readdir(path: FilePath, opts: {
withFileTypes: true;
}): Promise<Dirent[]>;
readdirSync(path: FilePath, opts?: {
withFileTypes?: false;
}): FilePath[];
readdirSync(path: FilePath, opts: {
withFileTypes: true;
}): Dirent[];
symlink(target: FilePath, path: FilePath): Promise<void>;
unlink(path: FilePath): Promise<void>;
realpath(path: FilePath): Promise<FilePath>;
realpathSync(path: FilePath): FilePath;
exists(path: FilePath): Promise<boolean>;
existsSync(path: FilePath): boolean;
mkdirp(path: FilePath): Promise<void>;
rimraf(path: FilePath): Promise<void>;
ncp(source: FilePath, destination: FilePath): Promise<void>;
createReadStream(path: FilePath, options?: FileOptions | null | undefined): Readable;
createWriteStream(path: FilePath, options?: FileOptions | null | undefined): Writable;
cwd(): FilePath;
chdir(dir: FilePath): void;
watch(dir: FilePath, fn: (err: Error | null | undefined, events: Array<Event>) => unknown, opts: WatcherOptions): Promise<AsyncSubscription>;
getEventsSince(dir: FilePath, snapshot: FilePath, opts: WatcherOptions): Promise<Array<Event>>;
writeSnapshot(dir: FilePath, snapshot: FilePath, opts: WatcherOptions): Promise<void>;
findAncestorFile(fileNames: Array<string>, fromDir: FilePath, root: FilePath): FilePath | null | undefined;
findNodeModule(moduleName: string, fromDir: FilePath): FilePath | null | undefined;
findFirstFile(filePaths: Array<FilePath>): FilePath | null | undefined;
}
// https://nodejs.org/api/fs.html#fs_class_fs_dirent
export interface Dirent {
readonly name: string;
isBlockDevice(): boolean;
isCharacterDevice(): boolean;
isDirectory(): boolean;
isFIFO(): boolean;
isFile(): boolean;
isSocket(): boolean;
isSymbolicLink(): boolean;
}

View File

@@ -0,0 +1,71 @@
{
"name": "@parcel/fs",
"version": "2.12.0",
"description": "Blazing fast, zero configuration web application bundler",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"repository": {
"type": "git",
"url": "https://github.com/parcel-bundler/parcel.git"
},
"main": "lib/index.js",
"source": "src/index.js",
"types": "index.d.ts",
"engines": {
"node": ">= 12.0.0"
},
"targets": {
"types": false,
"main": {
"includeNodeModules": {
"@parcel/core": false,
"@parcel/rust": false,
"@parcel/types": false,
"@parcel/utils": false,
"@parcel/watcher": false,
"@parcel/workers": false
}
},
"browser": {
"includeNodeModules": {
"@parcel/core": false,
"@parcel/rust": false,
"@parcel/types": false,
"@parcel/utils": false,
"@parcel/watcher": false,
"@parcel/workers": false
}
}
},
"scripts": {
"build-ts": "mkdir -p lib && flow-to-ts src/types.js > lib/types.d.ts",
"check-ts": "tsc --noEmit index.d.ts"
},
"dependencies": {
"@parcel/rust": "2.12.0",
"@parcel/types": "2.12.0",
"@parcel/utils": "2.12.0",
"@parcel/watcher": "^2.0.7",
"@parcel/workers": "2.12.0"
},
"devDependencies": {
"graceful-fs": "^4.2.4",
"ncp": "^2.0.0",
"nullthrows": "^1.1.1",
"utility-types": "^3.10.0"
},
"peerDependencies": {
"@parcel/core": "^2.12.0"
},
"browser": {
"@parcel/fs": "./lib/browser.js",
"./src/NodeFS.js": "./src/NodeFS.browser.js"
},
"gitHead": "2059029ee91e5f03a273b0954d3e629d7375f986"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
// @flow
import type {FileSystem} from './types';
// $FlowFixMe[prop-missing] handled by the throwing constructor
export class NodeFS implements FileSystem {
constructor() {
throw new Error("NodeFS isn't available in the browser");
}
}

View File

@@ -0,0 +1,236 @@
// @flow
import type {ReadStream, Stats} from 'fs';
import type {Writable} from 'stream';
import type {FileOptions, FileSystem, Encoding} from './types';
import type {FilePath} from '@parcel/types';
import type {
Event,
Options as WatcherOptions,
AsyncSubscription,
} from '@parcel/watcher';
import fs from 'graceful-fs';
import nativeFS from 'fs';
import ncp from 'ncp';
import {promisify} from 'util';
import {registerSerializableClass} from '@parcel/core';
import {hashFile} from '@parcel/utils';
import watcher from '@parcel/watcher';
import packageJSON from '../package.json';
import * as searchNative from '@parcel/rust';
import * as searchJS from './find';
// Most of this can go away once we only support Node 10+, which includes
// require('fs').promises
const realpath = promisify(
process.platform === 'win32' ? fs.realpath : fs.realpath.native,
);
const isPnP = process.versions.pnp != null;
export class NodeFS implements FileSystem {
readFile: any = promisify(fs.readFile);
copyFile: any = promisify(fs.copyFile);
stat: any = promisify(fs.stat);
readdir: any = promisify(fs.readdir);
symlink: any = promisify(fs.symlink);
unlink: any = promisify(fs.unlink);
utimes: any = promisify(fs.utimes);
ncp: any = promisify(ncp);
createReadStream: (path: string, options?: any) => ReadStream =
fs.createReadStream;
cwd: () => string = () => process.cwd();
chdir: (directory: string) => void = directory => process.chdir(directory);
statSync: (path: string) => Stats = path => fs.statSync(path);
realpathSync: (path: string, cache?: any) => string =
process.platform === 'win32' ? fs.realpathSync : fs.realpathSync.native;
existsSync: (path: string) => boolean = fs.existsSync;
readdirSync: any = (fs.readdirSync: any);
findAncestorFile: any = isPnP
? (...args) => searchJS.findAncestorFile(this, ...args)
: searchNative.findAncestorFile;
findNodeModule: any = isPnP
? (...args) => searchJS.findNodeModule(this, ...args)
: searchNative.findNodeModule;
findFirstFile: any = isPnP
? (...args) => searchJS.findFirstFile(this, ...args)
: searchNative.findFirstFile;
createWriteStream(filePath: string, options: any): Writable {
// Make createWriteStream atomic
let tmpFilePath = getTempFilePath(filePath);
let failed = false;
const move = async () => {
if (!failed) {
try {
await fs.promises.rename(tmpFilePath, filePath);
} catch (e) {
// This is adapted from fs-write-stream-atomic. Apparently
// Windows doesn't like renaming when the target already exists.
if (
process.platform === 'win32' &&
e.syscall &&
e.syscall === 'rename' &&
e.code &&
e.code === 'EPERM'
) {
let [hashTmp, hashTarget] = await Promise.all([
hashFile(this, tmpFilePath),
hashFile(this, filePath),
]);
await this.unlink(tmpFilePath);
if (hashTmp != hashTarget) {
throw e;
}
}
}
}
};
let writeStream = fs.createWriteStream(tmpFilePath, {
...options,
fs: {
...fs,
close: (fd, cb) => {
fs.close(fd, err => {
if (err) {
cb(err);
} else {
move().then(
() => cb(),
err => cb(err),
);
}
});
},
},
});
writeStream.once('error', () => {
failed = true;
fs.unlinkSync(tmpFilePath);
});
return writeStream;
}
async writeFile(
filePath: FilePath,
contents: Buffer | string,
options: ?FileOptions,
): Promise<void> {
let tmpFilePath = getTempFilePath(filePath);
await fs.promises.writeFile(tmpFilePath, contents, options);
await fs.promises.rename(tmpFilePath, filePath);
}
readFileSync(filePath: FilePath, encoding?: Encoding): any {
if (encoding != null) {
return fs.readFileSync(filePath, encoding);
}
return fs.readFileSync(filePath);
}
async realpath(originalPath: string): Promise<string> {
try {
return await realpath(originalPath, 'utf8');
} catch (e) {
// do nothing
}
return originalPath;
}
exists(filePath: FilePath): Promise<boolean> {
return new Promise(resolve => {
fs.exists(filePath, resolve);
});
}
watch(
dir: FilePath,
fn: (err: ?Error, events: Array<Event>) => mixed,
opts: WatcherOptions,
): Promise<AsyncSubscription> {
return watcher.subscribe(dir, fn, opts);
}
getEventsSince(
dir: FilePath,
snapshot: FilePath,
opts: WatcherOptions,
): Promise<Array<Event>> {
return watcher.getEventsSince(dir, snapshot, opts);
}
async writeSnapshot(
dir: FilePath,
snapshot: FilePath,
opts: WatcherOptions,
): Promise<void> {
await watcher.writeSnapshot(dir, snapshot, opts);
}
static deserialize(): NodeFS {
return new NodeFS();
}
serialize(): null {
return null;
}
async mkdirp(filePath: FilePath): Promise<void> {
await nativeFS.promises.mkdir(filePath, {recursive: true});
}
async rimraf(filePath: FilePath): Promise<void> {
if (fs.promises.rm) {
await fs.promises.rm(filePath, {recursive: true, force: true});
return;
}
// fs.promises.rm is not supported in node 12...
let stat;
try {
stat = await this.stat(filePath);
} catch (err) {
return;
}
if (stat.isDirectory()) {
// $FlowFixMe
await nativeFS.promises.rmdir(filePath, {recursive: true});
} else {
await nativeFS.promises.unlink(filePath);
}
}
}
registerSerializableClass(`${packageJSON.version}:NodeFS`, NodeFS);
let writeStreamCalls = 0;
let threadId;
try {
({threadId} = require('worker_threads'));
} catch {
//
}
// Generate a temporary file path used for atomic writing of files.
function getTempFilePath(filePath: FilePath) {
writeStreamCalls = writeStreamCalls % Number.MAX_SAFE_INTEGER;
return (
filePath +
'.' +
process.pid +
(threadId != null ? '.' + threadId : '') +
'.' +
(writeStreamCalls++).toString(36)
);
}

View File

@@ -0,0 +1,439 @@
// @flow
import type {Readable, Writable} from 'stream';
import type {
Encoding,
FileOptions,
FileSystem,
ReaddirOptions,
Stats,
} from './types';
import type {FilePath} from '@parcel/types';
import type {
Event,
Options as WatcherOptions,
AsyncSubscription,
} from '@parcel/watcher';
import {registerSerializableClass} from '@parcel/core';
import WorkerFarm from '@parcel/workers';
import packageJSON from '../package.json';
import {findAncestorFile, findNodeModule, findFirstFile} from './find';
import {MemoryFS} from './MemoryFS';
import nullthrows from 'nullthrows';
import path from 'path';
export class OverlayFS implements FileSystem {
deleted: Set<FilePath> = new Set();
writable: FileSystem;
readable: FileSystem;
_cwd: FilePath;
constructor(workerFarmOrFS: WorkerFarm | FileSystem, readable: FileSystem) {
if (workerFarmOrFS instanceof WorkerFarm) {
this.writable = new MemoryFS(workerFarmOrFS);
} else {
this.writable = workerFarmOrFS;
}
this.readable = readable;
this._cwd = readable.cwd();
}
static deserialize(opts: any): OverlayFS {
let fs = new OverlayFS(opts.writable, opts.readable);
if (opts.deleted != null) fs.deleted = opts.deleted;
return fs;
}
serialize(): {|
$$raw: boolean,
readable: FileSystem,
writable: FileSystem,
deleted: Set<FilePath>,
|} {
return {
$$raw: false,
writable: this.writable,
readable: this.readable,
deleted: this.deleted,
};
}
_deletedThrows(filePath: FilePath): FilePath {
filePath = this._normalizePath(filePath);
if (this.deleted.has(filePath)) {
throw new FSError('ENOENT', filePath, 'does not exist');
}
return filePath;
}
_checkExists(filePath: FilePath): FilePath {
filePath = this._deletedThrows(filePath);
if (!this.existsSync(filePath)) {
throw new FSError('ENOENT', filePath, 'does not exist');
}
return filePath;
}
_isSymlink(filePath: FilePath): boolean {
filePath = this._normalizePath(filePath);
// Check the parts of the path to see if any are symlinks.
let {root, dir, base} = path.parse(filePath);
let segments = dir.slice(root.length).split(path.sep).concat(base);
while (segments.length) {
filePath = path.join(root, ...segments);
let name = segments.pop();
if (this.deleted.has(filePath)) {
return false;
} else if (
this.writable instanceof MemoryFS &&
this.writable.symlinks.has(filePath)
) {
return true;
} else {
// HACK: Parcel fs does not provide `lstatSync`,
// so we use `readdirSync` to check if the path is a symlink.
let parent = path.resolve(filePath, '..');
if (parent === filePath) {
return false;
}
try {
for (let dirent of this.readdirSync(parent, {withFileTypes: true})) {
if (typeof dirent === 'string') {
break; // {withFileTypes: true} not supported
} else if (dirent.name === name) {
if (dirent.isSymbolicLink()) {
return true;
}
}
}
} catch (e) {
if (e.code === 'ENOENT') {
return false;
}
throw e;
}
}
}
return false;
}
async _copyPathForWrite(filePath: FilePath): Promise<FilePath> {
filePath = await this._normalizePath(filePath);
let dirPath = path.dirname(filePath);
if (this.existsSync(dirPath) && !this.writable.existsSync(dirPath)) {
await this.writable.mkdirp(dirPath);
}
return filePath;
}
_normalizePath(filePath: FilePath): FilePath {
return path.resolve(this.cwd(), filePath);
}
// eslint-disable-next-line require-await
async readFile(filePath: FilePath, encoding?: Encoding): Promise<any> {
return this.readFileSync(filePath, encoding);
}
async writeFile(
filePath: FilePath,
contents: string | Buffer,
options: ?FileOptions,
): Promise<void> {
filePath = await this._copyPathForWrite(filePath);
await this.writable.writeFile(filePath, contents, options);
this.deleted.delete(filePath);
}
async copyFile(source: FilePath, destination: FilePath): Promise<void> {
source = this._normalizePath(source);
destination = await this._copyPathForWrite(destination);
if (await this.writable.exists(source)) {
await this.writable.writeFile(
destination,
await this.writable.readFile(source),
);
} else {
await this.writable.writeFile(
destination,
await this.readable.readFile(source),
);
}
this.deleted.delete(destination);
}
// eslint-disable-next-line require-await
async stat(filePath: FilePath): Promise<Stats> {
return this.statSync(filePath);
}
async symlink(target: FilePath, filePath: FilePath): Promise<void> {
target = this._normalizePath(target);
filePath = this._normalizePath(filePath);
await this.writable.symlink(target, filePath);
this.deleted.delete(filePath);
}
async unlink(filePath: FilePath): Promise<void> {
filePath = this._normalizePath(filePath);
let toDelete = [filePath];
if (this.writable instanceof MemoryFS && this._isSymlink(filePath)) {
this.writable.symlinks.delete(filePath);
} else if (this.statSync(filePath).isDirectory()) {
let stack = [filePath];
// Recursively add every descendant path to deleted.
while (stack.length) {
let root = nullthrows(stack.pop());
for (let ent of this.readdirSync(root, {withFileTypes: true})) {
if (typeof ent === 'string') {
let childPath = path.join(root, ent);
toDelete.push(childPath);
if (this.statSync(childPath).isDirectory()) {
stack.push(childPath);
}
} else {
let childPath = path.join(root, ent.name);
toDelete.push(childPath);
if (ent.isDirectory()) {
stack.push(childPath);
}
}
}
}
}
try {
await this.writable.unlink(filePath);
} catch (e) {
if (e.code === 'ENOENT' && !this.readable.existsSync(filePath)) {
throw e;
}
}
for (let pathToDelete of toDelete) {
this.deleted.add(pathToDelete);
}
}
async mkdirp(dir: FilePath): Promise<void> {
dir = this._normalizePath(dir);
await this.writable.mkdirp(dir);
if (this.deleted != null) {
let root = path.parse(dir).root;
while (dir !== root) {
this.deleted.delete(dir);
dir = path.dirname(dir);
}
}
}
async rimraf(filePath: FilePath): Promise<void> {
try {
await this.unlink(filePath);
} catch (e) {
// noop
}
}
// eslint-disable-next-line require-await
async ncp(source: FilePath, destination: FilePath): Promise<void> {
// TODO: Implement this correctly.
return this.writable.ncp(source, destination);
}
createReadStream(filePath: FilePath, opts?: ?FileOptions): Readable {
filePath = this._deletedThrows(filePath);
if (this.writable.existsSync(filePath)) {
return this.writable.createReadStream(filePath, opts);
}
return this.readable.createReadStream(filePath, opts);
}
createWriteStream(path: FilePath, opts?: ?FileOptions): Writable {
path = this._normalizePath(path);
this.deleted.delete(path);
return this.writable.createWriteStream(path, opts);
}
cwd(): FilePath {
return this._cwd;
}
chdir(path: FilePath): void {
this._cwd = this._checkExists(path);
}
// eslint-disable-next-line require-await
async realpath(filePath: FilePath): Promise<FilePath> {
return this.realpathSync(filePath);
}
readFileSync(filePath: FilePath, encoding?: Encoding): any {
filePath = this.realpathSync(filePath);
try {
// $FlowFixMe[incompatible-call]
return this.writable.readFileSync(filePath, encoding);
} catch (err) {
// $FlowFixMe[incompatible-call]
return this.readable.readFileSync(filePath, encoding);
}
}
statSync(filePath: FilePath): Stats {
filePath = this._normalizePath(filePath);
try {
return this.writable.statSync(filePath);
} catch (e) {
if (e.code === 'ENOENT' && this.existsSync(filePath)) {
return this.readable.statSync(filePath);
}
throw e;
}
}
realpathSync(filePath: FilePath): FilePath {
filePath = this._deletedThrows(filePath);
filePath = this._deletedThrows(this.writable.realpathSync(filePath));
if (!this.writable.existsSync(filePath)) {
return this.readable.realpathSync(filePath);
}
return filePath;
}
// eslint-disable-next-line require-await
async exists(filePath: FilePath): Promise<boolean> {
return this.existsSync(filePath);
}
existsSync(filePath: FilePath): boolean {
filePath = this._normalizePath(filePath);
if (this.deleted.has(filePath)) return false;
try {
filePath = this.realpathSync(filePath);
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}
if (this.deleted.has(filePath)) return false;
return (
this.writable.existsSync(filePath) || this.readable.existsSync(filePath)
);
}
// eslint-disable-next-line require-await
async readdir(path: FilePath, opts?: ReaddirOptions): Promise<any> {
return this.readdirSync(path, opts);
}
readdirSync(dir: FilePath, opts?: ReaddirOptions): any {
dir = this.realpathSync(dir);
// Read from both filesystems and merge the results
let entries = new Map();
try {
for (let entry: any of this.writable.readdirSync(dir, opts)) {
let filePath = path.join(dir, entry.name ?? entry);
if (this.deleted.has(filePath)) continue;
entries.set(filePath, entry);
}
} catch {
// noop
}
try {
for (let entry: any of this.readable.readdirSync(dir, opts)) {
let filePath = path.join(dir, entry.name ?? entry);
if (this.deleted.has(filePath)) continue;
if (entries.has(filePath)) continue;
entries.set(filePath, entry);
}
} catch {
// noop
}
return Array.from(entries.values());
}
async watch(
dir: FilePath,
fn: (err: ?Error, events: Array<Event>) => mixed,
opts: WatcherOptions,
): Promise<AsyncSubscription> {
let writableSubscription = await this.writable.watch(dir, fn, opts);
let readableSubscription = await this.readable.watch(dir, fn, opts);
return {
unsubscribe: async () => {
await writableSubscription.unsubscribe();
await readableSubscription.unsubscribe();
},
};
}
async getEventsSince(
dir: FilePath,
snapshot: FilePath,
opts: WatcherOptions,
): Promise<Array<Event>> {
let writableEvents = await this.writable.getEventsSince(
dir,
snapshot,
opts,
);
let readableEvents = await this.readable.getEventsSince(
dir,
snapshot,
opts,
);
return [...writableEvents, ...readableEvents];
}
async writeSnapshot(
dir: FilePath,
snapshot: FilePath,
opts: WatcherOptions,
): Promise<void> {
await this.writable.writeSnapshot(dir, snapshot, opts);
}
findAncestorFile(
fileNames: Array<string>,
fromDir: FilePath,
root: FilePath,
): ?FilePath {
return findAncestorFile(this, fileNames, fromDir, root);
}
findNodeModule(moduleName: string, fromDir: FilePath): ?FilePath {
return findNodeModule(this, moduleName, fromDir);
}
findFirstFile(filePaths: Array<FilePath>): ?FilePath {
return findFirstFile(this, filePaths);
}
}
class FSError extends Error {
code: string;
path: FilePath;
constructor(code: string, path: FilePath, message: string) {
super(`${code}: ${path} ${message}`);
this.name = 'FSError';
this.code = code;
this.path = path;
Error.captureStackTrace?.(this, this.constructor);
}
}
registerSerializableClass(`${packageJSON.version}:OverlayFS`, OverlayFS);

View File

@@ -0,0 +1,82 @@
// @flow
import type {FilePath} from '@parcel/types';
import type {FileSystem} from './types';
import path from 'path';
export function findNodeModule(
fs: FileSystem,
moduleName: string,
dir: FilePath,
): ?FilePath {
let {root} = path.parse(dir);
while (dir !== root) {
// Skip node_modules directories
if (path.basename(dir) === 'node_modules') {
dir = path.dirname(dir);
}
try {
let moduleDir = path.join(dir, 'node_modules', moduleName);
let stats = fs.statSync(moduleDir);
if (stats.isDirectory()) {
return moduleDir;
}
} catch (err) {
// ignore
}
// Move up a directory
dir = path.dirname(dir);
}
return null;
}
export function findAncestorFile(
fs: FileSystem,
fileNames: Array<string>,
dir: FilePath,
root: FilePath,
): ?FilePath {
let {root: pathRoot} = path.parse(dir);
// eslint-disable-next-line no-constant-condition
while (true) {
if (path.basename(dir) === 'node_modules') {
return null;
}
for (const fileName of fileNames) {
let filePath = path.join(dir, fileName);
try {
if (fs.statSync(filePath).isFile()) {
return filePath;
}
} catch (err) {
// ignore
}
}
if (dir === root || dir === pathRoot) {
break;
}
dir = path.dirname(dir);
}
return null;
}
export function findFirstFile(
fs: FileSystem,
filePaths: Array<FilePath>,
): ?FilePath {
for (let filePath of filePaths) {
try {
if (fs.statSync(filePath).isFile()) {
return filePath;
}
} catch (err) {
// ignore
}
}
}

View File

@@ -0,0 +1,41 @@
// @flow strict-local
import type {FileSystem} from './types';
import type {FilePath} from '@parcel/types';
import type {Readable, Writable} from 'stream';
import path from 'path';
import stream from 'stream';
import {promisify} from 'util';
export type * from './types';
export * from './NodeFS';
export * from './MemoryFS';
export * from './OverlayFS';
const pipeline: (Readable, Writable) => Promise<void> = promisify(
stream.pipeline,
);
// Recursively copies a directory from the sourceFS to the destinationFS
export async function ncp(
sourceFS: FileSystem,
source: FilePath,
destinationFS: FileSystem,
destination: FilePath,
) {
await destinationFS.mkdirp(destination);
let files = await sourceFS.readdir(source);
for (let file of files) {
let sourcePath = path.join(source, file);
let destPath = path.join(destination, file);
let stats = await sourceFS.stat(sourcePath);
if (stats.isFile()) {
await pipeline(
sourceFS.createReadStream(sourcePath),
destinationFS.createWriteStream(destPath),
);
} else if (stats.isDirectory()) {
await ncp(sourceFS, sourcePath, destinationFS, destPath);
}
}
}

View File

@@ -0,0 +1,127 @@
// @flow
import type {FilePath} from '@parcel/types';
import type {Readable, Writable} from 'stream';
import type {
Event,
Options as WatcherOptions,
AsyncSubscription,
} from '@parcel/watcher';
export type FileOptions = {mode?: number, ...};
export type ReaddirOptions =
| {withFileTypes?: false, ...}
| {withFileTypes: true, ...};
export interface Stats {
dev: number;
ino: number;
mode: number;
nlink: number;
uid: number;
gid: number;
rdev: number;
size: number;
blksize: number;
blocks: number;
atimeMs: number;
mtimeMs: number;
ctimeMs: number;
birthtimeMs: number;
atime: Date;
mtime: Date;
ctime: Date;
birthtime: Date;
isFile(): boolean;
isDirectory(): boolean;
isBlockDevice(): boolean;
isCharacterDevice(): boolean;
isSymbolicLink(): boolean;
isFIFO(): boolean;
isSocket(): boolean;
}
export type Encoding =
| 'hex'
| 'utf8'
| 'utf-8'
| 'ascii'
| 'binary'
| 'base64'
| 'ucs2'
| 'ucs-2'
| 'utf16le'
| 'latin1';
export interface FileSystem {
readFile(filePath: FilePath): Promise<Buffer>;
readFile(filePath: FilePath, encoding: Encoding): Promise<string>;
readFileSync(filePath: FilePath): Buffer;
readFileSync(filePath: FilePath, encoding: Encoding): string;
writeFile(
filePath: FilePath,
contents: Buffer | string,
options: ?FileOptions,
): Promise<void>;
copyFile(
source: FilePath,
destination: FilePath,
flags?: number,
): Promise<void>;
stat(filePath: FilePath): Promise<Stats>;
statSync(filePath: FilePath): Stats;
readdir(
path: FilePath,
opts?: {withFileTypes?: false, ...},
): Promise<FilePath[]>;
readdir(path: FilePath, opts: {withFileTypes: true, ...}): Promise<Dirent[]>;
readdirSync(path: FilePath, opts?: {withFileTypes?: false, ...}): FilePath[];
readdirSync(path: FilePath, opts: {withFileTypes: true, ...}): Dirent[];
symlink(target: FilePath, path: FilePath): Promise<void>;
unlink(path: FilePath): Promise<void>;
realpath(path: FilePath): Promise<FilePath>;
realpathSync(path: FilePath): FilePath;
exists(path: FilePath): Promise<boolean>;
existsSync(path: FilePath): boolean;
mkdirp(path: FilePath): Promise<void>;
rimraf(path: FilePath): Promise<void>;
ncp(source: FilePath, destination: FilePath): Promise<void>;
createReadStream(path: FilePath, options?: ?FileOptions): Readable;
createWriteStream(path: FilePath, options?: ?FileOptions): Writable;
cwd(): FilePath;
chdir(dir: FilePath): void;
watch(
dir: FilePath,
fn: (err: ?Error, events: Array<Event>) => mixed,
opts: WatcherOptions,
): Promise<AsyncSubscription>;
getEventsSince(
dir: FilePath,
snapshot: FilePath,
opts: WatcherOptions,
): Promise<Array<Event>>;
writeSnapshot(
dir: FilePath,
snapshot: FilePath,
opts: WatcherOptions,
): Promise<void>;
findAncestorFile(
fileNames: Array<string>,
fromDir: FilePath,
root: FilePath,
): ?FilePath;
findNodeModule(moduleName: string, fromDir: FilePath): ?FilePath;
findFirstFile(filePaths: Array<FilePath>): ?FilePath;
}
// https://nodejs.org/api/fs.html#fs_class_fs_dirent
export interface Dirent {
+name: string;
isBlockDevice(): boolean;
isCharacterDevice(): boolean;
isDirectory(): boolean;
isFIFO(): boolean;
isFile(): boolean;
isSocket(): boolean;
isSymbolicLink(): boolean;
}

View File

@@ -0,0 +1,232 @@
// @flow
import {OverlayFS} from '../src/OverlayFS';
import {fsFixture} from '@parcel/test-utils/src/fsFixture';
import {MemoryFS} from '../src/MemoryFS';
import WorkerFarm from '@parcel/workers';
import assert from 'assert';
import path from 'path';
describe('OverlayFS', () => {
let underlayFS;
let fs;
let workerFarm;
beforeEach(() => {
workerFarm = new WorkerFarm({
workerPath: require.resolve('@parcel/core/src/worker.js'),
});
underlayFS = new MemoryFS(workerFarm);
fs = new OverlayFS(workerFarm, underlayFS);
});
afterEach(async () => {
await workerFarm.end();
});
it('copies on write', async () => {
await fsFixture(underlayFS)`
foo: foo
`;
assert.equal(fs.readFileSync('foo', 'utf8'), 'foo');
await fs.writeFile('foo', 'bar');
assert.equal(fs.readFileSync('foo', 'utf8'), 'bar');
assert.equal(underlayFS.readFileSync('foo', 'utf8'), 'foo');
});
it('copies on write with dir', async () => {
await fsFixture(underlayFS)`
foo/foo: foo
`;
assert.equal(fs.readFileSync('foo/foo', 'utf8'), 'foo');
await fs.writeFile('foo/bar', 'bar');
assert.equal(fs.readFileSync('foo/bar', 'utf8'), 'bar');
assert(!underlayFS.existsSync('foo/bar'));
});
it('copies on write when copying', async () => {
await fsFixture(underlayFS)`
foo: foo
`;
assert.equal(fs.readFileSync('foo', 'utf8'), 'foo');
await fs.copyFile('foo', 'bar');
assert.equal(fs.readFileSync('bar', 'utf8'), 'foo');
assert(!underlayFS.existsSync('bar'));
});
it('copies on write when copying with dir', async () => {
await fsFixture(underlayFS)`
foo/foo: foo
bar
`;
assert.equal(fs.readFileSync('foo/foo', 'utf8'), 'foo');
await fs.copyFile('foo/foo', 'bar/bar');
assert.equal(fs.readFileSync('bar/bar', 'utf8'), 'foo');
assert(!underlayFS.existsSync('bar/bar'));
});
it('writes to memory', async () => {
await fs.writeFile('foo', 'foo');
assert.equal(fs.readFileSync('foo', 'utf8'), 'foo');
assert(!underlayFS.existsSync('foo'));
});
it('symlinks in memory', async () => {
await fsFixture(underlayFS)`
foo: foo
`;
assert(fs.existsSync('foo'));
await fs.symlink('foo', 'bar');
assert.equal(fs.readFileSync('bar', 'utf8'), 'foo');
assert.equal(underlayFS.readFileSync('foo', 'utf8'), 'foo');
assert.equal(fs.realpathSync('bar'), path.resolve('/foo'));
assert(!underlayFS.existsSync('bar'));
});
it('tracks deletes', async () => {
await fsFixture(underlayFS)`
foo: bar
baz -> foo`;
assert(fs.existsSync('foo'));
assert.equal(fs.realpathSync('baz'), path.resolve('/foo'));
assert(fs._isSymlink('baz'));
await fs.rimraf('foo');
assert(!fs.existsSync('foo'));
assert(fs._isSymlink('baz'));
assert(!fs.existsSync('baz'));
assert(underlayFS.existsSync('foo'));
assert(underlayFS.existsSync('baz'));
});
it('tracks unlinks', async () => {
await fsFixture(underlayFS)`
foo: bar
baz -> foo`;
assert(fs.existsSync('baz'));
assert.equal(fs.realpathSync('baz'), path.resolve('/foo'));
assert(fs._isSymlink('baz'));
await fs.unlink('baz');
assert(!fs._isSymlink('baz'));
assert(!fs.existsSync('baz'));
assert(fs.existsSync('foo'));
assert(underlayFS.existsSync('foo'));
assert(underlayFS.existsSync('baz'));
assert.equal(underlayFS.realpathSync('baz'), path.resolve('/foo'));
});
it('tracks nested deletes', async () => {
await fsFixture(underlayFS)`
foo/bar: baz
foo/bat/baz: qux
`;
assert(fs.existsSync('foo/bar'));
assert(fs.existsSync('foo/bat/baz'));
await fs.rimraf('foo');
assert(!fs.existsSync('foo/bar'));
assert(!fs.existsSync('foo/bat/baz'));
assert(underlayFS.existsSync('foo/bar'));
assert(underlayFS.existsSync('foo/bat/baz'));
await fs.mkdirp('foo');
assert(fs.existsSync('foo'));
assert(!fs.existsSync('foo/bar'));
assert(!fs.existsSync('foo/baz/bat'));
await fs.mkdirp('foo/baz');
assert(fs.existsSync('foo/baz'));
assert(!fs.existsSync('foo/baz/bat'));
});
it('supports changing to a dir that is only on the readable fs', async () => {
await fsFixture(underlayFS)`
foo/bar: baz
`;
assert.equal(fs.cwd(), path.resolve('/'));
fs.chdir('/foo');
assert.equal(fs.cwd(), path.resolve('/foo'));
});
it('supports changing to a dir that is only on the writable fs', async () => {
await fsFixture(underlayFS)`
foo/bar: bar
`;
await fs.mkdirp('/bar');
assert(!underlayFS.existsSync('/bar'));
assert.equal(fs.cwd(), path.resolve('/'));
fs.chdir('/bar');
assert.equal(fs.cwd(), path.resolve('/bar'));
});
it('supports changing dir relative to cwd', async () => {
await fsFixture(underlayFS)`
foo/bar: bar
`;
assert.equal(fs.cwd(), path.resolve('/'));
fs.chdir('foo');
assert.equal(fs.cwd(), path.resolve('/foo'));
});
it('changes dir without changing underlying fs dir', async () => {
await fsFixture(underlayFS)`
foo/bar: baz
foo/bat/baz: qux
`;
assert.equal(fs.cwd(), path.resolve('/'));
assert.equal(underlayFS.cwd(), path.resolve('/'));
fs.chdir('foo');
assert.equal(fs.cwd(), path.resolve('/foo'));
assert.equal(underlayFS.cwd(), path.resolve('/'));
});
it('errors when changing to a dir that does not exist on either fs', async () => {
await fsFixture(underlayFS)`
foo/bar: bar
`;
assert.throws(() => fs.chdir('/bar'), /ENOENT/);
});
it('errors when changing to a deleted dir', async () => {
await fsFixture(underlayFS)`
foo/bar: bar
`;
await fs.rimraf('foo');
assert(!fs.existsSync('foo'));
assert(underlayFS.existsSync('foo'));
assert.throws(() => fs.chdir('/foo'), /ENOENT/);
});
});