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" } } 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/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 476e999d42c..f09c0bd2a42 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", @@ -115,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", @@ -133,6 +135,7 @@ "@sigstore/tuf", "abbrev", "archy", + "brace-expansion", "cacache", "chalk", "ci-info", @@ -185,6 +188,7 @@ "tar", "text-table", "tiny-relative-date", + "tinyglobby", "treeverse", "validate-npm-package-name", "which", 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/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/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) diff --git a/src/nsolid/nsolid_api.cc b/src/nsolid/nsolid_api.cc index f05a81009b9..49fd368f156 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); @@ -1806,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/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/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-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); })); }); }, diff --git a/test/agents/test-grpc-tracing-race.mjs b/test/agents/test-grpc-tracing-race.mjs new file mode 100644 index 00000000000..340f0b10bb2 --- /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') { 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))); })); })); });