chore: add mobile app

This commit is contained in:
2026-02-07 22:33:02 +01:00
parent db34640acc
commit e89384ad4e
29327 changed files with 3886944 additions and 12 deletions
@@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.AbstractWatcher = void 0;
var _common = require("./common");
var _events = _interopRequireDefault(require("events"));
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
class AbstractWatcher {
#emitter = new _events.default();
constructor(dir, { ignored, globs, dot }) {
this.dot = dot || false;
this.ignored = ignored;
this.globs = globs;
this.doIgnore = ignored
? (filePath) => (0, _common.posixPathMatchesPattern)(ignored, filePath)
: () => false;
this.root = path.resolve(dir);
}
onFileEvent(listener) {
this.#emitter.on("fileevent", listener);
return () => {
this.#emitter.removeListener("fileevent", listener);
};
}
onError(listener) {
this.#emitter.on("error", listener);
return () => {
this.#emitter.removeListener("error", listener);
};
}
async startWatching() {}
async stopWatching() {
this.#emitter.removeAllListeners();
}
emitFileEvent(event) {
this.#emitter.emit("fileevent", {
...event,
root: this.root,
});
}
emitError(error) {
this.#emitter.emit("error", error);
}
getPauseReason() {
return null;
}
}
exports.AbstractWatcher = AbstractWatcher;
@@ -0,0 +1,84 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import type {
WatcherBackend,
WatcherBackendChangeEvent,
WatcherBackendOptions,
} from '../flow-types';
import {posixPathMatchesPattern} from './common';
import EventEmitter from 'events';
import * as path from 'path';
export type Listeners = $ReadOnly<{
onFileEvent: (event: WatcherBackendChangeEvent) => void,
onError: (error: Error) => void,
}>;
export class AbstractWatcher implements WatcherBackend {
+root: string;
+ignored: ?RegExp;
+globs: $ReadOnlyArray<string>;
+dot: boolean;
+doIgnore: (path: string) => boolean;
#emitter: EventEmitter = new EventEmitter();
constructor(dir: string, {ignored, globs, dot}: WatcherBackendOptions) {
this.dot = dot || false;
this.ignored = ignored;
this.globs = globs;
this.doIgnore = ignored
? (filePath: string) => posixPathMatchesPattern(ignored, filePath)
: () => false;
this.root = path.resolve(dir);
}
onFileEvent(
listener: (event: WatcherBackendChangeEvent) => void,
): () => void {
this.#emitter.on('fileevent', listener);
return () => {
this.#emitter.removeListener('fileevent', listener);
};
}
onError(listener: (error: Error) => void): () => void {
this.#emitter.on('error', listener);
return () => {
this.#emitter.removeListener('error', listener);
};
}
async startWatching(): Promise<void> {
// Must be implemented by subclasses
}
async stopWatching() {
this.#emitter.removeAllListeners();
}
emitFileEvent(event: Omit<WatcherBackendChangeEvent, 'root'>) {
this.#emitter.emit('fileevent', {
...event,
root: this.root,
});
}
emitError(error: Error) {
this.#emitter.emit('error', error);
}
getPauseReason(): ?string {
return null;
}
}
@@ -0,0 +1,359 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _AbstractWatcher = require("./AbstractWatcher");
var common = _interopRequireWildcard(require("./common"));
var _fs = _interopRequireDefault(require("fs"));
var _os = _interopRequireDefault(require("os"));
var _path = _interopRequireDefault(require("path"));
var _walker = _interopRequireDefault(require("walker"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
const platform = _os.default.platform();
const fsPromises = _fs.default.promises;
const TOUCH_EVENT = common.TOUCH_EVENT;
const DELETE_EVENT = common.DELETE_EVENT;
const DEBOUNCE_MS = 100;
class FallbackWatcher extends _AbstractWatcher.AbstractWatcher {
_changeTimers = new Map();
_dirRegistry = Object.create(null);
watched = Object.create(null);
async startWatching() {
this._watchdir(this.root);
await new Promise((resolve) => {
recReaddir(
this.root,
(dir) => {
this._watchdir(dir);
},
(filename) => {
this._register(filename, "f");
},
(symlink) => {
this._register(symlink, "l");
},
() => {
resolve();
},
this._checkedEmitError,
this.ignored,
);
});
}
_register(filepath, type) {
const dir = _path.default.dirname(filepath);
const filename = _path.default.basename(filepath);
if (this._dirRegistry[dir] && this._dirRegistry[dir][filename]) {
return false;
}
const relativePath = _path.default.relative(this.root, filepath);
if (
this.doIgnore(relativePath) ||
(type === "f" &&
!common.includedByGlob("f", this.globs, this.dot, relativePath))
) {
return false;
}
if (!this._dirRegistry[dir]) {
this._dirRegistry[dir] = Object.create(null);
}
this._dirRegistry[dir][filename] = true;
return true;
}
_unregister(filepath) {
const dir = _path.default.dirname(filepath);
if (this._dirRegistry[dir]) {
const filename = _path.default.basename(filepath);
delete this._dirRegistry[dir][filename];
}
}
_unregisterDir(dirpath) {
if (this._dirRegistry[dirpath]) {
delete this._dirRegistry[dirpath];
}
}
_registered(fullpath) {
const dir = _path.default.dirname(fullpath);
return !!(
this._dirRegistry[fullpath] ||
(this._dirRegistry[dir] &&
this._dirRegistry[dir][_path.default.basename(fullpath)])
);
}
_checkedEmitError = (error) => {
if (!isIgnorableFileError(error)) {
this.emitError(error);
}
};
_watchdir = (dir) => {
if (this.watched[dir]) {
return false;
}
const watcher = _fs.default.watch(
dir,
{
persistent: true,
},
(event, filename) => this._normalizeChange(dir, event, filename),
);
this.watched[dir] = watcher;
watcher.on("error", this._checkedEmitError);
if (this.root !== dir) {
this._register(dir, "d");
}
return true;
};
async _stopWatching(dir) {
if (this.watched[dir]) {
await new Promise((resolve) => {
this.watched[dir].once("close", () => process.nextTick(resolve));
this.watched[dir].close();
delete this.watched[dir];
});
}
}
async stopWatching() {
await super.stopWatching();
const promises = Object.keys(this.watched).map((dir) =>
this._stopWatching(dir),
);
await Promise.all(promises);
}
_detectChangedFile(dir, event, callback) {
if (!this._dirRegistry[dir]) {
return;
}
let found = false;
let closest = null;
let c = 0;
Object.keys(this._dirRegistry[dir]).forEach((file, i, arr) => {
_fs.default.lstat(_path.default.join(dir, file), (error, stat) => {
if (found) {
return;
}
if (error) {
if (isIgnorableFileError(error)) {
found = true;
callback(file);
} else {
this.emitError(error);
}
} else {
if (closest == null || stat.mtime > closest.mtime) {
closest = {
file,
mtime: stat.mtime,
};
}
if (arr.length === ++c) {
callback(closest.file);
}
}
});
});
}
_normalizeChange(dir, event, file) {
if (!file) {
this._detectChangedFile(dir, event, (actualFile) => {
if (actualFile) {
this._processChange(dir, event, actualFile).catch((error) =>
this.emitError(error),
);
}
});
} else {
this._processChange(dir, event, _path.default.normalize(file)).catch(
(error) => this.emitError(error),
);
}
}
async _processChange(dir, event, file) {
const fullPath = _path.default.join(dir, file);
const relativePath = _path.default.join(
_path.default.relative(this.root, dir),
file,
);
const registered = this._registered(fullPath);
try {
const stat = await fsPromises.lstat(fullPath);
if (stat.isDirectory()) {
if (event === "change") {
return;
}
if (
this.doIgnore(relativePath) ||
!common.includedByGlob("d", this.globs, this.dot, relativePath)
) {
return;
}
recReaddir(
_path.default.resolve(this.root, relativePath),
(dir, stats) => {
if (this._watchdir(dir)) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath: _path.default.relative(this.root, dir),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: "d",
},
});
}
},
(file, stats) => {
if (this._register(file, "f")) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath: _path.default.relative(this.root, file),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: "f",
},
});
}
},
(symlink, stats) => {
if (this._register(symlink, "l")) {
this.emitFileEvent({
event: TOUCH_EVENT,
relativePath: _path.default.relative(this.root, symlink),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: "l",
},
});
}
},
function endCallback() {},
this._checkedEmitError,
this.ignored,
);
} else {
const type = common.typeFromStat(stat);
if (type == null) {
return;
}
const metadata = {
modifiedTime: stat.mtime.getTime(),
size: stat.size,
type,
};
if (registered) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath,
metadata,
});
} else {
if (this._register(fullPath, type)) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath,
metadata,
});
}
}
}
} catch (error) {
if (!isIgnorableFileError(error)) {
this.emitError(error);
return;
}
this._unregister(fullPath);
this._unregisterDir(fullPath);
if (registered) {
this._emitEvent({
event: DELETE_EVENT,
relativePath,
});
}
await this._stopWatching(fullPath);
}
}
_emitEvent(change) {
const { event, relativePath } = change;
const key = event + "-" + relativePath;
const existingTimer = this._changeTimers.get(key);
if (existingTimer) {
clearTimeout(existingTimer);
}
this._changeTimers.set(
key,
setTimeout(() => {
this._changeTimers.delete(key);
this.emitFileEvent(change);
}, DEBOUNCE_MS),
);
}
getPauseReason() {
return null;
}
}
exports.default = FallbackWatcher;
function isIgnorableFileError(error) {
return (
error.code === "ENOENT" || (error.code === "EPERM" && platform === "win32")
);
}
function recReaddir(
dir,
dirCallback,
fileCallback,
symlinkCallback,
endCallback,
errorCallback,
ignored,
) {
const walk = (0, _walker.default)(dir);
if (ignored) {
walk.filterDir(
(currentDir) => !common.posixPathMatchesPattern(ignored, currentDir),
);
}
walk
.on("dir", normalizeProxy(dirCallback))
.on("file", normalizeProxy(fileCallback))
.on("symlink", normalizeProxy(symlinkCallback))
.on("error", errorCallback)
.on("end", () => {
if (platform === "win32") {
setTimeout(endCallback, 1000);
} else {
endCallback();
}
});
}
function normalizeProxy(callback) {
return (filepath, stats) =>
callback(_path.default.normalize(filepath), stats);
}
@@ -0,0 +1,446 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
/**
* Originally vendored from https://github.com/amasad/sane/blob/64ff3a870c42e84f744086884bf55a4f9c22d376/src/node_watcher.js
*/
import type {
ChangeEventMetadata,
WatcherBackendChangeEvent,
} from '../flow-types';
import type {FSWatcher, Stats} from 'fs';
import {AbstractWatcher} from './AbstractWatcher';
import * as common from './common';
import fs from 'fs';
import os from 'os';
import path from 'path';
// $FlowFixMe[untyped-import] - Write libdefs for `walker`
import walker from 'walker';
const platform = os.platform();
const fsPromises = fs.promises;
const TOUCH_EVENT = common.TOUCH_EVENT;
const DELETE_EVENT = common.DELETE_EVENT;
/**
* This setting delays all events. It suppresses 'change' events that
* immediately follow an 'add', and debounces successive 'change' events to
* only emit the latest.
*/
const DEBOUNCE_MS = 100;
export default class FallbackWatcher extends AbstractWatcher {
+_changeTimers: Map<string, TimeoutID> = new Map();
+_dirRegistry: {
[directory: string]: {[file: string]: true, __proto__: null},
__proto__: null,
} = Object.create(null);
+watched: {[key: string]: FSWatcher, __proto__: null} = Object.create(null);
async startWatching() {
this._watchdir(this.root);
await new Promise(resolve => {
recReaddir(
this.root,
dir => {
this._watchdir(dir);
},
filename => {
this._register(filename, 'f');
},
symlink => {
this._register(symlink, 'l');
},
() => {
resolve();
},
this._checkedEmitError,
this.ignored,
);
});
}
/**
* Register files that matches our globs to know what to type of event to
* emit in the future.
*
* Registry looks like the following:
*
* dirRegister => Map {
* dirpath => Map {
* filename => true
* }
* }
*
* Return false if ignored or already registered.
*/
_register(filepath: string, type: ChangeEventMetadata['type']): boolean {
const dir = path.dirname(filepath);
const filename = path.basename(filepath);
if (this._dirRegistry[dir] && this._dirRegistry[dir][filename]) {
return false;
}
const relativePath = path.relative(this.root, filepath);
if (
this.doIgnore(relativePath) ||
(type === 'f' &&
!common.includedByGlob('f', this.globs, this.dot, relativePath))
) {
return false;
}
if (!this._dirRegistry[dir]) {
this._dirRegistry[dir] = Object.create(null);
}
this._dirRegistry[dir][filename] = true;
return true;
}
/**
* Removes a file from the registry.
*/
_unregister(filepath: string) {
const dir = path.dirname(filepath);
if (this._dirRegistry[dir]) {
const filename = path.basename(filepath);
delete this._dirRegistry[dir][filename];
}
}
/**
* Removes a dir from the registry.
*/
_unregisterDir(dirpath: string): void {
if (this._dirRegistry[dirpath]) {
delete this._dirRegistry[dirpath];
}
}
/**
* Checks if a file or directory exists in the registry.
*/
_registered(fullpath: string): boolean {
const dir = path.dirname(fullpath);
return !!(
this._dirRegistry[fullpath] ||
(this._dirRegistry[dir] &&
this._dirRegistry[dir][path.basename(fullpath)])
);
}
/**
* Emit "error" event if it's not an ignorable event
*/
_checkedEmitError: (error: Error) => void = error => {
if (!isIgnorableFileError(error)) {
this.emitError(error);
}
};
/**
* Watch a directory.
*/
_watchdir: string => boolean = (dir: string) => {
if (this.watched[dir]) {
return false;
}
const watcher = fs.watch(dir, {persistent: true}, (event, filename) =>
this._normalizeChange(dir, event, filename),
);
this.watched[dir] = watcher;
watcher.on('error', this._checkedEmitError);
if (this.root !== dir) {
this._register(dir, 'd');
}
return true;
};
/**
* Stop watching a directory.
*/
async _stopWatching(dir: string): Promise<void> {
if (this.watched[dir]) {
await new Promise(resolve => {
this.watched[dir].once('close', () => process.nextTick(resolve));
this.watched[dir].close();
delete this.watched[dir];
});
}
}
/**
* End watching.
*/
async stopWatching(): Promise<void> {
await super.stopWatching();
const promises = Object.keys(this.watched).map(dir =>
this._stopWatching(dir),
);
await Promise.all(promises);
}
/**
* On some platforms, as pointed out on the fs docs (most likely just win32)
* the file argument might be missing from the fs event. Try to detect what
* change by detecting if something was deleted or the most recent file change.
*/
_detectChangedFile(
dir: string,
event: string,
callback: (file: string) => void,
) {
if (!this._dirRegistry[dir]) {
return;
}
let found = false;
let closest: ?$ReadOnly<{file: string, mtime: Stats['mtime']}> = null;
let c = 0;
Object.keys(this._dirRegistry[dir]).forEach((file, i, arr) => {
fs.lstat(path.join(dir, file), (error, stat) => {
if (found) {
return;
}
if (error) {
if (isIgnorableFileError(error)) {
found = true;
callback(file);
} else {
this.emitError(error);
}
} else {
if (closest == null || stat.mtime > closest.mtime) {
closest = {file, mtime: stat.mtime};
}
if (arr.length === ++c) {
callback(closest.file);
}
}
});
});
}
/**
* Normalize fs events and pass it on to be processed.
*/
_normalizeChange(dir: string, event: string, file: string) {
if (!file) {
this._detectChangedFile(dir, event, actualFile => {
if (actualFile) {
this._processChange(dir, event, actualFile).catch(error =>
this.emitError(error),
);
}
});
} else {
this._processChange(dir, event, path.normalize(file)).catch(error =>
this.emitError(error),
);
}
}
/**
* Process changes.
*/
async _processChange(dir: string, event: string, file: string) {
const fullPath = path.join(dir, file);
const relativePath = path.join(path.relative(this.root, dir), file);
const registered = this._registered(fullPath);
try {
const stat = await fsPromises.lstat(fullPath);
if (stat.isDirectory()) {
// win32 emits usless change events on dirs.
if (event === 'change') {
return;
}
if (
this.doIgnore(relativePath) ||
!common.includedByGlob('d', this.globs, this.dot, relativePath)
) {
return;
}
recReaddir(
path.resolve(this.root, relativePath),
(dir, stats) => {
if (this._watchdir(dir)) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath: path.relative(this.root, dir),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: 'd',
},
});
}
},
(file, stats) => {
if (this._register(file, 'f')) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath: path.relative(this.root, file),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: 'f',
},
});
}
},
(symlink, stats) => {
if (this._register(symlink, 'l')) {
this.emitFileEvent({
event: TOUCH_EVENT,
relativePath: path.relative(this.root, symlink),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: 'l',
},
});
}
},
function endCallback() {},
this._checkedEmitError,
this.ignored,
);
} else {
const type = common.typeFromStat(stat);
if (type == null) {
return;
}
const metadata: ChangeEventMetadata = {
modifiedTime: stat.mtime.getTime(),
size: stat.size,
type,
};
if (registered) {
this._emitEvent({event: TOUCH_EVENT, relativePath, metadata});
} else {
if (this._register(fullPath, type)) {
this._emitEvent({event: TOUCH_EVENT, relativePath, metadata});
}
}
}
} catch (error) {
if (!isIgnorableFileError(error)) {
this.emitError(error);
return;
}
this._unregister(fullPath);
this._unregisterDir(fullPath);
if (registered) {
this._emitEvent({event: DELETE_EVENT, relativePath});
}
await this._stopWatching(fullPath);
}
}
/**
* Emits the given event after debouncing, to emit only the latest
* information when we receive several events in quick succession. E.g.,
* Linux emits two events for every new file.
*
* See also note above for DEBOUNCE_MS.
*/
_emitEvent(change: Omit<WatcherBackendChangeEvent, 'root'>) {
const {event, relativePath} = change;
const key = event + '-' + relativePath;
const existingTimer = this._changeTimers.get(key);
if (existingTimer) {
clearTimeout(existingTimer);
}
this._changeTimers.set(
key,
setTimeout(() => {
this._changeTimers.delete(key);
this.emitFileEvent(change);
}, DEBOUNCE_MS),
);
}
getPauseReason(): ?string {
return null;
}
}
/**
* Determine if a given FS error can be ignored
*/
function isIgnorableFileError(error: Error | {code: string}) {
return (
error.code === 'ENOENT' ||
// Workaround Windows EPERM on watched folder deletion, and when
// reading locked files (pending further writes or pending deletion).
// In such cases, we'll receive a subsequent event when the file is
// deleted or ready to read.
// https://github.com/facebook/metro/issues/1001
// https://github.com/nodejs/node-v0.x-archive/issues/4337
(error.code === 'EPERM' && platform === 'win32')
);
}
/**
* Traverse a directory recursively calling `callback` on every directory.
*/
function recReaddir(
dir: string,
dirCallback: (string, Stats) => void,
fileCallback: (string, Stats) => void,
symlinkCallback: (string, Stats) => void,
endCallback: () => void,
errorCallback: Error => void,
ignored: ?RegExp,
) {
const walk = walker(dir);
if (ignored) {
walk.filterDir(
(currentDir: string) =>
!common.posixPathMatchesPattern(ignored, currentDir),
);
}
walk
.on('dir', normalizeProxy(dirCallback))
.on('file', normalizeProxy(fileCallback))
.on('symlink', normalizeProxy(symlinkCallback))
.on('error', errorCallback)
.on('end', () => {
if (platform === 'win32') {
setTimeout(endCallback, 1000);
} else {
endCallback();
}
});
}
/**
* Returns a callback that when called will normalize a path and call the
* original callback
*/
function normalizeProxy<T>(
callback: (filepath: string, stats: Stats) => T,
): (string, Stats) => T {
return (filepath: string, stats: Stats) =>
callback(path.normalize(filepath), stats);
}
@@ -0,0 +1,109 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _AbstractWatcher = require("./AbstractWatcher");
var _common = require("./common");
var _fs = require("fs");
var _os = require("os");
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
const debug = require("debug")("Metro:NativeWatcher");
const TOUCH_EVENT = "touch";
const DELETE_EVENT = "delete";
class NativeWatcher extends _AbstractWatcher.AbstractWatcher {
#fsWatcher;
static isSupported() {
return (0, _os.platform)() === "darwin";
}
constructor(dir, opts) {
if (!NativeWatcher.isSupported) {
throw new Error("This watcher can only be used on macOS");
}
super(dir, opts);
}
async startWatching() {
this.#fsWatcher = (0, _fs.watch)(
this.root,
{
persistent: false,
recursive: true,
},
(_event, relativePath) => {
this._handleEvent(relativePath).catch((error) => {
this.emitError(error);
});
},
);
debug("Watching %s", this.root);
}
async stopWatching() {
await super.stopWatching();
if (this.#fsWatcher) {
this.#fsWatcher.close();
}
}
async _handleEvent(relativePath) {
const absolutePath = path.resolve(this.root, relativePath);
if (this.doIgnore(relativePath)) {
debug("Ignoring event on %s (root: %s)", relativePath, this.root);
return;
}
debug("Handling event on %s (root: %s)", relativePath, this.root);
try {
const stat = await _fs.promises.lstat(absolutePath);
const type = (0, _common.typeFromStat)(stat);
if (!type) {
return;
}
if (
!(0, _common.includedByGlob)(type, this.globs, this.dot, relativePath)
) {
return;
}
this.emitFileEvent({
event: TOUCH_EVENT,
relativePath,
metadata: {
type,
modifiedTime: stat.mtime.getTime(),
size: stat.size,
},
});
} catch (error) {
if (error?.code !== "ENOENT") {
this.emitError(error);
return;
}
this.emitFileEvent({
event: DELETE_EVENT,
relativePath,
});
}
}
}
exports.default = NativeWatcher;
@@ -0,0 +1,137 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import type {FSWatcher} from 'fs';
import {AbstractWatcher} from './AbstractWatcher';
import {includedByGlob, typeFromStat} from './common';
import {promises as fsPromises, watch} from 'fs';
import {platform} from 'os';
import * as path from 'path';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:NativeWatcher');
const TOUCH_EVENT = 'touch';
const DELETE_EVENT = 'delete';
/**
* NativeWatcher uses Node's native fs.watch API with recursive: true.
*
* Supported on macOS (and potentially Windows), because both natively have a
* concept of recurisve watching, via FSEvents and ReadDirectoryChangesW
* respectively. Notably Linux lacks this capability at the OS level.
*
* Node.js has at times supported the `recursive` option to fs.watch on Linux
* by walking the directory tree and creating a watcher on each directory, but
* this fits poorly with the synchronous `watch` API - either it must block for
* arbitrarily large IO, or it may drop changes after `watch` returns. See:
* https://github.com/nodejs/node/issues/48437
*
* Therefore, we retain a fallback to our own application-level recursive
* FallbackWatcher for Linux, which has async `startWatching`.
*
* On Windows, this watcher could be used in principle, but needs work around
* some Windows-specific edge cases handled in FallbackWatcher, like
* deduping file change events, ignoring directory changes, and handling EPERM.
*/
export default class NativeWatcher extends AbstractWatcher {
#fsWatcher: ?FSWatcher;
static isSupported(): boolean {
return platform() === 'darwin';
}
constructor(
dir: string,
opts: $ReadOnly<{
ignored: ?RegExp,
globs: $ReadOnlyArray<string>,
dot: boolean,
...
}>,
) {
if (!NativeWatcher.isSupported) {
throw new Error('This watcher can only be used on macOS');
}
super(dir, opts);
}
async startWatching(): Promise<void> {
this.#fsWatcher = watch(
this.root,
{
// Don't hold the process open if we forget to close()
persistent: false,
// FSEvents or ReadDirectoryChangesW should mean this is cheap and
// ~instant on macOS or Windows.
recursive: true,
},
(_event, relativePath) => {
// _event is always 'rename' on macOS, so we don't use it.
this._handleEvent(relativePath).catch(error => {
this.emitError(error);
});
},
);
debug('Watching %s', this.root);
}
/**
* End watching.
*/
async stopWatching(): Promise<void> {
await super.stopWatching();
if (this.#fsWatcher) {
this.#fsWatcher.close();
}
}
async _handleEvent(relativePath: string) {
const absolutePath = path.resolve(this.root, relativePath);
if (this.doIgnore(relativePath)) {
debug('Ignoring event on %s (root: %s)', relativePath, this.root);
return;
}
debug('Handling event on %s (root: %s)', relativePath, this.root);
try {
const stat = await fsPromises.lstat(absolutePath);
const type = typeFromStat(stat);
// Ignore files of an unrecognized type
if (!type) {
return;
}
if (!includedByGlob(type, this.globs, this.dot, relativePath)) {
return;
}
this.emitFileEvent({
event: TOUCH_EVENT,
relativePath,
metadata: {
type,
modifiedTime: stat.mtime.getTime(),
size: stat.size,
},
});
} catch (error) {
if (error?.code !== 'ENOENT') {
this.emitError(error);
return;
}
this.emitFileEvent({event: DELETE_EVENT, relativePath});
}
}
}
@@ -0,0 +1,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
class RecrawlWarning {
static RECRAWL_WARNINGS = [];
static REGEXP =
/Recrawled this watch (\d+) times?, most recently because:\n([^:]+)/;
constructor(root, count) {
this.root = root;
this.count = count;
}
static findByRoot(root) {
for (let i = 0; i < this.RECRAWL_WARNINGS.length; i++) {
const warning = this.RECRAWL_WARNINGS[i];
if (warning.root === root) {
return warning;
}
}
return undefined;
}
static isRecrawlWarningDupe(warningMessage) {
if (typeof warningMessage !== "string") {
return false;
}
const match = warningMessage.match(this.REGEXP);
if (!match) {
return false;
}
const count = Number(match[1]);
const root = match[2];
const warning = this.findByRoot(root);
if (warning) {
if (warning.count >= count) {
return true;
} else {
warning.count = count;
return false;
}
} else {
this.RECRAWL_WARNINGS.push(new RecrawlWarning(root, count));
return false;
}
}
}
exports.default = RecrawlWarning;
@@ -0,0 +1,69 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
/**
* Originally vendored from
* https://github.com/amasad/sane/blob/64ff3a870c42e84f744086884bf55a4f9c22d376/src/utils/recrawl-warning-dedupe.js
*/
export default class RecrawlWarning {
static RECRAWL_WARNINGS: Array<RecrawlWarning> = [];
static REGEXP: RegExp =
/Recrawled this watch (\d+) times?, most recently because:\n([^:]+)/;
root: string;
count: number;
constructor(root: string, count: number) {
this.root = root;
this.count = count;
}
static findByRoot(root: string): ?RecrawlWarning {
for (let i = 0; i < this.RECRAWL_WARNINGS.length; i++) {
const warning = this.RECRAWL_WARNINGS[i];
if (warning.root === root) {
return warning;
}
}
return undefined;
}
static isRecrawlWarningDupe(warningMessage: mixed): boolean {
if (typeof warningMessage !== 'string') {
return false;
}
const match = warningMessage.match(this.REGEXP);
if (!match) {
return false;
}
const count = Number(match[1]);
const root = match[2];
const warning = this.findByRoot(root);
if (warning) {
// only keep the highest count, assume count to either stay the same or
// increase.
if (warning.count >= count) {
return true;
} else {
// update the existing warning to the latest (highest) count
warning.count = count;
return false;
}
} else {
this.RECRAWL_WARNINGS.push(new RecrawlWarning(root, count));
return false;
}
}
}
@@ -0,0 +1,294 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _normalizePathSeparatorsToSystem = _interopRequireDefault(
require("../lib/normalizePathSeparatorsToSystem"),
);
var _AbstractWatcher = require("./AbstractWatcher");
var common = _interopRequireWildcard(require("./common"));
var _RecrawlWarning = _interopRequireDefault(require("./RecrawlWarning"));
var _assert = _interopRequireDefault(require("assert"));
var _crypto = require("crypto");
var _fbWatchman = _interopRequireDefault(require("fb-watchman"));
var _invariant = _interopRequireDefault(require("invariant"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:WatchmanWatcher");
const DELETE_EVENT = common.DELETE_EVENT;
const TOUCH_EVENT = common.TOUCH_EVENT;
const SUB_PREFIX = "metro-file-map";
class WatchmanWatcher extends _AbstractWatcher.AbstractWatcher {
#deferringStates = null;
constructor(dir, { watchmanDeferStates, ...opts }) {
super(dir, opts);
this.watchmanDeferStates = watchmanDeferStates;
const watchKey = (0, _crypto.createHash)("md5")
.update(this.root)
.digest("hex");
const readablePath = this.root
.replace(/[\/\\]/g, "-")
.replace(/[^\-\w]/g, "");
this.subscriptionName = `${SUB_PREFIX}-${process.pid}-${readablePath}-${watchKey}`;
}
async startWatching() {
await new Promise((resolve, reject) => this._init(resolve, reject));
}
_init(onReady, onError) {
if (this.client) {
this.client.removeAllListeners();
}
const self = this;
this.client = new _fbWatchman.default.Client();
this.client.on("error", (error) => {
this.emitError(error);
});
this.client.on("subscription", (changeEvent) =>
this._handleChangeEvent(changeEvent),
);
this.client.on("end", () => {
console.warn(
"[metro-file-map] Warning: Lost connection to Watchman, reconnecting..",
);
self._init(
() => {},
(error) => self.emitError(error),
);
});
this.watchProjectInfo = null;
function getWatchRoot() {
return self.watchProjectInfo ? self.watchProjectInfo.root : self.root;
}
function onWatchProject(error, resp) {
if (error) {
onError(error);
return;
}
debug("Received watch-project response: %s", resp.relative_path);
handleWarning(resp);
self.watchProjectInfo = {
relativePath: resp.relative_path
? (0, _normalizePathSeparatorsToSystem.default)(resp.relative_path)
: "",
root: (0, _normalizePathSeparatorsToSystem.default)(resp.watch),
};
self.client.command(["clock", getWatchRoot()], onClock);
}
function onClock(error, resp) {
if (error) {
onError(error);
return;
}
debug("Received clock response: %s", resp.clock);
const watchProjectInfo = self.watchProjectInfo;
(0, _invariant.default)(
watchProjectInfo != null,
"watch-project response should have been set before clock response",
);
handleWarning(resp);
const options = {
fields: ["name", "exists", "new", "type", "size", "mtime_ms"],
since: resp.clock,
defer: self.watchmanDeferStates,
relative_root: watchProjectInfo.relativePath,
};
if (self.globs.length === 0 && !self.dot) {
options.expression = [
"match",
"**",
"wholename",
{
includedotfiles: false,
},
];
}
self.client.command(
["subscribe", getWatchRoot(), self.subscriptionName, options],
onSubscribe,
);
}
const onSubscribe = (error, resp) => {
if (error) {
onError(error);
return;
}
debug("Received subscribe response: %s", resp.subscribe);
handleWarning(resp);
if (resp["asserted-states"] != null) {
this.#deferringStates = new Set(resp["asserted-states"]);
}
onReady();
};
self.client.command(["watch-project", getWatchRoot()], onWatchProject);
}
_handleChangeEvent(resp) {
debug(
"Received subscription response: %s (fresh: %s, files: %s, enter: %s, leave: %s, clock: %s)",
resp.subscription,
resp.is_fresh_instance,
resp.files?.length,
resp["state-enter"],
resp["state-leave"],
resp.clock,
);
_assert.default.equal(
resp.subscription,
this.subscriptionName,
"Invalid subscription event.",
);
if (Array.isArray(resp.files)) {
resp.files.forEach((change) =>
this._handleFileChange(change, resp.clock),
);
}
const { "state-enter": stateEnter, "state-leave": stateLeave } = resp;
if (
stateEnter != null &&
(this.watchmanDeferStates ?? []).includes(stateEnter)
) {
this.#deferringStates?.add(stateEnter);
debug(
'Watchman reports "%s" just started. Filesystem notifications are paused.',
stateEnter,
);
}
if (
stateLeave != null &&
(this.watchmanDeferStates ?? []).includes(stateLeave)
) {
this.#deferringStates?.delete(stateLeave);
debug(
'Watchman reports "%s" ended. Filesystem notifications resumed.',
stateLeave,
);
}
}
_handleFileChange(changeDescriptor, rawClock) {
const self = this;
const watchProjectInfo = self.watchProjectInfo;
(0, _invariant.default)(
watchProjectInfo != null,
"watch-project response should have been set before receiving subscription events",
);
const {
name: relativePosixPath,
new: isNew = false,
exists = false,
type,
mtime_ms,
size,
} = changeDescriptor;
const relativePath = (0, _normalizePathSeparatorsToSystem.default)(
relativePosixPath,
);
debug(
"Handling change to: %s (new: %s, exists: %s, type: %s)",
relativePath,
isNew,
exists,
type,
);
if (type != null && !(type === "f" || type === "d" || type === "l")) {
return;
}
if (
this.doIgnore(relativePath) ||
!common.includedByGlob(type, this.globs, this.dot, relativePath)
) {
return;
}
const clock =
typeof rawClock === "string" && this.watchProjectInfo != null
? [this.watchProjectInfo.root, rawClock]
: undefined;
if (!exists) {
self.emitFileEvent({
event: DELETE_EVENT,
clock,
relativePath,
});
} else {
(0, _invariant.default)(
type != null && mtime_ms != null && size != null,
'Watchman file change event for "%s" missing some requested metadata. ' +
"Got type: %s, mtime_ms: %s, size: %s",
relativePath,
type,
mtime_ms,
size,
);
if (!(type === "d" && !isNew)) {
const mtime = Number(mtime_ms);
self.emitFileEvent({
event: TOUCH_EVENT,
clock,
relativePath,
metadata: {
modifiedTime: mtime !== 0 ? mtime : null,
size,
type,
},
});
}
}
}
async stopWatching() {
await super.stopWatching();
if (this.client) {
this.client.removeAllListeners();
this.client.end();
}
this.#deferringStates = null;
}
getPauseReason() {
if (this.#deferringStates == null || this.#deferringStates.size === 0) {
return null;
}
const states = [...this.#deferringStates];
if (states.length === 1) {
return `The watch is in the '${states[0]}' state.`;
}
return `The watch is in the ${states
.slice(0, -1)
.map((s) => `'${s}'`)
.join(", ")} and '${states[states.length - 1]}' states.`;
}
}
exports.default = WatchmanWatcher;
function handleWarning(resp) {
if ("warning" in resp) {
if (_RecrawlWarning.default.isRecrawlWarningDupe(resp.warning)) {
return true;
}
console.warn(resp.warning);
return true;
} else {
return false;
}
}
@@ -0,0 +1,353 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {WatcherOptions} from './common';
import type {
Client,
WatchmanClockResponse,
WatchmanFileChange,
WatchmanQuery,
WatchmanSubscribeResponse,
WatchmanSubscriptionEvent,
WatchmanWatchResponse,
} from 'fb-watchman';
import normalizePathSeparatorsToSystem from '../lib/normalizePathSeparatorsToSystem';
import {AbstractWatcher} from './AbstractWatcher';
import * as common from './common';
import RecrawlWarning from './RecrawlWarning';
import assert from 'assert';
import {createHash} from 'crypto';
import watchman from 'fb-watchman';
import invariant from 'invariant';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:WatchmanWatcher');
const DELETE_EVENT = common.DELETE_EVENT;
const TOUCH_EVENT = common.TOUCH_EVENT;
const SUB_PREFIX = 'metro-file-map';
/**
* Watches `dir`.
*/
export default class WatchmanWatcher extends AbstractWatcher {
client: Client;
+subscriptionName: string;
watchProjectInfo: ?$ReadOnly<{
relativePath: string,
root: string,
}>;
+watchmanDeferStates: $ReadOnlyArray<string>;
#deferringStates: ?Set<string> = null;
constructor(dir: string, {watchmanDeferStates, ...opts}: WatcherOptions) {
super(dir, opts);
this.watchmanDeferStates = watchmanDeferStates;
// Use a unique subscription name per process per watched directory
const watchKey = createHash('md5').update(this.root).digest('hex');
const readablePath = this.root
.replace(/[\/\\]/g, '-') // \ and / to -
.replace(/[^\-\w]/g, ''); // Remove non-word/hyphen
this.subscriptionName = `${SUB_PREFIX}-${process.pid}-${readablePath}-${watchKey}`;
}
async startWatching() {
await new Promise((resolve, reject) => this._init(resolve, reject));
}
/**
* Run the watchman `watch` command on the root and subscribe to changes.
*/
_init(onReady: () => void, onError: (error: Error) => void) {
if (this.client) {
this.client.removeAllListeners();
}
const self = this;
this.client = new watchman.Client();
this.client.on('error', error => {
this.emitError(error);
});
this.client.on('subscription', changeEvent =>
this._handleChangeEvent(changeEvent),
);
this.client.on('end', () => {
console.warn(
'[metro-file-map] Warning: Lost connection to Watchman, reconnecting..',
);
self._init(
() => {},
error => self.emitError(error),
);
});
this.watchProjectInfo = null;
function getWatchRoot() {
return self.watchProjectInfo ? self.watchProjectInfo.root : self.root;
}
function onWatchProject(error: ?Error, resp: WatchmanWatchResponse) {
if (error) {
onError(error);
return;
}
debug('Received watch-project response: %s', resp.relative_path);
handleWarning(resp);
// NB: Watchman outputs posix-separated paths even on Windows, convert
// them to system-native separators.
self.watchProjectInfo = {
relativePath: resp.relative_path
? normalizePathSeparatorsToSystem(resp.relative_path)
: '',
root: normalizePathSeparatorsToSystem(resp.watch),
};
self.client.command(['clock', getWatchRoot()], onClock);
}
function onClock(error: ?Error, resp: WatchmanClockResponse) {
if (error) {
onError(error);
return;
}
debug('Received clock response: %s', resp.clock);
const watchProjectInfo = self.watchProjectInfo;
invariant(
watchProjectInfo != null,
'watch-project response should have been set before clock response',
);
handleWarning(resp);
const options: WatchmanQuery = {
fields: ['name', 'exists', 'new', 'type', 'size', 'mtime_ms'],
since: resp.clock,
defer: self.watchmanDeferStates,
relative_root: watchProjectInfo.relativePath,
};
// Make sure we honor the dot option if even we're not using globs.
if (self.globs.length === 0 && !self.dot) {
options.expression = [
'match',
'**',
'wholename',
{
includedotfiles: false,
},
];
}
self.client.command(
['subscribe', getWatchRoot(), self.subscriptionName, options],
onSubscribe,
);
}
const onSubscribe = (error: ?Error, resp: WatchmanSubscribeResponse) => {
if (error) {
onError(error);
return;
}
debug('Received subscribe response: %s', resp.subscribe);
handleWarning(resp);
if (resp['asserted-states'] != null) {
this.#deferringStates = new Set(resp['asserted-states']);
}
onReady();
};
self.client.command(['watch-project', getWatchRoot()], onWatchProject);
}
/**
* Handles a change event coming from the subscription.
*/
_handleChangeEvent(resp: WatchmanSubscriptionEvent) {
debug(
'Received subscription response: %s (fresh: %s, files: %s, enter: %s, leave: %s, clock: %s)',
resp.subscription,
resp.is_fresh_instance,
resp.files?.length,
resp['state-enter'],
resp['state-leave'],
resp.clock,
);
assert.equal(
resp.subscription,
this.subscriptionName,
'Invalid subscription event.',
);
if (Array.isArray(resp.files)) {
resp.files.forEach(change => this._handleFileChange(change, resp.clock));
}
const {'state-enter': stateEnter, 'state-leave': stateLeave} = resp;
if (
stateEnter != null &&
(this.watchmanDeferStates ?? []).includes(stateEnter)
) {
this.#deferringStates?.add(stateEnter);
debug(
'Watchman reports "%s" just started. Filesystem notifications are paused.',
stateEnter,
);
}
if (
stateLeave != null &&
(this.watchmanDeferStates ?? []).includes(stateLeave)
) {
this.#deferringStates?.delete(stateLeave);
debug(
'Watchman reports "%s" ended. Filesystem notifications resumed.',
stateLeave,
);
}
}
/**
* Handles a single change event record.
*/
_handleFileChange(
changeDescriptor: WatchmanFileChange,
rawClock: WatchmanSubscriptionEvent['clock'],
) {
const self = this;
const watchProjectInfo = self.watchProjectInfo;
invariant(
watchProjectInfo != null,
'watch-project response should have been set before receiving subscription events',
);
const {
name: relativePosixPath,
new: isNew = false,
exists = false,
type,
mtime_ms,
size,
} = changeDescriptor;
// Watchman emits posix-separated paths on Windows, which is inconsistent
// with other watchers. Normalize to system-native separators.
const relativePath = normalizePathSeparatorsToSystem(relativePosixPath);
debug(
'Handling change to: %s (new: %s, exists: %s, type: %s)',
relativePath,
isNew,
exists,
type,
);
// Ignore files of an unrecognized type
if (type != null && !(type === 'f' || type === 'd' || type === 'l')) {
return;
}
if (
this.doIgnore(relativePath) ||
!common.includedByGlob(type, this.globs, this.dot, relativePath)
) {
return;
}
const clock =
typeof rawClock === 'string' && this.watchProjectInfo != null
? ([this.watchProjectInfo.root, rawClock]: [string, string])
: undefined;
if (!exists) {
self.emitFileEvent({event: DELETE_EVENT, clock, relativePath});
} else {
invariant(
type != null && mtime_ms != null && size != null,
'Watchman file change event for "%s" missing some requested metadata. ' +
'Got type: %s, mtime_ms: %s, size: %s',
relativePath,
type,
mtime_ms,
size,
);
if (
// Change event on dirs are mostly useless.
!(type === 'd' && !isNew)
) {
const mtime = Number(mtime_ms);
self.emitFileEvent({
event: TOUCH_EVENT,
clock,
relativePath,
metadata: {
modifiedTime: mtime !== 0 ? mtime : null,
size,
type,
},
});
}
}
}
/**
* Closes the watcher.
*/
async stopWatching() {
await super.stopWatching();
if (this.client) {
this.client.removeAllListeners();
this.client.end();
}
this.#deferringStates = null;
}
getPauseReason(): ?string {
if (this.#deferringStates == null || this.#deferringStates.size === 0) {
return null;
}
const states = [...this.#deferringStates];
if (states.length === 1) {
return `The watch is in the '${states[0]}' state.`;
}
return `The watch is in the ${states
.slice(0, -1)
.map(s => `'${s}'`)
.join(', ')} and '${states[states.length - 1]}' states.`;
}
}
/**
* Handles a warning in the watchman resp object.
*/
function handleWarning(resp: $ReadOnly<{warning?: mixed, ...}>) {
if ('warning' in resp) {
if (RecrawlWarning.isRecrawlWarningDupe(resp.warning)) {
return true;
}
console.warn(resp.warning);
return true;
} else {
return false;
}
}
+42
View File
@@ -0,0 +1,42 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.TOUCH_EVENT = exports.DELETE_EVENT = exports.ALL_EVENT = void 0;
exports.includedByGlob = includedByGlob;
exports.posixPathMatchesPattern = void 0;
exports.typeFromStat = typeFromStat;
var _micromatch = _interopRequireDefault(require("micromatch"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const DELETE_EVENT = (exports.DELETE_EVENT = "delete");
const TOUCH_EVENT = (exports.TOUCH_EVENT = "touch");
const ALL_EVENT = (exports.ALL_EVENT = "all");
function includedByGlob(type, globs, dot, relativePath) {
if (globs.length === 0 || type !== "f") {
return dot || _micromatch.default.some(relativePath, "**/*");
}
return _micromatch.default.some(relativePath, globs, {
dot,
});
}
const posixPathMatchesPattern = (exports.posixPathMatchesPattern =
_path.default.sep === "/"
? (pattern, filePath) => pattern.test(filePath)
: (pattern, filePath) =>
pattern.test(filePath.replaceAll(_path.default.sep, "/")));
function typeFromStat(stat) {
if (stat.isSymbolicLink()) {
return "l";
}
if (stat.isDirectory()) {
return "d";
}
if (stat.isFile()) {
return "f";
}
return null;
}
+87
View File
@@ -0,0 +1,87 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
/**
* Originally vendored from
* https://github.com/amasad/sane/blob/64ff3a870c42e84f744086884bf55a4f9c22d376/src/common.js
*/
import type {ChangeEventMetadata} from '../flow-types';
import type {Stats} from 'fs';
// $FlowFixMe[untyped-import] - Write libdefs for `micromatch`
import micromatch from 'micromatch';
import path from 'path';
/**
* Constants
*/
export const DELETE_EVENT = 'delete';
export const TOUCH_EVENT = 'touch';
export const ALL_EVENT = 'all';
export type WatcherOptions = $ReadOnly<{
globs: $ReadOnlyArray<string>,
dot: boolean,
ignored: ?RegExp,
watchmanDeferStates: $ReadOnlyArray<string>,
watchman?: mixed,
watchmanPath?: string,
}>;
/**
* Checks a file relative path against the globs array.
*/
export function includedByGlob(
type: ?('f' | 'l' | 'd'),
globs: $ReadOnlyArray<string>,
dot: boolean,
relativePath: string,
): boolean {
// For non-regular files or if there are no glob matchers, just respect the
// `dot` option to filter dotfiles if dot === false.
if (globs.length === 0 || type !== 'f') {
return dot || micromatch.some(relativePath, '**/*');
}
return micromatch.some(relativePath, globs, {dot});
}
/**
* Whether the given filePath matches the given RegExp, after converting
* (on Windows only) system separators to posix separators.
*
* Conversion to posix is for backwards compatibility with the previous
* anymatch matcher, which normlises all inputs[1]. This may not be consistent
* with other parts of metro-file-map.
*
* [1]: https://github.com/micromatch/anymatch/blob/3.1.1/index.js#L50
*/
export const posixPathMatchesPattern: (
pattern: RegExp,
filePath: string,
) => boolean =
path.sep === '/'
? (pattern, filePath) => pattern.test(filePath)
: (pattern, filePath) => pattern.test(filePath.replaceAll(path.sep, '/'));
export function typeFromStat(stat: Stats): ?ChangeEventMetadata['type'] {
// Note: These tests are not mutually exclusive - a symlink passes isFile
if (stat.isSymbolicLink()) {
return 'l';
}
if (stat.isDirectory()) {
return 'd';
}
if (stat.isFile()) {
return 'f'; // "Regular" file
}
return null;
}