commit 87ab0b794b47a5147e3abc527f28beedc3e1f5b3 Author: guanjihuan Date: Sat Sep 12 14:19:56 2026 +0800 update diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..8d3ceed --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,140 @@ +/** + * ESLint 配置 —— 只 lint renderer 业务代码(src/renderer, src/shared, src/preload)。 + * + * 不 lint src/main:那里是 Electron 主进程,跑的是 Node 18 的能力,规则集合不一样, + * 引入会让 CI 噪声很大、价值很低。先把 renderer 这一层质量守住。 + * + * 主要规则选型: + * - "@typescript-eslint/recommended-type-checked" 启用类型感知规则,副作用比 no-type-checked 系列少。 + * - react/jsx-runtime(自动从 react/jsx-runtime 引入),不用 import React。 + * - jsx-a11y 全套,覆盖 ARIA、键盘、对比度前的标签语义(颜色用 tailwind,不在 a11y 范围里)。 + * - 自定义规则: + * * no-restricted-syntax 禁止 console.* 在 src 代码里残留(要日志走 electron log); + * * no-restricted-imports 禁掉 cross-process import(renderer 永远不该 import src/main)。 + * + * 故意没开:prettier(用 prettier --check)、import/order(争论太多收益太小)。 + */ +module.exports = { + root: true, + env: { browser: true, es2022: true, node: true }, + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { jsx: true }, + project: ['./tsconfig.json'], + tsconfigRootDir: __dirname + }, + settings: { react: { version: 'detect' } }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended-type-checked', + 'plugin:react/recommended', + 'plugin:react/jsx-runtime', + 'plugin:react-hooks/recommended', + 'plugin:jsx-a11y/recommended' + ], + plugins: ['@typescript-eslint', 'react', 'react-hooks', 'jsx-a11y'], + ignorePatterns: [ + 'out/**', + 'node_modules/**', + 'dist/**', + 'release/**', + 'coverage/**', + 'test-results/**', + 'playwright-report/**', + '*.config.cjs', + '*.config.js', + '*.config.ts', + '.eslintrc.cjs', + 'tailwind.config.cjs', + 'postcss.config.cjs' + ], + rules: { + // 显式 any 在这种规模的代码里其实有必要 —— @typescript-eslint/no-explicit-any 在 type-checked + // 模式下噪声太大(每个 IpcError catch 都会撞)。先关掉,等真有滥用时再开。 + '@typescript-eslint/no-explicit-any': 'off', + // ts-ignore 用一行注释比挨个枚举类型便宜;只在违规真的不可避免时才允许 + '@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': 'allow-with-description' }], + // React 17+ 没用 React.* API,没必要强制默认写 React import + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + // 性能优化 / a11y 都需要 conditional roles(比如 isSelected ? 'true' : undefined) + 'react/jsx-boolean-value': ['warn', 'never'], + // hooks 依赖数组:我们有 useEffect 用 useRef 当 token 守门,react-hooks 会误报 + 'react-hooks/exhaustive-deps': 'warn', + // a11y 误报:role="button" 在 / 上是正确做法;jsx-a11y v6 仍把它当 interactive + // 控件要求 tabIndex,规则偶尔会和组件本身定的 roving tabindex 打架 → warn 不 block + 'jsx-a11y/no-noninteractive-element-interactions': 'warn', + 'jsx-a11y/click-events-have-key-events': 'warn', + // src/shared 是纯类型和常量,应该优先用 type 而不是 interface —— 同名 interface 合并 + // 在 React 项目里几乎总是误用 + '@typescript-eslint/consistent-type-imports': ['warn', { prefer: 'type-imports' }], + // onClick={async () => await foo()} 在 React 里是常见且正确的写法 —— React 会忽略返回 + // 的 Promise(这是 React 的设计缺陷,但用 ESLint 卡它没有收益,只会让所有按钮代码都 + // 包一层 void 包装)。同理表单 onSubmit。 + '@typescript-eslint/no-misused-promises': [ + 'error', + { checksVoidReturn: { arguments: false, attributes: false } } + ], + + 'no-restricted-syntax': [ + 'warn', + { + // renderer 里允许 console.error 用于诊断,但不允许 console.log —— 那是 debug 残留 + selector: "CallExpression[callee.object.name='console'][callee.property.name!=/^(error|warn)$/]", + message: 'renderer 进程不要留 console.log/warn;日志走 main 进程的 electron-log。' + } + ], + 'no-restricted-imports': [ + 'error', + { + // 阻止 renderer 跨进程边界拉主进程代码 —— 一旦发生,contextBridge 的隔离就破了 + patterns: [ + { + group: ['**/src/main/**', '**/main/**'], + message: 'renderer 不允许 import src/main/;走 preload 暴露的 IPC 接口。' + } + ] + } + ] + }, + overrides: [ + { + // 测试文件允许 any 和未使用变量(fixture/dummy 经常违反)。console.log 也允许—— + // 测试期间的诊断输出没法走 electron-log。require-await 在 mock fn 里大量误报 + // (vi.fn().mockResolvedValue 总是返回 Promise,写成 async 是统一形式): + files: ['**/*.test.{ts,tsx}', '**/__tests__/**', 'e2e/**'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + 'no-restricted-syntax': 'off', + '@typescript-eslint/require-await': 'off' + } + }, + { + // e2e 测试禁止固定 sleep:waitForTimeout(>=1000) 几乎一定意味着「等某件事完成」, + // 而这件事可以用 web-first 断言(expect().toBeVisible/toHaveText/toPass) + // 表达成「等某件事真的发生」。固定 sleep 在 CI 慢的时候假阴、快的机器上又假阳。 + // 例外:≤50ms 紧跟在 keyboard.press / type 后面,给 DOM 派发的事件循环时间, + // 这种替代方案不如直接 sleep 直观。 + files: ['e2e/**'], + rules: { + 'no-restricted-syntax': [ + 'error', + { + selector: + "CallExpression[callee.object.name='page'][callee.property.name='waitForTimeout'][arguments.0.value>=1000]", + message: + '禁止固定 sleep >=1s。用 web-first 断言代替(expect.toBeVisible/toPass 等),CI 慢机器才不会假阴、快的机器才不会假阳。' + } + ] + } + }, + { + files: ['src/preload/**'], + // preload 在沙箱里跑,没有 Node DOM,但有 Node 全局 + env: { browser: false, node: true } + } + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..980edc2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +out/ +release/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..50e6e83 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +node_modules +out +dist +release +coverage +test-results +playwright-report +package-lock.json +.claude +engine \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..fc06094 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,10 @@ +{ + "printWidth": 110, + "tabWidth": 2, + "useTabs": false, + "semi": false, + "singleQuote": true, + "trailingComma": "none", + "arrowParens": "always", + "endOfLine": "auto" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..85a100a --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# Python Profiler Visualizer + +粘贴一段 Python 代码,一键运行,立刻看到每个函数各花多少时间。桌面应用,不联网,不传你的代码,不配环境。 + +## 下载 + +到 [Releases](../../releases) 下载 `Python Profiler Visualizer Setup x.y.z.exe`,双击安装。首次启动会扫描 Python;找不到可点应用里的「打开终端」按钮,按系统预填命令装即可。 + +## 从源码运行 + +需 Node 20+ 和 Python 3.9+:`npm install` 后 `npm run dev`。 + +## 用法 + +1. 左侧编辑器贴代码(或点工具栏「加载示例」) +2. `Ctrl/Cmd + Enter` 运行 +3. 右侧看摘要、热点表、6 种耗时可视化 +4. 点热点表任意函数跳到对应行;标准库/第三方包会新开标签页展示源文件 +5. 工具栏切换「剖析范围」:仅用户代码 / 含库函数(切换后旧结果会标为陈旧) + +按 `← / →` 在柱状图、火焰图、矩形树图、旭日图、累计柱状图、模块热力图之间切换。 + +### 快捷键 + +| 按键 | 作用 | +| --- | --- | +| `Ctrl/Cmd + Enter` | 运行 / 取消 | +| `Esc` | 取消运行 / 关设置面板 | +| `← / →` | 切标签页或图表视图 | +| `Home` / `End` | 跳到首/尾 | +| `Delete` / `Ctrl/Cmd + W` | 关闭当前标签页 | + +## 耗时数字说明 + +cProfile 会拖慢代码 1.5~3 倍。引擎启动时跑同一段 tight-loop 测出裸跑与仪器化倍率,把你的 instrumented 时间除回去,摘要数字接近真实耗时,并标「已校准」。 + +提醒: + +- 单次数字有抖动,比较时建议跑多次 +- 含 `plt.show()` / `input()` / `cv2.waitKey()` 等阻塞事件循环的等待时,UI 会提示「含交互等待」 + +## 限制 + +- 仅单文件自包含脚本,不支持命令行参数或多文件项目 +- 无自动超时(取消靠按钮或 `Esc`),长时间任务跑到自然结束 +- 不替代 `py-spy` / `scalene` + +## 给开发者 + +Electron 31 + React 18 + Vite 5 + TypeScript 5,Python 引擎用 cProfile + 标准库。 + +```bash +npm run dev # 开发 +npm run build # 打包 +npm run release # 出 Windows 安装包 +npm test # 单元测试 +npm run test:e2e # E2E +python -m pytest engine/tests -v # Python 引擎测试 +``` + +架构:`src/main/` 主进程、`src/renderer/` React 前端、`engine/` Python 引擎。跨语言契约 `AnalysisResult`,Python 侧 `engine/schema.py` 是事实源,TypeScript 侧 `src/shared/analysis.ts` 对齐,契约测试 `engine/tests/test_contract.py` 锁版本。 + +`npm run release` 产物在 `release/Python Profiler Visualizer Setup x.y.z.exe`。未签名,Windows SmartScreen 点「更多信息 → 仍要运行」即可。 + +## License + +MIT \ No newline at end of file diff --git a/e2e/perf-optimizations.spec.ts b/e2e/perf-optimizations.spec.ts new file mode 100644 index 0000000..5ef29b2 --- /dev/null +++ b/e2e/perf-optimizations.spec.ts @@ -0,0 +1,57 @@ +import { test, expect, _electron as electron, type ElectronApplication, type Page } from '@playwright/test' +import { join } from 'path' + +/** + * 守护 Monaco 懒加载的「运行时」正确性。 + * + * v1 这里还有「ELK Worker 在 worker 里完成布局」的用例(CallGraph 组件用 ELK 跑 + * 布局):chore/comprehensive-optimization 把 CallGraph 整个砍掉之后,那条用例 + * 失去意义,留着只会锁住"CallGraph 必须存在"的反向约束,移走。 + * + * 为什么必须有 e2e 兜:lazy 边界一旦加载失败,Suspense 只会一直显示 fallback 骨架, + * 页面看起来「在加载」—— typecheck + build 全绿但运行时静默坏掉。修这种 bug 不会 + * 让 ESLint / tsc 报警,必须在真窗口里断言。 + */ + +let app: ElectronApplication +let page: Page +const pageErrors: string[] = [] + +test.beforeAll(async () => { + // 透传父进程 env,但必须剔掉 ELECTRON_RUN_AS_NODE:Playwright 自己是 Electron-as-Node + // 跑的,继承这个变量会让被测应用也退化成纯 Node,永远开不出窗口。 + const env: Record = {} + for (const [k, v] of Object.entries(process.env)) { + if (k !== 'ELECTRON_RUN_AS_NODE' && v !== undefined) env[k] = v + } + app = await electron.launch({ args: [join(__dirname, '..', 'out', 'main', 'index.js')], env }) + page = await app.firstWindow() + page.on('pageerror', (e) => pageErrors.push(e.message)) + + // 启动后等待首屏标题 + await expect(page.getByRole('heading', { name: 'Python 程序耗时可视化分析' })).toBeVisible() + await page.reload() +}) + +test.afterAll(async () => { + await app?.close() +}) + +test('Monaco 懒加载:骨架屏被真实编辑器替换,且编辑器可编辑', async () => { + // 不 waitForTimeout —— 用 web-first 断言等 lazy chunk 落地 + const editor = page.locator('.monaco-editor') + await expect(editor).toBeVisible({ timeout: 30_000 }) + + // 骨架屏必须让位。它带 role="status" + aria-label="app.editorLoading"(App.tsx 用 + // useT 拿当前 lang 翻译;中英都覆盖)。默认 lang 是 zh-CN,所以这里断言中文文案。 + await expect(page.getByLabel('编辑器加载中…')).toHaveCount(0) + + // 编辑器不只是「渲染了个壳」:示例代码必须真的被 Monaco tokenize 出来。 + // sample.ts 当前是 `def matinv(A):` —— 跟着示例从「矩阵乘法」改成「线性回归」一并换掉。 + await expect(page.locator('.view-line').first()).toBeVisible() + await expect(page.locator('.monaco-editor')).toContainText('matinv') +}) + +test('全程没有未捕获的渲染进程异常', async () => { + expect(pageErrors).toEqual([]) +}) diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts new file mode 100644 index 0000000..81e9286 --- /dev/null +++ b/e2e/smoke.spec.ts @@ -0,0 +1,56 @@ +import { test, expect, _electron as electron } from '@playwright/test' +import { join } from 'path' + +/** + * 端到端冒烟:覆盖 v2 的 UI 流程(不依赖已删除的 CallGraph / LineHeatmap / + * ExportButton / FindingsList)。 + * + * v1 残留断言("Hotspots" / "build_report" / "What to fix" / "Lines" tab / + * "导出 JSON")随着 v2 重构(见 chore/comprehensive-optimization)一并移除。 + * 跑这条用例需要的本地条件:检测到至少一个 Python 解释器(CI matrix 上预装)。 + */ + +test('端到端:加载示例 → 运行分析 → 摘要 + 热点表 + 火焰图', async () => { + // Electron 在 ELECTRON_RUN_AS_NODE 存在时会退化为纯 Node,需从环境中移除 + const env: Record = {} + for (const [k, v] of Object.entries(process.env)) { + if (k !== 'ELECTRON_RUN_AS_NODE' && v !== undefined) env[k] = v + } + const app = await electron.launch({ + args: [join(__dirname, '..', 'out', 'main', 'index.js')], + env + }) + const page = await app.firstWindow() + + // 标题存在(TopBar 渲染 h1 + 副标;lang 切换后 h1 文案跟着切) + await expect(page.getByRole('heading', { name: 'Python 程序耗时可视化分析' })).toBeVisible() + await page.reload() + await expect(page.getByRole('heading', { name: 'Python 程序耗时可视化分析' })).toBeVisible() + + // 等待解释器检测填充(并行跑 5 个 phase,最坏情况要 15-20s) + const runBtn = page.getByRole('button', { name: '运行分析' }) + try { + await expect(runBtn).toBeEnabled({ timeout: 30_000 }) + } catch { + await app.close() + test.skip(true, '本机未检测到 Python 解释器') + return + } + + await runBtn.click() + + // 摘要(RunSummaryLite)出现 = 分析成功,引擎至少跑完了一次 wall-time 测量 + await expect(page.getByText('本次耗时')).toBeVisible({ timeout: 60_000 }) + + // 热点表 section(v2 标题 = results.hotspot.title) + await expect(page.getByText('热点函数')).toBeVisible() + + // DEMO_CODE 里的对比函数:纯 Python 高斯消元 vs numpy —— 最耗时的 `matinv` 一定出现在表里 + await expect(page.getByText('matinv').first()).toBeVisible() + + // 火焰图 section(v2 标题 = results.time.title = "耗时分布"),确保 SVG 真的渲染 + await expect(page.getByText('耗时分布')).toBeVisible() + await expect(page.locator('svg[aria-label="火焰图"]')).toBeVisible() + + await app.close() +}) diff --git a/electron.vite.config.ts b/electron.vite.config.ts new file mode 100644 index 0000000..adfec51 --- /dev/null +++ b/electron.vite.config.ts @@ -0,0 +1,35 @@ +import { resolve } from 'path' +import { defineConfig } from 'electron-vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + main: { + build: { + rollupOptions: { + input: { index: resolve(__dirname, 'src/main/index.ts') } + } + } + }, + preload: { + build: { + rollupOptions: { + input: { index: resolve(__dirname, 'src/preload/index.ts') } + } + } + }, + renderer: { + root: resolve(__dirname, 'src/renderer'), + resolve: { + alias: { + '@shared': resolve(__dirname, 'src/shared'), + '@renderer': resolve(__dirname, 'src/renderer/src') + } + }, + build: { + rollupOptions: { + input: { index: resolve(__dirname, 'src/renderer/index.html') } + } + }, + plugins: [react()] + } +}) diff --git a/engine/__init__.py b/engine/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/engine/harness.py b/engine/harness.py new file mode 100644 index 0000000..25b7784 --- /dev/null +++ b/engine/harness.py @@ -0,0 +1,186 @@ +import gc +import platform +import sys +import time +import types + +import cProfile + +from engine.schema import Calibration, Environment + + +def collect_environment() -> Environment: + """采集运行环境指纹,用于结果可复现性与 UI 展示。""" + return Environment( + python=platform.python_version(), + platform=sys.platform, + processor=platform.processor() or platform.machine(), + timerResolution=time.get_clock_info("perf_counter").resolution, + ) + + +def load_source(script_path: str) -> str: + """读取脚本文本,剥离 BOM。 + + utf-8-sig 自动剥离 BOM(Windows Notepad 默认带 BOM 的 UTF-8 文件); + 用 utf-8 会留下  前缀,compile() 抛 SyntaxError: invalid character。 + 引擎内所有读源码的地方都必须走这里,否则会出现"harness 能跑但 + runner 的语法预检先失败"这类不一致(历史 bug)。 + """ + with open(script_path, "r", encoding="utf-8-sig") as f: + return f.read() + + +def _load_code(script_path: str): + return compile(load_source(script_path), script_path, "exec") + + +def _build_user_globals(script_path: str) -> dict: + """构造用户代码 exec 的 globals。 + + 两件事一起处理: + 1) sys.argv 临时清成 [script_path] —— 防用户代码读 --out 注册 atexit 改写 + result.json(关键安全洞 C1)。exec 完后恢复。 + 2) 装一个 __main__ module —— 让 pickle / multiprocessing / joblib 这类 + 依赖 sys.modules['__main__'] 的库能正常工作。 + 之前只把 __name__ 设成 '__main__' 但 sys.modules['__main__'] 仍指向 + engine.runner,直接 break pickle.dumps(本地定义类) —— + PicklingError: ... not found as __main__.Point。 + """ + # 装 synthetic __main__ module,跟 exec 的 globals 共享同一份 dict + main_mod = types.ModuleType("__main__") + main_mod.__file__ = script_path + main_mod.__spec__ = None + g = main_mod.__dict__ + g["__name__"] = "__main__" + g["__file__"] = script_path + g["__builtins__"] = __builtins__ + sys.modules["__main__"] = main_mod + return g + + +def _scrub_argv_for_user_code(script_path: str) -> list: + """把 sys.argv 收成 [script_path] 后备一份原值,exec 完后由调用方还原。 + + 关键:用户代码能 sys.argv.index('--out') 拿到结果文件路径然后 + atexit.register(lambda: open(out, 'w').write('{...forged...}')),engine + 写完 result.json 紧接着被覆盖。父进程拿到的就是 attacker-controlled JSON。 + """ + saved = sys.argv + sys.argv = [script_path] + return saved + + +def _restore_argv(saved): + sys.argv = saved + + +# 校准负载:tight-loop,典型的 CPU-bound 工作量。 +# 选 50_000 次是因为典型机器上耗时 ~10-30ms —— 既让 cProfile 开销稳定可测, +# 又不会让用户感到"启动变慢"。如果换成 1 次循环,ratio 会被调度噪声主导; +# 换成 1M 次,校准本身就要 ~1s,用户能感知。 +_CALIB_SOURCE = ( + "def _pyrof_calib():\n" + " s = 0\n" + " for i in range(50_000):\n" + " s += i\n" + " return s\n" + "_pyrof_calib()\n" +) + +# 在 module load 时编译一次 —— 校准负载是常量字符串,之前 calibrate 跑两遍 +# (clean + instrumented) 就 compile 两次,纯浪费。co_filename 用合成路径, +# 不需要落到磁盘 —— _build_user_globals 只取它当 __file__,不校验文件存在。 +_CALIB_CODE = compile(_CALIB_SOURCE, "", "exec") +# 同样用于 _build_user_globals(script_path) —— synthetic __main__.__file__ +# 用同一条合成路径,跟 code.co_filename 保持一致。 +_CALIB_PATH = "" + + +def _exec_once_for_calibration() -> None: + """校准负载的纯 exec —— 不开 cProfile。 + + GC 状态由 caller 负责 —— calibrate_cprofile_overhead 已经 disable 后 + re-enable,这里再包一层是冗余 no-op,徒增两个 isenabled() 系统调用。 + """ + g = _build_user_globals(_CALIB_PATH) + saved_argv = _scrub_argv_for_user_code(_CALIB_PATH) + try: + exec(_CALIB_CODE, g) + finally: + _restore_argv(saved_argv) + + +def _exec_once_under_cprofile() -> None: + """同一段校准负载,套 cProfile 跑一次 —— 用于测 cProfile 自身开销。 + + GC 状态由 caller 负责(同上理由)。""" + g = _build_user_globals(_CALIB_PATH) + saved_argv = _scrub_argv_for_user_code(_CALIB_PATH) + pr = cProfile.Profile() + pr.enable() + try: + exec(_CALIB_CODE, g) + finally: + pr.disable() + _restore_argv(saved_argv) + + +def calibrate_cprofile_overhead(workdir: str | None = None) -> Calibration: + """跑同一段 tight-loop 各一次(裸 vs 仪器化),返回膨胀系数。 + + 为什么需要这步: + - 用户脚本只 exec 一次 (单跑架构,v4 起),这次 exec 必须开 cProfile 才能拿归因 + - 但 cProfile 是插桩式的,wall-time 会膨胀 1.5~3x;直接报给用户不真实 + - 思路是「cProfile 开销 ≈ 跟用户代码调用次数成正比」,跟代码时间无关。 + 所以可以在一段**已知调用次数**的 tight-loop 上测一次 ratio,再把 + instrumented_user / ratio 当作估计的干净耗时。 + - 这一步与用户代码 exec 解耦:校准不会被用户的 import / 全局副作用干扰, + 也不会因为校准改了用户的状态而报错。 + + 边界情况: + - 校准本身耗时 <5ms (机器极快):perf_counter 精度+调度抖动会让 ratio + 跳到 0.5x/5x 这种离谱值,此时回退 ratio=1.0 (即相信 instrumented 时间)。 + - 校准本身耗时 5~50ms:正常,ratio 在 1.5~3 之间。 + + workdir:历史参数,现已废弃 —— 校准脚本不再落盘(改成内存里 compile + + 合成 __file__),workdir 不再被本函数读取。为保持 profiler-service / + 老调用方不破,参数保留并忽略。 + """ + del workdir # 明确:workdir 已废弃,调用方传过来也不再消费 + + try: + # 干净跑 —— 不开 cProfile + gc_was = gc.isenabled(); gc.disable() + try: + t0 = time.perf_counter() + _exec_once_for_calibration() + clean = time.perf_counter() - t0 + finally: + if gc_was: gc.enable() + + # 仪器化跑 —— 开 cProfile + gc_was = gc.isenabled(); gc.disable() + try: + t0 = time.perf_counter() + _exec_once_under_cprofile() + inst = time.perf_counter() - t0 + finally: + if gc_was: gc.enable() + finally: + pass # 无文件要清 + + if clean < 0.005 or inst <= 0 or clean <= 0: + # 校准耗时过短或退化:ratio 不可信,回退 1.0 (即不补偿,UI 显示原始 instrumented 时间) + return Calibration( + ratio=1.0, + workloadName="tight-loop", + instrumentedWorkloadSec=inst, + cleanWorkloadSec=clean, + ) + return Calibration( + ratio=inst / clean, + workloadName="tight-loop", + instrumentedWorkloadSec=inst, + cleanWorkloadSec=clean, + ) \ No newline at end of file diff --git a/engine/runner.py b/engine/runner.py new file mode 100644 index 0000000..303f11a --- /dev/null +++ b/engine/runner.py @@ -0,0 +1,257 @@ +import argparse +import io +import shutil +import sys +import tempfile +import traceback + +from engine.harness import calibrate_cprofile_overhead, collect_environment, load_source +from engine.schema import SCHEMA_VERSION, AnalysisResult, Calibration, FlameNode, WallTime +from engine.structure import profile_and_measure + + +# Result 文件大小防护(防用户脚本异常把磁盘撑爆) +# - error.message 上限 1KB:1KB 一般足够覆盖正常异常说明 +# - error.traceback 上限 8KB:100 帧左右 traceback 的体量,UI 可读范围 +# - functions 列表上限 5000:UI 表格默认只看前 200,再多只是给后续解析加负载 +_MAX_ERROR_MESSAGE_BYTES = 1024 +_MAX_TRACEBACK_BYTES = 8 * 1024 +_MAX_FUNCTIONS = 5000 + + +def _truncate_text(text: str, max_bytes: int) -> str: + """把文本按 UTF-8 字节截断到 max_bytes 以内,补一个明确截断标记。 + + 直接 [:max_chars] 在多字节字符中间切会破坏 UTF-8,UI 解析挂。encode → 截 + → 走 errors='ignore' 让不完整尾部自然掉落 → decode,保证结果仍是合法 UTF-8。 + """ + if not text: + return text + encoded = text.encode("utf-8", errors="replace") + if len(encoded) <= max_bytes: + return text + truncated = encoded[:max_bytes].decode("utf-8", errors="ignore") + # 留 64 字节给标记(总长仍 ≤ max_bytes) + suffix = f"\n\n[... 截断:共 {len(encoded)} 字节,仅保留前 {max_bytes} 字节 ...]" + # 如果 suffix 太长导致总超,再砍一次 + return (truncated + suffix)[:max_bytes] + + +def _cap_functions(functions: list, flame: FlameNode | None) -> tuple: + """截断 functions 列表到 _MAX_FUNCTIONS,并把超出部分累加到 flame 的 root。 + + 为什么不只截 functions 不动 flame? + - flame 是 UI 看到的"时间分布",丢了 functions 里那些小函数会让 flame + 总和 < 用户报的总耗时,UI 显示出现 100% 但 sum 不到 80% 的诡异情况。 + - 把被丢函数的 tottime 加到 root.value 上,UI 那边仍按 flame 内部占比显示, + 多出来的部分归到 root 节点(最大最宽的 tile),用户视觉上只是「其他」变 + 大了一点 —— 总和不撒谎。 + """ + if len(functions) <= _MAX_FUNCTIONS: + return functions, flame + kept = functions[:_MAX_FUNCTIONS] + dropped_tottime = sum(f.tottime for f in functions[_MAX_FUNCTIONS:]) + if flame is None: + flame = FlameNode(name="root", value=dropped_tottime, children=[]) + else: + flame.value += dropped_tottime + return kept, flame + + +def _compute_wall_time(instrumented_sec: float, calibration: Calibration) -> float: + """用校准 ratio 折算 instrumented 时间为估计的干净耗时。 + + 思路:cProfile 是插桩式的,wall-time 会被膨胀 ratio 倍(instrumented / clean); + 用同一段 tight-loop 测出 ratio 后,把用户的 instrumented 时间除回去,就是 + 「去掉 cProfile 开销后的估计耗时」。 + + 边界:calibration.ratio 是 0 / 负数 / NaN 时直接信 instrumented 时间 —— 跟 + calibrate_cprofile_overhead 在「校准耗时过短」时回退 ratio=1.0 的兜底策略 + 保持一致,UI 显示真实测得的 instrumented 时间而不是除以无效值。 + """ + ratio = calibration.ratio + # `ratio == ratio` 排除 NaN;`ratio > 0` 排除 0 / 负数 + if ratio == ratio and ratio > 0: + return instrumented_sec / ratio + return instrumented_sec + + +def _cap_result(result: AnalysisResult) -> AnalysisResult: + """给 result.json 上一道防线,防止异常情况把磁盘/IPC 通道撑爆。 + + 触发场景: + - 用户代码 raise 一个带 50MB 文件内容的 ValueError → error.message 撑爆 + - 用户代码触发深递归异常,Python 自动 dump 几千层 traceback → 撑爆 + - 用户脚本 import 一个庞大的库(numpy/pandas)scope=all 把整棵树都归因 → + 38MB+ 的 functions 列表 + + 截断而非拒绝:UI 仍能看到「有错」「发生了什么」,只是具体细节截断;functions + 截断后仍按 tottime 排序,前 5000 一定是最值得用户关注的热点。 + """ + if result.error: + msg = result.error.get("message") + if isinstance(msg, str) and msg: + result.error["message"] = _truncate_text(msg, _MAX_ERROR_MESSAGE_BYTES) + tb = result.error.get("traceback") + if isinstance(tb, str) and tb: + result.error["traceback"] = _truncate_text(tb, _MAX_TRACEBACK_BYTES) + if result.functions: + result.functions, result.flame = _cap_functions(result.functions, result.flame) + return result + + +def _progress(phase, pct): + # 前导 \n:tqdm / 进度条类库会写"working..."到 stderr 但不换行, + # 直接接 PROGRESS 会拼成 "working...PROGRESS running 10",主进程正则匹配挂、 + # 进度事件掉一档且把工程协议行混进 stderrTail 让用户看到 \r 进度条残片。 + print(f"\nPROGRESS {phase} {pct}", file=sys.stderr, flush=True) + + +def _emit(result: AnalysisResult, out_path=None): + """发出结果。 + + out_path 给定时结果写入独立文件,stdout/stderr 就完整留给用户代码 —— + 否则用户代码里一句 sys.stdout.write("done") 或 print(..., end='') 就会和 + JSON 挤在同一行,主进程按"最后一行"取 JSON 时解析失败, + 用户看到的却是"结果解析失败"。不给 out_path 时退回 stdout(直接跑 CLI / 老测试)。 + """ + payload = result.to_json() + if out_path: + with io.open(out_path, "w", encoding="utf-8", newline="\n") as f: + f.write(payload) + else: + print(payload, flush=True) + + +def _base(env, config, status, error=None, wall_time=None, functions=None, flame=None, calibration=None): + return AnalysisResult( + schemaVersion=SCHEMA_VERSION, + environment=env, + config=config, + status=status, + error=error, + wallTime=WallTime(seconds=wall_time, unit="s") if wall_time is not None else None, + functions=functions or [], + flame=flame, + calibration=calibration, + ) + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--script", required=True) + ap.add_argument("--out", default=None, + help="结果 JSON 的输出文件;不给则打到 stdout") + ap.add_argument("--scope", choices=["user", "all"], default="user", + help="剖析范围:user(默认)只归因用户脚本里的函数;" + "all 包含所有非 cProfile 内部帧(标准库 + 第三方包 + 用户代码)," + "让耗时可以下钻到 import 的包里。") + ap.add_argument("--workdir", default=None, + help="校准文件落盘的临时目录;不给则 runner 自己 mkdtemp。" + "profiler-service 一般传过来(它创建的 pyprof-xxx)," + "直跑 CLI 不传也行。") + ap.add_argument("--hide-internal", dest="hide_internal", + action=argparse.BooleanOptionalAction, default=True, + help="过滤掉测试代码(tests/ / test_*.py / _pyrof_calib 等)+ " + "tottime=0 的量化噪声帧。默认 on,用户原话:「软件内部的测试部分" + "默认百分百过滤掉」。--no-hide-internal 关掉这条过滤,看完整数据。") + args = ap.parse_args(argv) + + # 统一走这个 emit:结果去 --out 指定的文件,stdout 留给用户代码。 + # 每条 emit 之前过 _cap_result 给 result.json 上一道防线 —— error.traceback / + # error.message / functions 列表都设硬上限,防止用户异常把磁盘 / IPC 撑爆。 + def emit(result): + _emit(_cap_result(result), args.out) + + env = collect_environment() + config: dict = {} + + # 版本预检:主进程的 validateInterpreter 已经挡过一层,这里是纵深防御—— + # 用户可能绕过 UI 直接调引擎,或 PATH 上的 python 在探测后被换掉。 + # 低于 3.9 时后面的 list[X] 泛型注解会直接 SyntaxError,拿不到结构化错误。 + if sys.version_info < (3, 9): + got = ".".join(str(p) for p in sys.version_info[:3]) + emit(_base(env, config, "runtime_error", + error={"type": "PythonTooOld", + "message": f"引擎需要 Python 3.9 或更高版本,当前为 {got}"})) + return + + # 一次 IO 拿到 src:语法预检和后续 profile 都用这份文本 + # 之前没 try/except:临时目录被删 / 路径权限不够 → 进程崩,无 JSON 输出 → UI 显示 "引擎无输出" + # UnicodeDecodeError 不是 OSError 的子类:二进制文件 / 非 UTF-8 脚本会绕过这层保护再次崩溃。 + try: + src = load_source(args.script) + except (OSError, UnicodeDecodeError) as e: + emit(_base(env, config, "runtime_error", + error={"type": type(e).__name__, "message": f"无法读取脚本: {e}"})) + return + + # Syntax precheck (用文本而非再读一次) + # compile() 还会在源码含 null 字节时抛 ValueError,也一并按 syntax_error 归类 + # 把 code object 留起来 —— profile_and_measure 还需要再 exec 一次, + # 直接复用这份省一次 read + compile。 + try: + code = compile(src, args.script, "exec") + except (SyntaxError, ValueError) as e: + emit(_base(env, config, "syntax_error", error={"type": type(e).__name__, "message": str(e)})) + return + + # v4 单跑架构:校准 + 用户代码 under cProfile 一次拿到归因 + instrumented 时间。 + # 跟 v3 的「先裸跑测 wall_time 再 cProfile 跑」相比, + # 用户脚本的副作用(plot / file write / etc)只发生一次,语义更直观。 + # workdir 优先用 caller 传的(profiler-service 已经 mkdir pyprof-xxx 落 _pyrof_calib.py + # 和用户脚本);caller 不传则自己 mkdtemp —— 这种情况跑完自己清掉。 + workdir = args.workdir + workdir_owned = False + if not workdir: + workdir = tempfile.mkdtemp(prefix="pyprof-") + workdir_owned = True + + try: + try: + _progress("calibrating", 5) + # 校准脚本落在 caller 提供的 workdir 里 —— profiler-service 已经 mkdir + # pyrof-xxx 装用户脚本,把 _pyrof_calib.py 放一起能让 rmtree(workdir) 一次 + # 清干净。runner 自己 mkdtemp 时(直跑 CLI 场景)workdir_owned=True, + # calibration 拿到的是同一个目录,临时文件一起被 finally 兜底删。 + calibration = calibrate_cprofile_overhead(workdir) + + _progress("running", 30) + st, instrumented = profile_and_measure( + args.script, scope=args.scope, hide_internal=args.hide_internal, + code=code, src=src, + ) + wall_time = _compute_wall_time(instrumented, calibration) + + _progress("done", 100) + emit(_base(env, config, "ok", + wall_time=wall_time, + functions=st.functions, + flame=st.flame, + calibration=calibration)) + return + except SystemExit as e: + # sys.exit() 抛 SystemExit(BaseException 的子类)—— 不被下面的 except BaseException 捕获, + # 子进程直接退出无 JSON,UI 就会显示 "引擎无输出 (exit N)"。转成 runtime_error。 + # e.code 默认 None(无参 sys.exit()),文案要单独处理,否则会出现"sys.exit(None)" + code = e.code if isinstance(e.code, int) else (0 if e.code is None else f"非整数 {e.code!r}") + emit(_base(env, config, "runtime_error", + error={"type": "SystemExit", "message": f"用户代码调用 sys.exit({code})"})) + except BaseException as e: + # KeyboardInterrupt / GeneratorExit / 自定义 BaseException 子类 以及 + # 普通 Exception 一并兜底 —— 之前再写一条 `except Exception` 是 dead code + # (BaseException 是 Exception 的父类,先匹配的赢)。父进程端有 cancel + + # 进程树 kill 在管 Ctrl+C,这里只负责兜底写 JSON。 + emit(_base(env, config, "runtime_error", + error={"type": type(e).__name__, "message": str(e), + "traceback": traceback.format_exc()})) + finally: + # runner 自己 mkdtemp 的目录要自己清 —— profiler-service 已经传 --workdir 时 + # 整个 workdir 由 caller 管(主进程清理临时目录的逻辑在 before-quit),这里只兜 + # 「直接 python -m engine.runner」的直跑场景。 + if workdir_owned: + shutil.rmtree(workdir, ignore_errors=True) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/engine/schema.py b/engine/schema.py new file mode 100644 index 0000000..9ef6bfc --- /dev/null +++ b/engine/schema.py @@ -0,0 +1,87 @@ +from dataclasses import dataclass, field, asdict +import json + +SCHEMA_VERSION = 4 + + +@dataclass +class Environment: + python: str + platform: str + processor: str + timerResolution: float + + +@dataclass +class WallTime: + seconds: float + unit: str = "s" + + +@dataclass +class Calibration: + # v4 新增:cProfile 开销校准系数,用于把 instrumented wall-time 折算为干净耗时。 + # 用户脚本只 exec 一次(单跑架构)的代价是 wall-time 带 cProfile 开销;启动期 + # 用同一段 tight-loop 各跑一次(裸 vs 仪器化),ratio = instrumented / clean, + # 用户 wall_time = instrumented_user / ratio。 + ratio: float + # 校准负载名:方便 debug 时看出是哪段负载产生的系数;目前固定为 "tight-loop" + workloadName: str = "tight-loop" + # 校准负载在 cProfile 下的耗时(秒)—— 透传给 UI 方便 debug + instrumentedWorkloadSec: float = 0.0 + # 校准负载裸跑的耗时(秒) + cleanWorkloadSec: float = 0.0 + + +@dataclass +class FunctionNode: + id: str # "file:line:name" + file: str + line: int + name: str + cumtime: float + tottime: float + ncalls: int + percallTot: float + # 顶层模块名(用于 scope=all 时的 UI 分类): + # "" 用户脚本 / "json" / "numpy" / "" 等。 + # 提取逻辑见 engine/structure._top_module;这一层做归类, + # 让 UI 不必重新解析文件路径(路径 normalize 在 OS 间不一致)。 + module: str = "" + # 帧来源(v3 新增;用于 UI 按 origin 分组): + # "user" 用户脚本 / "stdlib" 标准库 / "third_party" 第三方包 / + # "builtin" 内置(C 实现的 builtin) / "frozen" frozen importlib 等 / + # "other" 兜底(未匹配任何已知来源,例如奇怪的 帧) + # + # 优先用 sys.stdlib_module_names 校准(3.10+),降级用路径启发式: + # /Lib/ 或 /lib/pythonX.Y/ → stdlib;含 site-packages/dist-packages → third_party。 + # 推导逻辑见 engine/structure._classify_origin。 + origin: str = "" + + +@dataclass +class FlameNode: + name: str + value: float + children: list = field(default_factory=list) + + +@dataclass +class AnalysisResult: + schemaVersion: int + environment: Environment + config: dict + status: str # ok|syntax_error|runtime_error|timeout + error: dict | None + wallTime: WallTime | None + functions: list + flame: FlameNode | None + # v4 新增:ok 状态下必有;非 ok(语法错 / 运行时错 / 超时)下为 None。 + # 见 Calibration 字段注释 —— 折算 wallTime 时需要,存到 result 里给 UI 看。 + calibration: Calibration | None = None + + def to_json(self) -> str: + # ensure_ascii=True(默认):在 GBK locale / 没设 PYTHONUTF8 的环境下 + # stdout 不是合法 UTF-8,UI 解析会挂。转义成纯 ASCII 后逐字节一致, + # JSON.parse 原生还原。主进程仍然会设那两个环境变量,这里是纵深防御。 + return json.dumps(asdict(self), ensure_ascii=True) \ No newline at end of file diff --git a/engine/structure.py b/engine/structure.py new file mode 100644 index 0000000..22c49a9 --- /dev/null +++ b/engine/structure.py @@ -0,0 +1,576 @@ +import ast +import cProfile +import functools +import os +import pstats +import re +import sys +import time +from dataclasses import dataclass + +from engine.schema import FlameNode, FunctionNode +from engine.harness import _build_user_globals, _load_code, _restore_argv, _scrub_argv_for_user_code, load_source + + +@dataclass +class StructureResult: + functions: list + flame: FlameNode + + +# ────────────────────────────────────────────────────────────────────────────── +# 内部测试代码识别 + 量化噪声过滤 +# ────────────────────────────────────────────────────────────────────────────── +# +# 用户原话:「软件内部的测试部分默认百分百过滤掉,不在统计范围内容。完全不显示。」 +# —— 所以测试代码 + tottime=0 的帧默认不进 result.json / functions[] / flame。 +# +# 这是 v5 才加的过滤。在 App 入口(filterAnalysis)也有一份,这里再加一份是为了 +# 「不打开 UI 也想看干净数据」的场景:用户拿 result.json 跑自己的聚合脚本时, +# 拿到的就是过滤后的数据,而不是 774 帧 + 90% 是噪声。 +# +# 跟 App 层契约一致:只匹配 CONTEXT(文件路径 / 模块路径) + 引擎内部硬编码白名单, +# 完全不看函数名 —— TS 端 isInternalTest 的同样设计原则,误伤 = bug。 + +import re as _re_noise + +# 文件名是 test_*.py / *_test.py —— 任意位置都算测试代码(pytest 文件命名约定) +_INTERNAL_TEST_FILE_RE = _re_noise.compile(r'[\\/](?:test_[^\\/]+|[^\\/]+_test)\.py$') +# 在 tests/ / test/ / __tests__/ **直接下面**的文件 —— pytest 目录约定。 +# 收紧到要求「tests/ 后面紧接一个文件名,不能再有 / 子目录」: +# * tests/foo.py → 算 (foo.py 直接在 tests/ 下) +# * tests/fixtures/foo.py → 不算 (fixtures 是子目录,foo.py 不直接挂在 tests/ 下) +# 之前用 `[\\/](tests?|__tests__)[\\/]` 任何含 `/tests/` 的路径都中招,把 fixtures 这类 +# 测试数据夹具也当成测试代码误伤 —— 用户脚本只要住在带 tests/ 子目录的路径下, +# 函数表直接被过滤成 0 行。 +_INTERNAL_TEST_DIR_RE = _re_noise.compile(r'[\\/](tests?|__tests__)[\\/][^\\/]+$') +_INTERNAL_TEST_MODULE_RE = _re_noise.compile(r'^(tests?|__tests__)([._]|$)') +_INTERNAL_NAMES: frozenset = frozenset(["_pyrof_calib"]) + + +def _is_internal_test(name: str, file: str, module: str) -> bool: + """Python 版的 isInternalTest —— 命中即视作「噪声帧」,不进 functions[]。 + + 设计原则(同 TS 版 utils/origin.ts): + - 只看语境(文件 / 模块 / 引擎白名单),不看函数名 —— 函数名匹配太容易误伤 + (test_helper() / TestCase.test_login() 都是合法业务函数) + - 引擎内部硬编码白名单(_pyrof_calib)是兜底防御,任何情况下都不该出现在用户 stats 里 + """ + if name in _INTERNAL_NAMES: + return True + # 文件名是 test_*.py / *_test.py —— 任意位置 + if _INTERNAL_TEST_FILE_RE.search(file): + return True + # 在 tests/ / test/ / __tests__/ 直接下面的文件(不能是子目录) + if _INTERNAL_TEST_DIR_RE.search(file): + return True + # module 一定非空(_top_module 不会返回空字符串),不用做 truthy 兜底 + if _INTERNAL_TEST_MODULE_RE.match(module): + return True + return False + + +def _extract_user_imports(src: str) -> set: + """从用户脚本源码里抽出顶层显式 import 的模块名集合。 + + 只看模块级 Import / ImportFrom —— 函数/类内部的 import 是延迟副作用,不是 + 「我需要分析的目标」。返回集合里保留顶级包名(numpy / json / os 等),不展开 + as 后的别名(as np → numpy 也在集合里,别名不进集合)。 + + 解析失败(syntax error 等)时返回空集合 —— 调用方已经独立做了 compile 预检, + 走到 profile_and_measure 的代码一定是合法 Python,这里只是防御。 + """ + try: + tree = ast.parse(src) + except SyntaxError: + return set() + modules: set = set() + for node in tree.body: + if isinstance(node, ast.Import): + for alias in node.names: + # 'import a.b.c' → 'a' 就够了;子包自然跟着顶级包一起保留。 + modules.add(alias.name.split(".")[0]) + elif isinstance(node, ast.ImportFrom): + # 'from . import x' 的 level > 0 是相对导入,跳过 —— 它们依附于某个 + # 已知包(用户脚本 / 已导入的第三方),不展开根模块。 + # node.level 是 int(0/1/2/...),0 是 falsy,这里只需 if node.level。 + if node.level: + continue + if node.module: + modules.add(node.module.split(".")[0]) + return modules + + +def _fid(func): + file, line, name = func + return f"{file}:{line}:{name}" + + +@functools.lru_cache(maxsize=4096) +def _norm(path): + """规范化路径 —— Windows NTFS 大小写不敏感 + 跨斜杠风格统一。 + + 包 lru_cache:典型 profile 5000 帧 / 50 unique file path —— 之前每个 + frame 都跑 abspath + normcase,scope=user 的 hot loop 里反复调; + 缓存后只有 50 次真正的 abspath。同一路径跨多次 _top_module / + _classify_origin / _make_frame_filter 复用同一结果。 + maxsize=4096 覆盖任何真实 profile 看不到的 unique file 数。 + """ + try: + return os.path.normcase(os.path.abspath(path)) + except Exception: + return path + + +def _make_frame_filter(script_path, scope="user"): + """构造帧过滤器。 + + scope=user(默认):只保留用户脚本里的函数 — 历史上一直如此。 + scope=all:保留所有非 cProfile 内部帧(包含标准库和第三方包)— 让耗时 + 可以"进入到 import 的包里",否则 `pandas.read_csv` 永远是一个黑盒, + 看到 3s 也不知道是序列化慢、IO 慢还是解析慢。 + + `~` 帧的过滤有讲究 —— 之前一刀切 file == '~' 都丢,实际上: + - cProfile 用 `~` 作为**所有 C 函数帧**的文件名(time.sleep / numpy C 核 + / json 加速 / re 等),丢这些直接打瞎 scope=all + - 真正的 cProfile 内部帧靠 name 区分:`'_lsprof.Profiler'` / + `'Profiler' / ''` 之类 + 只剔除 name 含 '_lsprof.Profiler' 的帧,其它 C 函数保留 —— schema 里 + origin='builtin' / module='' 也终于能命中真实 builtin 帧。 + """ + target = _norm(script_path) + + def _is_kept(func): + file, _, name = func + # cProfile 内部帧 —— file='~',name 含 "_lsprof.Profiler" 子串 + # (实际形态:'Profiler' / '_lsprof.Profiler' / + # "") + if file == "~" and ("_lsprof.Profiler" in name or name == "Profiler"): + return False + if scope == "all": + return True + # 虚拟路径( / / 等)不可能是用户脚本, + # 直接 False 省一次 _norm —— _norm("") 会跑 abspath 拼成 + # "/" 然后 normcase,跟用户脚本路径比必然不等,但白做一次 + # 文件系统查询。~ 在 cProfile 内部已上面短路,这里同等处理。 + if file.startswith("<") or file == "~": + return False + return _norm(file) == target + + return _is_kept + + +def _top_module(file_path: str, user_script_path: str, user_script_norm: str | None = None) -> str: + """把 cProfile 给的 file 路径归类成「顶层模块名」,给 UI 做分类用。 + + 输出样例: + - 用户脚本(==user_script_path) → "" + - "/usr/lib/python3.11/json/decoder.py" → "json" + - "C:\\Python39\\Lib\\json\\decoder.py" → "json" + - "C:\\Python39\\Lib\\functools.py" → "functools" ← 顶层 .py 必须剥掉后缀 + - "/.../site-packages/numpy/core/array.py" → "numpy" + - "" → "" + - "" → "" + - 其他 → 取倒数第二个目录段作 fallback(基本不会走到) + + user_script_norm: 调用方预计算的 `_norm(user_script_path)` —— 同一进程内 + 同一脚本会查几百次,提到外面省 abspath;不传则本函数内现算(保持单点调用方兼容)。 + """ + if file_path == "~": + # C 函数(time.sleep / numpy 加速 / json C decoder 等): + # file==~ + name 是 "" + # 之前丢光后这里 dead code,现在 _make_frame_filter 不再丢 C 帧, + # module 字段需要给出有意义分类 —— 用 "" 跟 origin 字段对齐。 + return "" + if _norm(file_path) == (user_script_norm if user_script_norm is not None else _norm(user_script_path)): + return "" + # frozen / built-in / 这种「虚拟」文件:整段作为标签 + if file_path.startswith("<"): + if file_path.startswith("" + if file_path.startswith("" + return file_path + # 路径规范化:跨平台 + 跨斜杠 + norm = file_path.replace("\\", "/") + parts = [p for p in norm.split("/") if p] + # site-packages / dist-packages:标记之后的第一个目录段就是包名 + for marker in ("site-packages", "dist-packages"): + if marker in parts: + idx = parts.index(marker) + 1 + if idx < len(parts): + return parts[idx] + # 标准库(Windows 安装布局):C:\Python39\Lib\\... 或顶层 .py + # `Lib\\functools.py` → "functools"(不是 "functools.py"),用 _strip_py 兜底 + if "Lib" in parts: + idx = parts.index("Lib") + 1 + if idx < len(parts): + return _strip_py(parts[idx]) + # 标准库(Linux / macOS 安装布局):/usr/lib/python3.X/\... + # 之前用 p.startswith("python") + p[6:7].isdigit() 太松散 —— "python3-extra" + # 这种目录会被误识别;收紧到严格的 `pythonX(.Y)?` 形式。 + for i, p in enumerate(parts): + if re.fullmatch(r"python\d+(\.\d+)?", p): + if i + 1 < len(parts): + return _strip_py(parts[i + 1]) + # 兜底:取倒数第二个目录段(例如 ".../myproj/src/utils/helper.py" → "utils") + if len(parts) >= 2: + return _strip_py(parts[-2]) + return _strip_py(file_path) + + +def _strip_py(name: str) -> str: + """顶层 .py 文件剥掉扩展名 —— `functools.py` → `functools`。 + + 只剥 `.py` 后缀;其它段('__init__'、'site-packages' 等)原样保留。 + 非顶层文件不会被这个函数触碰 —— _top_module 把它包在 `parts[idx]` 之外的位置时 + 返回的就是 `parts[-2]` 这种目录段,永远不带 `.py`;只有顶层 `.py` 才走到这里。""" + if name.endswith(".py"): + return name[:-3] + return name + + +# sys.stdlib_module_names 是 3.10+ 才有的;3.9 及之前要走路径兜底。 +# 提前 frozen 一次 —— 同一进程内不变,反复判 in 走 frozenset 是 O(1)。 +_STDLIB_MODULES: frozenset | None +try: + _STDLIB_MODULES = frozenset(getattr(sys, "stdlib_module_names", set())) +except Exception: + _STDLIB_MODULES = None + + +# 路径兜底"是不是真的在 stdlib 根下"用 sysconfig —— sysconfig.get_paths() 是 +# Python 官方给出的 stdlib 根解析工具,远比手算 + 'Lib' / '/lib/pythonX.Y' +# 靠谱(venv / embed / framework 几种安装布局都覆盖)。 +# +# M5 fix:之前只看路径里是否含 "/Lib/" 或 "/lib/pythonX.Y/" —— 用户项目 +# 里有 `myproject/Lib/foo.py` 这种就会被错认成 stdlib。现在锚到 sysconfig +# 算出的真 stdlib 根上:不是真正的 Python 安装根下面的,一律不算 stdlib。 +# +# 一次性算好缓存,frozen 之后 hot path 上 O(1) prefix 比对。 +def _stdlib_roots() -> tuple: + roots: list = [] + try: + import sysconfig + stdlib_path = sysconfig.get_paths().get("stdlib", "") + if stdlib_path: + roots.append(_norm(stdlib_path)) + except Exception: + pass + # 兜底再放 sys.prefix/Lib —— 有些 embedded 安装 sysconfig 拿不到 + fallback = os.path.join(sys.prefix, "Lib") + roots.append(_norm(fallback)) + return tuple(roots) + + +_STDLIB_ROOTS_NORM: tuple = _stdlib_roots() + + +def _is_under_real_stdlib(file_path: str) -> bool: + """判断 file_path 是否在真正的 Python stdlib 根下。 + + 只走"路径兜底"分支 (3.9 / 未知模块名 兜底),已有 _STDLIB_MODULES 命中时不调用本函数,无谓开销。 + """ + fp = _norm(file_path) + for root in _STDLIB_ROOTS_NORM: + if fp == root or fp.startswith(root + os.sep): + return True + return False + + +def _classify_origin(file_path: str, user_script_path: str, module_name: str, user_script_norm: str | None = None) -> str: + """给一帧函数归类来源(v3 新增字段 origin)。 + + 返回值(即 JSON 里的字面量,UI 端按这个 group): + "user" 用户脚本(路径与 user_script_path 一致) + "frozen" 等冻结帧 + "builtin" 等 C 实现的 builtin + "stdlib" Python 标准库(按 sys.stdlib_module_names 校准;3.10+ + 才生效,老版本退化为路径启发式) + "third_party" site-packages / dist-packages 下的第三方包 + "other" 兜底 —— 例如 、未匹配任何已知布局的奇怪路径 + + 顺序很关键: + 1) user / frozen / builtin 用文件路径前缀直接判,O(1) + 2) stdlib 先查 sys.stdlib_module_names(权威),命中即返回 + 3) 路径里出现 site-packages / dist-packages → third_party + 4) 路径里出现 /Lib/ 或 /lib/pythonX.Y/ → stdlib(启发式兜底) + 5) 其它 → other + + user_script_norm: 调用方预计算的 `_norm(user_script_path)` —— 同 _top_module, + 热路径上几百次调用,提到外面省一次 abspath。 + """ + # C 扩展函数(time.sleep / numpy C 核 / json C 加速器 等)cProfile 把 file 标成 "~"。 + # _top_module 已经把 module 字段定为 ""(与 origin 字段对齐的契约见那里), + # 这里也要走 builtin 分支,否则 origin = "other" 与 module = "" 错位,UI + # 端按 origin 分组时这条帧会落到别的桶里 —— 之前一直漏到这里。 + if file_path == "~": + return "builtin" + # 虚拟文件路径( / / 等)优先短路 —— 之前 + # 先 _norm 再判 < 是浪费 abspath,而且 "<..." 这种路径跟用户脚本路径无论如何 + # 都不可能相等,白调一次 norm。顺序调成「<... 优先」后 hot path 上少 100+ + # 次 _norm 调用(典型 scope=all 的 profile 里 / 帧占大头)。 + if file_path.startswith("<"): + if file_path.startswith(" tuple: + """v4 单跑架构:一次 exec(code) under cProfile,同时拿到函数归因和 instrumented wall-time。 + + 替代 v3 的两阶段执行(先裸跑测 wall_time 再 cProfile 跑): + - 用户脚本只 exec 一次 → plot / print / file-write 等副作用只发生一次 + - 返回的 instrumented_wall 含 cProfile 自身开销(典型 1.5~3x 膨胀) + - 调用方需要配合 calibrate_cprofile_overhead 折算:wall_time = instrumented / ratio + + scope: "user"(默认)只归因用户脚本里的函数;"all" 包含所有非 cProfile 内部帧 + (标准库 + 第三方包 + 用户代码),让用户能下钻到 import 的包里。 + + hide_internal (v5 新增,默认 True):过滤掉测试代码 + tottime=0 的量化噪声帧。 + - 测试代码:tests/ / test_*.py / _pyrof_calib 等(见 _is_internal_test) + - tottime=0:scope=all 时 typing / inspect / functools 等内部展开常被 cProfile 量化精度截到 0, + 这些不是优化目标,默认剔除能让 result.json 干净到「只剩真正在跑的代码」 + - 设为 False 时不过滤 —— 给想排查调用栈 / 自定义聚合的用户留一条后路 + + code / src:可选的预读 code object 和源码文本。runner.py 已经在做 syntax + precheck 时 read + compile 过一份,这里直接复用 —— 避免重复 IO(原来 + profile_and_measure 自己又 read 两次 + AST parse 一次)。这两个参数 + 给 None 时回退到「自己 load_source + compile」,供老调用方 / 单测继续工作。 + + NOTE: 不在 cProfile exec 周围禁用 GC —— cProfile 应该看到真实的执行环境 + (包括 GC 暂停),折算后的 wall-time 才能反映真实耗时。calibration 那两次 + tight-loop 跑各跑各的 GC 策略(详见 harness.calibrate_cprofile_overhead)。 + """ + # 复用 caller 读好的 code / src —— runner.py 的 syntax precheck 已经 read+compile 过一次, + # 再读一次等于把同样的字节流从磁盘捞 2 次 + AST parse 一次。None 时退回到旧的「自己读」路径。 + if code is None: + code = _load_code(script_path) + if src is None and hide_internal: + src = load_source(script_path) + g = _build_user_globals(script_path) + saved_argv = _scrub_argv_for_user_code(script_path) + pr = cProfile.Profile() + pr.enable() + try: + t0 = time.perf_counter() + exec(code, g) + instrumented = time.perf_counter() - t0 + finally: + pr.disable() + _restore_argv(saved_argv) + + stats = pstats.Stats(pr) + is_kept = _make_frame_filter(script_path, scope) + # 规范化一次:每个函数帧都会把 file_path 与 script_path 比对;规范化结果 + # 与具体帧无关 —— 提到循环外,几百行的 functions 表能省几百次 abspath 调用。 + script_norm = _norm(script_path) + # 解析用户脚本里显式 import 的模块集合。stdlib 内部帧如果来自「用户没 + # 显式 import 的包」(typing/inspect/functools/re/_py_warnings/...), + # 一律视为 numpy/pandas 这类第三方包触发的「间接调用链」—— 用户无法优化, + # 默认剔除。结果:scope=all 时 result.json 也只剩用户代码 + 显式导入的 + # 第三方包 + 真正大头的 stdlib 模块(json/os/etc.,用户写了 `import json` + # 就看 json,否则不看)。 + # 之前无条件 ast.parse 整个 src —— 即便用户脚本 module 全是 ""(scope=user) + # 根本进不到这条 stdlib 过滤分支,几百行的 fixture 也走一遍 AST。改成只在真正会 + # 消费 user_imports 的组合里算(scope=all + hide_internal=True)。 + if hide_internal and scope == "all": + user_imports = _extract_user_imports(src) + else: + user_imports = set() + # file → (module, origin) 缓存:同一文件的多个函数帧(numpy 几百帧共享一个 file) + # 只算一次 module + origin。_top_module 和 _classify_origin 各自又会再调一次 + # _norm(file),加 cache 后这两个调用也都省了 —— 典型 profile 5000 帧 / 50 文件, + # _top_module 从 5000 次降到 50 次,_classify_origin 同。 + file_info_cache: dict[str, tuple] = {} + def _classify_file(file_path: str) -> tuple: + cached = file_info_cache.get(file_path) + if cached is not None: + return cached + module = _top_module(file_path, script_path, script_norm) + origin = _classify_origin(file_path, script_path, module, script_norm) + cached = (module, origin) + file_info_cache[file_path] = cached + return cached + + # ── v6 「用户直接调用」过滤 ── + # 用户原话:「只要代码的本身和import 调用的耗时统计,其他的不需要」/「目前好像 + # 仍然统计到内部测试的代码了,不合理」—— 之前虽然过滤掉了 stdlib 内部噪声帧 + # (typing/inspect/functools 等),但 487 帧里仍有: + # - numpy 内部 250+ 帧(np.array 调用的 _core.fromnumeric 等)→ 间接 + # - importlib._bootstrap 90 帧 → 间接导入机制 + # - C 函数 131 帧(len / numpy C 核 / _warnings / dict.keys 等) + # - _distutils_hack 2 帧、mkl 7 帧 → setup machinery,非用户调用 + # 用户其实只要: + # 1) 自己写的函数(模块名 == "") + # 2) 自己「直接调用」的 import 入口(np.array / np.mean / json.dumps 等) + # 实现:cProfile 的 callers 字段自带调用方信息。「某帧的 caller 含用户脚本 + # 里的帧」=「用户直接调用」。再加 origin 闸门:仅 third_party / 用户显式 import + # 的 stdlib 才算「import 调用」—— builtin / frozen / other / 没显式 import 的 + # stdlib 一律不保留,即使技术上确实被用户代码调用到(len / print / numpy C 核)。 + # ────────────────────────────────────────────────────────────────────────────── + functions = [] + if hide_internal: + # Pass 1:收集用户帧 + 把通过基础过滤的帧的 (module, origin) 缓存下来。 + # 注意:这里不再做 tt <= 0 过滤 —— 用户代码 + import 入口里常有纯 C 分派的 + # 薄包装(np.random.rand / numpy.__getattr__ 等),cumtime 远大于 0 但 tottime + # 恰好压在 cProfile 量化精度地板上,被滤掉就把"用户调用了哪个 API"这条信息丢了。 + # 用户代码 + import 入口自然就少,即便有 tt=0 也只是干净 user-function 占位, + # 全保留就行。 + user_frames: set = set() + func_info_cache: dict = {} + for func, (cc, nc, tt, ct, callers) in stats.stats.items(): + if not is_kept(func): + continue + file = func[0] + module_name, origin = _classify_file(file) + if _is_internal_test(func[2], file, module_name): + continue + func_info_cache[func] = (module_name, origin) + if module_name == "": + user_frames.add(func) + + # Pass 2:边判断「是不是 import_callee」边构造 FunctionNode(原版是分两个独立 + # pass 跑 import_callees 再跑 functions,合一遍能省掉一次 stats.stats 全量迭代。 + # import_callees 设单独 set 也不必要 —— 这里用 include 标志位本地决定,跳出本 + # 帧判断后立即 append / continue。 + for func, (cc, nc, tt, ct, callers) in stats.stats.items(): + info = func_info_cache.get(func) + if info is None: + continue # pass 1 已过滤 + module_name, origin = info + if func in user_frames: + include = True + else: + include = False + for caller in callers: + if caller in user_frames: + if origin == "third_party" or ( + origin == "stdlib" and module_name in user_imports + ): + # 用户代码确实调到了这个 import 入口 —— 但还要看 ct: + # 低于 cProfile 量化精度(1µs)的「被调到的帧」(典型: + # numpy._mean_dispatcher 这种注册期被触发的辅助分发器, + # cProfile 把调用方记成 但实际不干活)是噪声。 + # 用户代码写的空函数另算(user_frames 不受这条约束)。 + include = ct >= 1e-6 + break + if not include: + continue + file = func[0] + functions.append( + FunctionNode( + id=_fid(func), + file=file, + line=func[1], + name=func[2], + cumtime=ct, + tottime=tt, + ncalls=nc, + percallTot=(tt / nc if nc else 0.0), + module=module_name, + origin=origin, + ) + ) + else: + # hide_internal=False:用户要的是全量原始数据 —— 不做 import_callee / 测试代码 / 零耗时过滤, + # 直接把 is_kept 通过的帧全收下来。 + for func, (cc, nc, tt, ct, callers) in stats.stats.items(): + if not is_kept(func): + continue + file = func[0] + module_name, origin = _classify_file(file) + functions.append( + FunctionNode( + id=_fid(func), + file=file, + line=func[1], + name=func[2], + cumtime=ct, + tottime=tt, + ncalls=nc, + percallTot=(tt / nc if nc else 0.0), + module=module_name, + origin=origin, + ) + ) + + functions.sort(key=lambda f: f.tottime, reverse=True) + total = sum(f.tottime for f in functions) + flame = _build_flame(functions, total) + return StructureResult(functions=functions, flame=flame), instrumented + + +def profile_structure( + script_path: str, + scope: str = "user", + hide_internal: bool = True, +) -> StructureResult: + """Backward-compat shim:v3 时期暴露的「只拿归因、不读 wall-time」接口。 + + v4 起实际工作在 profile_and_measure 里完成;保留这个包装是为了不破坏直接 + import engine.structure.profile_structure 的测试 / 旧调用方。语义跟 v3 一样: + 只跑一次 cProfile exec、返回 functions + flame。 + """ + result, _ = profile_and_measure(script_path, scope=scope, hide_internal=hide_internal) + return result + + +def _build_flame(functions: list, total: float) -> FlameNode: + """构造火焰图根节点。 + + 火焰图第一层有两个 layout 选项: + - 单模块(scope=user 或刚好只 import 一个包):保持扁平(函数列表), + 和 v2 之前完全一致,向后兼容。 + - 多模块(scope=all 且命中 ≥2 个不同的顶层模块):按 module 聚合—— + 用户问「时间花在了哪个包」时第一眼就能看到;点模块 tile 下钻看内部函数。 + """ + # 单模块时维持扁平 —— 同名兄弟不会被 module 节点挤占,截断阈值不变 + modules = {f.module for f in functions} + if len(modules) <= 1: + return FlameNode( + name="root", + value=total, + children=[FlameNode(name=f.name, value=f.tottime) for f in functions], + ) + + # 多模块:按 module 聚合;模块自身 value 是该模块下所有函数 tottime 之和 + by_module: dict = {} + for f in functions: + node = by_module.get(f.module) + if node is None: + node = FlameNode(name=f.module, value=0.0, children=[]) + by_module[f.module] = node + node.value += f.tottime + node.children.append(FlameNode(name=f.name, value=f.tottime)) + # 模块按总 tottime 降序,让最贵的包排最左(icicle 布局的视觉约定) + sorted_modules = sorted(by_module.values(), key=lambda m: m.value, reverse=True) + return FlameNode(name="root", value=total, children=sorted_modules) \ No newline at end of file diff --git a/engine/tests/__init__.py b/engine/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/engine/tests/fixtures/demo_sort.py b/engine/tests/fixtures/demo_sort.py new file mode 100644 index 0000000..12abd8e --- /dev/null +++ b/engine/tests/fixtures/demo_sort.py @@ -0,0 +1,27 @@ +"""Demo script for users: contains several detectable anti-patterns. + +- membership test against a list inside a loop -> membership_in_list +- nested loops -> nested_loops +- string concatenation with += inside a loop -> str_concat_loop +""" + + +def build_report(rows, allow): + out = "" + for r in rows: + if r in allow: # O(n) membership test on a list + out += str(r) + "," # string += accumulation + for other in rows: # nested loop -> O(n^2) + if r == other: + pass + return out + + +def main(): + rows = list(range(400)) + allow = list(range(0, 400, 2)) + for _ in range(30): + build_report(rows, allow) + + +main() diff --git a/engine/tests/fixtures/nested_calls.py b/engine/tests/fixtures/nested_calls.py new file mode 100644 index 0000000..b8b749e --- /dev/null +++ b/engine/tests/fixtures/nested_calls.py @@ -0,0 +1,16 @@ +def leaf(): + s = 0 + for i in range(20000): + s += i + return s + + +def mid(): + return sum(leaf() for _ in range(3)) + + +def main(): + return mid() + + +main() diff --git a/engine/tests/fixtures/with_imports.py b/engine/tests/fixtures/with_imports.py new file mode 100644 index 0000000..d64834f --- /dev/null +++ b/engine/tests/fixtures/with_imports.py @@ -0,0 +1,19 @@ +"""scope=all 的测试 fixture —— 故意 import 一些标准库并实际用它们, +让 cProfile 能归因到非用户脚本里的帧。""" +import json +import time + + +def payload(): + # 强制 json.loads / json.dumps 进 cProfile 栈 + return json.loads(json.dumps({"a": 1, "b": [1, 2, 3]})) + + +def run(): + for _ in range(50): + payload() + # 让 time.sleep / 调度器也进栈 —— 注意 sleep < cProfile tick 的话归不到 + # 它自己头上,但 time 模块的辅助函数会被经过 + + +run() \ No newline at end of file diff --git a/engine/tests/test_contract.py b/engine/tests/test_contract.py new file mode 100644 index 0000000..b1ac9fc --- /dev/null +++ b/engine/tests/test_contract.py @@ -0,0 +1,125 @@ +"""跨语言契约测试:engine/schema.py 的输出必须与 src/shared/analysis.ts 的类型对齐。 + +之前这个文件把 key 集合硬编码在 Python 里,从不读 analysis.ts —— 也就是说 +TS 侧加一个字段、或者两边字段名写歧了,这个"契约测试"一律绿灯, +而 README 声称的"用契约测试保证一致"并不成立。 +现在两边都是解析出来的:Python 侧从 dataclass 的注解拿,TS 侧从 interface 声明拿。 +""" + +import dataclasses +import io +import json +import os +import re +import subprocess +import sys + +from engine import schema + +ROOT = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) +FX = os.path.join(os.path.dirname(__file__), "fixtures") +ANALYSIS_TS = os.path.join(ROOT, "src", "shared", "analysis.ts") + + +def _ts_interface_fields(name): + """从 analysis.ts 里抽出某个 interface 的字段名集合。 + + 只处理"一行一个字段"的写法(本仓库的风格),够用且不需要引 TS parser。 + 可选字段的 `?` 去掉后再比较。 + """ + src = io.open(ANALYSIS_TS, encoding="utf-8").read() + m = re.search(r"export interface %s\s*\{(.*?)\n\}" % re.escape(name), src, re.S) + assert m, "analysis.ts 里找不到 interface %s" % name + fields = set() + for line in m.group(1).splitlines(): + line = line.strip() + if not line or line.startswith("//") or line.startswith("*"): + continue + fm = re.match(r"([A-Za-z_][A-Za-z0-9_]*)\??\s*:", line) + if fm: + fields.add(fm.group(1)) + assert fields, "interface %s 解析出 0 个字段,解析器该修了" % name + return fields + + +def _py_dataclass_fields(cls): + return {f.name for f in dataclasses.fields(cls)} + + +def _run_engine(extra=(), env=None): + # 默认关掉 hide_internal:契约测试要断言 keys / shapes, + # filtered 之后的 functions 列表可能为空,IndexError 挂掉。 + p = subprocess.run( + [sys.executable, "-m", "engine.runner", + "--script", os.path.join(FX, "nested_calls.py"), + "--no-hide-internal", *extra], + capture_output=True, cwd=ROOT, env=env, + ) + return p + + +def _engine_result(): + p = _run_engine() + return json.loads(p.stdout.decode("utf-8").strip().splitlines()[-1]) + + +def test_top_level_keys_match_typescript(): + data = _engine_result() + assert set(data.keys()) == _ts_interface_fields("AnalysisResult") + + +def test_top_level_keys_match_python_dataclass(): + data = _engine_result() + assert set(data.keys()) == _py_dataclass_fields(schema.AnalysisResult) + + +def test_wall_time_keys_match_both_sides(): + data = _engine_result() + keys = set(data["wallTime"].keys()) + assert keys == _ts_interface_fields("WallTime") + assert keys == _py_dataclass_fields(schema.WallTime) + + +def test_function_keys_match_both_sides(): + data = _engine_result() + keys = set(data["functions"][0].keys()) + assert keys == _ts_interface_fields("FunctionNode") + assert keys == _py_dataclass_fields(schema.FunctionNode) + + +def test_output_is_pure_ascii(): + """输出必须是纯 ASCII。 + + 反模式说明现在是历史的;保留 ensure_ascii=True 后输出应当逐字节 ASCII, + 跨 locale 一致。这个回归保护防止有人"为可读性"改回 ensure_ascii=False。 + """ + env = {k: v for k, v in os.environ.items() + if k not in ("PYTHONUTF8", "PYTHONIOENCODING")} + p = _run_engine(env=env) + raw = p.stdout + bad = [b for b in raw if b > 127] + assert not bad, "引擎输出含 %d 个非 ASCII 字节" % len(bad) + raw.decode("ascii") # 不该抛 + + +def test_out_flag_writes_result_to_file(tmp_path): + """--out:结果走独立文件,stdout 留给用户代码。 + + 锁的是那个回归 —— 结果曾和用户输出共用 stdout,一句不带换行的 + sys.stdout.write 就能让合法 Python 报"结果解析失败"。 + """ + out = os.path.join(str(tmp_path), "result.json") + script = os.path.join(str(tmp_path), "chatty.py") + io.open(script, "w", encoding="utf-8", newline="\n").write( + 'import sys\ndef f():\n sys.stdout.write("done")\n return 1\nf()\n' + ) + p = subprocess.run( + [sys.executable, "-m", "engine.runner", "--script", script, "--out", out], + capture_output=True, cwd=ROOT, + ) + assert os.path.exists(out), "引擎没写出结果文件;stderr=%s" % p.stderr[-400:] + data = json.loads(io.open(out, encoding="utf-8").read()) + assert data["status"] == "ok" + # 用户的输出确实进了 stdout,而且没污染结果 + assert b"done" in p.stdout + assert b"schemaVersion" not in p.stdout \ No newline at end of file diff --git a/engine/tests/test_harness.py b/engine/tests/test_harness.py new file mode 100644 index 0000000..8b2a1d4 --- /dev/null +++ b/engine/tests/test_harness.py @@ -0,0 +1,77 @@ +import pytest + +from engine.harness import calibrate_cprofile_overhead, collect_environment, load_source + + +def test_calibrate_returns_well_formed_calibration(): + """校准应该返回一个 well-formed Calibration:ratio > 0,两段耗时 > 0,workloadName 已知。 + + 不强求 ratio > 1.0:在极快的机器上校准本身 < 5ms,会走 fallback ratio=1.0; + 此时 instrumentedWorkloadSec 可能比 clean 略小(perf_counter 在 1ms 量级有 noise), + 也不强求 instrumented >= clean。这条测试只锁住「返回结构对 + 数值非负」。 + """ + calib = calibrate_cprofile_overhead() + assert calib.ratio > 0, f"ratio 必须正:{calib.ratio}" + assert calib.ratio <= 10.0, f"ratio 太大,可能校准失效:{calib.ratio}" + assert calib.cleanWorkloadSec > 0 + assert calib.instrumentedWorkloadSec > 0 + assert calib.workloadName == "tight-loop" + + +def test_calibrate_fallback_when_workload_too_fast(monkeypatch): + """校准本身耗时 < 5ms 时应回退 ratio=1.0 —— 见 calibrate_cprofile_overhead 的边界处理。 + + 直接 mock calibrate_cprofile_overhead 内部的 _exec_once_* 行为不可行(它们是模块内 helper), + 改成 monkeypatch time.perf_counter 返回极短耗时:第一次返回 t0=0.0,第二次返回 t0+1e-6。 + """ + from engine import harness + + counter = {"n": 0} + base = {"t": 1000.0} + + def fake_perf_counter(): + counter["n"] += 1 + # 第一次调用返回 0,之后每次 + 极小 delta,让 elapsed = ~3us (远小于 5ms) + idx = counter["n"] + return base["t"] + idx * 1e-6 + + monkeypatch.setattr(harness.time, "perf_counter", fake_perf_counter) + calib = harness.calibrate_cprofile_overhead() + assert calib.ratio == 1.0, f"短耗时下应回退 ratio=1.0,实际:{calib.ratio}" + + +def test_calibrate_does_not_leak_files(monkeypatch, tmp_path): + """校准自己 scratch 一个临时文件,跑完应清干净 —— 不污染系统临时目录。""" + # 让 mkstemp 落到 tmp_path 方便断言 + import tempfile + monkeypatch.setattr(tempfile, "gettempdir", lambda: str(tmp_path)) + before = set(tmp_path.iterdir()) + calibrate_cprofile_overhead() + after = set(tmp_path.iterdir()) + # 不应有残留 —— 之前 / 之后 iterdir 集合应相等 + assert after == before, f"残留文件: {after - before}" + + +def test_collect_environment(): + env = collect_environment() + assert env.python.count(".") >= 2 + assert env.timerResolution > 0 + + +def test_load_source_strips_bom(tmp_path): + """load_source 必须剥掉 BOM —— 引擎内所有读源码的地方都依赖这一点。""" + script = tmp_path / "bom.py" + script.write_text("x = 1\n", encoding="utf-8-sig") + assert script.read_bytes().startswith(b"\xef\xbb\xbf") + src = load_source(str(script)) + assert not src.startswith("") + # 剥离后必须能直接过 compile(),这正是 runner 语法预检做的事 + compile(src, str(script), "exec") + + +def test_load_source_raises_on_binary(tmp_path): + """二进制文件抛 UnicodeDecodeError(而不是 OSError)—— runner 依赖这个类型来归类错误。""" + script = tmp_path / "bin.py" + script.write_bytes(b"\xff\xfe\x00\x01\x80\x81") + with pytest.raises(UnicodeDecodeError): + load_source(str(script)) \ No newline at end of file diff --git a/engine/tests/test_runner.py b/engine/tests/test_runner.py new file mode 100644 index 0000000..90b1344 --- /dev/null +++ b/engine/tests/test_runner.py @@ -0,0 +1,158 @@ +import json +import os +import subprocess +import sys + +FX = os.path.join(os.path.dirname(__file__), "fixtures") +ROOT = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + + +def _run(script, *extra): + # 默认关掉 hide_internal:这些测试是结构 / 状态 / 形状的契约测试, + # 要看到 fixture 里所有 frame 才能断言。hide_internal 是 UI 默认开的产品行为, + # 不是引擎协议的一部分 —— 测协议就别测产品默认。 + p = subprocess.run( + [sys.executable, "-m", "engine.runner", "--script", script, + "--no-hide-internal", *extra], + capture_output=True, text=True, cwd=ROOT, + ) + assert p.stdout.strip(), f"no stdout; stderr={p.stderr}" + return json.loads(p.stdout.strip().splitlines()[-1]) + + +def test_ok_run(): + data = _run(os.path.join(FX, "nested_calls.py")) + assert data["status"] == "ok" + assert data["wallTime"]["seconds"] > 0 + assert data["wallTime"]["unit"] == "s" + assert len(data["functions"]) >= 3 + + +def test_syntax_error(tmp_path): + bad = tmp_path / "syntax_error.py" + bad.write_text("def f(:\n pass\n", encoding="utf-8") + data = _run(str(bad)) + assert data["status"] == "syntax_error" + assert data["error"]["type"] + + +def test_runtime_error(tmp_path): + bad = tmp_path / "runtime_error.py" + bad.write_text("raise ValueError('boom')\n", encoding="utf-8") + data = _run(str(bad)) + assert data["status"] == "runtime_error" + assert "boom" in data["error"]["message"] + + +def test_sys_exit_becomes_runtime_error(tmp_path): + """sys.exit() 抛 SystemExit(BaseException 子类),之前 except Exception 抓不到, + 子进程裸退出无 JSON → UI 显示 "引擎无输出"。现在应被显式捕获并归为 runtime_error。""" + bad = tmp_path / "exit.py" + bad.write_text("import sys\nsys.exit(7)\n", encoding="utf-8") + data = _run(str(bad)) + assert data["status"] == "runtime_error" + assert "SystemExit" in data["error"]["type"] + assert "7" in data["error"]["message"] + + +def test_io_error_returns_runtime_error(tmp_path): + """脚本文件不存在 / 不可读时,不要让子进程裸崩 — 应该回 runtime_error 给出有用信息。""" + missing = tmp_path / "does_not_exist.py" + data = _run(str(missing)) + assert data["status"] == "runtime_error" + assert "FileNotFoundError" in data["error"]["type"] or "无法读取" in data["error"]["message"] + + +def test_bom_script_runs_ok(tmp_path): + """带 BOM 的 UTF-8 脚本(Windows 记事本默认保存格式)必须能正常跑。 + + 回归测试:runner 曾用 encoding="utf-8" 读源码,BOM 的  前缀残留下来, + 语法预检的 compile() 直接抛 SyntaxError: invalid character,用户看到的是 + "语法错误"而不是正常结果 —— 而 harness.py 早就用 utf-8-sig 处理对了, + 两处不一致导致这个 bug 只在预检阶段出现。 + """ + script = tmp_path / "bom.py" + script.write_text("x = sum(range(1000))\n", encoding="utf-8-sig") + # 确认 fixture 真的带 BOM,否则这个测试会静默失效 + assert script.read_bytes().startswith(b"\xef\xbb\xbf") + data = _run(str(script)) + assert data["status"] == "ok", f"BOM 脚本被误判: {data.get('error')}" + + +def test_binary_script_returns_runtime_error(tmp_path): + """非 UTF-8 / 二进制文件应回结构化 runtime_error,而不是让引擎裸崩。 + + 回归测试:UnicodeDecodeError 不是 OSError 的子类,之前只 except OSError + 的那层保护抓不到它,子进程崩溃且无 JSON 输出 → UI 显示"引擎无输出"。 + """ + script = tmp_path / "binary.py" + script.write_bytes(b"\x00\x01\x02\xff\xfe\xfd binary garbage \x80\x81") + data = _run(str(script)) + assert data["status"] in ("runtime_error", "syntax_error") + assert data["error"]["type"] + + +def test_scope_default_is_user(): + """默认 --scope=user —— 不给 scope 参数时只看到用户脚本里的函数。""" + script = os.path.join(FX, "with_imports.py") + data = _run(script) + assert data["status"] == "ok" + files = {f["file"] for f in data["functions"]} + # user 模式:不应出现 json 模块的内部帧(虽然 fixture 里 import 了 json) + json_internal = {f for f in files if "json" in f and "decoder" in f or "json" in f and "encoder" in f} + assert not json_internal, f"默认 scope 应为 user,但出现了 json 内部帧: {json_internal}" + + +def test_scope_all_includes_library_frames(): + """--scope=all:让耗时可以下钻到 import 的包里 —— json 模块的函数也应进入结果。""" + script = os.path.join(FX, "with_imports.py") + data = _run(script, "--scope", "all") + assert data["status"] == "ok" + files = {f["file"] for f in data["functions"]} + # scope=all 时至少有一个 json 相关的文件路径进来 —— 说明 import 的包被归因了 + has_json = any("json" in f for f in files) + assert has_json, f"scope=all 应包含 json 模块帧,实际 files: {files}" + + +def test_user_side_effects_run_once(tmp_path): + """v4 单跑架构核心回归:用户脚本里的副作用只执行一次。 + + v3 时期 runner 跑两次(裸跑测 wall_time + cProfile 跑), + 用户脚本里 plot()、print()、写文件等副作用也跟着跑两次 —— 经典"plot 弹两个窗口" + bug 的根因。v4 改成单次 exec(code) under cProfile + 校准系数折算 wall-time 后, + 副作用应只发生 1 次。 + + 检测手段:让用户脚本 append 一行到文件;exec 一次就一行,跑两次就两行。 + 读 marker 文件的行数即可判断 exec 次数。 + """ + marker = tmp_path / "marker.txt" + script = tmp_path / "s.py" + # 用 raw 字符串避 Windows 反斜杠麻烦;append 模式保证两次跑不会被覆盖 + script.write_text( + "with open(r'" + str(marker) + "', 'a', encoding='utf-8') as f:\n" + " f.write('X\\n')\n", + encoding="utf-8", + ) + + # 直接 spawn runner 子进程(不走 _run helper —— 那个 helper 是从 stdout 解析 JSON + # 的,我们这次主要观察 marker 文件) + out_path = tmp_path / "result.json" + p = subprocess.run( + [sys.executable, "-m", "engine.runner", + "--script", str(script), "--out", str(out_path)], + capture_output=True, text=True, cwd=ROOT, + ) + assert p.returncode == 0, f"runner 失败:{p.stderr}" + + assert marker.exists(), "用户脚本副作用根本没执行" + lines = marker.read_text(encoding="utf-8").splitlines() + assert len(lines) == 1, ( + f"副作用被多次执行(找到 {len(lines)} 行),v4 单跑架构回归:{lines}" + ) + + # 同时验证 result.json 里 calibration 字段在 + wallTime 被校准 + result = json.loads(out_path.read_text(encoding="utf-8")) + assert result["status"] == "ok" + assert "calibration" in result and result["calibration"] is not None + assert result["calibration"]["ratio"] >= 1.0 + assert result["wallTime"]["seconds"] > 0 \ No newline at end of file diff --git a/engine/tests/test_schema.py b/engine/tests/test_schema.py new file mode 100644 index 0000000..05f8906 --- /dev/null +++ b/engine/tests/test_schema.py @@ -0,0 +1,21 @@ +import json + +from engine.schema import AnalysisResult, Environment, SCHEMA_VERSION, WallTime + + +def test_analysis_result_serializes_to_json(): + r = AnalysisResult( + schemaVersion=SCHEMA_VERSION, + environment=Environment(python="3.9.19", platform="win32", processor="x86", timerResolution=1e-7), + config={}, + status="ok", + error=None, + wallTime=WallTime(seconds=0.105, unit="s"), + functions=[], + flame=None, + ) + data = json.loads(r.to_json()) + assert data["schemaVersion"] == SCHEMA_VERSION + assert data["status"] == "ok" + assert data["wallTime"]["seconds"] == 0.105 + assert data["environment"]["python"] == "3.9.19" \ No newline at end of file diff --git a/engine/tests/test_structure.py b/engine/tests/test_structure.py new file mode 100644 index 0000000..4f6642a --- /dev/null +++ b/engine/tests/test_structure.py @@ -0,0 +1,761 @@ +import os + +import pytest + +from engine.schema import FunctionNode +from engine.structure import ( + _build_flame, + _classify_origin, + _make_frame_filter, + _norm, + _top_module, + profile_structure, +) + + +def test_structure_finds_functions(): + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + res = profile_structure(fx, hide_internal=False) + names = {f.name for f in res.functions} + assert {"leaf", "mid", "main"} <= names + leaf = next(f for f in res.functions if f.name == "leaf") + assert leaf.tottime > 0 and leaf.ncalls >= 3 + assert res.flame.value >= leaf.tottime + + +def test_structure_default_scope_is_user(): + """默认 scope=user —— 只看到用户脚本里的函数(向后兼容)。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + res = profile_structure(fx, hide_internal=False) + files = {f.file for f in res.functions} + # 只有 fixture 自身;不应出现标准库路径( / / site-packages 等) + non_user = {f for f in files if f not in (fx, "~") and not f.endswith("nested_calls.py")} + assert not non_user, f"user 模式下出现非用户脚本帧: {non_user}" + + +def test_structure_scope_all_includes_stdlib(): + """scope=all:耗时可以下钻到 import 的包里,标准库 / 第三方包里的函数也进来。 + + 注:`~` 在这里可能出现在 files 里 —— 它不再是"cProfile 内部"的同义词, + 而是 cProfile 给所有 C 扩展函数(json C accelerator / numpy C 核 / time.sleep + 等)打的文件名。这些是真实工作,scope=all 必须保留。cProfile 内部帧靠 name + 识别("_lsprof.Profiler" / "Profiler"),由 _make_frame_filter 剔除。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "with_imports.py") + res = profile_structure(fx, scope="all", hide_internal=False) + files = {f.file for f in res.functions} + # fixture 里会 import json 和 time —— 至少其中一个 stdlib 路径应被纳入 + has_stdlib = any("json" in f or "time" in f or f == "" for f in files) + assert has_stdlib, f"scope=all 应包含标准库帧,实际 files: {files}" + # cProfile 内部帧(name 含 _lsprof.Profiler 子串)必须剔除 —— 但其它 + # `~` 帧(time.sleep / json C 加速器 / builtins.exec 等真实 C 扩展)必须保留。 + cprofile_internal = [f for f in res.functions if "_lsprof.Profiler" in f.name] + assert not cprofile_internal, ( + f"cProfile 内部帧不应漏进 functions:{[(f.name, f.file) for f in cprofile_internal]}" + ) + + +def test_norm_normalizes_case_and_slashes(): + """Windows (NTFS) 上同一文件可能被报成 `C:/Foo/Bar.py` 或 `c:\\foo\\BAR.py`, + 取决于谁产生的字符串。_norm 必须把两边都规范成同一种形式才能正确比 + 对脚本路径。""" + raw = "C:/Foo/Bar.py" + expected = os.path.normcase(os.path.abspath(raw)) + assert _norm(raw) == expected + + +def test_user_frame_filter_is_case_and_slash_agnostic(): + """_make_frame_filter 对 frame.file 也要走 _norm —— 跨平台/跨斜杠风格 + 仍然能命中用户脚本。这条用例专门守回归:之前只用同一字符串匹配, + 在 Windows 上大小写或斜杠不一致就会漏掉所有函数。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + is_kept = _make_frame_filter(fx) + # 把 frame.file 里的正反斜杠互换 —— 如果原始路径里有 '\\' + swapped = fx.replace("\\", "/") if "\\" in fx else fx.replace("/", "\\") + if swapped != fx: + assert is_kept((swapped, 1, "main")) is True + + +def test_frame_filter_excludes_cprofile_internal(): + """cProfile 内部帧(_lsprof.Profiler 类成员,file='~' 且 name 含 Profiler) + 永远被剔除 —— 不论 scope=user 还是 scope=all。 + + 关键:「`~` == cProfile 内部」是错的 —— cProfile 用 `~` 表示所有 C 扩展函数 + (time.sleep / json C accelerator / numpy C 核),这些必须留下。cProfile + 自己靠 name 区分:'_lsprof.Profiler' / 'Profiler' / '' 等。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + for scope in ("user", "all"): + is_kept = _make_frame_filter(fx, scope) + # 真实 cProfile 内部 name —— 必须被过滤 + assert is_kept(("~", 0, "_lsprof.Profiler")) is False + assert is_kept(("~", 0, "Profiler")) is False + assert is_kept(("~", 0, "")) is False + + +def test_frame_filter_keeps_c_extension_frames(): + """scope=all:C 扩展函数帧(file='~' + name 是真实 builtin)必须留下 — + 否则 numpy C 核 / json C 加速器 / time.sleep 等会从结果里消失, + scope=all 失去下钻意义。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + is_kept = _make_frame_filter(fx, "all") + # C 扩展(不是 cProfile 内部)—— 必须保留 + assert is_kept(("~", 0, "")) is True + assert is_kept(("~", 0, "_default_encoder")) is True # json C accelerator + # scope=user:C 帧不会进入用户脚本 —— 也过滤掉(与历史行为一致) + is_kept_user = _make_frame_filter(fx, "user") + assert is_kept_user(("~", 0, "")) is False + + +def test_frame_filter_scope_all_keeps_any_non_internal_frame(): + """scope=all:只要不是 cProfile 内部(~)就保留 —— 用户脚本、stdlib、第三方都进。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + is_kept = _make_frame_filter(fx, "all") + # 任意"非 ~"路径都应通过 + assert is_kept(("C:/Python39/Lib/json/decoder.py", 100, "decode")) is True + assert is_kept(("/some/site-packages/pandas/core/frame.py", 1, "from_records")) is True + + +def test_top_module_user_script(): + """用户脚本(==user_script_path)→ ""。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + assert _top_module(fx, fx) == "" + + +def test_top_module_user_script_case_and_slash_agnostic(): + """_top_module 比较 user_script_path 时走 _norm —— Windows 大小写 / 斜杠差异不影响。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + swapped = fx.replace("\\", "/") if "\\" in fx else fx.replace("/", "\\") + if swapped != fx: + assert _top_module(swapped, fx) == "" + + +def test_top_module_stdlib_windows(): + """Windows 标准库:C:\\Python39\\Lib\\json\\decoder.py → json""" + assert _top_module(r"C:\Python39\Lib\json\decoder.py", "") == "json" + assert _top_module(r"C:\Python39\Lib\json\__init__.py", "") == "json" + # 多层嵌套:Lib/site-packages 这种 dev 布局也行 + assert _top_module(r"C:\Python39\Lib\site-packages\foo\bar.py", "") == "foo" + + +def test_top_module_top_level_py_strips_extension(): + """顶层 .py 文件必须剥掉扩展名 —— `Lib\\functools.py` 是模块 functools 而不是 `functools.py`。 + 之前 bug:直接返回 `parts[idx]`(即 `functools.py`),UI 分组里看到一坨文件名而不是模块名。""" + assert _top_module(r"C:\Python39\Lib\functools.py", "") == "functools" + # Linux 顶层 stdlib + assert _top_module("/usr/lib/python3.11/functools.py", "") == "functools" + # __init__ 不剥(不是 .py 后缀)—— 顶层 `Lib\json\__init__.py` 走 _strip_py 时返回 "json" + # (这里 _strip_py 不会动 __init__,但 parts[idx] 已经是 "json\__init__.py" 这种, + # 实际代码路径是先取 parts[idx] 再 _strip_py,所以 "json\__init__.py" → "json\__init__") + # 这里的关键是 module 名不是 filename-with-py-extension + result = _top_module(r"C:\Python39\Lib\functools.py", "") + assert not result.endswith(".py"), f"module 名不能带 .py 后缀,实际 {result!r}" + + +def test_top_module_stdlib_linux(): + """Linux/macOS 标准库:/usr/lib/python3.X//... → """ + assert _top_module("/usr/lib/python3.11/json/decoder.py", "") == "json" + assert _top_module( + "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/json/decoder.py", + "", + ) == "json" + + +def test_top_module_site_packages(): + """site-packages / dist-packages:标记后的第一个目录段就是包名。""" + assert _top_module( + "/usr/lib/python3.11/site-packages/numpy/core/array.py", "" + ) == "numpy" + assert _top_module( + "C:\\Python39\\Lib\\site-packages\\pandas\\core\\frame.py", "" + ) == "pandas" + # dist-packages(Debian 系) + assert _top_module( + "/usr/lib/python3.11/dist-packages/requests/api.py", "" + ) == "requests" + + +def test_top_module_frozen(): + """frozen / built-in / 虚拟帧 → 整段保留作为标签。""" + assert _top_module("", "") == "" + assert _top_module("", "") == "" + assert _top_module("", "") == "" + + +def test_top_module_c_extension(): + """`~` 在 cProfile 里代表所有 C 扩展函数(numpy C 核 / json 加速器 / + time.sleep 等)。这些 frame 现在 scope=all 会留下(不再被一刀切),需要给个 + 有意义的 module 标签 —— 用 "" 跟 _classify_origin 的 builtin + 字段保持一致。""" + assert _top_module("~", "") == "" + # user_script_norm 也要匹配上 user 脚本 —— 防御性 + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + assert _top_module("~", fx, _norm(fx)) == "" + + +def test_top_module_fallback_dirname(): + """未匹配任何标记 → 取倒数第二段目录。""" + # 例如:".../someproj/src/utils/helper.py" → "utils" + assert _top_module("/path/to/someproj/src/utils/helper.py", "") == "utils" + + +def test_profile_structure_populates_module(): + """profile_structure 给每条 fn 算 module:fixture 是用户脚本,全部 。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + res = profile_structure(fx, hide_internal=False) + assert res.functions + modules = {f.module for f in res.functions} + assert modules == {""}, f"默认 scope 下应只有 ,实际 {modules}" + + +def test_profile_structure_scope_all_populates_real_modules(): + """scope=all:fn.module 应来自真实的 stdlib / 第三方包。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "with_imports.py") + res = profile_structure(fx, scope="all", hide_internal=False) + modules = {f.module for f in res.functions} + # fixture 里 import 了 json / time,至少其中一个 stdlib 应出现 + assert "" in modules, f"用户脚本应在模块集合里,实际 {modules}" + assert any(m in ("json", "time", "", "") for m in modules), ( + f"scope=all 应有 stdlib 模块,实际 {modules}" + ) + + +def _mk_fn(name: str, module: str, tottime: float) -> FunctionNode: + """测试用 FunctionNode 工厂 —— 关注 module / tottime,其他字段填占位。""" + return FunctionNode( + id=f"f.py:1:{name}", + file="f.py", + line=1, + name=name, + cumtime=tottime, + tottime=tottime, + ncalls=1, + percallTot=tottime, + module=module, + ) + + +def test_build_flame_single_module_stays_flat(): + """单模块时(scope=user 默认情况)保持扁平 —— 同名兄弟不会被 module + 节点挤占,截断阈值不变,向后兼容。""" + fns = [ + _mk_fn("main", "", 0.5), + _mk_fn("mid", "", 0.3), + _mk_fn("leaf", "", 0.2), + ] + flame = _build_flame(fns, total=1.0) + assert flame.name == "root" + assert flame.value == 1.0 + # 扁平:root.children 是函数名(不是 module) + assert [c.name for c in flame.children] == ["main", "mid", "leaf"] + # 每个函数 tile 没有 children(不会再下钻一层) + for child in flame.children: + assert child.children == [] + + +def test_build_flame_multi_module_groups_by_module(): + """多模块(scope=all 且 import 多个包):root.children 是 module 节点, + 每个 module 节点的 children 是该模块下的函数。""" + fns = [ + _mk_fn("main", "", 0.5), + _mk_fn("decode", "json", 0.3), + _mk_fn("loads", "json", 0.2), + _mk_fn("sleep", "time", 0.1), + ] + flame = _build_flame(fns, total=1.1) + assert flame.name == "root" + # module 节点按总 tottime 降序:(0.5) > json(0.5) > time(0.1) + # 和 json 都是 0.5,排序稳定时顺序取决于字典遍历顺序 —— 这里不强制 + # 顺序,只验证排序结果一致(用 sorted() 拿到一组) + module_nodes = flame.children + assert len(module_nodes) == 3 + # 第一个必须是最大的 (0.5 严格大于 json 的 0.5,因为总和 tie-break + # 由 sorted 的 stable 行为兜底: 在 fns 里排前面 → 同值时排前) + assert module_nodes[0].name == "" + assert module_nodes[0].value == pytest.approx(0.5) + # json 模块 value = 0.3 + 0.2 = 0.5 + json_node = next(c for c in module_nodes if c.name == "json") + assert json_node.value == pytest.approx(0.5) + assert sorted(c.name for c in json_node.children) == ["decode", "loads"] + # time 模块只有 sleep + time_node = next(c for c in module_nodes if c.name == "time") + assert time_node.value == pytest.approx(0.1) + assert [c.name for c in time_node.children] == ["sleep"] + + +def test_build_flame_empty_returns_empty_root(): + """没函数时不爆 —— root.value=0、children=空列表。""" + flame = _build_flame([], total=0.0) + assert flame.name == "root" + assert flame.value == 0.0 + assert flame.children == [] + + +# --- origin 分类(v3 新增) ----------------------------------------------------- + +def test_classify_origin_user_script_matches_norm(): + """用户脚本:路径(_norm 后)与 user_script_path 一致 → user。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + assert _classify_origin(fx, fx, "") == "user" + swapped = fx.replace("\\", "/") if "\\" in fx else fx.replace("/", "\\") + if swapped != fx: + assert _classify_origin(swapped, fx, "") == "user" + + +def test_classify_origin_frozen_and_builtin(): + """frozen / built-in 虚拟帧 → frozen / builtin。""" + assert _classify_origin("", "/x.py", "") == "frozen" + assert _classify_origin("", "/x.py", "") == "builtin" + # 其它 <...> 兜底 other + assert _classify_origin("", "/x.py", "") == "other" + + +def test_classify_origin_stdlib_via_sys_stdlib_module_names(): + """module_name 在 sys.stdlib_module_names 里 → stdlib(权威路径)。""" + # json / os / re / sys / pathlib 都肯定在 stdlib 里 + assert _classify_origin("C:/Python311/Lib/json/decoder.py", "/x.py", "json") == "stdlib" + assert _classify_origin("/usr/lib/python3.11/pathlib/__init__.py", "/x.py", "pathlib") == "stdlib" + + +def test_classify_origin_third_party_site_packages(): + """site-packages / dist-packages 下的包 → third_party(即使包名可能撞车)。""" + assert _classify_origin( + "/usr/lib/python3.11/site-packages/numpy/core/array.py", "/x.py", "numpy" + ) == "third_party" + assert _classify_origin( + "C:\\Python39\\Lib\\site-packages\\pandas\\core\\frame.py", "/x.py", "pandas" + ) == "third_party" + # dist-packages(Debian 系) + assert _classify_origin( + "/usr/lib/python3.11/dist-packages/requests/api.py", "/x.py", "requests" + ) == "third_party" + + +def test_classify_origin_stdlib_path_heuristic_fallback(): + """sys.stdlib_module_names 漏判时,只要路径真的在 sysconfig 给的 stdlib 根 + 下,就该归为 stdlib —— 即使模块名未知。 + + 之前 bug(M5):只看路径里含 "/Lib/" 子串就当 stdlib,结果用户项目里 + /home/x/myproject/Lib/foo.py 被误认成 stdlib。现在锚到 sysconfig 的真根。 + """ + import sysconfig + + real_stdlib = sysconfig.get_paths().get("stdlib") or os.path.join(os.sep, "Lib") + # 构造一个 fake 包路径,放在真实 stdlib 根下面 —— 必须真在那个目录里 + fake_pkg = os.path.join(real_stdlib, "_weirdstdlib_pkg_for_test") + # 文件不存在也没事 —— _is_under_real_stdlib 只比对 abspath,不要求文件真存在 + assert _classify_origin(fake_pkg, "/x.py", "_weirdstdlib_pkg_for_test") == "stdlib" + + # 反向用例:用户在 /home/x/myproject/Lib/foo.py 下,即使路径含 "/Lib/" 也不该 + # 误判成 stdlib —— 必须真的在 sysconfig 那个根下面 + assert _classify_origin( + "/home/user/myproject/Lib/foo.py", "/x.py", "foo" + ) == "other" + assert _classify_origin( + "/home/user/myproject/lib/python3.11/foo.py", "/x.py", "foo" + ) == "other" + + +def test_classify_origin_unknown_path_is_other(): + """未匹配的奇怪路径 → other。""" + assert _classify_origin( + "/path/to/someproj/src/utils/helper.py", "/x.py", "utils" + ) == "other" + + +def test_profile_structure_populates_origin(): + """profile_structure 给每条 fn 算 origin:fixture 是用户脚本,全部 user。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + res = profile_structure(fx, hide_internal=False) + origins = {f.origin for f in res.functions} + assert origins == {"user"}, f"默认 scope 下 origin 应全是 user,实际 {origins}" + + +def test_profile_structure_scope_all_classifies_by_origin(): + """scope=all:用户脚本帧归 user,import 的 json 帧归 stdlib —— 至少这两个 origin 都有。""" + fx = os.path.join(os.path.dirname(__file__), "fixtures", "with_imports.py") + res = profile_structure(fx, scope="all", hide_internal=False) + origins = {f.origin for f in res.functions} + # 用户脚本帧必有 + assert "user" in origins + # fixture 显式 import json → 必有 stdlib 帧 + assert "stdlib" in origins, f"json import 后应有 stdlib 帧,实际 origins: {origins}" + # 没 origin 字段缺失 / 其它 origin 兜底值(如 cprofile)混进来的情况 + assert "cprofile" not in origins, f"cProfile 内部帧不应漏进 origin,实际 {origins}" + # 所有 origin 必须是已知值之一 + KNOWN = {"user", "stdlib", "third_party", "builtin", "frozen", "other"} + assert origins <= KNOWN, f"未知 origin 混进来:{origins - KNOWN}" + + + +# ────────────────────────────────────────────────────────────────────────────── +# v5 噪声过滤 —— 测试代码 + tottime=0 默认剔除 +# ────────────────────────────────────────────────────────────────────────────── +# +# 用户场景:scope=all 时 cProfile 报告里夹带大量测试代码(numpy.tests.test_xxx / +# pytest fixture / unittest runner 等)和 tottime=0 的内部调用展开(typing / inspect / +# functools 等)。这些不是优化目标,默认应该剔除,result.json 一开始就是干净的。 +# 用户原话:"软件内部的测试部分默认百分百过滤掉,不在统计范围内容"。 + + +from engine.structure import _is_internal_test + + +def test_is_internal_test_recognizes_pytest_tests_dir(): + # /tests/ /test/ /__tests__/ 目录下的函数 —— pytest 标准约定 + assert _is_internal_test("helper", "/p/foo/tests/x.py", "foo.tests") + assert _is_internal_test("helper", "/p/foo/test/x.py", "foo.test") + assert _is_internal_test("helper", "/p/foo/__tests__/x.py", "foo.__tests__") + + +def test_is_internal_test_recognizes_pytest_file_naming(): + # test_xxx.py / xxx_test.py —— pytest 文件命名约定 + assert _is_internal_test("f", "/p/test_foo.py", "foo") + assert _is_internal_test("f", "/p/foo_test.py", "foo") + assert _is_internal_test("f", "/p/tests/test_bar.py", "foo") + + +def test_is_internal_test_recognizes_tests_module_path(): + # 模块路径就是 tests / test / __tests__,或它们的子模块 + assert _is_internal_test("f", "p/x.py", "tests") + assert _is_internal_test("f", "p/x.py", "test") + assert _is_internal_test("f", "p/x.py", "__tests__") + assert _is_internal_test("f", "p/x.py", "tests.foo") + assert _is_internal_test("f", "p/x.py", "test.bar") + assert _is_internal_test("f", "p/x.py", "__tests__.baz") + + +def test_is_internal_test_hardcoded_engine_whitelist(): + # _pyrof_calib —— 引擎内部白名单,任何情况下都算内部 + assert _is_internal_test("_pyrof_calib", "/p/anything.py", "") + assert _is_internal_test("_pyrof_calib", "/p/", "weird_module") + + +def test_is_internal_test_does_not_match_function_names(): + # 关键:只看语境不看函数名 —— 函数名匹配太容易误伤 + assert not _is_internal_test("test_helper", "", "a.py") + assert not _is_internal_test("test_visualization", "", "/p/app/main.py") + assert not _is_internal_test("TestFoo.test_foo", "", "/p/myapp/svc.py") + assert not _is_internal_test("MyClass.test_count", "", "a.py") + assert not _is_internal_test("UserService.test_login", "", "a.py") + + +def test_is_internal_test_does_not_match_test_substring_in_module(): + # testing / dataset / contest 等含 test 子串的合法模块名 + assert not _is_internal_test("f", "p/x.py", "testing") + assert not _is_internal_test("f", "p/x.py", "dataset") + assert not _is_internal_test("f", "p/x.py", "contest") + + +def test_is_internal_test_does_not_match_standalone_test_py(): + # test.py / tests.py —— 项目入口常这么命名 + assert not _is_internal_test("main", "", "/p/test.py") + assert not _is_internal_test("main", "", "/p/tests.py") + + +def test_is_internal_test_does_not_match_tests_subdir_non_test_files(): + """回归测试:tests/ 子目录下的非测试文件不应当成测试代码剔除。 + + 之前用宽匹配 `[\\/](tests?|__tests__)[\\/]` —— 任何含 `/tests/` 的路径都中招, + 包括 `tests/fixtures/x.py` 这种测试数据夹具。结果用户脚本只要住在带 tests/ + 子目录的路径下,functions 表直接被过滤成 0 行。 + + 收紧到「tests/ 后面紧接文件名,不能再有 / 子目录」后: + - tests/foo.py → 算测试代码 (foo.py 直接在 tests/ 下) + - tests/fixtures/foo.py → 不算 (fixtures 是子目录,foo.py 不挂在 tests/ 下) + - tests/data/sample.py → 不算 (data 是子目录) + - tests/__init__.py / conftest.py → 仍走模块兜底命中 (module == "tests") + """ + # 子目录下的非测试文件 —— 不应被过滤 + assert not _is_internal_test("f", "/p/tests/fixtures/x.py", "fixtures") + assert not _is_internal_test("f", "/p/foo/tests/data/sample.py", "data") + assert not _is_internal_test("f", "/p/mine/tests/integration/helper.py", "integration") + # tests/ 直接下面的文件 —— 仍走 dir 兜底命中 + assert _is_internal_test("f", "/p/tests/foo.py", "tests") + + +def test_profile_structure_default_filters_zero_time_and_test_code(): + # scope=all + 默认过滤:测试代码 + tottime=0 不进 functions[] + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + res = profile_structure(fx, scope="all") + assert all(f.tottime > 0 for f in res.functions), ( + f"默认过滤下不应有 tottime=0 的帧: {[f.name for f in res.functions if f.tottime == 0]}" + ) + for f in res.functions: + assert not _is_internal_test(f.name, f.file, f.module), ( + f"默认过滤下不应有测试代码帧: {f.name} ({f.module})" + ) + + +def test_profile_structure_hide_internal_false_keeps_more(): + # hide_internal=False 时不过滤 —— result 含全量数据 + fx = os.path.join(os.path.dirname(__file__), "fixtures", "nested_calls.py") + res_full = profile_structure(fx, scope="all", hide_internal=False) + res_filtered = profile_structure(fx, scope="all", hide_internal=True) + assert len(res_full.functions) >= len(res_filtered.functions) + + +# ────────────────────────────────────────────────────────────────────────────── +# v6 stdlib 间接调用链过滤 —— typing/inspect/functools 等默认剔除 +# ────────────────────────────────────────────────────────────────────────────── +# +# 用户场景:scope=all 时 numpy 这类第三方包会通过类型注解 / importlib 钩子触发 +# typing / inspect / functools / re / _py_warnings / ctypes / enum 等几十个 stdlib +# 内部帧。这些帧 tottime 通常在 1µs~1ms 之间(高于 cProfile 量化地板但低于噪声线), +# 既不是优化目标也不是用户写的代码。默认剔除,只保留用户脚本里显式 import 的 +# stdlib 模块帧 —— 「用户能看到『自己 import 了什么』,而不是『numpy 的依赖图』」。 + +from engine.structure import _extract_user_imports + + +def test_extract_user_imports_simple_import(): + src = "import json\nimport os\nx = 1\n" + assert _extract_user_imports(src) == {"json", "os"} + + +def test_extract_user_imports_from_import(): + src = "from collections import OrderedDict\nfrom os.path import join\nx = 1\n" + # 顶级包名进集合,不展开子模块 + assert _extract_user_imports(src) == {"collections", "os"} + + +def test_extract_user_imports_dotted(): + src = "import numpy as np\nimport a.b.c\nx = 1\n" + # 取顶级包名,as 别名不进集合 + assert _extract_user_imports(src) == {"numpy", "a"} + + +def test_extract_user_imports_inside_function_ignored(): + """函数/类内部的 import 不算 —— 它们是延迟副作用,不是「我需要分析的目标」。""" + src = "import json\ndef f():\n import typing\n return typing\n" + assert _extract_user_imports(src) == {"json"} + + +def test_extract_user_imports_relative_skipped(): + """from . import x 是相对导入 —— 依附于已知包,跳过。""" + src = "from . import utils\nimport json\n" + assert _extract_user_imports(src) == {"json"} + + +def test_extract_user_imports_syntax_error_returns_empty(): + # 语法错误的源码(走不到 profile_and_measure,这里只是防御) + assert _extract_user_imports("def f(:\n pass") == set() + + +def test_profile_structure_default_filters_indirect_stdlib(tmp_path): + """scope=all 默认应剔除间接 stdlib 调用链 —— 用户显式 import 的保留,其他剔除。 + + fixture 设计:用户只 import json + os.path + collections,显式调用 list() + + json.dumps + os.path.join。间接触发 typing/inspect/functools/_py_warnings 的 + 帧(来自 cProfile 内部 / 解释器启动 / json 自身依赖)不应出现在 result 里。 + """ + script = tmp_path / "user.py" + script.write_text( + "import json\n" + "import os.path\n" + "from collections import OrderedDict\n" + "x = json.dumps(OrderedDict([('a', 1)]))\n" + "y = os.path.join('a', 'b')\n" + "z = list(range(10))\n", + encoding="utf-8", + ) + res = profile_structure(str(script), scope="all") + modules = {f.module for f in res.functions} + # 用户显式 import 的 stdlib 模块保留 + assert "json" in modules, f"json 应保留(用户显式 import),实际 {modules}" + # 间接触发的 stdlib 内部模块剔除 + leak = modules & {"typing", "inspect", "functools", "_py_warnings", "re", + "annotationlib", "ctypes", "enum", "warnings"} + assert not leak, f"默认应剔除间接 stdlib 调用链,但 {leak} 漏进来了" + + +def test_profile_structure_hide_internal_false_keeps_indirect_stdlib(tmp_path): + """--no-hide-internal:间接 stdlib 帧也保留,排查调用栈时用得到。 + + fixture 用 traceback.print_exc() —— 它在 CPython 3.11+ 实现里会触发 inspect / + annotationlib / linecache / tokenize / dataclasses / enum / reprlib / textwrap / + ast / codeop / contextlib 等十几 stdlib 内部模块(都是 Python 层面的,而非 C)。 + fixture 只显式 import traceback,其他都是「间接调用链」—— hide_internal=True + 时它们被剔除,hide_internal=False 时重新出现。对照组验证 v6 过滤真的生效。 + """ + script = tmp_path / "user.py" + script.write_text( + "import traceback\n" + "def f():\n" + " return 1 / 0\n" + "for i in range(50):\n" + " try:\n" + " f()\n" + " except Exception:\n" + " traceback.print_exc()\n", + encoding="utf-8", + ) + res_on = profile_structure(str(script), scope="all", hide_internal=True) + res_off = profile_structure(str(script), scope="all", hide_internal=False) + on_modules = {f.module for f in res_on.functions} + off_modules = {f.module for f in res_off.functions} + # 关掉过滤时应恢复间接 stdlib 模块 —— inspect 是 traceback 的核心依赖,必然出现 + extra = off_modules - on_modules + stdlib_noise = {"inspect", "annotationlib", "linecache", "tokenize", + "dataclasses", "enum", "reprlib", "textwrap", "ast", + "codeop", "contextlib"} + assert extra & stdlib_noise, ( + f"--no-hide-internal 应恢复 traceback 的间接 stdlib 调用链帧," + f"但 on vs off 的 module 差异 {extra} 里没出现 {stdlib_noise}。" + f"v6 过滤可能没生效,或者测试 fixture 没触发干扰。" + ) + # hide_internal=True 时不应漏出 inspect / annotationlib / linecache 等间接帧 + leak = on_modules & stdlib_noise + assert not leak, ( + f"hide_internal=True 时不应漏出间接 stdlib 噪声,但 {leak} 出现了。" + f"这意味着 fixture 没显式 import 它们,却进了 result.json。" + ) + + +# ────────────────────────────────────────────────────────────────────────────── +# v7 「用户直接调用」过滤 —— 只保留用户代码 + 用户直接调用的 import 入口 +# ────────────────────────────────────────────────────────────────────────────── +# +# 用户原话:「只要代码的本身和import 调用的耗时统计,其他的不需要」/「目前好像 +# 仍然统计到内部测试的代码了,不合理」—— 之前的间接 stdlib 过滤解决了 stdlib 噪声, +# 但 487 帧里仍有 numpy 内部 250+ 帧、 C 函数 131 帧、 importlib +# 90 帧等「不是用户调用的」帧。v7 把规则收紧到「caller 链含用户帧才算 import 调用」, +# 并加 cumtime >= 1µs 闸门(剔除纯注册/分发占位函数如 _mean_dispatcher)。 +# +# 测试矩阵: +# - 用户写的函数 → 保留 +# - 用户直接调用的第三方函数 → 保留 +# - 用户调用的第三方函数内部又调的第三方函数 → 剔除 +# - 用户调用的第三方函数内部又调的 stdlib(非用户 import)→ 剔除 +# - C 函数 → 剔除(不是 import 调用) +# - importlib → 剔除(不是 import 调用) +# - 用户没 import 的 stdlib 帧 → 剔除 + + +def test_profile_structure_default_keeps_only_user_and_direct_calls(tmp_path): + """默认过滤:只保留用户代码 + 用户直接调用的 third-party / 用户 import 的 stdlib。 + + fixture 用 json + 一个明显干活的 helper(json.dumps 大循环),结果应只有: + - 用户模块(脚本本身) + - 用户函数 helper + - 用户直接调的 json.dumps + 不应出现:json.decoder / json.scanner / re / typing / inspect 等内部帧。 + """ + script = tmp_path / "user.py" + script.write_text( + "import json\n" + "def helper(data):\n" + " return json.dumps(data)\n" + "result = helper({'a': list(range(100))})\n", + encoding="utf-8", + ) + res = profile_structure(str(script), scope="all") + modules = {f.module for f in res.functions} + names = {f.name for f in res.functions} + # 1) 用户脚本本身 + assert "" in modules, f"用户代码应保留,实际 modules: {modules}" + assert "" in names and "helper" in names + # 2) 用户直接调用的 json.dumps + assert "json" in modules, f"json 是用户显式 import 且被直接调用,应保留" + assert "dumps" in names, "用户直接调用 json.dumps,应保留" + # 3) json 内部帧被剔除(json.decoder / encoder / scanner 等) + json_internal_leak = {"decode", "encode", "scanstring", "JSONDecoder", + "JSONEncoder", "__init__"} + bad = json_internal_leak & names + assert not bad, f"json 内部帧不应漏出,但 {bad} 出现了" + # 4) stdlib 内部展开(typing/inspect/re 等)不应出现 + leak = modules & {"typing", "inspect", "re", "_py_warnings", "functools"} + assert not leak, f"间接 stdlib 噪声应剔除,但 {leak} 漏进了" + + +def test_profile_structure_default_drops_indirect_nested_calls(tmp_path): + """用户调用 numpy.mean,但 numpy.mean 内部调的 numpy._core._methods 不应出现。 + + 这是 v7 的核心语义:只看「直接被用户调用」,不传递。哪怕是同包内部的辅助函数, + 只要不是用户写的,就不在 result 里。 + """ + script = tmp_path / "user.py" + script.write_text( + "import numpy as np\n" + "x = np.array([1, 2, 3, 4, 5])\n" + "y = np.mean(x)\n", + encoding="utf-8", + ) + res = profile_structure(str(script), scope="all") + names = {f.name for f in res.functions} + # 用户直接调用的入口应保留 + assert "mean" in names, f"用户直接调用 np.mean,应保留,实际 names: {names}" + # numpy 内部辅助方法不应出现(_methods / fromnumeric 之类的子帧) + # 注:numpy.mean 实际上调用 numpy._core.fromnumeric.mean,我们想要的是 fromnumeric.mean + # 出现在 result 里(因为它是 user-direct call 的代理),但其内部又调了 _methods + # 这种,不该出现。 + bad = {"_methods", "_sum", "_mean", "_std", "_var"} + bad &= names + assert not bad, f"numpy 内部辅助函数不应漏出,但 {bad} 出现了" + + +def test_profile_structure_default_drops_builtin_and_frozen(tmp_path): + """ C 函数 / importlib 即使被用户代码用到也不保留。 + + 用户原意是「import 调用」,builtin C 函数(len / dict.keys / numpy C 核等) + 和 frozen import machinery 不属于「import 调用」—— 即使技术上被用户代码调到。 + """ + script = tmp_path / "user.py" + script.write_text( + "import numpy as np\n" + "data = [1, 2, 3, 4, 5]\n" + "x = np.array(data)\n" + "y = len(data)\n" + "z = sorted(data)\n", + encoding="utf-8", + ) + res = profile_structure(str(script), scope="all") + modules = {f.module for f in res.functions} + # 完全不应该出现 —— 不是 import 调用 + assert "" not in modules, ( + f" C 函数不应作为 import 调用保留,实际 modules: {modules}" + ) + # 也不应出现 + assert "" not in modules, ( + f" importlib 帧不应保留,实际 modules: {modules}" + ) + + +def test_profile_structure_default_keeps_user_function_with_no_args(tmp_path): + """用户写的不带参数的函数(只调用 import)仍应保留 —— 用户代码一律保留。 + + 这是 v7 的关键 invariant:用户帧不管 tottime/cumtime 多小,不管有没有调用 + 别的东西,都进 result —— 这是「代码本身」的定义。 + """ + script = tmp_path / "user.py" + script.write_text( + "import json\n" + "def tiny():\n" + " return json.dumps({})\n" + "tiny()\n", + encoding="utf-8", + ) + res = profile_structure(str(script), scope="all") + user_fns = {f.name for f in res.functions if f.module == ""} + assert "tiny" in user_fns, f"用户函数 tiny 应保留,实际 user fns: {user_fns}" + + +def test_profile_structure_default_cumtime_floor_drops_dispatcher(tmp_path): + """cumtime < 1µs 的 entry point 视为注册期/分发期占位 → 剔除。 + + numpy 在用户 import 时会触发 _mean_dispatcher / _std_dispatcher 等纯注册函数 + (cProfile 把 caller 记成 ,但 cumtime 几乎为 0)。这些不是真正的 + 「用户 import 调用」,默认剔除。 + """ + script = tmp_path / "user.py" + script.write_text( + "import numpy as np\n" + "y = np.mean([1, 2, 3])\n", + encoding="utf-8", + ) + res = profile_structure(str(script), scope="all") + # _mean_dispatcher 是注册期占位,cumtime 应 < 1µs + dispatcher_frames = [f for f in res.functions if "_dispatcher" in f.name] + assert not dispatcher_frames, ( + f"_dispatcher 注册占位函数应剔除(它们是 import 期触发的分发器," + f"cumtime < 1µs),但 {[(f.name, f.cumtime) for f in dispatcher_frames]} 出现了" + ) diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..4bae4d4 Binary files /dev/null and b/icon.ico differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e24e8e0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10589 @@ +{ + "name": "python-profiler-visualizer", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "python-profiler-visualizer", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@monaco-editor/react": "^4.6.0", + "monaco-editor": "^0.52.0" + }, + "devDependencies": { + "@playwright/test": "^1.46.0", + "@testing-library/jest-dom": "^6.4.8", + "@testing-library/react": "^16.0.1", + "@types/node": "^20.14.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.19", + "electron": "^31.3.1", + "electron-builder": "^24.13.3", + "electron-vite": "^2.3.0", + "eslint": "^8.57.1", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^4.6.2", + "jsdom": "^24.1.1", + "postcss": "^8.4.40", + "prettier": "^3.9.6", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwindcss": "^3.4.7", + "typescript": "^5.5.4", + "vite": "^5.3.5", + "vitest": "^2.0.5" + }, + "engines": { + "node": ">=20", + "npm": ">=10" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.2.1.tgz", + "integrity": "sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.0.5.tgz", + "integrity": "sha512-k9ZzUQtamSoweGQDV2jILiRIHUu7lYlJ3c6IEmjv1hC17rclE+eb9U+f6UFlOOETo0JzY1HNlXy4YOlCvl+Lww==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/universal": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz", + "integrity": "sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.1", + "@malept/cross-spawn-promise": "^1.1.0", + "debug": "^4.3.1", + "dir-compare": "^3.0.0", + "fs-extra": "^9.0.1", + "minimatch": "^3.0.4", + "plist": "^3.0.4" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@monaco-editor/loader": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/@monaco-editor/loader/-/loader-1.7.0.tgz", + "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmmirror.com/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmmirror.com/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmmirror.com/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", + "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.12.tgz", + "integrity": "sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/app-builder-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz", + "integrity": "sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.13.3.tgz", + "integrity": "sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.2.1", + "@electron/osx-sign": "1.0.5", + "@electron/universal": "1.5.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chromium-pickle-js": "^0.2.0", + "debug": "^4.3.4", + "ejs": "^3.1.8", + "electron-publish": "24.13.1", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "minimatch": "^5.1.1", + "read-config-file": "6.3.2", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "24.13.3", + "electron-builder-squirrel-windows": "24.13.3" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/archiver-utils/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==", + "dev": true, + "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/archiver-utils/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==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/archiver-utils/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==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.3", + "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.5.3.tgz", + "integrity": "sha512-bJRzflk8GgE4JX+iZNEwz9f9p460NCHnU7bd+CZ9vIjIlZuTkt6F3WSl2oNO8StZBFx17nLEsiQ6H2wcZiY7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001805", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", + "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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.10.43", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "optional": true + }, + "node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "dev": true, + "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" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "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==", + "dev": true, + "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-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.13.1.tgz", + "integrity": "sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "4.0.0", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz", + "integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/builder-util/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "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.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "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==", + "dev": true, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001805", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "dev": true, + "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" + } + ] + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": 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/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-file-ts": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.6.tgz", + "integrity": "sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.10", + "typescript": "^5.3.3" + } + }, + "node_modules/config-file-ts/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "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-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "optional": true + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dir-compare": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz", + "integrity": "sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal": "^1.0.0", + "minimatch": "^3.0.4" + } + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dmg-builder": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz", + "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "peer": true + }, + "node_modules/dotenv": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", + "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "31.7.7", + "resolved": "https://registry.npmmirror.com/electron/-/electron-31.7.7.tgz", + "integrity": "sha512-HZtZg8EHsDGnswFt0QeV8If8B+et63uD6RJ7I4/xhcXqmTIbI08GoubX/wm+HdY0DwcuPe1/xsgqpmYvjdjRoA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^20.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.13.3.tgz", + "integrity": "sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "dmg-builder": "24.13.3", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "read-config-file": "6.3.2", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-24.13.3.tgz", + "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "24.13.3", + "archiver": "^5.3.1", + "builder-util": "24.13.1", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-publish": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz", + "integrity": "sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.391", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.391.tgz", + "integrity": "sha512-YmCu4856jkgKT1Nh6fwRdeVrM6Ydf/fBnq51tpmSfX+jOcUMTxh31yH6hjKScRenhB2oDSvA9oooxcpjogPeig==", + "dev": true + }, + "node_modules/electron-vite": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/electron-vite/-/electron-vite-2.3.0.tgz", + "integrity": "sha512-lsN2FymgJlp4k6MrcsphGqZQ9fKRdJKasoaiwIrAewN1tapYI/KINLdfEL7n10LuF0pPSNf/IqjzZbB5VINctg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "cac": "^6.7.14", + "esbuild": "^0.21.5", + "magic-string": "^0.30.10", + "picocolors": "^1.0.1" + }, + "bin": { + "electron-vite": "bin/electron-vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@swc/core": "^1.0.0", + "vite": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + } + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "optional": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "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/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=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==", + "dev": true, + "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-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "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.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmmirror.com/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "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==", + "dev": true, + "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": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "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-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^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.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "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-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "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.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "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==", + "dev": true, + "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-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "24.1.3", + "resolved": "https://registry.npmmirror.com/jsdom/-/jsdom-24.1.3.tgz", + "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", + "dev": true, + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lazystream/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==", + "dev": true, + "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/lazystream/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==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lazystream/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==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/monaco-editor": { + "version": "0.52.2", + "resolved": "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.52.2.tgz", + "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmmirror.com/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "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==", + "dev": true, + "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/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/plist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", + "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.9.10", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.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==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-config-file": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", + "integrity": "sha512-M80lpCjnE6Wt6zb98DoW8WHR09nzMSpu8XHtPkiTHrJ5Az9CybfeQhTJ8D7saeBHpGhLPIVyA8lcL6ZmdKwY6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-file-ts": "^0.2.4", + "dotenv": "^9.0.2", + "dotenv-expand": "^5.1.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.0", + "lazy-val": "^1.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "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/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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", + "peer": true + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "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/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "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.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi/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==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": 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==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "optional": true + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "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/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "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" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "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/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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/wrap-ansi-cjs": { + "name": "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==", + "dev": true, + "license": "MIT", + "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/wrap-ansi-cjs/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "dev": 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/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ac118ac --- /dev/null +++ b/package.json @@ -0,0 +1,83 @@ +{ + "name": "python-profiler-visualizer", + "version": "0.1.0", + "description": "粘贴 Python 代码,科学地可视化各部分耗时并给出可优化点", + "main": "out/main/index.js", + "author": "guanjihuan", + "license": "MIT", + "scripts": { + "dev": "electron-vite dev", + "build": "electron-vite build", + "start": "electron-vite preview", + "typecheck": "tsc --noEmit", + "lint": "eslint . --ext .ts,.tsx,.cjs", + "lint:fix": "eslint . --ext .ts,.tsx,.cjs --fix", + "format": "prettier --write \"**/*.{ts,tsx,css,json,md}\"", + "format:check": "prettier --check \"**/*.{ts,tsx,css,json,md}\"", + "test": "vitest run", + "test:watch": "vitest", + "fixture:golden": "node scripts/regen-golden.mjs", + "pretest:e2e": "electron-vite build", + "test:e2e": "playwright test", + "release": "npm run build && electron-builder --win --x64" + }, + "engines": { + "node": ">=20", + "npm": ">=10" + }, + "build": { + "appId": "com.guanjihuan.python-profiler-visualizer", + "productName": "Python Profiler Visualizer", + "directories": { + "output": "release" + }, + "files": [ + "out/**/*", + "package.json", + "icon.ico" + ], + "win": { + "target": [ + { + "target": "nsis", + "arch": [ + "x64" + ] + } + ], + "icon": "icon.ico" + } + }, + "dependencies": { + "@monaco-editor/react": "^4.6.0", + "monaco-editor": "^0.52.0" + }, + "devDependencies": { + "@playwright/test": "^1.46.0", + "@testing-library/jest-dom": "^6.4.8", + "@testing-library/react": "^16.0.1", + "@types/node": "^20.14.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.19", + "electron": "^31.3.1", + "electron-builder": "^24.13.3", + "electron-vite": "^2.3.0", + "eslint": "^8.57.1", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^4.6.2", + "jsdom": "^24.1.1", + "postcss": "^8.4.40", + "prettier": "^3.9.6", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwindcss": "^3.4.7", + "typescript": "^5.5.4", + "vite": "^5.3.5", + "vitest": "^2.0.5" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..69414f4 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, devices } from '@playwright/test' + +/** + * Playwright 配置。 + * + * 几个值得说明的选择: + * - fullyParallel: false + workers: 1:每个测试启动一个完整 Electron 实例,几百兆内存。 + * 真机 CI 上多 worker 容易 OOM 也不见得更快。 + * - reporter: [['list'], ['html', { open: 'never' }]]:list 走 stdout 让人看,html + * 写 report/ 让人事后翻(CI 上传 artifact 用)。 + * - timeout: 90s:引擎跑 Python 最坏 timeout 30s + 启动开销;hot path 单测。 + * - use.expect.timeout: 10s:默认 expect 超时。但有 call graph worker 那种「真的 + * 要算 5 秒」的断言会自己覆盖成 30s。 + */ +export default defineConfig({ + testDir: './e2e', + timeout: 90_000, + fullyParallel: false, + workers: 1, + reporter: [['list'], ['html', { outputFolder: 'playwright-report', open: 'never' }]], + use: { + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + video: 'retain-on-failure' + // expect 超时不在顶层设(@playwright/test v1.46 不支持); + // 各处按需在 expect().toBeVisible({ timeout: ... }) 里指定 + }, + projects: [ + { + name: 'electron', + use: { ...devices['Desktop Chrome'] } + } + ] +}) diff --git a/postcss.config.cjs b/postcss.config.cjs new file mode 100644 index 0000000..85f717c --- /dev/null +++ b/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +} diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..f238dd3 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +testpaths = engine/tests diff --git a/scripts/regen-golden.mjs b/scripts/regen-golden.mjs new file mode 100644 index 0000000..f4575b5 --- /dev/null +++ b/scripts/regen-golden.mjs @@ -0,0 +1,76 @@ +#!/usr/bin/env node +/** + * 重新生成 src/renderer/src/fixtures/golden.json。 + * + * golden.json 是冻结的真实引擎输出,被测试套件当夹具用。此前没有任何东西 + * 能重新生成它 —— schema 一改,夹具就悄悄过期,而依赖它的测试照样全绿。 + * + * 用法:node scripts/regen-golden.mjs (或 npm run fixture:golden) + * PYTHON=python3.9 npm run fixture:golden 指定解释器 + * + * 注意:environment(python 版本 / platform / processor / timerResolution)和 + * wallTime 的具体数字必然随机器和每次运行而变,所以"重新生成后应与仓库内容逐字节 + * 一致"这种校验是做不到的,也没有加。这个脚本的作用是让夹具在 schema 演化后 + * 能被有意识地刷新 —— 刷新后请跑 npm test 确认依赖它的断言仍然成立。 + */ +import { spawnSync } from 'child_process' +import { mkdtempSync, writeFileSync, readFileSync } from 'fs' +import { tmpdir } from 'os' +import { join, dirname, resolve } from 'path' +import { fileURLToPath } from 'url' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const PY = process.env.PYTHON || 'python' +// 与 golden.json 现有内容同源:functions[].file 指向的就是这个脚本 +const SOURCE = join(ROOT, 'engine', 'tests', 'fixtures', 'nested_calls.py') +const TARGET = join(ROOT, 'src', 'renderer', 'src', 'fixtures', 'golden.json') + +const outDir = mkdtempSync(join(tmpdir(), 'golden-')) +const outFile = join(outDir, 'result.json') + +const args = ['-m', 'engine.runner', '--script', SOURCE, '--out', outFile] + +const r = spawnSync(PY, args, { + cwd: ROOT, + encoding: 'utf-8', + env: { ...process.env, PYTHONUTF8: '1', PYTHONIOENCODING: 'utf-8' } +}) + +if (r.error) { + console.error(`无法启动 ${PY}:${r.error.message}`) + console.error('设置 PYTHON 环境变量指向可用的解释器后重试。') + process.exit(1) +} +if (r.status !== 0) { + console.error(`引擎退出码 ${r.status}`) + console.error(r.stderr?.slice(-2000) ?? '') + process.exit(1) +} + +let parsed +try { + parsed = JSON.parse(readFileSync(outFile, 'utf-8')) +} catch (e) { + console.error(`读取/解析引擎结果失败:${e.message}`) + console.error(r.stderr?.slice(-2000) ?? '') + process.exit(1) +} + +if (parsed.status !== 'ok') { + console.error(`引擎返回 status=${parsed.status},不写入夹具:`, parsed.error) + process.exit(1) +} + +// functions[].file 用仓库相对路径(POSIX 分隔符),否则夹具里会带上本机绝对路径 +const rel = (p) => + p + .replace(ROOT, '') + .replace(/^[\\/]+/, '') + .replace(/\\/g, '/') +for (const f of parsed.functions) f.file = rel(f.file) + +writeFileSync(TARGET, JSON.stringify(parsed, null, 2) + '\n', 'utf-8') +console.log(`已写入 ${rel(TARGET)}`) +console.log(` functions=${parsed.functions.length}`) +console.log(` wallTime=${parsed.wallTime?.seconds?.toFixed(6)}s`) +console.log('接下来跑 npm test 确认依赖这份夹具的断言仍然成立。') diff --git a/src/main/index.ts b/src/main/index.ts new file mode 100644 index 0000000..9a1165c --- /dev/null +++ b/src/main/index.ts @@ -0,0 +1,408 @@ +import { app, BrowserWindow, dialog, ipcMain, Menu, nativeImage, shell } from 'electron' +import { readFile, rm, stat } from 'fs/promises' +import { readdirSync } from 'fs' +import { join } from 'path' +import { tmpdir } from 'os' +import { detectInterpreters, validateInterpreter, isPythonLikePath } from './interpreter' +import { runAnalysis, cancel, cancelAndWait } from './profiler-service' +import { openTerminal } from './terminal' +import { + isFileReadRequest, + isRunOptions, + MAX_CODE_BYTES, + MAX_FILE_READ_BYTES, + normalizeFileReadPath +} from './ipc-validation' +import { IpcError } from '../shared/ipc' + +let mainWindow: BrowserWindow | null = null +// before-quit 重入守卫:清理是异步的,第一次 preventDefault 后 app.quit() 会再触发 +// before-quit;用这个 flag 防止无限递归 +let awaitingQuitCleanup = false + +// 软件图标 — 项目根的 icon.ico 是 Windows ICO 文件,作为 BrowserWindow 的运行时图标。 +// app.getAppPath() 在 dev(electron-vite dev)和 prod(electron . 或打包后)都会指向 +// package.json 所在目录,也就是项目根,所以 'icon.ico' 直接拼上即可。 +// nativeImage 原生支持 .ico;createFromPath 返回 isEmpty() 时跳过,避免把坏路径传给 +// BrowserWindow 导致 Windows 上整个图标系统降级。 +function getAppIcon(): Electron.NativeImage | undefined { + const iconPath = join(app.getAppPath(), 'icon.ico') + const img = nativeImage.createFromPath(iconPath) + return img.isEmpty() ? undefined : img +} + +function createWindow(): void { + mainWindow = new BrowserWindow({ + width: 1440, + height: 920, + minWidth: 1080, + minHeight: 680, + backgroundColor: '#0f1115', + show: false, + // 关闭 OS 标题栏 — 放大/缩小/关闭按钮由 renderer 在 TopBar 自绘,保持 Linear + // 极简风格。顶栏整体作为 -webkit-app-region: drag 区域,按钮单独标记 no-drag。 + frame: false, + icon: getAppIcon(), + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + contextIsolation: true, + nodeIntegration: false, + // sandbox: true — preload 只用 contextBridge + ipcRenderer(无 Node 内置模块依赖), + // 开 sandbox 不会断功能,同时拿回 Electron 31 的默认安全姿势,避免 OS 级别 + // 任意代码执行的下行风险 + sandbox: true + } + }) + + // macOS:BrowserWindow.icon 不会更新 dock 图标,需要单独调用 app.dock.setIcon。 + // Windows / Linux 上 app.dock 是 undefined,无需处理。 + const icon = getAppIcon() + if (process.platform === 'darwin' && app.dock && icon) { + app.dock.setIcon(icon) + } + + mainWindow.on('ready-to-show', () => mainWindow?.show()) + + // 把 maximize 状态推到 renderer —— Windows 用户从任务栏右键菜单还原 / 双击标题栏 + // 最大化时,renderer 端按钮图标要跟着切,否则状态不一致。 + // 这里用 closure 持有 mainWindow 引用,避免 ready-to-show 之前的早期事件丢失。 + mainWindow.on('maximize', () => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('window:maximize-changed', true) + } + }) + mainWindow.on('unmaximize', () => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('window:maximize-changed', false) + } + }) + + if (process.env['ELECTRON_RENDERER_URL']) { + // loadURL/loadFile 返回 Promise 但没人需要等 —— ready-to-show / did-fail-load 事件已经 + // 帮我们处理失败路径,void 标记避免 ESLint 把每个 fire-and-forget 都报为 no-floating-promise + void mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + void mainWindow.loadFile(join(__dirname, '../renderer/index.html')) + } +} + +function registerIpc(): void { + ipcMain.handle('interp:detect', () => detectInterpreters()) + ipcMain.handle('interp:pick', async () => { + if (!mainWindow) return null + const win = mainWindow + // 非 Windows 平台不需要 .exe 过滤(python / python3 默认无后缀),把过滤器按平台分支 + const filters = + process.platform === 'win32' + ? [ + { name: '可执行文件', extensions: ['exe'] }, + { name: '所有文件', extensions: ['*'] } + ] + : [ + { name: '所有文件', extensions: ['*'] }, + { name: '无后缀可执行', extensions: [''] } + ] + const res = await dialog.showOpenDialog(win, { + title: '选择 Python 解释器', + properties: ['openFile'], + filters + }) + if (res.canceled || res.filePaths.length === 0) return null + const chosen = res.filePaths[0] + if (!isPythonLikePath(chosen)) { + // 走 IpcError 翻译链 —— 不再单独返回 {ok:false, error: '...'} 让 renderer 自己塞文案。 + // 之前 en-US 用户看到的是 main 进程 hardcoded 的中文。 + throw new IpcError('interpreter_not_allowed', `不是有效的 Python 解释器路径: ${chosen}`, { + path: chosen + }) + } + try { + return await validateInterpreter(chosen) + } catch (err) { + // 选了文件但 spawn 失败(不是 Python / 版本不对 / 执行报错等)—— 走 + // interpreter_probe_failed 让 renderer 按 lang 翻译。原始 message 仍保留在 + // details.message 给 stderr / 排错用。 + const detailMsg = err instanceof Error ? err.message : String(err) + throw new IpcError('interpreter_probe_failed', `解释器探测失败: ${detailMsg}`, { + path: chosen, + originalMessage: detailMsg + }) + } + }) + ipcMain.handle('analyze:run', async (e, opts: unknown) => { + // 先做形状校验 — 否则 renderer 篡改类型会让 spawn 抛莫名错误 + if (!isRunOptions(opts)) { + throw new IpcError('invalid_payload', 'RunOptions 形状校验失败(需要 interpreter + code)') + } + // 解释器必须在白名单(python / python3 / python.exe) + if (!isPythonLikePath(opts.interpreter)) { + throw new IpcError('interpreter_not_allowed', `不允许的解释器路径: ${opts.interpreter}`, { + interpreter: opts.interpreter + }) + } + // 用户代码大小上限,防 DoS + const byteLen = Buffer.byteLength(opts.code, 'utf-8') + if (byteLen > MAX_CODE_BYTES) { + throw new IpcError( + 'payload_too_large', + `代码过大 (${(byteLen / 1024 / 1024).toFixed(2)} MB > ${MAX_CODE_BYTES / 1024 / 1024} MB)`, + { byteLen, maxBytes: MAX_CODE_BYTES } + ) + } + return runAnalysis( + opts, + (p) => { + // 渲染层 reload 时 e.sender 可能指向已销毁的 webContents;放任 .send 会抛未捕获 + // 异常,进而把整个分析 Promise 弄 reject。这里用 mainWindow 兜底 + isDestroyed 守卫, + // 单进程多窗口(macOS activate)也能正常工作。 + const wc = (mainWindow && !mainWindow.isDestroyed() ? mainWindow.webContents : null) ?? e.sender + if (!wc || wc.isDestroyed()) return + try { + wc.send('analyze:progress', p) + } catch (err) { + // eslint-disable-next-line no-console + console.warn('[analyze:progress] send failed (webContents gone?)', err) + } + }, + // stdout 流式推送:和 progress 一样的 webContents.send 通道,渲染端按需订阅。 + // RunConsole 用它做实时滚动;旧 stderrTail 路径只在 error 时丢尾部 —— stdout 不 + // 落 AnalysisResult,直接 IPC 流过去更省内存(renderer 自己累计 buffer)。 + (chunk) => { + const wc = (mainWindow && !mainWindow.isDestroyed() ? mainWindow.webContents : null) ?? e.sender + if (!wc || wc.isDestroyed()) return + try { + wc.send('analyze:stdout', chunk) + } catch (err) { + // eslint-disable-next-line no-console + console.warn('[analyze:stdout] send failed (webContents gone?)', err) + } + } + ) + }) + ipcMain.handle('analyze:cancel', () => { + try { + cancel() + } catch (err) { + // 静默吞错不影响 UI(用户已经点了取消),但日志留痕 + // eslint-disable-next-line no-console + console.error('[analyze:cancel] failed:', err) + } + }) + /** + * 读取一个 Python 源文件(stdlib / 第三方包热点点击 → 打开 tab 用)。 + * + * 不走 throw —— 用 discriminated union 返回,让渲染端能直接把「文件不存在 / + * 权限不足 / 太大」展示在 tab 里,不需要再走 readIpcError 那套翻译链路。 + * 真正的意外错误(ENOENT 之外、磁盘 I/O 故障等)才会 throw IpcError。 + * + * 安全策略: + * - isFileReadRequest 已经在入口挡掉非法输入(非绝对路径、`..` 段、非 .py/.pyi) + * - normalizeFileReadPath 把路径规范化后再 stat / readFile,避免符号链接 / 大小写 + * 不一致导致绕过 + * - stat 先拿 size,超过 MAX_FILE_READ_BYTES 直接返回 too_large,不读内容 + * (stat 失败但 readFile 能成的极端情况罕见;这里以 stat 为准拒绝) + */ + ipcMain.handle('file:read', async (_e, req: unknown) => { + if (!isFileReadRequest(req)) { + throw new IpcError('invalid_payload', 'file:read 必须是 { filePath: string }', { + gotType: typeof req + }) + } + const normalized = normalizeFileReadPath(req.filePath) + try { + const st = await stat(normalized) + if (!st.isFile()) { + return { kind: 'not_a_file' as const, message: `${normalized} 不是常规文件` } + } + if (st.size > MAX_FILE_READ_BYTES) { + return { + kind: 'too_large' as const, + message: `文件过大 (${(st.size / 1024 / 1024).toFixed(2)} MB > ${MAX_FILE_READ_BYTES / 1024 / 1024} MB)` + } + } + const content = await readFile(normalized, 'utf-8') + return { kind: 'ok' as const, content, size: st.size } + } catch (err) { + const code = (err as NodeJS.ErrnoException).code + if (code === 'ENOENT') { + return { kind: 'not_found' as const, message: `文件不存在: ${normalized}` } + } + if (code === 'EACCES' || code === 'EPERM') { + return { kind: 'permission_denied' as const, message: `权限不足: ${normalized}` } + } + // 其他 fs 错误(EIO / ENOMEM / EBUSY …)属于真正异常,throw 走通用 IpcError 路径 + throw new IpcError('unknown', err instanceof Error ? err.message : String(err), { + code, + path: normalized + }) + } + }) + ipcMain.handle('terminal:open', async (_e, command: unknown, shell: unknown) => { + // 输入校验:command 必须是 string 或 undefined。 + // 不接受 object/array —— renderer 没有合法理由传非字符串,强行接受会 + // 让我们在主进程拼 shell 命令时被奇怪的 toString() 形状坑到。 + if (command !== undefined && typeof command !== 'string') { + throw new IpcError('invalid_payload', 'terminal command 必须是 string 或 undefined', { + gotType: typeof command + }) + } + // 长度上限:防止 renderer 被注入后塞几 MB 字符串给 spawn 撑爆命令行。 + // 实际合法命令(installPythonCommand)才 30-60 字节,500 已经是 10x 余量。 + if (typeof command === 'string' && command.length > 500) { + throw new IpcError('payload_too_large', `terminal command 过长 (${command.length} > 500)`, { + length: command.length + }) + } + // shell 必须是 'cmd' / 'powershell' / undefined(默认 cmd)。非 Windows 平台忽略此参数 + if (shell !== undefined && shell !== 'cmd' && shell !== 'powershell') { + throw new IpcError('invalid_payload', "terminal shell 必须是 'cmd' / 'powershell' / undefined", { + gotShell: String(shell) + }) + } + try { + await openTerminal(command ?? '', shell ?? 'cmd') + } catch (err) { + throw new IpcError('terminal_open_failed', err instanceof Error ? err.message : String(err)) + } + }) + // ============ 内嵌终端已移除 ============ + // 之前用 xterm.js + spawn shell 做内嵌终端,问题多(焦点跨 IPC 不稳、键盘输入丢失、 + // 渲染开销大、Electron sandbox 下 xterm 兼容性差)。改用「打开系统终端」按钮,按需弹 + // 用户本机的 cmd.exe / Terminal.app / gnome-terminal —— 由 src/main/terminal.ts 的 + // openTerminal 提供。Settings 的「打开终端安装 Python」也走它。 + + // 顶栏左上角图标:把项目根 icon.ico 读成 32×32 PNG data URL 交给 renderer。 + // 顶栏实际显示 ~18px;ICO 默认挑最大帧(一般 256×256)直传浪费 IPC payload, + // 还会被 Chromium 双线性缩到 18px 后发糊。先 resize 到 32×32 再 toDataURL, + // 既省字节又是更高质量的重采样。文件不存在 / 解不开时返回 null,renderer 退 SVG。 + ipcMain.handle('app:icon', () => { + const img = getAppIcon() + if (!img) return null + return img.resize({ width: 32, height: 32 }).toDataURL() + }) + /** + * 在系统默认浏览器里打开一个外部 URL(设置 → 关于里的开发者主页链接)。 + * + * 协议白名单:只放行 http / https —— 拒绝 javascript: / file: / data: 等, + * 防止 renderer 被注入后塞恶意 scheme 让 shell.openExternal 触发本地协议处理器 + * (macOS 上 .webloc / Windows 上 ms-msdt 之类的协议 handler 已被用作钓鱼面)。 + * + * URL.parse 在这里既做语法校验又做兜底 —— `new URL(...)` 抛 TypeError 直接转 IpcError, + * 渲染侧按 lang 翻译展示,不暴露堆栈。 + */ + ipcMain.handle('shell:openExternal', async (_e, url: unknown) => { + if (typeof url !== 'string' || url.length === 0) { + throw new IpcError('invalid_payload', 'openExternal url 必须是非空字符串', { gotType: typeof url }) + } + // 长度上限:正常个人主页 / 文档链接最多 200 字节,2KB 已经是 10x 余量,挡住畸形大输入。 + if (url.length > 2048) { + throw new IpcError('payload_too_large', `url 过长 (${url.length} > 2048)`, { length: url.length }) + } + let parsed: URL + try { + parsed = new URL(url) + } catch (err) { + throw new IpcError('invalid_payload', `url 解析失败: ${err instanceof Error ? err.message : String(err)}`, { + url + }) + } + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + throw new IpcError('invalid_payload', `不支持的协议: ${parsed.protocol}`, { + protocol: parsed.protocol, + url + }) + } + try { + await shell.openExternal(parsed.toString()) + } catch (err) { + throw new IpcError('unknown', err instanceof Error ? err.message : String(err), { url: parsed.toString() }) + } + }) + + // ============ 自定义窗口控制按钮(顶栏右上角)============ + // frame: false 后 OS 标题栏没了,缩放 / 还原 / 关闭必须由 renderer 触发 IPC。 + // 主进程在这层做唯一权威操作 —— renderer 不直接调 BrowserWindow。 + ipcMain.handle('window:minimize', () => { + mainWindow?.minimize() + }) + ipcMain.handle('window:maximize-toggle', () => { + if (!mainWindow) return { isMaximized: false } + if (mainWindow.isMaximized()) { + mainWindow.unmaximize() + } else { + mainWindow.maximize() + } + // 返回权威状态让 renderer 立即同步图标;不要等 'window:maximize-changed' 事件, + // 那条事件可能在 IPC response 之后到达,会闪一下旧图标。 + return { isMaximized: mainWindow.isMaximized() } + }) + ipcMain.handle('window:close', () => { + mainWindow?.close() + }) + // renderer mount 时拉一次当前状态 —— 之前用户可能已经处于最大化,重启后默认 false + // 会让图标错一拍 + ipcMain.handle('window:is-maximized', () => mainWindow?.isMaximized() ?? false) +} + +// whenReady().then(...) 本身就是 fire-and-forget;createWindow 内的加载是同步的(loadURL/file +// 是 async 但交给事件处理),这里只是把 setup 包进 ready 回调。catch 单独打错误日志。 +app + .whenReady() + .then(() => { + // 隐去 Electron 默认菜单栏(File / Edit / View / Window / Help): + // 本应用是 Python profiler,没有"新建窗口/打开文件"等文件操作入口; + // 设置走顶栏齿轮按钮、运行/取消走顶部按钮、Cut/Copy/Paste 走 Monaco 自带实现、 + // DevTools 仍可通过 F12 / Ctrl+Shift+I 唤起(不依赖菜单项)。 + Menu.setApplicationMenu(null) + registerIpc() + createWindow() + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) + }) + .catch((err: unknown) => { + // eslint-disable-next-line no-console + console.error('[app:ready] failed:', err) + }) + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') app.quit() +}) + +// 应用退出前清理所有残留的 pyprof-* 临时目录。 +// 单次 runAnalysis 退出时 close 事件已经清,但用户 X-out / OS 强制关机 +// / spawn 失败 / mkdtemp 后 writeFile 抛错 这些路径会泄漏。 +// 这里扫描 tmpdir 下所有 pyprof- 前缀的目录尽力删一次。 +// 同时要先杀 child:Cmd+Q / Alt+F4 中途退出时,留着 Python 子进程会成孤儿 +// (macOS / detached POSIX 尤甚)。用 cancelAndWait 而不是 cancel:Windows 上 +// killTree 走的是 taskkill 子进程,不等它回来就 quit 照样留孤儿。 +app.on('before-quit', (e) => { + if (awaitingQuitCleanup) return + e.preventDefault() + awaitingQuitCleanup = true + // 清理必须串成一条 await 链再 app.quit():之前 cancel() 和 rm() 都是发出去就不管, + // quit 抢在 taskkill / rm 之前跑完,两个清理都等于没做。 + void (async () => { + try { + await cancelAndWait() + } catch { + /* 杀进程失败不该挡住退出 */ + } + try { + // 循环变量用 `name`(而不是 `e`):外层 `e` 是 before-quit 的事件对象, + // 内层 if 用 `e` 会遮蔽它。 + const entries = readdirSync(tmpdir()).filter((name) => name.startsWith('pyprof-')) + await Promise.allSettled( + entries.map((name) => + rm(join(tmpdir(), name), { recursive: true, force: true }).catch((err) => { + // eslint-disable-next-line no-console + console.warn('[cleanup] failed to remove', name, err) + }) + ) + ) + } catch { + /* tmpdir 不可读时静默 — 不是阻塞性操作 */ + } + app.quit() + })() +}) diff --git a/src/main/interpreter.test.ts b/src/main/interpreter.test.ts new file mode 100644 index 0000000..4bc0663 --- /dev/null +++ b/src/main/interpreter.test.ts @@ -0,0 +1,112 @@ +import { validateInterpreter, parsePyLauncherOutput, isPythonLikePath } from './interpreter' + +// 需要本机存在 python/python3。CI 无 Python 时用 it.skip 而不是 try/catch + return: +// - 静默 return 让测试报告看不出哪些用例被跳了,CI 矩阵跑起来像绿了但其实没覆盖 +// - it.skip 会显式报告 skip 计数 + skip 原因(条件 if 表达式就是文案) +const PY = process.env.PYTHON || 'python' + +describe('interpreter', () => { + it('validateInterpreter 返回版本', async () => { + let info + try { + info = await validateInterpreter(PY) + } catch (err) { + // 单独这一项 skip — 不要把整个 file skip,因为下面的 parsePyLauncherOutput 是纯字符串 + it.skip(`validateInterpreter 跳过:本机找不到 ${PY} (${(err as Error).message})`, () => {}) + return + } + expect(info.path).toBe(PY) + expect(info.version).toMatch(/^\d+\.\d+\.\d+$/) + }) + + describe('parsePyLauncherOutput', () => { + it('从普通 py -0p 输出中提取每个 exe 路径', () => { + const stdout = [ + ' -V:3.12 * C:\\Python312\\python.exe', + ' -V:3.11 C:\\Python311\\python.exe' + ].join('\r\n') + expect(parsePyLauncherOutput(stdout)).toEqual([ + 'C:\\Python312\\python.exe', + 'C:\\Python311\\python.exe' + ]) + }) + + it('路径含 `*`(默认标记)也要正确剥离版本前缀', () => { + // 之前的 bug:^\s*-\s*(.+?\.(?:exe|EXE))\s*$ 会把整行吞下,导致路径里含 `*` + const stdout = ' -V:3.12 * C:\\Python312\\python.exe' + const paths = parsePyLauncherOutput(stdout) + expect(paths).toEqual(['C:\\Python312\\python.exe']) + expect(paths[0]).not.toContain('*') + }) + + it('处理带引号的路径(含空格的安装目录)', () => { + const stdout = ' -V:3.10 * "C:\\Program Files\\Python310\\python.exe"' + expect(parsePyLauncherOutput(stdout)).toEqual(['C:\\Program Files\\Python310\\python.exe']) + }) + + it('忽略不含 .exe 的垃圾行(warning / 错误)', () => { + const stdout = [ + 'Some warning message', + ' -V:3.12 * C:\\Python312\\python.exe', + 'error: not a real path' + ].join('\n') + expect(parsePyLauncherOutput(stdout)).toEqual(['C:\\Python312\\python.exe']) + }) + + it('空 stdout 返回空数组', () => { + expect(parsePyLauncherOutput('')).toEqual([]) + }) + + it('路径后面跟注释 / 额外 token 时仍能提取 .exe', () => { + // 之前的 bug:取行末最后一个 token 再过滤 .exe;尾随注释/环境标记会让该 token 不是路径, + // 那条安装就被漏掉。修复后改为"最后一个以 .exe 结尾的 token"。 + const stdout = ' -V:3.12 * C:\\Python312\\python.exe # default' + expect(parsePyLauncherOutput(stdout)).toEqual(['C:\\Python312\\python.exe']) + }) + + it('引号路径后跟额外 token 时仍能提取', () => { + const stdout = ' -V:3.10 * "C:\\Program Files\\Python310\\python.exe" trailing-junk' + expect(parsePyLauncherOutput(stdout)).toEqual(['C:\\Program Files\\Python310\\python.exe']) + }) + }) + + describe('isPythonLikePath', () => { + // 平台相关:win32 只认 C:\ / \\server\,POSIX 只认 /...。用 helper 拼当前平台的绝对路径, + // 否则这些用例在另一个平台上会因为 isAbsolute 检查全挂。 + const abs = (name: string): string => + process.platform === 'win32' ? `C:\\Python311\\${name}` : `/usr/bin/${name}` + + it('接受带小版本号的解释器名', () => { + // 回归测试:正则曾是 /^python(\d*)?(\.exe)?$/,只匹配 python / python3, + // 把 macOS/Linux 上最常见的 /usr/bin/python3.11 全拒了,用户被迫做符号链接。 + expect(isPythonLikePath(abs('python3.11'))).toBe(true) + expect(isPythonLikePath(abs('python3.12'))).toBe(true) + expect(isPythonLikePath(abs('python3.12.1'))).toBe(true) + }) + + it('继续接受无版本号 / 单段版本号 / .exe 后缀', () => { + expect(isPythonLikePath(abs('python'))).toBe(true) + expect(isPythonLikePath(abs('python3'))).toBe(true) + expect(isPythonLikePath(abs('python.exe'))).toBe(true) + expect(isPythonLikePath(abs('python3.exe'))).toBe(true) + }) + + it('拒绝裸名(会被 spawn 按 PATH 解析,可被劫持)', () => { + expect(isPythonLikePath('python')).toBe(false) + expect(isPythonLikePath('python3.11')).toBe(false) + }) + + it('拒绝非 python 可执行文件', () => { + expect(isPythonLikePath(abs('node'))).toBe(false) + expect(isPythonLikePath(abs('pythonw.exe'))).toBe(false) + expect(isPythonLikePath(abs('python-evil'))).toBe(false) + expect(isPythonLikePath(abs('notpython'))).toBe(false) + }) + + it('拒绝 .. 穿透与空值', () => { + expect(isPythonLikePath(abs('../../evil/python'))).toBe(false) + expect(isPythonLikePath('')).toBe(false) + expect(isPythonLikePath(undefined as unknown as string)).toBe(false) + }) + }) +}) diff --git a/src/main/interpreter.ts b/src/main/interpreter.ts new file mode 100644 index 0000000..6417e3b --- /dev/null +++ b/src/main/interpreter.ts @@ -0,0 +1,209 @@ +import { execFile } from 'child_process' +import { promisify } from 'util' +import { existsSync, readdirSync, statSync } from 'fs' +import { join } from 'path' +import { homedir } from 'os' +import type { InterpreterInfo } from '../shared/analysis' + +const execFileAsync = promisify(execFile) + +// 一行探测脚本:输出 JSON {version} +const PROBE = 'import sys,json;' + 'print(json.dumps({"version":"%d.%d.%d"%sys.version_info[:3]}))' + +// 候选可执行名(PATH 中) +const CANDIDATES = + process.platform === 'win32' ? ['python', 'py', 'python3', 'python.exe'] : ['python3', 'python'] + +// 解释器白名单:只接受"看起来像 python"且**绝对路径**的可执行文件。 +// 之前 basename 检查 (`python.exe` / python3) 接受任何匹配名,包括光 `"python"` +// 这种裸名 — spawn 会按 PATH 解析,攻击者把恶意的 `python` 放进靠前 PATH 就能劫持。 +// +// 放在 interpreter.ts 而不是 index.ts:index.ts 导入 electron,单元测试里没法直接 +// require;这个函数是纯字符串判断,挪过来就能被 interpreter.test.ts 直接覆盖。 +export function isPythonLikePath(p: string): boolean { + if (typeof p !== 'string' || !p) return false + // 绝对路径:Windows (C:\ / D:\ / \\server\) 或 POSIX (/...) + const isAbsolute = + process.platform === 'win32' ? /^[a-zA-Z]:[\\/]/.test(p) || p.startsWith('\\\\') : p.startsWith('/') + if (!isAbsolute) return false + // 拒绝 ../ 段穿透 — normalize 后还含 .. 就视为不安全 + const normalized = p.replace(/\\/g, '/') + if (/\.\.[/\\]/.test(normalized) || normalized.endsWith('/..')) return false + const name = p.split(/[\\/]/).pop()?.toLowerCase() ?? '' + // 允许带小版本号:macOS/Linux 上 /usr/bin/python3.11、python3.12 是系统 Python 的 + // 常见形态。之前的 /^python(\d*)?(\.exe)?$/ 只匹配 python / python3,把这些全拒了, + // 用户被迫做符号链接才能用。 + return /^python(\d+(\.\d+)*)?(\.exe)?$/.test(name) +} + +/** 在常见安装路径下补全候选可执行文件(venv、conda、pyenv、uv、官方安装器) */ +function extraCandidates(): string[] { + const out: string[] = [] + const home = homedir() + if (process.platform === 'win32') { + const local = process.env['LOCALAPPDATA'] + const programFiles = process.env['ProgramFiles'] || 'C:\\Program Files' + if (local) { + // Windows 官方安装器默认路径 + for (let i = 0; i < 5; i++) { + out.push(join(local, 'Programs', 'Python', `Python3${i}`, 'python.exe')) + out.push(join(local, 'Programs', 'Python', `Python3${i}-32`, 'python.exe')) + } + } + out.push(join(programFiles, 'Python313', 'python.exe')) + out.push(join(programFiles, 'Python312', 'python.exe')) + out.push(join(programFiles, 'Python311', 'python.exe')) + out.push(join(programFiles, 'Python310', 'python.exe')) + } else { + // Linux/macOS 常见路径。pyenv 的 .pyenv/versions 是目录,会在 step 3 展开处理; + // 这里不再尝试直接 validate 整个目录(之前会被 `path.includes('*')` 的"占位"逻辑误判) + out.push('/usr/bin/python3') + out.push('/usr/local/bin/python3') + out.push('/opt/homebrew/bin/python3') + if (home) { + out.push(join(home, '.local', 'bin', 'python3')) + } + } + return out +} + +/** 列出 conda 环境中的 python 路径(尽力而为,失败静默) */ +async function listCondaPythons(): Promise { + try { + const { stdout } = await execFileAsync('conda', ['env', 'list', '--json'], { timeout: 5000 }) + const data: unknown = JSON.parse(stdout) + const envs: string[] = Array.isArray((data as { envs?: unknown[] } | null)?.envs) + ? (data as { envs: unknown[] }).envs.filter((p): p is string => typeof p === 'string') + : [] + const out: string[] = [] + for (const env of envs) { + const py = process.platform === 'win32' ? join(env, 'python.exe') : join(env, 'bin', 'python') + if (existsSync(py)) out.push(py) + } + return out + } catch { + return [] + } +} + +/** 列出 `py -0p` 给出的所有 Windows Python Launcher 安装 */ +async function listPyLauncher(): Promise { + if (process.platform !== 'win32') return [] + try { + const { stdout } = await execFileAsync('py', ['-0p'], { timeout: 5000 }) + return parsePyLauncherOutput(stdout) + } catch { + return [] + } +} + +/** + * 把 `py -0p` 的 stdout 解析成候选 python.exe 路径。 + * 每行格式: ` -V:3.X [*] PATH` 或 ` -V:3.X [*] "PATH with spaces"` + * 之前取行内最后一个 token 再过滤 .exe 后缀 — 但某些环境下 launcher 会在末尾追加注释/环境标记, + * 导致行末 token 不是路径,那条安装就被漏掉。 + * 改成"扫所有 token,取最后一个以 .exe / .EXE 结尾的"——更稳,且兼容原有所有合法行。 + */ +export function parsePyLauncherOutput(stdout: string): string[] { + const out: string[] = [] + const tokenRe = /"([^"]*)"|(\S+)/g + for (const line of stdout.split(/\r?\n/)) { + let last: string | undefined + let m: RegExpExecArray | null + tokenRe.lastIndex = 0 + while ((m = tokenRe.exec(line)) !== null) { + const tok = m[1] !== undefined ? m[1] : m[2] + if (/\.(exe|EXE)$/.test(tok)) last = tok + } + if (last) out.push(last) + } + return out +} + +export async function validateInterpreter(path: string): Promise { + const { stdout } = await execFileAsync(path, ['-c', PROBE], { timeout: 8000 }) + // 不直接 JSON.parse(stdout.trim()):第三方包可能在 stdout 印警告(deprecation banner 等), + // 那样就把本来能用的解释器当失败丢掉了。取最后一个 '{' 开头的 token 解 JSON。 + const lines = stdout.split(/\r?\n/) + for (let i = lines.length - 1; i >= 0; i--) { + const line = lines[i].trim() + if (line.startsWith('{')) { + const data: unknown = JSON.parse(line) + // 校验形状:避免 {version: undefined} 这种"看起来成功"的假阳性 + // 导致 UI 显示 "Python undefined"。有 PYTHONSTARTUP / 旧 Python 警告时可能踩到。 + const probe = data as { version?: unknown } | null + if (typeof probe?.version !== 'string' || !/^\d+\.\d+\.\d+/.test(probe.version)) { + throw new Error(`probe 返回的 JSON 缺少有效 version 字段: ${line.slice(0, 120)}`) + } + return { path, version: probe.version } + } + } + throw new Error('interpreter probe returned no JSON object') +} + +async function tryValidate(path: string): Promise { + if (!path) return null + // pyenv 版本目录占位(前缀如 /home/.../.pyenv/versions 本身)— 不是可执行文件 + if (path.includes('*')) return null + if (!existsSync(path)) return null + try { + return await validateInterpreter(path) + } catch { + return null + } +} + +/** 列出一个 phase 的所有候选(不做 validate,路径解析 / 文件系统访问)。 */ +function pyenvCandidates(): string[] { + const home = homedir() + if (!home) return [] + const pyenvRoot = join(home, '.pyenv', 'versions') + if (!existsSync(pyenvRoot)) return [] + try { + const out: string[] = [] + for (const ver of readdirSync(pyenvRoot)) { + const bin = join(pyenvRoot, ver, process.platform === 'win32' ? 'python.exe' : 'bin/python') + if (existsSync(bin) && statSync(bin).isFile()) out.push(bin) + } + return out + } catch { + return [] + } +} + +/** 并行 validate 一组候选;返回成功的去重后列表(不去重由调用方做)。 */ +async function validateAll(paths: string[]): Promise { + const results = await Promise.all(paths.map((p) => tryValidate(p))) + const out: InterpreterInfo[] = [] + for (const r of results) if (r) out.push(r) + return out +} + +export async function detectInterpreters(): Promise { + const found: InterpreterInfo[] = [] + const seen = new Set() + + const add = (info: InterpreterInfo) => { + const key = info.path + if (seen.has(key)) return + seen.add(key) + found.push(info) + } + + // 五个 phase 并行跑,每个 phase 内部也并行 validate。 + // 之前是串行 5 段,每段里再串行 tryValidate,20+ 候选在慢盘上能到 4s。 + // 并行后总耗时 = max(单 phase 耗时),通常 < 1s。 + const [phasePath, phaseExtra, phasePyenv, phaseLauncher, phaseConda] = await Promise.all([ + validateAll(CANDIDATES), + validateAll(extraCandidates()), + validateAll(pyenvCandidates()), + listPyLauncher().then(validateAll), + listCondaPythons().then(validateAll) + ]) + + for (const list of [phasePath, phaseExtra, phasePyenv, phaseLauncher, phaseConda]) { + for (const info of list) add(info) + } + + return found +} diff --git a/src/main/ipc-validation.test.ts b/src/main/ipc-validation.test.ts new file mode 100644 index 0000000..fb1013d --- /dev/null +++ b/src/main/ipc-validation.test.ts @@ -0,0 +1,156 @@ +import { + isFileReadRequest, + isRunOptions, + MAX_CODE_BYTES, + MAX_FILE_READ_BYTES, + normalizeFileReadPath +} from './ipc-validation' + +const VALID = { + interpreter: 'python', + code: 'print(1)' +} + +/** 在合法对象上改一个字段,用来测单字段的拒绝。 */ +const withField = (key: string, value: unknown): unknown => ({ ...VALID, [key]: value }) + +describe('isRunOptions', () => { + it('接受合法的 RunOptions', () => { + expect(isRunOptions(VALID)).toBe(true) + }) + + it('拒绝非对象', () => { + for (const bad of [null, undefined, 'x', 42, true, [], () => {}]) { + expect(isRunOptions(bad)).toBe(false) + } + }) + + it('拒绝缺字段的对象', () => { + for (const key of Object.keys(VALID)) { + const partial: Record = { ...VALID } + delete partial[key] + expect(isRunOptions(partial)).toBe(false) + } + }) + + describe('字符串字段', () => { + it('interpreter / code 必须是字符串', () => { + for (const key of ['interpreter', 'code']) { + for (const bad of [1, null, undefined, {}, [], true]) { + expect(isRunOptions(withField(key, bad))).toBe(false) + } + } + }) + + it('interpreter 不能为空字符串(解释器路径必须有内容)', () => { + expect(isRunOptions(withField('interpreter', ''))).toBe(false) + }) + + it('允许 code 是空字符串(用户清空编辑器是合法状态)', () => { + expect(isRunOptions(withField('code', ''))).toBe(true) + }) + }) + + it('忽略多余字段(renderer 版本较新时不该整体拒绝)', () => { + expect(isRunOptions({ ...VALID, futureField: 'whatever' })).toBe(true) + }) +}) + +describe('MAX_CODE_BYTES', () => { + it('是 1MB —— 上限值本身别被误改', () => { + expect(MAX_CODE_BYTES).toBe(1024 * 1024) + }) +}) + +describe('MAX_FILE_READ_BYTES', () => { + it('是 2MB —— 上限值本身别被误改', () => { + expect(MAX_FILE_READ_BYTES).toBe(2 * 1024 * 1024) + }) +}) + +describe('normalizeFileReadPath', () => { + it('Windows 路径上的重复斜杠和 ./ 被收掉', () => { + // path.normalize 是平台依赖的 —— 测试在 win32 跑就用 win32 语义断言。 + // 真实场景:C:\Python311\Lib\json\decoder.py 没必要 normalize (已经干净) + expect(normalizeFileReadPath('C:\\Python311\\Lib\\json\\decoder.py')).toBe( + 'C:\\Python311\\Lib\\json\\decoder.py' + ) + // 有冗余 ./ 或重复斜杠 → normalize 应当收敛 + expect(normalizeFileReadPath('C:\\Python311\\.\\Lib\\\\json\\decoder.py')).toBe( + 'C:\\Python311\\Lib\\json\\decoder.py' + ) + }) +}) + +describe('isFileReadRequest', () => { + it('接受合法的绝对 .py / .pyi 路径', () => { + for (const ok of [ + { filePath: 'C:\\Python311\\Lib\\json\\decoder.py' }, + { filePath: 'C:\\Python311\\Lib\\json\\decoder.pyi' }, + { filePath: '/usr/lib/python3.11/json/decoder.py' }, + { filePath: 'C:\\Python311\\Lib\\json\\decoder.PY' } // 大小写不敏感 + ]) { + expect(isFileReadRequest(ok)).toBe(true) + } + }) + + it('拒绝非对象', () => { + for (const bad of [null, undefined, 'x', 42, true, [], () => {}]) { + expect(isFileReadRequest(bad)).toBe(false) + } + }) + + it('拒绝缺字段的对象(与 isRunOptions 一致,不严格拒多余字段)', () => { + // 缺 filePath + expect(isFileReadRequest({})).toBe(false) + expect(isFileReadRequest({ path: 'x' })).toBe(false) + // 多余字段:和 isRunOptions 一样忽略 —— renderer 版本较新时不该整体拒绝 + expect(isFileReadRequest({ filePath: 'C:/x.py', futureField: 'whatever' })).toBe(true) + }) + + it('filePath 不是字符串就拒', () => { + for (const bad of [1, null, undefined, {}, [], true, false]) { + expect(isFileReadRequest({ filePath: bad })).toBe(false) + } + }) + + it('空字符串 / 超长字符串拒绝', () => { + expect(isFileReadRequest({ filePath: '' })).toBe(false) + // 长度边界: 'C:\\' (3) + 'a'.repeat(N) + '.py' (3) = 6 + N 总长 + // 6 + 4090 = 4096 → 边界值,接受 + expect(isFileReadRequest({ filePath: 'C:\\' + 'a'.repeat(4090) + '.py' })).toBe(true) + // 6 + 4091 = 4097 → 越界 1,拒绝 + expect(isFileReadRequest({ filePath: 'C:\\' + 'a'.repeat(4091) + '.py' })).toBe(false) + }) + + it('相对路径拒绝 —— 防止 ../../etc/passwd', () => { + expect(isFileReadRequest({ filePath: 'json/decoder.py' })).toBe(false) + expect(isFileReadRequest({ filePath: './json/decoder.py' })).toBe(false) + expect(isFileReadRequest({ filePath: '../../etc/passwd' })).toBe(false) + }) + + it('即便绝对路径,normalize 后含 .. 也拒绝', () => { + // path.isAbsolute 通过,但 segment 含 '..' → 拒 + expect(isFileReadRequest({ filePath: '/usr/lib/../../../etc/passwd.py' })).toBe(false) + expect(isFileReadRequest({ filePath: 'C:\\Python311\\..\\..\\Windows\\foo.py' })).toBe(false) + }) + + it('非 .py / .pyi 扩展名拒绝', () => { + for (const bad of [ + 'C:/path/file.txt', + 'C:/path/file.js', + 'C:/path/file.pyc', + 'C:/path/file.so', + 'C:/path/file', + 'C:/path/no-extension', + 'C:/path/file.py.bak' + ]) { + expect(isFileReadRequest({ filePath: bad })).toBe(false) + } + }) + + it('目录路径(无扩展名部分)拒绝', () => { + // 末段是空串 → 无扩展名匹配 + expect(isFileReadRequest({ filePath: 'C:/python/Lib/' })).toBe(false) + }) +}) diff --git a/src/main/ipc-validation.ts b/src/main/ipc-validation.ts new file mode 100644 index 0000000..23f06ef --- /dev/null +++ b/src/main/ipc-validation.ts @@ -0,0 +1,94 @@ +import { isAbsolute, normalize } from 'path' +import type { FileReadRequest, RunOptions } from '../shared/analysis' + +/** + * IPC 入口的形状与边界校验。 + * + * 单独成一个模块是为了能被单元测试直接 import —— 它原本躺在 index.ts 里, + * 而 index.ts 顶层 `import { app, BrowserWindow } from 'electron'`,测试环境 + * require 不进来。结果是整个 IPC 安全边界(renderer 可以往这儿传任何东西) + * 零测试覆盖。isPythonLikePath 之前是同样的处境,已经用"抽出去再测"解决过一次, + * 这里是同一个做法。 + */ + +/** 用户代码大小上限:防 10GB 字符串塞爆 tmp 盘。 + * 1MB 对正常脚本绰绰有余;再大就拒绝。 */ +export const MAX_CODE_BYTES = 1 * 1024 * 1024 + +/** + * 单个外部源文件读取上限 —— 给 stdlib / 第三方包热点打开文件用。 + * + * 选 2MB 的依据:标准库最大的 .py(pydoc / tarfile)也就 100~200KB,site-packages 里 + * 99% 的 .py 不超过 500KB。2MB 是 20x 安全余量 —— 超过这个数基本可以肯定是误用 + * (cProfile 把 .pyc / 编译产物路径记成 .file,或打开了一个奇怪的二进制)。 + * + * 跟 MAX_CODE_BYTES 一样在主进程入口处强制,避免把一个 500MB 的 numpy .so 当 .py 读进来 + * 把 Monaco editor 撑爆。 + */ +export const MAX_FILE_READ_BYTES = 2 * 1024 * 1024 + +/** + * IPC 入口处统一校验 RunOptions 形状;renderer 篡改或版本不匹配时 + * 传错类型会导致 spawn 抛 "args.slice is not a function" 这类莫名错误。 + */ +export function isRunOptions(o: unknown): o is RunOptions { + if (typeof o !== 'object' || o === null) return false + const r = o as Record + if (typeof r['interpreter'] !== 'string' || r['interpreter'].length === 0) return false + if (typeof r['code'] !== 'string') return false + // scope 可选:缺失等同于 "user"。给出非 "user"/"all" 的值直接拒掉, + // 避免传 "alll" / null / 数组这类值让 spawn 报 "unrecognized arguments"。 + if (r['scope'] !== undefined && r['scope'] !== 'user' && r['scope'] !== 'all') return false + return true +} + +/** + * 校验 file:read 的入参。决定 renderer 是否能读到指定文件。 + * + * 校验项(按顺序): + * 1. 形状:必须是 `{ filePath: string }` + * 2. 长度:1..4096 字节 —— 实际合法路径最长几百字符,4096 是 10x 上限。 + * 太长的输入几乎可以肯定是构造的探测串(path traversal 探测、buffer 测试)。 + * 3. 必须是绝对路径(path.isAbsolute):相对路径在主进程工作目录里 resolve, + * 工作目录不可预测(打包后是 app.asar,开发时是 src/),会让同一份 fn.file 在 + * 不同启动方式下指向不同文件 → 结果不一致。强制绝对路径也防止 renderer 写 + * `../../etc/passwd` 这种相对探测。 + * 4. normalize 后不含 `..` 段:即便绝对路径,normalize('/usr/lib/../../../etc/passwd') + * 也是合法绝对路径,但含 `..` → 仍拒。双重保险。 + * 5. 扩展名必须是 .py / .pyi:cProfile 的 fn.file 应该指向 .py,但万一引擎把 .pyc 或 + * 别的路径记进来,给用户展示 Python 之外的二进制没意义。.pyi(类型存根)也允许, + * Python 生态里它跟 .py 一样是源码。 + * + * 不挂额外字段 —— 校验通过的入参 handler 自己再调 normalizeFileReadPath 拿规范化路径。 + */ +export function isFileReadRequest(o: unknown): o is FileReadRequest { + if (typeof o !== 'object' || o === null) return false + const r = o as Record + if (typeof r['filePath'] !== 'string') return false + const p = r['filePath'] + if (p.length < 1 || p.length > 4096) return false + if (!isAbsolute(p)) return false + // 路径段检查:先 split **原始输入** 看任一段是否为 '..' 即视为 traversal 尝试。 + // 注意:必须在 normalize 之前做 —— path.normalize 会把 '/usr/lib/../etc/x.py' + // 收成 '/usr/etc/x.py',把 '..' 段消掉,这样 segments.includes('..') 就漏检了。 + // 把检查放在 normalize 之前相当于「看到 ../ 就拒」,双重保险。 + const segments = p.split(/[/\\]/) + if (segments.includes('..')) return false + // 取 normalize 后的最后一段判扩展名:Windows 上 path.normalize 保留反斜杠, + // 但 split 还是按 / 或 \ 都能切 —— 直接复用 segments 末尾更直接,不必多一道。 + const last = segments[segments.length - 1] ?? '' + if (!/\.(py|pyi)$/i.test(last)) return false + return true +} + +/** + * 给 isFileReadRequest 校验通过的路径做 normalize。 + * 拆出来而不是让 isFileReadRequest 挂在入参上 —— 避免"改入参"的副作用 + * 让类型断言(`o is FileReadRequest`)看起来在说谎。 + * + * 注意调用方应在 isFileReadRequest 通过之后再调 —— 这里不做形状校验,只负责 + * 把绝对路径收成 path.normalize 形式(handler 拿来直接 fs.readFile)。 + */ +export function normalizeFileReadPath(p: string): string { + return normalize(p) +} diff --git a/src/main/profiler-service.test.ts b/src/main/profiler-service.test.ts new file mode 100644 index 0000000..1a99bb4 --- /dev/null +++ b/src/main/profiler-service.test.ts @@ -0,0 +1,201 @@ +import { existsSync } from 'fs' +import { join } from 'path' +import { runAnalysis, resolveEngineRoot, cancelAndWait, userFacingStderr } from './profiler-service' + +const PY = process.env.PYTHON || 'python' + +async function hasPython(): Promise { + try { + const { execFile } = await import('child_process') + const { promisify } = await import('util') + await promisify(execFile)(PY, ['--version']) + return true + } catch { + return false + } +} + +// 探测一次,用 it.skipIf 声明式跳过。 +// 之前是在用例体内调 it.skip() 再 return —— 那是个 no-op(运行中注册新用例不生效), +// 没有 Python 时这些用例会「静默全绿」,正是它上面注释声称已经修掉的反模式。 +const PYTHON_AVAILABLE = await hasPython() +const itPy = it.skipIf(!PYTHON_AVAILABLE) +const BASE = { interpreter: PY, code: 'x = 1\nprint(x)\n' } + +describe('profiler-service', () => { + describe('userFacingStderr 过滤内部进度协议', () => { + it('去掉 PROGRESS 行', () => { + expect(userFacingStderr('PROGRESS timing 10\nPROGRESS lines 70\n')).toBe('') + }) + + it('保留真正的报错,顺序不变', () => { + const raw = + 'PROGRESS timing 10\nTraceback (most recent call last):\nPROGRESS lines 70\n ZeroDivisionError\n' + expect(userFacingStderr(raw)).toBe('Traceback (most recent call last):\n ZeroDivisionError') + }) + + it('兼容 CRLF(Windows 上引擎输出是 \\r\\n)', () => { + expect(userFacingStderr('PROGRESS timing 10\r\nboom\r\n')).toBe('boom') + }) + + it('只过滤行首的 PROGRESS,用户自己打印的内容不受影响', () => { + // 用户代码 print 出 "log: PROGRESS timing 10" 这种不该被吃掉。 + // 注意整块最后会 trim,所以别拿首行的前导空格做断言 —— 把缩进行放在中间。 + const raw = 'boom\n PROGRESS timing 10 只是被缩进了\nlog: PROGRESS timing 10' + expect(userFacingStderr(raw)).toBe(raw) + }) + + it('形似但不合规的 PROGRESS 行保留(宁可多显示也不要吞掉真信息)', () => { + expect(userFacingStderr('PROGRESS timing\nPROGRESSING fast')).toBe('PROGRESS timing\nPROGRESSING fast') + }) + + it('空输入返回空串', () => { + expect(userFacingStderr('')).toBe('') + expect(userFacingStderr(' \n\n')).toBe('') + }) + }) + + it('resolveEngineRoot 能定位到含 engine/runner.py 的目录', () => { + const root = resolveEngineRoot() + expect(typeof root).toBe('string') + // 只断言 typeof 等于什么都没测:这里断言 runner.py 真的在那儿 + expect(existsSync(join(root, 'engine', 'runner.py'))).toBe(true) + }) + + itPy( + 'runAnalysis 正常运行返回 ok 并回调进度', + async () => { + const phases: string[] = [] + const result = await runAnalysis( + { + ...BASE, + code: 'def slow():\n s=0\n for i in range(50000):\n s+=i\n return s\nslow()\n' + }, + (p) => phases.push(p.phase) + ) + expect(result.status).toBe('ok') + expect(result.wallTime?.seconds).toBeGreaterThan(0) + expect(phases.length).toBeGreaterThan(0) + }, + 30000 + ) + + // onStdout 流式回调:每个 stdout 数据块都同步转发给回调,而不是攒在末尾再丢一份。 + // 这条锁的是 RunConsole 实时滚动的关键不变量 —— 用户写 `print('hi')`, 期望 + // 立刻看到一行 'hi', 而不是等 run 结束才一次性出现。flush=True 强制 Python 不 + // 缓冲 stdout,否则本用例在 Windows 上有时只看到 1 个数据块而不是 3 个。 + itPy( + 'onStdout 回调在运行中实时收到每块', + async () => { + const chunks: string[] = [] + const result = await runAnalysis( + { + ...BASE, + code: 'import sys\nfor i in range(3):\n print(f"line {i}", flush=True)\n' + }, + undefined, + (chunk) => chunks.push(chunk) + ) + expect(result.status).toBe('ok') + // chunks 拼起来应包含全部 3 行 —— 顺序可能因数据块边界变化,但内容必须齐全 + const joined = chunks.join('') + expect(joined).toContain('line 0') + expect(joined).toContain('line 1') + expect(joined).toContain('line 2') + // 至少应该收到 1 块 —— 不能因为 appendCapped 把整段合并成 1 块就让测试 + // 失去意义(只要数据真实流过来就够) + expect(chunks.length).toBeGreaterThan(0) + }, + 30000 + ) + + // onStdout 抛错不能让 child 死掉 —— 之前没守护时一处 throw 会让整次 run 提前 + // resolve,用户拿不到 result.json。这是 useAnalysis / RunConsole 解耦后必须 + // 保住的不变量:渲染端 setState 出 bug 不能毁掉主进程运行。 + itPy( + 'onStdout 回调抛错被吞掉,runAnalysis 继续返回 ok', + async () => { + const result = await runAnalysis( + { ...BASE, code: 'print("a", flush=True)\nprint("b", flush=True)\n' }, + undefined, + () => { + throw new Error('simulated render crash') + } + ) + expect(result.status).toBe('ok') + expect(result.wallTime?.seconds).toBeGreaterThan(0) + }, + 30000 + ) + + // v3 起主进程彻底移除了自动超时(之前是 30s 常量 → IPC 上 timeoutSec 字段被砍掉 + // → 现在连 30s 也没有了)。超时路径的「setTimeout + taskkill」胶水代码不存在, + // 单元测试不需要等任何自动 kill。cancelAndWait 那条用例仍然能验「外部取消」能 + // 杀掉子进程并 resolve,这是用户主动取消(按钮 / Esc)的覆盖。 + + // 以下三条锁的是同一个回归:结果 JSON 曾和用户代码共用 stdout,主进程取"最后一行" + // 解析。任何不以换行结尾的用户输出都会和 JSON 挤在同一行,合法 Python 报 + // "结果解析失败"。现在结果走 --out 文件,用户想怎么写 stdout 都不影响契约。 + describe('用户代码写 stdout 不破坏结果契约', () => { + itPy( + 'sys.stdout.write 不带换行', + async () => { + const result = await runAnalysis({ + ...BASE, + code: 'import sys\ndef f():\n sys.stdout.write("done")\n return 1\nf()\n' + }) + expect(result.status).toBe('ok') + expect(result.wallTime?.seconds).toBeGreaterThan(0) + }, + 30000 + ) + + itPy( + 'print(end="") 与 \\r 进度条', + async () => { + const result = await runAnalysis({ + ...BASE, + code: 'def f():\n for i in range(5):\n print("\\rprogress %d" % i, end="")\n return 1\nf()\n' + }) + expect(result.status).toBe('ok') + }, + 30000 + ) + + itPy( + '输出超过 8MB 上限也不再判失败', + async () => { + // 越过主进程 MAX_CHILD_OUTPUT(8MB) 的缓冲上限。以前必然 OutputOverflow; + // 现在结果在文件里,缓冲溢出只是内存保护,不该让运行失败。 + const result = await runAnalysis({ + ...BASE, + code: 'def f():\n print("x" * 9_000_000)\n return 1\nf()\n' + }) + expect(result.status).toBe('ok') + }, + 60000 + ) + }) + + // 退出路径要能等到"进程树真的没了"。cancel() 是 void,await 它等于 await undefined, + // 所以 before-quit 里原来那句 await cancel() 从来没有真的等过 taskkill。 + describe('cancelAndWait 可等待', () => { + it('没有在跑的子进程时也能 resolve', async () => { + await expect(cancelAndWait()).resolves.toBeUndefined() + }) + + itPy( + '取消正在跑的分析后 resolve,且运行方随即结束', + async () => { + const running = runAnalysis({ ...BASE, code: 'while True:\n pass\n' }) + // 给引擎一点时间真的 spawn 起来 + await new Promise((r) => setTimeout(r, 800)) + await expect(cancelAndWait()).resolves.toBeUndefined() + // 不关心它是 reject(AnalysisCancelledError)还是 resolve 成错误结果, + // 只要求它不再挂着 + await Promise.allSettled([running]) + }, + 20000 + ) + }) +}) diff --git a/src/main/profiler-service.ts b/src/main/profiler-service.ts new file mode 100644 index 0000000..b451f3f --- /dev/null +++ b/src/main/profiler-service.ts @@ -0,0 +1,499 @@ +import type { ChildProcess } from 'child_process' +import { spawn, execFile } from 'child_process' +import { mkdtemp, writeFile, rm, readFile } from 'fs/promises' +import { existsSync } from 'fs' +import { tmpdir } from 'os' +import { join, dirname } from 'path' +import type { AnalysisResult, ProgressEvent, RunOptions } from '../shared/analysis' +import { isAnalysisResult } from '../shared/analysis' +import { IpcError } from '../shared/ipc' + +let current: ChildProcess | null = null + +// 串行化所有 runAnalysis 调用: +// - 之前只用 `if (current) killTree(current)` 防双跑,但 mkdtemp/writeFile +// 是 await 的,第二次调用进 spawn 之前 current 可能是 null,第一次 spawn +// 才追上就成"孤儿 child"(既不被 kill 也没 close 事件清理 workdir) +// - 改成 Promise 队列:前一次 finish 之后下一次才开始 spawn;cancel() 通过 +// 让"等待中的调用"直接 reject 来立即返回,不需要 spawn 也立刻 kill +let runQueue: Promise = Promise.resolve() + +// Cancel-fence:单调递增计数器,cancel() 提一次号。所有"已入队但还没 spawn" +// 的 runAnalysisImpl 在真正动 IO 之前先读 fence — 旧 fence 的请求直接 reject, +// 不创建 workdir、不写脚本、不 spawn。 +// 这条与 runIdRef 的差别:runIdRef 是 renderer 侧用来丢陈旧 resolve 的;这里是 +// 主进程侧用来在 spawn 之前把"还在排队等上一轮跑完"的请求也拒掉,避免 +// "上一轮跑完之前 cancel(),但 spawn 已经排队 → cancel 之后再 spawn 出来孤儿 child"。 +let cancelFence = 0 + +/** Walk upward from candidate dirs to locate the folder containing engine/runner.py. */ +export function resolveEngineRoot(): string { + const starts = [process.cwd(), __dirname] + for (const start of starts) { + let dir = start + for (let i = 0; i < 8; i++) { + if (existsSync(join(dir, 'engine', 'runner.py'))) return dir + const parent = dirname(dir) + if (parent === dir) break + dir = parent + } + } + // 找不到时抛出 IpcError 而不是默默返回 cwd 让 Python 报 ModuleNotFoundError。 + // 这样渲染层能展示一个有用的诊断(kind='engine_not_found'),而不是误导性的 import error。 + throw new IpcError( + 'engine_not_found', + `engine/runner.py 未在 ${starts.join(' / ')} 的祖先目录中找到;` + + `请确认应用是从仓库根目录启动、或打包脚本把 engine/ 放在了正确位置。`, + { searched: starts } + ) +} + +/** + * 砍掉整棵进程树。返回 Promise 是为了让"退出前必须杀干净"的调用方能等 —— + * Windows 上 taskkill 是另开一个进程做的,不等它回来就 app.quit() 的话 + * Python 子进程仍可能活到应用消失之后(孤儿)。 + * 不关心时机的调用方直接 void 掉即可。 + */ +function killTree(child: ChildProcess): Promise { + if (!child.pid) return Promise.resolve() + if (process.platform === 'win32') { + return new Promise((resolve) => { + execFile('taskkill', ['/pid', String(child.pid), '/T', '/F'], () => resolve()) + }) + } + try { + process.kill(-child.pid, 'SIGKILL') + } catch (e) { + try { + child.kill('SIGKILL') + } catch (innerErr) { + // 两条 kill 路径都失败:进程会成孤儿。下次 cancel() 找不到 current 也不会再试。 + // 留 warn 给开发者排查,不要静默吞 —— 之前完全静默让 SIGKILL 失效的场景无从定位。 + // eslint-disable-next-line no-console + console.warn('[profiler] kill failed, child may be orphaned', child.pid, e, innerErr) + } + } + return Promise.resolve() +} + +/** + * cancel 的可等待版本:拿到"进程树真的没了"这个保证。 + * 退出路径(before-quit)用这个,避免 taskkill 还没跑完应用就退了。 + */ +export function cancelAndWait(): Promise { + // fence 提号:所有还在排队的请求会在轮到它们时看到这个变化并立即 reject + cancelFence++ + const child = current + // 同步置空,与旧的 cancel() 语义一致(调用返回后 current 已是 null) + current = null + return child ? killTree(child) : Promise.resolve() +} + +export function cancel(): void { + // 返回 void 不是 Promise,因为 IPC handler 同步调用即可 —— 对应 IpcError 中 + // cancel 类型不需要 await。fence 提号和 current 置空都在同步段完成, + // 只有 Windows 下等 taskkill 收尾这一步是异步的,IPC 侧不需要等。 + void cancelAndWait() +} + +export async function runAnalysis( + opts: RunOptions, + onProgress?: (e: ProgressEvent) => void, + onStdout?: (chunk: string) => void, + engineRoot: string = resolveEngineRoot() +): Promise { + // 记录当前 fence 值 — 队列轮到时如果 fence 已经变了就立刻 reject + const fenceAtEnqueue = cancelFence + // 串行化:等上一次真正结束(resolve/reject)才开下一次 + const next = runQueue + .then(() => { + if (fenceAtEnqueue !== cancelFence) { + // 排队期间被 cancel 了 — 立即返回,不 spawn、不写 workdir + throw new AnalysisCancelledError() + } + return runAnalysisImpl(opts, onProgress, onStdout, engineRoot) + }) + .catch((err) => { + // 链式队列:吞掉错误避免断链 — 但要重新抛给当前 await 的调用方 + throw err + }) + // next 是 Promise(resolve 时 AnalysisResult,cancel reject 时 void); + // 串行队列只需要一个"是否在忙"的 sentinel,强制归一为 Promise,让 runQueue 类型保持稳定 + runQueue = next.then( + () => undefined, + () => undefined + ) + return next +} + +/** render-side 可以用 instanceof 区分"被取消"和真正的引擎错误,UI 上给不同文案 */ +export class AnalysisCancelledError extends Error { + constructor() { + super('分析已被取消') + this.name = 'AnalysisCancelledError' + } +} + +function runAnalysisImpl( + opts: RunOptions, + onProgress?: (e: ProgressEvent) => void, + onStdout?: (chunk: string) => void, + engineRoot: string = resolveEngineRoot() +): Promise { + // 注意:这里的 try/catch 只覆盖 IO 部分。spawn 之后的事件用 Promise 封装, + // 出错由 cleanup + reject 路径处理;workdir 创建失败的极端路径单独 try/catch。 + let workdir: string | null = null + return (async () => { + workdir = await mkdtemp(join(tmpdir(), 'pyprof-')) + const scriptPath = join(workdir, 'user_script.py') + try { + await writeFile(scriptPath, opts.code, 'utf-8') + } catch (err) { + // writeFile 抛错(盘满 / 权限):mkdtemp 已经创建了 workdir,要清掉 + if (workdir) + await rm(workdir, { recursive: true, force: true }).catch((e) => logCleanupFail(workdir!, e)) + throw err + } + return runChild(workdir, scriptPath, opts, onProgress, onStdout, engineRoot) + })().catch(async (err) => { + // 任何 IO 异常都确保不泄漏 workdir + if (workdir) await rm(workdir, { recursive: true, force: true }).catch((e) => logCleanupFail(workdir!, e)) + throw err + }) +} + +/** 临时目录清理失败(非阻塞):留下 warn 给开发者排查,避免静默吞噬。 */ +function logCleanupFail(path: string, err: unknown): void { + // eslint-disable-next-line no-console + console.warn('[profiler] workdir cleanup failed', path, err) +} + +function runChild( + workdir: string, + scriptPath: string, + opts: RunOptions, + onProgress?: (e: ProgressEvent) => void, + onStdout?: (chunk: string) => void, + engineRoot?: string +): Promise { + const root = engineRoot ?? resolveEngineRoot() + // 结果 JSON 走独立文件,不跟用户代码抢 stdout。 + // 之前引擎把结果 print 到 stdout,主进程取"最后一行"解析 —— 用户脚本里一句 + // sys.stdout.write("done") 或 print(x, end='') 就会和 JSON 挤在同一行, + // 合法的 Python 却报"结果解析失败"。 + const resultPath = join(workdir, 'result.json') + // 透传剖析范围:缺省 user(只归因用户脚本),用户开启"包含库函数"时改成 all。 + // 用 push 而不是 spread,避免 opts.scope 字段在类型上被严格收紧时被编译挡掉。 + // --workdir 告诉 runner 把校准脚本落在我们已经 mkdir 好的 workdir 里, + // 跟用户脚本一起被 cleanup() 清掉,runner 不用再 mkdtemp。 + const args = ['-m', 'engine.runner', '--script', scriptPath, '--out', resultPath, '--workdir', workdir] + if (opts.scope === 'all') args.push('--scope', 'all') + + const childEnv = buildChildEnv(root) + + return new Promise((resolvePromise, rejectPromise) => { + // 防止双击 / 快速重跑泄露上一轮子进程:React 端的 runIdRef 只压制 resolve, + // 不会杀 spawn。先杀旧的再启动新的。 + if (current) { + void killTree(current) + current = null + } + let child: ChildProcess + try { + child = spawn(opts.interpreter, args, { + cwd: root, + env: childEnv, + detached: process.platform !== 'win32' + }) + } catch (err) { + // spawn 同步抛(opts.interpreter 不是字符串 / spawn options 非法) + rm(workdir, { recursive: true, force: true }).catch((e) => logCleanupFail(workdir, e)) + rejectPromise(err) + return + } + current = child + const stdoutStream = child.stdout + const stderrStream = child.stderr + if (!stdoutStream || !stderrStream) { + // 极端情况:stdio: 'pipe' 没启用。理论上 default stdio 是 pipe,但 TS 类型 + // 标注为 nullable,老代码也没处理。这里显式补一个 reject,比 .on() 抛 + // "Cannot read properties of null" 友好。 + rm(workdir, { recursive: true, force: true }).catch((e) => logCleanupFail(workdir, e)) + rejectPromise(new Error('child stdout/stderr 不可读')) + return + } + + let stdout = '' + let stderr = '' + let stdoutOverflow = false + let stderrOverflow = false + // settled: 防止 error + close 两个出口互相覆盖 + let settled = false + + // 不再有自动超时:用户脚本可以跑任意长时间。 + // 取消路径完全交给 UI 上的「取消」按钮 + Esc 快捷键(→cancel() → killTree)。 + // 之前的 30s 常量是历史决定 —— 但用户反馈「要跑长时间任务」,硬截断反而 + // 是负体验:cProfile 自己写完 result.json 还要被外层误杀,落盘一半被当 timeout 抛掉。 + // 现在没有任何 setTimeout 兜底 kill,close 事件就是唯一出口。 + + const cleanup = () => { + // 仅当我们仍是 current 时清空:cancel + 立即重跑的极端时序下, + // 老 child 的 close 事件晚到,若无条件置 null 会把新 child 引用抹掉, + // 导致后续 cancel() 找不到目标进程。 + if (current === child) current = null + rm(workdir, { recursive: true, force: true }).catch((e) => logCleanupFail(workdir, e)) + } + + const finish = (action: () => void) => { + if (settled) return + settled = true + cleanup() + action() + } + + // 缓冲区上限:用户脚本 print 几 GB 会让 Electron 主进程 OOM 然后整个应用崩 — + // 之前 stdout/stderr 是无限累积的,只有出口(slice(-N))截了一段。 + // 8MB 已经远超任何健康脚本输出;超过就停止追加但保留尾部,方便 debug。 + const MAX_CHILD_OUTPUT = 8 * 1024 * 1024 + const appendCapped = (cur: string, chunk: string): { value: string; overflowed: boolean } => { + const next = cur + chunk + if (next.length > MAX_CHILD_OUTPUT) { + // 保留尾部 MAX_CHILD_OUTPUT/2 字节,仍能带出有用的诊断信息 + return { value: next.slice(-MAX_CHILD_OUTPUT / 2), overflowed: true } + } + return { value: next, overflowed: false } + } + + stdoutStream.setEncoding('utf-8') + stdoutStream.on('data', (d: string) => { + const r = appendCapped(stdout, d) + stdout = r.value + stdoutOverflow = stdoutOverflow || r.overflowed + // 流式推送给渲染层(左侧 RunConsole 实时滚动)。appendCapped 已经把总量 + // 压到 8MB,所以这里直接 forward 完整 chunk,渲染端做最终 buffer 即可。 + // onStdout throw 不能让 child 死掉 —— 包一层 try/catch,异常仅 warn。 + if (onStdout) { + try { + onStdout(d) + } catch (err) { + // eslint-disable-next-line no-console + console.warn('[profiler] onStdout callback threw, ignoring', err) + } + } + }) + + // 把跨 data 事件的半行拼回去 — 之前逐 chunk split 会让 "PROGR|ESS lines 70\n" 两半 + // 都匹配不到 ^PROGRESS,丢事件,进度条卡住。 + let progressTail = '' + const PROGRESS_RE = /^PROGRESS\s+(\S+)\s+(\d+)/ + // progressTail 的上限。stderr 整体有 appendCapped 兜着,但这个"未完成行"缓冲 + // 之前是无界的:用户代码往 stderr 写一个不含换行的超大块(例如 + // sys.stderr.write('x' * 10**9))就能把主进程吃到 OOM。 + // PROGRESS 行只有几十字节,留 64KB 远够拼回任何被切开的半行。 + const MAX_PROGRESS_TAIL = 64 * 1024 + stderrStream.setEncoding('utf-8') + stderrStream.on('data', (d: string) => { + const r = appendCapped(stderr, d) + stderr = r.value + stderrOverflow = stderrOverflow || r.overflowed + if (!onProgress) return + // 即使总体已 overflow,progress 行的解析仍然继续 — 用户能看到引擎在跑 + progressTail += d + const parts = progressTail.split(/\r?\n/) + progressTail = parts.pop() ?? '' + // 尾部仍然超限说明这一大块里根本没有换行,不可能是 PROGRESS 行。 + // 只保留末尾一段,既防无界增长,又不影响后续真正的半行拼接。 + if (progressTail.length > MAX_PROGRESS_TAIL) { + progressTail = progressTail.slice(-MAX_PROGRESS_TAIL) + } + for (const line of parts) { + const m = line.match(PROGRESS_RE) + if (m) { + // 夹紧:上游约定 PROGRESS 行只有整数,但 line_profiler 自己的 stderr 噪声如果 + // 哪天沾上 PROGRESS 前缀(极小概率),或者引擎被扩展到打印 50.x,都不应该 + // 让 UI 出现 `NaN%` 或负数。 + const n = Number(m[2]) + onProgress({ phase: m[1], pct: Number.isFinite(n) ? Math.max(0, Math.min(100, n)) : 0 }) + } + } + }) + + child.on('error', (err) => { + // Electron IPC 不会传递 Error 上挂的自定义属性(如 .stderr),所以这里不要 + // 通过 reject 抛带 stderr 字段的 Error — renderer 那边只能拿到 message。 + // 改成 resolve 一个 status='runtime_error' 的 AnalysisResult,把 stderrTail + // 放进 error 对象,renderer 一处处理所有失败 UI。 + const msg = `${err.message}\n${stderr.slice(-500)}`.trim() + finish(() => resolvePromise(makeRuntimeError('EngineError', msg, stderr))) + }) + + child.on('close', async (code) => { + const codeDesc = code === null ? '被信号终止' : `exit ${code}` + + // 结果是文件而不是 stdout 的最后一行,所以先读文件:读到了就是成功, + // 哪怕用户脚本把 stdout 刷爆了(overflow 只是主进程内存保护,不再是失败原因)。 + // 必须在 finish() 之前读 —— finish → cleanup 会把 workdir 整个 rm 掉。 + // + // 没有自动超时:进程是被 cancel(用户中途点「取消」)还是被用户脚本正常 exit + // 触发的 close,都走这条同一路径。区别只是 result.json 是否完整: + // - 完整 → resolve 出 result + // - 写一半被 cancel 打断(readFile 抛 / JSON 不合法)→ runtime_error, + // 但 render 侧 runIdRef guard 会把这次 resolve 吞掉,UI 仍是 idle, + // 用户不会看到「点取消却报错」的混淆 UI。 + let raw: string + try { + raw = await readFile(resultPath, 'utf-8') + } catch { + // 引擎没写出结果:脚本崩到连兜底 JSON 都没发出、被用户中途取消杀掉、或盘写不进去 + // —— 全部走 runtime_error,让 UI 给出 stderr 折叠区排查。 + // 注意:render 侧的 runIdRef guard 会把「中途 cancel」造成的这次 resolve 吞掉, + // 用户看到的依然是 idle 状态,不会把这个当成运行错误。 + if (stdoutOverflow || stderrOverflow) { + const which = stdoutOverflow ? 'stdout' : 'stderr' + finish(() => + resolvePromise( + makeRuntimeError( + 'OutputOverflow', + `子进程 ${which} 输出超过 ${MAX_CHILD_OUTPUT / 1024 / 1024} MB 上限(用户脚本可能疯狂打印),且引擎未产出结果 (${codeDesc})`, + stderr + ) + ) + ) + return + } + // 同样走 resolve + runtime_error,避免自定义 Error 属性在 IPC 上丢失 + finish(() => resolvePromise(makeRuntimeError('NoOutput', `引擎无输出 (${codeDesc})`, stderr))) + return + } + + try { + const parsed: unknown = JSON.parse(raw) + // schema 漂移保护:JSON 解析成功但形状不对(schemaVersion 不对 / 关键字段缺失) + // 时不要直接把 unknown 强转 AnalysisResult,renderer 端会撞 "Cannot read + // property 'wallTime' of undefined"。用 isAnalysisResult 走 runtime_error + // 路径,UI 给用户看到的是 "结果结构不匹配",而不是冷不丁的白屏 + if (!isAnalysisResult(parsed)) { + const shape = describeShape(parsed) + const msg = `结果结构不匹配 (${codeDesc}): ${shape}` + finish(() => resolvePromise(makeRuntimeError('ParseError', msg, stderr))) + return + } + // 拿到合法 result:这就是用户已经跑出来的真实数据,直接返回。 + finish(() => resolvePromise(parsed)) + return + } catch (e) { + const msg = `结果解析失败 (${codeDesc}): ${(e as Error).message}` + finish(() => resolvePromise(makeRuntimeError('ParseError', msg, stderr))) + } + }) + }) +} + +/** + * 把 stderr 整理成给用户看的"引擎日志"。 + * + * PROGRESS 行是主进程和引擎之间的内部协议(见上面的 PROGRESS_RE),对用户没有任何意义。 + * 之前直接把原始 stderr 塞进 stderrTail,于是超时错误页里显示的是 + * `PROGRESS timing 10` —— 超时场景下引擎往往还没来得及打真正的报错, + * 这几行进度协议就成了日志的全部内容,用户看到一堆看不懂的东西, + * 真正有用的 traceback 反而被 2000 字节的截断挤掉。 + */ +export function userFacingStderr(stderr: string): string { + return stderr + .split(/\r?\n/) + .filter((line) => !/^PROGRESS\s+\S+\s+\d+/.test(line)) + .join('\n') + .trim() +} + +/** + * 子进程环境变量最小白名单:避免把 GITHUB_TOKEN / AWS_* / *_KEY 这类 + * 父进程 secret 透给用户代码(之前的 `{ ...process.env, ... }` 会全继承)。 + * 留下的字段都是 Python 解释器 / Windows DLL 解析实际需要的。 + * + * PYTHONIOENCODING 只管标准流的编码;Windows 上 sys.stdout.buffer 底层仍可能 + * 走 cp936,用户代码 print 中文/emoji 会乱码或抛 UnicodeEncodeError。 + * PYTHONUTF8=1 (PEP 540, Python 3.7+) 强制整个解释器进 UTF-8 模式。 + */ +function buildChildEnv(root: string): NodeJS.ProcessEnv { + const env: NodeJS.ProcessEnv = { + PYTHONPATH: root, + PYTHONIOENCODING: 'utf-8', + PYTHONUTF8: '1', + PYTHONUNBUFFERED: '1' + } + for (const k of CHILD_ENV_INHERIT_KEYS) { + const v = process.env[k] + if (v !== undefined) env[k] = v + } + return env +} + +/** 从父进程透传给子进程的 env 键名白名单 —— 其余一律不带。 */ +const CHILD_ENV_INHERIT_KEYS: readonly string[] = [ + 'PATH', + 'SystemRoot', + 'HOME', + 'TEMP', + 'TMP', + 'LANG', + 'LC_ALL' +] + +/** + * 任何"engine 路径上"的错误(spawn 失败 / 无输出 / 解析失败 / child.on('error'))都 + * 用这个 helper 构造一个 status='runtime_error' 的 AnalysisResult,而不是 reject。 + * + * 原因:Electron ipcMain.handle 的 reject 路径只把 Error.message 跨 IPC 序列化过去, + * 自定义属性(如 .stderr)会丢失。把 stderrTail 放进 result.error 字段,renderer 走 + * 统一的 ErrorBanner 渲染路径,不需要为每种 IPC 错误再写一次 UI。 + */ +function makeRuntimeError(type: string, message: string, stderr = ''): AnalysisResult { + return makeErrorResult('runtime_error', type, message, stderr) +} + +/** + * 统一的「合成错误结果」构造器 —— makeRuntimeError 共用。 + * schemaVersion=4 与 Python engine SCHEMA_VERSION 对齐,合成结果混着旧 version + * 会让 UI 一次运行里看到两种 version。 + */ +function makeErrorResult( + status: AnalysisResult['status'], + errorType: string, + message: string, + stderr: string +): AnalysisResult { + const tail = userFacingStderr(stderr) + return { + schemaVersion: 4, + environment: { python: '', platform: '', processor: '', timerResolution: 0 }, + config: {}, + status, + error: { + type: errorType, + message, + ...(tail ? { stderrTail: tail.slice(-2000) } : {}) + }, + wallTime: null, + functions: [], + flame: null + } +} + +/** + * 给"形状不对"的 JSON 输出个简短诊断(防止用户看到"Cannot read property X of undefined" + * 这种没头没尾的崩溃)。只取前几个键 + 主要类型,truncate 到 200 字符避免日志撑爆。 + */ +function describeShape(x: unknown): string { + if (x === null) return 'null' + if (typeof x !== 'object') return `${typeof x}: ${String(x).slice(0, 80)}` + const keys = Object.keys(x).slice(0, 8) + const sig = keys + .map((k) => { + const v = (x as Record)[k] + const t = Array.isArray(v) ? `Array(${(v as unknown[]).length})` : typeof v + return `${k}:${t}` + }) + .join(', ') + return sig.length > 200 ? sig.slice(0, 200) + '…' : sig +} diff --git a/src/main/terminal.test.ts b/src/main/terminal.test.ts new file mode 100644 index 0000000..04b2991 --- /dev/null +++ b/src/main/terminal.test.ts @@ -0,0 +1,252 @@ +import type * as cp from 'child_process' +import { openTerminal, type TerminalDeps } from './terminal' + +/** + * 跨平台 spawn 行为单元测试。 + * + * 平台分支(process.platform 在不同 OS 测出来不一样)用 vi.stubGlobal 切掉不现实, + * 改用 DI 注入 TerminalDeps + 直接走对应的 openWindows/openMac/openLinux 间接覆盖: + * - openTerminal 在每个平台上各跑一次,验证 spawn 调用参数 + * - 不在测试里 import process.platform 默认值,转而依赖 DI 验证三个分支各自的代码路径 + * + * 关于 mock 形状: + * - spawn 需要返回带 .unref() 方法的对象(否则 terminal.ts 调 unref 抛 TypeError) + * - execFile 是 promisified,这里 mock 返回 Promise<{ stdout, stderr }> + */ + +function makeFakeChild(): cp.ChildProcess { + // 只用 unref,其他事件都不挂。Node 的 ChildProcess 类型很复杂,这里 cast 出去 + return { unref: () => undefined } as unknown as cp.ChildProcess +} + +function makeDeps(overrides: Partial = {}): { + deps: TerminalDeps + spawnCalls: Array<{ cmd: string; args: readonly string[]; opts: cp.SpawnOptions }> + execCalls: Array<{ cmd: string; args: readonly string[] }> +} { + const spawnCalls: Array<{ cmd: string; args: readonly string[]; opts: cp.SpawnOptions }> = [] + const execCalls: Array<{ cmd: string; args: readonly string[] }> = [] + const deps: TerminalDeps = { + spawn: ((cmd: string, args: readonly string[], opts: cp.SpawnOptions) => { + spawnCalls.push({ cmd, args, opts }) + return makeFakeChild() + }) as unknown as typeof cp.spawn, + execFile: ((cmd: string, args: readonly string[]) => { + execCalls.push({ cmd, args }) + // 默认成功:返回带 stdout 的对象。Linux 探测用 which,stdout 内容我们不在乎。 + return Promise.resolve({ stdout: '/usr/bin/' + args[0], stderr: '' }) + }) as unknown as typeof cp.execFile, + ...overrides + } + return { deps, spawnCalls, execCalls } +} + +describe('openTerminal', () => { + describe('Windows', () => { + // 在 Windows runner 上 process.platform === 'win32',openTerminal 会走 openWindows 分支 + if (process.platform !== 'win32') { + it.skip('Windows 分支:本机不是 win32,跳过', () => {}) + return + } + + it('用 cmd /c start "" cmd /K 开新窗口', async () => { + const { deps, spawnCalls } = makeDeps() + await openTerminal('winget install Python.Python.3.12', 'cmd', deps) + expect(spawnCalls).toHaveLength(1) + const call = spawnCalls[0] + expect(call.cmd).toBe('cmd.exe') + expect(call.args).toEqual(['/c', 'start', '""', 'cmd.exe', '/k', 'winget install Python.Python.3.12']) + // detached + stdio:ignore 三件套:父进程退出不影响终端 + expect(call.opts.detached).toBe(true) + expect(call.opts.stdio).toBe('ignore') + }) + + it('空命令也能开窗口', async () => { + const { deps, spawnCalls } = makeDeps() + await openTerminal('', 'cmd', deps) + expect(spawnCalls).toHaveLength(1) + // 不包含 echo / winget 等;只有 /k 在末尾 + expect(spawnCalls[0].args).toEqual(['/c', 'start', '""', 'cmd.exe', '/k']) + }) + + it('shell=powershell 时用 powershell.exe -NoExit -Command ""', async () => { + const { deps, spawnCalls } = makeDeps() + await openTerminal('winget install Python.Python.3.12', 'powershell', deps) + expect(spawnCalls).toHaveLength(1) + const call = spawnCalls[0] + // 外层 spawn 还是 cmd.exe —— 我们用 cmd /c start 把 powershell 拉进新窗口 + expect(call.cmd).toBe('cmd.exe') + // start 内层: powershell.exe + -NoExit + -Command "" + expect(call.args).toEqual([ + '/c', + 'start', + '""', + 'powershell.exe', + '-NoExit', + '-Command', + '"winget install Python.Python.3.12"' + ]) + expect(call.opts.detached).toBe(true) + expect(call.opts.stdio).toBe('ignore') + }) + + it('shell=powershell 空命令时只开 powershell -NoExit,无 -Command', async () => { + const { deps, spawnCalls } = makeDeps() + await openTerminal('', 'powershell', deps) + expect(spawnCalls).toHaveLength(1) + expect(spawnCalls[0].args).toEqual(['/c', 'start', '""', 'powershell.exe', '-NoExit']) + }) + + it('shell 缺省时走 cmd(向后兼容老调用方)', async () => { + const { deps, spawnCalls } = makeDeps() + await openTerminal('echo hi', 'cmd', deps) + expect(spawnCalls).toHaveLength(1) + expect(spawnCalls[0].cmd).toBe('cmd.exe') + expect(spawnCalls[0].args).toEqual(['/c', 'start', '""', 'cmd.exe', '/k', 'echo hi']) + }) + }) + + describe('macOS', () => { + if (process.platform !== 'darwin') { + it.skip('macOS 分支:本机不是 darwin,跳过', () => {}) + return + } + + it('用 osascript 让 Terminal.app 执行命令', async () => { + const { deps, spawnCalls } = makeDeps() + await openTerminal('brew install python3', 'cmd', deps) + // 两次 spawn:do script + activate + expect(spawnCalls.length).toBeGreaterThanOrEqual(2) + const doScriptCall = spawnCalls.find((c) => c.args[0] === '-e' && c.args[1].includes('do script')) + expect(doScriptCall).toBeDefined() + expect(doScriptCall!.args[1]).toContain('tell application "Terminal"') + expect(doScriptCall!.args[1]).toContain('brew install python3') + }) + + it('命令里的双引号被转义为 \\",避免切 AppleScript 字符串', async () => { + const { deps, spawnCalls } = makeDeps() + // 用户命令里含双引号 — 透到 AppleScript 必须转义,否则切串后命令走样 + await openTerminal('echo "hi" | tee /tmp/log', 'cmd', deps) + const doScriptCall = spawnCalls.find((c) => c.args[0] === '-e') + expect(doScriptCall).toBeDefined() + // AppleScript 字符串里的 " 应被 escape 成 \" + expect(doScriptCall!.args[1]).toContain('\\"hi\\"') + expect(doScriptCall!.args[1]).not.toMatch(/echo "hi"/) // 原文里的 " 不能裸出现 + }) + }) + + describe('Linux', () => { + if (process.platform !== 'linux') { + it.skip('Linux 分支:本机不是 linux,跳过', () => {}) + return + } + + it('优先选 x-terminal-emulator', async () => { + const { deps, spawnCalls, execCalls } = makeDeps() + await openTerminal('sudo apt install python3', 'cmd', deps) + // which 探测一次就成功,x-terminal-emulator 在 PATH 上 + expect(execCalls[0]).toEqual({ cmd: 'which', args: ['x-terminal-emulator'] }) + // spawn 调 x-terminal-emulator + bash -c + expect(spawnCalls).toHaveLength(1) + expect(spawnCalls[0].cmd).toBe('x-terminal-emulator') + expect(spawnCalls[0].args).toEqual(['-e', 'bash', '-c', 'sudo apt install python3\nexec bash']) + }) + + it('探测全部失败时 reject,错误带尝试列表', async () => { + const failingDeps: TerminalDeps = { + spawn: (() => makeFakeChild()) as unknown as typeof cp.spawn, + execFile: (() => Promise.reject(new Error('not found'))) as unknown as typeof cp.execFile + } + await expect(openTerminal('whatever', 'cmd', failingDeps)).rejects.toThrow(/未找到可用的终端模拟器/) + await expect(openTerminal('whatever', 'cmd', failingDeps)).rejects.toThrow(/x-terminal-emulator/) + await expect(openTerminal('whatever', 'cmd', failingDeps)).rejects.toThrow(/xterm/) + }) + + it('x-terminal-emulator 缺失时回退到 gnome-terminal', async () => { + let whichIndex = 0 + const partialDeps: TerminalDeps = { + spawn: (() => makeFakeChild()) as unknown as typeof cp.spawn, + execFile: ((cmd: string, args: readonly string[]) => { + // 第一次 which x-terminal-emulator 失败,第二次 which gnome-terminal 成功 + if (cmd === 'which' && args[0] === 'gnome-terminal' && whichIndex === 1) { + return Promise.resolve({ stdout: '/usr/bin/gnome-terminal', stderr: '' }) + } + whichIndex++ + return Promise.reject(new Error('not found')) + }) as unknown as typeof cp.execFile + } + await openTerminal('echo hi', 'cmd', partialDeps) + // 已经走到 spawn,但我们没传 spawnCalls 进去所以不验证具体调用,只验证不 reject + }) + + it('gnome-terminal 用 -- 而不是 -e 分隔参数', async () => { + const fixedDeps: TerminalDeps = { + spawn: (() => makeFakeChild()) as unknown as typeof cp.spawn, + execFile: ((cmd: string, args: readonly string[]) => { + if (cmd === 'which' && args[0] === 'x-terminal-emulator') { + return Promise.reject(new Error('no')) + } + if (cmd === 'which' && args[0] === 'gnome-terminal') { + return Promise.resolve({ stdout: '/usr/bin/gnome-terminal', stderr: '' }) + } + return Promise.reject(new Error('not in path')) + }) as unknown as typeof cp.execFile + } + // 用一个 spy 替代 spawn 来抓调用 + const calls: Array<{ cmd: string; args: readonly string[] }> = [] + fixedDeps.spawn = ((cmd: string, args: readonly string[]) => { + calls.push({ cmd, args }) + return makeFakeChild() + }) as unknown as typeof cp.spawn + await openTerminal('echo hi', 'cmd', fixedDeps) + expect(calls).toHaveLength(1) + expect(calls[0].cmd).toBe('gnome-terminal') + // 关键断言:首参是 -- 而不是 -e + expect(calls[0].args[0]).toBe('--') + }) + + it('其他终端(xterm / konsole / xfce4-terminal)用 -e', async () => { + for (const term of ['xterm', 'konsole', 'xfce4-terminal']) { + const calls: Array<{ cmd: string; args: readonly string[] }> = [] + const deps: TerminalDeps = { + spawn: ((cmd: string, args: readonly string[]) => { + calls.push({ cmd, args }) + return makeFakeChild() + }) as unknown as typeof cp.spawn, + execFile: ((cmd: string, args: readonly string[]) => { + // 前面的全部 reject,只在目标 term 上成功 + const order = ['x-terminal-emulator', 'gnome-terminal', 'konsole', 'xfce4-terminal', 'xterm'] + if (cmd === 'which' && args[0] === term) { + return Promise.resolve({ stdout: '/usr/bin/' + term, stderr: '' }) + } + // 顺序在 term 之前的也 reject;之后的不会跑(因为已经找到了) + const idx = order.indexOf(term) + const argIdx = order.indexOf(args[0]) + if (argIdx < idx || argIdx === -1) { + return Promise.reject(new Error('not in path')) + } + return Promise.resolve({ stdout: '/usr/bin/' + args[0], stderr: '' }) + }) as unknown as typeof cp.execFile + } + await openTerminal('echo hi', 'cmd', deps) + expect(calls).toHaveLength(1) + expect(calls[0].args[0]).toBe('-e') + } + }) + }) + + describe('默认 deps', () => { + it('不传 deps 也能走通(用真实 child_process,但 spawn 不会真的开窗口 — 测试环境 detached 后会失败/被 OS 吞)', async () => { + // 我们不验证真实 spawn 是否成功(那要看 OS),只验证调用路径不会立刻抛同步异常 + // — 真正开窗口失败由调用方通过 catch 处理 + if (process.platform === 'win32' || process.platform === 'darwin') { + // macOS: osascript 可能不在 PATH 上;Windows: cmd.exe 几乎一定在 + // 不做 hard assert,只是验证 sync 段不抛 + await expect(openTerminal('echo hi')).resolves.not.toThrow() + } else { + // Linux: 大概率没有任何终端模拟器,会 reject — 这是正确行为 + await openTerminal('echo hi').catch(() => undefined) + } + }) + }) +}) diff --git a/src/main/terminal.ts b/src/main/terminal.ts new file mode 100644 index 0000000..a7f8fc7 --- /dev/null +++ b/src/main/terminal.ts @@ -0,0 +1,172 @@ +import * as cp from 'child_process' +import { promisify } from 'util' + +/** 测试时可注入 mock。默认是真实的 child_process 引用,生产路径不感知。 */ +export interface TerminalDeps { + spawn: typeof cp.spawn + execFile: typeof cp.execFile +} + +const defaultDeps: TerminalDeps = { + spawn: cp.spawn, + execFile: cp.execFile +} + +const execFileAsync = promisify(cp.execFile) + +/** Windows 上要启哪个 shell。Mac / Linux 不用这个 — 由 OS 决定。 */ +export type WindowsShell = 'cmd' | 'powershell' + +/** + * 跨平台打开系统终端,可预填一条命令。窗口在命令跑完后保持打开(/K / -NoExit / exec bash)。 + * + * 用途:用户没装 Python 时,UI 上点一下 → 弹 cmd/Terminal/gnome-terminal, + * 命令已经预填好(`winget install ...` / `brew install ...`),回车即装。 + * + * 安全边界:不解析 shell 元字符(`&&` / `|` / `>` 等),由调用方负责构造安全命令 — + * 我们这里只接受简单字面量(plan 里的 installPythonCommand 输出正是这种形式)。 + * IPC 层额外做 string 类型 + 长度校验(参见 src/main/index.ts)。 + * + * 设计: + * - detached + stdio:'ignore' + unref() 三件套:父进程(Electron)退出后,终端窗口 + * 不被一起带走。spawn 拿到的子引用也要 unref(),否则 Node 会维持引用。 + * - 不等子进程 exit:打开就 resolve,UI 立刻恢复可交互。"窗口是否真的弹出来"由 OS 决定, + * 父进程无法可靠等待(Windows `start` / macOS osascript 都是 fire-and-forget)。 + */ +export async function openTerminal( + command: string = '', + shell: WindowsShell = 'cmd', + deps: TerminalDeps = defaultDeps +): Promise { + if (process.platform === 'win32') { + openWindows(command, shell, deps) + return + } + if (process.platform === 'darwin') { + await openMac(command, deps) + return + } + await openLinux(command, deps) +} + +/** + * Windows:`cmd /c start "" ` 模式开新窗口。 + * + * - 必须用 `cmd /c start ... ...` 才能真开新窗口;直接 + * `spawn("cmd", ["/k", cmd])` 会绑回父进程的关联控制台,Electron 没有 → 不可见 + * - `start` 的第一个位置参数是窗口标题,传 `""` 占位 — 不传会被吞掉首参 + * + * shell=cmd:`cmd.exe /K ` — /K 让 cmd 跑完命令后窗口留着 + * shell=powershell:`powershell.exe -NoExit -Command ""` — -NoExit 是 PS 里 /K 的等价物 + * + * PowerShell 的 -Command 参数需要包在双引号里(整个 -Command 值被当作 PS 表达式解析); + * 我们的命令字面量不含双引号(`winget install Python.Python.3.12`),直接套引号即可。 + * 含双引号的命令未来要加 escape(把内部 " 替换成 `\"` 后再外层 ")。 + */ +function openWindows(command: string, shell: WindowsShell, deps: TerminalDeps): void { + let innerCmd: string + let innerArgs: string[] + if (shell === 'powershell') { + innerCmd = 'powershell.exe' + // -NoExit 等价 cmd 的 /K;命令用双引号包成 -Command 的参数值 + innerArgs = command ? ['-NoExit', '-Command', `"${command}"`] : ['-NoExit'] + } else { + innerCmd = 'cmd.exe' + innerArgs = command ? ['/k', command] : ['/k'] + } + // cmd /c start "" + const args: string[] = ['/c', 'start', '""', innerCmd, ...innerArgs] + const child = deps.spawn('cmd.exe', args, { + detached: true, + stdio: 'ignore', + // windowsHide: false 是默认,显式写出来防止有人手滑改成 true 让窗口藏起来 + windowsHide: false + }) + // unref():不维持对子进程的引用,否则 Node 进程会等它退出 + child.unref() +} + +/** + * macOS:AppleScript 让 Terminal.app 开新 tab 执行命令。 + * + * - 选 osascript 而不是 `open -a Terminal`:后者只能开窗口不能预填命令 + * - activate 让窗口浮到前面;部分 macOS 版本不开新窗口 — 但 do script + * 保证有地方跑命令即可(可能是新 tab / 新窗口) + * - AppleScript 里 `"` 必须转义为 `\"`,否则用户命令里的引号会截断字符串 + * - 反引号 / 美元符 等 AppleScript 不直接解释,但保险起见一并 escape + * (避免某些边界场景被当成 AppleScript 控制字符) + */ +async function openMac(command: string, deps: TerminalDeps): Promise { + const escaped = escapeForAppleScript(command) + const script = command + ? `tell application "Terminal" to do script "${escaped}"` + : `tell application "Terminal" to do script ""` + // 两次 spawn 顺序无所谓,各自 detached + unref,父进程退出不影响 + const a = deps.spawn('osascript', ['-e', script], { + detached: true, + stdio: 'ignore' + }) + a.unref() + const b = deps.spawn('osascript', ['-e', 'tell application "Terminal" to activate'], { + detached: true, + stdio: 'ignore' + }) + b.unref() + // 即使函数体内没 await,也保持 async — 三个平台分支的返回类型签名要一致, + // openTerminal 顶层 await 不会因此出错;Promise.resolve() 让 await 关键字有意义 + await Promise.resolve() +} + +/** + * Linux:探测一组常见终端模拟器,挑第一个存在的用。 + * + * 候选优先级(都在 PATH 上时):x-terminal-emulator (Debian alternatives 系统) > + * gnome-terminal > konsole > xfce4-terminal > xterm + * + * 探测用 `which`,失败静默继续下一个。 + * + * 命令走 `bash -c "; exec bash"` 包装: + * - 空命令时用 `:`(bash 的 no-op)避免 bash -c 空串报 "no command" + * - `exec bash` 让窗口跑完原命令后留在 bash 提示符,不立即关闭 + * + * 参数分隔符差异:gnome-terminal 用 `--`,其他用 `-e`。混用会报 unknown option。 + */ +async function openLinux(command: string, deps: TerminalDeps): Promise { + const candidates = ['x-terminal-emulator', 'gnome-terminal', 'konsole', 'xfce4-terminal', 'xterm'] + let found: string | null = null + for (const term of candidates) { + try { + // which 0 exit code + 路径输出 = 存在 + await execFileAsync('which', [term], { timeout: 2000 }) + found = term + break + } catch { + continue + } + } + if (!found) { + throw new Error( + `未找到可用的终端模拟器(尝试 ${candidates.join(' / ')} 均失败)。` + + `请安装其中一个,或在系统终端手动运行命令。` + ) + } + + const shellCmd = `${command || ':'}\nexec bash` + const args = + found === 'gnome-terminal' + ? // gnome-terminal 用 -- 分隔后续参数(老版本才支持 -e) + ['--', 'bash', '-c', shellCmd] + : ['-e', 'bash', '-c', shellCmd] + const child = deps.spawn(found, args, { + detached: true, + stdio: 'ignore' + }) + child.unref() +} + +/** AppleScript 字符串字面量转义:`\` → `\\`,`"` → `\"`。 + * 不转义 `$` / 反引号 — AppleScript do script 不会解释它们, + * 转义反而会被当成命令的一部分传给 shell。 */ +function escapeForAppleScript(s: string): string { + return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"') +} diff --git a/src/preload/index.ts b/src/preload/index.ts new file mode 100644 index 0000000..64ee09c --- /dev/null +++ b/src/preload/index.ts @@ -0,0 +1,55 @@ +import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron' +import type { FileReadRequest, ProgressEvent, RendererApi, RunOptions } from '../shared/analysis' + +const api: RendererApi = { + detectInterpreters: () => ipcRenderer.invoke('interp:detect'), + pickInterpreter: () => ipcRenderer.invoke('interp:pick'), + analyze: (opts: RunOptions) => ipcRenderer.invoke('analyze:run', opts), + cancel: () => ipcRenderer.invoke('analyze:cancel'), + onProgress: (cb: (e: ProgressEvent) => void) => { + const listener = (_e: IpcRendererEvent, payload: ProgressEvent) => cb(payload) + ipcRenderer.on('analyze:progress', listener) + return () => ipcRenderer.removeListener('analyze:progress', listener) + }, + onStdout: (cb: (chunk: string) => void) => { + // 与 onProgress 同型:每次主进程 'analyze:stdout' 事件触发就回调一行 utf-8 字符串。 + // RunConsole 用它做实时滚动 + 累积 buffer。 + const listener = (_e: IpcRendererEvent, chunk: string) => cb(chunk) + ipcRenderer.on('analyze:stdout', listener) + return () => ipcRenderer.removeListener('analyze:stdout', listener) + }, + // 打开系统终端:Settings 里安装 Python、TopBar「打开终端」按钮都走它。 + // 命令可选 —— 不传就开一个默认 shell 窗口。 + // shell 仅 Windows 生效 —— 选 cmd.exe / powershell.exe;Mac / Linux 由 OS 决定。 + openTerminal: (command?: string, shell?: 'cmd' | 'powershell') => + ipcRenderer.invoke('terminal:open', command ?? '', shell ?? 'cmd'), + // 读 Python 源文件(stdlib / 第三方包热点打开 tab 用)。主进程已做路径 / 大小校验, + // 返回值是 discriminated union —— 失败不 throw,kind 字段告诉 UI 怎么展示。 + readFile: (req: FileReadRequest) => ipcRenderer.invoke('file:read', req), + // process.platform 在 preload 上下文里可访问(sandbox 模式下仍保留的 Electron-managed 属性), + // 无需 IPC:一个常量字符串,启动时定一次终身不变 + platform: process.platform, + // 顶栏左上角的图标 PNG data URL(主进程从 icon.ico 解码 + toDataURL 后传过来)。 + // null 表示读不到 —— renderer 退回 SVG fallback。 + getAppIcon: () => ipcRenderer.invoke('app:icon') as Promise, + // 在系统默认浏览器里打开外部 URL(设置「关于」section 的开发者主页链接)。 + // 主进程做协议白名单(只放行 http/https)+ URL.parse 校验,失败 throw IpcError。 + openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url) as Promise, + // 自定义窗口控制(顶栏右上角 minimize / maximize / close)。 + // minimize / close / toggleMaximize:request-response + // onMaximizeChanged:event push(OS 触发的最大化状态变更也同步给 renderer) + windowControls: { + minimize: () => ipcRenderer.invoke('window:minimize'), + toggleMaximize: () => + ipcRenderer.invoke('window:maximize-toggle') as Promise<{ isMaximized: boolean }>, + close: () => ipcRenderer.invoke('window:close'), + isMaximized: () => ipcRenderer.invoke('window:is-maximized') as Promise, + onMaximizeChanged: (cb: (isMaximized: boolean) => void) => { + const listener = (_e: IpcRendererEvent, isMaximized: boolean) => cb(isMaximized) + ipcRenderer.on('window:maximize-changed', listener) + return () => ipcRenderer.removeListener('window:maximize-changed', listener) + } + } +} + +contextBridge.exposeInMainWorld('api', api) diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..4f550a8 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,43 @@ + + + + + + + + Python 耗时可视化分析 + + + + +
+ + + diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx new file mode 100644 index 0000000..4cadb06 --- /dev/null +++ b/src/renderer/src/App.tsx @@ -0,0 +1,521 @@ +import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState, type RefObject } from 'react' +import type { CodePanelHandle } from './components/CodePanel' +import EditorTabs, { type EditorTabView } from './components/EditorTabs' +import MinimalRunConfig, { type RunConfigHandle } from './components/MinimalRunConfig' +import ResultsPanel from './components/ResultsPanel' +import TopBar from './components/TopBar' +import SettingsModal from './components/SettingsModal' +import EmptyState from './components/EmptyState' +import ErrorBanner from './components/ErrorBanner' +import FailureResult from './components/FailureResult' +import Splitter from './components/Splitter' +import RunConsole from './components/RunConsole' +import { useAnalysis } from './hooks/useAnalysis' +import { useExternalFiles } from './hooks/useExternalFiles' +import { useInterpreters } from './hooks/useInterpreters' +import { useResizableFraction } from './hooks/useResizableFraction' +import { useScope } from './hooks/useScope' +import { useShellPreference } from './hooks/useShellPreference' +import { useTheme } from './hooks/useTheme' +import { I18nProvider, useT } from './i18n' +import { resolveOrigin } from './utils/origin' +import { errorTitleForTab } from './utils/editorTabs' +import { DEMO_CODE } from './sample' +import type { FunctionNode, RunOptions } from '../../shared/analysis' + +/** + * 编辑器按需加载:monaco-editor 是首屏 bundle 的最大来源。 + * CodePanelLazy 顺带把有副作用的 monaco-setup 一起带进这个 chunk(详见该文件注释)。 + * React.lazy 会透传 ref,所以 editorRef 的 revealLine/clearHighlight 照旧可用; + * chunk 未就绪时 ref 为 null,调用点本来就用 ?. 兜着(和挂载前的行为一致)。 + */ +const CodePanel = lazy(() => import('./components/CodePanelLazy')) + +/** + * 编辑器 chunk 加载期间的骨架占位(Suspense fallback)。 + * + * 没有装饰 chrome —— 等宽字体的行占位直接铺满,避免 monaco 就绪时再跳一下。 + */ +function EditorSkeleton() { + const t = useT() + return ( +
+ + ) +} + +/** + * 用 ResizeObserver 跟踪元素当前 contentRect 的 width 或 height,返回 0 直到首次挂载后量到值。 + * 用于 splitter 把「像素 delta」换算成「fraction 增量」时拿分母 —— 容器尺寸变化(窗口 resize、 + * DevTools 开关、字号变更)都要跟上,不能依赖 mount 时的 getBoundingClientRect 一次性快照。 + */ +function useElementSize(ref: RefObject, axis: 'width' | 'height'): number { + const [size, setSize] = useState(0) + useEffect(() => { + const el = ref.current + if (!el) return + setSize(el.getBoundingClientRect()[axis]) + const ro = new ResizeObserver((entries) => { + const entry = entries[0] + if (entry) setSize(entry.contentRect[axis]) + }) + ro.observe(el) + return () => ro.disconnect() + }, [ref, axis]) + return size +} + +export default function App() { + // I18nProvider + ThemeProvider 套在 App 外面,让 App 内部所有 useT / useTheme 都拿到上下文 + return ( + + + + ) +} + +function AppInner() { + const t = useT() + const [code, setCode] = useState(DEMO_CODE) + const [selectedFuncId, setSelectedFuncId] = useState() + // 缓存最近一次运行的参数:失败页「再试一次」直接复用,不再 hack 触发隐藏按钮 + const [lastOpts, setLastOpts] = useState(null) + // SettingsModal open state —— 用 prop drilling 而非 Context,因为只 TopBar 和 Modal 两个消费点 + const [settingsOpen, setSettingsOpen] = useState(false) + const editorRef = useRef(null) + const runConfigRef = useRef(null) + const mainAreaRef = useRef(null) + // 编辑器 + 运行输出面板这一列的总高(高度方向 splitter 用)。贴在外层 wrapper 上, + // —— 因为高度 splitter 的像素 delta 需要除以「这一列的总高度」才能换算成 fraction。 + // 不贴在 #pyrof-editor-pane 上是因为它还要管宽度,语义上不如单独一个 ref 干净。 + const consoleAreaRef = useRef(null) + const { + result, + state, + progress, + errorMessage, + stderrTail, + stdout, + clearStdout, + resultScope, + run, + cancel, + reset + } = useAnalysis() + // 解释器列表提到 App 顶层:MinimalRunConfig mount 时取,不要每次 mount 重 detect + const interpreters = useInterpreters() + // 主题:useTheme 内部已经和 同步 + const { theme, setTheme } = useTheme() + // 剖析范围:useScope 内部已经持久化到 localStorage.pyrof.scope + const { scope, toggle: toggleScope } = useScope() + // 打开系统终端时用的 shell(Windows: cmd / PowerShell);非 Win 不生效 + const { shell, setShell } = useShellPreference() + + // 外部文件 tab 状态(stdlib / 第三方包热点打开的源码 tab)。 + // 用户代码 tab **不**在这里 —— 是 code state 单独持有。 + const { + externalTabs, + activeExternalId, + openFile: openExternalFile, + closeFile, + setActive: setActiveExternal, + consumePendingReveal, + closeAll: closeAllExternal + } = useExternalFiles() + + // —— 区域尺寸(编辑器 ↔ 结果区) —— + // 用 ResizeObserver 跟踪主区宽度,拖拽时按「像素 delta / 主区宽度」换算成比例 + // (窗口尺寸变化或 DevTools 开关 / 字号变更都会让宽度变;不是 mount 时一次性 getBoundingClientRect) + const mainAreaWidth = useElementSize(mainAreaRef, 'width') + const { fraction: leftFrac, applyDelta: applyLeftDelta } = useResizableFraction('pyrof.split.main', { + default: 0.5, + min: 0.2, + max: 0.8 + }) + + // —— 区域尺寸(编辑器 ↔运行输出面板) —— + // 同样的 fraction + ResizeObserver 模式,跟主区 splitter 完全对称。min=0 是因为 + // 用户可能想把输出区拖到完全消失(隐藏),最大留 60% 是因为太高会挤压编辑器。 + // consoleFraction 是「console 高度 / 该列总高」,applyDelta(dy, height) 拿 + // 像素增量除以总高换算成 fraction。 + const consoleAreaHeight = useElementSize(consoleAreaRef, 'height') + const { fraction: consoleFrac, applyDelta: applyConsoleDelta } = useResizableFraction( + 'pyrof.split.console', + { + default: 0.25, + min: 0, + max: 0.6 + } + ) + // 「收起」按钮的折叠状态:放在 App 里是为了让 wrapper 高度跟着收缩 —— 只藏 body 的话 + // consoleFrac * 100% 还占着列高,header 下面会留一大块空白,看起来没收完。 + // 高度降到 auto 让 wrapper 只剩 header(≈44px),editor 段在 flex-1 里把多出来的空间拿走。 + const [consoleCollapsed, setConsoleCollapsed] = useState(false) + + // 全局快捷键:Ctrl/Cmd+Enter 运行;Esc 取消 + // state 通过 ref 引用:避免每次 state 变化都 remove + addEventListener(每帧 rebuild 监听是浪费)。 + const stateRef = useRef(state) + stateRef.current = state + + // 编辑器内 Ctrl+Enter:Monaco 会拦截这个键(默认行为是「下方插入新行」),必须通过 addCommand 接管; + // 走 window keydown 收不到,因为 Monaco 在 capture 阶段 stopPropagation 了。 + const triggerRun = useCallback(() => { + if (stateRef.current === 'running') void cancel() + else runConfigRef.current?.run() + }, [cancel]) + + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + // SettingsModal 打开时,Esc 已经被 Modal 在 capture 阶段 stopPropagation 截走, + // 不会到这里取消运行(保留 Esc 取消运行的语义由 Modal 决定)。 + const cmdKey = e.metaKey || e.ctrlKey + const target = e.target as HTMLElement | null + const inMonaco = !!target?.closest('.monaco-editor') + if (cmdKey && e.key === 'Enter') { + if (inMonaco) return + e.preventDefault() + triggerRun() + } else if (e.key === 'Escape' && stateRef.current === 'running') { + e.preventDefault() + void cancel() + } + } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [cancel, triggerRun]) + + const selectHotspot = useCallback( + (fn: FunctionNode) => { + setSelectedFuncId(fn.id) + const origin = resolveOrigin(fn) + // 路由按 origin 分流: + // - user: 文件路径已不存在(profiler-service 在 tmpdir 建的脚本被 rm 掉), + // 只能 highlight 当前用户代码编辑器里的行。 + // - builtin / frozen: cProfile 给的是虚拟帧,没有源文件。静默 no-op + // —— toast 太吵,每次 run 都有几条。 + // - stdlib / third_party / other: 打开对应文件 tab,编辑器只读 + reveal line。 + if (origin === 'user') { + editorRef.current?.revealLine(fn.line) + return + } + if (origin === 'builtin' || origin === 'frozen') return + if (!fn.file) return + openExternalFile(fn.file, fn.line, origin) + }, + [openExternalFile] + ) + + const onRun = useCallback( + (opts: RunOptions) => { + setLastOpts(opts) + setSelectedFuncId(undefined) + editorRef.current?.clearHighlight() + // 注意:不清外部 tab —— 用户可能正在读源文件比对,新一轮结果不影响已开的源文件。 + void run(opts) + }, + [run] + ) + + // 「范围已切换」提示条上的「以新范围重跑」按钮 —— 用最近一次 run 的 opts + // 把 scope 换成当前的,然后再跑一次。不在 onRun 里偷换 scope 是为了不让 + // 普通 Retry 路径跟着 scope toggle 走(错误页 Retry 应当 100% 复用旧参数)。 + const onRerunWithCurrentScope = useCallback(() => { + if (!lastOpts) return + void onRun({ ...lastOpts, scope }) + }, [lastOpts, scope, onRun]) + + // 「加载示例 / 新建空白」共用一条复位通道 —— 这两个动作是"重置整个工作区"语义, + // 顺手把外部 tab 也清空(用户切到完全不同的代码,原来开的 numpy / json tab 也没用了)。 + // 用工厂函数接受目标 code,deps 只跟 reset 走,避免两个 callback 各持一份 closure。 + // 完全空串而不是 '\n':'\n' 会让 Monaco 渲染一个 phantom 行(视觉上「两行空着」), + // 且 Monaco 对 '' 的处理已经合理——光标落在 line 1 column 1,行号正常。 + const resetWorkspace = useCallback( + (nextCode: string) => { + setCode(nextCode) + editorRef.current?.clearHighlight() + setSelectedFuncId(undefined) + setLastOpts(null) + closeAllExternal() + reset() + }, + [reset, closeAllExternal] + ) + const onLoadSample = useCallback(() => resetWorkspace(DEMO_CODE), [resetWorkspace]) + const onNewBlank = useCallback(() => resetWorkspace(''), [resetWorkspace]) + + // aria-live 状态播报 + const liveMessage = + state === 'running' + ? t('app.live.running', { pct: progress?.pct ?? 0 }) + : state === 'done' && result?.status === 'ok' + ? t('app.live.done') + : state === 'error' + ? t('app.live.error', { message: errorMessage ?? t('errorBanner.unknown') }) + : '' + + // 派生给 EditorTabs / CodePanel 的状态。 + // editorTabId: null 时 = 用户 tab,否则是外部 tab id。 + const editorTabId = activeExternalId ?? 'user' + // knownTabIds: CodePanel 用来 dispose 不再存在的 tab 对应的 model。 + const knownTabIds = useMemo(() => ['user', ...externalTabs.map((tb) => tb.id)], [externalTabs]) + // modelContentByTabId: CodePanel 重建外部 tab model 用的内容快照。 + // 只在 ready 时塞(loading 状态 model 内部用 '' 占位,empty content 也合理)。 + const modelContentByTabId = useMemo>(() => { + const out: Record = {} + for (const tb of externalTabs) { + // 即使 status=error 也把 message 塞进去 —— tab 内显示的「错误」只是 banner, + // Monaco 区域仍然用 message 占位(避免模型换空字符串让 cursor 跳 line 1)。 + out[tb.id] = tb.content + } + return out + }, [externalTabs]) + // 活跃 tab 应展示的内容(外部触发 setValue 时用)。 + // 用户 tab:等于 code。外部 tab:等于该 tab 的 content。 + const activeContent = editorTabId === 'user' ? code : (modelContentByTabId[editorTabId] ?? '') + // 只读条件:外部 tab 一律只读;运行中所有 tab 都只读。 + const isReadOnly = editorTabId !== 'user' || state === 'running' + + // 外部 tab 从 loading → ready 时,调 revealLine 把光标移到对应行。 + // useExternalFiles.openFile 时已经把 {tabId, line} 暂存到 pendingRevealRef, + // 这里把 ref 里的最新一条拿出来 revealLine(多次点击同一 tab → 只有最新的 line)。 + // 依赖 externalTabs 而不是 [status] 是为了在 effect 里能遍历到最新列表。 + useEffect(() => { + const reveal = consumePendingReveal() + if (!reveal) return + // 只对当前 active tab 的 reveal 感兴趣 —— 如果用户已经切到别的 tab, + // 暂存的 revealLine 属于旧 tab,不该在新 tab 强制跳行 + if (reveal.tabId !== editorTabId) return + editorRef.current?.revealLine(reveal.line) + }, [externalTabs, editorTabId, consumePendingReveal]) + + // 切到用户 tab 时,如果 pending reveal 暂存的是 user tab 的 line(理论上不会)也走 reveal。 + // 实际场景是「已经在 user tab 高亮状态下,点 user 热点」—— 但 selectHotspot 走的是 + // editorRef.current?.revealLine(不走 openFile),所以 pendingReveal 不会被填 user tab。 + // 这条 effect 仍然是兜底:用户代码状态从 loading 过渡的极端场景(理论上不存在)。 + useEffect(() => { + if (editorTabId !== 'user') return + const reveal = consumePendingReveal() + if (!reveal) return + if (reveal.tabId === 'user') { + editorRef.current?.revealLine(reveal.line) + } + // user tab 的 reveal 在 selectHotspot 里同步触发,这里只兜底; + // 如果 reveal.tabId 是某个外部 tab 但当前 editorTabId === 'user',丢弃即可。 + }, [editorTabId, consumePendingReveal]) + + // FileReadErrorKind → i18n key 后缀。放在模块作用域(不依赖任何 state)避免每次渲染 + // 重新建一次表 —— 'unknown' 走不同分支(catch 兜底 message),不在表里。 + const editorTabs: EditorTabView[] = useMemo(() => { + const userTab: EditorTabView = { + id: 'user', + displayName: t('editorTab.user'), + origin: 'user', + closable: false + } + const ext: EditorTabView[] = externalTabs.map((tb) => ({ + id: tb.id, + displayName: tb.displayName, + origin: tb.origin, + closable: true, + status: tb.status, + errorTitle: errorTitleForTab(tb, t) + })) + return [userTab, ...ext] + }, [externalTabs, t]) + + // EditorTabs onSelect 收到 tabId;如果是 null(关掉最后一个外部 tab 后 user tab 仍 active), + // 永远走 user。EditorTabs 当前不传 null —— active 切到 user 就是 onSelect('user')。 + const onEditorTabSelect = useCallback( + (tabId: string) => { + if (tabId === 'user') setActiveExternal(null) + else setActiveExternal(tabId) + }, + [setActiveExternal] + ) + + // 关外部 tab 时,如果关的是 active —— useExternalFiles.closeFile 会自动把 activeExternalId + // 置 null(App 这边等价于切回 user)。这里只需要原样转发。 + const onEditorTabClose = useCallback( + (tabId: string) => { + closeFile(tabId) + }, + [closeFile] + ) + + return ( +
+ setSettingsOpen(true)} + theme={theme} + setTheme={setTheme} + onOpenTerminal={() => void window.api.openTerminal(undefined, shell)} + /> + + setSettingsOpen(false)} + interpreters={interpreters} + shell={shell} + setShell={setShell} + /> + +
+ {/* mainArea:内层 flex row,管理编辑器 ↔ 结果区的宽度分配(v-splitter)。 + ResizeObserver 绑在这里——拖拽时用它换算像素 delta 到 fraction */} +
+ {/* Left: editor with its own header. + flexShrink:0 让左栏不被压扁,左宽由 leftFrac 决定;右栏 flex-1 拿剩余空间 */} +
+ + {/* + consoleAreaRef:水平 splitter + RunConsole 共用这一列的高度作为 fraction 分母。 + 编辑器段在 flex-1 上自适应剩余空间,RunConsole 段取 consoleFrac 占比的固定高度—— + 两条路径一起让"拖到顶就只剩编辑器,拖到底底就只剩 console"成为一条简单的不变量。 + consoleHeight=0 时 consoleFrac*100%=0%,wrapper 高度为 0,splitter 紧贴编辑器底边, + 视觉上等同于「运行输出完全收起」(但 splitter 仍在,方便拖回来)。 + */} +
+
+ +
+ }> + + +
+
+ applyConsoleDelta(dy, consoleAreaHeight)} + ariaLabel={t('splitter.console.aria')} + ariaControls={['pyrof-editor-panel', 'pyrof-console']} + // ariaValue 用像素单位,跟主 splitter 一致:applyDelta 用的 dy 同单位, + // 这样 Home/End 才能一次推到 min=0 / max=0.6,否则 pixel fraction 算出来 + // 太小被 clamp 到边界时键盘步进没反应 + ariaValueNow={consoleAreaHeight > 0 ? Math.round(consoleFrac * consoleAreaHeight) : undefined} + ariaValueMin={0} + ariaValueMax={consoleAreaHeight > 0 ? Math.round(0.6 * consoleAreaHeight) : undefined} + /> +
+ +
+
+
+ + applyLeftDelta(dx, mainAreaWidth)} + ariaLabel={t('splitter.main.aria')} + ariaControls={['pyprof-editor-pane', 'pyprof-results']} + // ariaValue 用像素单位 —— 跟 applyLeftDelta(dx, width) 里 dx 同单位, + // Splitter 的 Home/End 才会真的推到 min/max;否则 fraction*100 / width + // 算出来的 fraction delta 太小,clamp 到不到边界 + ariaValueNow={mainAreaWidth > 0 ? Math.round(leftFrac * mainAreaWidth) : undefined} + ariaValueMin={mainAreaWidth > 0 ? Math.round(0.2 * mainAreaWidth) : undefined} + ariaValueMax={mainAreaWidth > 0 ? Math.round(0.8 * mainAreaWidth) : undefined} + /> + + {/* Right: results or empty state */} +
+ {state === 'error' ? ( + + ) : result && result.status !== 'ok' ? ( + + ) : result && result.status === 'ok' ? ( + + ) : ( + setSettingsOpen(true)} + /> + )} +
+
+
+ + {/* aria-live 状态播报 */} +
+ {liveMessage} +
+
+ ) +} diff --git a/src/renderer/src/__tests__/App.test.tsx b/src/renderer/src/__tests__/App.test.tsx new file mode 100644 index 0000000..9140e8a --- /dev/null +++ b/src/renderer/src/__tests__/App.test.tsx @@ -0,0 +1,549 @@ +import { forwardRef, useImperativeHandle } from 'react' +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import App from '../App' +import type { AnalysisResult, ProgressEvent } from '../../../shared/analysis' + +/** + * Mock CodePanelLazy — 真实 CodePanel 拉 Monaco 会拖垮 jsdom(10+ MB)。 + * 这里用一个 stub forwardRef 暴露 CodePanelHandle 接口:revealLine / clearHighlight / getValue。 + * App 顶层 editorRef.current?.xxx() 全部走这条路。 + */ +vi.mock('../components/CodePanelLazy', () => ({ + default: forwardRef< + { revealLine: (n: number) => void; clearHighlight: () => void; getValue: () => string }, + { + value: string + onChange: (v: string) => void + disabled?: boolean + onCtrlEnter?: () => void + } + >(function CodePanelStub({ value, onChange, onCtrlEnter }, ref) { + useImperativeHandle(ref, () => ({ + revealLine: () => {}, + clearHighlight: () => {}, + getValue: () => value + })) + return ( +
+