diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000000..de695c209d --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,86 @@ +name: E2E Tests + +on: + pull_request: + paths: + - 'meteor' + - 'tools/modern-tests/**' + - 'packages/rspack/**' + - 'packages/tools-core/**' + - 'packages/babel-compiler/**' + - 'packages/meteor-tool/**' + - 'npm-packages/meteor-rspack/**' + - 'tools/static-assets/skel-**' + - '.github/workflows/e2e-tests.yml' + +concurrency: + group: meteor-rspack-tests-${{ github.ref }} + cancel-in-progress: true + +env: + NODE_OPTIONS: "--max_old_space_size=12288" + +jobs: + test: + name: ${{ matrix.category }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + category: + - Angular + - Babel + - Blaze + - Coffeescript + - Library + - Monorepo + - React + - R.Router + - Solid + - Svelte + - Typescript + - Vue + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.npm + node_modules + tools/modern-tests/node_modules + packages/**/.npm + .meteor + dev_bundle + .babel-cache + ~/.cache/ms-playwright + + key: ${{ runner.os }}-meteor-${{ hashFiles('**/package-lock.json', 'meteor') }} + restore-keys: | + ${{ runner.os }}-meteor- + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.x + + - name: Install deps + run: npm install + + - name: Install test deps + run: npm run install:modern + + - name: Prepare Meteor + run: ./meteor --get-ready + + - name: Run tests for ${{ matrix.category }} + uses: nick-fields/retry@v3 + with: + max_attempts: 3 + retry_on: error + timeout_minutes: 15 + retry_wait_seconds: 90 + command: npm run test:modern -- -t="${{ matrix.category }}" diff --git a/.github/workflows/windows-selftest.yml b/.github/workflows/windows-selftest.yml index 60da3b955d..04b3abc05d 100644 --- a/.github/workflows/windows-selftest.yml +++ b/.github/workflows/windows-selftest.yml @@ -60,7 +60,7 @@ jobs: ~/.npm node_modules/ packages/**/.npm - key: ${{ runner.os }}-meteor-${{ hashFiles('**/package-lock.json', 'meteor', 'meteor.bat') }} + key: ${{ runner.os }}-meteor-${{ hashFiles('meteor', 'meteor.bat') }} restore-keys: | ${{ runner.os }}-meteor- diff --git a/README.md b/README.md index 11aa1c4c6c..e37bdc7cae 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ How about trying a tutorial to get started with your favorite technology? | [ React](https://docs.meteor.com/tutorials/react/) | | - | | [ Blaze](https://blaze-tutorial.meteor.com/) | -| [ Vue](https://docs.meteor.com/tutorials/vue/meteorjs3-vue3-vue-meteor-tracker.html) | +| [ Vue](https://docs.meteor.com/tutorials/vue/meteorjs3-vue3.html) | # 🚀 Quick Start diff --git a/docs/source/api/check.md b/docs/source/api/check.md index 107c3c865d..208b6c5747 100644 --- a/docs/source/api/check.md +++ b/docs/source/api/check.md @@ -87,6 +87,10 @@ Matches a primitive of the given type. Matches a signed 32-bit integer. Doesn't match `Infinity`, `-Infinity`, or `NaN`. {% enddtdd %} +{% dtdd name:"Match.NonEmptyString" %} +Matches a non-empty string. +{% enddtdd %} + {% dtdd name:"[pattern]" %} A one-element array matches an array of elements, each of which match *pattern*. For example, `[Number]` matches a (possibly empty) array of numbers; @@ -160,12 +164,13 @@ from the call to `check` or `Match.test`. Examples: {% codeblock lang:js %} check(buffer, Match.Where(EJSON.isBinary)); -const NonEmptyString = Match.Where((x) => { - check(x, String); - return x.length > 0; +// Example: creating a custom pattern for positive numbers +const PositiveNumber = Match.Where((x) => { + check(x, Number); + return x > 0; }); -check(arg, NonEmptyString); +check(arg, PositiveNumber); {% endcodeblock %} {% enddtdd %} diff --git a/meteor b/meteor index 517e4bd0fe..d1412f0105 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=22.18.0.3 +BUNDLE_VERSION=22.22.0.3 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js index aedf49c8c0..432d64b0fb 100644 --- a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js +++ b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js @@ -10,7 +10,7 @@ var packageJson = { dependencies: { // Explicit dependency because we are replacing it with a bundled version // and we want to make sure there are no dependencies on a higher version - npm: "10.9.3", + npm: "10.9.4", pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8", "node-gyp": "9.4.0", "@mapbox/node-pre-gyp": "1.0.11", diff --git a/npm-packages/meteor-installer/config.js b/npm-packages/meteor-installer/config.js index 4f96ac3ad4..a771459f81 100644 --- a/npm-packages/meteor-installer/config.js +++ b/npm-packages/meteor-installer/config.js @@ -1,7 +1,7 @@ const os = require('os'); const path = require('path'); -const METEOR_LATEST_VERSION = '3.3.2'; +const METEOR_LATEST_VERSION = '3.4'; const sudoUser = process.env.SUDO_USER || ''; function isRoot() { return process.getuid && process.getuid() === 0; diff --git a/npm-packages/meteor-installer/package-lock.json b/npm-packages/meteor-installer/package-lock.json index 95184ff90f..8b670686b6 100644 --- a/npm-packages/meteor-installer/package-lock.json +++ b/npm-packages/meteor-installer/package-lock.json @@ -1,12 +1,12 @@ { "name": "meteor", - "version": "3.3.2", + "version": "3.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "meteor", - "version": "3.3.2", + "version": "3.4.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/npm-packages/meteor-installer/package.json b/npm-packages/meteor-installer/package.json index e9fc928561..61ebd3389c 100644 --- a/npm-packages/meteor-installer/package.json +++ b/npm-packages/meteor-installer/package.json @@ -1,6 +1,6 @@ { "name": "meteor", - "version": "3.3.2", + "version": "3.4.0", "description": "Install Meteor", "main": "install.js", "scripts": { diff --git a/npm-packages/meteor-rspack/index.d.ts b/npm-packages/meteor-rspack/index.d.ts new file mode 100644 index 0000000000..896b9703df --- /dev/null +++ b/npm-packages/meteor-rspack/index.d.ts @@ -0,0 +1,99 @@ +/** + * Extend Rspack’s Configuration with Meteor-specific options. + */ +import { + defineConfig as _rspackDefineConfig, + Configuration as _RspackConfig, +} from '@rspack/cli'; +import { HtmlRspackPluginOptions, RuleSetConditions, SwcLoaderOptions } from '@rspack/core'; + +export interface MeteorRspackConfig extends _RspackConfig { + meteor?: { + packageNamespace?: string; + }; +} + +type MeteorEnv = Record & { + isDevelopment: boolean; + isProduction: boolean; + isClient: boolean; + isServer: boolean; + isTest: boolean; + isDebug: boolean; + isRun: boolean; + isBuild: boolean; + isReactEnabled: boolean; + isBlazeEnabled: boolean; + isBlazeHotEnabled: boolean; + /** + * A function that creates an instance of HtmlRspackPlugin with default options. + * @param options - Optional configuration options that will be merged with defaults + * @returns An instance of HtmlRspackPlugin + */ + HtmlRspackPlugin: (options?: HtmlRspackPluginOptions) => HtmlRspackPlugin; + /** + * Wrap externals for Meteor runtime. + * @param deps - Package names or module IDs + * @returns A config object with externals configuration + */ + compileWithMeteor: (deps: RuleSetConditions) => Record; + /** + * Add SWC transpilation rules limited to specific deps (monorepo-friendly). + * @param deps - Package names to include in SWC loader + * @param options - Optional configuration options + * @returns A config object with module rules configuration + */ + compileWithRspack: (deps: RuleSetConditions, options?: SwcLoaderOptions) => Record; + /** + * Enable or disable Rspack cache config. + * @param enabled - Whether to enable caching + * @param cacheConfig - Optional cache configuration + * @returns A config object with cache configuration + */ + setCache: (enabled: boolean | 'memory') => Record; + /** + * Enable Rspack split vendor chunk. + * @returns A config object with optimization configuration + */ + splitVendorChunk: () => Record; + /** + * Extend Rspack SWC loader config. + * @returns A config object with SWC loader config + */ + extendSwcConfig: (swcConfig: SwcLoaderOptions) => Record; + /** + * Extend Rspack configs. + * @returns A config object with merged configs + */ + extendConfig: (...configs: Record[]) => Record; + + /** + * Remove plugins from a Rspack config by name, RegExp, predicate, or array of them. + * @param matchers - String, RegExp, function, or array of them to match plugin names + * @returns The modified config object + */ + disablePlugins: ( + matchers: string | RegExp | ((plugin: any, index: number) => boolean) | Array boolean)> + ) => Record; +} + +export type ConfigFactory = ( + env: MeteorEnv, + argv: Record +) => MeteorRspackConfig; + +export function defineConfig( + factory: ConfigFactory +): ReturnType; + +/** + * A plugin that composes the original HtmlRspackPlugin from @rspack/core + * and RspackMeteorHtmlPlugin, in that order. + */ +export class HtmlRspackPlugin { + constructor(options?: HtmlRspackPluginOptions); + apply(compiler: any): void; +} + +// Re-export HtmlRspackPluginOptions from @rspack/cli +export { HtmlRspackPluginOptions }; diff --git a/npm-packages/meteor-rspack/index.js b/npm-packages/meteor-rspack/index.js new file mode 100644 index 0000000000..696487151f --- /dev/null +++ b/npm-packages/meteor-rspack/index.js @@ -0,0 +1,28 @@ +const { defineConfig: rspackDefineConfig } = require('@rspack/cli'); +const HtmlRspackPlugin = require('./plugins/HtmlRspackPlugin.js'); + +/** + * @typedef {import('rspack').Configuration & { + * meteor?: { packageNamespace?: string } + * }} MeteorRspackConfig + */ + +/** + * @typedef {(env: Record, argv: Record) => MeteorRspackConfig} ConfigFactory + */ + +/** + * Wrap rspack.defineConfig but only accept a factory function. + * @param {ConfigFactory} factory + * @returns {ReturnType} + */ +function defineConfig(factory) { + return rspackDefineConfig(factory); +} + +// Export our helper plus passthrough as default export +module.exports = defineConfig; + +// Export the HtmlRspackPlugin and defineConfig as named exports +module.exports.defineConfig = defineConfig; +module.exports.HtmlRspackPlugin = HtmlRspackPlugin; diff --git a/npm-packages/meteor-rspack/lib/ignore.js b/npm-packages/meteor-rspack/lib/ignore.js new file mode 100644 index 0000000000..17058cce19 --- /dev/null +++ b/npm-packages/meteor-rspack/lib/ignore.js @@ -0,0 +1,139 @@ +var fs = require('fs'); +var path = require('path'); + +/** + * Reads the .meteorignore file from the given project directory and returns + * the parsed entries. Empty lines and comment lines (starting with #) are filtered out. + * + * @param {string} projectDir - The project directory path + * @returns {string[]} - Array of ignore patterns + */ +const getMeteorIgnoreEntries = function (projectDir) { + const meteorIgnorePath = path.join(projectDir, '.meteorignore'); + + // Check if .meteorignore file exists + try { + const fileContent = fs.readFileSync(meteorIgnorePath, 'utf8'); + + // Process each line in the file + const entries = fileContent.split(/\r?\n/) + .map(line => line.trim()) + .filter(line => line !== '' && !line.startsWith('#')); + + return entries; + } catch (e) { + // If the file doesn't exist or can't be read, return empty array + return []; + } +}; + +/** + * Creates a glob config array for ignoring specified patterns. + * Transforms .gitignore-style entries into chokidar-compatible glob patterns. + * @param {string[]} entries - Array of .gitignore-style patterns + * @returns {string[]} - Array of glob patterns for chokidar + */ +function createIgnoreGlobConfig(entries = []) { + if (!Array.isArray(entries)) { + throw new Error('Entries must be an array'); + } + + const globPatterns = []; + + entries.forEach(entry => { + // Skip empty entries + if (!entry.trim()) { + return; + } + + // Handle comments + if (entry.startsWith('#')) { + return; + } + + // Check if it's a negation pattern + const isNegation = entry.startsWith('!'); + let pattern = isNegation ? entry.substring(1).trim() : entry.trim(); + + // Remove leading ./ or / if present + pattern = pattern.replace(/^(\.\/|\/)/g, ''); + + // If it ends with /, it's a directory pattern, add ** to match all contents + if (pattern.endsWith('/')) { + pattern = pattern.slice(0, -1) + '/**'; + } + + // If it doesn't include a /, it could match anywhere in the path + if (!pattern.includes('/')) { + pattern = '**/' + pattern; + } else if (!pattern.startsWith('**/') && !pattern.startsWith('/')) { + // If it has a / but doesn't start with **/, add **/ to match anywhere + pattern = '**/' + pattern; + } + + // Add the negation back if it was present + if (isNegation) { + pattern = '!' + pattern; + } + + globPatterns.push(pattern); + }); + + return globPatterns; +} + +/** + * Creates a regex pattern to match the specified glob patterns. + * Converts glob patterns with * and ** into regex equivalents. + * + * @param {string[]} globPatterns - Array of glob patterns from createIgnoreGlobConfig + * @returns {RegExp} - Regex pattern to match the specified patterns + */ +function createIgnoreRegex(globPatterns) { + if (!Array.isArray(globPatterns) || globPatterns.length === 0) { + throw new Error('globPatterns must be a non-empty array'); + } + + // Process each glob pattern and convert to regex + const regexPatterns = globPatterns.map(pattern => { + // Skip negation patterns for the regex + if (pattern.startsWith('!')) { + return null; + } + + // Escape special regex characters, but not * and / + let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); + + // Use a temporary placeholder for ** that won't be affected by the * replacement + // This is necessary because if we directly replace ** with .* and then replace * with [^/]* + const DOUBLE_ASTERISK_PLACEHOLDER = '__DOUBLE_ASTERISK__'; + regexPattern = regexPattern.replace(/\*\*/g, DOUBLE_ASTERISK_PLACEHOLDER); + + // Convert * to regex equivalent (any number of characters except /) + regexPattern = regexPattern.replace(/\*/g, '[^/]*'); + + // Convert the ** placeholder to its regex equivalent (any number of characters including /) + regexPattern = regexPattern.replace(new RegExp(DOUBLE_ASTERISK_PLACEHOLDER, 'g'), '.*'); + + // For absolute paths, we don't want to force the pattern to match from the beginning + // but we still want to ensure it matches to the end of the path segment + regexPattern = '(?:^|/)' + regexPattern + '$'; + + return regexPattern; + }).filter(pattern => pattern !== null); + + if (regexPatterns.length === 0) { + // If all patterns were negations, return a regex that matches nothing + return new RegExp('^$'); + } + + // Join all patterns with | to create a single regex + const combinedPattern = regexPatterns.join('|'); + return new RegExp(combinedPattern); +} + +module.exports = { + createIgnoreRegex, + getMeteorIgnoreEntries, + createIgnoreGlobConfig, +}; diff --git a/npm-packages/meteor-rspack/lib/mergeRulesSplitOverlap.js b/npm-packages/meteor-rspack/lib/mergeRulesSplitOverlap.js new file mode 100644 index 0000000000..c7a73234d2 --- /dev/null +++ b/npm-packages/meteor-rspack/lib/mergeRulesSplitOverlap.js @@ -0,0 +1,325 @@ +/** + * Utilities for merging webpack/rspack configurations with special handling for + * overlapping file extensions in module rules. + */ + +const { mergeWithCustomize } = require('webpack-merge'); +const isEqual = require('fast-deep-equal'); + +/** + * File extensions to check when determining rule overlaps. + */ +const EXT_CATALOG = [ + '.tsx', '.ts', '.mts', '.cts', + '.jsx', '.js', '.mjs', '.cjs', +]; + +/** + * Converts rule.test to predicate functions. + * @param {Object} rule - Rule object + * @returns {Function[]} Predicate functions + */ +function testsFrom(rule) { + const t = rule.test; + if (!t) return [() => true]; // no test means match all; you can tighten if you want + const arr = Array.isArray(t) ? t : [t]; + return arr.map(el => { + if (el instanceof RegExp) return (s) => el.test(s); + if (typeof el === 'function') return el; + if (typeof el === 'string') { + // Webpack allows string match; treat as substring + return (s) => s.includes(el); + } + return () => false; + }); +} + +/** + * Checks if rule matches a file extension. + * @param {Object} rule - Rule object + * @param {string} ext - File extension + * @returns {boolean} True if matches + */ +function ruleMatchesExt(rule, ext) { + // simulate a filename to test against + const filename = `x${ext}`; + const preds = testsFrom(rule); + return preds.some(fn => { + try { return !!fn(filename); } catch { return false; } + }); +} + +/** + * Creates regex for matching file extensions. + * @param {string[]} exts - File extensions + * @returns {RegExp} Regex like /\.(js|jsx)$/ + */ +function regexFromExts(exts) { + const body = exts.map(e => e.replace(/^\./, '')).join('|'); + return new RegExp(`\\.(${body})$`, 'i'); +} + +/** + * Clones rule with new test property. + * @param {Object} rule - Rule to clone + * @param {RegExp|Function|string} newTest - New test value + * @returns {Object} Cloned rule + */ +function cloneWithTest(rule, newTest) { + return { ...rule, test: newTest }; +} + +/** + * Merges rules with special handling for overlapping extensions. + * - Replaces overlapping parts with B rules + * - Preserves non-overlapping parts from A rules + * + * @param {Array} aRules - Base rules + * @param {Array} bRules - Rules to merge in + * @returns {Array} Merged rules + */ +function splitOverlapRulesMerge(aRules, bRules) { + const result = [...aRules]; + + for (const bRule of bRules) { + // Try to find an A rule that overlaps B by extensions + let replaced = false; + + for (let i = 0; i < result.length; i++) { + const aRule = result[i]; + + + const isMergeableRule = isEqual(aRule?.include || [], bRule?.include || []); + if (!isMergeableRule) continue; + + // Determine which extensions each rule matches (within our catalog) + const aExts = EXT_CATALOG.filter(ext => ruleMatchesExt(aRule, ext)); + const bExts = EXT_CATALOG.filter(ext => ruleMatchesExt(bRule, ext)); + + if (aExts.length === 0 || bExts.length === 0) { + continue; // nothing meaningful to compare in our catalog + } + + const overlap = aExts.filter(e => bExts.includes(e)); + if (overlap.length === 0) continue; + + // 1) Replace the overlapping A rule with B + result[i] = bRule; + + // 2) Add a "residual" A rule for the non-overlapping extensions + const residual = aExts.filter(e => !overlap.includes(e)); + if (residual.length > 0) { + const residualRule = cloneWithTest(aRule, regexFromExts(residual)); + result.splice(i, 0, residualRule); // keep residual before B, or after—your choice + i++; // skip over the newly inserted residual + } + + replaced = true; + break; + } + + // If we didn’t overlap with any A rule, just add B + if (!replaced) { + result.push(bRule); + } + } + + return result; +} + +/** + * Creates a customizer function for unique plugins. + * + * @param {string} key - The key to check for uniqueness + * @param {string[]} pluginNames - Array of plugin constructor names to make unique + * @param {Function} getter - Function to get the identifier from the plugin + * @returns {Function} Customizer function + */ +function unique(key, pluginNames = [], getter = item => item.constructor && item.constructor.name) { + return (a, b, k) => { + if (k !== key) return undefined; + + const aItems = Array.isArray(a) ? a : []; + const bItems = Array.isArray(b) ? b : []; + + // If not dealing with plugins, return undefined to use default merging + if (key !== 'plugins') return undefined; + + // Create a map to track plugins by their identifier + const uniquePlugins = new Map(); + + // Process all plugins from both arrays + [...aItems, ...bItems].forEach(plugin => { + const id = getter(plugin); + + // If this is a plugin we want to make unique and we can identify it + if (id && pluginNames.includes(id)) { + uniquePlugins.set(id, plugin); // Keep only the last instance + } + }); + + // Create the result array with all non-unique plugins from a + const result = aItems.filter(plugin => { + const id = getter(plugin); + return !id || !pluginNames.includes(id) || uniquePlugins.get(id) === plugin; + }); + + // Add unique plugins from b that weren't already in the result + bItems.forEach(plugin => { + const id = getter(plugin); + if (!id || !pluginNames.includes(id)) { + result.push(plugin); + } else if (uniquePlugins.get(id) === plugin) { + result.push(plugin); + } + }); + + return result; + }; +} + +/** + * Helper function to clean fields in an object based on omit paths. + * Supports nested path strings like 'output.filename'. + * + * @param {Object} obj - The object to clean + * @param {Object} options - Configuration options + * @param {string[]} [options.omitPaths] - Paths to omit from the object (e.g., 'output.filename') + * @param {Function} [options.warningFn] - Custom warning function that receives the path string + * @returns {Object} The cleaned object with specified paths removed + */ +function cleanOmittedPaths(obj, options = {}) { + if (!obj || typeof obj !== 'object') { + return obj; + } + + const { omitPaths = [], warningFn } = options; + + // If no omit paths, return the original object + if (!omitPaths.length) { + return obj; + } + + const result = { ...obj }; + + // Process each omit path + omitPaths.forEach(path => { + // Convert path to array of keys + const pathArray = Array.isArray(path) ? path : path.split('.'); + const pathString = Array.isArray(path) ? path.join('.') : path; + + // Start with the root object + let current = result; + let parent = null; + let lastKey = null; + + // Traverse the path to find the target property + for (let i = 0; i < pathArray.length - 1; i++) { + const key = pathArray[i]; + if (current && typeof current === 'object' && key in current) { + parent = current; + lastKey = key; + current = current[key]; + } else { + // Path doesn't exist in the object, nothing to remove + return; + } + } + + // Get the final key in the path + const finalKey = pathArray[pathArray.length - 1]; + + // Handle single-level paths (from root) + if (pathArray.length === 1) { + const rootKey = pathArray[0]; + if (rootKey in result) { + // Log warning + if (typeof warningFn === 'function') { + warningFn(pathString); + } + delete result[rootKey]; + } + return; + } + + // If we found the property for nested paths, remove it + if (parent && lastKey && finalKey) { + if (current && typeof current === 'object' && finalKey in current) { + // Log warning + if (typeof warningFn === 'function') { + warningFn(pathString); + } + delete current[finalKey]; + } + } + }); + + return result; +} + +/** + * Normalizes externals configuration to ensure consistent handling. + * @param {Object} config - The configuration object + * @returns {Object} - The normalized configuration + */ +function normalizeExternals(config) { + if (!config || !config.externals) return config; + + // Create a deep clone of the config to avoid modifying the original + const result = { ...config }; + + // If externals is not an array, convert it to an array + if (!Array.isArray(result.externals)) { + result.externals = [result.externals]; + } + + return result; +} + +/** + * Merges webpack/rspack configs with smart handling of overlapping rules. + * + * @param {...Object} configs - Configs to merge + * @returns {Object} Merged config + */ +function mergeSplitOverlap(...configs) { + // Normalize externals in all configs before merging + const normalizedConfigs = configs.map(normalizeExternals); + + return mergeWithCustomize({ + customizeArray(a, b, key) { + if (key === 'module.rules') { + const aRules = Array.isArray(a) ? a : []; + const bRules = Array.isArray(b) ? b : []; + return splitOverlapRulesMerge(aRules, bRules); + } + + // Ensure custom extensions first + if (key === 'resolve.extensions') { + const aRules = Array.isArray(a) ? a : []; + const bRules = Array.isArray(b) ? b : []; + const merged = [...bRules, ...aRules]; + return [...new Set(merged)]; + } + + // Handle plugins uniqueness + if (key === 'plugins') { + return unique( + 'plugins', + ['HtmlRspackPlugin', 'RsdoctorRspackPlugin'], + (plugin) => plugin.constructor && plugin.constructor.name + )(a, b, key); + } + + // fall through to default merging + return undefined; + } + })(...normalizedConfigs); +} + +module.exports = { + EXT_CATALOG, + unique, + cleanOmittedPaths, + mergeSplitOverlap +}; diff --git a/npm-packages/meteor-rspack/lib/meteorRspackConfigFactory.js b/npm-packages/meteor-rspack/lib/meteorRspackConfigFactory.js new file mode 100644 index 0000000000..3dbeaf04a5 --- /dev/null +++ b/npm-packages/meteor-rspack/lib/meteorRspackConfigFactory.js @@ -0,0 +1,99 @@ +// meteorRspackConfigFactory.js + +const { mergeSplitOverlap } = require("./mergeRulesSplitOverlap.js"); + +const DEFAULT_PREFIX = "meteorRspackConfig"; +let counter = 0; + +/** + * Create a uniquely keyed Rspack config fragment. + * Example return: { meteorRspackConfig1: { ...customConfig } } + * + * @param {object} customConfig + * @param {{ key?: number|string, prefix?: string }} [opts] + * @returns {Record} + */ +function prepareMeteorRspackConfig(customConfig, opts = {}) { + if (!customConfig || typeof customConfig !== "object") { + throw new TypeError("customConfig must be an object"); + } + const prefix = opts.prefix || DEFAULT_PREFIX; + + let name; + if (opts.key != null) { + const k = String(opts.key).trim(); + if (/^\d+$/.test(k)) name = `${prefix}${k}`; + else if (k.startsWith(prefix) && /^\d+$/.test(k.slice(prefix.length))) + name = k; + else + throw new Error(`opts.key must be a positive integer or "${prefix}"`); + + const n = parseInt(name.slice(prefix.length), 10); + if (Number.isFinite(n) && n > counter) counter = n; + } else { + counter += 1; + name = `${prefix}${counter}`; + } + + return { [name]: customConfig }; +} + +/** + * Merge all `{prefix}` fragments into `config` using `mergeSplitOverlap`, + * then remove those temporary keys. Mutates `config`. + * + * Position-aware merge: + * Walk the config in insertion order and fold: + * - for a fragment key: out = mergeSplitOverlap(out, fragment) + * - for a normal key: out = mergeSplitOverlap(out, { [key]: value }) + * + * Result: fragments behave like spreads at their exact position; + * later inline keys override earlier ones (including fragments). + * + * @param {object} config + * @param {{ prefix?: string }} [opts] + * @returns {object} same (mutated) config + */ +function mergeMeteorRspackFragments(config, opts = {}) { + if (!config || typeof config !== "object" || Array.isArray(config)) { + throw new TypeError("config must be a plain object"); + } + const prefix = opts.prefix || DEFAULT_PREFIX; + + let out = {}; + for (const key of Object.keys(config)) { + const val = config[key]; + + const isFragment = + typeof key === "string" && + key.startsWith(prefix) && + /^\d+$/.test(key.slice(prefix.length)); + + if (isFragment) { + if (!val || typeof val !== "object" || Array.isArray(val)) { + throw new Error(`Fragment "${key}" must be a plain object`); + } + out = mergeSplitOverlap(out, val); + } else { + out = mergeSplitOverlap(out, { [key]: val }); + } + } + + // keep object identity; fragments disappear because `out` doesn't include them + replaceObject(config, out); + return config; +} + +function replaceObject(target, source) { + for (const k of Object.keys(target)) { + if (!(k in source)) delete target[k]; + } + for (const k of Object.keys(source)) { + target[k] = source[k]; + } +} + +module.exports = { + prepareMeteorRspackConfig, + mergeMeteorRspackFragments, +}; diff --git a/npm-packages/meteor-rspack/lib/meteorRspackHelpers.js b/npm-packages/meteor-rspack/lib/meteorRspackHelpers.js new file mode 100644 index 0000000000..1b77f977cb --- /dev/null +++ b/npm-packages/meteor-rspack/lib/meteorRspackHelpers.js @@ -0,0 +1,213 @@ +const path = require("path"); +const { prepareMeteorRspackConfig } = require("./meteorRspackConfigFactory"); +const { builtinModules } = require("module"); + +/** + * Wrap externals for Meteor runtime (marks deps as externals). + * Usage: compileWithMeteor(["sharp", "vimeo", "fs"]) + * + * @param {string[]} deps - package names or module IDs + * @returns {Record} `{ meteorRspackConfigX: { externals: [...] } }` + */ +function compileWithMeteor(deps) { + const flat = deps.flat().filter(Boolean); + return prepareMeteorRspackConfig({ + externals: flat, + }); +} + +/** + * Add SWC transpilation rules limited to specific deps (monorepo-friendly). + * Usage: compileWithRspack(["@org/lib-a", "zod"]) + * + * Requires global `Meteor.swcConfigOptions` (as in your setup). + * + * @param {string[]} deps - package names to include in SWC loader + * @returns {Record} `{ meteorRspackConfigX: { module: { rules: [...] } } }` + */ +function compileWithRspack(deps, { options = {} } = {}) { + const includeDirs = deps.flat().filter(Boolean) + .map(pkg => typeof pkg === 'string' && !pkg.includes('node_modules') + ? path.join(process.cwd(), 'node_modules', pkg) + : pkg + ); + + return prepareMeteorRspackConfig({ + module: { + rules: [ + { + test: /\.(?:[mc]?js|jsx|[mc]?ts|tsx)$/i, + include: includeDirs, + loader: "builtin:swc-loader", + options, + }, + ], + }, + }); +} + +/** + * Enable or disable Rspack cache config + * Usage: setCache(false) + * + * @param {boolean} enabled + * @param {Record} cacheConfig + * @returns {Record} `{ meteorRspackConfigX: { cache: {} } }` + */ +function setCache( + enabled, + cacheConfig = { cache: true, experiments: { cache: true } } +) { + return prepareMeteorRspackConfig( + enabled + ? cacheConfig + : { + cache: false, // disable cache + experiments: { + cache: false, // disable persistent cache (experimental flag) + }, + } + ); +} + +/** + * Build an alias map that disables ALL Node core modules in a web build. + * - Includes both 'fs' and 'node:fs' keys + * - Optional extras let you block non-core modules too + */ +function makeWebNodeBuiltinsAlias(extras = []) { + // Node core list, normalized (strip `node:` prefix) + const core = new Set(builtinModules.map((m) => m.replace(/^node:/, ""))); + + // browser-safe allowlist (these we *don't* mark as false) + const allowlist = new Set([ + "process", + "util", + "events", + "path", + "stream", + "assert", + "assert/strict", + ]); + + const names = new Set(); + for (const m of core) { + // Add both 'fs' and 'node:fs' variants + names.add(m); + names.add(`node:${m}`); + } + for (const x of extras) names.add(x); + + // ❌ Everything except the allowlist gets mapped to false + const entries = [...names] + .filter((m) => !allowlist.has(m.replace(/^node:/, ""))) + .map((m) => [m, false]); + + return Object.fromEntries(entries); +} + +/** + * Enable Rspack split vendor chunk config + * Usage: splitVendorChunk() + * + * @returns {Record} `{ meteorRspackConfigX: { optimization: { ... } } }` + */ +function splitVendorChunk() { + return prepareMeteorRspackConfig({ + optimization: { + splitChunks: { + chunks: "all", // split both sync and async imports + cacheGroups: { + vendor: { + test: /[\\/]node_modules[\\/]/, + name: "vendor", + enforce: true, + priority: 10, + chunks: "all", + }, + }, + }, + }, + }); +} + +/** + * Extend SWC loader config + * Usage: extendSwcConfig() + * + * @returns {Record} `{ meteorRspackConfigX: { optimization: { ... } } }` + */ +function extendSwcConfig(swcConfig) { + return prepareMeteorRspackConfig({ + module: { + rules: [ + { + test: /\.(?:[mc]?js|jsx|[mc]?ts|tsx)$/i, + exclude: /node_modules|\.meteor\/local/, + loader: 'builtin:swc-loader', + options: swcConfig, + }, + ], + }, + }); +} + +/** + * Remove plugins from a Rspack config by name, RegExp, predicate, or array of them. + * When using a function predicate, it receives both the plugin and its index in the plugins array. + * + * @param {object} config Rspack config object + * @param {string | RegExp | ((plugin: any, index: number) => boolean) | Array} matchers + * @returns {object} The modified config object + */ +function disablePlugins(config, matchers) { + if (!config || typeof config !== "object") { + throw new TypeError("disablePlugins: `config` must be an object"); + } + + const plugins = Array.isArray(config.plugins) ? config.plugins : []; + const kept = []; + + const list = Array.isArray(matchers) ? matchers : [matchers]; + + const getPluginName = (p) => { + if (!p) return ""; + return ( + (p.constructor && typeof p.constructor.name === "string" && p.constructor.name) || + (typeof p.name === "string" && p.name) || + (typeof p.pluginName === "string" && p.pluginName) || + (typeof p.__pluginName === "string" && p.__pluginName) || + "" + ); + }; + + const predicates = list.map((m) => { + if (typeof m === "function") return m; + if (m instanceof RegExp) { + return (p) => m.test(getPluginName(p)); + } + if (typeof m === "string") { + return (p) => getPluginName(p) === m; + } + throw new TypeError( + "disablePlugins: matchers must be string, RegExp, function, or array of them" + ); + }); + + config.plugins = plugins.filter((p, index) => { + const matches = predicates.some(fn => fn(p, index)); + return !matches; + }); + + return config; +} + +module.exports = { + compileWithMeteor, + compileWithRspack, + setCache, + splitVendorChunk, + extendSwcConfig, + makeWebNodeBuiltinsAlias, + disablePlugins, +}; diff --git a/npm-packages/meteor-rspack/lib/swc.js b/npm-packages/meteor-rspack/lib/swc.js new file mode 100644 index 0000000000..8b43703059 --- /dev/null +++ b/npm-packages/meteor-rspack/lib/swc.js @@ -0,0 +1,67 @@ +const fs = require('fs'); +const vm = require('vm'); + +/** + * Reads and parses the SWC configuration file. + * @param {string} file - The name of the SWC configuration file (default: '.swcrc') + * @returns {Object|undefined} The parsed SWC configuration or undefined if an error occurs + */ +function getMeteorAppSwcrc(file = '.swcrc') { + try { + const filePath = `${process.cwd()}/${file}`; + if (file.endsWith('.js')) { + let content = fs.readFileSync(filePath, 'utf-8'); + // Check if the content uses ES module syntax (export default) + if (content.includes('export default')) { + // Transform ES module syntax to CommonJS + content = content.replace(/export\s+default\s+/, 'module.exports = '); + } + const script = new vm.Script(` + (function() { + const module = {}; + module.exports = {}; + (function(exports, module) { + ${content} + })(module.exports, module); + return module.exports; + })() + `); + const context = vm.createContext({ process }); + return script.runInContext(context); + } else { + // For .swcrc and other JSON files, parse as JSON + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); + } + } catch (e) { + return undefined; + } +} + +/** + * Checks for SWC configuration files and returns the configuration. + * If the configuration has a baseUrl property, it will be set to process.cwd(). + * @returns {Object|undefined} The SWC configuration or undefined if no configuration exists + */ +function getMeteorAppSwcConfig() { + const hasSwcRc = fs.existsSync(`${process.cwd()}/.swcrc`); + const hasSwcJs = !hasSwcRc && fs.existsSync(`${process.cwd()}/swc.config.js`); + + if (!hasSwcRc && !hasSwcJs) { + return undefined; + } + + const swcFile = hasSwcJs ? 'swc.config.js' : '.swcrc'; + const config = getMeteorAppSwcrc(swcFile); + + // Set baseUrl to process.cwd() if it exists + if (config?.jsc && config.jsc.baseUrl) { + config.jsc.baseUrl = process.cwd(); + } + + return config; +} + +module.exports = { + getMeteorAppSwcrc, + getMeteorAppSwcConfig +}; diff --git a/npm-packages/meteor-rspack/lib/test.js b/npm-packages/meteor-rspack/lib/test.js new file mode 100644 index 0000000000..3c2644b337 --- /dev/null +++ b/npm-packages/meteor-rspack/lib/test.js @@ -0,0 +1,80 @@ +const fs = require('fs'); +const path = require('path'); +const { createIgnoreRegex, createIgnoreGlobConfig } = require("./ignore.js"); + +/** + * Generates eager test files dynamically + * @param {Object} options - Options for generating the test file + * @param {boolean} options.isAppTest - Whether this is an app test + * @param {string} options.projectDir - The project directory + * @param {string} options.buildContext - The build context + * @param {string[]} options.ignoreEntries - Array of ignore patterns + * @param {string} options.extraEntry - Extra entry to load + * @returns {string} The path to the generated file + */ +const generateEagerTestFile = ({ + isAppTest, + projectDir, + buildContext, + ignoreEntries: inIgnoreEntries = [], + prefix: inPrefix = '', + extraEntry, + }) => { + const distDir = path.resolve(projectDir, ".meteor/local/test"); + if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); + } + + // Combine all ignore entries + const ignoreEntries = [ + "**/node_modules/**", + "**/.meteor/**", + "**/public/**", + "**/private/**", + `**/${buildContext}/**`, + ...inIgnoreEntries, + ]; + + // Create regex from ignore entries + const excludeFoldersRegex = createIgnoreRegex( + createIgnoreGlobConfig(ignoreEntries) + ); + + const prefix = (inPrefix && `${inPrefix}-`) || ""; + const filename = isAppTest + ? `${prefix}eager-app-tests.mjs` + : `${prefix}eager-tests.mjs`; + const filePath = path.resolve(distDir, filename); + const regExp = isAppTest + ? "/\\.app-(?:test|spec)s?\\.[^.]+$/" + : "/\\.(?:test|spec)s?\\.[^.]+$/"; + + const content = `{ + const ctx = import.meta.webpackContext('/', { + recursive: true, + regExp: ${regExp}, + exclude: ${excludeFoldersRegex.toString()}, + mode: 'eager', + }); + ctx.keys().forEach(ctx); + ${ + extraEntry + ? `const extra = import.meta.webpackContext('${path.dirname( + extraEntry + )}', { + recursive: false, + regExp: ${new RegExp(`${path.basename(extraEntry)}$`).toString()}, + mode: 'eager', + }); + extra.keys().forEach(extra);` + : '' + } +}`; + + fs.writeFileSync(filePath, content); + return filePath; +}; + +module.exports = { + generateEagerTestFile, +}; diff --git a/npm-packages/meteor-rspack/package-lock.json b/npm-packages/meteor-rspack/package-lock.json new file mode 100644 index 0000000000..7ac96ff574 --- /dev/null +++ b/npm-packages/meteor-rspack/package-lock.json @@ -0,0 +1,5470 @@ +{ + "name": "@meteorjs/rspack", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@meteorjs/rspack", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "ignore-loader": "^0.1.2", + "node-polyfill-webpack-plugin": "^4.1.0", + "webpack-merge": "^6.0.1" + }, + "peerDependencies": { + "@rspack/cli": ">=1.3.0", + "@rspack/core": ">=1.3.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.4.tgz", + "integrity": "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", + "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.3.tgz", + "integrity": "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", + "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", + "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT", + "peer": true + }, + "node_modules/@module-federation/error-codes": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.16.0.tgz", + "integrity": "sha512-TfmA45b8vvISniGudMg8jjIy1q3tLPon0QN/JdFp5f8AJ8/peICN5b+dkEQnWsAVg2fEusYhk9dO7z3nUeJM8A==", + "license": "MIT", + "peer": true + }, + "node_modules/@module-federation/runtime": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.16.0.tgz", + "integrity": "sha512-6o84WI8Qhc9O3HwPLx89kTvOSkyUOHQr73R/zr0I04sYhlMJgw5xTwXeGE7bQAmNgbJclzW9Kh7JTP7+3o3CHg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/error-codes": "0.16.0", + "@module-federation/runtime-core": "0.16.0", + "@module-federation/sdk": "0.16.0" + } + }, + "node_modules/@module-federation/runtime-core": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.16.0.tgz", + "integrity": "sha512-5SECQowG4hlUVBRk/y6bnYLfxbsl5NcMmqn043WPe7NDOhGQWbTuYibJ3Bk+ZBv5U4uYLEmXipBGDc1FKsHklQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/error-codes": "0.16.0", + "@module-federation/sdk": "0.16.0" + } + }, + "node_modules/@module-federation/runtime-tools": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.16.0.tgz", + "integrity": "sha512-OzmXNluXBQ2E6znzX4m9CJt1MFHVGmbN8c8MSKcYIDcLzLSKBQAiaz9ZUMhkyWx2YrPgD134glyPEqJrc+fY8A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/runtime": "0.16.0", + "@module-federation/webpack-bundler-runtime": "0.16.0" + } + }, + "node_modules/@module-federation/sdk": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.16.0.tgz", + "integrity": "sha512-UXJW1WWuDoDmScX0tpISjl4xIRPzAiN62vg9etuBdAEUM+ja9rz/zwNZaByiUPFS2aqlj2RHenCRvIapE8mYEg==", + "license": "MIT", + "peer": true + }, + "node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.16.0.tgz", + "integrity": "sha512-yqIDQTelJZP0Rxml0OXv4Er8Kbdxy7NFh6PCzPwDFWI1SkiokJ3uXQJBvtlxZ3lOnCDYOzdHstqa8sJG4JP02Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/runtime": "0.16.0", + "@module-federation/sdk": "0.16.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "license": "MIT", + "peer": true + }, + "node_modules/@rspack/binding": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.4.8.tgz", + "integrity": "sha512-VKE+2InUdudBUOn3xMZfK9a6KlOwmSifA0Nupjsh7N9/brcBfJtJGSDCnfrIKCq54FF+QAUCgcNAS0DB4/tZmw==", + "license": "MIT", + "peer": true, + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.4.8", + "@rspack/binding-darwin-x64": "1.4.8", + "@rspack/binding-linux-arm64-gnu": "1.4.8", + "@rspack/binding-linux-arm64-musl": "1.4.8", + "@rspack/binding-linux-x64-gnu": "1.4.8", + "@rspack/binding-linux-x64-musl": "1.4.8", + "@rspack/binding-wasm32-wasi": "1.4.8", + "@rspack/binding-win32-arm64-msvc": "1.4.8", + "@rspack/binding-win32-ia32-msvc": "1.4.8", + "@rspack/binding-win32-x64-msvc": "1.4.8" + } + }, + "node_modules/@rspack/binding-darwin-arm64": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.4.8.tgz", + "integrity": "sha512-PQRNjC3Fc0avpx8Gk+sT5P+HAXxTSzmBA8lU7QLlmbW5GGXO2taVhNstbZ4oxyIX5uDVZpQ2yQ2E0zXirK6/UQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rspack/binding-darwin-x64": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.4.8.tgz", + "integrity": "sha512-ZnPZbo1dhhbfevxSS99y8w02xuEbxyiV1HaUie/S8jzy9DPmk+4Br+DddufnibPNU85e3BZKjp+HDFMYkdn6cg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.4.8.tgz", + "integrity": "sha512-mJK9diM4Gd8RIGO90AZnl27WwUuAOoRplPQv9G+Vxu2baCt1xE1ccf8PntIJ70/rMgsUdnmkR5qQBaGxhAMJvA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.4.8.tgz", + "integrity": "sha512-+n9QxeDDZKwVB4D6cwpNRJzsCeuwNqd/fwwbMQVTctJ+GhIHlUPsE8y5tXN7euU7kDci81wMBBFlt6LtXNcssA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.4.8.tgz", + "integrity": "sha512-rEypDlbIfv9B/DcZ2vYVWs56wo5VWE5oj/TvM9JT+xuqwvVWsN/A2TPMiU6QBgOKGXat3EM/MEgx8NhNZUpkXg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.4.8.tgz", + "integrity": "sha512-o9OsvJ7olH0JPU9exyIaYTNQ+aaR5CNAiinkxr+LkV2i3DMIi/+pDVveDiodYjVhzZjWfsP/z8QPO4c6Z06bEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rspack/binding-wasm32-wasi": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.4.8.tgz", + "integrity": "sha512-hF5gqT0aQ66VUclM2A9MSB6zVdEJqzp++TAXaShBK/eVBI0R4vWrMfJ2TOdzEsSbg4gXgeG4swURpHva3PKbcA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.12" + } + }, + "node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.4.8.tgz", + "integrity": "sha512-umD0XzesJq4nnStv9/2/VOmzNUWHfLMIjeHmiHYHpc7iVC0SkXgIdc6Ac7c+g2q7/V3/MFxL66Y60oy7lQE3fg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.4.8.tgz", + "integrity": "sha512-Uu+F/sxz7GgIMbuCCZVOD1HPjoHQdyrFHi/TE2EmuZzs9Ji9a9mtNJNrKc8+h9YFpaLeade7cbMDjRu4MHxiVA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.4.8.tgz", + "integrity": "sha512-BVkOfJDZnexHNpGgc/sWENyGrsle1jUQTeUEdSyNYsu4Elsgk/T9gnGK8xyLRd2c6k20M5FN38t0TumCp4DscQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rspack/cli": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/cli/-/cli-1.4.8.tgz", + "integrity": "sha512-rqQ8iI/zKaT+xiETFQvzzZI4Bpx5hk0IR4BXJwiR/llPQLN/oc1saKyatsn2/p4r0+ABLMftdzKPv6FzIvnzZA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.7", + "@rspack/dev-server": "~1.1.3", + "colorette": "2.0.20", + "exit-hook": "^4.0.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-bundle-analyzer": "4.10.2", + "yargs": "17.7.2" + }, + "bin": { + "rspack": "bin/rspack.js" + }, + "peerDependencies": { + "@rspack/core": "^1.0.0-alpha || ^1.x" + } + }, + "node_modules/@rspack/core": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.4.8.tgz", + "integrity": "sha512-ARHuZ+gx3P//RIUKSjk/riQUn/D5tCwCWbfgeM5pk/Ti2JsgVnqiP9Sksge8JovVPf7b6Zgw73Cq5FpX4aOXeQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@module-federation/runtime-tools": "0.16.0", + "@rspack/binding": "1.4.8", + "@rspack/lite-tapable": "1.0.1" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@rspack/dev-server": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rspack/dev-server/-/dev-server-1.1.3.tgz", + "integrity": "sha512-jWPeyiZiGpbLYGhwHvwxhaa4rsr8CQvsWkWslqeMLb2uXwmyy3UWjUR1q+AhAPnf0gs3lZoFZ1hjBQVecHKUvg==", + "license": "MIT", + "peer": true, + "dependencies": { + "chokidar": "^3.6.0", + "http-proxy-middleware": "^2.0.9", + "p-retry": "^6.2.0", + "webpack-dev-server": "5.2.2", + "ws": "^8.18.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@rspack/core": "*" + } + }, + "node_modules/@rspack/lite-tapable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz", + "integrity": "sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.16", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/node": { + "version": "24.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", + "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.13.tgz", + "integrity": "sha512-zePQJSW5QkwSHKRApqWCVKeKoSOt4xvEnLENZPjyvm9Ezdf/EyDeJM7jqLzOwjVICQQzvLZ63T55MKdJB5H6ww==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "peer": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "peer": true, + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "peer": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT", + "peer": true + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz", + "integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "license": "MIT", + "peer": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "peer": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "license": "MIT", + "dependencies": { + "resolve": "^1.17.0" + } + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", + "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.2", + "browserify-rsa": "^4.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.6.1", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.9", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT", + "peer": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001754", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "peer": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT", + "peer": true + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", + "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT", + "peer": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "license": "MIT", + "peer": true + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", + "peer": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT", + "peer": true + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/domain-browser": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz", + "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "license": "MIT", + "peer": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT", + "peer": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.245", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.245.tgz", + "integrity": "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ==", + "license": "ISC", + "peer": true + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "peer": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT", + "peer": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT", + "peer": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT", + "peer": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/exit-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-4.0.0.tgz", + "integrity": "sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.22", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "peer": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "peer": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "peer": true + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "license": "MIT", + "peer": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT", + "peer": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "license": "MIT", + "peer": true + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "license": "MIT", + "peer": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT", + "peer": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "license": "MIT" + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore-loader": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ignore-loader/-/ignore-loader-0.1.2.tgz", + "integrity": "sha512-yOJQEKrNwoYqrWLS4DcnzM7SEQhRKis5mB+LdKKh4cPmGYlLPR0ozRzHV5jmEk2IxptqJNQA5Cc0gw8Fj12bXA==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "peer": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-timers-promises": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz", + "integrity": "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "peer": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", + "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/media-typer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", + "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT", + "peer": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "peer": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", + "peer": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT", + "peer": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "peer": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-polyfill-webpack-plugin": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/node-polyfill-webpack-plugin/-/node-polyfill-webpack-plugin-4.1.0.tgz", + "integrity": "sha512-b4ei444EKkOagG/yFqojrD3QTYM5IOU1f8tn9o6uwrG4qL+brI7oVhjPVd0ZL2xy+Z6CP5bu9w8XTvlWgiXHcw==", + "license": "MIT", + "dependencies": { + "node-stdlib-browser": "^1.3.0", + "type-fest": "^4.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "webpack": ">=5" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT", + "peer": true + }, + "node_modules/node-stdlib-browser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-stdlib-browser/-/node-stdlib-browser-1.3.1.tgz", + "integrity": "sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw==", + "license": "MIT", + "dependencies": { + "assert": "^2.0.0", + "browser-resolve": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^5.7.1", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "create-require": "^1.1.1", + "crypto-browserify": "^3.12.1", + "domain-browser": "4.22.0", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "isomorphic-timers-promises": "^1.0.1", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "pkg-dir": "^5.0.0", + "process": "^0.11.10", + "punycode": "^1.4.1", + "querystring-es3": "^0.2.1", + "readable-stream": "^3.6.0", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.1", + "url": "^0.11.4", + "util": "^0.12.4", + "vm-browserify": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT", + "peer": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "peer": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "license": "(WTFPL OR MIT)", + "peer": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT", + "peer": true + }, + "node_modules/pbkdf2": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC", + "peer": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "peer": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT", + "peer": true + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/ripemd160/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/ripemd160/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ripemd160/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "peer": true + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "license": "MIT", + "peer": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC", + "peer": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "license": "ISC", + "peer": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC", + "peer": true + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy-transport/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/spdy/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "license": "MIT", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT", + "peer": true + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "license": "Unlicense", + "peer": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT", + "peer": true + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "license": "MIT", + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-dump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", + "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "license": "MIT" + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT", + "peer": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "license": "MIT" + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "license": "MIT", + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webpack": { + "version": "5.102.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", + "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.26.3", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "license": "MIT", + "peer": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", + "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.21.2", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/npm-packages/meteor-rspack/package.json b/npm-packages/meteor-rspack/package.json new file mode 100644 index 0000000000..92c294f62c --- /dev/null +++ b/npm-packages/meteor-rspack/package.json @@ -0,0 +1,19 @@ +{ + "name": "@meteorjs/rspack", + "version": "1.0.0", + "description": "Configuration logic for using Rspack in Meteor projects", + "main": "index.js", + "type": "commonjs", + "author": "", + "license": "ISC", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "ignore-loader": "^0.1.2", + "node-polyfill-webpack-plugin": "^4.1.0", + "webpack-merge": "^6.0.1" + }, + "peerDependencies": { + "@rspack/cli": ">=1.3.0", + "@rspack/core": ">=1.3.0" + } +} diff --git a/npm-packages/meteor-rspack/plugins/AssetExternalsPlugin.js b/npm-packages/meteor-rspack/plugins/AssetExternalsPlugin.js new file mode 100644 index 0000000000..ca3d33879c --- /dev/null +++ b/npm-packages/meteor-rspack/plugins/AssetExternalsPlugin.js @@ -0,0 +1,40 @@ +// AssetExternalsPlugin.js +// +// This plugin externalizes assets within CSS/SCSS and other files. +// It prevents Rspack from bundling assets referenced in CSS url() and similar contexts, +// allowing them to be served directly from the public directory. + +// Regular expression to match CSS, SCSS, and other style files +const CSS_EXT_REGEX = /\.(css|scss|sass|less|styl)$/; + +class AssetExternalsPlugin { + constructor(options = {}) { + this.pluginName = 'AssetExternalsPlugin'; + this.options = options; + } + + apply(compiler) { + // Add the externals function to handle asset URLs in CSS files + compiler.options.externals = [ + ...compiler.options.externals || [], + (data, callback) => { + const req = data.request; + + // Webpack provides dependencyType === "url" for CSS url() deps. + // Rspack is webpack-compatible here, but keep this tolerant. + const isUrlDep = data.dependencyType === 'url'; + const issuer = data.contextInfo?.issuer || ''; + const fromCss = CSS_EXT_REGEX.test(issuer); + + if (req && req.startsWith('/') && (isUrlDep || fromCss)) { + // Keep the URL as-is (served by your server from /public) + return callback(null, `asset ${req}`); + } + + callback(); + } + ]; + } +} + +module.exports = { AssetExternalsPlugin }; diff --git a/npm-packages/meteor-rspack/plugins/HtmlRspackPlugin.js b/npm-packages/meteor-rspack/plugins/HtmlRspackPlugin.js new file mode 100644 index 0000000000..5af36fc29e --- /dev/null +++ b/npm-packages/meteor-rspack/plugins/HtmlRspackPlugin.js @@ -0,0 +1,31 @@ +const RspackMeteorHtmlPlugin = require('./RspackMeteorHtmlPlugin.js'); +const { loadHtmlRspackPluginFromHost } = RspackMeteorHtmlPlugin; + +/** + * A plugin that composes the original HtmlRspackPlugin from @rspack/core + * and RspackMeteorHtmlPlugin, in that order. + */ +class HtmlRspackPlugin { + constructor(options = {}) { + this.options = options; + } + + apply(compiler) { + // Load the original HtmlRspackPlugin from the host project + const OriginalHtmlRspackPlugin = loadHtmlRspackPluginFromHost(compiler); + + if (!OriginalHtmlRspackPlugin) { + throw new Error('Could not load HtmlRspackPlugin from host project.'); + } + + // Apply the original HtmlRspackPlugin + const originalPlugin = new OriginalHtmlRspackPlugin(this.options); + originalPlugin.apply(compiler); + + // Apply the RspackMeteorHtmlPlugin + const meteorPlugin = new RspackMeteorHtmlPlugin(); + meteorPlugin.apply(compiler); + } +} + +module.exports = HtmlRspackPlugin; diff --git a/npm-packages/meteor-rspack/plugins/RequireExtenalsPlugin.js b/npm-packages/meteor-rspack/plugins/RequireExtenalsPlugin.js new file mode 100644 index 0000000000..fb400a4382 --- /dev/null +++ b/npm-packages/meteor-rspack/plugins/RequireExtenalsPlugin.js @@ -0,0 +1,496 @@ +// RequireExternalsPlugin.js +// +// This plugin prepare the require of externals used to be lazy required by Meteor bundler. +// +// It can describe additional externals using the externals option by array, RegExp or function. +// These externals will be lazy required as well, and optionally could be resolved using +// the externalMap function if provided. +// Used for Blaze to translate require of html files to require of js files bundled by Meteor. + +const fs = require('fs'); +const path = require('path'); + +class RequireExternalsPlugin { + constructor({ + filePath, + // Externals can be: + // - An array of strings: module name must be included in the array + // - A RegExp: module name must match the regex + // - A function: function(name) must return true for the module name + externals = null, + // ExternalMap is a function that receives the request object and returns the external request path + // It can be used to customize how external modules are mapped to file paths + // If not provided, the default behavior is to map the external module name. + externalMap = null, + // Enable global polyfill for module and exports + // If true, globalThis.module and globalThis.exports will be defined if they don't exist + enableGlobalPolyfill = true, + // Check function to determine if an external import should be eager + // If provided, it will be called with the package name and should return true for eager imports + // If not provided or returns false, the import will be lazy (default behavior) + isEagerImport = null, + // Array of module paths that should always be imported at the end of the file + // These will be treated as eager imports but will always be placed after all other imports + lastImports = null, + } = {}) { + this.pluginName = 'RequireExternalsPlugin'; + + // Prepare externals + this._externals = externals; + this._externalMap = externalMap; + this._enableGlobalPolyfill = enableGlobalPolyfill; + this._isEagerImport = isEagerImport; + this._lastImports = lastImports; + this._defaultExternalPrefix = 'external '; + + // Prepare paths + this.filePath = path.resolve(process.cwd(), filePath); + this.backRoot = '../'.repeat( + filePath.replace(/^\.?\/+/, '').split('/').length - 1 + ); + + // Initialize funcCount based on existing helpers in the file + this._funcCount = this._computeNextFuncCount(); + } + + // Helper method to check if a module name matches the externals or default prefix + _isExternalModule(name) { + if (typeof name !== 'string') return false; + + // Check externals if provided + if (this._externals) { + // If externals is an array, use includes method + if (Array.isArray(this._externals)) { + if (this._externals.includes(name)) { + return { isExternal: true, type: 'externals', value: name }; + } + } + // If externals is a RegExp, use test method + else if (this._externals instanceof RegExp) { + if (this._externals.test(name)) { + return { isExternal: true, type: 'externals', value: name }; + } + } + // If externals is a function, call it with the name + else if (typeof this._externals === 'function') { + if (this._externals(name)) { + return { isExternal: true, type: 'externals', value: name }; + } + } + } + + if (name.startsWith(this._defaultExternalPrefix)) { + return { isExternal: true, type: 'prefix', value: name }; + } + + return { isExternal: false }; + } + + // Helper method to extract package name from module name + _extractPackageName(name) { + let pkg = name.slice(this._defaultExternalPrefix.length); + if (pkg.startsWith('"') && pkg.endsWith('"')) pkg = pkg.slice(1, -1); + const depInfo = path.parse(name); + // If the extracted package name is a path, use the path as is + if ( + pkg && + (path.isAbsolute(pkg) || + pkg.startsWith('./') || + pkg.startsWith('../') || + !!depInfo.ext) + ) { + const module = this.externalsMeta.get(pkg); + if (module) { + return `${this.backRoot}${module.relativeRequest}`; + } + return `${this.backRoot}${name}`; + } + + return pkg; + } + + apply(compiler) { + // Initialize externalsMeta if it doesn't exist + this.externalsMeta = this.externalsMeta || new Map(); + + // Only set compiler.options.externals if both externals and externalMap are defined + if (this._externals && this._externalMap) { + compiler.options.externals = [ + ...compiler.options.externals || [], + (module, callback) => { + const { request, context } = module; + const matchInfo = this._isExternalModule(request); + if (matchInfo.isExternal) { + + let externalRequest; + // Use externalMap function if provided + if (this._externalMap && typeof this._externalMap === 'function') { + externalRequest = this._externalMap(module); + + const relContext = path.relative(process.cwd(), context); + // Store the original request to resolve properly the lazy html require later + this.externalsMeta.set(externalRequest, { + originalRequest: request, + externalRequest, + relativeRequest: path.join(relContext, request), + }); + + // tell Rspack "don't bundle this, import it at runtime" + return callback(null, externalRequest); + } + } + + callback(); // otherwise normal resolution + } + ]; + } + + compiler.hooks.done.tap({ name: this.pluginName, stage: -10 }, (stats) => { + // 1) Ensure globalThis.module / exports block is present if enabled + if (this._enableGlobalPolyfill) { + this._ensureGlobalThisModule(); + } + + // 2) Re-load existing requires from disk on every run + const existing = this._readExistingRequires(); + + // 2a) Compute the *current* externals in this build + const info = stats.toJson({ modules: true }); + const current = new Set(); + for (const m of info.modules) { + const matchInfo = this._isExternalModule(m.name); + if (matchInfo.isExternal) { + const pkg = this._extractPackageName(m.name, matchInfo); + if (pkg) { + current.add(pkg); + } + } + } + + // 2b) Remove any requires that are no longer in `current` + const toRemove = [...existing].filter(p => !current.has(p)); + if (toRemove.length) { + let content = fs.readFileSync(this.filePath, 'utf-8'); + + // Strip stale require(...) lines + for (const pkg of toRemove) { + const re = new RegExp(`^.*require\\('${pkg}'\\);?.*(\\r?\\n)?`, 'gm'); + content = content.replace(re, ''); + } + + // Strip out any now-empty helper functions: + // function lazyExternalImportsX() { + // } + // or new format: + // // (function eagerExternalImportsX() { + // // }) + // or lastImports format: + // // (function lastImports() { + // // }) + const emptyLazyFnRe = /^function\s+lazyExternalImports\d+\s*\(\)\s*{\s*}\s*(\r?\n)?/gm; + const emptyEagerFnRe = /^\/\/\s*\(function\s+eagerExternalImports\d+\s*\(\)\s*{\s*\n\/\/\s*\}\)\s*(\r?\n)?/gm; + const emptyLastFnRe = /^\/\/\s*\(function\s+lastImports(?:\d+)?\s*\(\)\s*{\s*\n\/\/\s*\}\)\s*(\r?\n)?/gm; + content = content.replace(emptyLazyFnRe, ''); + content = content.replace(emptyEagerFnRe, ''); + content = content.replace(emptyLastFnRe, ''); + + // Write the cleaned file back + fs.writeFileSync(this.filePath, content, 'utf-8'); + + // Re-populate `existing` so the add-diff is accurate + existing.clear(); + // Check for require statements + for (const match of content.matchAll(/require\('([^']+)'\)/g)) { + existing.add(match[1]); + } + // Also check for import statements (used in the new format) + for (const match of content.matchAll(/import\s+'([^']+)'/g)) { + existing.add(match[1]); + } + } + + // 3) Collect any new externals from this build and separate into eager, lazy, and last + const newLazyRequires = []; + const newEagerRequires = []; + const newLastRequires = []; + + for (const module of info.modules) { + const name = module.name; + const matchInfo = this._isExternalModule(name); + if (!matchInfo.isExternal) continue; + + const pkg = this._extractPackageName(name, matchInfo); + if (pkg && !existing.has(pkg)) { + existing.add(pkg); + + // Check if this should be a last import + if (this._lastImports && Array.isArray(this._lastImports) && this._lastImports.includes(pkg)) { + newLastRequires.push(`require('${pkg}')`); + } + // Check if this should be an eager import + else if (this._isEagerImport && typeof this._isEagerImport === 'function' && this._isEagerImport(pkg)) { + newEagerRequires.push(`require('${pkg}')`); + } else { + // Default to lazy import + newLazyRequires.push(`require('${pkg}')`); + } + } + } + + // 4) Append new lazy imports if any + if (newLazyRequires.length) { + const fnName = `lazyExternalImports${this._funcCount++}`; + const body = newLazyRequires.map(req => ` ${req};`).join('\n'); + const fnCode = `\nfunction ${fnName}() {\n${body}\n}\n`; + try { + fs.appendFileSync(this.filePath, fnCode); + } catch (err) { + console.error(`Failed to append lazy imports to ${this.filePath}:`, err); + } + } + + // 5) Append new eager imports if any + if (newEagerRequires.length) { + const fnName = `eagerExternalImports${this._funcCount++}`; + // Convert require statements to import statements + const body = newEagerRequires + .map(req => { + // Extract the module path from require('path') + const modulePath = req.match(/require\('([^']+)'\)/)[1]; + return `import '${modulePath}';`; + }) + .join('\n'); + // Use comments instead of actual function + const fnCode = `\n// (function ${fnName}() {\n${body}\n// })\n`; + try { + fs.appendFileSync(this.filePath, fnCode); + } catch (err) { + console.error(`Failed to append eager imports to ${this.filePath}:`, err); + } + } + + // 6) Handle lastImports - these should always be at the end of the file + // First, check if lastImports already exist in the file + let lastImportsExist = false; + let lastImportsAtEnd = false; + let content = ''; + + if (fs.existsSync(this.filePath)) { + content = fs.readFileSync(this.filePath, 'utf-8'); + + // Check if lastImports exist in the file + const lastImportsRe = /\/\/\s*\(function\s+lastImports(?:\d+)?\s*\(\)\s*{\s*\n([\s\S]*?)\/\/\s*\}\)/g; + const match = lastImportsRe.exec(content); + + if (match) { + lastImportsExist = true; + + // Check if lastImports are at the end of the file + // We'll consider them at the end if there's only whitespace after them + const afterLastImports = content.substring(match.index + match[0].length); + if (/^\s*$/.test(afterLastImports)) { + lastImportsAtEnd = true; + } + } + } + + // If lastImports exist but are not at the end, move them to the end + if (lastImportsExist && !lastImportsAtEnd) { + // Remove the existing lastImports + const lastImportsRe = /\/\/\s*\(function\s+lastImports(?:\d+)?\s*\(\)\s*{\s*\n[\s\S]*?\/\/\s*\}\)\s*(\r?\n)?/g; + content = content.replace(lastImportsRe, ''); + + // Extract the imports from the existing lastImports + const importRe = /import\s+'([^']+)'/g; + const existingLastImports = []; + let match; + + while ((match = importRe.exec(content)) !== null) { + if (this._lastImports && Array.isArray(this._lastImports) && this._lastImports.includes(match[1])) { + existingLastImports.push(`import '${match[1]}';`); + } + } + + // Add any new lastImports + if (this._lastImports && Array.isArray(this._lastImports)) { + for (const pkg of this._lastImports) { + if (!existingLastImports.some(imp => imp === `import '${pkg}';`) && existing.has(pkg)) { + existingLastImports.push(`import '${pkg}';`); + } + } + } + + // Add the lastImports to the end of the file + if (existingLastImports.length > 0) { + const body = existingLastImports.join('\n'); + const fnCode = `\n// (function lastImports() {\n${body}\n// })\n`; + fs.writeFileSync(this.filePath, content + fnCode); + } else { + fs.writeFileSync(this.filePath, content); + } + } + // If lastImports don't exist, add them if needed + else if (!lastImportsExist) { + // Collect all lastImports + const allLastImports = []; + + // Add any new lastImports from this build + if (newLastRequires.length) { + for (const req of newLastRequires) { + const modulePath = req.match(/require\('([^']+)'\)/)[1]; + allLastImports.push(`import '${modulePath}';`); + } + } + + // Add any existing lastImports from the configuration + if (this._lastImports && Array.isArray(this._lastImports)) { + for (const pkg of this._lastImports) { + if (!allLastImports.some(imp => imp === `import '${pkg}';`) && !existing.has(pkg)) { + allLastImports.push(`import '${pkg}';`); + } + } + } + + // Add the lastImports to the end of the file + if (allLastImports.length > 0) { + const body = allLastImports.join('\n'); + const fnCode = `\n// (function lastImports() {\n${body}\n// })\n`; + try { + fs.appendFileSync(this.filePath, fnCode); + } catch (err) { + console.error(`Failed to append last imports to ${this.filePath}:`, err); + } + } + } + // If lastImports exist and are already at the end, add any new ones + else if (lastImportsExist && lastImportsAtEnd && newLastRequires.length) { + // Extract the existing lastImports + const lastImportsRe = /\/\/\s*\(function\s+lastImports(?:\d+)?\s*\(\)\s*{\s*\n([\s\S]*?)\/\/\s*\}\)/; + const match = lastImportsRe.exec(content); + + if (match) { + const existingBody = match[1]; + const existingImports = new Set(); + + // Extract the imports from the existing lastImports + const importRe = /import\s+'([^']+)'/g; + let importMatch; + + while ((importMatch = importRe.exec(existingBody)) !== null) { + existingImports.add(importMatch[1]); + } + + // Add any new lastImports + let newBody = existingBody; + for (const req of newLastRequires) { + const modulePath = req.match(/require\('([^']+)'\)/)[1]; + if (!existingImports.has(modulePath)) { + newBody += `import '${modulePath}';\n`; + } + } + + // Replace the existing lastImports with the updated ones + const updatedContent = content.replace( + lastImportsRe, + `// (function lastImports() {\n${newBody}// })` + ); + + fs.writeFileSync(this.filePath, updatedContent); + } + } + }); + } + + _computeNextFuncCount() { + let max = 0; + if (fs.existsSync(this.filePath)) { + try { + const content = fs.readFileSync(this.filePath, 'utf-8'); + // Check for lazy, eager, and last external imports functions + const lazyFnRe = /function\s+lazyExternalImports(\d+)\s*\(\)/g; + // Only match the new commented format + const eagerFnRe = /\/\/\s*\(function\s+eagerExternalImports(\d+)\s*\(\)/g; + // Match the lastImports format + const lastFnRe = /\/\/\s*\(function\s+lastImports(\d+)?\s*\(\)/g; + + let match; + // Check lazy imports + while ((match = lazyFnRe.exec(content)) !== null) { + const n = parseInt(match[1], 10); + if (n > max) max = n; + } + + // Check eager imports + while ((match = eagerFnRe.exec(content)) !== null) { + const n = parseInt(match[1], 10); + if (n > max) max = n; + } + + // Check last imports + while ((match = lastFnRe.exec(content)) !== null) { + if (match[1]) { + const n = parseInt(match[1], 10); + if (n > max) max = n; + } + } + } catch { + // ignore read errors + } + } + // next count is max found plus one + return max + 1; + } + + _ensureGlobalThisModule() { + const block = [ + `/* Polyfill globalThis.module, exports & module for legacy */`, + `if (typeof globalThis !== 'undefined') {`, + ` if (typeof globalThis.module === 'undefined') {`, + ` globalThis.module = { exports: {} };`, + ` }`, + ` if (typeof globalThis.exports === 'undefined') {`, + ` globalThis.exports = globalThis.module.exports;`, + ` }`, + `}`, + `if (typeof window.module === 'undefined') {`, + ` window.module = { exports: {} };`, + `}`, + ].join('\n') + '\n'; + + let content = ''; + if (fs.existsSync(this.filePath)) { + content = fs.readFileSync(this.filePath, 'utf-8'); + if (!content.includes(`typeof globalThis.module === 'undefined'`)) { + // Prepend so it lives at the very top + fs.writeFileSync(this.filePath, content + '\n' + block, 'utf-8'); + } + } else { + // File doesn’t exist yet: create with just the block + fs.writeFileSync(this.filePath, block, 'utf-8'); + } + } + + _readExistingRequires() { + const existing = new Set(); + try { + const content = fs.readFileSync(this.filePath, 'utf-8'); + // Check for require statements + const requireRegex = /require\('([^']+)'\)/g; + let match; + while ((match = requireRegex.exec(content)) !== null) { + existing.add(match[1]); + } + + // Also check for import statements (used in the new format) + const importRegex = /import\s+'([^']+)'/g; + while ((match = importRegex.exec(content)) !== null) { + existing.add(match[1]); + } + } catch { + // ignore if file missing or unreadable + } + return existing; + } +} + +module.exports = { RequireExternalsPlugin }; diff --git a/npm-packages/meteor-rspack/plugins/RspackMeteorHtmlPlugin.js b/npm-packages/meteor-rspack/plugins/RspackMeteorHtmlPlugin.js new file mode 100644 index 0000000000..0e1ec8dd59 --- /dev/null +++ b/npm-packages/meteor-rspack/plugins/RspackMeteorHtmlPlugin.js @@ -0,0 +1,50 @@ +const path = require('node:path'); +const { createRequire } = require('node:module'); + +function loadHtmlRspackPluginFromHost(compiler) { + // Prefer the compiler's context; fall back to process.cwd() + const ctx = compiler.options?.context || compiler.context || process.cwd(); + const requireFromHost = createRequire(path.join(ctx, 'package.json')); + + const core = requireFromHost('@rspack/core'); // host's instance + // Rspack exports can be shaped a couple ways; be defensive + return core.HtmlRspackPlugin || core.rspack?.HtmlRspackPlugin || core.default?.HtmlRspackPlugin; +} + +/** + * Rspack plugin to: + * 1. Remove the injected `*-rspack.js` script tags + * 2. Strip and … wrappers from the final HTML + */ +class RspackMeteorHtmlPlugin { + apply(compiler) { + const HtmlRspackPlugin = loadHtmlRspackPluginFromHost(compiler); + if (!HtmlRspackPlugin?.getCompilationHooks) { + throw new Error('Could not load HtmlRspackPlugin from host project.'); + } + + compiler.hooks.compilation.tap('RspackMeteorHtmlPlugin', compilation => { + const hooks = HtmlRspackPlugin.getCompilationHooks(compilation); + + // remove ")({ + src: process.env.METEOR_APP_CUSTOM_SCRIPT_URL + }) + : '', '', '', '', diff --git a/packages/check/check.d.ts b/packages/check/check.d.ts index 607528c3d2..84e2239ed0 100644 --- a/packages/check/check.d.ts +++ b/packages/check/check.d.ts @@ -66,6 +66,7 @@ export namespace Match { function Where(condition: (val: any) => val is T): Matcher; function Where(condition: (val: any) => boolean): Matcher; + var NonEmptyString: Matcher; /** * Returns true if the value matches the pattern. * @param value The value to check diff --git a/packages/check/match.js b/packages/check/match.js index d2836c701e..20f534c2e8 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -17,6 +17,11 @@ const format = result => { return err; } +function nonEmptyStringCondition(value) { + check(value, String); + return value.length > 0; +} + /** * @summary Check that a value matches a [pattern](#matchpatterns). * If the value does not match the pattern, throw a `Match.Error`. @@ -77,6 +82,8 @@ export const Match = { return new Where(condition); }, + NonEmptyString: ['__NonEmptyString__'], + ObjectIncluding: function(pattern) { return new ObjectIncluding(pattern) }, @@ -204,6 +211,7 @@ const stringForErrorMessage = (value, options = {}) => { return EJSON.stringify(value); }; + const typeofChecks = [ [String, 'string'], [Number, 'number'], @@ -283,6 +291,11 @@ const testSubtree = (value, pattern, collectErrors = false, errors = [], path = if (pattern === Object) { pattern = Match.ObjectIncluding({}); } + // This must be invoked before pattern instanceof Array as strings are regarded as arrays + // We invoke the pattern as IIFE so that `pattern isntanceof Where` catches it + if (pattern === Match.NonEmptyString) { + pattern = new Where(nonEmptyStringCondition); + } // Array (checked AFTER Any, which is implemented as an Array). if (pattern instanceof Array) { diff --git a/packages/check/match_test.js b/packages/check/match_test.js index 07771c9963..2aaa21cae7 100644 --- a/packages/check/match_test.js +++ b/packages/check/match_test.js @@ -175,7 +175,8 @@ Tinytest.add('check - check', test => { fails(true, false); fails(true, 'true'); fails('false', false); - + matches('xx', Match.NonEmptyString); + fails('', Match.NonEmptyString); matches(/foo/, RegExp); fails(/foo/, String); matches(new Date, Date); @@ -787,4 +788,4 @@ Tinytest.add( test.equal(new Match.ObjectIncluding(), Match.ObjectIncluding()); test.equal(new Match.ObjectWithValues(), Match.ObjectWithValues()); } -); +); \ No newline at end of file diff --git a/packages/check/package.js b/packages/check/package.js index 1f30b324c1..a095044bbb 100644 --- a/packages/check/package.js +++ b/packages/check/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'Check whether a value matches a pattern', - version: '1.4.4', + version: '1.5.0', }); Package.onUse(api => { diff --git a/packages/ecmascript/package.js b/packages/ecmascript/package.js index dcc86c7f0b..c975433f37 100644 --- a/packages/ecmascript/package.js +++ b/packages/ecmascript/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'ecmascript', - version: '0.16.13', + version: '0.17.0', summary: 'Compiler plugin that supports ES2015+ in all .js files', documentation: 'README.md', }); diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 00beae7bc0..f009d06cfb 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "The Meteor command-line tool", - version: "3.3.1", + version: "3.4.0", }); Package.includeTool(); diff --git a/packages/meteor/meteor.d.ts b/packages/meteor/meteor.d.ts index d14ec0d1ef..9edb9cff6d 100644 --- a/packages/meteor/meteor.d.ts +++ b/packages/meteor/meteor.d.ts @@ -1,6 +1,6 @@ -import { Mongo } from 'meteor/mongo'; -import { EJSONable, EJSONableProperty } from 'meteor/ejson'; -import { DDP } from 'meteor/ddp'; +import { Mongo } from "meteor/mongo"; +import { EJSONable, EJSONableProperty } from "meteor/ejson"; +import { DDP } from "meteor/ddp"; export type global_Error = Error; @@ -21,7 +21,7 @@ export namespace Meteor { var release: string; var meteorRelease: string; - + interface ErrorConstructor { new (...args: any[]): Error; errorType: string; @@ -181,7 +181,13 @@ export namespace Meteor { | EJSONable[] | EJSONableProperty | EJSONableProperty[] - >(name: string, ...args: any[]): Promise & { stubPromise: Promise, serverPromise: Promise }; + >( + name: string, + ...args: any[] + ): Promise & { + stubPromise: Promise; + serverPromise: Promise; + }; interface MethodApplyOptions< Result extends @@ -261,7 +267,10 @@ export namespace Meteor { error: global_Error | Meteor.Error | undefined, result?: Result ) => void - ): Promise & { stubPromise: Promise, serverPromise: Promise }; + ): Promise & { + stubPromise: Promise; + serverPromise: Promise; + }; /** Method **/ /** Url **/ @@ -317,6 +326,28 @@ export namespace Meteor { * @param func The function to run */ function defer(func: Function): void; + + /** + * Wrap a function so that it only runs in the specified environments. + * @param func The function to wrap + * @param options An object with an `on` property that is an array of environment names: `"development"`, `"production"`, and/or `"test"`. + */ + function deferrable( + func: T, + options: { on: Array<"development" | "production" | "test"> } + ): T | void; + + /** + * Wrap a function so that it only runs in development environment. + * @param func The function to wrap + */ + function deferDev(func: T): T | void; + + /** + * Wrap a function so that it only runs in production environment. + * @param func The function to wrap + */ + function deferProd(func: T): T | void; /** Timeout **/ /** utils **/ @@ -336,7 +367,10 @@ export namespace Meteor { * @param func A function that takes a callback as its final parameter * @param context Optional `this` object against which the original function will be invoked */ - function wrapAsync(func: T, context?: ThisParameterType): Function; + function wrapAsync( + func: T, + context?: ThisParameterType + ): Function; function bindEnvironment(func: TFunc): TFunc; @@ -396,7 +430,7 @@ export namespace Meteor { * others can be set using Meteor's standard OAuth login parameters */ loginUrlParameters?: { include_granted_scopes: boolean; - }, + }; }, callback?: (error?: global_Error | Meteor.Error | Meteor.TypedError) => void ): void; @@ -440,7 +474,6 @@ export namespace Meteor { ): void; /** Login **/ - /** Connection **/ function reconnect(): void; @@ -518,7 +551,11 @@ export interface Subscription { * @param fields The fields in the document that have changed, together with their new values. If a field is not present in `fields` it was left unchanged; if it is present in `fields` and * has a value of `undefined` it was removed from the document. If `_id` is present it is ignored. */ - changed(collection: string, id: string, fields: Record): void; + changed( + collection: string, + id: string, + fields: Record + ): void; /** Access inside the publish function. The incoming connection for this subscription. */ connection: Meteor.Connection; /** diff --git a/packages/meteor/package.js b/packages/meteor/package.js index 9b7ca6575a..35bd3f00b6 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -2,7 +2,7 @@ Package.describe({ summary: "Core Meteor environment", - version: '2.1.1', + version: '2.2.0', }); Package.registerBuildPlugin({ diff --git a/packages/meteor/timers.js b/packages/meteor/timers.js index 9b0596bfa1..a6c5d25c0d 100644 --- a/packages/meteor/timers.js +++ b/packages/meteor/timers.js @@ -15,9 +15,8 @@ function withoutInvocation(f) { return function () { CurrentInvocation.withValue(null, f); }; - } else { - return f; } + return f; } function bindAndCatch(context, f) { @@ -56,7 +55,7 @@ Meteor.setInterval = function (f, duration) { * @locus Anywhere * @param {Object} id The handle returned by `Meteor.setInterval` */ -Meteor.clearInterval = function(x) { +Meteor.clearInterval = function (x) { return clearInterval(x); }; @@ -66,7 +65,7 @@ Meteor.clearInterval = function(x) { * @locus Anywhere * @param {Object} id The handle returned by `Meteor.setTimeout` */ -Meteor.clearTimeout = function(x) { +Meteor.clearTimeout = function (x) { return clearTimeout(x); }; @@ -84,3 +83,54 @@ Meteor.clearTimeout = function(x) { Meteor.defer = function (f) { Meteor._setImmediate(bindAndCatch("defer callback", f)); }; + +/** + * @memberOf Meteor + * @summary Defer execution of a function to run asynchronously in the background based on environment (similar to Meteor.isDevelopment ? Meteor.defer(fn) : Meteor.startup(fn)). + * @locus Anywhere + * @param {Function} func The function to run + * @param {Object} options The options object + * @param {Array} options.on Condition to determine whether to defer the function, you can pass an array of environments ['development', 'production', 'test'] + */ +Meteor.deferrable = function (f, options) { + var on = (options && options.on) || []; + + // throw if on is not an array + if (!Array.isArray(on)) { + throw new Error("options.on must be an array"); + } + + var env = Meteor.isDevelopment + ? "development" + : Meteor.isProduction + ? "production" + : "test"; + + if (on.includes(env)) { + return Meteor.defer(f); + } + + return f(); +}; + +/** + * @memberOf Meteor + * @summary Defer execution of a function to run asynchronously in the background in development (similar to Meteor.isDevelopment ? Meteor.defer(fn) : Meteor.startup(fn)). + * @locus Anywhere + * @param {Function} func The function to run + * @param {Object} options The options object + */ +Meteor.deferDev = function (f) { + return Meteor.deferrable(f, { on: ["development", "test"] }); +}; + +/** + * @memberOf Meteor + * @summary Defer execution of a function to run asynchronously in the background in production (similar to Meteor.isProduction ? Meteor.defer(fn) : Meteor.startup(fn)). + * @locus Anywhere + * @param {Function} func The function to run + * @param {Object} options The options object + */ +Meteor.deferProd = function (f) { + return Meteor.deferrable(f, { on: ["production"] }); +}; diff --git a/packages/meteor/timers_tests.js b/packages/meteor/timers_tests.js index 246f7e7b39..1db203dd00 100644 --- a/packages/meteor/timers_tests.js +++ b/packages/meteor/timers_tests.js @@ -1,21 +1,77 @@ -Tinytest.addAsync('timers - defer', function (test, onComplete) { - var x = 'a'; +Tinytest.addAsync("timers - defer", function (test, onComplete) { + let x = "a"; Meteor.defer(function () { - test.equal(x, 'b'); + test.equal(x, "b"); onComplete(); }); - x = 'b'; + x = "b"; }); -Tinytest.addAsync('timers - nested defer', function (test, onComplete) { - var x = 'a'; +Tinytest.addAsync("timers - nested defer", function (test, onComplete) { + let x = "a"; Meteor.defer(function () { - test.equal(x, 'b'); + test.equal(x, "b"); Meteor.defer(function () { - test.equal(x, 'c'); + test.equal(x, "c"); onComplete(); }); - x = 'c'; + x = "c"; }); - x = 'b'; + x = "b"; }); + +Tinytest.addAsync("timers - deferrable", function (test, onComplete) { + let x = "a"; + Meteor.deferrable( + function () { + test.equal(x, "b"); + onComplete(); + }, + { on: ["development", "production", "test"] } + ); + x = "b"; +}); + +Tinytest.addAsync( + "timers - deferrable not in current env", + function (test, onComplete) { + let x = "a"; + Meteor.deferrable( + function () { + x = "b"; + }, + { on: [] } + ); + test.equal(x, "b"); + onComplete(); + } +); + +Tinytest.addAsync( + "timers - deferrable works with async functions", + function (test, onComplete) { + let x = Meteor.deferrable( + function () { + return "start value"; + }, + { on: [] } + ); + test.equal(x, "start value"); + + Meteor.deferrable( + function () { + test.equal(x, "value"); + onComplete(); + }, + { on: ["development", "production", "test"] } + ); + + Meteor.deferrable( + async function () { + return "value"; + }, + { on: [] } + ).then((value) => (x = value)); + + } +); diff --git a/packages/minifier-js/package.js b/packages/minifier-js/package.js index 788cf783b6..15a6012d09 100644 --- a/packages/minifier-js/package.js +++ b/packages/minifier-js/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "JavaScript minifier", - version: '3.0.4', + version: '3.1.0', }); Npm.depends({ diff --git a/packages/minimongo/local_collection.js b/packages/minimongo/local_collection.js index 7d564ca564..a345d4938c 100644 --- a/packages/minimongo/local_collection.js +++ b/packages/minimongo/local_collection.js @@ -743,7 +743,7 @@ export default class LocalCollection { for (const id of specificIds) { const doc = this._docs.get(id); - if (doc && !fn(doc, id)) { + if (doc && fn(doc, id) === false) { break } } diff --git a/packages/minimongo/minimongo_tests.js b/packages/minimongo/minimongo_tests.js index 9801384815..65953b5f91 100644 --- a/packages/minimongo/minimongo_tests.js +++ b/packages/minimongo/minimongo_tests.js @@ -59,6 +59,36 @@ Tinytest.add('minimongo - wrapTransform', test => { handle.stop(); }); +Tinytest.add('minimongo - bulk remove with $in operator removes all matching documents', function(test) { + const coll = new LocalCollection(); + + // Insert multiple documents + const ids = ['id1', 'id2', 'id3', 'id4']; + ids.forEach(id => { + coll.insert({ _id: id, value: `item-${id}` }); + }); + + // Verify we have 4 documents + test.equal(coll.find().count(), 4); + + // Remove 2 documents using $in operator + const removedCount = coll.remove({ _id: { $in: ['id1', 'id2'] } }); + + // This should remove 2 documents, not just 1 + test.equal(removedCount, 2); + + // Verify only 2 documents remain + test.equal(coll.find().count(), 2); + + // Verify the correct documents were removed + test.isUndefined(coll.findOne('id1')); + test.isUndefined(coll.findOne('id2')); + + // Verify the other documents still exist + test.isNotUndefined(coll.findOne('id3')); + test.isNotUndefined(coll.findOne('id4')); +}); + if (Meteor.isClient) { Tinytest.add('minimongo - $geoIntersects should throw error', function(test) { const collection = new LocalCollection(); diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 074705f33c..c1405552a5 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -4061,4 +4061,4 @@ Tinytest.addAsync('minimongo - operation result fields (async)', async test => { // Test remove const removeResult = await c.removeAsync({name: 'doc1'}); test.equal(removeResult, 1, 'remove should return removed count'); -}); +}); \ No newline at end of file diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index b3ff2000be..42ffdf3101 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: "2.0.4", + version: "2.0.5", }); Package.onUse((api) => { diff --git a/packages/mongo/collection/collection.js b/packages/mongo/collection/collection.js index f41f97a68c..23cb81f7f0 100644 --- a/packages/mongo/collection/collection.js +++ b/packages/mongo/collection/collection.js @@ -62,8 +62,15 @@ Mongo.Collection = function Collection(name, options) { setupAutopublish(this, name, options); Mongo._collections.set(name, this); + + // Apply collection extensions + CollectionExtensions._applyExtensions(this, name, options); }; +// Apply static methods to the Collection constructor +CollectionExtensions._applyStaticMethods(Mongo.Collection); + + Object.assign(Mongo.Collection.prototype, { _getFindSelector(args) { if (args.length == 0) return {}; @@ -153,6 +160,118 @@ Object.assign(Mongo.Collection, { return selector; }, + + // Collection Extensions API - delegate to CollectionExtensions + /** + * @summary Add a constructor extension function that runs when collections are created. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {Function} extension Extension function called with (name, options) and 'this' bound to collection instance + */ + addExtension(extension) { + return CollectionExtensions.addExtension(extension); + }, + + /** + * @summary Add a prototype method to all collection instances. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the method to add + * @param {Function} method The method function, bound to the collection instance + */ + addPrototypeMethod(name, method) { + return CollectionExtensions.addPrototypeMethod(name, method); + }, + + /** + * @summary Add a static method to the Mongo.Collection constructor. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the static method to add + * @param {Function} method The static method function + */ + addStaticMethod(name, method) { + return CollectionExtensions.addStaticMethod(name, method); + }, + + /** + * @summary Remove a constructor extension (useful for testing). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {Function} extension The extension function to remove + */ + removeExtension(extension) { + return CollectionExtensions.removeExtension(extension); + }, + + /** + * @summary Remove a prototype method from all collection instances. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the method to remove + */ + removePrototypeMethod(name) { + return CollectionExtensions.removePrototypeMethod(name); + }, + + /** + * @summary Remove a static method from the Mongo.Collection constructor. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the static method to remove + */ + removeStaticMethod(name) { + return CollectionExtensions.removeStaticMethod(name); + }, + + /** + * @summary Clear all extensions, prototype methods, and static methods (useful for testing). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + */ + clearExtensions() { + return CollectionExtensions.clearExtensions(); + }, + + /** + * @summary Get all registered constructor extensions (useful for debugging). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @returns {Array} Array of registered extension functions + */ + getExtensions() { + return CollectionExtensions.getExtensions(); + }, + + /** + * @summary Get all registered prototype methods (useful for debugging). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @returns {Map} Map of method names to functions + */ + getPrototypeMethods() { + return CollectionExtensions.getPrototypeMethods(); + }, + + /** + * @summary Get all registered static methods (useful for debugging). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @returns {Map} Map of method names to functions + */ + getStaticMethods() { + return CollectionExtensions.getStaticMethods(); + } }); Object.assign(Mongo.Collection.prototype, ReplicationMethods, SyncMethods, AsyncMethods, IndexMethods); @@ -230,6 +349,13 @@ Object.assign(Mongo, { * @protected */ _collections: new Map(), + + /** + * @summary Collection Extensions API + * @memberof Mongo + * @static + */ + CollectionExtensions: CollectionExtensions }) diff --git a/packages/mongo/collection/collection_extensions.js b/packages/mongo/collection/collection_extensions.js new file mode 100644 index 0000000000..c259f9a4f2 --- /dev/null +++ b/packages/mongo/collection/collection_extensions.js @@ -0,0 +1,146 @@ +/** + * Collection Extensions System + * + * Provides a clean way to extend Mongo.Collection functionality + * without monkey patching. Supports constructor extensions, + * prototype methods, and static methods. + */ + +if (Package['lai:collection-extensions']) { + console.warn('lai:collection-extensions is not deprecated. Use Mongo.Collection.addExtension instead.'); +} + +CollectionExtensions = { + _extensions: [], + _prototypeMethods: new Map(), + _staticMethods: new Map(), + + /** + * Add a constructor extension function + * Extension function is called with (name, options) and 'this' bound to collection instance + */ + addExtension(extension) { + if (typeof extension !== 'function') { + throw new Error('Extension must be a function'); + } + this._extensions.push(extension); + }, + + /** + * Add a prototype method to all collection instances + * Method is bound to the collection instance + */ + addPrototypeMethod(name, method) { + if (typeof name !== 'string' || !name) { + throw new Error('Prototype method name must be a non-empty string'); + } + if (typeof method !== 'function') { + throw new Error('Prototype method must be a function'); + } + + this._prototypeMethods.set(name, method); + }, + + /** + * Add a static method to the Mongo.Collection constructor + */ + addStaticMethod(name, method) { + if (typeof name !== 'string' || !name) { + throw new Error('Static method name must be a non-empty string'); + } + if (typeof method !== 'function') { + throw new Error('Static method must be a function'); + } + + this._staticMethods.set(name, method); + }, + + /** + * Remove an extension (useful for testing) + */ + removeExtension(extension) { + const index = this._extensions.indexOf(extension); + if (index > -1) { + this._extensions.splice(index, 1); + } + }, + + /** + * Remove a prototype method + */ + removePrototypeMethod(name) { + this._prototypeMethods.delete(name); + }, + + /** + * Remove a static method + */ + removeStaticMethod(name) { + this._staticMethods.delete(name); + }, + + /** + * Clear all extensions (useful for testing) + */ + clearExtensions() { + this._extensions.length = 0; + this._prototypeMethods.clear(); + this._staticMethods.clear(); + }, + + /** + * Get all registered extensions (useful for debugging) + */ + getExtensions() { + return [...this._extensions]; + }, + + /** + * Get all registered prototype methods (useful for debugging) + */ + getPrototypeMethods() { + return new Map(this._prototypeMethods); + }, + + /** + * Get all registered static methods (useful for debugging) + */ + getStaticMethods() { + return new Map(this._staticMethods); + }, + + + + /** + * Apply all extensions to a collection instance + * Called during collection construction + */ + _applyExtensions(instance, name, options) { + // Apply constructor extensions + for (const extension of this._extensions) { + try { + extension.call(instance, name, options); + } catch (error) { + // Provide helpful error context + throw new Error(`Extension failed for collection '${name}': ${error.message}`); + } + } + + // Apply prototype methods + for (const [methodName, method] of this._prototypeMethods) { + instance[methodName] = method.bind(instance); + } + }, + + /** + * Apply static methods to the Mongo.Collection constructor + * Called during package initialization + */ + _applyStaticMethods(CollectionConstructor) { + for (const [methodName, method] of this._staticMethods) { + CollectionConstructor[methodName] = method; + } + }, + + +}; \ No newline at end of file diff --git a/packages/mongo/mongo.d.ts b/packages/mongo/mongo.d.ts index 2f3c941732..0d499ee588 100644 --- a/packages/mongo/mongo.d.ts +++ b/packages/mongo/mongo.d.ts @@ -53,6 +53,50 @@ export namespace Mongo { ? T : U; + /** + * Configuration options for Mongo Collection constructor + */ + interface CollectionOptions { + /** + * The server connection that will manage this collection. Uses the default connection if not specified. + * Pass the return value of calling `DDP.connect` to specify a different server. Pass `null` to specify + * no connection. Unmanaged (`name` is null) collections cannot specify a connection. + */ + connection?: DDP.DDPStatic | null | undefined; + + /** + * The method of generating the `_id` fields of new documents in this collection. Possible values: + * - **`'STRING'`**: random strings + * - **`'MONGO'`**: random [`Mongo.ObjectID`](#mongo_object_id) values + * + * The default id generation technique is `'STRING'`. + */ + idGeneration?: string | undefined; + + /** + * An optional transformation function. Documents will be passed through this function before being + * returned from `fetch` or `findOne`, and before being passed to callbacks of `observe`, `map`, + * `forEach`, `allow`, and `deny`. Transforms are *not* applied for the callbacks of `observeChanges` + * or to cursors returned from publish functions. + */ + transform?: (doc: T) => U; + + /** + * Set to `false` to skip setting up the mutation methods that enable insert/update/remove from client code. + * Default `true`. + */ + defineMutationMethods?: boolean | undefined; + + // Internal options (from normalizeOptions function) + /** @internal */ + _driver?: any; + /** @internal */ + _preventAutopublish?: boolean; + + // Allow additional properties for extensibility + [key: string]: any; + } + var Collection: CollectionStatic; interface CollectionStatic { /** @@ -61,27 +105,7 @@ export namespace Mongo { */ new ( name: string | null, - options?: { - /** - * The server connection that will manage this collection. Uses the default connection if not specified. Pass the return value of calling `DDP.connect` to specify a different - * server. Pass `null` to specify no connection. Unmanaged (`name` is null) collections cannot specify a connection. - */ - connection?: DDP.DDPStatic | null | undefined; - /** The method of generating the `_id` fields of new documents in this collection. Possible values: - * - **`'STRING'`**: random strings - * - **`'MONGO'`**: random [`Mongo.ObjectID`](#mongo_object_id) values - * - * The default id generation technique is `'STRING'`. - */ - idGeneration?: string | undefined; - /** - * An optional transformation function. Documents will be passed through this function before being returned from `fetch` or `findOne`, and before being passed to callbacks of - * `observe`, `map`, `forEach`, `allow`, and `deny`. Transforms are *not* applied for the callbacks of `observeChanges` or to cursors returned from publish functions. - */ - transform?: (doc: T) => U; - /** Set to `false` to skip setting up the mutation methods that enable insert/update/remove from client code. Default `true`. */ - defineMutationMethods?: boolean | undefined; - } + options?: CollectionOptions ): Collection; /** @@ -92,6 +116,68 @@ export namespace Mongo { getCollection< TCollection extends Collection | undefined = Collection | undefined >(name: string): TCollection; + + // Collection Extensions API + /** + * Add a constructor extension function that runs when collections are created. + * @param extension Extension function called with (name, options) and 'this' bound to collection instance + */ + addExtension(extension: (this: Collection, name: string | null, options?: CollectionOptions) => void): void; + + /** + * Add a prototype method to all collection instances. + * @param name The name of the method to add + * @param method The method function, bound to the collection instance + */ + addPrototypeMethod(name: string, method: (this: Collection, ...args: any[]) => any): void; + + /** + * Add a static method to the Mongo.Collection constructor. + * @param name The name of the static method to add + * @param method The static method function + */ + addStaticMethod(name: string, method: Function): void; + + /** + * Remove a constructor extension (useful for testing). + * @param extension The extension function to remove + */ + removeExtension(extension: Function): void; + + /** + * Remove a prototype method from all collection instances. + * @param name The name of the method to remove + */ + removePrototypeMethod(name: string): void; + + /** + * Remove a static method from the Mongo.Collection constructor. + * @param name The name of the static method to remove + */ + removeStaticMethod(name: string): void; + + /** + * Clear all extensions, prototype methods, and static methods (useful for testing). + */ + clearExtensions(): void; + + /** + * Get all registered constructor extensions (useful for debugging). + * @returns Array of registered extension functions + */ + getExtensions(): Array; + + /** + * Get all registered prototype methods (useful for debugging). + * @returns Map of method names to functions + */ + getPrototypeMethods(): Map; + + /** + * Get all registered static methods (useful for debugging). + * @returns Map of method names to functions + */ + getStaticMethods(): Map; } interface Collection { allow = undefined>(options: { @@ -479,6 +565,87 @@ export namespace Mongo { equals(otherID: ObjectID): boolean; } + /** + * Collection Extensions API + */ + interface CollectionExtensions { + /** + * Add a constructor extension function that runs when collections are created. + * @param extension Extension function called with (name, options) and 'this' bound to collection instance + */ + addExtension(extension: (this: Collection, name: string | null, options?: CollectionOptions) => void): void; + + /** + * Add a prototype method to all collection instances. + * @param name The name of the method to add + * @param method The method function, bound to the collection instance + */ + addPrototypeMethod(name: string, method: (this: Collection, ...args: any[]) => any): void; + + /** + * Add a static method to the Mongo.Collection constructor. + * @param name The name of the static method to add + * @param method The static method function + */ + addStaticMethod(name: string, method: Function): void; + + /** + * Remove a constructor extension (useful for testing). + * @param extension The extension function to remove + */ + removeExtension(extension: Function): void; + + /** + * Remove a prototype method from all collection instances. + * @param name The name of the method to remove + */ + removePrototypeMethod(name: string): void; + + /** + * Remove a static method from the Mongo.Collection constructor. + * @param name The name of the static method to remove + */ + removeStaticMethod(name: string): void; + + /** + * Clear all extensions, prototype methods, and static methods (useful for testing). + */ + clearExtensions(): void; + + /** + * Get all registered constructor extensions (useful for debugging). + * @returns Array of registered extension functions + */ + getExtensions(): Array; + + /** + * Get all registered prototype methods (useful for debugging). + * @returns Map of method names to functions + */ + getPrototypeMethods(): Map; + + /** + * Get all registered static methods (useful for debugging). + * @returns Map of method names to functions + */ + getStaticMethods(): Map; + } + + var CollectionExtensions: CollectionExtensions; + + /** + * Retrieve a Meteor collection instance by name. Only collections defined with `new Mongo.Collection(...)` are available with this method. + * @param name Name of your collection as it was defined with `new Mongo.Collection()`. + * @returns The collection instance or undefined if not found + */ + function getCollection | undefined = Collection | undefined>(name: string): T; + + /** + * A record of all defined Mongo.Collection instances, indexed by collection name. + * @internal + */ + var _collections: Map>; + function setConnectionOptions(options: any): void; } diff --git a/packages/mongo/oplog_tailing.ts b/packages/mongo/oplog_tailing.ts index 2df4bf7aaa..1a84fd2c16 100644 --- a/packages/mongo/oplog_tailing.ts +++ b/packages/mongo/oplog_tailing.ts @@ -41,6 +41,8 @@ export class OplogHandle { excludeCollections?: string[]; includeCollections?: string[]; }; + private _includeNSRegex?: RegExp; + private _excludeNSRegex?: RegExp; private _stopped: boolean; private _tailHandle: any; private _readyPromiseResolver: (() => void) | null; @@ -82,6 +84,18 @@ export class OplogHandle { } this._oplogOptions = { includeCollections, excludeCollections }; + if (includeCollections?.length) { + const incAlt = includeCollections.map((c) => Meteor._escapeRegExp(c)).join('|'); + + this._includeNSRegex = new RegExp(`^${Meteor._escapeRegExp(this._dbName)}\\.(?:${incAlt})$`); + } + + if (excludeCollections?.length) { + const excAlt = excludeCollections.map((c) => Meteor._escapeRegExp(c)).join('|'); + + this._excludeNSRegex = new RegExp(`^${Meteor._escapeRegExp(this._dbName)}\\.(?:${excAlt})$`); + } + this._catchingUpResolvers = []; this._lastProcessedTS = null; @@ -92,6 +106,15 @@ export class OplogHandle { this._startTrailingPromise = this._startTailing(); } + private _nsAllowed(ns: string | undefined): boolean { + if (!ns) return false; + if (ns === 'admin.$cmd') return true; + if (this._includeNSRegex && !this._includeNSRegex.test(ns)) return false; + if (this._excludeNSRegex && this._excludeNSRegex.test(ns)) return false; + + return true; + } + private _getOplogSelector(lastProcessedTS?: any): any { const oplogCriteria: any = [ { @@ -104,40 +127,55 @@ export class OplogHandle { }, ]; - const nsRegex = new RegExp( - "^(?:" + - [ - // @ts-ignore - Meteor._escapeRegExp(this._dbName + "."), - // @ts-ignore - Meteor._escapeRegExp("admin.$cmd"), - ].join("|") + - ")" - ); - if (this._oplogOptions.excludeCollections?.length) { - oplogCriteria.push({ - ns: { - $regex: nsRegex, - $nin: this._oplogOptions.excludeCollections.map( - (collName: string) => `${this._dbName}.${collName}` - ), - }, - }); - } else if (this._oplogOptions.includeCollections?.length) { + const nsRegex = new RegExp( + '^(?:' + + [ + // @ts-ignore + Meteor._escapeRegExp(this._dbName + '.'), + ].join('|') + + ')' + ); + const excludeNs = { + $regex: nsRegex, + $nin: this._oplogOptions.excludeCollections.map( + (collName: string) => `${this._dbName}.${collName}` + ), + }; oplogCriteria.push({ $or: [ - { ns: /^admin\.\$cmd/ }, + { ns: excludeNs }, { - ns: { - $in: this._oplogOptions.includeCollections.map( - (collName: string) => `${this._dbName}.${collName}` - ), - }, + ns: /^admin\.\$cmd/, + 'o.applyOps': { $elemMatch: { ns: excludeNs } }, }, ], }); + } else if (this._oplogOptions.includeCollections?.length) { + const includeNs = { + $in: this._oplogOptions.includeCollections.map( + (collName: string) => `${this._dbName}.${collName}` + ), + }; + oplogCriteria.push({ + $or: [ + { + ns: includeNs, + }, + { ns: /^admin\.\$cmd/, 'o.applyOps.ns': includeNs }, + ], + }); } else { + const nsRegex = new RegExp( + "^(?:" + + [ + // @ts-ignore + Meteor._escapeRegExp(this._dbName + "."), + // @ts-ignore + Meteor._escapeRegExp("admin.$cmd"), + ].join("|") + + ")" + ); oplogCriteria.push({ ns: nsRegex, }); @@ -411,6 +449,11 @@ async function handleDoc(handle: OplogHandle, doc: OplogEntry): Promise { op.ts = nextTimestamp; nextTimestamp = nextTimestamp.add(Long.ONE); } + // Only forward sub-ops whose ns is allowed + // See https://github.com/meteor/meteor/issues/13945 + if (!handle['_nsAllowed'](op.ns)) { + continue; + } await handleDoc(handle, op); } return; diff --git a/packages/mongo/package.js b/packages/mongo/package.js index c0ff30242f..46a5776273 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: "2.1.4", + version: "2.2.0", }); Npm.depends({ @@ -79,6 +79,7 @@ Package.onUse(function (api) { api.export("MongoInternals", "server"); api.export("Mongo"); + api.export("CollectionExtensions"); api.export("ObserveMultiplexer", "server", { testOnly: true }); api.addFiles( @@ -100,6 +101,7 @@ Package.onUse(function (api) { ); api.addFiles("local_collection_driver.js", ["client", "server"]); api.addFiles("remote_collection_driver.ts", "server"); + api.addFiles("collection/collection_extensions.js", ["client", "server"]); api.addFiles("collection/collection.js", ["client", "server"]); api.addFiles("connection_options.ts", "server"); // For zodern:types to pick up our published types. @@ -130,6 +132,7 @@ Package.onTest(function (api) { api.addFiles("tests/collection_tests.js", ["client", "server"]); api.addFiles("tests/collection_async_tests.js", ["client", "server"]); api.addFiles("tests/observe_changes_tests.js", ["client", "server"]); + api.addFiles("tests/collection_extensions_tests.js", ["client", "server"]); api.addFiles("tests/oplog_tests.js", "server"); api.addFiles("tests/oplog_v2_converter_tests.js", "server"); api.addFiles("tests/doc_fetcher_tests.js", "server"); diff --git a/packages/mongo/tests/collection_extensions_tests.js b/packages/mongo/tests/collection_extensions_tests.js new file mode 100644 index 0000000000..0a6e9dc3c5 --- /dev/null +++ b/packages/mongo/tests/collection_extensions_tests.js @@ -0,0 +1,233 @@ +import { Tinytest } from "meteor/tinytest"; +import { Mongo } from "meteor/mongo"; +import { CollectionExtensions } from "meteor/mongo"; +import { Random } from "meteor/random"; + +// Test setup and teardown +function setupTest() { + CollectionExtensions.clearExtensions(); +} + +function teardownTest() { + CollectionExtensions.clearExtensions(); +} + +Tinytest.add("CollectionExtensions - constructor extension", function (test) { + setupTest(); + + let extensionCallCount = 0; + let extensionData = null; + + CollectionExtensions.addExtension(function(name, options) { + extensionCallCount++; + extensionData = { name, options, instance: this }; + }); + + const testCollection = new Mongo.Collection(Random.id()); + + test.equal(extensionCallCount, 1); + test.equal(extensionData.name, testCollection._name); + test.equal(extensionData.instance, testCollection); + test.isTrue(extensionData.options && typeof extensionData.options === 'object'); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - multiple extensions", function (test) { + setupTest(); + + let callOrder = []; + + CollectionExtensions.addExtension(function(name, options) { + callOrder.push('extension1'); + }); + + CollectionExtensions.addExtension(function(name, options) { + callOrder.push('extension2'); + }); + + CollectionExtensions.addExtension(function(name, options) { + callOrder.push('extension3'); + }); + + const testCollection = new Mongo.Collection(Random.id()); + + test.equal(callOrder, ['extension1', 'extension2', 'extension3']); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - prototype methods", function (test) { + setupTest(); + + CollectionExtensions.addPrototypeMethod('testMethod', function() { + return 'testResult'; + }); + + const testCollection = new Mongo.Collection(Random.id()); + + test.isTrue(typeof testCollection.testMethod === 'function'); + test.equal(testCollection.testMethod(), 'testResult'); + + teardownTest(); +}); + +// Test prototype method with collection context +Tinytest.add("CollectionExtensions - prototype method context", function (test) { + setupTest(); + + // Add prototype method that uses collection context + CollectionExtensions.addPrototypeMethod('getCollectionName', function() { + return this._name; + }); + + // Create collection + const testCollection = new Mongo.Collection(Random.id()); + + // Verify method has correct context + test.equal(testCollection.getCollectionName(), testCollection._name); + + teardownTest(); +}); + +// Test static methods +Tinytest.add("CollectionExtensions - static methods", function (test) { + setupTest(); + + // Add static method + CollectionExtensions.addStaticMethod('testStaticMethod', function() { + return 'staticResult'; + }); + + // Apply static methods (this happens automatically in real usage) + CollectionExtensions._applyStaticMethods(Mongo.Collection); + + // Verify static method was added + test.isTrue(typeof Mongo.Collection.testStaticMethod === 'function'); + test.equal(Mongo.Collection.testStaticMethod(), 'staticResult'); + + // Clean up + delete Mongo.Collection.testStaticMethod; + teardownTest(); +}); + +// Test error handling in extensions +Tinytest.add("CollectionExtensions - extension error handling", function (test) { + setupTest(); + + // Add extension that throws error + CollectionExtensions.addExtension(function(name, options) { + throw new Error('Test extension error'); + }); + + // Creating collection should throw with helpful error message + test.throws(() => { + new Mongo.Collection(Random.id()); + }, /Extension failed for collection/); + + teardownTest(); +}); + +// Test extension removal +Tinytest.add("CollectionExtensions - extension removal", function (test) { + setupTest(); + + let callCount = 0; + + const extension = function(name, options) { + callCount++; + }; + + CollectionExtensions.addExtension(extension); + + const testCollection1 = new Mongo.Collection(Random.id()); + test.equal(callCount, 1); + + CollectionExtensions.removeExtension(extension); + + // Create another collection - should not call extension + const testCollection2 = new Mongo.Collection(Random.id()); + test.equal(callCount, 1); // Still 1, not 2 + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - prototype method removal", function (test) { + setupTest(); + + CollectionExtensions.addPrototypeMethod('testMethod', function() { + return 'test'; + }); + + const testCollection1 = new Mongo.Collection(Random.id()); + test.isTrue(typeof testCollection1.testMethod === 'function'); + + CollectionExtensions.removePrototypeMethod('testMethod'); + + const testCollection2 = new Mongo.Collection(Random.id()); + test.isUndefined(testCollection2.testMethod); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - input validation", function (test) { + setupTest(); + + test.throws(() => { + CollectionExtensions.addExtension("not a function"); + }, /Extension must be a function/); + + test.throws(() => { + CollectionExtensions.addPrototypeMethod("", function() {}); + }, /Prototype method name must be a non-empty string/); + + test.throws(() => { + CollectionExtensions.addPrototypeMethod(123, function() {}); + }, /Prototype method name must be a non-empty string/); + + test.throws(() => { + CollectionExtensions.addPrototypeMethod("test", "not a function"); + }, /Prototype method must be a function/); + + test.throws(() => { + CollectionExtensions.addStaticMethod("", function() {}); + }, /Static method name must be a non-empty string/); + + test.throws(() => { + CollectionExtensions.addStaticMethod("test", "not a function"); + }, /Static method must be a function/); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - introspection", function (test) { + setupTest(); + + const extension1 = function() {}; + const extension2 = function() {}; + + test.equal(CollectionExtensions.getExtensions(), []); + test.equal(CollectionExtensions.getPrototypeMethods().size, 0); + test.equal(CollectionExtensions.getStaticMethods().size, 0); + + CollectionExtensions.addExtension(extension1); + CollectionExtensions.addExtension(extension2); + CollectionExtensions.addPrototypeMethod('test1', function() {}); + CollectionExtensions.addStaticMethod('test2', function() {}); + + // Test introspection + const extensions = CollectionExtensions.getExtensions(); + test.equal(extensions.length, 2); + test.equal(extensions[0], extension1); + test.equal(extensions[1], extension2); + + const prototypeMethods = CollectionExtensions.getPrototypeMethods(); + test.equal(prototypeMethods.size, 1); + test.isTrue(prototypeMethods.has('test1')); + + const staticMethods = CollectionExtensions.getStaticMethods(); + test.equal(staticMethods.size, 1); + test.isTrue(staticMethods.has('test2')); + + teardownTest(); +}); \ No newline at end of file diff --git a/packages/mongo/tests/mongo_livedata_tests.js b/packages/mongo/tests/mongo_livedata_tests.js index af69aee7b4..f3e32106a5 100644 --- a/packages/mongo/tests/mongo_livedata_tests.js +++ b/packages/mongo/tests/mongo_livedata_tests.js @@ -2776,6 +2776,53 @@ const setsEqual = function (a, b) { }); }); + // Test operation result fields with allow/deny rules (similar to issue #12159) + if (Meteor.isServer) { + testAsyncMulti('mongo-livedata - operation result fields with allow/deny, ' + idGeneration, [ + async function(test, expect) { + var collectionName = 'test_operation_results_' + Random.id(); + var coll = new Mongo.Collection(collectionName, { idGeneration: idGeneration }); + + // Set up allow rules for all operations + coll.allow({ + insert: function() { return true; }, + update: function() { return true; }, + remove: function() { return true; } + }); + + // Test insert + var insertedId = await coll.insertAsync({name: 'doc1'}); + test.isTrue(insertedId !== undefined, 'insert should return an ID'); + + // Test update + var updateResult = await coll.updateAsync({name: 'doc1'}, {$set: {value: 1}}); + test.equal(updateResult, 1, 'update should return affected count'); + + // Test upsert (update case) + var upsertUpdateResult = await coll.upsertAsync({name: 'doc1'}, {$set: {value: 2}}); + test.equal(upsertUpdateResult.numberAffected, 1); + test.isFalse(upsertUpdateResult.hasOwnProperty('insertedId')); + + // Test upsert (insert case) + var upsertInsertResult = await coll.upsertAsync({name: 'doc2'}, {$set: {value: 3}}); + test.equal(upsertInsertResult.numberAffected, 1); + test.isTrue(upsertInsertResult.hasOwnProperty('insertedId')); + + // Test remove + var removeResult = await coll.removeAsync({name: 'doc1'}); + test.equal(removeResult, 1, 'remove should return removed count'); + + // Test insert with explicit ID + var explicitId = idGeneration === 'MONGO' ? new Mongo.ObjectID() : 'explicit-test-id'; + var insertExplicitResult = await coll.insertAsync({_id: explicitId, name: 'explicit-doc'}); + test.equal(insertExplicitResult, explicitId, 'insert with explicit ID should return that ID'); + + // Clean up + await coll.dropCollectionAsync(); + } + ]); + } + }); // end idGeneration parametrization Tinytest.add('mongo-livedata - rewrite selector', function(test) { diff --git a/packages/mongo/tests/oplog_tests.js b/packages/mongo/tests/oplog_tests.js index 2ffb0c32eb..0ef4bf8089 100644 --- a/packages/mongo/tests/oplog_tests.js +++ b/packages/mongo/tests/oplog_tests.js @@ -181,11 +181,46 @@ process.env.MONGO_OPLOG_URL && const defaultOplogHandle = MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle; let previousMongoPackageSettings = {}; +async function oplogSimpleInsertion(IncludeCollection, ExcludeCollection) { + await IncludeCollection.rawCollection().insertOne({ include: 'yes', foo: 'bar' }); + await ExcludeCollection.rawCollection().insertOne({ include: 'no', foo: 'bar' }); +} + +async function oplogInsertionTransaction(IncludeCollection, ExcludeCollection) { + const client = MongoInternals.defaultRemoteCollectionDriver().mongo.client; + const session = client.startSession(); + + try { + await session.withTransaction(async () => { + await IncludeCollection.rawCollection().insertOne({ include: 'yes', foo: 'bar' }, { session }); + await ExcludeCollection.rawCollection().insertOne({ include: 'no', foo: 'bar' }, { session }); + }); + } finally { + await session.endSession(); + } +} + +async function oplogMassiveInsertion(IncludeCollection, ExcludeCollection) { + const totalDocuments = 10000; + const documentInclude = Array.from( + { length: totalDocuments }, + (_, index) => ({ include: "yes", foo: "bar" + index }) + ); + const documentExclude = Array.from( + { length: totalDocuments }, + (_, index) => ({ include: "no", foo: "bar" + index }) + ); + + await IncludeCollection.rawCollection().insertMany(documentInclude); + await ExcludeCollection.rawCollection().insertMany(documentExclude); +} + async function oplogOptionsTest({ test, includeCollectionName, excludeCollectionName, - mongoPackageSettings = {} + mongoPackageSettings = {}, + functionToRun }) { try { previousMongoPackageSettings = { ...(Meteor.settings?.packages?.mongo || {}) }; @@ -199,9 +234,11 @@ async function oplogOptionsTest({ const IncludeCollection = new Mongo.Collection(includeCollectionName); const ExcludeCollection = new Mongo.Collection(excludeCollectionName); - const shouldBeTracked = new Promise((resolve) => { - IncludeCollection.find({ include: 'yes' }).observeChanges({ - added(id, fields) { resolve(true) } + const shouldBeTracked = new Promise((resolve, reject) => { + IncludeCollection.find({ include: "yes" }).observeChanges({ + added(id, fields) { + resolve(true); + }, }); }); const shouldBeIgnored = new Promise((resolve, reject) => { @@ -218,8 +255,7 @@ async function oplogOptionsTest({ }); // do the inserts: - await IncludeCollection.rawCollection().insertOne({ include: 'yes', foo: 'bar' }); - await ExcludeCollection.rawCollection().insertOne({ include: 'no', foo: 'bar' }); + await functionToRun(IncludeCollection, ExcludeCollection); test.equal(await shouldBeTracked, true); test.equal(await shouldBeIgnored, true); @@ -229,6 +265,73 @@ async function oplogOptionsTest({ MongoInternals.defaultRemoteCollectionDriver().mongo._setOplogHandle(defaultOplogHandle); } } +async function oplogTailingOptionsTest({ + test, + includeCollectionName, + excludeCollectionName, + mongoPackageSettings = {}, + functionToRun +}) { + let stopRaw; + try { + previousMongoPackageSettings = { ...(Meteor.settings?.packages?.mongo || {}) }; + if (!Meteor.settings.packages) Meteor.settings.packages = {}; + Meteor.settings.packages.mongo = mongoPackageSettings; + + const myOplogHandle = new MongoInternals.OplogHandle(process.env.MONGO_OPLOG_URL, 'meteor'); + await myOplogHandle._startTrailingPromise; + + const IncludeCollection = new Mongo.Collection(includeCollectionName); + const ExcludeCollection = new Mongo.Collection(excludeCollectionName); + + // Listen for INCLUDE collection oplog entries + const includeSeen = new Promise(async (resolve, reject) => { + const includeStop = await myOplogHandle.onOplogEntry( + { dropCollection: false, dropDatabase: false, collection: includeCollectionName }, + ({ op, collection, id }) => { + try { + // Only accept actual inserts for the include collection + if (op?.op === 'i' && collection === includeCollectionName && op?.o?.include === 'yes') { + includeStop.stop(); + resolve(true); + } + } catch (e) { + includeStop.stop(); + reject(e); + } + } + ); + }); + + // Ensure EXCLUDE collection does NOT get processed + const excludeNotSeen = new Promise(async (resolve, reject) => { + const excludeStop = await myOplogHandle.onOplogEntry( + { dropCollection: false, dropDatabase: false, collection: excludeCollectionName }, + ({ op, collection, id }) => { + // If anything for excluded collection arrives, fail + excludeStop.stop(); + reject("Recieved a document in a excluded collection"); + } + ); + // Resolve after 2s if nothing arrived + setTimeout(() => { + excludeStop.stop(); + resolve(true); + }, 2000); + }); + + // Do the inserts (e.g., oplogInsertionTransaction or your chosen function) + await functionToRun(IncludeCollection, ExcludeCollection); + + // Await raw-oplog assertions + test.equal(await includeSeen, true); + test.equal(await excludeNotSeen, true); + } finally { + if (stopRaw?.stop) await stopRaw.stop(); + // Reset: + Meteor.settings.packages.mongo = { ...previousMongoPackageSettings }; + } +} process.env.MONGO_OPLOG_URL && Tinytest.addAsync( 'mongo-livedata - oplog - oplogSettings - oplogExcludeCollections', @@ -242,7 +345,8 @@ process.env.MONGO_OPLOG_URL && Tinytest.addAsync( test, includeCollectionName: collectionNameA, excludeCollectionName: collectionNameB, - mongoPackageSettings + mongoPackageSettings, + functionToRun: oplogSimpleInsertion }); } ); @@ -259,7 +363,8 @@ process.env.MONGO_OPLOG_URL && Tinytest.addAsync( test, includeCollectionName: collectionNameB, excludeCollectionName: collectionNameA, - mongoPackageSettings + mongoPackageSettings, + functionToRun: oplogSimpleInsertion }); } ); @@ -279,7 +384,8 @@ process.env.MONGO_OPLOG_URL && Tinytest.addAsync( test, includeCollectionName: collectionNameA, excludeCollectionName: collectionNameB, - mongoPackageSettings + mongoPackageSettings, + functionToRun: oplogSimpleInsertion }); test.fail(); } catch (err) { @@ -350,6 +456,78 @@ process.env.MONGO_OPLOG_URL && } ); +process.env.MONGO_OPLOG_URL && Tinytest.addAsync( + 'mongo-livedata - oplog - oplogSettings - massiveInsertion - oplogIncludeCollections', + async test => { + const collectionNameA = "oplog-a-massive-" + Random.id(); + const collectionNameB = "oplog-b-massive-" + Random.id(); + const mongoPackageSettings = { + oplogIncludeCollections: [collectionNameA] + }; + await oplogTailingOptionsTest({ + test, + includeCollectionName: collectionNameA, + excludeCollectionName: collectionNameB, + mongoPackageSettings, + functionToRun: oplogMassiveInsertion + }); + } +); + +process.env.MONGO_OPLOG_URL && Tinytest.addAsync( + 'mongo-livedata - oplog - oplogSettings - massiveInsertion - oplogExcludeCollections', + async test => { + const collectionNameA = "oplog-a-massive-" + Random.id(); + const collectionNameB = "oplog-b-massive-" + Random.id(); + const mongoPackageSettings = { + oplogExcludeCollections: [collectionNameA] + }; + await oplogTailingOptionsTest({ + test, + includeCollectionName: collectionNameB, + excludeCollectionName: collectionNameA, + mongoPackageSettings, + functionToRun: oplogMassiveInsertion + }); + } +); + +process.env.MONGO_OPLOG_URL && Tinytest.addAsync( + 'mongo-livedata - oplog - oplogSettings - transaction - oplogExcludeCollections', + async test => { + const collectionNameA = "oplog-a-transaction-" + Random.id(); + const collectionNameB = "oplog-b-transaction-" + Random.id(); + const mongoPackageSettings = { + oplogExcludeCollections: [collectionNameA] + }; + await oplogTailingOptionsTest({ + test, + includeCollectionName: collectionNameB, + excludeCollectionName: collectionNameA, + mongoPackageSettings, + functionToRun: oplogInsertionTransaction + }); + } +); + +process.env.MONGO_OPLOG_URL && Tinytest.addAsync( + 'mongo-livedata - oplog - oplogSettings - transaction - oplogIncludeCollections', + async test => { + const collectionNameA = "oplog-a-transaction-" + Random.id(); + const collectionNameB = "oplog-b-transaction-" + Random.id(); + const mongoPackageSettings = { + oplogIncludeCollections: [collectionNameA] + }; + await oplogTailingOptionsTest({ + test, + includeCollectionName: collectionNameA, + excludeCollectionName: collectionNameB, + mongoPackageSettings, + functionToRun: oplogInsertionTransaction + }); + } +); + // TODO this is commented for now, but we need to find out the cause // PR: https://github.com/meteor/meteor/pull/12057 // Meteor.isServer && Tinytest.addAsync( diff --git a/packages/react-fast-refresh/package.js b/packages/react-fast-refresh/package.js index 723aaa4b77..fb5dc1b720 100644 --- a/packages/react-fast-refresh/package.js +++ b/packages/react-fast-refresh/package.js @@ -1,9 +1,8 @@ Package.describe({ name: 'react-fast-refresh', - version: '0.2.9', + version: '0.3.0', summary: 'Automatically update React components with HMR', documentation: 'README.md', - devOnly: true, }); Npm.depends({ diff --git a/packages/rspack/README.md b/packages/rspack/README.md new file mode 100644 index 0000000000..651d1c3449 --- /dev/null +++ b/packages/rspack/README.md @@ -0,0 +1,3 @@ +# rspack + +The rspack package hooks into the Meteor lifecycle to run the rspack bundler independently, compiling app code while preserving Meteor packages as external. It automatically integrates the rspack dev server and HMR mechanism, and manages client and server bundles for development and production. By default, rspack is configured to support secured code for client and server, tree shaking, full ESM support with export fields in package.json, and so on. It also enables the user to provide custom configuration. diff --git a/packages/rspack/lib/build-context.js b/packages/rspack/lib/build-context.js new file mode 100644 index 0000000000..765f841f41 --- /dev/null +++ b/packages/rspack/lib/build-context.js @@ -0,0 +1,609 @@ +/** + * @module build-context + * @description Functions for managing build context and module files for Rspack plugin + */ +import { RSPACK_DOCTOR_CONTEXT } from "./constants"; + +const fs = require('fs'); +const path = require('path'); + +const { getCustomConfigFilePath } = require('./processes'); + +const { logError } = require('meteor/tools-core/lib/log'); + +const { capitalizeFirstLetter } = require('meteor/tools-core/lib/string'); + +const { + getMeteorAppDir, + getMeteorInitialAppEntrypoints, + isMeteorAppDevelopment, + isMeteorAppRun, + isMeteorAppBuild, + isMeteorBlazeProject, + isMeteorAppNative, +} = require('meteor/tools-core/lib/meteor'); + +const { + getGlobalState, + setGlobalState +} = require('meteor/tools-core/lib/global-state'); + +const { + addGitignoreEntries +} = require('meteor/tools-core/lib/git'); + +const { + RSPACK_BUILD_CONTEXT, + RSPACK_CHUNKS_CONTEXT, + RSPACK_ASSETS_CONTEXT, + GLOBAL_STATE_KEYS, + FILE_ROLE, +} = require('./constants'); + +// Common warning message for autogenerated files +const AUTO_GENERATED_WARNING = `* ⚠️ Note: This file is autogenerated. It is not meant to be modified manually. +* These files also act as a cache: they can be safely removed and will be +* regenerated on the next build. They should be ignored in IDE suggestions +* and version control.`; + +/** + * Gets entry points from Meteor configuration + * Retrieves from global state if already stored, otherwise gets from Meteor + * @returns {Object} Object containing entry points for client and server + */ +export function getInitialEntrypoints() { + const existingEntrypoint = getGlobalState(GLOBAL_STATE_KEYS.INITIAL_ENTRYPONTS); + if (existingEntrypoint) return existingEntrypoint; + const initialEntrypoints = getMeteorInitialAppEntrypoints(); + const hasInitialEntrypoints = initialEntrypoints && Object.values(initialEntrypoints).length > 0 && Object.values(initialEntrypoints).every((value) => value != null); + if (hasInitialEntrypoints) { + setGlobalState(GLOBAL_STATE_KEYS.INITIAL_ENTRYPONTS, initialEntrypoints); + } + return initialEntrypoints; +} + +/** + * Ensures the Rspack build context directory exists + * Creates the directory if it doesn't exist and adds it to .gitignore + * @returns {string} Path to the build context directory + * @throws {Error} If directory creation fails + */ +export function ensureRspackBuildContextExists() { + const appDir = getMeteorAppDir(); + const buildContextPath = path.join(appDir, RSPACK_BUILD_CONTEXT); + + if (!fs.existsSync(buildContextPath)) { + try { + fs.mkdirSync(buildContextPath, { recursive: true }); + } catch (error) { + logError(`Failed to create Rspack build context directory: ${error.message}`); + throw error; + } + } + + addGitignoreEntries( + appDir, + [ + RSPACK_BUILD_CONTEXT, + `*/${RSPACK_ASSETS_CONTEXT}`, + `*/${RSPACK_CHUNKS_CONTEXT}`, + RSPACK_DOCTOR_CONTEXT, + ], + 'Meteor Modern-Tools build context directories', + ); + + return buildContextPath; +} + +/** + * Ensures module files exist in the build context directory + * Creates default module files if they don't exist + * @returns {void} + */ +export function ensureModuleFilesExist() { + const appDir = getMeteorAppDir(); + + const env = { + ...(isMeteorAppDevelopment() ? { isDevelopment: true } : { isProduction: true }), + isNative: isMeteorAppNative(), + }; + const commandRole = isMeteorAppRun() + ? { role: FILE_ROLE.run } + : isMeteorAppBuild() + ? { role: FILE_ROLE.build } + : { role: FILE_ROLE.run }; + const initialEntrypoints = getInitialEntrypoints(); + const mainClientFiles = { + entryFile: initialEntrypoints.mainClient || '', + outputFile: getBuildFilePath({ isMain: true, isClient: true, ...env, role: FILE_ROLE.output, onlyFilename: true }), + }; + const mainServerFiles = { + entryFile: initialEntrypoints.mainServer || '', + outputFile: getBuildFilePath({ isMain: true, isServer: true, ...env, role: FILE_ROLE.output, onlyFilename: true }), + }; + const isTestEager = + initialEntrypoints.testModule == null && + initialEntrypoints.testClient == null && + initialEntrypoints.testServer == null; + const isTestModule = initialEntrypoints.testModule != null || isTestEager; + const testClientFiles = { + entryFile: initialEntrypoints.testClient || '', + outputFile: getBuildFilePath({ isTest: true, isTestModule, isClient: true, role: FILE_ROLE.output, onlyFilename: true }), + }; + const testServerFiles = { + entryFile: initialEntrypoints.testServer || '', + outputFile: getBuildFilePath({ isTest: true, isTestModule, isServer: true, role: FILE_ROLE.output, onlyFilename: true }), + }; + + const moduleFiles = { + /* Main module files for client and server */ + [getBuildFilePath({ isMain: true, isClient: true, ...env, ...commandRole })]: + getBuildFileContent({ isMain: true, isClient: true, ...env, ...commandRole, ...mainClientFiles }), + [getBuildFilePath({ isMain: true, isClient: true, ...env, role: FILE_ROLE.entry })]: + getBuildFileContent({ isMain: true, isClient: true, ...env, role: FILE_ROLE.entry, ...mainClientFiles }), + [getBuildFilePath({ isMain: true, isClient: true, ...env, role: FILE_ROLE.output })]: + getBuildFileContent({ isMain: true, isClient: true, ...env, role: FILE_ROLE.output, ...mainClientFiles }), + [getBuildFilePath({ isMain: true, isServer: true, ...env, ...commandRole })]: + getBuildFileContent({ isMain: true, isServer: true, ...env, ...commandRole, ...mainServerFiles }), + [getBuildFilePath({ isMain: true, isServer: true, ...env, role: FILE_ROLE.entry })]: + getBuildFileContent({ isMain: true, isServer: true, ...env, role: FILE_ROLE.entry, ...mainServerFiles }), + [getBuildFilePath({ isMain: true, isServer: true, ...env, role: FILE_ROLE.output })]: + getBuildFileContent({ isMain: true, isServer: true, ...env, role: FILE_ROLE.output, ...mainServerFiles }), + /* Test module files when test module, test module files for client and server are present or eager discovery */ + [getBuildFilePath({ isTest: true, isTestModule, isClient: true, ...commandRole })]: + getBuildFileContent({ isTest: true, isTestModule, isClient: true, ...commandRole, ...testClientFiles }), + [getBuildFilePath({ isTest: true, isTestModule, isClient: true, role: FILE_ROLE.entry })]: + getBuildFileContent({ isTest: true, isTestModule, isClient: true, role: FILE_ROLE.entry, ...testClientFiles }), + [getBuildFilePath({ isTest: true, isTestModule, isClient: true, role: FILE_ROLE.output })]: + getBuildFileContent({ isTest: true, isTestModule, isClient: true, role: FILE_ROLE.output, ...testClientFiles }), + [getBuildFilePath({ isTest: true, isTestModule, isServer: true, ...commandRole })]: + getBuildFileContent({ isTest: true, isTestModule, isServer: true, ...commandRole, ...testServerFiles }), + [getBuildFilePath({ isTest: true, isTestModule, isServer: true, role: FILE_ROLE.entry })]: + getBuildFileContent({ isTest: true, isTestModule, isServer: true, role: FILE_ROLE.entry, ...testServerFiles }), + [getBuildFilePath({ isTest: true, isTestModule, isServer: true, role: FILE_ROLE.output })]: + getBuildFileContent({ isTest: true, isTestModule, isServer: true, role: FILE_ROLE.output, ...testServerFiles }), + }; + + Object.entries(moduleFiles).forEach(([filename, defaultContent]) => { + // 1. Build full path and ensure directory exists + const filePath = path.join(appDir, RSPACK_BUILD_CONTEXT, filename); + const dir = path.dirname(filePath); + if (!fs.existsSync(dir)) { + try { + fs.mkdirSync(dir, { recursive: true }); + } catch (err) { + logError(`Failed to create directory ${dir}: ${err.message}`); + return; // stop here if we can’t make the folder + } + } + + // 2. If the file exists, check its contents + if (fs.existsSync(filePath)) { + let existing; + try { + existing = fs.readFileSync(filePath, 'utf8'); + } catch (err) { + logError(`Failed to read existing file ${filename}: ${err.message}`); + return; + } + + // 3. If it doesn't already start with the new defaultContent, overwrite it + if (!existing.includes(defaultContent)) { + try { + fs.writeFileSync(filePath, defaultContent, 'utf8'); + } catch (err) { + logError(`Failed to rewrite module file ${filename}: ${err.message}`); + } + } + + // 4. If the file doesn't exist at all, write it for the first time + } else { + try { + fs.writeFileSync(filePath, defaultContent, 'utf8'); + } catch (err) { + logError(`Failed to create module file ${filename}: ${err.message}`); + } + } + }); +} + +/** + * Generates a build file path based on configuration parameters + * @param {Object} config - Configuration object containing build settings + * @returns {string} The build file path or filename + */ +export function getBuildFilePath(config) { + // Determine the module part (directory name) + let module = ''; + if (config?.isTest) { + module = 'test'; + } else if (config?.isMain) { + module = 'main'; + } + + // Determine the side part (first part of filename) + let side = ''; + if (config?.isServer) { + side = 'server'; + } else if (config?.isClient) { + side = 'client'; + } + + // Determine the environment part (only for non-test files) + let env = ''; + if (!config?.isTest) { + if (config?.isDevelopment) { + env = 'dev'; + } else if (config?.isProduction) { + env = 'prod'; + } + } + + // Determine the role part + let role = config?.role; + if ([FILE_ROLE.run, FILE_ROLE.build].includes(role)) { + role = 'meteor'; + } else if ([FILE_ROLE.output].includes(role)) { + role = 'rspack'; + } + + // 5. Get file extension (default to js) + const extension = config?.extension || 'js'; + + // 6. Construct the filename: {side}-{role}.{extension} + const filename = `${side}-${role}.${extension}`; + + // Return either just the filename or the full path + if (config?.onlyFilename) { + return filename; + } else { + // Full path format: {module}[-{env}]/{filename} + const envSuffix = env ? `-${env}` : ''; + return `${module}${envSuffix}/${filename}`; + } +} + +/** + * Gets the appropriate banner based on file configuration + * @param {Object} config - Configuration object + * @param {string} side - The side (client, server, test) + * @param {string} env - The environment (development, production) + * @param {string} module - The module (main, test) + * @param {string} role - The role (build, entry, run, output) + * @returns {string} The banner content + */ +function getBanner(config, side, env, module, role) { + const envDisplay = capitalizeFirstLetter(env || module); + const sideDisplay = capitalizeFirstLetter(side); + + // For test mode, use the existing banners + if (module === 'test') { + // Test file banners + if (role === FILE_ROLE.entry) { + // For test mode, if side is client or server, include it in the title + const testType = side === 'test' ? 'Test' : `Test ${sideDisplay}`; + return `/** +* @file ${side}-entry.js +* @description Entry point for Rspack test build process +* -------------------------------------------------------------------------- +* ⚡ Rspack ${testType} Entry (${envDisplay}) +* -------------------------------------------------------------------------- +* • [■ ${side}-entry.js ] ──▶ [ ${side}-rspack.js ] ──▶ [ ${side}-meteor.js ] +* +* This file is the starting point for the Rspack test build. It imports your +* Meteor app's test modules so Rspack can resolve every dependency and +* generate the bundled output: \`${side}-rspack.js\`. +* +${AUTO_GENERATED_WARNING} +*/`; + } + + if (role === FILE_ROLE.output) { + // For test mode, if side is client or server, include it in the title + const testType = side === 'test' ? 'Test' : `Test ${sideDisplay}`; + return `/** +* @file ${side}-rspack.js +* @description Bundled output generated by Rspack for tests +* -------------------------------------------------------------------------- +* ⚡ Rspack ${testType} App (${envDisplay}) +* -------------------------------------------------------------------------- +* • [ ${side}-entry.js ] ──▶ [■ ${side}-rspack.js ] ──▶ [ ${side}-meteor.js ] +* +* This file is the bundle that Rspack outputs for tests. It contains all of +* your test code in one optimized file. Next step is loading this bundle via +* \`${side}-meteor.js\`. +* +${AUTO_GENERATED_WARNING} +*/`; + } + + if (role === FILE_ROLE.run || role === FILE_ROLE.build) { + // For test mode, if side is client or server, include it in the title + const testType = side === 'test' ? 'Test' : `Test ${sideDisplay}`; + return `/** +* @file ${side}-meteor.js +* @description Meteor runtime file that imports the Rspack test bundle +* -------------------------------------------------------------------------- +* ☄️ Meteor ${testType} App (${envDisplay}) +* -------------------------------------------------------------------------- +* • [ ${side}-entry.js ] ──▶ [ ${side}-rspack.js ] ──▶ [■ ${side}-meteor.js ] +* +* Defined under \`meteor.testModule${side === 'test' ? '' : `.${side}`}\` in package.json. Meteor loads this +* file at runtime to import the Rspack test bundle (\`${side}-rspack.js\`) and +* run your tests. +* +${AUTO_GENERATED_WARNING} +*/`; + } + return ''; + } + + // For main modules (not test mode), use the new templates + // Entry files + if (role === FILE_ROLE.entry) { + return `/** +* @file ${side}-entry.js +* @description Entry point for Rspack build process +* -------------------------------------------------------------------------- +* 🔌 Rspack ${sideDisplay} Entry (${envDisplay}) +* -------------------------------------------------------------------------- +* • [■ ${side}-entry.js ] ──▶ [ ${side}-rspack.js ] ──▶ [ ${side}-meteor.js ] +* +* This file is the entry point that Rspack uses to start the build process. +* It imports the module defined in \`meteor.mainModule.${side}\` inside package.json. +* From here, Rspack can trace the entire dependency graph of your application +* and generate the bundled output (\`${side}-rspack.js\`). +* +${AUTO_GENERATED_WARNING} +*/`; + } + + // Rspack output files + if (role === FILE_ROLE.output) { + return `/** +* @file ${side}-rspack.js +* @description Bundled output generated by Rspack +* -------------------------------------------------------------------------- +* ⚡ Rspack ${sideDisplay} App (${envDisplay}) +* -------------------------------------------------------------------------- +* • [ ${side}-entry.js ] ──▶ [■ ${side}-rspack.js ] ──▶ [ ${side}-meteor.js ] +* +* This file is the bundled output generated by Rspack. +* It contains all application code and assets combined into one build. +* It is not used directly, but will be imported by the Meteor main module +* file (\`${side}-meteor.js\`) so that Meteor runs the Rspack bundle. +* +${AUTO_GENERATED_WARNING} +*/`; + } + + // Meteor files (run or build role) + if (role === FILE_ROLE.run || role === FILE_ROLE.build) { + return `/** +* @file ${side}-meteor.js +* @description Meteor runtime file that imports the Rspack bundle +* -------------------------------------------------------------------------- +* ☄️ Meteor ${sideDisplay} App (${envDisplay}) +* -------------------------------------------------------------------------- +* • [ ${side}-entry.js ] ──▶ [ ${side}-rspack.js ] ──▶ [■ ${side}-meteor.js ] +* +* This file overrides the corresponding \`meteor.mainModule.${side}\` entry in +* package.json. Meteor loads it at runtime, and it imports the Rspack +* bundle (\`${side}-rspack.js\`) so the application executes using the build +* produced by Rspack. +* +${AUTO_GENERATED_WARNING} +*/`; + } + + return ''; +} + +/** + * Gets the HMR code if applicable + * @returns {string} The HMR code or empty string + */ +function getHmrCode(config, role) { + if (role === FILE_ROLE.entry && config?.isClient && !config?.isTest) { + return `/* Enables HMR */ +if (module.hot) { + module.hot.accept(); +}`; + } + return ''; +} + +/** + * Gets the import content based on configuration + * @returns {string} The import content + */ +function getImportContent(config, side, role) { + if (config?.entryFile && role === FILE_ROLE.entry) { + return `/* Link to 🔌 Meteor ${capitalizeFirstLetter(side)} Entry */ +import '../../${config?.entryFile}';`; + } + + if (config?.outputFile && + (role === FILE_ROLE.build || config?.isProduction || + (role === FILE_ROLE.run && + (config?.isServer || config?.isTest || config?.isNative))) + ) { + return `/* Link to ⚡ Rspack ${capitalizeFirstLetter(side)} App */ +${ + (isMeteorBlazeProject() && config?.isClient && '// In Blaze, import happens last so HTML files preload first') || + `import './${config?.outputFile || ''}';` +}`; + } + + if (role === FILE_ROLE.run && config?.isServer && !config?.isTest) { + return '/* No link to ☄️ Meteor Server App as served by HMR server */'; + } + + if (role === FILE_ROLE.run && config?.isClient && !config?.isTest) { + return '/* No link to ⚡ Rspack Client App as served by HMR server */'; + } + + if (role === FILE_ROLE.output && config?.isClient && !config?.isTest) { + return '/* No code generated as served by HMR server */'; + } + + if (role === FILE_ROLE.output && (config?.isServer || config?.isTest)) { + return '/* Code generated */'; + } + + if (role === FILE_ROLE.entry && config?.isTest) { + return '/* Tests automatically imported */'; + } + + return ''; +} + +/** + * Generates build file content based on configuration parameters + * @param {Object} config - Configuration object + * @returns {string} The build file content + */ +export function getBuildFileContent(config) { + // Extract configuration values + const module = config?.isTest ? 'test' : config?.isMain ? 'main' : ''; + const side = config?.isTestModule ? 'test' : config?.isServer ? 'server' : config?.isClient ? 'client' : ''; + const env = config?.isDevelopment ? 'development' : config?.isProduction ? 'production' : ''; + const role = config?.role; + + // Get banner based on configuration + const banner = getBanner(config, side, env, module, role); + + // Get HMR code if applicable + const hmr = getHmrCode(config, role); + + // Get import content based on configuration + const importContent = getImportContent(config, side, role); + + // Combine all parts to create the file content + return `${banner} +${hmr && ` +${hmr} +` || ''} +${importContent} +`; +} + +/** + * Cleans the build context files of the current environment + * Removes all build files and directories for the current environment + * Also cleans _build-* files from public and private folders + * @returns {void} + */ +export function cleanBuildContextFiles() { + const appDir = getMeteorAppDir(); + const buildContextPath = path.join(appDir, RSPACK_BUILD_CONTEXT); + + // Only proceed if the build context directory exists + if (!fs.existsSync(buildContextPath)) { + return; + } + + // Get current environment + const env = { + ...(isMeteorAppDevelopment() ? { isDevelopment: true } : { isProduction: true }), + isNative: isMeteorAppNative(), + }; + + try { + // Clean main module directories + const mainClientPath = path.dirname(path.join(buildContextPath, getBuildFilePath({ isMain: true, isClient: true, ...env }))); + const mainServerPath = path.dirname(path.join(buildContextPath, getBuildFilePath({ isMain: true, isServer: true, ...env }))); + + // Clean test module directories if they exist + const testModulePath = path.dirname(path.join(buildContextPath, getBuildFilePath({ isTest: true, isTestModule: true }))); + const testClientPath = path.dirname(path.join(buildContextPath, getBuildFilePath({ isTest: true, isClient: true }))); + const testServerPath = path.dirname(path.join(buildContextPath, getBuildFilePath({ isTest: true, isServer: true }))); + + // Create a Set to ensure unique directory paths + const uniqueDirPaths = new Set([mainClientPath, mainServerPath, testModulePath, testClientPath, testServerPath]); + + // Remove directories if they exist + [...uniqueDirPaths].forEach(dirPath => { + if (fs.existsSync(dirPath)) { + fs.rmSync(dirPath, { recursive: true, force: true }); + } + }); + + // Clean _build-* files from public and private folders + const publicDir = path.join(appDir, 'public'); + const privateDir = path.join(appDir, 'private'); + + [publicDir, privateDir].forEach(dir => { + if (fs.existsSync(dir)) { + try { + const files = fs.readdirSync(dir); + files.forEach(file => { + if ([RSPACK_ASSETS_CONTEXT, RSPACK_CHUNKS_CONTEXT, RSPACK_DOCTOR_CONTEXT].includes(file)) { + const filePath = path.join(dir, file); + fs.rmSync(filePath, { recursive: true, force: true }); + } + }); + + // Also remove client-rspack.js from public directory if it exists + if (dir === publicDir) { + const clientRspackPath = path.join(dir, 'client-rspack.js'); + if (fs.existsSync(clientRspackPath)) { + fs.rmSync(clientRspackPath, { force: true }); + } + } + } catch (err) { + logError(`Failed to clean _build-* files from ${dir}: ${err.message}`); + } + } + }); + } catch (error) { + logError(`Failed to clean build context files: ${error.message}`); + } +} + +/** + * Ensures the rspack.config.js file exists at the project level + * Creates the file if it doesn't exist with the required template + * Will not create a new file if rspack.config.mjs or rspack.config.cjs exists + * @returns {string} Path to the rspack.config file (.js, .mjs, or .cjs) + */ +export function ensureRspackConfigExists() { + const appDir = getMeteorAppDir(); + + // Check if any config file already exists using the helper function + const existingConfigPath = getCustomConfigFilePath(appDir); + if (existingConfigPath) { + return existingConfigPath; + } + + // If no config file exists, we'll create a .js one + const jsConfigPath = path.join(appDir, 'rspack.config.js'); + + const configTemplate = `const { defineConfig } = require('@meteorjs/rspack'); + +/** + * Rspack configuration for Meteor projects. + * + * Provides typed flags on the \`Meteor\` object, such as: + * - \`Meteor.isClient\` / \`Meteor.isServer\` + * - \`Meteor.isDevelopment\` / \`Meteor.isProduction\` + * - …and other flags available + * + * Use these flags to adjust your build settings based on environment. + */ +module.exports = defineConfig(Meteor => { + return {}; +}); +`; + + if (!fs.existsSync(jsConfigPath)) { + try { + fs.writeFileSync(jsConfigPath, configTemplate, 'utf8'); + } catch (error) { + logError(`Failed to create rspack.config.js file: ${error.message}`); + throw error; + } + } + + return jsConfigPath; +} diff --git a/packages/rspack/lib/compilation.js b/packages/rspack/lib/compilation.js new file mode 100644 index 0000000000..1a3de1511e --- /dev/null +++ b/packages/rspack/lib/compilation.js @@ -0,0 +1,226 @@ +/** + * @module compilation-helpers + * @description Helper functions for Rspack compilation tracking + * + * This module provides utility functions for tracking Rspack compilations, + * including setting up compilation tracking, waiting for first compilation, + * and formatting time values. + */ + +const { + GLOBAL_STATE_KEYS +} = require('./constants'); + +const { + getGlobalState, + setGlobalState +} = require('meteor/tools-core/lib/global-state'); + +// Helper function to format milliseconds with comma separators +function formatMilliseconds(ms) { + return ms.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); +} + +/** + * Sets up compilation tracking and callbacks + * @returns {Object} Object containing compilation tracking state and callbacks + */ +export function setupCompilationTracking() { + // Initialize global state for first compilation tracking + const clientFirstCompile = { + resolved: false, + resolve: null + }; + const serverFirstCompile = { + resolved: false, + resolve: null + }; + + // Store in global state + setGlobalState(GLOBAL_STATE_KEYS.CLIENT_FIRST_COMPILE, clientFirstCompile); + setGlobalState(GLOBAL_STATE_KEYS.SERVER_FIRST_COMPILE, serverFirstCompile); + + // Create promises for first compilation of client and server + const clientFirstCompilePromise = new Promise(resolve => { + clientFirstCompile.resolve = resolve; + }); + + const serverFirstCompilePromise = new Promise(resolve => { + serverFirstCompile.resolve = resolve; + }); + + // Create a shared state to track compilation times + const compilationState = { + clientMs: null, + serverMs: null, + timeoutId: null, + initialCompilationOccurred: false, + previousClientResolved: false, + previousServerResolved: false, + previousMaxTime: 0, + // Base delay in milliseconds + baseDelay: 100, + // Calculate dynamic defer time based on previous maximum time + calculateDeferTime: function() { + // Use a fixed base delay plus a margin based on previous maximum time + // The margin is 20% of the previous maximum time + return this.baseDelay + this.previousMaxTime; + }, + // Function to print the maximum time once compilations are complete + printMaxTime: function() { + const clientResolved = clientFirstCompile?.resolved || false; + const serverResolved = serverFirstCompile?.resolved || false; + + // Check if this is the first time both client and server are resolved + // but were previously not both resolved + if (clientResolved && serverResolved && + !(this.previousClientResolved && this.previousServerResolved) && + !this.initialCompilationOccurred) { + this.initialCompilationOccurred = true; + } + + // Update previous resolved states for next call + this.previousClientResolved = clientResolved; + this.previousServerResolved = serverResolved; + + const shouldPrint = this.initialCompilationOccurred && + (this.clientMs !== null || this.serverMs !== null); + + // Clear any existing timeout + if (this.timeoutId !== null) { + clearTimeout(this.timeoutId); + this.timeoutId = null; + } + + // Handle cases where only one compilation runs + if (shouldPrint) { + // Use the available time or default to the other one + const clientTime = this.clientMs !== null ? this.clientMs : 0; + const serverTime = this.serverMs !== null ? this.serverMs : 0; + + // Calculate defer time based on previous maximum time + const deferTime = this.calculateDeferTime(); + + // Set a timeout to wait for both compilations to likely finish + this.timeoutId = setTimeout(() => { + const maxMs = Math.max(clientTime, serverTime); + console.log( + `| Total: ${formatMilliseconds(maxMs)} ms (Rspack ${ + this.initialCompilationOccurred ? 'Rebuild' : 'Build' + } App)` + ); + + // Store the current maximum time for future defer time calculations + this.previousMaxTime = Math.max(maxMs, this.previousMaxTime); + + // Reset the state for next compilation cycle + clearTimeout(this.timeoutId); + this.clientMs = null; + this.serverMs = null; + this.timeoutId = null; + }, deferTime); + } + }, + }; + + // Define separate onCompile callbacks for client and server + const onCompileClient = (data) => { + // Resolve the promise if it's the first compilation + const clientState = getGlobalState(GLOBAL_STATE_KEYS.CLIENT_FIRST_COMPILE, clientFirstCompile); + if (!clientState?.resolved) { + clientState.resolved = true; + clientState.resolve(); + setGlobalState(GLOBAL_STATE_KEYS.CLIENT_FIRST_COMPILE, clientState); + } + + if (process.env.METEOR_PROFILE) { + // Extract milliseconds from compilation message + const msMatch = data.match(/in (\d+) ms/); + if (msMatch && msMatch[1]) { + // Store the client compilation time + compilationState.clientMs = parseInt(msMatch[1], 10); + // Try to print max time if both compilations are complete + compilationState.printMaxTime(); + } + } + }; + + const onCompileServer = (data) => { + // Resolve the promise if it's the first compilation + const serverState = getGlobalState(GLOBAL_STATE_KEYS.SERVER_FIRST_COMPILE, serverFirstCompile); + if (!serverState?.resolved) { + serverState.resolved = true; + serverState.resolve(); + setGlobalState(GLOBAL_STATE_KEYS.SERVER_FIRST_COMPILE, serverState); + } + + if (process.env.METEOR_PROFILE) { + // Extract milliseconds from compilation message + const msMatch = data.match(/in (\d+) ms/); + if (msMatch && msMatch[1]) { + // Store the server compilation time + compilationState.serverMs = parseInt(msMatch[1], 10); + // Try to print max time if both compilations are complete + compilationState.printMaxTime(); + } + } + }; + + return { + clientFirstCompile, + serverFirstCompile, + clientFirstCompilePromise, + serverFirstCompilePromise, + onCompileClient, + onCompileServer + }; +} + +/** + * Waits for first compilation to complete + * @param {Object} clientFirstCompile - Client first compilation state + * @param {Object} serverFirstCompile - Server first compilation state + * @param {Promise} clientFirstCompilePromise - Promise for client first compilation + * @param {Promise} serverFirstCompilePromise - Promise for server first compilation + * @param {Object} options - Options for waiting + * @param {string} options.target - Target to wait for: 'client', 'server', or 'both' (default) + * @param {string} options.version - Specific version to wait for (optional) + * @returns {Promise} A promise that resolves when first compilation is complete + */ +export async function waitForFirstCompilation( + clientFirstCompile, + serverFirstCompile, + clientFirstCompilePromise, + serverFirstCompilePromise, + options = { target: 'both' } +) { + const clientState = getGlobalState(GLOBAL_STATE_KEYS.CLIENT_FIRST_COMPILE, clientFirstCompile); + const serverState = getGlobalState(GLOBAL_STATE_KEYS.SERVER_FIRST_COMPILE, serverFirstCompile); + + // If compilation is already complete, return immediately + if (process.env.RSPACK_FIRST_COMPILATION_COMPLETE) { + return; + } + + // Determine which compilation(s) to wait for based on target + switch (options.target) { + case 'client': + if (!clientState?.resolved) { + await clientFirstCompilePromise; + } + break; + case 'server': + if (!serverState?.resolved) { + await serverFirstCompilePromise; + } + break; + case 'both': + default: + if (!clientState?.resolved && !serverState?.resolved) { + await Promise.all([clientFirstCompilePromise, serverFirstCompilePromise]); + } + break; + } + + process.env.RSPACK_FIRST_COMPILATION_COMPLETE = true; +} diff --git a/packages/rspack/lib/config.js b/packages/rspack/lib/config.js new file mode 100644 index 0000000000..23ef8005a3 --- /dev/null +++ b/packages/rspack/lib/config.js @@ -0,0 +1,377 @@ +/** + * @module config + * @description Functions for configuring Meteor for Rspack + */ +import { glob } from 'glob'; +import path from 'path'; +import fs from 'fs'; + +const { logInfo } = require('meteor/tools-core/lib/log'); +const { + getMeteorAppFilesAndFolders, + setMeteorAppIgnore, + setMeteorAppEntrypoints, + setMeteorAppCustomScriptUrl, + isMeteorAppDevelopment, + isMeteorAppRun, + isMeteorAppBuild, + isMeteorAppDebug, + isMeteorAppTest, + isMeteorAppConfigModernVerbose, + isMeteorBlazeProject, + isMeteorLessProject, + isMeteorScssProject, + getMeteorEnvPackageDirs, + getMeteorAppConfig, + getMeteorAppDir, +} = require('meteor/tools-core/lib/meteor'); +const { buildUnignorePatterns } = require('meteor/tools-core/lib/ignore'); + +import { getInitialEntrypoints } from './build-context'; + +const { ensureModuleFilesExist, getBuildFilePath } = require('./build-context'); +const { RSPACK_BUILD_CONTEXT, FILE_ROLE } = require('./constants'); + +/** + * Checks if entries exist in .meteorignore file + * @param {string[]} entries - Entries to check + * @returns {Object} Results with entry keys and boolean values + */ +function checkMeteorIgnoreExactEntries(entries) { + const meteorIgnorePath = path.join(getMeteorAppDir(), '.meteorignore'); + const results = {}; + + // Initialize results object with false for each entry + entries.forEach(entry => { + results[entry] = false; + }); + + // Check if .meteorignore file exists + if (!fs.existsSync(meteorIgnorePath)) { + return results; + } + + // Read the .meteorignore file + try { + const content = fs.readFileSync(meteorIgnorePath, 'utf8'); + const lines = content.split('\n'); + + // Check each line against all entries + lines.forEach(line => { + // Skip empty lines and comments + if (!line.trim() || line.trim().startsWith('#')) { + return; + } + + const trimmedLine = line.trim(); + + // Check for exact matches + entries.forEach(entry => { + if (trimmedLine === entry) { + results[entry] = true; + } + }); + }); + } catch (error) { + // If there's an error reading the file, return the initialized results + } + + return results; +} + +/** + * Gets the list of file extensions to ignore based on project type + * For Blaze projects, it excludes .html as used by Blaze + * For Less projects, it excludes .less files + * For SCSS projects, it excludes .scss files + * @returns {string[]} Array of file extensions to ignore + */ +function getFileExtensionsToIgnore() { + const isAnyCompilerProject = + isMeteorBlazeProject() || isMeteorLessProject() || isMeteorScssProject(); + if (!isAnyCompilerProject) { + return []; + } + + const allFiles = glob.sync('**/*', { + nodir: true, + dot: true, + ignore: ['node_modules/**', '.meteor/**'], + }); + const existingExts = Array.from( + new Set(allFiles.map(f => path.extname(f).toLowerCase())), + ); + + // Base extensions to ignore + const baseExtensions = [ + '.ts', + '.tsx', + '.js', + '.jsx', + '.mjs', + '.cjs', + '.json', + ]; + + // Filter existing extensions based on project type + let filteredExts = existingExts; + + // For Blaze projects, exclude .html files + if (isMeteorBlazeProject()) { + filteredExts = existingExts.filter(ext => ext !== '.html'); + } + + // Check for Less projects and exclude .less files + if (isMeteorLessProject()) { + filteredExts = filteredExts.filter(ext => ext !== '.less'); + } + + // Check for SCSS projects and exclude .scss files + if (isMeteorScssProject()) { + filteredExts = filteredExts.filter(ext => ext !== '.scss'); + } + + return Array.from(new Set([...baseExtensions, ...filteredExts])).filter( + ext => ext !== '', + ); +} + +/** + * Configures Meteor settings for Rspack + * Sets up file ignores, entry points, and custom script URL + * Creates necessary module files and writes content to them + * @returns {void} + */ +export function configureMeteorForRspack() { + const meteorAppConfig = getMeteorAppConfig(); + const initialEntrypoints = getInitialEntrypoints(); + + // Ignore node_modules to prevent Meteor from processing them + const projectRootFilesAndFolders = getMeteorAppFilesAndFolders({ + recursive: false, + }); + + const initialEntrypointContexts = [ + initialEntrypoints.mainClient, + initialEntrypoints.mainServer, + ].map(entrypoint => path.dirname(entrypoint)); + const includedDirs = ['public', 'private', '.meteor', RSPACK_BUILD_CONTEXT]; + const ignoredDirs = projectRootFilesAndFolders.directories.filter( + dir => !includedDirs.includes(dir), + ); + + const envPackageDirs = getMeteorEnvPackageDirs().map( + dir => path.normalize(dir)?.split(path.sep)?.filter(Boolean)?.[0], + ); + let extraFoldersToIgnore = [ + ...ignoredDirs + .filter( + dir => + ![ + 'public', + 'private', + '.meteor', + 'packages', + ...envPackageDirs, + RSPACK_BUILD_CONTEXT, + ].includes(dir), + ) + .map(dir => `${dir}/**`), + ]; + let extraFilesToIgnore = []; + + // Get extensions to ignore based on project type + const extensionsToIgnore = getFileExtensionsToIgnore(); + // If we have extensions to ignore, apply them to the ignored directories + if (extensionsToIgnore.length > 0) { + extraFilesToIgnore = ignoredDirs.flatMap(dir => + extensionsToIgnore.map(ext => `${dir}/**/*${ext}`), + ); + extraFoldersToIgnore = []; + } + + // Skip CSS/HTML files in entrypoint contexts + extraFilesToIgnore = [ + ...extraFilesToIgnore, + ...initialEntrypointContexts.flatMap(entrypoint => { + const cssPattern = `${entrypoint}/*.css`; + const htmlPattern = `${entrypoint}/*.html`; + + const cssFiles = glob.sync(cssPattern); + const htmlFiles = glob.sync(htmlPattern); + + const entriesToCheck = [ + cssPattern, + htmlPattern, + ...cssFiles, + ...htmlFiles + ]; + + const entryResults = checkMeteorIgnoreExactEntries(entriesToCheck); + const hasMatchingCssPattern = entryResults[cssPattern]; + const hasMatchingHtmlPattern = entryResults[htmlPattern]; + const hasAnyCssFileInMeteorIgnore = cssFiles.some(file => entryResults[file]); + const hasAnyHtmlFileInMeteorIgnore = htmlFiles.some(file => entryResults[file]); + + const result = []; + + // Handle HTML files + if (hasAnyHtmlFileInMeteorIgnore) { + // Add individual HTML files that are not in meteorignore + htmlFiles.forEach(file => { + if (!entryResults[file]) { + result.push(`!${file}`); + } + }); + } else if (!hasMatchingHtmlPattern) { + // Skip HTML pattern if not in meteorignore + result.push(`!${htmlPattern}`); + } + + // Handle CSS files + if (hasAnyCssFileInMeteorIgnore) { + // Add individual CSS files that are not in meteorignore + cssFiles.forEach(file => { + if (!entryResults[file]) { + result.push(`!${file}`); + } + }); + } else if (!hasMatchingCssPattern) { + // Skip CSS pattern if not in meteorignore + result.push(`!${cssPattern}`); + } + + return result; + }), + ]; + + const testIgnorePath = `${RSPACK_BUILD_CONTEXT}/${path.dirname( + getBuildFilePath({ + isTest: true, + }), + )}/**`; + const otherMainIgnorePath = + (isMeteorAppDevelopment() && + `${RSPACK_BUILD_CONTEXT}/${path.dirname( + getBuildFilePath({ + isMain: true, + isProduction: true, + }), + )}/**`) || + `${RSPACK_BUILD_CONTEXT}/${path.dirname( + getBuildFilePath({ + isMain: true, + isDevelopment: true, + }), + )}/**`; + const foldersToIgnore = [ + ...((isMeteorAppTest() && [otherMainIgnorePath]) || [ + testIgnorePath, + otherMainIgnorePath, + ]), + 'node_modules/**', + ...extraFoldersToIgnore, + ].filter(Boolean); + const rootFilesToIgnore = [ + ...projectRootFilesAndFolders.files.filter( + file => + ![ + 'package.json', + '.meteorignore', + 'tsconfig.json', + 'postcss.config.js', + 'scss-config.json', + ].includes(file), + ), + ]; + const filesToIgnore = [...rootFilesToIgnore, ...extraFilesToIgnore]; + const unignoredFilesAndFolders = buildUnignorePatterns( + meteorAppConfig?.modules || [], + { skipLevel: 1 }, + ); + const meteorAppIgnores = `${foldersToIgnore.join(' ')} ${filesToIgnore.join( + ' ', + )} ${unignoredFilesAndFolders.join(' ')}`.trim(); + setMeteorAppIgnore(meteorAppIgnores); + + if (isMeteorAppDebug() || isMeteorAppConfigModernVerbose()) { + logInfo(`[i] Meteor app ignores: ${meteorAppIgnores}`); + } + + const env = isMeteorAppDevelopment() + ? { isDevelopment: true } + : { isProduction: true }; + const commandRole = isMeteorAppRun() + ? { role: FILE_ROLE.run } + : isMeteorAppBuild() + ? { role: FILE_ROLE.build } + : { role: FILE_ROLE.run }; + const mainClientModule = getBuildFilePath({ + isMain: true, + ...env, + ...commandRole, + isClient: true, + }); + const mainServerModule = getBuildFilePath({ + isMain: true, + ...env, + ...commandRole, + isServer: true, + }); + const isTestEager = + initialEntrypoints.testModule == null && + initialEntrypoints.testClient == null && + initialEntrypoints.testServer == null; + const isTestModule = initialEntrypoints.testModule != null || isTestEager; + const testClientModule = getBuildFilePath({ + isTest: true, + ...env, + ...commandRole, + isTestModule, + isClient: true, + }); + const testServerModule = getBuildFilePath({ + isTest: true, + ...env, + ...commandRole, + isTestModule, + isServer: true, + }); + + const appEntrypoints = { + mainClient: `${RSPACK_BUILD_CONTEXT}/${mainClientModule}`, + mainServer: `${RSPACK_BUILD_CONTEXT}/${mainServerModule}`, + ...((isTestModule && { + testClient: `${RSPACK_BUILD_CONTEXT}/${testClientModule}`, + testServer: `${RSPACK_BUILD_CONTEXT}/${testServerModule}`, + }) || { + testClient: `${RSPACK_BUILD_CONTEXT}/${testClientModule}`, + testServer: `${RSPACK_BUILD_CONTEXT}/${testServerModule}`, + }), + }; + // Set entry points in environment variables if they exist + setMeteorAppEntrypoints(appEntrypoints); + + if (isMeteorAppDebug() || isMeteorAppConfigModernVerbose()) { + logInfo(`[i] App entrypoints: ${JSON.stringify(appEntrypoints, null, 2)}`); + } + + // Ensure module files exist + ensureModuleFilesExist(); + + // Write content to module files + if (isMeteorAppRun() && isMeteorAppDevelopment()) { + const customScriptUrl = `/__rspack__/${getBuildFilePath({ + ...env, + isMain: true, + isClient: true, + role: FILE_ROLE.output, + onlyFilename: true, + })}`; + setMeteorAppCustomScriptUrl(customScriptUrl); + + if (isMeteorAppDebug() || isMeteorAppConfigModernVerbose()) { + logInfo(`[i] App custom script: ${customScriptUrl}`); + } + } +} diff --git a/packages/rspack/lib/constants.js b/packages/rspack/lib/constants.js new file mode 100644 index 0000000000..3e5a1e222f --- /dev/null +++ b/packages/rspack/lib/constants.js @@ -0,0 +1,102 @@ +/** + * @module constants + * @description Constants and global state keys for Rspack plugin + */ + +export const DEFAULT_RSPACK_VERSION = '1.7.1'; + +export const DEFAULT_METEOR_RSPACK_VERSION = '1.0.0'; + +export const DEFAULT_METEOR_RSPACK_REACT_HMR_VERSION = '1.4.3'; + +export const DEFAULT_METEOR_RSPACK_REACT_REFRESH_VERSION = '0.17.0'; + +export const DEFAULT_METEOR_RSPACK_SWC_LOADER_VERSION = '0.2.6'; + +export const DEFAULT_METEOR_RSPACK_SWC_HELPERS_VERSION = '0.5.17'; + +export const DEFAULT_RSDOCTOR_RSPACK_PLUGIN_VERSION = '1.2.3'; + +/** + * Global state keys used for storing and retrieving state across the application + * @constant {Object} + * @property {string} CLIENT_PROCESS - Key for storing the client process + * @property {string} SERVER_PROCESS - Key for storing the server process + * @property {string} RSPACK_INSTALLATION_CHECKED - Key for tracking if Rspack installation was checked + * @property {string} IS_REACT_ENABLED - Key for tracking if React is enabled + * @property {string} INITIAL_ENTRYPONTS - Key for storing initial entrypoints + * @property {string} CLIENT_FIRST_COMPILE - Key for tracking client first compilation state + * @property {string} SERVER_FIRST_COMPILE - Key for tracking server first compilation state + * @property {string} BUILD_CONTEXT_FILES_CLEANED - Key for tracking if build context files have been cleaned + */ +export const GLOBAL_STATE_KEYS = { + CLIENT_PROCESS: 'rspack.clientProcess', + SERVER_PROCESS: 'rspack.serverProcess', + RSPACK_INSTALLATION_CHECKED: 'rspack.rspackInstallationChecked', + RSPACK_REACT_INSTALLATION_CHECKED: 'rspack.rspackReactInstallationChecked', + RSPACK_DOCTOR_INSTALLATION_CHECKED: 'rspack.rspackDoctorInstallationChecked', + REACT_CHECKED: 'rspack.reactChecked', + TYPESCRIPT_CHECKED: 'rspack.typescriptChecked', + ANGULAR_CHECKED: 'rspack.angularChecked', + INITIAL_ENTRYPONTS: 'meteor.initialEntrypoints', + CLIENT_FIRST_COMPILE: 'rspack.clientFirstCompile', + SERVER_FIRST_COMPILE: 'rspack.serverFirstCompile', + BUILD_CONTEXT_FILES_CLEANED: 'rspack.buildContextFilesCleaned', +}; + +const meteorConfig = typeof Plugin !== 'undefined' ? Plugin?.getMeteorConfig() : null; + +/** + * Directory name for Rspack build context + * Can be overridden with RSPACK_BUILD_CONTEXT environment variable + * @constant {string} + */ +export const RSPACK_BUILD_CONTEXT = + meteorConfig?.buildContext || + process.env.RSPACK_BUILD_CONTEXT || + '_build'; + +process.env.RSPACK_BUILD_CONTEXT = RSPACK_BUILD_CONTEXT; + +/** + * Directory name for Rspack assets context + * Can be overridden with RSPACK_ASSETS_CONTEXT environment variable + * @constant {string} + */ +export const RSPACK_ASSETS_CONTEXT = + meteorConfig?.assetsContext || + process.env.RSPACK_ASSETS_CONTEXT || + 'build-assets'; + +process.env.RSPACK_ASSETS_CONTEXT = RSPACK_ASSETS_CONTEXT; + +/** + * Directory name for Rspack bundles context + * Can be overridden with RSPACK_ASSETS_CONTEXT environment variable + * @constant {string} + */ +export const RSPACK_CHUNKS_CONTEXT = + meteorConfig?.chunksContext || + process.env.RSPACK_CHUNKS_CONTEXT || + 'build-chunks'; + +process.env.RSPACK_CHUNKS_CONTEXT = RSPACK_CHUNKS_CONTEXT; + +/** + * Directory name for Rspack doctor context + * @type {string} + */ +export const RSPACK_DOCTOR_CONTEXT = '.rsdoctor'; + +/** + * Regex pattern for hot update files + * @constant {RegExp} + */ +export const RSPACK_HOT_UPDATE_REGEX = /^\/(.+\.hot-update\.(?:json|js))$/; + +export const FILE_ROLE = { + build: 'build', + entry: 'entry', + run: 'run', + output: 'output', +}; diff --git a/packages/rspack/lib/dependencies.js b/packages/rspack/lib/dependencies.js new file mode 100644 index 0000000000..20ebca4271 --- /dev/null +++ b/packages/rspack/lib/dependencies.js @@ -0,0 +1,315 @@ +/** + * @module dependencies + * @description Functions for managing dependencies for Rspack plugin + */ +import { + DEFAULT_METEOR_RSPACK_REACT_REFRESH_VERSION, + DEFAULT_METEOR_RSPACK_SWC_HELPERS_VERSION, + DEFAULT_RSDOCTOR_RSPACK_PLUGIN_VERSION +} from "./constants"; + +const { + getGlobalState, + setGlobalState, +} = require('meteor/tools-core/lib/global-state'); +const { + logProgress, + logSuccess, + logInfo, + logError, +} = require('meteor/tools-core/lib/log'); +const { + isMeteorAppUpdate, + getMeteorAppDir, +} = require('meteor/tools-core/lib/meteor'); +const { + checkNpmDependencyExists, + installNpmDependency, + checkNpmDependencyVersion, +} = require('meteor/tools-core/lib/npm'); +const { + joinWithAnd, +} = require('meteor/tools-core/lib/string'); + +const { + DEFAULT_RSPACK_VERSION, + DEFAULT_METEOR_RSPACK_VERSION, + DEFAULT_METEOR_RSPACK_REACT_HMR_VERSION, + GLOBAL_STATE_KEYS, +} = require('./constants'); + +/** + * Generic function to ensure dependencies are installed with correct versions + * @param {Object[]} dependencies - Array of dependency objects with name, version, and semverCondition + * @param {string} globalStateKey - Global state key to track if check has been done + * @param {string} packageName - Name of the package for logging purposes + * @returns {Promise} A promise that resolves when the check/installation is complete + * @throws {Error} If installation fails + */ +async function ensureDependenciesInstalled(dependencies, globalStateKey, packageName) { + // Skip if already checked + if (getGlobalState(globalStateKey, false)) { + return; + } + + const appDir = getMeteorAppDir(); + + // Filter dependencies that need to be installed (missing or wrong version) + const allDepsToInstall = dependencies.filter(dep => + !checkNpmDependencyExists(dep.name, { cwd: appDir }) || + !checkNpmDependencyVersion(dep.name, { + cwd: appDir, + versionRequirement: dep.version, + semverCondition: dep.semverCondition || 'gte', + existenceOnly: dep.existenceOnly, + }) + ); + + // Format dependencies for installation + const dependencyStrings = allDepsToInstall.map(dep => `${dep.name}@${dep.version}`); + + if (allDepsToInstall.length > 0) { + let devDepsSuccess = true; + let regularDepsSuccess = true; + let devDepsStrings = []; + let regularDepsStrings = []; + + // Display a header for the installation process + logProgress(`┌─────────────────────────────────────────────────`); + logProgress(`│ ${packageName} Dependencies Installation`); + logProgress(`└─────────────────────────────────────────────────`); + + // Show what dependencies will be installed + logInfo(`The following ${packageName} dependencies need to be installed:`); + dependencyStrings.forEach(dep => { + logInfo(` • ${dep}`); + }); + + // Check if this is a Yarn project + const isYarnProj = process.env.YARN_ENABLED === 'true'; + + // Install dev dependencies + const devDepsToInstall = allDepsToInstall.filter(dep => dep.dev === true || dep.dev == null); + if (devDepsToInstall.length > 0) { + devDepsStrings = devDepsToInstall.map(dep => `${dep.name}@${dep.version}`); + + // Log progress for dev dependencies + logProgress( + `🔧 Installing ${devDepsToInstall.length} dev dependenc${ + devDepsToInstall.length === 1 ? "y" : "ies" + }...` + ); + + devDepsSuccess = await installNpmDependency(devDepsStrings, { + cwd: appDir, + dev: true, + yarn: isYarnProj, + }); + } + + // Install regular dependencies + const regularDepsToInstall = allDepsToInstall.filter(dep => dep.dev === false); + if (regularDepsToInstall.length > 0) { + regularDepsStrings = regularDepsToInstall.map(dep => `${dep.name}@${dep.version}`); + + // Log progress for regular dependencies + logProgress( + `🔧 Installing ${regularDepsToInstall.length} dependenc${ + regularDepsToInstall.length === 1 ? "y" : "ies" + }...` + ); + + regularDepsSuccess = await installNpmDependency(regularDepsStrings, { + cwd: appDir, + dev: false, + yarn: isYarnProj, + }); + } + + const success = devDepsSuccess && regularDepsSuccess; + + if (!success) { + const isYarnProj = process.env.YARN_ENABLED === 'true'; + + logError(`\n┌─────────────────────────────────────────────────`); + logError(`│ ❌ ${packageName} Installation Failed`); + logError(`└─────────────────────────────────────────────────`); + + if (!devDepsSuccess && devDepsStrings.length > 0) { + const devInstallCommand = isYarnProj + ? `yarn add --dev ${devDepsStrings.join(' ').trim()}` + : `meteor npm install -D ${devDepsStrings.join(' ').trim()}`; + logError(`For dev dependencies, run: ${devInstallCommand}`); + } + + if (!regularDepsSuccess && regularDepsStrings.length > 0) { + const regularInstallCommand = isYarnProj + ? `yarn add ${regularDepsStrings.join(' ').trim()}` + : `meteor npm install ${regularDepsStrings.join(' ').trim()}`; + logError(`For regular dependencies, run: ${regularInstallCommand}`); + } + + const allFailedDeps = []; + if (!devDepsSuccess) allFailedDeps.push('dev dependencies'); + if (!regularDepsSuccess) allFailedDeps.push('regular dependencies'); + + throw new Error( + `Failed to install ${packageName} ${joinWithAnd(allFailedDeps)}. Please install them manually with the commands above.` + ); + } + + logSuccess(`✅ ${packageName} dependencies installed`); + + if (isMeteorAppUpdate()) { + const isYarnProj = process.env.YARN_ENABLED === 'true'; + const installCommand = isYarnProj ? 'yarn install' : 'npm install'; + + logInfo(`\n┌───────────────────────────────────────────────────────────────────────┐`); + logInfo(`│ 🔔 IMPORTANT: Project Stability Reminder │`); + logInfo(`├───────────────────────────────────────────────────────────────────────┤`); + logInfo(`│ After the Meteor update finishes, please run \`${installCommand}\` in your │`); + logInfo(`│ project directory. │`); + logInfo(`│ │`); + logInfo(`│ This helps keep your dependencies correct and your project stable. │`); + logInfo(`└───────────────────────────────────────────────────────────────────────┘`); + } + } + + // Mark as checked + setGlobalState(globalStateKey, true); +} + +/** + * Checks if Rspack is installed, and installs it if not + * @returns {Promise} A promise that resolves when the check/installation is complete + * @throws {Error} If Rspack installation fails + */ +export async function ensureRspackInstalled() { + const dependencies = [ + { name: '@rspack/cli', version: DEFAULT_RSPACK_VERSION, semverCondition: 'gte', dev: true }, + { name: '@rspack/core', version: DEFAULT_RSPACK_VERSION, semverCondition: 'gte', dev: true }, + { name: '@meteorjs/rspack', version: DEFAULT_METEOR_RSPACK_VERSION, semverCondition: 'gte', dev: true }, + { name: '@swc/helpers', version: DEFAULT_METEOR_RSPACK_SWC_HELPERS_VERSION, semverCondition: 'gte', dev: false }, + { name: '@rsdoctor/rspack-plugin', version: DEFAULT_RSDOCTOR_RSPACK_PLUGIN_VERSION, semverCondition: 'gte', dev: true }, + ]; + + await ensureDependenciesInstalled( + dependencies, + GLOBAL_STATE_KEYS.RSPACK_INSTALLATION_CHECKED, + 'Rspack', + ); +} + +/** + * Checks if React is installed and sets global state accordingly + * Sets global state and environment variables based on React detection + * @returns {Promise} A promise that resolves when the check is complete + */ +export function checkReactInstalled() { + // Skip if already checked + if (getGlobalState(GLOBAL_STATE_KEYS.REACT_CHECKED, false)) { + return; + } + + const appDir = getMeteorAppDir(); + // Check if React is a dependency in the project + const isReactInstalled = checkNpmDependencyExists('react', { cwd: appDir }); + + if (isReactInstalled) { + // Set environment variable to indicate React is enabled + process.env.METEOR_REACT_ENABLED = 'true'; + } else { + process.env.METEOR_REACT_ENABLED = 'false'; + } + + // Mark as checked + setGlobalState(GLOBAL_STATE_KEYS.REACT_CHECKED, true); + + return isReactInstalled; +} + +export async function ensureRspackReactInstalled() { + const dependencies = [ + { name: '@rspack/plugin-react-refresh', version: DEFAULT_METEOR_RSPACK_REACT_HMR_VERSION, semverCondition: 'gte', dev: true }, + { name: 'react-refresh', version: DEFAULT_METEOR_RSPACK_REACT_REFRESH_VERSION, semverCondition: 'gte', dev: true }, + ]; + + await ensureDependenciesInstalled( + dependencies, + GLOBAL_STATE_KEYS.RSPACK_REACT_INSTALLATION_CHECKED, + 'Rspack React' + ); +} + +/** + * Checks if Rspack Doctor is installed, and installs it if not + * @returns {Promise} A promise that resolves when the check/installation is complete + * @throws {Error} If Rspack Doctor installation fails + */ +export async function ensureRspackDoctorInstalled() { + const dependencies = [ + { name: '@rsdoctor/rspack-plugin', version: DEFAULT_RSDOCTOR_RSPACK_PLUGIN_VERSION, semverCondition: 'gte', dev: true }, + ]; + + await ensureDependenciesInstalled( + dependencies, + GLOBAL_STATE_KEYS.RSPACK_DOCTOR_INSTALLATION_CHECKED, + 'Rspack Doctor' + ); +} + +/** + * Checks if TypeScript is installed and sets global state accordingly + * Sets global state and environment variables based on TypeScript detection + * @returns {boolean} Whether TypeScript is installed + */ +export function checkTypescriptInstalled() { + // Skip if already checked + if (getGlobalState(GLOBAL_STATE_KEYS.TYPESCRIPT_CHECKED, false)) { + return; + } + + const appDir = getMeteorAppDir(); + // Check if TypeScript is a dependency in the project + const isTypescriptInstalled = checkNpmDependencyExists('typescript', { cwd: appDir }); + + if (isTypescriptInstalled) { + // Set environment variable to indicate TypeScript is enabled + process.env.METEOR_TYPESCRIPT_ENABLED = 'true'; + } else { + process.env.METEOR_TYPESCRIPT_ENABLED = 'false'; + } + + // Mark as checked + setGlobalState(GLOBAL_STATE_KEYS.TYPESCRIPT_CHECKED, true); + + return isTypescriptInstalled; +} + +/** + * Checks if Angular is installed and sets global state accordingly + * Sets global state and environment variables based on Angular detection + * @returns {boolean} Whether Angular is installed + */ +export function checkAngularInstalled() { + // Skip if already checked + if (getGlobalState(GLOBAL_STATE_KEYS.ANGULAR_CHECKED, false)) { + return; + } + + const appDir = getMeteorAppDir(); + // Check if @nx/angular-rspack is a dependency in the project + const isAngularInstalled = checkNpmDependencyExists('@nx/angular-rspack', { cwd: appDir }); + + if (isAngularInstalled) { + // Set environment variable to indicate Angular is enabled + process.env.METEOR_ANGULAR_ENABLED = 'true'; + } else { + process.env.METEOR_ANGULAR_ENABLED = 'false'; + } + + // Mark as checked + setGlobalState(GLOBAL_STATE_KEYS.ANGULAR_CHECKED, true); + + return isAngularInstalled; +} diff --git a/packages/rspack/lib/processes.js b/packages/rspack/lib/processes.js new file mode 100644 index 0000000000..d97d731257 --- /dev/null +++ b/packages/rspack/lib/processes.js @@ -0,0 +1,515 @@ +/** + * @module processes + * @description Functions for managing Rspack processes + */ + +import fs from 'fs'; +import path from 'path'; + +const { + spawnProcess, + stopProcess, + isProcessRunning +} = require('meteor/tools-core/lib/process'); + +const { + logError, + logInfo, +} = require('meteor/tools-core/lib/log'); + +const { + getMeteorAppDir, + isMeteorAppTest, + isMeteorAppTestFullApp, + isMeteorAppDevelopment, + isMeteorAppProduction, + isMeteorAppDebug, + isMeteorAppRun, + isMeteorAppBuild, + isMeteorAppNative, + isMeteorBlazeProject, + isMeteorBlazeHotProject, + getMeteorInitialAppEntrypoints, + isMeteorAppConfigModernVerbose, + isMeteorBundleVisualizerProject, + getMeteorAppPort, +} = require('meteor/tools-core/lib/meteor'); + +const { + checkNpmDependencyExists, + getNpxCommand, + getMonorepoPath, +} = require('meteor/tools-core/lib/npm'); + +const { + getGlobalState, + setGlobalState +} = require('meteor/tools-core/lib/global-state'); + +const { + GLOBAL_STATE_KEYS, + RSPACK_CHUNKS_CONTEXT, + RSPACK_ASSETS_CONTEXT, + FILE_ROLE, +} = require('./constants'); + +const { + getBuildFilePath, + getBuildFileContent, +} = require('./build-context'); + +/** + * Calculates the devServerPort based on process.env.PORT + * Base port is 8077, and we add the sum of the digits of process.env.PORT + * @returns {number} The calculated devServerPort + */ +export function calculateDevServerPort() { + const port = getMeteorAppPort(); + const basePort = 8077; + + // Sum the digits of the port + const digitSum = port.split('').reduce((sum, digit) => sum + parseInt(digit, 10), 0); + + return basePort + digitSum; +} + +/** + * Calculates the Rsdoctor client port based on process.env.PORT + * Base port is 8885, and we add the sum of the digits of process.env.PORT + * @returns {number} The calculated Rsdoctor client port + */ +export function calculateRsdoctorClientPort() { + const port = getMeteorAppPort(); + const basePort = 8885; + + // Sum the digits of the port + const digitSum = port.split('').reduce((sum, digit) => sum + parseInt(digit, 10), 0); + + return basePort + digitSum; +} + +/** + * Calculates the Rsdoctor server port based on process.env.PORT + * Base port is 8885, and we add the sum of the digits of process.env.PORT + 1 + * @returns {number} The calculated Rsdoctor server port + */ +export function calculateRsdoctorServerPort() { + const port = getMeteorAppPort(); + const basePort = 8885; + + // Sum the digits of the port + const digitSum = port.split('').reduce((sum, digit) => sum + parseInt(digit, 10), 0); + + // Add 1 to differentiate from client port + return basePort + digitSum + 1; +} + +/** + * Helper function to check for a file with different extensions in order of priority + * @param {string} basePath - The base directory path (without 'rspack.config' and extension) + * @returns {string|null} The full path with extension if found, null otherwise + */ +export function getCustomConfigFilePath(basePath = getMeteorAppDir()) { + const configBasePath = path.join(basePath, 'rspack.config'); + + // Check for .js extension first (highest priority) + const jsPath = `${configBasePath}.js`; + if (fs.existsSync(jsPath)) { + return jsPath; + } + + // Check for .ts extension next + const tsPath = `${configBasePath}.ts`; + if (fs.existsSync(tsPath)) { + return tsPath; + } + + // Check for .mjs extension next + const mjsPath = `${configBasePath}.mjs`; + if (fs.existsSync(mjsPath)) { + return mjsPath; + } + + // Check for .cjs extension last + const cjsPath = `${configBasePath}.cjs`; + if (fs.existsSync(cjsPath)) { + return cjsPath; + } + + // No valid config file found with any extension + return null; +} + +/** + * Gets the appropriate config file name based on environment + * @returns {string} The name of the Rspack config file + * @throws {Error} If no valid config file is found + */ +export function getConfigFilePath() { + // Check if the config file exists at the current path with any of the supported extensions + const defaultConfigBasePath = path.join(process.cwd(), 'node_modules/@meteorjs/rspack'); + const defaultConfigPath = getCustomConfigFilePath(defaultConfigBasePath); + if (defaultConfigPath) { + return defaultConfigPath; + } + + // If not found, check if we're in a monorepo and look for alternative config + const monorepoPath = getMonorepoPath(); + if (monorepoPath) { + const alternativeConfigBasePath = path.join(monorepoPath, 'node_modules/@meteorjs/rspack'); + const alternativeConfigPath = getCustomConfigFilePath(alternativeConfigBasePath); + if (alternativeConfigPath) { + return alternativeConfigPath; + } + } + + // If no config file is found, throw an error with suggestion to run npm install + const isYarnProj = process.env.YARN_ENABLED === 'true'; + const installCommand = isYarnProj ? 'yarn install' : 'npm install'; + throw new Error( + `Could not find rspack.config.js, rspack.config.ts, rspack.config.mjs, or rspack.config.cjs.\n\n` + + `Try running \`${installCommand}\` in your project directory and then re-run the build.\n` + + `This will ensure @meteorjs/rspack is installed correctly.` + ); +} + +/** + * Gets the appropriate Rspack environment variables and command line arguments + * @param {Object} options - Options for environment variables + * @param {boolean} options.isClient - Whether this is for client-side build + * @param {boolean} options.isServer - Whether this is for server-side build + * @param {boolean} options.isTest - Whether this is for test build + * @param {boolean} options.isTestLike - Whether test envs should be inherited + * @returns {Object} Object containing params (command line arguments) and envs (environment variables) + */ +export function getRspackEnv({ isClient, isServer, isTest: inIsTest, isTestLike: inIsTestLike }) { + const RSPACK_BUILD_CONTEXT = require('./constants').RSPACK_BUILD_CONTEXT; + + const initialEntrypoints = getMeteorInitialAppEntrypoints(); + const isTest = inIsTest != null ? inIsTest : isMeteorAppTest(); + const isTestLike = isTest || inIsTestLike; + const isTestEager = + initialEntrypoints.testModule == null && + initialEntrypoints.testClient == null && + initialEntrypoints.testServer == null; + const isTestModule = initialEntrypoints.testModule != null || isTestEager; + const isTestFullApp = isMeteorAppTestFullApp(); + + const module = isTest ? { isTest: true } : { isMain: true }; + const env = isMeteorAppDevelopment() + ? { isDevelopment: true } + : { isProduction: true }; + const side = isClient ? { isClient: true } : { isServer: true }; + const commandRole = isMeteorAppRun() + ? { role: FILE_ROLE.run } + : isMeteorAppBuild() + ? { role: FILE_ROLE.build } + : { role: FILE_ROLE.run }; + + const entryKey = `${isTest && isTestModule ? 'test' : 'main'}${isClient ? 'Client' : 'Server'}`; + const inputFilePath = initialEntrypoints[entryKey]; + const isTypescriptEnabled = process.env.METEOR_TYPESCRIPT_ENABLED === 'true' || + inputFilePath?.endsWith('.ts') || + inputFilePath?.endsWith('.tsx'); + + const isReactEnabled = process.env.METEOR_REACT_ENABLED === 'true'; + const isAngularEnabled = process.env.METEOR_ANGULAR_ENABLED === 'true'; + const isTsxEnabled = isTypescriptEnabled && (inputFilePath?.endsWith('.tsx') || isReactEnabled); + const isJsxEnabled = !isTypescriptEnabled && (inputFilePath?.endsWith('.jsx') || isReactEnabled); + + const isBlazeEnabled = isMeteorBlazeProject(); + const isBlazeHotEnabled = isMeteorBlazeHotProject(); + const isBundleVisualizerEnabled = isMeteorBundleVisualizerProject(); + + const swcExternalHelpers = checkNpmDependencyExists('@swc/helpers'); + + const configPath = getConfigFilePath(); + const projectConfigPath = getCustomConfigFilePath(); + + const pairs = [ + ['isDevelopment', isMeteorAppDevelopment()], + ['isProduction', isMeteorAppProduction()], + ['isDebug', isMeteorAppDebug()], + ['isVerbose', isMeteorAppConfigModernVerbose()], + ['isTest', isTest], + ...(isTestLike ? [['isTestLike', isTestLike || isTest]] : []), + ...(isTestLike && isTestFullApp && [['isTestFullApp', isTestFullApp]] || []), + ...(isTestLike && isTestModule && [['isTestModule', isTestModule]] || []), + ...(isTestLike && isTestEager && [['isTestEager', isTestEager]] || []), + ['isRun', isMeteorAppRun()], + ['isBuild', isMeteorAppBuild()], + ['isNative', isMeteorAppNative()], + ['isClient', isClient], + ['isServer', isServer], + ['entryPath', getBuildFilePath({ ...module, ...env, ...side, isTestModule, role: FILE_ROLE.entry }) ], + ['outputPath', getBuildFilePath({ ...module, ...env, ...side, isTestModule, role: FILE_ROLE.output }) ], + ['outputFilename', + getBuildFilePath({ + ...env, + ...side, + isMain: true, + role: FILE_ROLE.output, + onlyFilename: true, + }), + ], + ['runPath', getBuildFilePath({ ...module, ...env, ...side, ...commandRole }) ], + ['buildContext', RSPACK_BUILD_CONTEXT], + ['chunksContext', RSPACK_CHUNKS_CONTEXT], + ['assetsContext', RSPACK_ASSETS_CONTEXT], + ['devServerPort', process.env.RSPACK_DEVSERVER_PORT], + ['projectConfigPath', projectConfigPath], + ['configPath', configPath], + ...((isTest && + initialEntrypoints.testClient && + initialEntrypoints.testServer && [ + ['testClientEntry', initialEntrypoints.testClient], + ['testServerEntry', initialEntrypoints.testServer], + ]) || + (isTest && + initialEntrypoints.testModule && [ + ['testEntry', initialEntrypoints.testModule], + ]) || [ + ['mainClientEntry', initialEntrypoints.mainClient], + ['mainClientHtmlEntry', initialEntrypoints.mainClientHtml], + ['mainServerEntry', initialEntrypoints.mainServer], + ]), + ...(swcExternalHelpers && [['swcExternalHelpers', swcExternalHelpers]] || []), + ...(isReactEnabled && [['isReactEnabled', isReactEnabled]] || []), + ...(isBlazeEnabled && [['isBlazeEnabled', isBlazeEnabled]] || []), + ...(isBlazeHotEnabled && [['isBlazeHotEnabled', isBlazeHotEnabled]] || []), + ...(isTypescriptEnabled && [['isTypescriptEnabled', isTypescriptEnabled]] || []), + ...(isAngularEnabled && [['isAngularEnabled', isAngularEnabled]] || []), + ...(isTsxEnabled && [['isTsxEnabled', isTsxEnabled]] || []), + ...(isJsxEnabled && [['isJsxEnabled', isJsxEnabled]] || []), + ...(isBundleVisualizerEnabled && [ + ['isBundleVisualizerEnabled', isBundleVisualizerEnabled], + ['rsdoctorClientPort', process.env.RSDOCTOR_CLIENT_PORT], + ['rsdoctorServerPort', process.env.RSDOCTOR_SERVER_PORT], + ] || []), + + ].filter(Boolean); + + // Create environment variables object with bannerOutput + const envs = { + RSPACK_BANNER: JSON.stringify(getBuildFileContent({ ...module, ...env, ...side, role: FILE_ROLE.output })) + }; + + // Create params from pairs + const params = pairs.flatMap(([key, val]) => [ + '--env', + `${key}=${val}` + ]); + + return { params, envs }; +} + +/** + * Starts Rspack for client in serve mode + * @param {Object} options - Options for client serve + * @param {Function} options.onCompile - Callback function to be called when compilation is complete + * @returns {Object} The client process object + */ +export function startRspackClientServe(options = {}) { + const { onCompile } = options; + // Get the current client process from global state + const clientProcess = getGlobalState(GLOBAL_STATE_KEYS.CLIENT_PROCESS, null); + + // Skip if client process is already running + if (clientProcess && isProcessRunning(clientProcess)) { + return clientProcess; + } + + const appDir = getMeteorAppDir(); + const configFile = getConfigFilePath(); + const { params, envs } = getRspackEnv({ isClient: true, isServer: false }); + const { command, args } = getNpxCommand(['rspack', 'serve', '--config', configFile, ...params]); + const newClientProcess = spawnProcess( + command, + args, { + cwd: appDir, + env: { ...process.env, ...envs }, + onStdout: (data) => { + logInfo(`[Rspack Client] ${data}`); + if (onCompile && data.trim().includes("compiled")) { + onCompile(data); + } + }, + onStderr: (data) => { + // Check if this is an EADDRINUSE error in development mode (which we want to completely ignore) + if (isMeteorAppDevelopment() && data.includes('EADDRINUSE')) { + logError(`[Rspack Client Error] ${data}`); + return; + } + // Check if this is actually an informational message (like webpack-dev-server messages) + if (data.includes('Loopback:') || data.includes('Project is running at:')) { + logInfo(`[Rspack Client] ${data}`); + } else { + // Check if this is the "npm error could not determine executable to run" error + if (data.includes('npm error could not determine executable to run')) { + const errorMsg = '[Rspack Client Error] Try running "meteor npm install" to ensure rspack is available'; + logError(errorMsg); + throw new Error(errorMsg); + } + logError(`[Rspack Client Error] ${data}`); + } + }, + onError: (err) => { + const errorMsg = `Rspack Error: ${err.message}`; + logError(errorMsg); + throw new Error(errorMsg); + } + }); + + // Store the new process in global state + setGlobalState(GLOBAL_STATE_KEYS.CLIENT_PROCESS, newClientProcess); + + return newClientProcess; +} + +/** + * Starts Rspack for server in build --watch mode + * @param {Object} options - Options for server watch + * @param {Function} options.onCompile - Callback function to be called when compilation is complete + * @returns {Object} The server process object + */ +export function startRspackServerWatch(options = {}) { + const { onCompile } = options; + // Get the current server process from global state + const serverProcess = getGlobalState(GLOBAL_STATE_KEYS.SERVER_PROCESS, null); + + // Skip if server process is already running + if (serverProcess && isProcessRunning(serverProcess)) { + return serverProcess; + } + + const appDir = getMeteorAppDir(); + const configFile = getConfigFilePath(); + const { params, envs } = getRspackEnv({ isClient: false, isServer: true }); + const { command, args } = getNpxCommand(['rspack', 'build', '--watch', '--config', configFile, ...params]); + const newServerProcess = spawnProcess( + command, + args, { + cwd: appDir, + env: { ...process.env, ...envs }, + onStdout: (data) => { + logInfo(`[Rspack Server] ${data}`); + if (onCompile && data.trim().includes("compiled")) { + onCompile(data); + } + }, + onStderr: (data) => { + // Check if this is actually an informational message (like webpack-dev-server messages) + if (data.includes('Project is running at:')) { + logInfo(`[Rspack Server] ${data}`); + } else { + // Check if this is the "npm error could not determine executable to run" error + if (data.includes('npm error could not determine executable to run')) { + const errorMsg = '[Rspack Server Error] Try running "meteor npm install" to ensure rspack is available'; + logError(errorMsg); + throw new Error(errorMsg); + } + logError(`[Rspack Server Error] ${data}`); + } + }, + onError: (err) => { + const errorMsg = `Rspack Error: ${err.message}`; + logError(errorMsg); + throw new Error(errorMsg); + } + }); + + // Store the new process in global state + setGlobalState(GLOBAL_STATE_KEYS.SERVER_PROCESS, newServerProcess); + + return newServerProcess; +} + +/** + * Runs Rspack build for both client and server without watch mode + * @param {Object} options - Options for the build + * @param {boolean} options.isClient - Whether this is a client build + * @param {boolean} options.isServer - Whether this is a server build + * @param {boolean} options.isTestModule - Whether this is a test module + * @param {Function} options.onCompile - Callback function to be called when compilation is complete + * @param {boolean} options.watch - Whether to run Rspack in watch mode + * @returns {Promise} A promise that resolves when the build is complete + * @throws {Error} If the build process fails + */ +export async function runRspackBuild({ isClient, isServer, isTest, isTestModule, isTestLike, onCompile, watch, label = 'Build' } = {}) { + const appDir = getMeteorAppDir(); + const configFile = getConfigFilePath(); + + const endpoint = isClient ? 'Client' : 'Server'; + // Use a promise to ensure Meteor waits until Rspack finishes + return new Promise((resolve, reject) => { + const { params, envs } = getRspackEnv({ isClient, isServer, isTest, isTestModule, isTestLike }); + const rspackArgs = [ + 'rspack', + 'build', + '--config', + configFile, + ...(watch && ['--watch']) || [], + ...params, + ].filter(Boolean); + const { command, args } = getNpxCommand(rspackArgs); + spawnProcess( + command, + args, + { + cwd: appDir, + env: { ...process.env, ...envs }, + onStdout: (data) => { + logInfo(`[Rspack ${label} ${endpoint}] ${data}`); + if (onCompile && data.trim().includes("compiled")) { + onCompile(data); + } + }, + onStderr: (data) => { + // Check if this is actually an informational message (like webpack-dev-server messages) + if (data.includes('Project is running at:')) { + logInfo(`[Rspack ${label} ${endpoint}] ${data}`); + } else { + // Check if this is the "npm error could not determine executable to run" error + if (data.includes('npm error could not determine executable to run')) { + const errorMsg = `[Rspack ${label} Error ${endpoint}] Try running "meteor npm install" to ensure rspack is available`; + logError(errorMsg); + throw new Error(errorMsg); + } + logError(`[Rspack ${label} Error ${endpoint}] ${data}`); + } + }, + onExit: (code) => { + if (code === 0) { + resolve(); + } else { + const error = new Error(`Rspack ${label} failed in ${endpoint} with exit code ${code}`); + logError(error.message); + reject(error); + } + }, + onError: (err) => { + logError(`Rspack ${label} ${endpoint} error: ${err.message}`); + reject(err); + } + }); + }); +} + +/** + * Cleans up processes when the plugin is stopped + * Stops any running client and server processes and clears their global state + * @returns {void} + */ +export function cleanup() { + const clientProcess = getGlobalState(GLOBAL_STATE_KEYS.CLIENT_PROCESS, null); + if (clientProcess) { + stopProcess(clientProcess); + setGlobalState(GLOBAL_STATE_KEYS.CLIENT_PROCESS, null); + } + + const serverProcess = getGlobalState(GLOBAL_STATE_KEYS.SERVER_PROCESS, null); + if (serverProcess) { + stopProcess(serverProcess); + setGlobalState(GLOBAL_STATE_KEYS.SERVER_PROCESS, null); + } +} diff --git a/packages/rspack/package.js b/packages/rspack/package.js new file mode 100644 index 0000000000..5be0e1bdca --- /dev/null +++ b/packages/rspack/package.js @@ -0,0 +1,33 @@ +Package.describe({ + summary: "Integrate rspack into the Meteor lifecycle to run the bundler independently", + version: '1.0.0', +}); + +Package.registerBuildPlugin({ + name: 'rspack', + sources: [ + 'lib/constants.js', + 'lib/dependencies.js', + 'lib/build-context.js', + 'lib/processes.js', + 'lib/config.js', + 'rspack_plugin.js', + ], + use: ['modules@0.8.2', 'ecmascript', 'tools-core'], +}); + +Npm.devDepends({ + 'http-proxy-middleware': '3.0.5', +}); + +Package.onUse(function (api) { + api.use('ecmascript', ['client', 'server']); + api.use(['tools-core', 'webapp']); + + api.mainModule('rspack_server.js', 'server'); +}); + +Package.onTest(function (api) { + api.use(['tinytest', 'ecmascript', 'rspack']); + api.addFiles(['rspack_tests.js']); +}); diff --git a/packages/rspack/rspack_plugin.js b/packages/rspack/rspack_plugin.js new file mode 100644 index 0000000000..e852a22a6c --- /dev/null +++ b/packages/rspack/rspack_plugin.js @@ -0,0 +1,347 @@ +/** + * @module rspack_plugin + * @description Rspack Plugin for Meteor + * + * This is the main entry point for the Rspack plugin. It orchestrates the integration + * between Rspack and Meteor by: + * 1. Ensuring Rspack and related dependencies are installed + * 2. Setting up the build context directory + * 3. Configuring Meteor settings for Rspack + * 4. Starting Rspack processes based on the Meteor command (run or build) + * 5. Handling cleanup when the plugin is stopped + * + * The plugin uses top-level await to ensure asynchronous operations complete + * before Meteor continues execution. + */ + +// Import modules from lib +const { + GLOBAL_STATE_KEYS, +} = require('./lib/constants'); + +const { + ensureRspackInstalled, + checkReactInstalled, + checkAngularInstalled, + checkTypescriptInstalled, + ensureRspackReactInstalled, +} = require('./lib/dependencies'); + +const { + ensureRspackBuildContextExists, + ensureRspackConfigExists, + cleanBuildContextFiles, +} = require('./lib/build-context'); + +const { + startRspackClientServe, + startRspackServerWatch, + runRspackBuild, + cleanup, + calculateDevServerPort, + calculateRsdoctorClientPort, + calculateRsdoctorServerPort, + getConfigFilePath, + getCustomConfigFilePath, +} = require('./lib/processes'); + +const { + configureMeteorForRspack +} = require('./lib/config'); + +const { + setupCompilationTracking, + waitForFirstCompilation, +} = require('./lib/compilation'); + +const { + getGlobalState, + setGlobalState +} = require('meteor/tools-core/lib/global-state'); + +const { + isMeteorAppRun, + isMeteorAppBuild, + isMeteorAppUpdate, + getMeteorInitialAppEntrypoints, + getMeteorAppEntrypoints, + isMeteorAppTest, + isMeteorAppTestWatch, + isMeteorAppTestFullApp, + isMeteorAppDevelopment, + isMeteorAppProduction, + isMeteorAppDebug, + isMeteorAppConfigModernVerbose, + isMeteorAppNative, + isMeteorBundleVisualizerProject, +} = require('meteor/tools-core/lib/meteor'); + +const { + logInfo, + logError, +} = require('meteor/tools-core/lib/log'); + +const { + getNpxCommand, + getNpmCommand, + getYarnCommand, + isYarnProject, +} = require('meteor/tools-core/lib/npm'); +const { hasMeteorAppConfigAutoInstallDeps } = require("../tools-core/lib/meteor"); + +if (isMeteorAppRun() || isMeteorAppBuild() || isMeteorAppTest() || isMeteorAppUpdate()) { + // Get entry points from Meteor configuration + const initialEntrypoints = getMeteorInitialAppEntrypoints(); + + // Check if mainClient and mainServer exist + if (!initialEntrypoints.mainClient || !initialEntrypoints.mainServer) { + logError(`\n┌─────────────────────────────────────────────────`); + logError(`│ ❌ Missing Required Entry Points`); + logError(`└─────────────────────────────────────────────────`); + logError(`Your project is missing the required entry points for Rspack.`); + logError(`Please add the following to your package.json file:`); + logError(` +{ + "meteor": { + "mainModule": { + "client": "client/main.js", + "server": "server/main.js" + } + } +} +`); + logError(`Make sure to replace the paths with your actual entry point files.`); + + throw new Error( + "Missing required entry points. Please add meteor.mainModule.client and meteor.mainModule.server in your package.json file." + ); + } + + setGlobalState(GLOBAL_STATE_KEYS.INITIAL_ENTRYPONTS, getMeteorAppEntrypoints()); + + let isYarnProj = process.env.YARN_ENABLED === 'true'; + // Main entry point - using top-level await + try { + // Check if the project is a Yarn project and store the result in environment variable if not already set + if (process.env.YARN_ENABLED === undefined) { + isYarnProj = isYarnProject(); + process.env.YARN_ENABLED = isYarnProj ? 'true' : 'false'; + } + if (isMeteorAppDebug() || isMeteorAppConfigModernVerbose()) { + logInfo(`[i] Meteor Npx prefix: ${getNpxCommand([])?.prefix}`); + logInfo(`[i] Meteor Npm prefix: ${getNpmCommand([])?.prefix}`); + if (isYarnProj) { + logInfo(`[i] Meteor Yarn prefix: ${getYarnCommand([])?.prefix}`); + } + } + + // Clean build context files only if they haven't been cleaned yet + if (!getGlobalState(GLOBAL_STATE_KEYS.BUILD_CONTEXT_FILES_CLEANED)) { + cleanBuildContextFiles(); + setGlobalState(GLOBAL_STATE_KEYS.BUILD_CONTEXT_FILES_CLEANED, true); + } + + // Auto install deps (by default enabled) + if (hasMeteorAppConfigAutoInstallDeps()) { + // Ensure Rspack is installed + await ensureRspackInstalled(); + } + + // Check if Rspack React is installed + if (checkReactInstalled()) { + // Auto install deps (by default enabled) + if (hasMeteorAppConfigAutoInstallDeps()) { + await ensureRspackReactInstalled(); + } + } + } catch (error) { + logError(`Rspack plugin error: ${error.message}`); + throw error; + } +} + +if (isMeteorAppRun() || isMeteorAppBuild() || isMeteorAppTest()) { + try { + // Check if Angular is installed + checkAngularInstalled(); + + // Check if TypeScript is installed + checkTypescriptInstalled(); + + // Ensure the Rspack build context directory exists + ensureRspackBuildContextExists(); + + // Ensure the rspack.config.js file exists at the project level + ensureRspackConfigExists(); + + // Configure Meteor settings for Rspack + configureMeteorForRspack(); + + // Calculate and set the devServerPort at boot + if (!process.env.RSPACK_DEVSERVER_PORT) { + process.env.RSPACK_DEVSERVER_PORT = calculateDevServerPort(); + if (isMeteorAppDebug() || isMeteorAppConfigModernVerbose()) { + logInfo(`[i] Rspack DevServer Port: ${process.env.RSPACK_DEVSERVER_PORT}`); + } + } + + if (isMeteorAppDebug() || isMeteorAppConfigModernVerbose()) { + const configFile = getConfigFilePath(); + logInfo(`[i] Rspack default config: ${configFile}`); + const projectConfigFile = getCustomConfigFilePath(); + logInfo(`[i] Rspack custom config: ${projectConfigFile}`); + } + + // Calculate and set the Rsdoctor client and server ports at boot only if bundle visualizer is enabled + if (isMeteorBundleVisualizerProject()) { + if (!process.env.RSDOCTOR_CLIENT_PORT) { + process.env.RSDOCTOR_CLIENT_PORT = calculateRsdoctorClientPort(); + if (isMeteorAppDebug() || isMeteorAppConfigModernVerbose()) { + logInfo(`[i] Rsdoctor Client Port: ${process.env.RSDOCTOR_CLIENT_PORT}`); + } + } + + if (!process.env.RSDOCTOR_SERVER_PORT) { + process.env.RSDOCTOR_SERVER_PORT = calculateRsdoctorServerPort(); + if (isMeteorAppDebug() || isMeteorAppConfigModernVerbose()) { + logInfo(`[i] Rsdoctor Server Port: ${process.env.RSDOCTOR_SERVER_PORT}`); + } + } + } + + // Register cleanup handler + process.on('exit', cleanup); + process.on('SIGINT', () => { + cleanup(); + process.exit(); + }); + + // When running `meteor run` command + if (isMeteorAppRun()) { + // Setup compilation tracking and callbacks + const { + clientFirstCompile, + serverFirstCompile, + clientFirstCompilePromise, + serverFirstCompilePromise, + onCompileClient, + onCompileServer, + } = setupCompilationTracking(); + + // For 'run' command, start Rspack in appropriate modes with distinct callbacks + if (isMeteorAppDevelopment() && !isMeteorAppNative()) { + startRspackClientServe({ onCompile: onCompileClient }); + startRspackServerWatch({ onCompile: onCompileServer }); + } else if (isMeteorAppProduction() || isMeteorAppNative()) { + runRspackBuild({ + isClient: true, + isServer: false, + watch: true, + onCompile: onCompileClient, + }); + runRspackBuild({ + isServer: true, + isClient: false, + watch: true, + onCompile: onCompileServer, + }); + } + + // Wait for first compilation to complete + await waitForFirstCompilation(clientFirstCompile, serverFirstCompile, clientFirstCompilePromise, serverFirstCompilePromise); + + // When running `meteor test` command + } else if (isMeteorAppTest()) { + const initialEntrypoints = getMeteorInitialAppEntrypoints(); + + // Setup compilation tracking and callbacks + const { + clientFirstCompile, + serverFirstCompile, + clientFirstCompilePromise, + serverFirstCompilePromise, + onCompileClient, + onCompileServer, + } = setupCompilationTracking(); + + // When run test for full app, run Rspack app server as well + // isTestLike ensures the app runtime environment inherit test envs + if (isMeteorAppTestFullApp()) { + await runRspackBuild({ + isTest: false, + isTestLike: true, + isServer: true, + isClient: false, + }); + + if (isMeteorAppTestWatch()) { + runRspackBuild({ + isServer: true, + isClient: false, + isTest: false, + isTestLike: true, + watch: true, + }); + } + } + + // When testModule is specified for client or server, run Rspack considering those files + if (initialEntrypoints?.testClient || initialEntrypoints?.testServer) { + runRspackBuild({ + isTest: true, + isClient: true, + isServer: false, + watch: isMeteorAppTestWatch(), + onCompile: onCompileClient, + label: 'Test', + }); + + runRspackBuild({ + isTest: true, + isClient: false, + isServer: true, + watch: isMeteorAppTestWatch(), + onCompile: onCompileServer, + label: 'Test', + }); + + // Wait for first compilation to complete + await waitForFirstCompilation(clientFirstCompile, serverFirstCompile, clientFirstCompilePromise, serverFirstCompilePromise); + + // When testModule is specified as a single file or not specified + } else { + runRspackBuild({ + isTest: true, + isTestModule: true, + isClient: true, + isServer: false, + watch: isMeteorAppTestWatch(), + onCompile: onCompileClient, + label: 'Test', + }); + runRspackBuild({ + isTest: true, + isTestModule: true, + isClient: false, + isServer: true, + watch: isMeteorAppTestWatch(), + onCompile: onCompileServer, + label: 'Test', + }); + await waitForFirstCompilation(clientFirstCompile, serverFirstCompile, clientFirstCompilePromise, serverFirstCompilePromise, { target: 'server' }); + } + + // When running `meteor build` command + } else if (isMeteorAppBuild()) { + // For 'build' command, run Rspack build without watch mode + // Run client and server builds in parallel and wait for both to complete + await Promise.all([ + runRspackBuild({ isClient: true, isServer: false }), + runRspackBuild({ isServer: true, isClient: false }), + ]); + } + } catch (error) { + logError(`Rspack plugin error: ${error.message}`); + throw error; + } +} diff --git a/packages/rspack/rspack_server.js b/packages/rspack/rspack_server.js new file mode 100644 index 0000000000..53598b8de5 --- /dev/null +++ b/packages/rspack/rspack_server.js @@ -0,0 +1,204 @@ +import { Meteor } from 'meteor/meteor'; +import { WebApp, WebAppInternals } from 'meteor/webapp'; +import path from 'path'; +import { parse as parseUrl } from 'url'; +import { + RSPACK_CHUNKS_CONTEXT, + RSPACK_ASSETS_CONTEXT, + RSPACK_HOT_UPDATE_REGEX, +} from "./lib/constants"; + +// Define constants for both development and production +const rspackChunksContext = process.env.RSPACK_CHUNKS_CONTEXT || RSPACK_CHUNKS_CONTEXT; +const rspackAssetsContext = process.env.RSPACK_ASSETS_CONTEXT || RSPACK_ASSETS_CONTEXT; + +/** + * Regex pattern for rspack bundles + * @constant {RegExp} + */ +const RSPACK_CHUNKS_REGEX = new RegExp( + `^\/${rspackChunksContext}\/(.+)$`, +); + +/** + * Regex pattern for rspack assets + * @constant {RegExp} + */ +const RSPACK_ASSETS_REGEX = new RegExp( + `^\/${rspackAssetsContext}\/(.+)$`, +); + +if (Meteor.isDevelopment) { + const { shuffleString } = require('meteor/tools-core/lib/string'); + const { createProxyMiddleware } = require('http-proxy-middleware'); + + // Target URL for the Rspack dev server + const target = `http://localhost:${process.env.RSPACK_DEVSERVER_PORT}`; + + // Proxy HMR websocket upgrade requests + WebApp.connectHandlers.use('/ws', + createProxyMiddleware( { + target, + ws: true, + logLevel: 'debug' + }) + ); + + // Proxy all dev asset requests under the rspack prefix + WebApp.connectHandlers.use('/__rspack__', + createProxyMiddleware({ + target, + changeOrigin: true, + ws: true, + logLevel: 'debug', + }) + ); + + WebApp.rawConnectHandlers.use((req, res, next) => { + // If this request is already under /__rspack__/, don't redirect it again. + if (req.url.startsWith('/__rspack__/')) { + return next(); + } + + // 1) match ANY URL whose last segment ends with ".hot-update.js" or ".hot-update.json", + // e.g. "/main.ce385971e9f19307.hot-update.js" + // "/ui_pages_tasks_tasks-page_jsx.ce385971e9f19307.hot-update.js" + // "/foo/bar/baz.1234abcd.hot-update.json" + const hotUpdate = req.url.match(RSPACK_HOT_UPDATE_REGEX); + if (hotUpdate) { + // Redirect "/something.hot-update.js" → "/__rspack__/something.hot-update.js" + const target = `/__rspack__/${hotUpdate[1]}`; + res.writeHead(307, { Location: target }); + return res.end(); + } + + // 2) match "/build-chunks/" + const bundlesMatch = req.url.match(RSPACK_CHUNKS_REGEX); + if (bundlesMatch) { + // Redirect "/bundles/foo.js" → "/__rspack__/build-chunks/foo.js" + const target = `/__rspack__/${rspackChunksContext}/${bundlesMatch[1]}`; + res.writeHead(307, { Location: target }); + return res.end(); + } + + // 3) match "/build-assets/" + const assetsMatch = req.url.match(RSPACK_ASSETS_REGEX); + if (assetsMatch) { + // Redirect "/build-assets/foo.js" → "/__rspack__/build-assets/foo.js" + const target = `/__rspack__/${rspackAssetsContext}/${assetsMatch[1]}`; + res.writeHead(307, { Location: target }); + return res.end(); + } + + // Otherwise, let it pass through + next(); + }); + + /** + * Force client to reload after Rspack server compilation and restart, which doesn’t happen automatically. + * On each server reload, generate a new client hash once to force Meteor’s client reload. + * After the first reload, apply Meteor's default behavior. + */ + function enableClientReloadOnServerStart() { + Meteor.startup(() => { + const originalCalc = WebApp.calculateClientHashReplaceable; + let hasShuffled = false; + let cachedHash = {}; + let prevRealHash = {}; + WebApp.calculateClientHashReplaceable = function (...args) { + const arch = args[0]; + const realHash = originalCalc.apply(this, args); + if (prevRealHash[arch] && realHash !== prevRealHash[arch]) { + prevRealHash[arch] = realHash; + return realHash; + } + prevRealHash[arch] = realHash; + if (cachedHash[arch] == null) { + cachedHash[arch] = shuffleString(realHash); + hasShuffled = true; + } + return cachedHash[arch]; + }; + }); + } + + // Enable client reload on server startup + enableClientReloadOnServerStart(); +} + +/** + * Register a single rspack static asset with WebAppInternals.staticFilesByArch + * @param {string} arch - The architecture to register the asset for + * @param {string} pathname - The pathname of the asset + * @param {string} filePath - The absolute path to the asset on disk + * @returns {Object} The static file info object + */ +function registerRspackStaticAsset(arch, pathname, filePath) { + // Ensure the architecture exists in staticFilesByArch + if (!WebAppInternals.staticFilesByArch[arch]) { + WebAppInternals.staticFilesByArch[arch] = Object.create(null); + } + + // Get the static files object for this architecture + const staticFiles = WebAppInternals.staticFilesByArch[arch]; + + // Skip if already registered + if (staticFiles[pathname]) { + // Ensure the entry is marked as cacheable + staticFiles[pathname].cacheable = true; + return staticFiles[pathname]; + } + + // Determine file type based on extension + const type = pathname.endsWith(".js") ? "js" : + pathname.endsWith(".css") ? "css" : + pathname.endsWith(".json") ? "json" : undefined; + + // Extract hash from filename (assuming it's the second part after splitting by '.') + const filename = pathname.split("/").pop(); + const hash = filename.split(".")[1]; + + // Register the asset + staticFiles[pathname] = { + absolutePath: filePath, + cacheable: true, // Most rspack assets are cacheable + hash, + type + }; + + return staticFiles[pathname]; +} + +// Store the original staticFilesMiddleware +const originalStaticFilesMiddleware = WebAppInternals.staticFilesMiddleware; + +// Handle rspack assets on-demand to add Meteor's static files headers +WebAppInternals.staticFilesMiddleware = async function(staticFilesByArch, req, res, next) { + const pathname = parseUrl(req.url).pathname; + + try { + // Check if this is a rspack asset request + const chunksMatch = pathname.match(RSPACK_CHUNKS_REGEX); + const assetsMatch = pathname.match(RSPACK_ASSETS_REGEX); + + if (chunksMatch || assetsMatch) { + const cwd = process.cwd(); + const architectures = ["web.browser", "web.browser.legacy", "web.cordova"]; + WebApp.categorizeRequest(req); + + // Try to find the file on disk + const context = chunksMatch ? rspackChunksContext : rspackAssetsContext; + const filename = (chunksMatch ? chunksMatch[1] : assetsMatch[1]); + const filePath = path.join(cwd, context, filename); + + architectures.forEach(archName => { + registerRspackStaticAsset(archName, pathname, filePath); + }); + } + } catch (e) { + console.error(`Error handling rspack asset: ${e.message}`); + } + + // Call the original middleware + return originalStaticFilesMiddleware(staticFilesByArch, req, res, next); +}; diff --git a/packages/rspack/rspack_tests.js b/packages/rspack/rspack_tests.js new file mode 100644 index 0000000000..6dc6f52a00 --- /dev/null +++ b/packages/rspack/rspack_tests.js @@ -0,0 +1 @@ +Tinytest.add('rspack', () => {}); diff --git a/packages/shell-server/package.js b/packages/shell-server/package.js index 1dd7551714..885e168309 100644 --- a/packages/shell-server/package.js +++ b/packages/shell-server/package.js @@ -1,8 +1,9 @@ Package.describe({ name: "shell-server", - version: '0.6.2', + version: '0.7.0', summary: "Server-side component of the `meteor shell` command.", - documentation: "README.md" + documentation: "README.md", + devOnly: true, }); Package.onUse(function(api) { diff --git a/packages/standard-minifier-css/package.js b/packages/standard-minifier-css/package.js index fbc6531d43..353a747f45 100644 --- a/packages/standard-minifier-css/package.js +++ b/packages/standard-minifier-css/package.js @@ -1,8 +1,9 @@ Package.describe({ name: 'standard-minifier-css', - version: '1.9.3', + version: '1.10.0', summary: 'Standard css minifier used with Meteor apps by default.', documentation: 'README.md', + devOnly: true, }); Package.registerBuildPlugin({ diff --git a/packages/standard-minifier-js/package.js b/packages/standard-minifier-js/package.js index 6f23b6e939..eccea4f462 100644 --- a/packages/standard-minifier-js/package.js +++ b/packages/standard-minifier-js/package.js @@ -1,8 +1,9 @@ Package.describe({ name: 'standard-minifier-js', - version: '3.1.1', + version: '3.2.0', summary: 'Standard javascript minifiers used with Meteor apps by default.', documentation: 'README.md', + devOnly: true, }); Package.registerBuildPlugin({ @@ -12,7 +13,7 @@ Package.registerBuildPlugin({ 'ecmascript' ], npmDependencies: { - '@meteorjs/swc-core': '1.12.14', + '@meteorjs/swc-core': '1.15.3', 'acorn': '8.10.0', "@babel/runtime": "7.18.9", '@babel/parser': '7.22.7', diff --git a/packages/standard-minifier-js/plugin/minify-js.js b/packages/standard-minifier-js/plugin/minify-js.js index 0fe5ca613f..58bac3f119 100644 --- a/packages/standard-minifier-js/plugin/minify-js.js +++ b/packages/standard-minifier-js/plugin/minify-js.js @@ -49,6 +49,7 @@ export class MeteorMinifier { const NODE_ENV = process.env.NODE_ENV || 'development'; let content = file.getContentsAsString(); + const isLegacyWebArch = file?._arch === 'web.browser.legacy'; return swc.minifySync( content, @@ -60,6 +61,7 @@ export class MeteorMinifier { unused: true, dead_code: true, typeofs: false, + ...(isLegacyWebArch && { defaults: false }), global_defs: { 'process.env.NODE_ENV': NODE_ENV, diff --git a/packages/standard-minifiers/package.js b/packages/standard-minifiers/package.js index 1d533f9a61..acdd37f3c4 100644 --- a/packages/standard-minifiers/package.js +++ b/packages/standard-minifiers/package.js @@ -1,8 +1,9 @@ Package.describe({ name: 'standard-minifiers', - version: '1.1.1', + version: '1.2.0', summary: 'Standard minifiers used with Meteor apps by default.', - documentation: 'README.md' + documentation: 'README.md', + devOnly: true, }); Package.onUse(function(api) { diff --git a/packages/static-html/package.js b/packages/static-html/package.js index ff22f3efa2..8b990e8958 100644 --- a/packages/static-html/package.js +++ b/packages/static-html/package.js @@ -1,8 +1,9 @@ Package.describe({ name: 'static-html', summary: "Define static page content in .html files", - version: '1.4.0', - git: 'https://github.com/meteor/meteor.git' + version: '1.5.0', + git: 'https://github.com/meteor/meteor.git', + devOnly: true, }); Package.registerBuildPlugin({ diff --git a/packages/test-in-browser/diff_match_patch_uncompressed.js b/packages/test-in-browser/diff_match_patch_uncompressed.js deleted file mode 100644 index 4d5542c1d3..0000000000 --- a/packages/test-in-browser/diff_match_patch_uncompressed.js +++ /dev/null @@ -1,2218 +0,0 @@ -/** - * Diff Match and Patch - * Copyright 2018 The diff-match-patch Authors. - * https://github.com/google/diff-match-patch - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Computes the difference between two texts to create a patch. - * Applies the patch onto another text, allowing for errors. - * @author fraser@google.com (Neil Fraser) - */ - -/** - * Class containing the diff, match and patch methods. - * @constructor - */ -var diff_match_patch = function() { - - // Defaults. - // Redefine these in your program to override the defaults. - - // Number of seconds to map a diff before giving up (0 for infinity). - this.Diff_Timeout = 1.0; - // Cost of an empty edit operation in terms of edit characters. - this.Diff_EditCost = 4; - // At what point is no match declared (0.0 = perfection, 1.0 = very loose). - this.Match_Threshold = 0.5; - // How far to search for a match (0 = exact location, 1000+ = broad match). - // A match this many characters away from the expected location will add - // 1.0 to the score (0.0 is a perfect match). - this.Match_Distance = 1000; - // When deleting a large block of text (over ~64 characters), how close do - // the contents have to be to match the expected contents. (0.0 = perfection, - // 1.0 = very loose). Note that Match_Threshold controls how closely the - // end points of a delete need to match. - this.Patch_DeleteThreshold = 0.5; - // Chunk size for context length. - this.Patch_Margin = 4; - - // The number of bits in an int. - this.Match_MaxBits = 32; -}; - - -// DIFF FUNCTIONS - - -/** - * The data structure representing a diff is an array of tuples: - * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] - * which means: delete 'Hello', add 'Goodbye' and keep ' world.' - */ -var DIFF_DELETE = -1; -var DIFF_INSERT = 1; -var DIFF_EQUAL = 0; - -/** - * Class representing one diff tuple. - * ~Attempts to look like a two-element array (which is what this used to be).~ - * Constructor returns an actual two-element array, to allow destructing @JackuB - * See https://github.com/JackuB/diff-match-patch/issues/14 for details - * @param {number} op Operation, one of: DIFF_DELETE, DIFF_INSERT, DIFF_EQUAL. - * @param {string} text Text to be deleted, inserted, or retained. - * @constructor - */ -diff_match_patch.Diff = function(op, text) { - return [op, text]; -}; - -/** - * Find the differences between two texts. Simplifies the problem by stripping - * any common prefix or suffix off the texts before diffing. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean=} opt_checklines Optional speedup flag. If present and false, - * then don't run a line-level diff first to identify the changed areas. - * Defaults to true, which does a faster, slightly less optimal diff. - * @param {number=} opt_deadline Optional time when the diff should be complete - * by. Used internally for recursive calls. Users should set DiffTimeout - * instead. - * @return {!Array.} Array of diff tuples. - */ -diff_match_patch.prototype.diff_main = function(text1, text2, opt_checklines, - opt_deadline) { - // Set a deadline by which time the diff must be complete. - if (typeof opt_deadline == 'undefined') { - if (this.Diff_Timeout <= 0) { - opt_deadline = Number.MAX_VALUE; - } else { - opt_deadline = (new Date).getTime() + this.Diff_Timeout * 1000; - } - } - var deadline = opt_deadline; - - // Check for null inputs. - if (text1 == null || text2 == null) { - throw new Error('Null input. (diff_main)'); - } - - // Check for equality (speedup). - if (text1 == text2) { - if (text1) { - return [new diff_match_patch.Diff(DIFF_EQUAL, text1)]; - } - return []; - } - - if (typeof opt_checklines == 'undefined') { - opt_checklines = true; - } - var checklines = opt_checklines; - - // Trim off common prefix (speedup). - var commonlength = this.diff_commonPrefix(text1, text2); - var commonprefix = text1.substring(0, commonlength); - text1 = text1.substring(commonlength); - text2 = text2.substring(commonlength); - - // Trim off common suffix (speedup). - commonlength = this.diff_commonSuffix(text1, text2); - var commonsuffix = text1.substring(text1.length - commonlength); - text1 = text1.substring(0, text1.length - commonlength); - text2 = text2.substring(0, text2.length - commonlength); - - // Compute the diff on the middle block. - var diffs = this.diff_compute_(text1, text2, checklines, deadline); - - // Restore the prefix and suffix. - if (commonprefix) { - diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL, commonprefix)); - } - if (commonsuffix) { - diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, commonsuffix)); - } - this.diff_cleanupMerge(diffs); - return diffs; -}; - - -/** - * Find the differences between two texts. Assumes that the texts do not - * have any common prefix or suffix. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean} checklines Speedup flag. If false, then don't run a - * line-level diff first to identify the changed areas. - * If true, then run a faster, slightly less optimal diff. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ -diff_match_patch.prototype.diff_compute_ = function(text1, text2, checklines, - deadline) { - var diffs; - - if (!text1) { - // Just add some text (speedup). - return [new diff_match_patch.Diff(DIFF_INSERT, text2)]; - } - - if (!text2) { - // Just delete some text (speedup). - return [new diff_match_patch.Diff(DIFF_DELETE, text1)]; - } - - var longtext = text1.length > text2.length ? text1 : text2; - var shorttext = text1.length > text2.length ? text2 : text1; - var i = longtext.indexOf(shorttext); - if (i != -1) { - // Shorter text is inside the longer text (speedup). - diffs = [new diff_match_patch.Diff(DIFF_INSERT, longtext.substring(0, i)), - new diff_match_patch.Diff(DIFF_EQUAL, shorttext), - new diff_match_patch.Diff(DIFF_INSERT, - longtext.substring(i + shorttext.length))]; - // Swap insertions for deletions if diff is reversed. - if (text1.length > text2.length) { - diffs[0][0] = diffs[2][0] = DIFF_DELETE; - } - return diffs; - } - - if (shorttext.length == 1) { - // Single character string. - // After the previous speedup, the character can't be an equality. - return [new diff_match_patch.Diff(DIFF_DELETE, text1), - new diff_match_patch.Diff(DIFF_INSERT, text2)]; - } - - // Check to see if the problem can be split in two. - var hm = this.diff_halfMatch_(text1, text2); - if (hm) { - // A half-match was found, sort out the return data. - var text1_a = hm[0]; - var text1_b = hm[1]; - var text2_a = hm[2]; - var text2_b = hm[3]; - var mid_common = hm[4]; - // Send both pairs off for separate processing. - var diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline); - var diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline); - // Merge the results. - return diffs_a.concat([new diff_match_patch.Diff(DIFF_EQUAL, mid_common)], - diffs_b); - } - - if (checklines && text1.length > 100 && text2.length > 100) { - return this.diff_lineMode_(text1, text2, deadline); - } - - return this.diff_bisect_(text1, text2, deadline); -}; - - -/** - * Do a quick line-level diff on both strings, then rediff the parts for - * greater accuracy. - * This speedup can produce non-minimal diffs. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ -diff_match_patch.prototype.diff_lineMode_ = function(text1, text2, deadline) { - // Scan the text on a line-by-line basis first. - var a = this.diff_linesToChars_(text1, text2); - text1 = a.chars1; - text2 = a.chars2; - var linearray = a.lineArray; - - var diffs = this.diff_main(text1, text2, false, deadline); - - // Convert the diff back to original text. - this.diff_charsToLines_(diffs, linearray); - // Eliminate freak matches (e.g. blank lines) - this.diff_cleanupSemantic(diffs); - - // Rediff any replacement blocks, this time character-by-character. - // Add a dummy entry at the end. - diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, '')); - var pointer = 0; - var count_delete = 0; - var count_insert = 0; - var text_delete = ''; - var text_insert = ''; - while (pointer < diffs.length) { - switch (diffs[pointer][0]) { - case DIFF_INSERT: - count_insert++; - text_insert += diffs[pointer][1]; - break; - case DIFF_DELETE: - count_delete++; - text_delete += diffs[pointer][1]; - break; - case DIFF_EQUAL: - // Upon reaching an equality, check for prior redundancies. - if (count_delete >= 1 && count_insert >= 1) { - // Delete the offending records and add the merged ones. - diffs.splice(pointer - count_delete - count_insert, - count_delete + count_insert); - pointer = pointer - count_delete - count_insert; - var subDiff = - this.diff_main(text_delete, text_insert, false, deadline); - for (var j = subDiff.length - 1; j >= 0; j--) { - diffs.splice(pointer, 0, subDiff[j]); - } - pointer = pointer + subDiff.length; - } - count_insert = 0; - count_delete = 0; - text_delete = ''; - text_insert = ''; - break; - } - pointer++; - } - diffs.pop(); // Remove the dummy entry at the end. - - return diffs; -}; - - -/** - * Find the 'middle snake' of a diff, split the problem in two - * and return the recursively constructed diff. - * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ -diff_match_patch.prototype.diff_bisect_ = function(text1, text2, deadline) { - // Cache the text lengths to prevent multiple calls. - var text1_length = text1.length; - var text2_length = text2.length; - var max_d = Math.ceil((text1_length + text2_length) / 2); - var v_offset = max_d; - var v_length = 2 * max_d; - var v1 = new Array(v_length); - var v2 = new Array(v_length); - // Setting all elements to -1 is faster in Chrome & Firefox than mixing - // integers and undefined. - for (var x = 0; x < v_length; x++) { - v1[x] = -1; - v2[x] = -1; - } - v1[v_offset + 1] = 0; - v2[v_offset + 1] = 0; - var delta = text1_length - text2_length; - // If the total number of characters is odd, then the front path will collide - // with the reverse path. - var front = (delta % 2 != 0); - // Offsets for start and end of k loop. - // Prevents mapping of space beyond the grid. - var k1start = 0; - var k1end = 0; - var k2start = 0; - var k2end = 0; - for (var d = 0; d < max_d; d++) { - // Bail out if deadline is reached. - if ((new Date()).getTime() > deadline) { - break; - } - - // Walk the front path one step. - for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { - var k1_offset = v_offset + k1; - var x1; - if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { - x1 = v1[k1_offset + 1]; - } else { - x1 = v1[k1_offset - 1] + 1; - } - var y1 = x1 - k1; - while (x1 < text1_length && y1 < text2_length && - text1.charAt(x1) == text2.charAt(y1)) { - x1++; - y1++; - } - v1[k1_offset] = x1; - if (x1 > text1_length) { - // Ran off the right of the graph. - k1end += 2; - } else if (y1 > text2_length) { - // Ran off the bottom of the graph. - k1start += 2; - } else if (front) { - var k2_offset = v_offset + delta - k1; - if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { - // Mirror x2 onto top-left coordinate system. - var x2 = text1_length - v2[k2_offset]; - if (x1 >= x2) { - // Overlap detected. - return this.diff_bisectSplit_(text1, text2, x1, y1, deadline); - } - } - } - } - - // Walk the reverse path one step. - for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { - var k2_offset = v_offset + k2; - var x2; - if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { - x2 = v2[k2_offset + 1]; - } else { - x2 = v2[k2_offset - 1] + 1; - } - var y2 = x2 - k2; - while (x2 < text1_length && y2 < text2_length && - text1.charAt(text1_length - x2 - 1) == - text2.charAt(text2_length - y2 - 1)) { - x2++; - y2++; - } - v2[k2_offset] = x2; - if (x2 > text1_length) { - // Ran off the left of the graph. - k2end += 2; - } else if (y2 > text2_length) { - // Ran off the top of the graph. - k2start += 2; - } else if (!front) { - var k1_offset = v_offset + delta - k2; - if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { - var x1 = v1[k1_offset]; - var y1 = v_offset + x1 - k1_offset; - // Mirror x2 onto top-left coordinate system. - x2 = text1_length - x2; - if (x1 >= x2) { - // Overlap detected. - return this.diff_bisectSplit_(text1, text2, x1, y1, deadline); - } - } - } - } - } - // Diff took too long and hit the deadline or - // number of diffs equals number of characters, no commonality at all. - return [new diff_match_patch.Diff(DIFF_DELETE, text1), - new diff_match_patch.Diff(DIFF_INSERT, text2)]; -}; - - -/** - * Given the location of the 'middle snake', split the diff in two parts - * and recurse. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} x Index of split point in text1. - * @param {number} y Index of split point in text2. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ -diff_match_patch.prototype.diff_bisectSplit_ = function(text1, text2, x, y, - deadline) { - var text1a = text1.substring(0, x); - var text2a = text2.substring(0, y); - var text1b = text1.substring(x); - var text2b = text2.substring(y); - - // Compute both diffs serially. - var diffs = this.diff_main(text1a, text2a, false, deadline); - var diffsb = this.diff_main(text1b, text2b, false, deadline); - - return diffs.concat(diffsb); -}; - - -/** - * Split two texts into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {{chars1: string, chars2: string, lineArray: !Array.}} - * An object containing the encoded text1, the encoded text2 and - * the array of unique strings. - * The zeroth element of the array of unique strings is intentionally blank. - * @private - */ -diff_match_patch.prototype.diff_linesToChars_ = function(text1, text2) { - var lineArray = []; // e.g. lineArray[4] == 'Hello\n' - var lineHash = {}; // e.g. lineHash['Hello\n'] == 4 - - // '\x00' is a valid character, but various debuggers don't like it. - // So we'll insert a junk entry to avoid generating a null character. - lineArray[0] = ''; - - /** - * Split a text into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * Modifies linearray and linehash through being a closure. - * @param {string} text String to encode. - * @return {string} Encoded string. - * @private - */ - function diff_linesToCharsMunge_(text) { - var chars = ''; - // Walk the text, pulling out a substring for each line. - // text.split('\n') would would temporarily double our memory footprint. - // Modifying text would create many large strings to garbage collect. - var lineStart = 0; - var lineEnd = -1; - // Keeping our own length variable is faster than looking it up. - var lineArrayLength = lineArray.length; - while (lineEnd < text.length - 1) { - lineEnd = text.indexOf('\n', lineStart); - if (lineEnd == -1) { - lineEnd = text.length - 1; - } - var line = text.substring(lineStart, lineEnd + 1); - - if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : - (lineHash[line] !== undefined)) { - chars += String.fromCharCode(lineHash[line]); - } else { - if (lineArrayLength == maxLines) { - // Bail out at 65535 because - // String.fromCharCode(65536) == String.fromCharCode(0) - line = text.substring(lineStart); - lineEnd = text.length; - } - chars += String.fromCharCode(lineArrayLength); - lineHash[line] = lineArrayLength; - lineArray[lineArrayLength++] = line; - } - lineStart = lineEnd + 1; - } - return chars; - } - // Allocate 2/3rds of the space for text1, the rest for text2. - var maxLines = 40000; - var chars1 = diff_linesToCharsMunge_(text1); - maxLines = 65535; - var chars2 = diff_linesToCharsMunge_(text2); - return {chars1: chars1, chars2: chars2, lineArray: lineArray}; -}; - - -/** - * Rehydrate the text in a diff from a string of line hashes to real lines of - * text. - * @param {!Array.} diffs Array of diff tuples. - * @param {!Array.} lineArray Array of unique strings. - * @private - */ -diff_match_patch.prototype.diff_charsToLines_ = function(diffs, lineArray) { - for (var i = 0; i < diffs.length; i++) { - var chars = diffs[i][1]; - var text = []; - for (var j = 0; j < chars.length; j++) { - text[j] = lineArray[chars.charCodeAt(j)]; - } - diffs[i][1] = text.join(''); - } -}; - - -/** - * Determine the common prefix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the start of each - * string. - */ -diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) { - // Quick check for common null cases. - if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) { - return 0; - } - // Binary search. - // Performance analysis: https://neil.fraser.name/news/2007/10/09/ - var pointermin = 0; - var pointermax = Math.min(text1.length, text2.length); - var pointermid = pointermax; - var pointerstart = 0; - while (pointermin < pointermid) { - if (text1.substring(pointerstart, pointermid) == - text2.substring(pointerstart, pointermid)) { - pointermin = pointermid; - pointerstart = pointermin; - } else { - pointermax = pointermid; - } - pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); - } - return pointermid; -}; - - -/** - * Determine the common suffix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of each string. - */ -diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) { - // Quick check for common null cases. - if (!text1 || !text2 || - text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) { - return 0; - } - // Binary search. - // Performance analysis: https://neil.fraser.name/news/2007/10/09/ - var pointermin = 0; - var pointermax = Math.min(text1.length, text2.length); - var pointermid = pointermax; - var pointerend = 0; - while (pointermin < pointermid) { - if (text1.substring(text1.length - pointermid, text1.length - pointerend) == - text2.substring(text2.length - pointermid, text2.length - pointerend)) { - pointermin = pointermid; - pointerend = pointermin; - } else { - pointermax = pointermid; - } - pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); - } - return pointermid; -}; - - -/** - * Determine if the suffix of one string is the prefix of another. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of the first - * string and the start of the second string. - * @private - */ -diff_match_patch.prototype.diff_commonOverlap_ = function(text1, text2) { - // Cache the text lengths to prevent multiple calls. - var text1_length = text1.length; - var text2_length = text2.length; - // Eliminate the null case. - if (text1_length == 0 || text2_length == 0) { - return 0; - } - // Truncate the longer string. - if (text1_length > text2_length) { - text1 = text1.substring(text1_length - text2_length); - } else if (text1_length < text2_length) { - text2 = text2.substring(0, text1_length); - } - var text_length = Math.min(text1_length, text2_length); - // Quick check for the worst case. - if (text1 == text2) { - return text_length; - } - - // Start by looking for a single character match - // and increase length until no match is found. - // Performance analysis: https://neil.fraser.name/news/2010/11/04/ - var best = 0; - var length = 1; - while (true) { - var pattern = text1.substring(text_length - length); - var found = text2.indexOf(pattern); - if (found == -1) { - return best; - } - length += found; - if (found == 0 || text1.substring(text_length - length) == - text2.substring(0, length)) { - best = length; - length++; - } - } -}; - - -/** - * Do the two texts share a substring which is at least half the length of the - * longer text? - * This speedup can produce non-minimal diffs. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {Array.} Five element Array, containing the prefix of - * text1, the suffix of text1, the prefix of text2, the suffix of - * text2 and the common middle. Or null if there was no match. - * @private - */ -diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) { - if (this.Diff_Timeout <= 0) { - // Don't risk returning a non-optimal diff if we have unlimited time. - return null; - } - var longtext = text1.length > text2.length ? text1 : text2; - var shorttext = text1.length > text2.length ? text2 : text1; - if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { - return null; // Pointless. - } - var dmp = this; // 'this' becomes 'window' in a closure. - - /** - * Does a substring of shorttext exist within longtext such that the substring - * is at least half the length of longtext? - * Closure, but does not reference any external variables. - * @param {string} longtext Longer string. - * @param {string} shorttext Shorter string. - * @param {number} i Start index of quarter length substring within longtext. - * @return {Array.} Five element Array, containing the prefix of - * longtext, the suffix of longtext, the prefix of shorttext, the suffix - * of shorttext and the common middle. Or null if there was no match. - * @private - */ - function diff_halfMatchI_(longtext, shorttext, i) { - // Start with a 1/4 length substring at position i as a seed. - var seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); - var j = -1; - var best_common = ''; - var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b; - while ((j = shorttext.indexOf(seed, j + 1)) != -1) { - var prefixLength = dmp.diff_commonPrefix(longtext.substring(i), - shorttext.substring(j)); - var suffixLength = dmp.diff_commonSuffix(longtext.substring(0, i), - shorttext.substring(0, j)); - if (best_common.length < suffixLength + prefixLength) { - best_common = shorttext.substring(j - suffixLength, j) + - shorttext.substring(j, j + prefixLength); - best_longtext_a = longtext.substring(0, i - suffixLength); - best_longtext_b = longtext.substring(i + prefixLength); - best_shorttext_a = shorttext.substring(0, j - suffixLength); - best_shorttext_b = shorttext.substring(j + prefixLength); - } - } - if (best_common.length * 2 >= longtext.length) { - return [best_longtext_a, best_longtext_b, - best_shorttext_a, best_shorttext_b, best_common]; - } else { - return null; - } - } - - // First check if the second quarter is the seed for a half-match. - var hm1 = diff_halfMatchI_(longtext, shorttext, - Math.ceil(longtext.length / 4)); - // Check again based on the third quarter. - var hm2 = diff_halfMatchI_(longtext, shorttext, - Math.ceil(longtext.length / 2)); - var hm; - if (!hm1 && !hm2) { - return null; - } else if (!hm2) { - hm = hm1; - } else if (!hm1) { - hm = hm2; - } else { - // Both matched. Select the longest. - hm = hm1[4].length > hm2[4].length ? hm1 : hm2; - } - - // A half-match was found, sort out the return data. - var text1_a, text1_b, text2_a, text2_b; - if (text1.length > text2.length) { - text1_a = hm[0]; - text1_b = hm[1]; - text2_a = hm[2]; - text2_b = hm[3]; - } else { - text2_a = hm[0]; - text2_b = hm[1]; - text1_a = hm[2]; - text1_b = hm[3]; - } - var mid_common = hm[4]; - return [text1_a, text1_b, text2_a, text2_b, mid_common]; -}; - - -/** - * Reduce the number of edits by eliminating semantically trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ -diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { - var changes = false; - var equalities = []; // Stack of indices where equalities are found. - var equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - var lastEquality = null; - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - var pointer = 0; // Index of current position. - // Number of characters that changed prior to the equality. - var length_insertions1 = 0; - var length_deletions1 = 0; - // Number of characters that changed after the equality. - var length_insertions2 = 0; - var length_deletions2 = 0; - while (pointer < diffs.length) { - if (diffs[pointer][0] == DIFF_EQUAL) { // Equality found. - equalities[equalitiesLength++] = pointer; - length_insertions1 = length_insertions2; - length_deletions1 = length_deletions2; - length_insertions2 = 0; - length_deletions2 = 0; - lastEquality = diffs[pointer][1]; - } else { // An insertion or deletion. - if (diffs[pointer][0] == DIFF_INSERT) { - length_insertions2 += diffs[pointer][1].length; - } else { - length_deletions2 += diffs[pointer][1].length; - } - // Eliminate an equality that is smaller or equal to the edits on both - // sides of it. - if (lastEquality && (lastEquality.length <= - Math.max(length_insertions1, length_deletions1)) && - (lastEquality.length <= Math.max(length_insertions2, - length_deletions2))) { - // Duplicate record. - diffs.splice(equalities[equalitiesLength - 1], 0, - new diff_match_patch.Diff(DIFF_DELETE, lastEquality)); - // Change second copy to insert. - diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; - // Throw away the equality we just deleted. - equalitiesLength--; - // Throw away the previous equality (it needs to be reevaluated). - equalitiesLength--; - pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; - length_insertions1 = 0; // Reset the counters. - length_deletions1 = 0; - length_insertions2 = 0; - length_deletions2 = 0; - lastEquality = null; - changes = true; - } - } - pointer++; - } - - // Normalize the diff. - if (changes) { - this.diff_cleanupMerge(diffs); - } - this.diff_cleanupSemanticLossless(diffs); - - // Find any overlaps between deletions and insertions. - // e.g: abcxxxxxxdef - // -> abcxxxdef - // e.g: xxxabcdefxxx - // -> defxxxabc - // Only extract an overlap if it is as big as the edit ahead or behind it. - pointer = 1; - while (pointer < diffs.length) { - if (diffs[pointer - 1][0] == DIFF_DELETE && - diffs[pointer][0] == DIFF_INSERT) { - var deletion = diffs[pointer - 1][1]; - var insertion = diffs[pointer][1]; - var overlap_length1 = this.diff_commonOverlap_(deletion, insertion); - var overlap_length2 = this.diff_commonOverlap_(insertion, deletion); - if (overlap_length1 >= overlap_length2) { - if (overlap_length1 >= deletion.length / 2 || - overlap_length1 >= insertion.length / 2) { - // Overlap found. Insert an equality and trim the surrounding edits. - diffs.splice(pointer, 0, new diff_match_patch.Diff(DIFF_EQUAL, - insertion.substring(0, overlap_length1))); - diffs[pointer - 1][1] = - deletion.substring(0, deletion.length - overlap_length1); - diffs[pointer + 1][1] = insertion.substring(overlap_length1); - pointer++; - } - } else { - if (overlap_length2 >= deletion.length / 2 || - overlap_length2 >= insertion.length / 2) { - // Reverse overlap found. - // Insert an equality and swap and trim the surrounding edits. - diffs.splice(pointer, 0, new diff_match_patch.Diff(DIFF_EQUAL, - deletion.substring(0, overlap_length2))); - diffs[pointer - 1][0] = DIFF_INSERT; - diffs[pointer - 1][1] = - insertion.substring(0, insertion.length - overlap_length2); - diffs[pointer + 1][0] = DIFF_DELETE; - diffs[pointer + 1][1] = - deletion.substring(overlap_length2); - pointer++; - } - } - pointer++; - } - pointer++; - } -}; - - -/** - * Look for single edits surrounded on both sides by equalities - * which can be shifted sideways to align the edit to a word boundary. - * e.g: The cat came. -> The cat came. - * @param {!Array.} diffs Array of diff tuples. - */ -diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) { - /** - * Given two strings, compute a score representing whether the internal - * boundary falls on logical boundaries. - * Scores range from 6 (best) to 0 (worst). - * Closure, but does not reference any external variables. - * @param {string} one First string. - * @param {string} two Second string. - * @return {number} The score. - * @private - */ - function diff_cleanupSemanticScore_(one, two) { - if (!one || !two) { - // Edges are the best. - return 6; - } - - // Each port of this function behaves slightly differently due to - // subtle differences in each language's definition of things like - // 'whitespace'. Since this function's purpose is largely cosmetic, - // the choice has been made to use each language's native features - // rather than force total conformity. - var char1 = one.charAt(one.length - 1); - var char2 = two.charAt(0); - var nonAlphaNumeric1 = char1.match(diff_match_patch.nonAlphaNumericRegex_); - var nonAlphaNumeric2 = char2.match(diff_match_patch.nonAlphaNumericRegex_); - var whitespace1 = nonAlphaNumeric1 && - char1.match(diff_match_patch.whitespaceRegex_); - var whitespace2 = nonAlphaNumeric2 && - char2.match(diff_match_patch.whitespaceRegex_); - var lineBreak1 = whitespace1 && - char1.match(diff_match_patch.linebreakRegex_); - var lineBreak2 = whitespace2 && - char2.match(diff_match_patch.linebreakRegex_); - var blankLine1 = lineBreak1 && - one.match(diff_match_patch.blanklineEndRegex_); - var blankLine2 = lineBreak2 && - two.match(diff_match_patch.blanklineStartRegex_); - - if (blankLine1 || blankLine2) { - // Five points for blank lines. - return 5; - } else if (lineBreak1 || lineBreak2) { - // Four points for line breaks. - return 4; - } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { - // Three points for end of sentences. - return 3; - } else if (whitespace1 || whitespace2) { - // Two points for whitespace. - return 2; - } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { - // One point for non-alphanumeric. - return 1; - } - return 0; - } - - var pointer = 1; - // Intentionally ignore the first and last element (don't need checking). - while (pointer < diffs.length - 1) { - if (diffs[pointer - 1][0] == DIFF_EQUAL && - diffs[pointer + 1][0] == DIFF_EQUAL) { - // This is a single edit surrounded by equalities. - var equality1 = diffs[pointer - 1][1]; - var edit = diffs[pointer][1]; - var equality2 = diffs[pointer + 1][1]; - - // First, shift the edit as far left as possible. - var commonOffset = this.diff_commonSuffix(equality1, edit); - if (commonOffset) { - var commonString = edit.substring(edit.length - commonOffset); - equality1 = equality1.substring(0, equality1.length - commonOffset); - edit = commonString + edit.substring(0, edit.length - commonOffset); - equality2 = commonString + equality2; - } - - // Second, step character by character right, looking for the best fit. - var bestEquality1 = equality1; - var bestEdit = edit; - var bestEquality2 = equality2; - var bestScore = diff_cleanupSemanticScore_(equality1, edit) + - diff_cleanupSemanticScore_(edit, equality2); - while (edit.charAt(0) === equality2.charAt(0)) { - equality1 += edit.charAt(0); - edit = edit.substring(1) + equality2.charAt(0); - equality2 = equality2.substring(1); - var score = diff_cleanupSemanticScore_(equality1, edit) + - diff_cleanupSemanticScore_(edit, equality2); - // The >= encourages trailing rather than leading whitespace on edits. - if (score >= bestScore) { - bestScore = score; - bestEquality1 = equality1; - bestEdit = edit; - bestEquality2 = equality2; - } - } - - if (diffs[pointer - 1][1] != bestEquality1) { - // We have an improvement, save it back to the diff. - if (bestEquality1) { - diffs[pointer - 1][1] = bestEquality1; - } else { - diffs.splice(pointer - 1, 1); - pointer--; - } - diffs[pointer][1] = bestEdit; - if (bestEquality2) { - diffs[pointer + 1][1] = bestEquality2; - } else { - diffs.splice(pointer + 1, 1); - pointer--; - } - } - } - pointer++; - } -}; - -// Define some regex patterns for matching boundaries. -diff_match_patch.nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/; -diff_match_patch.whitespaceRegex_ = /\s/; -diff_match_patch.linebreakRegex_ = /[\r\n]/; -diff_match_patch.blanklineEndRegex_ = /\n\r?\n$/; -diff_match_patch.blanklineStartRegex_ = /^\r?\n\r?\n/; - -/** - * Reduce the number of edits by eliminating operationally trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ -diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) { - var changes = false; - var equalities = []; // Stack of indices where equalities are found. - var equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - var lastEquality = null; - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - var pointer = 0; // Index of current position. - // Is there an insertion operation before the last equality. - var pre_ins = false; - // Is there a deletion operation before the last equality. - var pre_del = false; - // Is there an insertion operation after the last equality. - var post_ins = false; - // Is there a deletion operation after the last equality. - var post_del = false; - while (pointer < diffs.length) { - if (diffs[pointer][0] == DIFF_EQUAL) { // Equality found. - if (diffs[pointer][1].length < this.Diff_EditCost && - (post_ins || post_del)) { - // Candidate found. - equalities[equalitiesLength++] = pointer; - pre_ins = post_ins; - pre_del = post_del; - lastEquality = diffs[pointer][1]; - } else { - // Not a candidate, and can never become one. - equalitiesLength = 0; - lastEquality = null; - } - post_ins = post_del = false; - } else { // An insertion or deletion. - if (diffs[pointer][0] == DIFF_DELETE) { - post_del = true; - } else { - post_ins = true; - } - /* - * Five types to be split: - * ABXYCD - * AXCD - * ABXC - * AXCD - * ABXC - */ - if (lastEquality && ((pre_ins && pre_del && post_ins && post_del) || - ((lastEquality.length < this.Diff_EditCost / 2) && - (pre_ins + pre_del + post_ins + post_del) == 3))) { - // Duplicate record. - diffs.splice(equalities[equalitiesLength - 1], 0, - new diff_match_patch.Diff(DIFF_DELETE, lastEquality)); - // Change second copy to insert. - diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; - equalitiesLength--; // Throw away the equality we just deleted; - lastEquality = null; - if (pre_ins && pre_del) { - // No changes made which could affect previous entry, keep going. - post_ins = post_del = true; - equalitiesLength = 0; - } else { - equalitiesLength--; // Throw away the previous equality. - pointer = equalitiesLength > 0 ? - equalities[equalitiesLength - 1] : -1; - post_ins = post_del = false; - } - changes = true; - } - } - pointer++; - } - - if (changes) { - this.diff_cleanupMerge(diffs); - } -}; - - -/** - * Reorder and merge like edit sections. Merge equalities. - * Any edit section can move as long as it doesn't cross an equality. - * @param {!Array.} diffs Array of diff tuples. - */ -diff_match_patch.prototype.diff_cleanupMerge = function(diffs) { - // Add a dummy entry at the end. - diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, '')); - var pointer = 0; - var count_delete = 0; - var count_insert = 0; - var text_delete = ''; - var text_insert = ''; - var commonlength; - while (pointer < diffs.length) { - switch (diffs[pointer][0]) { - case DIFF_INSERT: - count_insert++; - text_insert += diffs[pointer][1]; - pointer++; - break; - case DIFF_DELETE: - count_delete++; - text_delete += diffs[pointer][1]; - pointer++; - break; - case DIFF_EQUAL: - // Upon reaching an equality, check for prior redundancies. - if (count_delete + count_insert > 1) { - if (count_delete !== 0 && count_insert !== 0) { - // Factor out any common prefixies. - commonlength = this.diff_commonPrefix(text_insert, text_delete); - if (commonlength !== 0) { - if ((pointer - count_delete - count_insert) > 0 && - diffs[pointer - count_delete - count_insert - 1][0] == - DIFF_EQUAL) { - diffs[pointer - count_delete - count_insert - 1][1] += - text_insert.substring(0, commonlength); - } else { - diffs.splice(0, 0, new diff_match_patch.Diff(DIFF_EQUAL, - text_insert.substring(0, commonlength))); - pointer++; - } - text_insert = text_insert.substring(commonlength); - text_delete = text_delete.substring(commonlength); - } - // Factor out any common suffixies. - commonlength = this.diff_commonSuffix(text_insert, text_delete); - if (commonlength !== 0) { - diffs[pointer][1] = text_insert.substring(text_insert.length - - commonlength) + diffs[pointer][1]; - text_insert = text_insert.substring(0, text_insert.length - - commonlength); - text_delete = text_delete.substring(0, text_delete.length - - commonlength); - } - } - // Delete the offending records and add the merged ones. - pointer -= count_delete + count_insert; - diffs.splice(pointer, count_delete + count_insert); - if (text_delete.length) { - diffs.splice(pointer, 0, - new diff_match_patch.Diff(DIFF_DELETE, text_delete)); - pointer++; - } - if (text_insert.length) { - diffs.splice(pointer, 0, - new diff_match_patch.Diff(DIFF_INSERT, text_insert)); - pointer++; - } - pointer++; - } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) { - // Merge this equality with the previous one. - diffs[pointer - 1][1] += diffs[pointer][1]; - diffs.splice(pointer, 1); - } else { - pointer++; - } - count_insert = 0; - count_delete = 0; - text_delete = ''; - text_insert = ''; - break; - } - } - if (diffs[diffs.length - 1][1] === '') { - diffs.pop(); // Remove the dummy entry at the end. - } - - // Second pass: look for single edits surrounded on both sides by equalities - // which can be shifted sideways to eliminate an equality. - // e.g: ABAC -> ABAC - var changes = false; - pointer = 1; - // Intentionally ignore the first and last element (don't need checking). - while (pointer < diffs.length - 1) { - if (diffs[pointer - 1][0] == DIFF_EQUAL && - diffs[pointer + 1][0] == DIFF_EQUAL) { - // This is a single edit surrounded by equalities. - if (diffs[pointer][1].substring(diffs[pointer][1].length - - diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) { - // Shift the edit over the previous equality. - diffs[pointer][1] = diffs[pointer - 1][1] + - diffs[pointer][1].substring(0, diffs[pointer][1].length - - diffs[pointer - 1][1].length); - diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; - diffs.splice(pointer - 1, 1); - changes = true; - } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) == - diffs[pointer + 1][1]) { - // Shift the edit over the next equality. - diffs[pointer - 1][1] += diffs[pointer + 1][1]; - diffs[pointer][1] = - diffs[pointer][1].substring(diffs[pointer + 1][1].length) + - diffs[pointer + 1][1]; - diffs.splice(pointer + 1, 1); - changes = true; - } - } - pointer++; - } - // If shifts were made, the diff needs reordering and another shift sweep. - if (changes) { - this.diff_cleanupMerge(diffs); - } -}; - - -/** - * loc is a location in text1, compute and return the equivalent location in - * text2. - * e.g. 'The cat' vs 'The big cat', 1->1, 5->8 - * @param {!Array.} diffs Array of diff tuples. - * @param {number} loc Location within text1. - * @return {number} Location within text2. - */ -diff_match_patch.prototype.diff_xIndex = function(diffs, loc) { - var chars1 = 0; - var chars2 = 0; - var last_chars1 = 0; - var last_chars2 = 0; - var x; - for (x = 0; x < diffs.length; x++) { - if (diffs[x][0] !== DIFF_INSERT) { // Equality or deletion. - chars1 += diffs[x][1].length; - } - if (diffs[x][0] !== DIFF_DELETE) { // Equality or insertion. - chars2 += diffs[x][1].length; - } - if (chars1 > loc) { // Overshot the location. - break; - } - last_chars1 = chars1; - last_chars2 = chars2; - } - // Was the location was deleted? - if (diffs.length != x && diffs[x][0] === DIFF_DELETE) { - return last_chars2; - } - // Add the remaining character length. - return last_chars2 + (loc - last_chars1); -}; - - -/** - * Convert a diff array into a pretty HTML report. - * @param {!Array.} diffs Array of diff tuples. - * @return {string} HTML representation. - */ -diff_match_patch.prototype.diff_prettyHtml = function(diffs) { - var html = []; - var pattern_amp = /&/g; - var pattern_lt = //g; - var pattern_para = /\n/g; - for (var x = 0; x < diffs.length; x++) { - var op = diffs[x][0]; // Operation (insert, delete, equal) - var data = diffs[x][1]; // Text of change. - var text = data.replace(pattern_amp, '&').replace(pattern_lt, '<') - .replace(pattern_gt, '>').replace(pattern_para, '¶
'); - switch (op) { - case DIFF_INSERT: - html[x] = '' + text + ''; - break; - case DIFF_DELETE: - html[x] = '' + text + ''; - break; - case DIFF_EQUAL: - html[x] = '' + text + ''; - break; - } - } - return html.join(''); -}; - - -/** - * Compute and return the source text (all equalities and deletions). - * @param {!Array.} diffs Array of diff tuples. - * @return {string} Source text. - */ -diff_match_patch.prototype.diff_text1 = function(diffs) { - var text = []; - for (var x = 0; x < diffs.length; x++) { - if (diffs[x][0] !== DIFF_INSERT) { - text[x] = diffs[x][1]; - } - } - return text.join(''); -}; - - -/** - * Compute and return the destination text (all equalities and insertions). - * @param {!Array.} diffs Array of diff tuples. - * @return {string} Destination text. - */ -diff_match_patch.prototype.diff_text2 = function(diffs) { - var text = []; - for (var x = 0; x < diffs.length; x++) { - if (diffs[x][0] !== DIFF_DELETE) { - text[x] = diffs[x][1]; - } - } - return text.join(''); -}; - - -/** - * Compute the Levenshtein distance; the number of inserted, deleted or - * substituted characters. - * @param {!Array.} diffs Array of diff tuples. - * @return {number} Number of changes. - */ -diff_match_patch.prototype.diff_levenshtein = function(diffs) { - var levenshtein = 0; - var insertions = 0; - var deletions = 0; - for (var x = 0; x < diffs.length; x++) { - var op = diffs[x][0]; - var data = diffs[x][1]; - switch (op) { - case DIFF_INSERT: - insertions += data.length; - break; - case DIFF_DELETE: - deletions += data.length; - break; - case DIFF_EQUAL: - // A deletion and an insertion is one substitution. - levenshtein += Math.max(insertions, deletions); - insertions = 0; - deletions = 0; - break; - } - } - levenshtein += Math.max(insertions, deletions); - return levenshtein; -}; - - -/** - * Crush the diff into an encoded string which describes the operations - * required to transform text1 into text2. - * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. - * Operations are tab-separated. Inserted text is escaped using %xx notation. - * @param {!Array.} diffs Array of diff tuples. - * @return {string} Delta text. - */ -diff_match_patch.prototype.diff_toDelta = function(diffs) { - var text = []; - for (var x = 0; x < diffs.length; x++) { - switch (diffs[x][0]) { - case DIFF_INSERT: - text[x] = '+' + encodeURI(diffs[x][1]); - break; - case DIFF_DELETE: - text[x] = '-' + diffs[x][1].length; - break; - case DIFF_EQUAL: - text[x] = '=' + diffs[x][1].length; - break; - } - } - return text.join('\t').replace(/%20/g, ' '); -}; - - -/** - * Given the original text1, and an encoded string which describes the - * operations required to transform text1 into text2, compute the full diff. - * @param {string} text1 Source string for the diff. - * @param {string} delta Delta text. - * @return {!Array.} Array of diff tuples. - * @throws {!Error} If invalid input. - */ -diff_match_patch.prototype.diff_fromDelta = function(text1, delta) { - var diffs = []; - var diffsLength = 0; // Keeping our own length var is faster in JS. - var pointer = 0; // Cursor in text1 - var tokens = delta.split(/\t/g); - for (var x = 0; x < tokens.length; x++) { - // Each token begins with a one character parameter which specifies the - // operation of this token (delete, insert, equality). - var param = tokens[x].substring(1); - switch (tokens[x].charAt(0)) { - case '+': - try { - diffs[diffsLength++] = - new diff_match_patch.Diff(DIFF_INSERT, decodeURI(param)); - } catch (ex) { - // Malformed URI sequence. - throw new Error('Illegal escape in diff_fromDelta: ' + param); - } - break; - case '-': - // Fall through. - case '=': - var n = parseInt(param, 10); - if (isNaN(n) || n < 0) { - throw new Error('Invalid number in diff_fromDelta: ' + param); - } - var text = text1.substring(pointer, pointer += n); - if (tokens[x].charAt(0) == '=') { - diffs[diffsLength++] = new diff_match_patch.Diff(DIFF_EQUAL, text); - } else { - diffs[diffsLength++] = new diff_match_patch.Diff(DIFF_DELETE, text); - } - break; - default: - // Blank tokens are ok (from a trailing \t). - // Anything else is an error. - if (tokens[x]) { - throw new Error('Invalid diff operation in diff_fromDelta: ' + - tokens[x]); - } - } - } - if (pointer != text1.length) { - throw new Error('Delta length (' + pointer + - ') does not equal source text length (' + text1.length + ').'); - } - return diffs; -}; - - -// MATCH FUNCTIONS - - -/** - * Locate the best instance of 'pattern' in 'text' near 'loc'. - * @param {string} text The text to search. - * @param {string} pattern The pattern to search for. - * @param {number} loc The location to search around. - * @return {number} Best match index or -1. - */ -diff_match_patch.prototype.match_main = function(text, pattern, loc) { - // Check for null inputs. - if (text == null || pattern == null || loc == null) { - throw new Error('Null input. (match_main)'); - } - - loc = Math.max(0, Math.min(loc, text.length)); - if (text == pattern) { - // Shortcut (potentially not guaranteed by the algorithm) - return 0; - } else if (!text.length) { - // Nothing to match. - return -1; - } else if (text.substring(loc, loc + pattern.length) == pattern) { - // Perfect match at the perfect spot! (Includes case of null pattern) - return loc; - } else { - // Do a fuzzy compare. - return this.match_bitap_(text, pattern, loc); - } -}; - - -/** - * Locate the best instance of 'pattern' in 'text' near 'loc' using the - * Bitap algorithm. - * @param {string} text The text to search. - * @param {string} pattern The pattern to search for. - * @param {number} loc The location to search around. - * @return {number} Best match index or -1. - * @private - */ -diff_match_patch.prototype.match_bitap_ = function(text, pattern, loc) { - if (pattern.length > this.Match_MaxBits) { - throw new Error('Pattern too long for this browser.'); - } - - // Initialise the alphabet. - var s = this.match_alphabet_(pattern); - - var dmp = this; // 'this' becomes 'window' in a closure. - - /** - * Compute and return the score for a match with e errors and x location. - * Accesses loc and pattern through being a closure. - * @param {number} e Number of errors in match. - * @param {number} x Location of match. - * @return {number} Overall score for match (0.0 = good, 1.0 = bad). - * @private - */ - function match_bitapScore_(e, x) { - var accuracy = e / pattern.length; - var proximity = Math.abs(loc - x); - if (!dmp.Match_Distance) { - // Dodge divide by zero error. - return proximity ? 1.0 : accuracy; - } - return accuracy + (proximity / dmp.Match_Distance); - } - - // Highest score beyond which we give up. - var score_threshold = this.Match_Threshold; - // Is there a nearby exact match? (speedup) - var best_loc = text.indexOf(pattern, loc); - if (best_loc != -1) { - score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold); - // What about in the other direction? (speedup) - best_loc = text.lastIndexOf(pattern, loc + pattern.length); - if (best_loc != -1) { - score_threshold = - Math.min(match_bitapScore_(0, best_loc), score_threshold); - } - } - - // Initialise the bit arrays. - var matchmask = 1 << (pattern.length - 1); - best_loc = -1; - - var bin_min, bin_mid; - var bin_max = pattern.length + text.length; - var last_rd; - for (var d = 0; d < pattern.length; d++) { - // Scan for the best match; each iteration allows for one more error. - // Run a binary search to determine how far from 'loc' we can stray at this - // error level. - bin_min = 0; - bin_mid = bin_max; - while (bin_min < bin_mid) { - if (match_bitapScore_(d, loc + bin_mid) <= score_threshold) { - bin_min = bin_mid; - } else { - bin_max = bin_mid; - } - bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min); - } - // Use the result from this iteration as the maximum for the next. - bin_max = bin_mid; - var start = Math.max(1, loc - bin_mid + 1); - var finish = Math.min(loc + bin_mid, text.length) + pattern.length; - - var rd = Array(finish + 2); - rd[finish + 1] = (1 << d) - 1; - for (var j = finish; j >= start; j--) { - // The alphabet (s) is a sparse hash, so the following line generates - // warnings. - var charMatch = s[text.charAt(j - 1)]; - if (d === 0) { // First pass: exact match. - rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; - } else { // Subsequent passes: fuzzy match. - rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) | - (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | - last_rd[j + 1]; - } - if (rd[j] & matchmask) { - var score = match_bitapScore_(d, j - 1); - // This match will almost certainly be better than any existing match. - // But check anyway. - if (score <= score_threshold) { - // Told you so. - score_threshold = score; - best_loc = j - 1; - if (best_loc > loc) { - // When passing loc, don't exceed our current distance from loc. - start = Math.max(1, 2 * loc - best_loc); - } else { - // Already passed loc, downhill from here on in. - break; - } - } - } - } - // No hope for a (better) match at greater error levels. - if (match_bitapScore_(d + 1, loc) > score_threshold) { - break; - } - last_rd = rd; - } - return best_loc; -}; - - -/** - * Initialise the alphabet for the Bitap algorithm. - * @param {string} pattern The text to encode. - * @return {!Object} Hash of character locations. - * @private - */ -diff_match_patch.prototype.match_alphabet_ = function(pattern) { - var s = {}; - for (var i = 0; i < pattern.length; i++) { - s[pattern.charAt(i)] = 0; - } - for (var i = 0; i < pattern.length; i++) { - s[pattern.charAt(i)] |= 1 << (pattern.length - i - 1); - } - return s; -}; - - -// PATCH FUNCTIONS - - -/** - * Increase the context until it is unique, - * but don't let the pattern expand beyond Match_MaxBits. - * @param {!diff_match_patch.patch_obj} patch The patch to grow. - * @param {string} text Source text. - * @private - */ -diff_match_patch.prototype.patch_addContext_ = function(patch, text) { - if (text.length == 0) { - return; - } - if (patch.start2 === null) { - throw Error('patch not initialized'); - } - var pattern = text.substring(patch.start2, patch.start2 + patch.length1); - var padding = 0; - - // Look for the first and last matches of pattern in text. If two different - // matches are found, increase the pattern length. - while (text.indexOf(pattern) != text.lastIndexOf(pattern) && - pattern.length < this.Match_MaxBits - this.Patch_Margin - - this.Patch_Margin) { - padding += this.Patch_Margin; - pattern = text.substring(patch.start2 - padding, - patch.start2 + patch.length1 + padding); - } - // Add one chunk for good luck. - padding += this.Patch_Margin; - - // Add the prefix. - var prefix = text.substring(patch.start2 - padding, patch.start2); - if (prefix) { - patch.diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL, prefix)); - } - // Add the suffix. - var suffix = text.substring(patch.start2 + patch.length1, - patch.start2 + patch.length1 + padding); - if (suffix) { - patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, suffix)); - } - - // Roll back the start points. - patch.start1 -= prefix.length; - patch.start2 -= prefix.length; - // Extend the lengths. - patch.length1 += prefix.length + suffix.length; - patch.length2 += prefix.length + suffix.length; -}; - - -/** - * Compute a list of patches to turn text1 into text2. - * Use diffs if provided, otherwise compute it ourselves. - * There are four ways to call this function, depending on what data is - * available to the caller: - * Method 1: - * a = text1, b = text2 - * Method 2: - * a = diffs - * Method 3 (optimal): - * a = text1, b = diffs - * Method 4 (deprecated, use method 3): - * a = text1, b = text2, c = diffs - * - * @param {string|!Array.} a text1 (methods 1,3,4) or - * Array of diff tuples for text1 to text2 (method 2). - * @param {string|!Array.=} opt_b text2 (methods 1,4) or - * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2). - * @param {string|!Array.=} opt_c Array of diff tuples - * for text1 to text2 (method 4) or undefined (methods 1,2,3). - * @return {!Array.} Array of Patch objects. - */ -diff_match_patch.prototype.patch_make = function(a, opt_b, opt_c) { - var text1, diffs; - if (typeof a == 'string' && typeof opt_b == 'string' && - typeof opt_c == 'undefined') { - // Method 1: text1, text2 - // Compute diffs from text1 and text2. - text1 = /** @type {string} */(a); - diffs = this.diff_main(text1, /** @type {string} */(opt_b), true); - if (diffs.length > 2) { - this.diff_cleanupSemantic(diffs); - this.diff_cleanupEfficiency(diffs); - } - } else if (a && typeof a == 'object' && typeof opt_b == 'undefined' && - typeof opt_c == 'undefined') { - // Method 2: diffs - // Compute text1 from diffs. - diffs = /** @type {!Array.} */(a); - text1 = this.diff_text1(diffs); - } else if (typeof a == 'string' && opt_b && typeof opt_b == 'object' && - typeof opt_c == 'undefined') { - // Method 3: text1, diffs - text1 = /** @type {string} */(a); - diffs = /** @type {!Array.} */(opt_b); - } else if (typeof a == 'string' && typeof opt_b == 'string' && - opt_c && typeof opt_c == 'object') { - // Method 4: text1, text2, diffs - // text2 is not used. - text1 = /** @type {string} */(a); - diffs = /** @type {!Array.} */(opt_c); - } else { - throw new Error('Unknown call format to patch_make.'); - } - - if (diffs.length === 0) { - return []; // Get rid of the null case. - } - var patches = []; - var patch = new diff_match_patch.patch_obj(); - var patchDiffLength = 0; // Keeping our own length var is faster in JS. - var char_count1 = 0; // Number of characters into the text1 string. - var char_count2 = 0; // Number of characters into the text2 string. - // Start with text1 (prepatch_text) and apply the diffs until we arrive at - // text2 (postpatch_text). We recreate the patches one by one to determine - // context info. - var prepatch_text = text1; - var postpatch_text = text1; - for (var x = 0; x < diffs.length; x++) { - var diff_type = diffs[x][0]; - var diff_text = diffs[x][1]; - - if (!patchDiffLength && diff_type !== DIFF_EQUAL) { - // A new patch starts here. - patch.start1 = char_count1; - patch.start2 = char_count2; - } - - switch (diff_type) { - case DIFF_INSERT: - patch.diffs[patchDiffLength++] = diffs[x]; - patch.length2 += diff_text.length; - postpatch_text = postpatch_text.substring(0, char_count2) + diff_text + - postpatch_text.substring(char_count2); - break; - case DIFF_DELETE: - patch.length1 += diff_text.length; - patch.diffs[patchDiffLength++] = diffs[x]; - postpatch_text = postpatch_text.substring(0, char_count2) + - postpatch_text.substring(char_count2 + - diff_text.length); - break; - case DIFF_EQUAL: - if (diff_text.length <= 2 * this.Patch_Margin && - patchDiffLength && diffs.length != x + 1) { - // Small equality inside a patch. - patch.diffs[patchDiffLength++] = diffs[x]; - patch.length1 += diff_text.length; - patch.length2 += diff_text.length; - } else if (diff_text.length >= 2 * this.Patch_Margin) { - // Time for a new patch. - if (patchDiffLength) { - this.patch_addContext_(patch, prepatch_text); - patches.push(patch); - patch = new diff_match_patch.patch_obj(); - patchDiffLength = 0; - // Unlike Unidiff, our patch lists have a rolling context. - // https://github.com/google/diff-match-patch/wiki/Unidiff - // Update prepatch text & pos to reflect the application of the - // just completed patch. - prepatch_text = postpatch_text; - char_count1 = char_count2; - } - } - break; - } - - // Update the current character count. - if (diff_type !== DIFF_INSERT) { - char_count1 += diff_text.length; - } - if (diff_type !== DIFF_DELETE) { - char_count2 += diff_text.length; - } - } - // Pick up the leftover patch if not empty. - if (patchDiffLength) { - this.patch_addContext_(patch, prepatch_text); - patches.push(patch); - } - - return patches; -}; - - -/** - * Given an array of patches, return another array that is identical. - * @param {!Array.} patches Array of Patch objects. - * @return {!Array.} Array of Patch objects. - */ -diff_match_patch.prototype.patch_deepCopy = function(patches) { - // Making deep copies is hard in JavaScript. - var patchesCopy = []; - for (var x = 0; x < patches.length; x++) { - var patch = patches[x]; - var patchCopy = new diff_match_patch.patch_obj(); - patchCopy.diffs = []; - for (var y = 0; y < patch.diffs.length; y++) { - patchCopy.diffs[y] = - new diff_match_patch.Diff(patch.diffs[y][0], patch.diffs[y][1]); - } - patchCopy.start1 = patch.start1; - patchCopy.start2 = patch.start2; - patchCopy.length1 = patch.length1; - patchCopy.length2 = patch.length2; - patchesCopy[x] = patchCopy; - } - return patchesCopy; -}; - - -/** - * Merge a set of patches onto the text. Return a patched text, as well - * as a list of true/false values indicating which patches were applied. - * @param {!Array.} patches Array of Patch objects. - * @param {string} text Old text. - * @return {!Array.>} Two element Array, containing the - * new text and an array of boolean values. - */ -diff_match_patch.prototype.patch_apply = function(patches, text) { - if (patches.length == 0) { - return [text, []]; - } - - // Deep copy the patches so that no changes are made to originals. - patches = this.patch_deepCopy(patches); - - var nullPadding = this.patch_addPadding(patches); - text = nullPadding + text + nullPadding; - - this.patch_splitMax(patches); - // delta keeps track of the offset between the expected and actual location - // of the previous patch. If there are patches expected at positions 10 and - // 20, but the first patch was found at 12, delta is 2 and the second patch - // has an effective expected position of 22. - var delta = 0; - var results = []; - for (var x = 0; x < patches.length; x++) { - var expected_loc = patches[x].start2 + delta; - var text1 = this.diff_text1(patches[x].diffs); - var start_loc; - var end_loc = -1; - if (text1.length > this.Match_MaxBits) { - // patch_splitMax will only provide an oversized pattern in the case of - // a monster delete. - start_loc = this.match_main(text, text1.substring(0, this.Match_MaxBits), - expected_loc); - if (start_loc != -1) { - end_loc = this.match_main(text, - text1.substring(text1.length - this.Match_MaxBits), - expected_loc + text1.length - this.Match_MaxBits); - if (end_loc == -1 || start_loc >= end_loc) { - // Can't find valid trailing context. Drop this patch. - start_loc = -1; - } - } - } else { - start_loc = this.match_main(text, text1, expected_loc); - } - if (start_loc == -1) { - // No match found. :( - results[x] = false; - // Subtract the delta for this failed patch from subsequent patches. - delta -= patches[x].length2 - patches[x].length1; - } else { - // Found a match. :) - results[x] = true; - delta = start_loc - expected_loc; - var text2; - if (end_loc == -1) { - text2 = text.substring(start_loc, start_loc + text1.length); - } else { - text2 = text.substring(start_loc, end_loc + this.Match_MaxBits); - } - if (text1 == text2) { - // Perfect match, just shove the replacement text in. - text = text.substring(0, start_loc) + - this.diff_text2(patches[x].diffs) + - text.substring(start_loc + text1.length); - } else { - // Imperfect match. Run a diff to get a framework of equivalent - // indices. - var diffs = this.diff_main(text1, text2, false); - if (text1.length > this.Match_MaxBits && - this.diff_levenshtein(diffs) / text1.length > - this.Patch_DeleteThreshold) { - // The end points match, but the content is unacceptably bad. - results[x] = false; - } else { - this.diff_cleanupSemanticLossless(diffs); - var index1 = 0; - var index2; - for (var y = 0; y < patches[x].diffs.length; y++) { - var mod = patches[x].diffs[y]; - if (mod[0] !== DIFF_EQUAL) { - index2 = this.diff_xIndex(diffs, index1); - } - if (mod[0] === DIFF_INSERT) { // Insertion - text = text.substring(0, start_loc + index2) + mod[1] + - text.substring(start_loc + index2); - } else if (mod[0] === DIFF_DELETE) { // Deletion - text = text.substring(0, start_loc + index2) + - text.substring(start_loc + this.diff_xIndex(diffs, - index1 + mod[1].length)); - } - if (mod[0] !== DIFF_DELETE) { - index1 += mod[1].length; - } - } - } - } - } - } - // Strip the padding off. - text = text.substring(nullPadding.length, text.length - nullPadding.length); - return [text, results]; -}; - - -/** - * Add some padding on text start and end so that edges can match something. - * Intended to be called only from within patch_apply. - * @param {!Array.} patches Array of Patch objects. - * @return {string} The padding string added to each side. - */ -diff_match_patch.prototype.patch_addPadding = function(patches) { - var paddingLength = this.Patch_Margin; - var nullPadding = ''; - for (var x = 1; x <= paddingLength; x++) { - nullPadding += String.fromCharCode(x); - } - - // Bump all the patches forward. - for (var x = 0; x < patches.length; x++) { - patches[x].start1 += paddingLength; - patches[x].start2 += paddingLength; - } - - // Add some padding on start of first diff. - var patch = patches[0]; - var diffs = patch.diffs; - if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) { - // Add nullPadding equality. - diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL, nullPadding)); - patch.start1 -= paddingLength; // Should be 0. - patch.start2 -= paddingLength; // Should be 0. - patch.length1 += paddingLength; - patch.length2 += paddingLength; - } else if (paddingLength > diffs[0][1].length) { - // Grow first equality. - var extraLength = paddingLength - diffs[0][1].length; - diffs[0][1] = nullPadding.substring(diffs[0][1].length) + diffs[0][1]; - patch.start1 -= extraLength; - patch.start2 -= extraLength; - patch.length1 += extraLength; - patch.length2 += extraLength; - } - - // Add some padding on end of last diff. - patch = patches[patches.length - 1]; - diffs = patch.diffs; - if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) { - // Add nullPadding equality. - diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, nullPadding)); - patch.length1 += paddingLength; - patch.length2 += paddingLength; - } else if (paddingLength > diffs[diffs.length - 1][1].length) { - // Grow last equality. - var extraLength = paddingLength - diffs[diffs.length - 1][1].length; - diffs[diffs.length - 1][1] += nullPadding.substring(0, extraLength); - patch.length1 += extraLength; - patch.length2 += extraLength; - } - - return nullPadding; -}; - - -/** - * Look through the patches and break up any which are longer than the maximum - * limit of the match algorithm. - * Intended to be called only from within patch_apply. - * @param {!Array.} patches Array of Patch objects. - */ -diff_match_patch.prototype.patch_splitMax = function(patches) { - var patch_size = this.Match_MaxBits; - for (var x = 0; x < patches.length; x++) { - if (patches[x].length1 <= patch_size) { - continue; - } - var bigpatch = patches[x]; - // Remove the big old patch. - patches.splice(x--, 1); - var start1 = bigpatch.start1; - var start2 = bigpatch.start2; - var precontext = ''; - while (bigpatch.diffs.length !== 0) { - // Create one of several smaller patches. - var patch = new diff_match_patch.patch_obj(); - var empty = true; - patch.start1 = start1 - precontext.length; - patch.start2 = start2 - precontext.length; - if (precontext !== '') { - patch.length1 = patch.length2 = precontext.length; - patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, precontext)); - } - while (bigpatch.diffs.length !== 0 && - patch.length1 < patch_size - this.Patch_Margin) { - var diff_type = bigpatch.diffs[0][0]; - var diff_text = bigpatch.diffs[0][1]; - if (diff_type === DIFF_INSERT) { - // Insertions are harmless. - patch.length2 += diff_text.length; - start2 += diff_text.length; - patch.diffs.push(bigpatch.diffs.shift()); - empty = false; - } else if (diff_type === DIFF_DELETE && patch.diffs.length == 1 && - patch.diffs[0][0] == DIFF_EQUAL && - diff_text.length > 2 * patch_size) { - // This is a large deletion. Let it pass in one chunk. - patch.length1 += diff_text.length; - start1 += diff_text.length; - empty = false; - patch.diffs.push(new diff_match_patch.Diff(diff_type, diff_text)); - bigpatch.diffs.shift(); - } else { - // Deletion or equality. Only take as much as we can stomach. - diff_text = diff_text.substring(0, - patch_size - patch.length1 - this.Patch_Margin); - patch.length1 += diff_text.length; - start1 += diff_text.length; - if (diff_type === DIFF_EQUAL) { - patch.length2 += diff_text.length; - start2 += diff_text.length; - } else { - empty = false; - } - patch.diffs.push(new diff_match_patch.Diff(diff_type, diff_text)); - if (diff_text == bigpatch.diffs[0][1]) { - bigpatch.diffs.shift(); - } else { - bigpatch.diffs[0][1] = - bigpatch.diffs[0][1].substring(diff_text.length); - } - } - } - // Compute the head context for the next patch. - precontext = this.diff_text2(patch.diffs); - precontext = - precontext.substring(precontext.length - this.Patch_Margin); - // Append the end context for this patch. - var postcontext = this.diff_text1(bigpatch.diffs) - .substring(0, this.Patch_Margin); - if (postcontext !== '') { - patch.length1 += postcontext.length; - patch.length2 += postcontext.length; - if (patch.diffs.length !== 0 && - patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) { - patch.diffs[patch.diffs.length - 1][1] += postcontext; - } else { - patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, postcontext)); - } - } - if (!empty) { - patches.splice(++x, 0, patch); - } - } - } -}; - - -/** - * Take a list of patches and return a textual representation. - * @param {!Array.} patches Array of Patch objects. - * @return {string} Text representation of patches. - */ -diff_match_patch.prototype.patch_toText = function(patches) { - var text = []; - for (var x = 0; x < patches.length; x++) { - text[x] = patches[x]; - } - return text.join(''); -}; - - -/** - * Parse a textual representation of patches and return a list of Patch objects. - * @param {string} textline Text representation of patches. - * @return {!Array.} Array of Patch objects. - * @throws {!Error} If invalid input. - */ -diff_match_patch.prototype.patch_fromText = function(textline) { - var patches = []; - if (!textline) { - return patches; - } - var text = textline.split('\n'); - var textPointer = 0; - var patchHeader = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/; - while (textPointer < text.length) { - var m = text[textPointer].match(patchHeader); - if (!m) { - throw new Error('Invalid patch string: ' + text[textPointer]); - } - var patch = new diff_match_patch.patch_obj(); - patches.push(patch); - patch.start1 = parseInt(m[1], 10); - if (m[2] === '') { - patch.start1--; - patch.length1 = 1; - } else if (m[2] == '0') { - patch.length1 = 0; - } else { - patch.start1--; - patch.length1 = parseInt(m[2], 10); - } - - patch.start2 = parseInt(m[3], 10); - if (m[4] === '') { - patch.start2--; - patch.length2 = 1; - } else if (m[4] == '0') { - patch.length2 = 0; - } else { - patch.start2--; - patch.length2 = parseInt(m[4], 10); - } - textPointer++; - - while (textPointer < text.length) { - var sign = text[textPointer].charAt(0); - try { - var line = decodeURI(text[textPointer].substring(1)); - } catch (ex) { - // Malformed URI sequence. - throw new Error('Illegal escape in patch_fromText: ' + line); - } - if (sign == '-') { - // Deletion. - patch.diffs.push(new diff_match_patch.Diff(DIFF_DELETE, line)); - } else if (sign == '+') { - // Insertion. - patch.diffs.push(new diff_match_patch.Diff(DIFF_INSERT, line)); - } else if (sign == ' ') { - // Minor equality. - patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, line)); - } else if (sign == '@') { - // Start of next patch. - break; - } else if (sign === '') { - // Blank line? Whatever. - } else { - // WTF? - throw new Error('Invalid patch mode "' + sign + '" in: ' + line); - } - textPointer++; - } - } - return patches; -}; - - -/** - * Class representing one patch operation. - * @constructor - */ -diff_match_patch.patch_obj = function() { - /** @type {!Array.} */ - this.diffs = []; - /** @type {?number} */ - this.start1 = null; - /** @type {?number} */ - this.start2 = null; - /** @type {number} */ - this.length1 = 0; - /** @type {number} */ - this.length2 = 0; -}; - - -/** - * Emulate GNU diff's format. - * Header: @@ -382,8 +481,9 @@ - * Indices are printed as 1-based, not 0-based. - * @return {string} The GNU diff string. - */ -diff_match_patch.patch_obj.prototype.toString = function() { - var coords1, coords2; - if (this.length1 === 0) { - coords1 = this.start1 + ',0'; - } else if (this.length1 == 1) { - coords1 = this.start1 + 1; - } else { - coords1 = (this.start1 + 1) + ',' + this.length1; - } - if (this.length2 === 0) { - coords2 = this.start2 + ',0'; - } else if (this.length2 == 1) { - coords2 = this.start2 + 1; - } else { - coords2 = (this.start2 + 1) + ',' + this.length2; - } - var text = ['@@ -' + coords1 + ' +' + coords2 + ' @@\n']; - var op; - // Escape the body of the patch with %xx notation. - for (var x = 0; x < this.diffs.length; x++) { - switch (this.diffs[x][0]) { - case DIFF_INSERT: - op = '+'; - break; - case DIFF_DELETE: - op = '-'; - break; - case DIFF_EQUAL: - op = ' '; - break; - } - text[x + 1] = op + encodeURI(this.diffs[x][1]) + '\n'; - } - return text.join('').replace(/%20/g, ' '); -}; - - -// The following export code was added by @ForbesLindesay -module.exports = diff_match_patch; -module.exports['diff_match_patch'] = diff_match_patch; -module.exports['DIFF_DELETE'] = DIFF_DELETE; -module.exports['DIFF_INSERT'] = DIFF_INSERT; -module.exports['DIFF_EQUAL'] = DIFF_EQUAL; \ No newline at end of file diff --git a/packages/test-in-browser/driver.html b/packages/test-in-browser/driver.html index 52bd3b8aad..a8f976998b 100644 --- a/packages/test-in-browser/driver.html +++ b/packages/test-in-browser/driver.html @@ -44,12 +44,15 @@