Add <%br= %> jqhtml syntax docs, class override detection, npm update

Document event handler placement and model fetch clarification

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-01-15 10:16:06 +00:00
parent 61f8f058f2
commit 1594502cb2
791 changed files with 7044 additions and 6089 deletions

View File

@@ -103,7 +103,7 @@ You can specify the exact backend you wish to use by passing the `backend` optio
All of the APIs in `@parcel/watcher` support the following options, which are passed as an object as the last function argument.
- `ignore` - an array of paths or glob patterns to ignore. uses [`is-glob`](https://github.com/micromatch/is-glob) to distinguish paths from globs. glob patterns are parsed with [`micromatch`](https://github.com/micromatch/micromatch) (see [features](https://github.com/micromatch/micromatch#matching-features)).
- `ignore` - an array of paths or glob patterns to ignore. uses [`is-glob`](https://github.com/micromatch/is-glob) to distinguish paths from globs. glob patterns are parsed with [`picomatch`](https://github.com/micromatch/picomatch) (see [features](https://github.com/micromatch/picomatch#globbing-features)).
- paths can be relative or absolute and can either be files or directories. No events will be emitted about these files or directories or their children.
- glob patterns match on relative paths from the root that is watched. No events will be emitted for matching paths.
- `backend` - the name of an explicitly chosen backend to use. Allowed options are `"fs-events"`, `"watchman"`, `"inotify"`, `"kqueue"`, `"windows"`, or `"brute-force"` (only for querying). If the specified backend is not available on the current platform, the default backend will be used instead.
@@ -129,6 +129,7 @@ subscribe(/* ... */);
- [Gatsby Cloud](https://twitter.com/chatsidhartha/status/1435647412828196867)
- [Nx](https://nx.dev)
- [Nuxt](https://nuxt.com)
- [Meteor](https://github.com/meteor/meteor)
## License

View File

@@ -65,7 +65,21 @@
"msvs_settings": {
"VCCLCompilerTool": {
"ExceptionHandling": 1, # /EHsc
"AdditionalOptions": ['-std:c++17']
"AdditionalOptions": [
"-std:c++17",
"/guard:cf",
"/W3",
"/w34244",
"/w34267",
"/sdl",
"/ZH:SHA_256"
]
},
"VCLinkerTool": {
"AdditionalOptions": [
"/DYNAMICBASE",
"/guard:cf"
]
}
}
}],

View File

@@ -2,7 +2,8 @@ const {createWrapper} = require('./wrapper');
let name = `@parcel/watcher-${process.platform}-${process.arch}`;
if (process.platform === 'linux') {
const { MUSL, family } = require('detect-libc');
const { MUSL, familySync } = require('detect-libc');
const family = familySync();
if (family === MUSL) {
name += '-musl';
} else {

View File

@@ -1,6 +1,6 @@
{
"name": "@parcel/watcher",
"version": "2.5.1",
"version": "2.5.4",
"main": "index.js",
"types": "index.d.ts",
"repository": {
@@ -50,10 +50,10 @@
]
},
"dependencies": {
"detect-libc": "^1.0.3",
"detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
"micromatch": "^4.0.5",
"node-addon-api": "^7.0.0"
"node-addon-api": "^7.0.0",
"picomatch": "^4.0.3"
},
"devDependencies": {
"esbuild": "^0.19.8",
@@ -71,18 +71,18 @@
]
},
"optionalDependencies": {
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.1"
"@parcel/watcher-darwin-x64": "2.5.4",
"@parcel/watcher-darwin-arm64": "2.5.4",
"@parcel/watcher-win32-x64": "2.5.4",
"@parcel/watcher-win32-arm64": "2.5.4",
"@parcel/watcher-win32-ia32": "2.5.4",
"@parcel/watcher-linux-x64-glibc": "2.5.4",
"@parcel/watcher-linux-x64-musl": "2.5.4",
"@parcel/watcher-linux-arm64-glibc": "2.5.4",
"@parcel/watcher-linux-arm64-musl": "2.5.4",
"@parcel/watcher-linux-arm-glibc": "2.5.4",
"@parcel/watcher-linux-arm-musl": "2.5.4",
"@parcel/watcher-android-arm64": "2.5.4",
"@parcel/watcher-freebsd-x64": "2.5.4"
}
}

View File

@@ -21,7 +21,11 @@
#include "Backend.hh"
#include <unordered_map>
static std::unordered_map<std::string, std::shared_ptr<Backend>> sharedBackends;
static std::unordered_map<std::string, std::shared_ptr<Backend>>& getSharedBackends() {
static std::unordered_map<std::string, std::shared_ptr<Backend>>* sharedBackends =
new std::unordered_map<std::string, std::shared_ptr<Backend>>();
return *sharedBackends;
}
std::shared_ptr<Backend> getBackend(std::string backend) {
// Use FSEvents on macOS by default.
@@ -65,8 +69,8 @@ std::shared_ptr<Backend> getBackend(std::string backend) {
}
std::shared_ptr<Backend> Backend::getShared(std::string backend) {
auto found = sharedBackends.find(backend);
if (found != sharedBackends.end()) {
auto found = getSharedBackends().find(backend);
if (found != getSharedBackends().end()) {
return found->second;
}
@@ -76,21 +80,21 @@ std::shared_ptr<Backend> Backend::getShared(std::string backend) {
}
result->run();
sharedBackends.emplace(backend, result);
getSharedBackends().emplace(backend, result);
return result;
}
void removeShared(Backend *backend) {
for (auto it = sharedBackends.begin(); it != sharedBackends.end(); it++) {
for (auto it = getSharedBackends().begin(); it != getSharedBackends().end(); it++) {
if (it->second.get() == backend) {
sharedBackends.erase(it);
getSharedBackends().erase(it);
break;
}
}
// Free up memory.
if (sharedBackends.size() == 0) {
sharedBackends.rehash(0);
if (getSharedBackends().size() == 0) {
getSharedBackends().rehash(0);
}
}
@@ -145,7 +149,7 @@ void Backend::watch(WatcherRef watcher) {
try {
this->subscribe(watcher);
mSubscriptions.insert(watcher);
} catch (std::exception &err) {
} catch (std::exception&) {
unref();
throw;
}

View File

@@ -1,34 +1,46 @@
#include "DirTree.hh"
#include <inttypes.h>
static std::mutex mDirCacheMutex;
static std::unordered_map<std::string, std::weak_ptr<DirTree>> dirTreeCache;
// "Meyer's singleton", construction is ordered by use, likewise (reverse) for destruction.
// https://stackoverflow.com/a/17713799
// https://laristra.github.io/flecsi/src/developer-guide/patterns/meyers_singleton.html
static std::mutex& mDirCacheMutex() {
static std::mutex mutex;
return mutex;
}
static std::unordered_map<std::string, std::weak_ptr<DirTree>>& dirTreeCache() {
static std::unordered_map<std::string, std::weak_ptr<DirTree>> cache;
return cache;
}
struct DirTreeDeleter {
void operator()(DirTree *tree) {
std::lock_guard<std::mutex> lock(mDirCacheMutex);
dirTreeCache.erase(tree->root);
std::lock_guard<std::mutex> lock(mDirCacheMutex());
std::unordered_map<std::string, std::weak_ptr<DirTree>> &cache = dirTreeCache();
cache.erase(tree->root);
delete tree;
// Free up memory.
if (dirTreeCache.size() == 0) {
dirTreeCache.rehash(0);
if (cache.size() == 0) {
cache.rehash(0);
}
}
};
std::shared_ptr<DirTree> DirTree::getCached(std::string root) {
std::lock_guard<std::mutex> lock(mDirCacheMutex);
std::lock_guard<std::mutex> lock(mDirCacheMutex());
std::unordered_map<std::string, std::weak_ptr<DirTree>> &cache = dirTreeCache();
auto found = dirTreeCache.find(root);
auto found = cache.find(root);
std::shared_ptr<DirTree> tree;
// Use cached tree, or create an empty one.
if (found != dirTreeCache.end()) {
if (found != cache.end()) {
tree = found->second.lock();
} else {
tree = std::shared_ptr<DirTree>(new DirTree(root), DirTreeDeleter());
dirTreeCache.emplace(root, tree);
cache.emplace(root, tree);
}
return tree;
@@ -55,7 +67,7 @@ DirEntry *DirTree::_find(std::string path) {
}
DirEntry *DirTree::add(std::string path, uint64_t mtime, bool isDir) {
std::lock_guard<std::mutex> lock(mMutex);
std::lock_guard<std::mutex> lock(mDirCacheMutex());
DirEntry entry(path, mtime, isDir);
auto it = entries.emplace(entry.path, entry);
@@ -63,12 +75,12 @@ DirEntry *DirTree::add(std::string path, uint64_t mtime, bool isDir) {
}
DirEntry *DirTree::find(std::string path) {
std::lock_guard<std::mutex> lock(mMutex);
std::lock_guard<std::mutex> lock(mDirCacheMutex());
return _find(path);
}
DirEntry *DirTree::update(std::string path, uint64_t mtime) {
std::lock_guard<std::mutex> lock(mMutex);
std::lock_guard<std::mutex> lock(mDirCacheMutex());
DirEntry *found = _find(path);
if (found) {
@@ -79,7 +91,7 @@ DirEntry *DirTree::update(std::string path, uint64_t mtime) {
}
void DirTree::remove(std::string path) {
std::lock_guard<std::mutex> lock(mMutex);
std::lock_guard<std::mutex> lock(mDirCacheMutex());
DirEntry *found = _find(path);
@@ -99,7 +111,7 @@ void DirTree::remove(std::string path) {
}
void DirTree::write(FILE *f) {
std::lock_guard<std::mutex> lock(mMutex);
std::lock_guard<std::mutex> lock(mDirCacheMutex());
fprintf(f, "%zu\n", entries.size());
for (auto it = entries.begin(); it != entries.end(); it++) {
@@ -108,7 +120,7 @@ void DirTree::write(FILE *f) {
}
void DirTree::getChanges(DirTree *snapshot, EventList &events) {
std::lock_guard<std::mutex> lock(mMutex);
std::lock_guard<std::mutex> lock(mDirCacheMutex());
std::lock_guard<std::mutex> snapshotLock(snapshot->mMutex);
for (auto it = entries.begin(); it != entries.end(); it++) {

View File

@@ -14,7 +14,7 @@ struct Glob {
Glob(std::string raw);
bool operator==(const Glob &other) const {
return mHash == other.mHash;
return mHash == other.mHash && mRaw == other.mRaw;
}
bool isIgnored(std::string relative_path) const;

View File

@@ -15,30 +15,34 @@ struct WatcherCompare {
}
};
static std::unordered_set<WatcherRef , WatcherHash, WatcherCompare> sharedWatchers;
static std::unordered_set<WatcherRef , WatcherHash, WatcherCompare>& getSharedWatchers() {
static std::unordered_set<WatcherRef , WatcherHash, WatcherCompare>* sharedWatchers =
new std::unordered_set<WatcherRef , WatcherHash, WatcherCompare>();
return *sharedWatchers;
}
WatcherRef Watcher::getShared(std::string dir, std::unordered_set<std::string> ignorePaths, std::unordered_set<Glob> ignoreGlobs) {
WatcherRef watcher = std::make_shared<Watcher>(dir, ignorePaths, ignoreGlobs);
auto found = sharedWatchers.find(watcher);
if (found != sharedWatchers.end()) {
auto found = getSharedWatchers().find(watcher);
if (found != getSharedWatchers().end()) {
return *found;
}
sharedWatchers.insert(watcher);
getSharedWatchers().insert(watcher);
return watcher;
}
void removeShared(Watcher *watcher) {
for (auto it = sharedWatchers.begin(); it != sharedWatchers.end(); it++) {
for (auto it = getSharedWatchers().begin(); it != getSharedWatchers().end(); it++) {
if (it->get() == watcher) {
sharedWatchers.erase(it);
getSharedWatchers().erase(it);
break;
}
}
// Free up memory.
if (sharedWatchers.size() == 0) {
sharedWatchers.rehash(0);
if (getSharedWatchers().size() == 0) {
getSharedWatchers().rehash(0);
}
}
@@ -84,7 +88,7 @@ struct CallbackData {
Value callbackEventsToJS(const Env &env, std::vector<Event> &events) {
EscapableHandleScope scope(env);
Array arr = Array::New(env, events.size());
size_t currentEventIndex = 0;
uint32_t currentEventIndex = 0;
for (auto eventIterator = events.begin(); eventIterator != events.end(); eventIterator++) {
arr.Set(currentEventIndex++, eventIterator->toJS(env));
}

View File

@@ -18,7 +18,7 @@ std::unordered_set<std::string> getIgnorePaths(Env env, Value opts) {
if (v.IsArray()) {
Array items = v.As<Array>();
for (size_t i = 0; i < items.Length(); i++) {
Value item = items.Get(Number::New(env, i));
Value item = items.Get(Number::New(env, static_cast<double>(i)));
if (item.IsString()) {
result.insert(std::string(item.As<String>().Utf8Value().c_str()));
}
@@ -37,7 +37,7 @@ std::unordered_set<Glob> getIgnoreGlobs(Env env, Value opts) {
if (v.IsArray()) {
Array items = v.As<Array>();
for (size_t i = 0; i < items.Length(); i++) {
Value item = items.Get(Number::New(env, i));
Value item = items.Get(Number::New(env, static_cast<double>(i)));
if (item.IsString()) {
auto key = item.As<String>().Utf8Value();
try {
@@ -124,7 +124,7 @@ private:
Value getResult() override {
std::vector<Event> events = watcher->mEvents.getEvents();
Array eventsArray = Array::New(env, events.size());
size_t i = 0;
uint32_t i = 0;
for (auto it = events.begin(); it != events.end(); it++) {
eventsArray.Set(i++, it->toJS(env));
}
@@ -183,7 +183,7 @@ private:
void execute() override {
try {
backend->watch(watcher);
} catch (std::exception &err) {
} catch (std::exception&) {
watcher->destroy();
throw;
}

View File

@@ -170,7 +170,9 @@ bool InotifyBackend::handleSubscription(struct inotify_event *event, std::shared
struct stat st;
// Use lstat to avoid resolving symbolic links that we cannot watch anyway
// https://github.com/parcel-bundler/watcher/issues/76
lstat(path.c_str(), &st);
if (lstat(path.c_str(), &st) != 0) {
return false;
}
DirEntry *entry = sub->tree->add(path, CONVERT_TIME(st.st_mtim), S_ISDIR(st.st_mode));
if (entry->isDir) {
@@ -184,7 +186,9 @@ bool InotifyBackend::handleSubscription(struct inotify_event *event, std::shared
watcher->mEvents.update(path);
struct stat st;
stat(path.c_str(), &st);
if (stat(path.c_str(), &st) != 0) {
return false;
}
sub->tree->update(path, CONVERT_TIME(st.st_mtim));
} else if (event->mask & (IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM | IN_MOVE_SELF)) {
bool isSelfEvent = (event->mask & (IN_DELETE_SELF | IN_MOVE_SELF));

View File

@@ -98,7 +98,7 @@ public:
value.push_back(BSER(iss));
}
}
BSER::Array arrayValue() override {
return value;
}
@@ -184,7 +184,7 @@ public:
BSERBoolean(bool value) : Value(value) {}
bool boolValue() override { return value; }
void encode(std::ostream &oss) override {
int8_t t = value == true ? BSER_BOOL_TRUE : BSER_BOOL_FALSE;
int8_t t = value == true ? static_cast<int8_t>(BSER_BOOL_TRUE) : static_cast<int8_t>(BSER_BOOL_FALSE);
oss.write(reinterpret_cast<char*>(&t), sizeof(t));
}
};
@@ -295,7 +295,7 @@ std::string BSER::encode() {
std::ostringstream res(std::ios_base::binary);
res.write("\x00\x01", 2);
BSERInteger(oss.str().size()).encode(res);
res << oss.str();
return res.str();

View File

@@ -77,7 +77,7 @@ public:
bool success = WriteFile(
mPipe, // pipe handle
buf.data(), // message
buf.size(), // message length
static_cast<DWORD>(buf.size()), // message length
NULL, // bytes written
&overlapped // overlapped
);
@@ -125,7 +125,7 @@ public:
bool success = ReadFile(
mPipe, // pipe handle
buf, // buffer to receive reply
len, // size of buffer
static_cast<DWORD>(len), // size of buffer
NULL, // number of bytes read
&overlapped // overlapped
);

View File

@@ -21,7 +21,7 @@ template<typename T>
BSER readBSER(T &&do_read) {
std::stringstream oss;
char buffer[256];
int r;
size_t r;
int64_t len = -1;
do {
// Start by reading a minimal amount of data in order to decode the length.
@@ -46,7 +46,11 @@ std::string getSockPath() {
return std::string(var);
}
#ifdef _WIN32
FILE *fp = popen("watchman --output-encoding=bser get-sockname", "r");
#else
FILE *fp = popen("watchman --output-encoding=bser get-sockname 2>/dev/null", "r");
#endif
if (fp == NULL || errno == ECHILD) {
throw std::runtime_error("Failed to execute watchman");
}
@@ -104,7 +108,7 @@ bool WatchmanBackend::checkAvailable() {
try {
watchmanConnect();
return true;
} catch (std::exception &err) {
} catch (std::exception&) {
return false;
}
}

View File

@@ -5,17 +5,17 @@ std::wstring utf8ToUtf16(std::string input) {
WCHAR *output = new WCHAR[len];
MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, output, len);
std::wstring res(output);
delete output;
delete[] output;
return res;
}
std::string utf16ToUtf8(const WCHAR *input, size_t length) {
std::string utf16ToUtf8(const WCHAR *input, DWORD length) {
unsigned int len = WideCharToMultiByte(CP_UTF8, 0, input, length, NULL, 0, NULL, NULL);
char *output = new char[len + 1];
WideCharToMultiByte(CP_UTF8, 0, input, length, output, len, NULL, NULL);
output[len] = '\0';
std::string res(output);
delete output;
delete[] output;
return res;
}
@@ -24,7 +24,7 @@ std::string normalizePath(std::string path) {
std::wstring p = utf8ToUtf16("\\\\?\\" + path);
// Get the required length for the output
unsigned int len = GetLongPathNameW(p.data(), NULL, 0);
DWORD len = GetLongPathNameW(p.data(), NULL, 0);
if (!len) {
return path;
}
@@ -33,12 +33,12 @@ std::string normalizePath(std::string path) {
WCHAR *output = new WCHAR[len];
len = GetLongPathNameW(p.data(), output, len);
if (!len) {
delete output;
delete[] output;
return path;
}
// Convert back to utf8
std::string res = utf16ToUtf8(output + 4, len - 4);
delete output;
delete[] output;
return res;
}

View File

@@ -5,7 +5,7 @@
#include <windows.h>
std::wstring utf8ToUtf16(std::string input);
std::string utf16ToUtf8(const WCHAR *input, size_t length);
std::string utf16ToUtf8(const WCHAR *input, DWORD length);
std::string normalizePath(std::string path);
#endif

View File

@@ -1,5 +1,5 @@
const path = require('path');
const micromatch = require('micromatch');
const picomatch = require('picomatch');
const isGlob = require('is-glob');
function normalizeOptions(dir, opts = {}) {
@@ -14,16 +14,13 @@ function normalizeOptions(dir, opts = {}) {
opts.ignoreGlobs = [];
}
const regex = micromatch.makeRe(value, {
const regex = picomatch.makeRe(value, {
// We set `dot: true` to workaround an issue with the
// regular expression on Linux where the resulting
// negative lookahead `(?!(\\/|^)` was never matching
// in some cases. See also https://bit.ly/3UZlQDm
dot: true,
// C++ does not support lookbehind regex patterns, they
// were only added later to JavaScript engines
// (https://bit.ly/3V7S6UL)
lookbehinds: false
windows: process.platform === 'win32',
});
opts.ignoreGlobs.push(regex.source);
} else {