From 02b8c59c87b748cdbff48ac1a54a8a1029b5f683 Mon Sep 17 00:00:00 2001 From: JungMinu Date: Mon, 27 Apr 2026 18:47:13 +0900 Subject: [PATCH 1/8] deps: bump ncm-ng to 2.9.8 - tar: ^6.0.1 -> ^7.5.13 (fixes 6 HIGH CVEs) - uuid: ^8.3.0 -> ^14.0.0 (fixes GHSA-w5hq-g745-h8pq) Signed-off-by: Minwoo --- deps/ncm-ng/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deps/ncm-ng/package.json b/deps/ncm-ng/package.json index 2fd39e259e8..b1e9249e762 100644 --- a/deps/ncm-ng/package.json +++ b/deps/ncm-ng/package.json @@ -1,6 +1,6 @@ { "name": "@ns-private/ncm-ng", - "version": "2.9.7", + "version": "2.9.8", "main": "index.js", "publishConfig": { "registry": "http://packages-internal.nodesource.io" @@ -37,8 +37,8 @@ "spdx-satisfies": "^5.0.1", "ssri": "^8.0.1", "stats-lite": "^2.2.0", - "tar": "^6.0.1", + "tar": "^7.5.13", "tmp": "^0.2.1", - "uuid": "^8.3.0" + "uuid": "^14.0.0" } } From a1b52c5fa909c714a6c1eac143eed7ebd44125de Mon Sep 17 00:00:00 2001 From: Santiago Gimeno Date: Thu, 19 Mar 2026 16:20:28 +0100 Subject: [PATCH 2/8] src: replace duplicate loop hook regs Replace blocked and unblocked loop hook entries when the same callback is registered again. These hooks were stored in append-only TSList containers, so dynamic reconfiguration in agents like gRPC and ZMQ kept stale registrations alive. That caused duplicate callback delivery and let old blocked loop thresholds continue affecting detection. Add TSList::replace_if() and use it in blocked and unblocked loop hook registration. When the callback function pointer matches an existing entry, overwrite it instead of appending a new one. For blocked loop hooks, recompute min_blocked_threshold_ after each registration so a replaced threshold immediately becomes effective. PR-URL: https://github.com/nodesource/nsolid/pull/444 Reviewed-By: Rafael Gonzaga Reviewed-By: EHortua <55801532+EHortua@users.noreply.github.com> --- src/nsolid/nsolid_api.cc | 25 +++++-- src/nsolid/nsolid_api.h | 1 + src/nsolid/thread_safe.h | 12 ++++ test/agents/test-grpc-blocked-loop.mjs | 94 ++++++++++++++++++++++++-- 4 files changed, 124 insertions(+), 8 deletions(-) diff --git a/src/nsolid/nsolid_api.cc b/src/nsolid/nsolid_api.cc index f05a81009b9..602bb07cfed 100644 --- a/src/nsolid/nsolid_api.cc +++ b/src/nsolid/nsolid_api.cc @@ -17,6 +17,7 @@ #include "node_url.h" #include "v8-fast-api-calls.h" +#include #include #if defined(__linux__) @@ -962,10 +963,12 @@ void EnvList::OnBlockedLoopHook( void* data, internal::on_block_loop_hook_proxy_sig proxy, internal::deleter_sig deleter) { - blocked_hooks_list_.push_back( + blocked_hooks_list_.replace_if( + [proxy](const BlockedLoopStor& stor) { + return stor.cb == proxy; + }, { threshold, proxy, nsolid::internal::user_data(data, deleter) }); - if (threshold < min_blocked_threshold_) - min_blocked_threshold_ = threshold; + refresh_min_blocked_threshold(); } void EnvList::OnUnblockedLoopHook( @@ -974,7 +977,10 @@ void EnvList::OnUnblockedLoopHook( internal::deleter_sig deleter) { // Using BlockedLoopStor because it's easier than duplicating a bunch of code, // but that means some value needs to be passed in for threshold. - unblocked_hooks_list_.push_back( + unblocked_hooks_list_.replace_if( + [proxy](const BlockedLoopStor& stor) { + return stor.cb == proxy; + }, { 0, proxy, nsolid::internal::user_data(data, deleter) }); } @@ -1005,6 +1011,17 @@ nlohmann::json EnvList::CurrentConfigJSON() { } +void EnvList::refresh_min_blocked_threshold() { + uint64_t min_threshold = UINT64_MAX; + + blocked_hooks_list_.for_each([&min_threshold](const BlockedLoopStor& stor) { + min_threshold = std::min(min_threshold, stor.threshold); + }); + + min_blocked_threshold_ = min_threshold; +} + + void EnvList::AddEnv(Environment* env) { SharedEnvInst envinst_sp = EnvInst::Create(env); diff --git a/src/nsolid/nsolid_api.h b/src/nsolid/nsolid_api.h index 0853991b9ab..b36551ede82 100644 --- a/src/nsolid/nsolid_api.h +++ b/src/nsolid/nsolid_api.h @@ -631,6 +631,7 @@ class EnvList { void fill_trace_id_q(); void update_continuous_profiler(bool enabled, uint64_t interval); + void refresh_min_blocked_threshold(); #ifdef __POSIX__ static void signal_handler_(int signum, siginfo_t* info, void* ucontext); diff --git a/src/nsolid/thread_safe.h b/src/nsolid/thread_safe.h index a4d2e825a63..ee97888c0e1 100644 --- a/src/nsolid/thread_safe.h +++ b/src/nsolid/thread_safe.h @@ -142,6 +142,18 @@ struct TSList { list_.push_back(std::move(data)); return --list_.end(); } + template + inline bool replace_if(Match match, DataType&& data) { + nsuv::ns_mutex::scoped_lock lock(lock_); + for (auto it = list_.begin(); it != list_.end(); ++it) { + if (!match(*it)) + continue; + *it = std::move(data); + return true; + } + list_.push_back(std::move(data)); + return false; + } inline void for_each(std::function fn) { nsuv::ns_mutex::scoped_lock lock(lock_); std::for_each(list_.begin(), list_.end(), fn); diff --git a/test/agents/test-grpc-blocked-loop.mjs b/test/agents/test-grpc-blocked-loop.mjs index 977e877bd34..a563d6a348c 100644 --- a/test/agents/test-grpc-blocked-loop.mjs +++ b/test/agents/test-grpc-blocked-loop.mjs @@ -143,7 +143,43 @@ function checkUnblockedLoopData(blocked, metadata, agentId, threadId, bInfo) { const tests = []; tests.push({ - name: 'should work in the main thread', + name: 'should work in the main thread with default threshold of 200ms', + test: async (getEnv) => { + return new Promise((resolve) => { + let times = 0; + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + grpcServer.on('loop_blocked', mustCall(async (data) => { + checkBlockedLoopData(data.msg, data.metadata, agentId, threadId); + }, 2)); + + grpcServer.on('loop_unblocked', mustCall(async (data) => { + checkUnblockedLoopData(data.msg, data.metadata, agentId, threadId); + if (++times === 2) { + await child.shutdown(0); + grpcServer.close(); + resolve(); + } else { + await child.block(0, 400); + } + }, 2)); + + const env = getEnv(port); + + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + await child.block(0, 400); + })); + }); + }, +}); + +tests.push({ + name: 'should work in the main thread with different threshold', test: async (getEnv) => { return new Promise((resolve) => { const grpcServer = new GRPCServer(); @@ -163,18 +199,62 @@ tests.push({ const opts = { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], - env, + env: { + ...env, + NSOLID_BLOCKED_LOOP_THRESHOLD: '1000', + }, }; const child = new TestClient([], opts); const agentId = await child.id(); await child.block(0, 400); + setTimeout(() => { + child.block(0, 1500); + }, 500); + })); + }); + }, +}); + +tests.push({ + name: 'should work for workers with default threshold of 200ms', + test: async (getEnv) => { + return new Promise((resolve) => { + let times = 0; + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + grpcServer.on('loop_blocked', mustCall(async (data) => { + checkBlockedLoopData(data.msg, data.metadata, agentId, wid); + }, 2)); + + grpcServer.on('loop_unblocked', mustCall(async (data) => { + checkUnblockedLoopData(data.msg, data.metadata, agentId, wid); + if (++times === 2) { + await child.shutdown(0); + grpcServer.close(); + resolve(); + } else { + await child.block(wid, 800); + } + }, 2)); + + const env = getEnv(port); + + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([ '-w', 1 ], opts); + const agentId = await child.id(); + const workers = await child.workers(); + const wid = workers[0]; + await child.block(wid, 400); })); }); }, }); tests.push({ - name: 'should work for workers', + name: 'should work for workers with different threshold', test: async (getEnv) => { return new Promise((resolve) => { const grpcServer = new GRPCServer(); @@ -194,13 +274,19 @@ tests.push({ const opts = { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], - env, + env: { + ...env, + NSOLID_BLOCKED_LOOP_THRESHOLD: '1000', + }, }; const child = new TestClient([ '-w', 1 ], opts); const agentId = await child.id(); const workers = await child.workers(); const wid = workers[0]; await child.block(wid, 400); + setTimeout(() => { + child.block(wid, 1500); + }, 500); })); }); }, From 493631c083223e2e81fe1da8c118e9b7e195f239 Mon Sep 17 00:00:00 2001 From: Santiago Gimeno Date: Thu, 19 Mar 2026 15:39:28 +0100 Subject: [PATCH 3/8] lib,src,test: fix race during tracing toggles Keep JS tracing decisions tied to the env-local tracing state materialized by the native toggle path instead of rereading live flags in multiple places. Tracing flags can be updated from any thread, so reading them directly during span creation can observe a mid-flight toggle. That could make Tracer.startSpan() return a NonRecordingSpan after the caller had already passed an internal tracing gate, leading to crashes like TypeError: span._pushSpanDataString is not a function. Pass the full tracing bitmask from EnvList::update_tracing_flags() into JS, cache it in lib/internal/nsolid_trace.js, and make the internal OTel code consume that cached per-thread state. This removes the split-brain behavior where callback binding/unbinding followed flagsUpdated but Tracer.startSpan() could independently observe a later disable and return a NonRecordingSpan. For internal spans, stop Tracer.startSpan() from rechecking the current trace flags after the caller has already crossed an internal tracing gate. That keeps internal span creation locally consistent while tracing is being enabled and disabled and avoids crashes in code paths that expect a real N|Solid span object. Also add targeted repro coverage for the toggle race: - extend the tracing test addon with configurable setupTracing(flags), stopTracing(), and skipExpectedTracesCheck() - add a deterministic fetch-based repro that toggles HTTP client tracing while concurrent fetch() traffic is in flight - add a grpc-based repro harness for tracing reconfiguration and cover both fetch and http traffic - teach the grpc agent test client how to generate fetch transactions Together these changes make trace flags materialize per env/thread in JS, preserve the caller's local tracing decision for internal spans, and add regression coverage for the tracing enable/disable race. PR-URL: https://github.com/nodesource/nsolid/pull/441 Reviewed-By: Rafael Gonzaga --- lib/internal/nsolid_trace.js | 16 +++- lib/internal/otel/core.js | 12 +-- lib/internal/otel/trace.js | 5 +- src/nsolid/nsolid_api.cc | 2 +- test/addons/nsolid-tracing/binding.cc | 45 +++++++++-- .../test-otel-fetch-enable-disable-race.js | 75 +++++++++++++++++++ test/agents/test-grpc-tracing-race.mjs | 75 +++++++++++++++++++ test/common/nsolid-grpc-agent/client.js | 35 +++++++++ 8 files changed, 248 insertions(+), 17 deletions(-) create mode 100644 test/addons/nsolid-tracing/test-otel-fetch-enable-disable-race.js create mode 100644 test/agents/test-grpc-tracing-race.mjs diff --git a/lib/internal/nsolid_trace.js b/lib/internal/nsolid_trace.js index e696878108d..fa6125b978c 100644 --- a/lib/internal/nsolid_trace.js +++ b/lib/internal/nsolid_trace.js @@ -19,10 +19,18 @@ const TRACE_ID_PART = '(?![0]{32})[\\da-f]{32}'; const PARENT_ID_PART = '(?![0]{16})[\\da-f]{16}'; const FLAGS_PART = '[\\da-f]{2}'; const TRACE_PARENT_REGEX = new RegExp(`^\\s?(${VERSION_PART})-(${TRACE_ID_PART})-(${PARENT_ID_PART})-(${FLAGS_PART})(-.*)?\\s?$`); +let currentTraceFlags = 0; function generateSpan(type) { - return nsolidApi.traceFlags[0] & type; + return getTraceFlags() & type; +} + +function getTraceFlags() { + if (nsolidApi.traceFlags !== undefined) { + currentTraceFlags = nsolidApi.traceFlags[0]; + } + return currentTraceFlags; } function parseTraceParent(traceParent) { @@ -69,13 +77,15 @@ function extractSpanContextFromHttpHeaders(context, headers) { } const nsolidTracer = new EventEmitter(); -binding.setToggleTracingFn(() => { - nsolidTracer.emit('flagsUpdated'); +binding.setToggleTracingFn((flags) => { + currentTraceFlags = flags; + nsolidTracer.emit('flagsUpdated', flags); }); module.exports = { extractSpanContextFromHttpHeaders, generateSpan, + getTraceFlags, nsolidTracer, }; diff --git a/lib/internal/otel/core.js b/lib/internal/otel/core.js index e6f897c9d11..a0d213b8739 100644 --- a/lib/internal/otel/core.js +++ b/lib/internal/otel/core.js @@ -2,7 +2,6 @@ const { ArrayIsArray } = primordials; -const binding = internalBinding('nsolid_api'); const { codes: { ERR_INVALID_ARG_TYPE, @@ -26,16 +25,16 @@ function register(api) { // TODO(santigimeno): perform some kind of validation that the api is actually // the OTEL api. api_ = api; - const { nsolidTracer } = require('internal/nsolid_trace'); - nsolidTracer.on('flagsUpdated', () => { - if (binding.trace_flags[0]) { + const { getTraceFlags, nsolidTracer } = require('internal/nsolid_trace'); + nsolidTracer.on('flagsUpdated', (flags) => { + if (flags) { enableApi(); } else { disableApi(); } }); - if (binding.trace_flags[0]) { + if (getTraceFlags()) { return enableApi(); } @@ -53,7 +52,8 @@ function registerInstrumentations(instrumentations) { instrumentations); } - if (binding.trace_flags[0]) { + const { getTraceFlags } = require('internal/nsolid_trace'); + if (getTraceFlags()) { enableInsts(instrumentations); } else { disableInsts(instrumentations); diff --git a/lib/internal/otel/trace.js b/lib/internal/otel/trace.js index 4936a943b9f..f54844f8314 100644 --- a/lib/internal/otel/trace.js +++ b/lib/internal/otel/trace.js @@ -13,6 +13,9 @@ const { nsolid_consts } = binding; const { getApi, } = require('internal/otel/core'); +const { + getTraceFlags, +} = require('internal/nsolid_trace'); const { newInternalSpanId, @@ -251,7 +254,7 @@ Span.prototype._pushSpanDataUint64 = function(type, name) { class Tracer { startSpan(name, options = {}, context) { const api = getApi(); - if (binding.trace_flags[0] === 0) { + if (!options.internal && getTraceFlags() === 0) { return api.trace.wrapSpanContext(api.INVALID_SPAN_CONTEXT); } diff --git a/src/nsolid/nsolid_api.cc b/src/nsolid/nsolid_api.cc index 602bb07cfed..49fd368f156 100644 --- a/src/nsolid/nsolid_api.cc +++ b/src/nsolid/nsolid_api.cc @@ -1823,7 +1823,7 @@ void EnvList::update_tracing_flags(SharedEnvInst envinst_sp, uint32_t flags) { HandleScope handle_scope(isolate); Context::Scope context_scope(env->context()); Local argv[] = { - v8::Boolean::New(isolate, flags > 0) + Uint32::New(isolate, flags) }; // We don't care if Call throws or exits. So ignore the return value. diff --git a/test/addons/nsolid-tracing/binding.cc b/test/addons/nsolid-tracing/binding.cc index 442aa193477..b4b2e42a138 100644 --- a/test/addons/nsolid-tracing/binding.cc +++ b/test/addons/nsolid-tracing/binding.cc @@ -117,6 +117,11 @@ class Trace { Tracer* tracer_ = nullptr; std::queue spans_; json expected_traces_ = {}; +bool at_exit_registered_ = false; +bool check_expected_traces_ = true; + +static constexpr uint32_t kDefaultTraceFlags = + kSpanDns | kSpanHttpClient | kSpanHttpServer | kSpanCustom; // NOLINTNEXTLINE(runtime/references) @@ -166,6 +171,10 @@ static void at_exit_cb() { fprintf(stderr, "traces_array: %s\n", traces_array.dump(4).c_str()); // fprintf(stderr, "expected_traces: %s\n", expected_traces_.dump(4).c_str()); + if (!check_expected_traces_) { + return; + } + assert(traces_array.size() == expected_traces_.size()); for (auto i = traces_array.begin(); i != traces_array.end(); ++i) { for (auto j = expected_traces_.begin(); @@ -242,22 +251,46 @@ static void ExpectedTrace(const v8::FunctionCallbackInfo& args) { } static void SetupTracing(const v8::FunctionCallbackInfo& args) { - assert(0 == args.Length()); + assert(args.Length() <= 1); v8::Isolate* isolate = args.GetIsolate(); v8::Local context = isolate->GetCurrentContext(); node::nsolid::SharedEnvInst envinst = node::nsolid::GetLocalEnvInst(context); if (node::nsolid::IsMainThread(envinst)) { - tracer_ = Tracer::CreateInstance(kSpanDns | - kSpanHttpClient | - kSpanHttpServer | - kSpanCustom, got_trace); - atexit(at_exit_cb); + if (tracer_ != nullptr) { + return; + } + + uint32_t flags = kDefaultTraceFlags; + if (args.Length() == 1) { + assert(args[0]->IsUint32()); + flags = args[0].As()->Value(); + } + + tracer_ = Tracer::CreateInstance(flags, got_trace); + if (!at_exit_registered_) { + atexit(at_exit_cb); + at_exit_registered_ = true; + } } } +static void StopTracing(const v8::FunctionCallbackInfo& args) { + assert(0 == args.Length()); + delete tracer_; + tracer_ = nullptr; +} + +static void SkipExpectedTracesCheck( + const v8::FunctionCallbackInfo& args) { + assert(0 == args.Length()); + check_expected_traces_ = false; +} + NODE_MODULE_INIT(/* exports, module, context */) { NODE_SET_METHOD(exports, "expectedTrace", ExpectedTrace); NODE_SET_METHOD(exports, "setupTracing", SetupTracing); + NODE_SET_METHOD(exports, "stopTracing", StopTracing); + NODE_SET_METHOD(exports, "skipExpectedTracesCheck", SkipExpectedTracesCheck); #define V(Name, Val, Str) \ NODE_DEFINE_CONSTANT(exports, Name); NSOLID_SPAN_TYPES(V) diff --git a/test/addons/nsolid-tracing/test-otel-fetch-enable-disable-race.js b/test/addons/nsolid-tracing/test-otel-fetch-enable-disable-race.js new file mode 100644 index 00000000000..e6a203c085a --- /dev/null +++ b/test/addons/nsolid-tracing/test-otel-fetch-enable-disable-race.js @@ -0,0 +1,75 @@ +// Flags: --dns-result-order=ipv4first +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const http = require('http'); +const { once } = require('events'); + +const bindingPath = require.resolve(`./build/${common.buildType}/binding`); +const binding = require(bindingPath); + +const concurrency = 16; +const fetchRounds = 150; +const toggleRounds = 400; + +async function main() { + binding.skipExpectedTracesCheck(); + binding.setupTracing(binding.kSpanHttpClient); + + const server = http.createServer((req, res) => { + req.resume(); + res.writeHead(200, { 'content-type': 'text/plain' }); + setImmediate(() => res.end('ok')); + }); + + server.listen(0, '127.0.0.1'); + await once(server, 'listening'); + const { port } = server.address(); + const url = `http://127.0.0.1:${port}/`; + + const failures = []; + + async function runFetches() { + for (let i = 0; i < fetchRounds; i++) { + const batch = Array.from({ length: concurrency }, async () => { + try { + const res = await fetch(url, { + method: 'POST', + body: 'payload', + }); + await res.text(); + } catch (err) { + failures.push(err); + } + }); + await Promise.all(batch); + } + } + + async function toggleTracing() { + for (let i = 0; i < toggleRounds; i++) { + binding.stopTracing(); + await new Promise(setImmediate); + binding.setupTracing(binding.kSpanHttpClient); + await new Promise(setImmediate); + } + } + + try { + await Promise.all([ + runFetches(), + toggleTracing(), + ]); + } finally { + binding.stopTracing(); + server.close(); + await once(server, 'close'); + } + + assert.deepStrictEqual(failures, []); +} + +main().then(common.mustCall()).catch((err) => { + throw err; +}); diff --git a/test/agents/test-grpc-tracing-race.mjs b/test/agents/test-grpc-tracing-race.mjs new file mode 100644 index 00000000000..941ca9d24ae --- /dev/null +++ b/test/agents/test-grpc-tracing-race.mjs @@ -0,0 +1,75 @@ +// Flags: --expose-internals +import { mustCall, mustSucceed } from '../common/index.mjs'; +import { + checkExitData, + GRPCServer, + TestClient, +} from '../common/nsolid-grpc-agent/index.js'; + +const traceBursts = 100; +const toggleRounds = 20; + +async function runRepro(getEnv, kind) { + return new Promise((resolve, reject) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + + const client = new TestClient([], opts); + const agentId = await client.id(); + + grpcServer.on('exit', mustCall((data) => { + checkExitData(data.msg, data.metadata, agentId, { code: 0, error: null, profile: '' }); + grpcServer.close(); + resolve(); + })); + + // send lots of trace requests to trigger tracing + for (let i = 0; i < traceBursts; i++) { + client.tracing(kind, 0); + } + + // toggle tracing on and off + for (let i = 0; i < toggleRounds; i++) { + const enabled = (i % 2) !== 0; + await grpcServer.reconfigure(agentId, { tracingEnabled: enabled }); + } + + await client.shutdown(); + })); + }); +} + +const tests = []; +tests.push({ + name: 'should reproduce fetch tracing crash via grpc reconfigure', + test: async (getEnv) => runRepro(getEnv, 'fetch'), +}); + +tests.push({ + name: 'should reproduce http tracing crash via grpc reconfigure', + test: async (getEnv) => runRepro(getEnv, 'http'), +}); + +const testConfigs = [ + { + getEnv: (port) => ({ + NODE_DEBUG_NATIVE: 'nsolid_grpc_agent', + NSOLID_GRPC_INSECURE: 1, + NSOLID_GRPC: `localhost:${port}`, + NSOLID_TRACING_ENABLED: 1, + NSOLID_INTERVAL: 100000, + }), + }, +]; + +for (const testConfig of testConfigs) { + for (const { name, test } of tests) { + console.log(`[tracing] ${name}`); + await test(testConfig.getEnv); + } +} diff --git a/test/common/nsolid-grpc-agent/client.js b/test/common/nsolid-grpc-agent/client.js index f094e528b91..5acca808308 100644 --- a/test/common/nsolid-grpc-agent/client.js +++ b/test/common/nsolid-grpc-agent/client.js @@ -46,6 +46,33 @@ function execHttpTransaction() { }); } +function execFetchTransaction() { + const server = http.createServer((req, res) => { + req.resume(); + setTimeout(() => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('Hello World\n'); + }, 10); + }); + + server.listen(0, '127.0.0.1', async () => { + const port = server.address().port; + const url = `http://127.0.0.1:${port}/`; + for (let i = 0; i < 25; i++) { + const responses = await Promise.all(Array.from({ length: 32 }, () => { + return fetch(url, { + method: 'POST', + body: 'payload', + }); + })); + + await Promise.all(responses.map((response) => response.text())); + } + + server.close(); + }); +} + function execDnsTransaction() { const dns = require('node:dns'); dns.lookup('example.org', () => { @@ -97,6 +124,9 @@ function handleTrace(msg) { case 'http': execHttpTransaction(); break; + case 'fetch': + execFetchTransaction(); + break; case 'dns': execDnsTransaction(); break; @@ -217,6 +247,11 @@ if (isMainThread) { // immediately without the need of calling nsolid.start() nsolid.start(); execHttpTransaction(); + } else if (trace === 'fetch') { + // TODO(santigimeno): ideally we should be able to collect traces + // immediately without the need of calling nsolid.start() + nsolid.start(); + execFetchTransaction(); } else if (trace === 'dns') { execDnsTransaction(); } else if (trace === 'custom') { From ecb4ee0f10e6839fd94f9ab7b1c6ec6d9db2a97c Mon Sep 17 00:00:00 2001 From: Santiago Gimeno Date: Thu, 19 Mar 2026 17:25:46 +0100 Subject: [PATCH 4/8] test: fix linting in test-nsolid-file-handle-count PR-URL: https://github.com/nodesource/nsolid/pull/441 Reviewed-By: Rafael Gonzaga --- test/parallel/test-nsolid-file-handle-count.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/parallel/test-nsolid-file-handle-count.js b/test/parallel/test-nsolid-file-handle-count.js index d5106933c61..61cae057cba 100644 --- a/test/parallel/test-nsolid-file-handle-count.js +++ b/test/parallel/test-nsolid-file-handle-count.js @@ -56,8 +56,7 @@ fs.readFile(__filename, () => { assert.strictEqual(getClosed(), ++cCntr); checkPromise() - .then(common.mustCall(closePromiseFd)) - .catch(common.mustNotCall()); + .then(common.mustCall((fh) => closePromiseFd(fh))); })); })); }); From 2048c1743326a7115f105cf0bfd7eb61b94f989d Mon Sep 17 00:00:00 2001 From: Santiago Gimeno Date: Wed, 22 Apr 2026 17:42:29 +0200 Subject: [PATCH 5/8] lib: fix JS linting issues --- test/agents/test-grpc-tracing-race.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/agents/test-grpc-tracing-race.mjs b/test/agents/test-grpc-tracing-race.mjs index 941ca9d24ae..340f0b10bb2 100644 --- a/test/agents/test-grpc-tracing-race.mjs +++ b/test/agents/test-grpc-tracing-race.mjs @@ -28,12 +28,12 @@ async function runRepro(getEnv, kind) { resolve(); })); - // send lots of trace requests to trigger tracing + // Send lots of trace requests to trigger tracing for (let i = 0; i < traceBursts; i++) { client.tracing(kind, 0); } - // toggle tracing on and off + // Toggle tracing on and off for (let i = 0; i < toggleRounds; i++) { const enabled = (i % 2) !== 0; await grpcServer.reconfigure(agentId, { tracingEnabled: enabled }); From 7d50e4c8989efdf2296a4026b385c25c21506a44 Mon Sep 17 00:00:00 2001 From: Santiago Gimeno Date: Wed, 29 Apr 2026 15:48:48 +0200 Subject: [PATCH 6/8] deps: brace-expansion@2.0.3 --- deps/npm/node_modules/brace-expansion/index.js | 3 +-- deps/npm/node_modules/brace-expansion/package.json | 2 +- deps/npm/package.json | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/deps/npm/node_modules/brace-expansion/index.js b/deps/npm/node_modules/brace-expansion/index.js index 254ca75dd9a..d5959a953f8 100644 --- a/deps/npm/node_modules/brace-expansion/index.js +++ b/deps/npm/node_modules/brace-expansion/index.js @@ -148,7 +148,7 @@ function expand(str, isTop) { var y = numeric(n[1]); var width = Math.max(n[0].length, n[1].length) var incr = n.length == 3 - ? Math.abs(numeric(n[2])) + ? Math.max(Math.abs(numeric(n[2])), 1) : 1; var test = lte; var reverse = y < x; @@ -200,4 +200,3 @@ function expand(str, isTop) { return expansions; } - diff --git a/deps/npm/node_modules/brace-expansion/package.json b/deps/npm/node_modules/brace-expansion/package.json index c7eee345110..e3413ae7c1e 100644 --- a/deps/npm/node_modules/brace-expansion/package.json +++ b/deps/npm/node_modules/brace-expansion/package.json @@ -1,7 +1,7 @@ { "name": "brace-expansion", "description": "Brace expansion as known from sh/bash", - "version": "2.0.2", + "version": "2.0.3", "repository": { "type": "git", "url": "git://github.com/juliangruber/brace-expansion.git" diff --git a/deps/npm/package.json b/deps/npm/package.json index 476e999d42c..98c46f5b197 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -63,6 +63,7 @@ "@sigstore/tuf": "^3.1.1", "abbrev": "^3.0.1", "archy": "~1.0.0", + "brace-expansion": "^2.0.3", "cacache": "^19.0.1", "chalk": "^5.6.2", "ci-info": "^4.4.0", @@ -133,6 +134,7 @@ "@sigstore/tuf", "abbrev", "archy", + "brace-expansion", "cacache", "chalk", "ci-info", From 2ccd85d59c1613ce5b573db8881624bb852f3ff3 Mon Sep 17 00:00:00 2001 From: Santiago Gimeno Date: Wed, 29 Apr 2026 15:45:35 +0200 Subject: [PATCH 7/8] deps: tinyglobby@0.2.16 --- .../node_modules/picomatch/lib/constants.js | 4 + deps/npm/node_modules/picomatch/lib/parse.js | 301 ++++++++++++++++++ .../node_modules/picomatch/lib/picomatch.js | 14 +- deps/npm/node_modules/picomatch/package.json | 5 +- .../node_modules/tinyglobby/dist/index.cjs | 280 ++++++++-------- .../node_modules/tinyglobby/dist/index.d.cts | 65 ++-- .../node_modules/tinyglobby/dist/index.d.mts | 65 ++-- .../node_modules/tinyglobby/dist/index.mjs | 280 ++++++++-------- deps/npm/node_modules/tinyglobby/package.json | 20 +- deps/npm/package.json | 2 + 10 files changed, 662 insertions(+), 374 deletions(-) diff --git a/deps/npm/node_modules/picomatch/lib/constants.js b/deps/npm/node_modules/picomatch/lib/constants.js index 3f7ef7e53ad..f0aeda7d481 100644 --- a/deps/npm/node_modules/picomatch/lib/constants.js +++ b/deps/npm/node_modules/picomatch/lib/constants.js @@ -3,6 +3,8 @@ const WIN_SLASH = '\\\\/'; const WIN_NO_SLASH = `[^${WIN_SLASH}]`; +const DEFAULT_MAX_EXTGLOB_RECURSION = 0; + /** * Posix glob regex */ @@ -69,6 +71,7 @@ const WINDOWS_CHARS = { */ const POSIX_REGEX_SOURCE = { + __proto__: null, alnum: 'a-zA-Z0-9', alpha: 'a-zA-Z', ascii: '\\x00-\\x7F', @@ -86,6 +89,7 @@ const POSIX_REGEX_SOURCE = { }; module.exports = { + DEFAULT_MAX_EXTGLOB_RECURSION, MAX_LENGTH: 1024 * 64, POSIX_REGEX_SOURCE, diff --git a/deps/npm/node_modules/picomatch/lib/parse.js b/deps/npm/node_modules/picomatch/lib/parse.js index 8fd8ff499d1..57d994a8789 100644 --- a/deps/npm/node_modules/picomatch/lib/parse.js +++ b/deps/npm/node_modules/picomatch/lib/parse.js @@ -45,6 +45,277 @@ const syntaxError = (type, char) => { return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; }; +const splitTopLevel = input => { + const parts = []; + let bracket = 0; + let paren = 0; + let quote = 0; + let value = ''; + let escaped = false; + + for (const ch of input) { + if (escaped === true) { + value += ch; + escaped = false; + continue; + } + + if (ch === '\\') { + value += ch; + escaped = true; + continue; + } + + if (ch === '"') { + quote = quote === 1 ? 0 : 1; + value += ch; + continue; + } + + if (quote === 0) { + if (ch === '[') { + bracket++; + } else if (ch === ']' && bracket > 0) { + bracket--; + } else if (bracket === 0) { + if (ch === '(') { + paren++; + } else if (ch === ')' && paren > 0) { + paren--; + } else if (ch === '|' && paren === 0) { + parts.push(value); + value = ''; + continue; + } + } + } + + value += ch; + } + + parts.push(value); + return parts; +}; + +const isPlainBranch = branch => { + let escaped = false; + + for (const ch of branch) { + if (escaped === true) { + escaped = false; + continue; + } + + if (ch === '\\') { + escaped = true; + continue; + } + + if (/[?*+@!()[\]{}]/.test(ch)) { + return false; + } + } + + return true; +}; + +const normalizeSimpleBranch = branch => { + let value = branch.trim(); + let changed = true; + + while (changed === true) { + changed = false; + + if (/^@\([^\\()[\]{}|]+\)$/.test(value)) { + value = value.slice(2, -1); + changed = true; + } + } + + if (!isPlainBranch(value)) { + return; + } + + return value.replace(/\\(.)/g, '$1'); +}; + +const hasRepeatedCharPrefixOverlap = branches => { + const values = branches.map(normalizeSimpleBranch).filter(Boolean); + + for (let i = 0; i < values.length; i++) { + for (let j = i + 1; j < values.length; j++) { + const a = values[i]; + const b = values[j]; + const char = a[0]; + + if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) { + continue; + } + + if (a === b || a.startsWith(b) || b.startsWith(a)) { + return true; + } + } + } + + return false; +}; + +const parseRepeatedExtglob = (pattern, requireEnd = true) => { + if ((pattern[0] !== '+' && pattern[0] !== '*') || pattern[1] !== '(') { + return; + } + + let bracket = 0; + let paren = 0; + let quote = 0; + let escaped = false; + + for (let i = 1; i < pattern.length; i++) { + const ch = pattern[i]; + + if (escaped === true) { + escaped = false; + continue; + } + + if (ch === '\\') { + escaped = true; + continue; + } + + if (ch === '"') { + quote = quote === 1 ? 0 : 1; + continue; + } + + if (quote === 1) { + continue; + } + + if (ch === '[') { + bracket++; + continue; + } + + if (ch === ']' && bracket > 0) { + bracket--; + continue; + } + + if (bracket > 0) { + continue; + } + + if (ch === '(') { + paren++; + continue; + } + + if (ch === ')') { + paren--; + + if (paren === 0) { + if (requireEnd === true && i !== pattern.length - 1) { + return; + } + + return { + type: pattern[0], + body: pattern.slice(2, i), + end: i + }; + } + } + } +}; + +const getStarExtglobSequenceOutput = pattern => { + let index = 0; + const chars = []; + + while (index < pattern.length) { + const match = parseRepeatedExtglob(pattern.slice(index), false); + + if (!match || match.type !== '*') { + return; + } + + const branches = splitTopLevel(match.body).map(branch => branch.trim()); + if (branches.length !== 1) { + return; + } + + const branch = normalizeSimpleBranch(branches[0]); + if (!branch || branch.length !== 1) { + return; + } + + chars.push(branch); + index += match.end + 1; + } + + if (chars.length < 1) { + return; + } + + const source = chars.length === 1 + ? utils.escapeRegex(chars[0]) + : `[${chars.map(ch => utils.escapeRegex(ch)).join('')}]`; + + return `${source}*`; +}; + +const repeatedExtglobRecursion = pattern => { + let depth = 0; + let value = pattern.trim(); + let match = parseRepeatedExtglob(value); + + while (match) { + depth++; + value = match.body.trim(); + match = parseRepeatedExtglob(value); + } + + return depth; +}; + +const analyzeRepeatedExtglob = (body, options) => { + if (options.maxExtglobRecursion === false) { + return { risky: false }; + } + + const max = + typeof options.maxExtglobRecursion === 'number' + ? options.maxExtglobRecursion + : constants.DEFAULT_MAX_EXTGLOB_RECURSION; + + const branches = splitTopLevel(body).map(branch => branch.trim()); + + if (branches.length > 1) { + if ( + branches.some(branch => branch === '') || + branches.some(branch => /^[*?]+$/.test(branch)) || + hasRepeatedCharPrefixOverlap(branches) + ) { + return { risky: true }; + } + } + + for (const branch of branches) { + const safeOutput = getStarExtglobSequenceOutput(branch); + if (safeOutput) { + return { risky: true, safeOutput }; + } + + if (repeatedExtglobRecursion(branch) > max) { + return { risky: true }; + } + } + + return { risky: false }; +}; + /** * Parse the given input string. * @param {String} input @@ -225,6 +496,8 @@ const parse = (input, options) => { token.prev = prev; token.parens = state.parens; token.output = state.output; + token.startIndex = state.index; + token.tokensIndex = tokens.length; const output = (opts.capture ? '(' : '') + token.open; increment('parens'); @@ -234,6 +507,34 @@ const parse = (input, options) => { }; const extglobClose = token => { + const literal = input.slice(token.startIndex, state.index + 1); + const body = input.slice(token.startIndex + 2, state.index); + const analysis = analyzeRepeatedExtglob(body, opts); + + if ((token.type === 'plus' || token.type === 'star') && analysis.risky) { + const safeOutput = analysis.safeOutput + ? (token.output ? '' : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) + : undefined; + const open = tokens[token.tokensIndex]; + + open.type = 'text'; + open.value = literal; + open.output = safeOutput || utils.escapeRegex(literal); + + for (let i = token.tokensIndex + 1; i < tokens.length; i++) { + tokens[i].value = ''; + tokens[i].output = ''; + delete tokens[i].suffix; + } + + state.output = token.output + open.output; + state.backtrack = true; + + push({ type: 'paren', extglob: true, value, output: '' }); + decrement('parens'); + return; + } + let output = token.close + (opts.capture ? ')' : ''); let rest; diff --git a/deps/npm/node_modules/picomatch/lib/picomatch.js b/deps/npm/node_modules/picomatch/lib/picomatch.js index d0ebd9f163c..fbb8b1ca9f1 100644 --- a/deps/npm/node_modules/picomatch/lib/picomatch.js +++ b/deps/npm/node_modules/picomatch/lib/picomatch.js @@ -233,6 +233,14 @@ picomatch.scan = (input, options) => scan(input, options); * Compile a regular expression from the `state` object returned by the * [parse()](#parse) method. * + * ```js + * const picomatch = require('picomatch'); + * const state = picomatch.parse('*.js'); + * // picomatch.compileRe(state[, options]); + * + * console.log(picomatch.compileRe(state)); + * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ + * ``` * @param {Object} `state` * @param {Object} `options` * @param {Boolean} `returnOutput` Intended for implementors, this argument allows you to return the raw output from the parser. @@ -268,10 +276,10 @@ picomatch.compileRe = (state, options, returnOutput = false, returnState = false * * ```js * const picomatch = require('picomatch'); - * const state = picomatch.parse('*.js'); - * // picomatch.compileRe(state[, options]); + * // picomatch.makeRe(state[, options]); * - * console.log(picomatch.compileRe(state)); + * const result = picomatch.makeRe('*.js'); + * console.log(result); * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ * ``` * @param {String} `state` The object returned from the `.parse` method. diff --git a/deps/npm/node_modules/picomatch/package.json b/deps/npm/node_modules/picomatch/package.json index 372e27e05f4..9151f1d8358 100644 --- a/deps/npm/node_modules/picomatch/package.json +++ b/deps/npm/node_modules/picomatch/package.json @@ -1,7 +1,7 @@ { "name": "picomatch", "description": "Blazing fast and accurate glob matcher written in JavaScript, with no dependencies and full support for standard and extended Bash glob features, including braces, extglobs, POSIX brackets, and regular expressions.", - "version": "4.0.3", + "version": "4.0.4", "homepage": "https://github.com/micromatch/picomatch", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "funding": "https://github.com/sponsors/jonschlinkert", @@ -32,8 +32,7 @@ "fill-range": "^7.0.1", "gulp-format-md": "^2.0.0", "mocha": "^10.4.0", - "nyc": "^15.1.0", - "time-require": "github:jonschlinkert/time-require" + "nyc": "^15.1.0" }, "keywords": [ "glob", diff --git a/deps/npm/node_modules/tinyglobby/dist/index.cjs b/deps/npm/node_modules/tinyglobby/dist/index.cjs index e5cb03ccec9..5badacb548b 100644 --- a/deps/npm/node_modules/tinyglobby/dist/index.cjs +++ b/deps/npm/node_modules/tinyglobby/dist/index.cjs @@ -1,4 +1,5 @@ -//#region rolldown:runtime +Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); +//#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; @@ -19,51 +20,45 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge value: mod, enumerable: true }) : target, mod)); - //#endregion let fs = require("fs"); -fs = __toESM(fs); let path = require("path"); -path = __toESM(path); let url = require("url"); -url = __toESM(url); let fdir = require("fdir"); -fdir = __toESM(fdir); let picomatch = require("picomatch"); picomatch = __toESM(picomatch); - //#region src/utils.ts const isReadonlyArray = Array.isArray; +const BACKSLASHES = /\\/g; const isWin = process.platform === "win32"; const ONLY_PARENT_DIRECTORIES = /^(\/?\.\.)+$/; function getPartialMatcher(patterns, options = {}) { const patternsCount = patterns.length; const patternsParts = Array(patternsCount); const matchers = Array(patternsCount); - const globstarEnabled = !options.noglobstar; - for (let i = 0; i < patternsCount; i++) { + let i, j; + for (i = 0; i < patternsCount; i++) { const parts = splitPattern(patterns[i]); patternsParts[i] = parts; const partsCount = parts.length; const partMatchers = Array(partsCount); - for (let j = 0; j < partsCount; j++) partMatchers[j] = (0, picomatch.default)(parts[j], options); + for (j = 0; j < partsCount; j++) partMatchers[j] = (0, picomatch.default)(parts[j], options); matchers[i] = partMatchers; } return (input) => { const inputParts = input.split("/"); if (inputParts[0] === ".." && ONLY_PARENT_DIRECTORIES.test(input)) return true; - for (let i = 0; i < patterns.length; i++) { + for (i = 0; i < patternsCount; i++) { const patternParts = patternsParts[i]; const matcher = matchers[i]; const inputPatternCount = inputParts.length; const minParts = Math.min(inputPatternCount, patternParts.length); - let j = 0; + j = 0; while (j < minParts) { const part = patternParts[j]; if (part.includes("/")) return true; - const match = matcher[j](inputParts[j]); - if (!match) break; - if (globstarEnabled && part === "**") return true; + if (!matcher[j](inputParts[j])) break; + if (!options.noglobstar && part === "**") return true; j++; } if (j === inputPatternCount) return true; @@ -77,7 +72,7 @@ const isRoot = isWin ? (p) => WIN32_ROOT_DIR.test(p) : (p) => p === "/"; function buildFormat(cwd, root, absolute) { if (cwd === root || root.startsWith(`${cwd}/`)) { if (absolute) { - const start = isRoot(cwd) ? cwd.length : cwd.length + 1; + const start = cwd.length + +!isRoot(cwd); return (p, isDir) => p.slice(start, isDir ? -1 : void 0) || "."; } const prefix = root.slice(cwd.length + 1); @@ -98,22 +93,21 @@ function buildRelative(cwd, root) { } return (p) => { const result = path.posix.relative(cwd, `${root}/${p}`); - if (p.endsWith("/") && result !== "") return `${result}/`; - return result || "."; + return p[p.length - 1] === "/" && result !== "" ? `${result}/` : result || "."; }; } const splitPatternOptions = { parts: true }; -function splitPattern(path$2) { +function splitPattern(path$1) { var _result$parts; - const result = picomatch.default.scan(path$2, splitPatternOptions); - return ((_result$parts = result.parts) === null || _result$parts === void 0 ? void 0 : _result$parts.length) ? result.parts : [path$2]; + const result = picomatch.default.scan(path$1, splitPatternOptions); + return ((_result$parts = result.parts) === null || _result$parts === void 0 ? void 0 : _result$parts.length) ? result.parts : [path$1]; } const ESCAPED_WIN32_BACKSLASHES = /\\(?![()[\]{}!+@])/g; function convertPosixPathToPattern(path$2) { return escapePosixPath(path$2); } -function convertWin32PathToPattern(path$2) { - return escapeWin32Path(path$2).replace(ESCAPED_WIN32_BACKSLASHES, "/"); +function convertWin32PathToPattern(path$3) { + return escapeWin32Path(path$3).replace(ESCAPED_WIN32_BACKSLASHES, "/"); } /** * Converts a path to a pattern depending on the platform. @@ -124,8 +118,8 @@ function convertWin32PathToPattern(path$2) { const convertPathToPattern = isWin ? convertWin32PathToPattern : convertPosixPathToPattern; const POSIX_UNESCAPED_GLOB_SYMBOLS = /(? path$2.replace(POSIX_UNESCAPED_GLOB_SYMBOLS, "\\$&"); -const escapeWin32Path = (path$2) => path$2.replace(WIN32_UNESCAPED_GLOB_SYMBOLS, "\\$&"); +const escapePosixPath = (path$4) => path$4.replace(POSIX_UNESCAPED_GLOB_SYMBOLS, "\\$&"); +const escapeWin32Path = (path$5) => path$5.replace(WIN32_UNESCAPED_GLOB_SYMBOLS, "\\$&"); /** * Escapes a path's special characters depending on the platform. * @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath} @@ -152,31 +146,33 @@ function isDynamicPattern(pattern, options) { function log(...tasks) { console.log(`[tinyglobby ${(/* @__PURE__ */ new Date()).toLocaleTimeString("es")}]`, ...tasks); } - +function ensureStringArray(value) { + return typeof value === "string" ? [value] : value !== null && value !== void 0 ? value : []; +} //#endregion -//#region src/index.ts +//#region src/patterns.ts const PARENT_DIRECTORY = /^(\/?\.\.)+/; const ESCAPING_BACKSLASHES = /\\(?=[()[\]{}!*+?@|])/g; -const BACKSLASHES = /\\/g; -function normalizePattern(pattern, expandDirectories, cwd, props, isIgnore) { +function normalizePattern(pattern, opts, props, isIgnore) { + var _PARENT_DIRECTORY$exe; + const cwd = opts.cwd; let result = pattern; - if (pattern.endsWith("/")) result = pattern.slice(0, -1); - if (!result.endsWith("*") && expandDirectories) result += "/**"; + if (pattern[pattern.length - 1] === "/") result = pattern.slice(0, -1); + if (result[result.length - 1] !== "*" && opts.expandDirectories) result += "/**"; const escapedCwd = escapePath(cwd); - if (path.default.isAbsolute(result.replace(ESCAPING_BACKSLASHES, ""))) result = path.posix.relative(escapedCwd, result); - else result = path.posix.normalize(result); - const parentDirectoryMatch = PARENT_DIRECTORY.exec(result); + result = (0, path.isAbsolute)(result.replace(ESCAPING_BACKSLASHES, "")) ? path.posix.relative(escapedCwd, result) : path.posix.normalize(result); + const parentDir = (_PARENT_DIRECTORY$exe = PARENT_DIRECTORY.exec(result)) === null || _PARENT_DIRECTORY$exe === void 0 ? void 0 : _PARENT_DIRECTORY$exe[0]; const parts = splitPattern(result); - if (parentDirectoryMatch === null || parentDirectoryMatch === void 0 ? void 0 : parentDirectoryMatch[0]) { - const n = (parentDirectoryMatch[0].length + 1) / 3; + if (parentDir) { + const n = (parentDir.length + 1) / 3; let i = 0; const cwdParts = escapedCwd.split("/"); while (i < n && parts[i + n] === cwdParts[cwdParts.length + i - n]) { result = result.slice(0, (n - i - 1) * 3) + result.slice((n - i) * 3 + parts[i + n].length + 1) || "."; i++; } - const potentialRoot = path.posix.join(cwd, parentDirectoryMatch[0].slice(i * 3)); - if (!potentialRoot.startsWith(".") && props.root.length > potentialRoot.length) { + const potentialRoot = path.posix.join(cwd, parentDir.slice(i * 3)); + if (potentialRoot[0] !== "." && props.root.length > potentialRoot.length) { props.root = potentialRoot; props.depthOffset = -n + i; } @@ -192,7 +188,7 @@ function normalizePattern(pattern, expandDirectories, cwd, props, isIgnore) { newCommonPath.pop(); break; } - if (part !== props.commonPath[i] || isDynamicPattern(part) || i === parts.length - 1) break; + if (i === parts.length - 1 || part !== props.commonPath[i] || isDynamicPattern(part)) break; newCommonPath.push(part); } props.depthOffset = newCommonPath.length; @@ -201,150 +197,138 @@ function normalizePattern(pattern, expandDirectories, cwd, props, isIgnore) { } return result; } -function processPatterns({ patterns = ["**/*"], ignore = [], expandDirectories = true }, cwd, props) { - if (typeof patterns === "string") patterns = [patterns]; - if (typeof ignore === "string") ignore = [ignore]; +function processPatterns(options, patterns, props) { const matchPatterns = []; const ignorePatterns = []; - for (const pattern of ignore) { + for (const pattern of options.ignore) { if (!pattern) continue; - if (pattern[0] !== "!" || pattern[1] === "(") ignorePatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, true)); + if (pattern[0] !== "!" || pattern[1] === "(") ignorePatterns.push(normalizePattern(pattern, options, props, true)); } for (const pattern of patterns) { if (!pattern) continue; - if (pattern[0] !== "!" || pattern[1] === "(") matchPatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, false)); - else if (pattern[1] !== "!" || pattern[2] === "(") ignorePatterns.push(normalizePattern(pattern.slice(1), expandDirectories, cwd, props, true)); + if (pattern[0] !== "!" || pattern[1] === "(") matchPatterns.push(normalizePattern(pattern, options, props, false)); + else if (pattern[1] !== "!" || pattern[2] === "(") ignorePatterns.push(normalizePattern(pattern.slice(1), options, props, true)); } return { match: matchPatterns, ignore: ignorePatterns }; } -function formatPaths(paths, relative) { - for (let i = paths.length - 1; i >= 0; i--) { - const path$2 = paths[i]; - paths[i] = relative(path$2); - } - return paths; -} -function normalizeCwd(cwd) { - if (!cwd) return process.cwd().replace(BACKSLASHES, "/"); - if (cwd instanceof URL) return (0, url.fileURLToPath)(cwd).replace(BACKSLASHES, "/"); - return path.default.resolve(cwd).replace(BACKSLASHES, "/"); -} -function getCrawler(patterns, inputOptions = {}) { - const options = process.env.TINYGLOBBY_DEBUG ? { - ...inputOptions, - debug: true - } : inputOptions; - const cwd = normalizeCwd(options.cwd); - if (options.debug) log("globbing with:", { - patterns, - options, - cwd - }); - if (Array.isArray(patterns) && patterns.length === 0) return [{ - sync: () => [], - withPromise: async () => [] - }, false]; +//#endregion +//#region src/crawler.ts +function buildCrawler(options, patterns) { + const cwd = options.cwd; const props = { root: cwd, - commonPath: null, depthOffset: 0 }; - const processed = processPatterns({ - ...options, - patterns - }, cwd, props); + const processed = processPatterns(options, patterns, props); if (options.debug) log("internal processing patterns:", processed); + const { absolute, caseSensitiveMatch, debug, dot, followSymbolicLinks, onlyDirectories } = options; + const root = props.root.replace(BACKSLASHES, ""); const matchOptions = { - dot: options.dot, + dot, nobrace: options.braceExpansion === false, - nocase: options.caseSensitiveMatch === false, + nocase: !caseSensitiveMatch, noextglob: options.extglob === false, noglobstar: options.globstar === false, posix: true }; - const matcher = (0, picomatch.default)(processed.match, { - ...matchOptions, - ignore: processed.ignore - }); + const matcher = (0, picomatch.default)(processed.match, matchOptions); const ignore = (0, picomatch.default)(processed.ignore, matchOptions); const partialMatcher = getPartialMatcher(processed.match, matchOptions); - const format = buildFormat(cwd, props.root, options.absolute); - const formatExclude = options.absolute ? format : buildFormat(cwd, props.root, true); - const fdirOptions = { - filters: [options.debug ? (p, isDirectory) => { - const path$2 = format(p, isDirectory); - const matches = matcher(path$2); - if (matches) log(`matched ${path$2}`); + const format = buildFormat(cwd, root, absolute); + const excludeFormatter = absolute ? format : buildFormat(cwd, root, true); + const excludePredicate = (_, p) => { + const relativePath = excludeFormatter(p, true); + return relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath); + }; + let maxDepth; + if (options.deep !== void 0) maxDepth = Math.round(options.deep - props.depthOffset); + const crawler = new fdir.fdir({ + filters: [debug ? (p, isDirectory) => { + const path = format(p, isDirectory); + const matches = matcher(path) && !ignore(path); + if (matches) log(`matched ${path}`); return matches; - } : (p, isDirectory) => matcher(format(p, isDirectory))], - exclude: options.debug ? (_, p) => { - const relativePath = formatExclude(p, true); - const skipped = relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath); - if (skipped) log(`skipped ${p}`); - else log(`crawling ${p}`); + } : (p, isDirectory) => { + const path = format(p, isDirectory); + return matcher(path) && !ignore(path); + }], + exclude: debug ? (_, p) => { + const skipped = excludePredicate(_, p); + log(`${skipped ? "skipped" : "crawling"} ${p}`); return skipped; - } : (_, p) => { - const relativePath = formatExclude(p, true); - return relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath); - }, - fs: options.fs ? { - readdir: options.fs.readdir || fs.default.readdir, - readdirSync: options.fs.readdirSync || fs.default.readdirSync, - realpath: options.fs.realpath || fs.default.realpath, - realpathSync: options.fs.realpathSync || fs.default.realpathSync, - stat: options.fs.stat || fs.default.stat, - statSync: options.fs.statSync || fs.default.statSync - } : void 0, + } : excludePredicate, + fs: options.fs, pathSeparator: "/", - relativePaths: true, - resolveSymlinks: true, + relativePaths: !absolute, + resolvePaths: absolute, + includeBasePath: absolute, + resolveSymlinks: followSymbolicLinks, + excludeSymlinks: !followSymbolicLinks, + excludeFiles: onlyDirectories, + includeDirs: onlyDirectories || !options.onlyFiles, + maxDepth, signal: options.signal + }).crawl(root); + if (options.debug) log("internal properties:", { + ...props, + root + }); + return [crawler, cwd !== root && !absolute && buildRelative(cwd, root)]; +} +//#endregion +//#region src/index.ts +function formatPaths(paths, mapper) { + if (mapper) for (let i = paths.length - 1; i >= 0; i--) paths[i] = mapper(paths[i]); + return paths; +} +const defaultOptions = { + caseSensitiveMatch: true, + cwd: process.cwd(), + debug: !!process.env.TINYGLOBBY_DEBUG, + expandDirectories: true, + followSymbolicLinks: true, + onlyFiles: true +}; +function getOptions(options) { + const opts = { + ...defaultOptions, + ...options }; - if (options.deep !== void 0) fdirOptions.maxDepth = Math.round(options.deep - props.depthOffset); - if (options.absolute) { - fdirOptions.relativePaths = false; - fdirOptions.resolvePaths = true; - fdirOptions.includeBasePath = true; - } - if (options.followSymbolicLinks === false) { - fdirOptions.resolveSymlinks = false; - fdirOptions.excludeSymlinks = true; - } - if (options.onlyDirectories) { - fdirOptions.excludeFiles = true; - fdirOptions.includeDirs = true; - } else if (options.onlyFiles === false) fdirOptions.includeDirs = true; - props.root = props.root.replace(BACKSLASHES, ""); - const root = props.root; - if (options.debug) log("internal properties:", props); - const relative = cwd !== root && !options.absolute && buildRelative(cwd, props.root); - return [new fdir.fdir(fdirOptions).crawl(root), relative]; + opts.cwd = (opts.cwd instanceof URL ? (0, url.fileURLToPath)(opts.cwd) : (0, path.resolve)(opts.cwd)).replace(BACKSLASHES, "/"); + opts.ignore = ensureStringArray(opts.ignore); + opts.fs && (opts.fs = { + readdir: opts.fs.readdir || fs.readdir, + readdirSync: opts.fs.readdirSync || fs.readdirSync, + realpath: opts.fs.realpath || fs.realpath, + realpathSync: opts.fs.realpathSync || fs.realpathSync, + stat: opts.fs.stat || fs.stat, + statSync: opts.fs.statSync || fs.statSync + }); + if (opts.debug) log("globbing with options:", opts); + return opts; +} +function getCrawler(globInput, inputOptions = {}) { + var _ref; + if (globInput && (inputOptions === null || inputOptions === void 0 ? void 0 : inputOptions.patterns)) throw new Error("Cannot pass patterns as both an argument and an option"); + const isModern = isReadonlyArray(globInput) || typeof globInput === "string"; + const patterns = ensureStringArray((_ref = isModern ? globInput : globInput.patterns) !== null && _ref !== void 0 ? _ref : "**/*"); + const options = getOptions(isModern ? inputOptions : globInput); + return patterns.length > 0 ? buildCrawler(options, patterns) : []; } -async function glob(patternsOrOptions, options) { - if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option"); - const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string"; - const opts = isModern ? options : patternsOrOptions; - const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns; - const [crawler, relative] = getCrawler(patterns, opts); - if (!relative) return crawler.withPromise(); - return formatPaths(await crawler.withPromise(), relative); +async function glob(globInput, options) { + const [crawler, relative] = getCrawler(globInput, options); + return crawler ? formatPaths(await crawler.withPromise(), relative) : []; } -function globSync(patternsOrOptions, options) { - if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option"); - const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string"; - const opts = isModern ? options : patternsOrOptions; - const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns; - const [crawler, relative] = getCrawler(patterns, opts); - if (!relative) return crawler.sync(); - return formatPaths(crawler.sync(), relative); +function globSync(globInput, options) { + const [crawler, relative] = getCrawler(globInput, options); + return crawler ? formatPaths(crawler.sync(), relative) : []; } - //#endregion exports.convertPathToPattern = convertPathToPattern; exports.escapePath = escapePath; exports.glob = glob; exports.globSync = globSync; -exports.isDynamicPattern = isDynamicPattern; \ No newline at end of file +exports.isDynamicPattern = isDynamicPattern; diff --git a/deps/npm/node_modules/tinyglobby/dist/index.d.cts b/deps/npm/node_modules/tinyglobby/dist/index.d.cts index 9d67dae260a..3e8e16aa2e9 100644 --- a/deps/npm/node_modules/tinyglobby/dist/index.d.cts +++ b/deps/npm/node_modules/tinyglobby/dist/index.d.cts @@ -1,35 +1,7 @@ import { FSLike } from "fdir"; -//#region src/utils.d.ts - -/** -* Converts a path to a pattern depending on the platform. -* Identical to {@link escapePath} on POSIX systems. -* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern} -*/ -declare const convertPathToPattern: (path: string) => string; -/** -* Escapes a path's special characters depending on the platform. -* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath} -*/ -declare const escapePath: (path: string) => string; -/** -* Checks if a pattern has dynamic parts. -* -* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy: -* -* - Doesn't necessarily return `false` on patterns that include `\`. -* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not. -* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`. -* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`. -* -* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern} -*/ -declare function isDynamicPattern(pattern: string, options?: { - caseSensitiveMatch: boolean; -}): boolean; -//#endregion -//#region src/index.d.ts +//#region src/types.d.ts +type FileSystemAdapter = Partial; interface GlobOptions { /** * Whether to return absolute paths. Disable to have relative paths. @@ -124,7 +96,36 @@ interface GlobOptions { */ signal?: AbortSignal; } -type FileSystemAdapter = Partial; +//#endregion +//#region src/utils.d.ts +/** +* Converts a path to a pattern depending on the platform. +* Identical to {@link escapePath} on POSIX systems. +* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern} +*/ +declare const convertPathToPattern: (path: string) => string; +/** +* Escapes a path's special characters depending on the platform. +* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath} +*/ +declare const escapePath: (path: string) => string; +/** +* Checks if a pattern has dynamic parts. +* +* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy: +* +* - Doesn't necessarily return `false` on patterns that include `\`. +* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not. +* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`. +* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`. +* +* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern} +*/ +declare function isDynamicPattern(pattern: string, options?: { + caseSensitiveMatch: boolean; +}): boolean; +//#endregion +//#region src/index.d.ts /** * Asynchronously match files following a glob pattern. * @see {@link https://superchupu.dev/tinyglobby/documentation#glob} @@ -144,4 +145,4 @@ declare function globSync(patterns: string | readonly string[], options?: Omit string; -/** -* Escapes a path's special characters depending on the platform. -* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath} -*/ -declare const escapePath: (path: string) => string; -/** -* Checks if a pattern has dynamic parts. -* -* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy: -* -* - Doesn't necessarily return `false` on patterns that include `\`. -* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not. -* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`. -* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`. -* -* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern} -*/ -declare function isDynamicPattern(pattern: string, options?: { - caseSensitiveMatch: boolean; -}): boolean; -//#endregion -//#region src/index.d.ts +//#region src/types.d.ts +type FileSystemAdapter = Partial; interface GlobOptions { /** * Whether to return absolute paths. Disable to have relative paths. @@ -124,7 +96,36 @@ interface GlobOptions { */ signal?: AbortSignal; } -type FileSystemAdapter = Partial; +//#endregion +//#region src/utils.d.ts +/** +* Converts a path to a pattern depending on the platform. +* Identical to {@link escapePath} on POSIX systems. +* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern} +*/ +declare const convertPathToPattern: (path: string) => string; +/** +* Escapes a path's special characters depending on the platform. +* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath} +*/ +declare const escapePath: (path: string) => string; +/** +* Checks if a pattern has dynamic parts. +* +* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy: +* +* - Doesn't necessarily return `false` on patterns that include `\`. +* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not. +* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`. +* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`. +* +* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern} +*/ +declare function isDynamicPattern(pattern: string, options?: { + caseSensitiveMatch: boolean; +}): boolean; +//#endregion +//#region src/index.d.ts /** * Asynchronously match files following a glob pattern. * @see {@link https://superchupu.dev/tinyglobby/documentation#glob} @@ -144,4 +145,4 @@ declare function globSync(patterns: string | readonly string[], options?: Omit { const inputParts = input.split("/"); if (inputParts[0] === ".." && ONLY_PARENT_DIRECTORIES.test(input)) return true; - for (let i = 0; i < patterns.length; i++) { + for (i = 0; i < patternsCount; i++) { const patternParts = patternsParts[i]; const matcher = matchers[i]; const inputPatternCount = inputParts.length; const minParts = Math.min(inputPatternCount, patternParts.length); - let j = 0; + j = 0; while (j < minParts) { const part = patternParts[j]; if (part.includes("/")) return true; - const match = matcher[j](inputParts[j]); - if (!match) break; - if (globstarEnabled && part === "**") return true; + if (!matcher[j](inputParts[j])) break; + if (!options.noglobstar && part === "**") return true; j++; } if (j === inputPatternCount) return true; @@ -49,7 +48,7 @@ const isRoot = isWin ? (p) => WIN32_ROOT_DIR.test(p) : (p) => p === "/"; function buildFormat(cwd, root, absolute) { if (cwd === root || root.startsWith(`${cwd}/`)) { if (absolute) { - const start = isRoot(cwd) ? cwd.length : cwd.length + 1; + const start = cwd.length + +!isRoot(cwd); return (p, isDir) => p.slice(start, isDir ? -1 : void 0) || "."; } const prefix = root.slice(cwd.length + 1); @@ -70,22 +69,21 @@ function buildRelative(cwd, root) { } return (p) => { const result = posix.relative(cwd, `${root}/${p}`); - if (p.endsWith("/") && result !== "") return `${result}/`; - return result || "."; + return p[p.length - 1] === "/" && result !== "" ? `${result}/` : result || "."; }; } const splitPatternOptions = { parts: true }; -function splitPattern(path$1) { +function splitPattern(path) { var _result$parts; - const result = picomatch.scan(path$1, splitPatternOptions); - return ((_result$parts = result.parts) === null || _result$parts === void 0 ? void 0 : _result$parts.length) ? result.parts : [path$1]; + const result = picomatch.scan(path, splitPatternOptions); + return ((_result$parts = result.parts) === null || _result$parts === void 0 ? void 0 : _result$parts.length) ? result.parts : [path]; } const ESCAPED_WIN32_BACKSLASHES = /\\(?![()[\]{}!+@])/g; -function convertPosixPathToPattern(path$1) { - return escapePosixPath(path$1); +function convertPosixPathToPattern(path) { + return escapePosixPath(path); } -function convertWin32PathToPattern(path$1) { - return escapeWin32Path(path$1).replace(ESCAPED_WIN32_BACKSLASHES, "/"); +function convertWin32PathToPattern(path) { + return escapeWin32Path(path).replace(ESCAPED_WIN32_BACKSLASHES, "/"); } /** * Converts a path to a pattern depending on the platform. @@ -96,8 +94,8 @@ function convertWin32PathToPattern(path$1) { const convertPathToPattern = isWin ? convertWin32PathToPattern : convertPosixPathToPattern; const POSIX_UNESCAPED_GLOB_SYMBOLS = /(? path$1.replace(POSIX_UNESCAPED_GLOB_SYMBOLS, "\\$&"); -const escapeWin32Path = (path$1) => path$1.replace(WIN32_UNESCAPED_GLOB_SYMBOLS, "\\$&"); +const escapePosixPath = (path) => path.replace(POSIX_UNESCAPED_GLOB_SYMBOLS, "\\$&"); +const escapeWin32Path = (path) => path.replace(WIN32_UNESCAPED_GLOB_SYMBOLS, "\\$&"); /** * Escapes a path's special characters depending on the platform. * @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath} @@ -124,31 +122,33 @@ function isDynamicPattern(pattern, options) { function log(...tasks) { console.log(`[tinyglobby ${(/* @__PURE__ */ new Date()).toLocaleTimeString("es")}]`, ...tasks); } - +function ensureStringArray(value) { + return typeof value === "string" ? [value] : value !== null && value !== void 0 ? value : []; +} //#endregion -//#region src/index.ts +//#region src/patterns.ts const PARENT_DIRECTORY = /^(\/?\.\.)+/; const ESCAPING_BACKSLASHES = /\\(?=[()[\]{}!*+?@|])/g; -const BACKSLASHES = /\\/g; -function normalizePattern(pattern, expandDirectories, cwd, props, isIgnore) { +function normalizePattern(pattern, opts, props, isIgnore) { + var _PARENT_DIRECTORY$exe; + const cwd = opts.cwd; let result = pattern; - if (pattern.endsWith("/")) result = pattern.slice(0, -1); - if (!result.endsWith("*") && expandDirectories) result += "/**"; + if (pattern[pattern.length - 1] === "/") result = pattern.slice(0, -1); + if (result[result.length - 1] !== "*" && opts.expandDirectories) result += "/**"; const escapedCwd = escapePath(cwd); - if (path.isAbsolute(result.replace(ESCAPING_BACKSLASHES, ""))) result = posix.relative(escapedCwd, result); - else result = posix.normalize(result); - const parentDirectoryMatch = PARENT_DIRECTORY.exec(result); + result = isAbsolute(result.replace(ESCAPING_BACKSLASHES, "")) ? posix.relative(escapedCwd, result) : posix.normalize(result); + const parentDir = (_PARENT_DIRECTORY$exe = PARENT_DIRECTORY.exec(result)) === null || _PARENT_DIRECTORY$exe === void 0 ? void 0 : _PARENT_DIRECTORY$exe[0]; const parts = splitPattern(result); - if (parentDirectoryMatch === null || parentDirectoryMatch === void 0 ? void 0 : parentDirectoryMatch[0]) { - const n = (parentDirectoryMatch[0].length + 1) / 3; + if (parentDir) { + const n = (parentDir.length + 1) / 3; let i = 0; const cwdParts = escapedCwd.split("/"); while (i < n && parts[i + n] === cwdParts[cwdParts.length + i - n]) { result = result.slice(0, (n - i - 1) * 3) + result.slice((n - i) * 3 + parts[i + n].length + 1) || "."; i++; } - const potentialRoot = posix.join(cwd, parentDirectoryMatch[0].slice(i * 3)); - if (!potentialRoot.startsWith(".") && props.root.length > potentialRoot.length) { + const potentialRoot = posix.join(cwd, parentDir.slice(i * 3)); + if (potentialRoot[0] !== "." && props.root.length > potentialRoot.length) { props.root = potentialRoot; props.depthOffset = -n + i; } @@ -164,7 +164,7 @@ function normalizePattern(pattern, expandDirectories, cwd, props, isIgnore) { newCommonPath.pop(); break; } - if (part !== props.commonPath[i] || isDynamicPattern(part) || i === parts.length - 1) break; + if (i === parts.length - 1 || part !== props.commonPath[i] || isDynamicPattern(part)) break; newCommonPath.push(part); } props.depthOffset = newCommonPath.length; @@ -173,146 +173,134 @@ function normalizePattern(pattern, expandDirectories, cwd, props, isIgnore) { } return result; } -function processPatterns({ patterns = ["**/*"], ignore = [], expandDirectories = true }, cwd, props) { - if (typeof patterns === "string") patterns = [patterns]; - if (typeof ignore === "string") ignore = [ignore]; +function processPatterns(options, patterns, props) { const matchPatterns = []; const ignorePatterns = []; - for (const pattern of ignore) { + for (const pattern of options.ignore) { if (!pattern) continue; - if (pattern[0] !== "!" || pattern[1] === "(") ignorePatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, true)); + if (pattern[0] !== "!" || pattern[1] === "(") ignorePatterns.push(normalizePattern(pattern, options, props, true)); } for (const pattern of patterns) { if (!pattern) continue; - if (pattern[0] !== "!" || pattern[1] === "(") matchPatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, false)); - else if (pattern[1] !== "!" || pattern[2] === "(") ignorePatterns.push(normalizePattern(pattern.slice(1), expandDirectories, cwd, props, true)); + if (pattern[0] !== "!" || pattern[1] === "(") matchPatterns.push(normalizePattern(pattern, options, props, false)); + else if (pattern[1] !== "!" || pattern[2] === "(") ignorePatterns.push(normalizePattern(pattern.slice(1), options, props, true)); } return { match: matchPatterns, ignore: ignorePatterns }; } -function formatPaths(paths, relative) { - for (let i = paths.length - 1; i >= 0; i--) { - const path$1 = paths[i]; - paths[i] = relative(path$1); - } - return paths; -} -function normalizeCwd(cwd) { - if (!cwd) return process.cwd().replace(BACKSLASHES, "/"); - if (cwd instanceof URL) return fileURLToPath(cwd).replace(BACKSLASHES, "/"); - return path.resolve(cwd).replace(BACKSLASHES, "/"); -} -function getCrawler(patterns, inputOptions = {}) { - const options = process.env.TINYGLOBBY_DEBUG ? { - ...inputOptions, - debug: true - } : inputOptions; - const cwd = normalizeCwd(options.cwd); - if (options.debug) log("globbing with:", { - patterns, - options, - cwd - }); - if (Array.isArray(patterns) && patterns.length === 0) return [{ - sync: () => [], - withPromise: async () => [] - }, false]; +//#endregion +//#region src/crawler.ts +function buildCrawler(options, patterns) { + const cwd = options.cwd; const props = { root: cwd, - commonPath: null, depthOffset: 0 }; - const processed = processPatterns({ - ...options, - patterns - }, cwd, props); + const processed = processPatterns(options, patterns, props); if (options.debug) log("internal processing patterns:", processed); + const { absolute, caseSensitiveMatch, debug, dot, followSymbolicLinks, onlyDirectories } = options; + const root = props.root.replace(BACKSLASHES, ""); const matchOptions = { - dot: options.dot, + dot, nobrace: options.braceExpansion === false, - nocase: options.caseSensitiveMatch === false, + nocase: !caseSensitiveMatch, noextglob: options.extglob === false, noglobstar: options.globstar === false, posix: true }; - const matcher = picomatch(processed.match, { - ...matchOptions, - ignore: processed.ignore - }); + const matcher = picomatch(processed.match, matchOptions); const ignore = picomatch(processed.ignore, matchOptions); const partialMatcher = getPartialMatcher(processed.match, matchOptions); - const format = buildFormat(cwd, props.root, options.absolute); - const formatExclude = options.absolute ? format : buildFormat(cwd, props.root, true); - const fdirOptions = { - filters: [options.debug ? (p, isDirectory) => { - const path$1 = format(p, isDirectory); - const matches = matcher(path$1); - if (matches) log(`matched ${path$1}`); + const format = buildFormat(cwd, root, absolute); + const excludeFormatter = absolute ? format : buildFormat(cwd, root, true); + const excludePredicate = (_, p) => { + const relativePath = excludeFormatter(p, true); + return relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath); + }; + let maxDepth; + if (options.deep !== void 0) maxDepth = Math.round(options.deep - props.depthOffset); + const crawler = new fdir({ + filters: [debug ? (p, isDirectory) => { + const path = format(p, isDirectory); + const matches = matcher(path) && !ignore(path); + if (matches) log(`matched ${path}`); return matches; - } : (p, isDirectory) => matcher(format(p, isDirectory))], - exclude: options.debug ? (_, p) => { - const relativePath = formatExclude(p, true); - const skipped = relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath); - if (skipped) log(`skipped ${p}`); - else log(`crawling ${p}`); + } : (p, isDirectory) => { + const path = format(p, isDirectory); + return matcher(path) && !ignore(path); + }], + exclude: debug ? (_, p) => { + const skipped = excludePredicate(_, p); + log(`${skipped ? "skipped" : "crawling"} ${p}`); return skipped; - } : (_, p) => { - const relativePath = formatExclude(p, true); - return relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath); - }, - fs: options.fs ? { - readdir: options.fs.readdir || nativeFs.readdir, - readdirSync: options.fs.readdirSync || nativeFs.readdirSync, - realpath: options.fs.realpath || nativeFs.realpath, - realpathSync: options.fs.realpathSync || nativeFs.realpathSync, - stat: options.fs.stat || nativeFs.stat, - statSync: options.fs.statSync || nativeFs.statSync - } : void 0, + } : excludePredicate, + fs: options.fs, pathSeparator: "/", - relativePaths: true, - resolveSymlinks: true, + relativePaths: !absolute, + resolvePaths: absolute, + includeBasePath: absolute, + resolveSymlinks: followSymbolicLinks, + excludeSymlinks: !followSymbolicLinks, + excludeFiles: onlyDirectories, + includeDirs: onlyDirectories || !options.onlyFiles, + maxDepth, signal: options.signal + }).crawl(root); + if (options.debug) log("internal properties:", { + ...props, + root + }); + return [crawler, cwd !== root && !absolute && buildRelative(cwd, root)]; +} +//#endregion +//#region src/index.ts +function formatPaths(paths, mapper) { + if (mapper) for (let i = paths.length - 1; i >= 0; i--) paths[i] = mapper(paths[i]); + return paths; +} +const defaultOptions = { + caseSensitiveMatch: true, + cwd: process.cwd(), + debug: !!process.env.TINYGLOBBY_DEBUG, + expandDirectories: true, + followSymbolicLinks: true, + onlyFiles: true +}; +function getOptions(options) { + const opts = { + ...defaultOptions, + ...options }; - if (options.deep !== void 0) fdirOptions.maxDepth = Math.round(options.deep - props.depthOffset); - if (options.absolute) { - fdirOptions.relativePaths = false; - fdirOptions.resolvePaths = true; - fdirOptions.includeBasePath = true; - } - if (options.followSymbolicLinks === false) { - fdirOptions.resolveSymlinks = false; - fdirOptions.excludeSymlinks = true; - } - if (options.onlyDirectories) { - fdirOptions.excludeFiles = true; - fdirOptions.includeDirs = true; - } else if (options.onlyFiles === false) fdirOptions.includeDirs = true; - props.root = props.root.replace(BACKSLASHES, ""); - const root = props.root; - if (options.debug) log("internal properties:", props); - const relative = cwd !== root && !options.absolute && buildRelative(cwd, props.root); - return [new fdir(fdirOptions).crawl(root), relative]; + opts.cwd = (opts.cwd instanceof URL ? fileURLToPath(opts.cwd) : resolve(opts.cwd)).replace(BACKSLASHES, "/"); + opts.ignore = ensureStringArray(opts.ignore); + opts.fs && (opts.fs = { + readdir: opts.fs.readdir || readdir, + readdirSync: opts.fs.readdirSync || readdirSync, + realpath: opts.fs.realpath || realpath, + realpathSync: opts.fs.realpathSync || realpathSync, + stat: opts.fs.stat || stat, + statSync: opts.fs.statSync || statSync + }); + if (opts.debug) log("globbing with options:", opts); + return opts; +} +function getCrawler(globInput, inputOptions = {}) { + var _ref; + if (globInput && (inputOptions === null || inputOptions === void 0 ? void 0 : inputOptions.patterns)) throw new Error("Cannot pass patterns as both an argument and an option"); + const isModern = isReadonlyArray(globInput) || typeof globInput === "string"; + const patterns = ensureStringArray((_ref = isModern ? globInput : globInput.patterns) !== null && _ref !== void 0 ? _ref : "**/*"); + const options = getOptions(isModern ? inputOptions : globInput); + return patterns.length > 0 ? buildCrawler(options, patterns) : []; } -async function glob(patternsOrOptions, options) { - if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option"); - const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string"; - const opts = isModern ? options : patternsOrOptions; - const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns; - const [crawler, relative] = getCrawler(patterns, opts); - if (!relative) return crawler.withPromise(); - return formatPaths(await crawler.withPromise(), relative); +async function glob(globInput, options) { + const [crawler, relative] = getCrawler(globInput, options); + return crawler ? formatPaths(await crawler.withPromise(), relative) : []; } -function globSync(patternsOrOptions, options) { - if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option"); - const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string"; - const opts = isModern ? options : patternsOrOptions; - const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns; - const [crawler, relative] = getCrawler(patterns, opts); - if (!relative) return crawler.sync(); - return formatPaths(crawler.sync(), relative); +function globSync(globInput, options) { + const [crawler, relative] = getCrawler(globInput, options); + return crawler ? formatPaths(crawler.sync(), relative) : []; } - //#endregion -export { convertPathToPattern, escapePath, glob, globSync, isDynamicPattern }; \ No newline at end of file +export { convertPathToPattern, escapePath, glob, globSync, isDynamicPattern }; diff --git a/deps/npm/node_modules/tinyglobby/package.json b/deps/npm/node_modules/tinyglobby/package.json index d0247c25ae3..06dadac3a61 100644 --- a/deps/npm/node_modules/tinyglobby/package.json +++ b/deps/npm/node_modules/tinyglobby/package.json @@ -1,6 +1,6 @@ { "name": "tinyglobby", - "version": "0.2.15", + "version": "0.2.16", "description": "A fast and minimal alternative to globby and fast-glob", "type": "module", "main": "./dist/index.cjs", @@ -38,18 +38,18 @@ }, "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "devDependencies": { - "@biomejs/biome": "^2.2.3", - "@types/node": "^24.3.1", - "@types/picomatch": "^4.0.2", + "@biomejs/biome": "^2.4.10", + "@types/node": "^25.5.2", + "@types/picomatch": "^4.0.3", "fast-glob": "^3.3.3", - "fs-fixture": "^2.8.1", - "glob": "^11.0.3", - "tinybench": "^5.0.1", - "tsdown": "^0.14.2", - "typescript": "^5.9.2" + "fs-fixture": "^2.13.0", + "glob": "^13.0.6", + "tinybench": "^6.0.0", + "tsdown": "^0.21.7", + "typescript": "^6.0.2" }, "engines": { "node": ">=12.0.0" diff --git a/deps/npm/package.json b/deps/npm/package.json index 98c46f5b197..f09c0bd2a42 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -116,6 +116,7 @@ "tar": "^7.5.11", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", + "tinyglobby": "^0.2.16", "treeverse": "^3.0.0", "validate-npm-package-name": "^6.0.2", "which": "^5.0.0", @@ -187,6 +188,7 @@ "tar", "text-table", "tiny-relative-date", + "tinyglobby", "treeverse", "validate-npm-package-name", "which", From 5099011ecdc35a47a544026bd0fa09d08da8cd02 Mon Sep 17 00:00:00 2001 From: Santiago Gimeno Date: Mon, 20 Apr 2026 14:23:59 +0200 Subject: [PATCH 8/8] 2026-04-28, Version 20.20.2-nsolid-v6.2.3 'Iron' --- .../NSOLID_CHANGELOG_V6_NODE_V20.md | 21 +++++++++++++++++++ src/node_version.h | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/doc/changelogs/NSOLID_CHANGELOG_V6_NODE_V20.md b/doc/changelogs/NSOLID_CHANGELOG_V6_NODE_V20.md index 9410b31e5db..912e06697e7 100644 --- a/doc/changelogs/NSOLID_CHANGELOG_V6_NODE_V20.md +++ b/doc/changelogs/NSOLID_CHANGELOG_V6_NODE_V20.md @@ -2,6 +2,27 @@ +## 2026-04-28, Version 20.20.2-nsolid-v6.2.3 'Iron' + +### Commits + +* \[[`2ccd85d59c`](https://github.com/nodesource/nsolid/commit/2ccd85d59c)] - **deps**: tinyglobby\@0.2.16 (Santiago Gimeno) [#453](https://github.com/nodesource/nsolid/pull/453) +* \[[`7d50e4c898`](https://github.com/nodesource/nsolid/commit/7d50e4c898)] - **deps**: brace-expansion\@2.0.3 (Santiago Gimeno) [#453](https://github.com/nodesource/nsolid/pull/453) +* \[[`02b8c59c87`](https://github.com/nodesource/nsolid/commit/02b8c59c87)] - **deps**: bump ncm-ng to 2.9.8 (JungMinu) [#455](https://github.com/nodesource/nsolid/pull/455) +* \[[`ef9e9fcb82`](https://github.com/nodesource/nsolid/commit/ef9e9fcb82)] - **deps**: upgrade npm to 10.9.8 (npm team) [#458](https://github.com/nodesource/nsolid/pull/458) +* \[[`950ba9ed79`](https://github.com/nodesource/nsolid/commit/950ba9ed79)] - **deps**: upgrade npm to 10.9.7 (npm team) [#458](https://github.com/nodesource/nsolid/pull/458) +* \[[`f19ebe8c12`](https://github.com/nodesource/nsolid/commit/f19ebe8c12)] - **deps**: upgrade npm to 10.9.6 (npm team) [#458](https://github.com/nodesource/nsolid/pull/458) +* \[[`ae11abb351`](https://github.com/nodesource/nsolid/commit/ae11abb351)] - **deps**: upgrade npm to 10.9.4 (npm team) [#458](https://github.com/nodesource/nsolid/pull/458) +* \[[`45647db4ff`](https://github.com/nodesource/nsolid/commit/45647db4ff)] - **deps**: upgrade npm to 10.9.3 (npm team) [#458](https://github.com/nodesource/nsolid/pull/458) +* \[[`d97ee2f8af`](https://github.com/nodesource/nsolid/commit/d97ee2f8af)] - **deps**: upgrade npm to 10.9.2 (npm team) [#458](https://github.com/nodesource/nsolid/pull/458) +* \[[`e30b086f0c`](https://github.com/nodesource/nsolid/commit/e30b086f0c)] - **deps**: upgrade npm to 10.9.1 (npm team) [#458](https://github.com/nodesource/nsolid/pull/458) +* \[[`8ae7f317a9`](https://github.com/nodesource/nsolid/commit/8ae7f317a9)] - **deps**: upgrade npm to 10.9.0 (npm team) [#458](https://github.com/nodesource/nsolid/pull/458) +* \[[`43850daddb`](https://github.com/nodesource/nsolid/commit/43850daddb)] - **deps**: upgrade npm to 10.8.3 (npm team) [#458](https://github.com/nodesource/nsolid/pull/458) +* \[[`2048c17433`](https://github.com/nodesource/nsolid/commit/2048c17433)] - **lib**: fix JS linting issues (Santiago Gimeno) [#453](https://github.com/nodesource/nsolid/pull/453) +* \[[`493631c083`](https://github.com/nodesource/nsolid/commit/493631c083)] - **lib,src,test**: fix race during tracing toggles (Santiago Gimeno) [#441](https://github.com/nodesource/nsolid/pull/441) +* \[[`a1b52c5fa9`](https://github.com/nodesource/nsolid/commit/a1b52c5fa9)] - **src**: replace duplicate loop hook regs (Santiago Gimeno) [#444](https://github.com/nodesource/nsolid/pull/444) +* \[[`ecb4ee0f10`](https://github.com/nodesource/nsolid/commit/ecb4ee0f10)] - **test**: fix linting in test-nsolid-file-handle-count (Santiago Gimeno) [#441](https://github.com/nodesource/nsolid/pull/441) + ## 2026-03-25, Version 20.20.2-nsolid-v6.2.2 'Iron' ### Commits diff --git a/src/node_version.h b/src/node_version.h index c1893b32063..cff7ef96ca2 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -36,7 +36,7 @@ #define NSOLID_MINOR_VERSION 2 #define NSOLID_PATCH_VERSION 3 -#define NSOLID_VERSION_IS_RELEASE 0 +#define NSOLID_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)