diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index d5f092def38f32a5b6e8485dc8709cfe6db98249..3c3629e647f5ddf82548912e337bea9826b434af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,87 +1 @@ -docs/ - -# Dependencies -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Coverage directory used by tools like istanbul -coverage/ - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless - -# IDE files -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db \ No newline at end of file +node_modules diff --git a/README.en.md b/README.en.md deleted file mode 100644 index cb013d3875ce8445f626599599db5932b4d77ab4..0000000000000000000000000000000000000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# inula-conversion-tool - -#### Description -提供工具把openInula1.0的代码转成openInula2.0的代码。 - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md deleted file mode 100644 index 6b5b67a5f1c712837095918beec979d36b23bf41..0000000000000000000000000000000000000000 --- a/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# openInula 2.0 升级迁移工具 - -这是一个 CLI 工具,旨在帮助开发者将使用 openInula 1.0 语法的项目自动迁移到 openInula 2.0 的响应式语法。 - -## 安装与设置 - -1. **克隆仓库** - ```bash - # git clone - cd openinula_migrator - ``` - -2. **安装依赖** - ```bash - npm install - ``` - -3. **链接 CLI 命令** - 为了在本地方便地测试 CLI,请使用 `npm link` 将其链接到全局。 - ```bash - npm link - ``` - -## 使用方法 - -目前,工具提供一个 `migrate` 命令,可以对指定的文件或目录执行代码转换。 - -### 语法 - -```bash -inula-migrator migrate [options] -``` - -### 选项 - -* `-d, --dry-run`: 执行空运行,不会实际修改文件,但会在控制台输出转换后的代码。 - -### 示例 - -项目中的 `src/example.js` 文件包含一个 openInula 1.0 的 `useState` 示例。你可以用它来测试迁移工具: - -#### 预览转换结果 (推荐) - -使用 `--dry-run` 选项来预览转换后的代码,而不会修改原始文件: - -```bash -inula-migrator migrate src/example.js --dry-run -``` - -#### 实际执行转换 - -如果你想实际修改文件,请省略 `--dry-run` 选项: - -```bash -inula-migrator migrate src/example.js -``` - -执行后,`src/example.js` 中的代码将会被自动更新为 2.0 的语法。 \ No newline at end of file diff --git a/bin/inula-migrate b/bin/inula-migrate new file mode 100755 index 0000000000000000000000000000000000000000..32bf5ba3aca6ede4d8bfe0a63af3e08f9766b1ed --- /dev/null +++ b/bin/inula-migrate @@ -0,0 +1,3 @@ +#!/usr/bin/env node +require('../src/index.js').main(process.argv); + diff --git a/docs/.DS_Store b/docs/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c96ba1962183cc969e6f97a44b2d544733950809 Binary files /dev/null and b/docs/.DS_Store differ diff --git a/docs/computed.md b/docs/computed.md new file mode 100644 index 0000000000000000000000000000000000000000..42c192f1df496391ca260b2a9166d29db6d6e431 --- /dev/null +++ b/docs/computed.md @@ -0,0 +1,144 @@ +# OpenInula2 (Zouyu) 与 React 计算值(Computed)语法对比 + +在 OpenInula2(代号 Zouyu)中,**计算值(Computed)** 是指依赖于其他状态变量的表达式。你只需像写普通 JavaScript 一样声明变量,编译器会自动识别并优化这些依赖关系。而在 React 中,通常使用 `useMemo` 来实现类似效果。 + +--- + +## 基本用法 + +### OpenInula2 + +```javascript +function DoubleCounter() { + let count = 0; + // 计算值:double 会自动随着 count 变化 + const double = count * 2; + + return ( +
+

当前计数:{count}

+

双倍值:{double}

+ +
+ ); +} +``` + +### React 对比 + +```javascript +import { useState, useMemo } from 'react'; + +function DoubleCounter() { + const [count, setCount] = useState(0); + + const double = useMemo(() => count * 2, [count]); + + return ( +
+

当前计数:{count}

+

双倍值:{double}

+ +
+ ); +} +``` + +在 React 中,`useMemo` 用于缓存计算值,避免每次渲染都重新计算。 + +--- + +## 多重依赖 + +### OpenInula2 + +```javascript +function PriceCalculator() { + let price = 100; + let quantity = 2; + const total = price * quantity; + + return ( +
+

单价:{price}

+

数量:{quantity}

+

总价:{total}

+ +
+ ); +} +``` + +### React 对比 + +```javascript +import { useState, useMemo } from 'react'; + +function PriceCalculator() { + const [price, setPrice] = useState(100); + const [quantity, setQuantity] = useState(2); + + const total = useMemo(() => price * quantity, [price, quantity]); + + return ( +
+

单价:{price}

+

数量:{quantity}

+

总价:{total}

+ +
+ ); +} +``` + +在 React 中需要显式指定依赖数组 `[price, quantity]`,否则计算不会随状态更新。 + +--- + +## 嵌套计算 + +### OpenInula2 + +```javascript +function NestedComputed() { + let a = 1; + const b = a + 2; + const c = b * 3; + + return
c 的值:{c}
; +} +``` + +### React 对比 + +```javascript +import { useMemo } from 'react'; + +function NestedComputed() { + const a = 1; + const b = useMemo(() => a + 2, [a]); + const c = useMemo(() => b * 3, [b]); + + return
c 的值:{c}
; +} +``` + +在 React 中,为了性能和依赖管理,复杂的计算链条通常用 `useMemo` 包裹。 + +--- + +## 注意事项 + +- **OpenInula2**: + + - 只需像写普通 JS 一样声明依赖关系,编译器会自动优化。 + - 避免在计算值中产生副作用(如修改外部状态)。 + - 计算值应为纯表达式,适合用于 UI 展示、派生数据。 + - 如果依赖的状态较多,建议提前拆分变量,提升可读性。 + +- **React**: + + - 使用 `useMemo` 来缓存计算值,并显式指定依赖数组。 + - 确保依赖数组完整,否则可能导致计算值不更新或性能浪费。 + - 避免在 `useMemo` 中执行副作用,应保持纯函数。 + diff --git a/docs/doc.md b/docs/doc.md new file mode 100644 index 0000000000000000000000000000000000000000..f5fb05497f4b6974e650ed059fb055da74d2a833 --- /dev/null +++ b/docs/doc.md @@ -0,0 +1,161 @@ +# OpenInula 2.0 迁移 CLI — 工程实施说明(MVP,不含语法/规则清单) + +> 本文件仅描述**工程实施**与**交付标准**,不包含任何语法/API 差异,也不列出具体转换规则清单。这些内容将放入独立文档(“语法与规则对照”)与仓库内的 `api-map.json`。 + +--- + +## 1. MVP 范围(In‑scope) + +- 基于 Node.js/TypeScript 与 Commander 的 **CLI** 骨架(默认 dry‑run)。 +- **文件递归扫描**、忽略规则(合并 `.gitignore` 和 CLI 指定 ignore)、并发处理。 +- **AST 转换执行器**(集成 jscodeshift/recast/babel‑parser),按启用的转换 ID 依次执行。 +- **JSON 报告**(可供 CI 消费):运行摘要、文件粒度变更、警告/错误、转换命中统计。 +- **统一格式化**(Prettier)与 **diff 输出**(dry‑run 时展示 unified diff 摘要)。 +- **幂等性**与 **回滚指引**:重复执行不产生额外变更,失败情形给出回滚建议。 +- **Vitest 测试**:单元 + e2e(fixtures)。 + +## 2. 非范围(Out‑of‑scope) + +- 任何框架/库的**语法/API 差异说明**与**转换规则清单**(移至独立文档)。 +- 构建工具配置的自动改写(仅在报告中提示建议)。 +- 运行时行为等价性验证(以静态迁移为主)。 + +## 3. 架构概览 + +``` +CLI(Commander) + ├─ 配置解析(默认/文件/命令行合并) + ├─ 文件发现(fast-glob) + 并发队列(p-limit) + ├─ 转换执行器(TransformRunner) + │ ├─ 转换注册表(TransformRegistry) + │ ├─ jscodeshift 调用层(每条转换一个 transform) + │ └─ Prettier/格式化 & 冲突处理 + ├─ 差异计算(diff) + ├─ 报告聚合(JSON/控制台摘要) + └─ 退出码策略 & 错误上报 +``` + +## 4. CLI 规格 + +``` +Usage: inula-migrate [options] + +Options: + -w, --write 写回文件(默认 dry-run) + -r, --recursive 递归处理目录(默认开启,可关闭) + -e, --extensions 处理扩展名(默认:js,jsx,ts,tsx) + -i, --ignore 追加忽略 glob(可多次) + -p, --parser babel | ts | tsx(默认自动侦测) + -t, --transform 指定启用的转换 ID(来自独立规则文档或配置) + --report 输出 JSON 报告到文件 + --config 指定配置文件(默认 .inularc.*) + --concurrency 并发数(默认:CPU 核心数) + --fail-on-warn 有警告时退出码非 0(便于 CI) + --no-prettier 跳过格式化(默认开启) + -q, --quiet 减少控制台输出 + -v, --verbose 更详细日志 + -V, --version + -h, --help +``` + +**退出码**:`0` 正常/仅提示;`1` 有错误;`2` 有警告且开启 `--fail-on-warn`。 + +## 5. 配置文件(示例 .inularc.json) + +```jsonc +{ + // 规则/语法映射来源见独立文档;此处仅示例工程级参数 + "ignore": ["**/dist/**", "**/*.d.ts"], + "prettier": true, + "concurrency": 8, + "report": { "path": "migration-report.json" } +} +``` + +## 6. 报告格式(JSON 示例) + +```jsonc +{ + "version": "1.0.0", + "runAt": "2025-08-18T06:00:00Z", + "summary": { + "filesProcessed": 120, + "filesChanged": 73, + "errors": 2, + "warnings": 15, + "transforms": {"transform-a": 70, "transform-b": 60} + }, + "files": [ + { + "path": "src/App.tsx", + "changed": true, + "diff": "@@ -1,5 +1,5 @@ ...", + "appliedTransforms": [ + {"id": "transform-a", "count": 2} + ], + "warnings": [ + {"code": "EFFECT_COMPLEX", "message": "复杂副作用需人工拆分", "range": [12, 34] } + ], + "errors": [] + } + ] +} +``` + +## 7. 文件遍历与并发 + +- `fast-glob` 合并 `.gitignore` 与 CLI `--ignore`; +- `p-limit` 控制并发,默认 `os.cpus().length`; +- 流程:读取 → 执行启用转换 → 生成/比较 diff → 写入或汇报。 + +## 8. 格式化与 Diff + +- 写回前统一 `prettier`; +- dry‑run 打印 unified diff(长输出折叠)。 + +## 9. 错误处理与回滚 + +- 建议在**干净的 Git 工作区**执行; +- 可选:写回前将原内容保存在 `.inula-migrate-cache/`;失败自动回滚并在报告中标记。 + +## 10. 日志与可观测性 + +- 记录转换命中计数、文件级耗时、警告/错误; +- `--verbose` 输出每个文件的转换明细; +- 报告中包含工具版本、配置摘要与摘要哈希(便于溯源)。 + +## 11. 测试策略(Vitest) + +- **单元测试**:AST 工具函数、Runner、配置/过滤逻辑; +- **e2e(fixtures)**:对样例工程跑 CLI,做快照比对; +- **覆盖率目标(MVP)**:工具层 ≥70%,关键路径 ≥60%。 + +## 12. CI 与发布 + +- GitHub Actions:lint → unit → e2e → 构建 → (可选)发布; +- 版本语义化与变更日志; +- NPM 包含 `bin` 可执行,支持 `npx inula-migrate`。 + +## 13. 里程碑(≈6 周) + +- **W1:基础设施** — TS/ESLint/Prettier/Vitest、CLI 骨架、扫描/并发、报告管线、jscodeshift 最小集成。 +- **W2:转换执行流稳定** — Runner/注册表/幂等检查、dry‑run diff、Prettier 集成、e2e 基线。 +- **W3:错误处理与可观测性** — 退出码策略、日志分级、回滚/缓存策略、报告字段完善。 +- **W4:性能与大仓库压测** — 并发与 I/O 调优、采样基线; +- **W5:CI 与文档** — GitHub Actions、README 使用指南、报告 schema 注释; +- **W6:收口与 RC** — Bug bash、已知限制清单、示例仓库验证、发布候选。 + +## 14. 验收标准(DoD) + +1. `inula-migrate --report report.json` 在示例工程可运行; +2. 默认 dry‑run 输出合理 diff;加 `--write` 正确落盘,重复执行无额外变更; +3. 报告 JSON 含运行摘要、文件级结果、警告/错误与转换命中统计; +4. 单元与 e2e 通过,覆盖率达到 MVP 目标; +5. 错误路径与回滚指引清晰,退出码符合约定。 + +## 15. 待你提供/确认 + +- Node 最低版本(建议 ≥18)与包管理器(建议 pnpm); +- 2–3 个真实源文件作为 fixtures(覆盖状态、简单副作用与派生值场景即可); +- 独立“语法与规则对照”文档与 `api-map.json`(由你维护,工具在运行期读取)。 + diff --git a/docs/state_management.md b/docs/state_management.md new file mode 100644 index 0000000000000000000000000000000000000000..eebee705be6900158c5c470f3cdbb38bde2a4f20 --- /dev/null +++ b/docs/state_management.md @@ -0,0 +1,511 @@ +# React 与 OpenInula2.0 状态管理语法对比 + +## OpenInula2.0 示例 + +### 基础用法 + +```javascript +function Counter() { + let count = 0; // 直接声明状态 + + return ( +
+

计数:{count}

+ +
+ ); +} +``` + +在 OpenInula2.0 中,状态可以直接通过 `let` 声明变量并修改,语法更接近普通 JavaScript 变量。 + +### 复杂状态(对象) + +```javascript +function UserProfile() { + let user = { + name: '张三', + age: 25, + preferences: { + theme: 'dark', + language: 'zh' + } + }; + + function updateTheme(newTheme) { + user.preferences.theme = newTheme; // 直接修改对象属性 + } + + return ( +
+

{user.name}

+

年龄:{user.age}

+
+ 主题:{user.preferences.theme} + +
+
+ ); +} +``` + +OpenInula2.0 对于复杂状态依旧可以直接修改对象的属性,无需额外调用状态更新函数。 + +### 数组状态 + +```javascript +function TodoList() { + let todos = [ + { id: 1, text: '学习 OpenInula', done: false }, + { id: 2, text: '写文档', done: false } + ]; + + function addTodo(text) { + todos = [ + ...todos, + { + id: todos.length + 1, + text, + done: false + } + ]; + } + + function toggleTodo(id) { + todos = todos.map(todo => + todo.id === id + ? { ...todo, done: !todo.done } + : todo + ); + } + + return ( +
+ +
    + + {(todo) => ( +
  • toggleTodo(todo.id)} + style={{ + textDecoration: todo.done ? 'line-through' : 'none' + }} + > + {todo.text} +
  • + )} +
    +
+
+ ); +} +``` + +在 OpenInula2.0 中,数组状态也可以通过直接重新赋值的方式来更新,无需专门的 set 函数。 + +### 状态更新 - 直接赋值 + +```javascript +function Counter() { + let count = 0; + let message = ''; + + function increment() { + count++; // 直接赋值 + message = `当前计数:${count}`; // 状态之间可以互相依赖 + } + + return ( +
+

{message}

+ +
+ ); +} +``` + +在 OpenInula2.0 中,多个状态变量之间可以直接通过赋值来产生依赖关系,而无需额外的更新函数。 + +### 状态更新 - 批量更新 + +```javascript +function UserForm() { + let formData = { + username: '', + email: '', + age: 0 + }; + + function resetForm() { + // 一次性更新多个字段 + formData = { + username: '', + email: '', + age: 0 + }; + } + + function updateField(field, value) { + formData[field] = value; // 更新单个字段 + } + + return ( +
+ updateField('username', e.target.value)} + /> + updateField('email', e.target.value)} + /> + updateField('age', parseInt(e.target.value))} + /> + +
+ ); +} +``` + +在 OpenInula2.0 中,可以一次性更新整个对象(批量更新),也可以针对单个字段直接赋值更新。 + +### 状态初始化提取函数 + +```javascript +function createInitialState() { + return { + user: null, + preferences: { + theme: 'light', + language: 'zh' + }, + todos: [] + }; +} + +function App() { + let state = createInitialState(); + + function reset() { + state = createInitialState(); // 重置为初始状态 + } + + return ( +
+ {/* 使用状态 */} +
+ ); +} +``` + +在 OpenInula2.0 中,可以通过函数抽取初始状态,便于重置和复用。 + +### 状态组织(组合复杂状态) + +```javascript +function ShoppingCart() { + let cart = { + items: [], + total: 0, + + addItem(product) { + this.items = [...this.items, product]; + this.updateTotal(); + }, + + removeItem(productId) { + this.items = this.items.filter(item => item.id !== productId); + this.updateTotal(); + }, + + updateTotal() { + this.total = this.items.reduce((sum, item) => sum + item.price, 0); + } + }; + + return ( +
+

购物车 ({cart.items.length})

+

总计:¥{cart.total}

+ {/* 渲染购物车内容 */} +
+ ); +} +``` + +在 OpenInula2.0 中,可以将多个相关状态和操作方法组合在一个对象里,更加模块化和清晰。 + +--- + +## React 示例(对比) + +### 基础用法 + +```javascript +import { useState } from 'react'; + +function Counter() { + const [count, setCount] = useState(0); + + return ( +
+

计数:{count}

+ +
+ ); +} +``` + +### 复杂状态(对象) + +```javascript +import { useState } from 'react'; + +function UserProfile() { + const [user, setUser] = useState({ + name: '张三', + age: 25, + preferences: { + theme: 'dark', + language: 'zh' + } + }); + + function updateTheme(newTheme) { + setUser({ + ...user, + preferences: { + ...user.preferences, + theme: newTheme + } + }); + } + + return ( +
+

{user.name}

+

年龄:{user.age}

+
+ 主题:{user.preferences.theme} + +
+
+ ); +} +``` + +### 数组状态 + +```javascript +import { useState } from 'react'; + +function TodoList() { + const [todos, setTodos] = useState([ + { id: 1, text: '学习 React', done: false }, + { id: 2, text: '写文档', done: false } + ]); + + function addTodo(text) { + setTodos([ + ...todos, + { id: todos.length + 1, text, done: false } + ]); + } + + function toggleTodo(id) { + setTodos( + todos.map(todo => + todo.id === id ? { ...todo, done: !todo.done } : todo + ) + ); + } + + return ( +
+ +
    + {todos.map(todo => ( +
  • toggleTodo(todo.id)} + style={{ + textDecoration: todo.done ? 'line-through' : 'none' + }} + > + {todo.text} +
  • + ))} +
+
+ ); +} +``` + +### 状态更新 - 直接赋值的对比 + +```javascript +import { useState } from 'react'; + +function Counter() { + const [count, setCount] = useState(0); + const [message, setMessage] = useState(''); + + function increment() { + const newCount = count + 1; + setCount(newCount); + setMessage(`当前计数:${newCount}`); // 依赖另一个状态 + } + + return ( +
+

{message}

+ +
+ ); +} +``` + +### 状态更新 - 批量更新的对比 + +```javascript +import { useState } from 'react'; + +function UserForm() { + const [formData, setFormData] = useState({ + username: '', + email: '', + age: 0 + }); + + function resetForm() { + setFormData({ username: '', email: '', age: 0 }); + } + + function updateField(field, value) { + setFormData({ + ...formData, + [field]: value + }); + } + + return ( +
+ updateField('username', e.target.value)} + /> + updateField('email', e.target.value)} + /> + updateField('age', parseInt(e.target.value))} + /> + +
+ ); +} +``` + +### 状态初始化提取函数的对比 + +```javascript +import { useState } from 'react'; + +function createInitialState() { + return { + user: null, + preferences: { + theme: 'light', + language: 'zh' + }, + todos: [] + }; +} + +function App() { + const [state, setState] = useState(createInitialState()); + + function reset() { + setState(createInitialState()); // 使用函数重新初始化状态 + } + + return ( +
+ {/* 使用状态 */} +
+ ); +} +``` + +### 状态组织(组合复杂状态)的对比 + +```javascript +import { useState } from 'react'; + +function ShoppingCart() { + const [items, setItems] = useState([]); + const [total, setTotal] = useState(0); + + function addItem(product) { + const newItems = [...items, product]; + setItems(newItems); + setTotal(newItems.reduce((sum, item) => sum + item.price, 0)); + } + + function removeItem(productId) { + const newItems = items.filter(item => item.id !== productId); + setItems(newItems); + setTotal(newItems.reduce((sum, item) => sum + item.price, 0)); + } + + return ( +
+

购物车 ({items.length})

+

总计:¥{total}

+ {/* 渲染购物车内容 */} +
+ ); +} +``` + +在 React 中,通常会将复杂状态拆分为多个 `useState` 管理,或者使用 `useReducer` 统一管理复杂逻辑。 + +--- + +## 对比总结 + +- **声明方式**:OpenInula2.0 使用 `let` 直接声明,React 使用 `useState`。 +- **更新方式**:OpenInula2.0 可直接修改变量、对象或数组,React 必须通过状态更新函数(如 `setState`)。 +- **状态依赖**:OpenInula2.0 中状态之间可以直接互相赋值依赖,React 中必须通过 `set` 函数手动维护依赖关系。 +- **批量更新**:OpenInula2.0 可以一次性重置对象,React 需要借助 `setState` 和不可变数据模式实现。 +- **初始化函数**:OpenInula2.0 与 React 都支持提取初始化函数,React 通常结合 `useState` 使用以便重置。 +- **状态组织**:OpenInula2.0 倾向于将相关字段和方法组合在一个对象中,React 倾向于拆分状态或使用 `useReducer` 组织复杂状态。 +- **心智模型**:OpenInula2.0 更接近 JavaScript 原生变量操作,React 强制遵循不可变数据(immutable data)的更新模式。 + +--- + +## 注意事项 + +- 避免直接修改复杂对象的深层属性,推荐使用不可变更新模式。 +- 确保状态更新是同步的,避免在异步操作中直接修改状态。 +- 对于大型应用,考虑使用状态管理库(如 Redux、Zustand 等)。 +- 注意状态的作用域,避免全局状态导致难以维护。 + diff --git a/docs/watch.md b/docs/watch.md new file mode 100644 index 0000000000000000000000000000000000000000..2c91d6a929ac39103ac9b4538b23159b5a93a218 --- /dev/null +++ b/docs/watch.md @@ -0,0 +1,202 @@ +# OpenInula2 (Zouyu) 与 React 监听系统(Watch)语法对比 + +在 OpenInula2(代号 Zouyu)中,提供了 `watch` 方法,用于监听状态或计算值的变化,并在变化时执行副作用逻辑。而在 React 中,常用 `useEffect` 来实现类似的监听效果。 + +--- + +## 基本用法 + +### OpenInula2 +```javascript +function FetchData({ url }) { + let data = null; + + watch(() => { + fetch(url) + .then(res => res.json()) + .then(_data => { + data = _data; + }); + }); + + return ( +
+ +
{JSON.stringify(data, null, 2)}
+
+ +

加载中...

+
+
+ ); +} +``` + +### React 对比 +```javascript +import { useState, useEffect } from 'react'; + +function FetchData({ url }) { + const [data, setData] = useState(null); + + useEffect(() => { + fetch(url) + .then(res => res.json()) + .then(_data => setData(_data)); + }, [url]); + + return ( +
+ {data ? ( +
{JSON.stringify(data, null, 2)}
+ ) : ( +

加载中...

+ )} +
+ ); +} +``` + +在 React 中,`useEffect` 需要显式指定依赖数组 `[url]` 来触发副作用。 + +--- + +## 依赖追踪 + +### OpenInula2 +watch 会自动追踪其回调中用到的所有状态和计算值,只要依赖发生变化,回调就会重新执行。 +```javascript +function Logger() { + let count = 0; + watch(() => { + console.log('count 变化为:', count); + }); + + return ; +} +``` + +### React 对比 +```javascript +import { useState, useEffect } from 'react'; + +function Logger() { + const [count, setCount] = useState(0); + + useEffect(() => { + console.log('count 变化为:', count); + }, [count]); + + return ; +} +``` + +在 React 中,需要手动指定依赖 `[count]`。 + +--- + +## 清理副作用 + +### OpenInula2 +可以在 watch 回调中返回一个清理函数,用于移除定时器、事件监听等副作用: +```javascript +function Timer() { + let time = Date.now(); + + watch(() => { + const timer = setInterval(() => { + time = Date.now(); + }, 1000); + // 返回清理函数 + return () => clearInterval(timer); + }); + + return
当前时间:{new Date(time).toLocaleTimeString()}
; +} +``` + +### React 对比 +```javascript +import { useState, useEffect } from 'react'; + +function Timer() { + const [time, setTime] = useState(Date.now()); + + useEffect(() => { + const timer = setInterval(() => { + setTime(Date.now()); + }, 1000); + // 返回清理函数 + return () => clearInterval(timer); + }, []); + + return
当前时间:{new Date(time).toLocaleTimeString()}
; +} +``` + +React 的 `useEffect` 也支持返回清理函数来移除定时器、事件监听等副作用。 + +--- + +## 多个 Watch + +### OpenInula2 +可以在同一个组件中使用多个 watch,分别监听不同的状态: +```javascript +function MultiWatch() { + let a = 1; + let b = 2; + + watch(() => { + console.log('a 变化:', a); + }); + watch(() => { + console.log('b 变化:', b); + }); + + return ( +
+ + +
+ ); +} +``` + +### React 对比 +```javascript +import { useState, useEffect } from 'react'; + +function MultiWatch() { + const [a, setA] = useState(1); + const [b, setB] = useState(2); + + useEffect(() => { + console.log('a 变化:', a); + }, [a]); + + useEffect(() => { + console.log('b 变化:', b); + }, [b]); + + return ( +
+ + +
+ ); +} +``` + +在 React 中,需要通过多个 `useEffect` 来分别监听不同状态的变化。 + +--- + +## 注意事项 +- 只在需要副作用(如数据请求、日志、定时器等)时使用 `watch` +- 避免在 `watch` 中直接修改依赖自身的状态,防止死循环 +- 善用清理函数,避免内存泄漏 +- `watch` 回调应为同步函数,避免直接返回 Promise +- 清理函数只在依赖变化或组件卸载时调用 +- 不要在 `watch` 外部直接调用副作用逻辑 + diff --git a/index.js b/index.js deleted file mode 100755 index ad2dd82cc9b6a0c1960c0ebc3197d61f9dfe5d06..0000000000000000000000000000000000000000 --- a/index.js +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env node - -const { program } = require('commander'); -const { exec } = require('child_process'); -const path = require('path'); - -// --- CLI Definition --- - -program - .version('0.0.1') - .description('A CLI tool to migrate openInula v1 projects to v2'); - -// --- 'migrate' Command --- - -program - .command('migrate ') - .description('Migrate a file or directory using a specified codemod transform') - .option('-d, --dry-run', 'Perform a dry run without writing changes to the file system') - .action((targetPath, options) => { - console.log(`Starting migration for: ${targetPath}`); - - // --- Path Definitions --- - const transformScript = path.resolve(__dirname, './transforms/transform-useState.js'); - const jscodeshiftExecutable = path.resolve(__dirname, './node_modules/.bin/jscodeshift'); - - // --- Command Execution --- - // For a dry run, use -d (dry run) and --print (print to stdout). - const flags = options.dryRun ? '-d --print' : ''; - - const command = `${jscodeshiftExecutable} -t ${transformScript} ${targetPath} ${flags}`; - - console.log(`Executing: ${command}`); - - exec(command, (error, stdout, stderr) => { - if (error) { - console.error(`Migration failed: ${error.message}`); - return; - } - if (stderr) { - console.log(`stderr: ${stderr}`); - } - console.log(`stdout:\n${stdout}`); - }); - }); - -// --- Parse Arguments --- - -program.parse(process.argv); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 04fe17a7f1108e6af954021a35918c65ddd82eb4..2f069a2812a8d597a75bce6b4f888065b36560bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,31 @@ { - "name": "openinula_migrator", - "version": "1.0.0", + "name": "@openinula/migrator", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "openinula_migrator", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "commander": "^14.0.0", - "jscodeshift": "^17.3.0", - "vitest": "^3.2.4" + "name": "@openinula/migrator", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^12.1.0", + "diff": "^5.2.0", + "fast-glob": "^3.3.2", + "jscodeshift": "^0.16.1", + "p-limit": "^5.0.0", + "prettier": "^2.8.8", + "recast": "^0.23.9" }, "bin": { - "inula-migrator": "index.js" + "inula-migrate": "bin/inula-migrate" + }, + "devDependencies": { + "vitest": "^2.0.5" + }, + "engines": { + "node": ">=18" } }, "node_modules/@ampproject/remapping": { @@ -51,20 +62,20 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -80,12 +91,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -121,16 +132,16 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.3", "semver": "^6.3.1" }, "engines": { @@ -173,13 +184,13 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -260,23 +271,23 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -469,9 +480,9 @@ } }, "node_modules/@babel/register": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.27.1.tgz", - "integrity": "sha512-K13lQpoV54LATKkzBpBAEu1GGSIRzxR9f4IN4V8DCDgiUMo2UDGagEZr3lPeVNJPLkWUi5JE4hCHKneVTwQlYQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.28.3.tgz", + "integrity": "sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA==", "dependencies": { "clone-deep": "^4.0.1", "find-cache-dir": "^2.0.0", @@ -500,16 +511,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.2", "debug": "^4.3.1" }, "engines": { @@ -517,9 +528,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", - "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -529,399 +540,377 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", - "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "aix" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz", - "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz", - "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@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": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz", - "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "android" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz", - "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz", - "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@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": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz", - "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz", - "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@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": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz", - "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz", - "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@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": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz", - "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz", - "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@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": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz", - "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], + "dev": true, "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz", - "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@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": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz", - "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz", - "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz", - "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz", - "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz", - "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz", - "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz", - "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz", - "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz", - "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@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": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz", - "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz", - "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@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": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz", - "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -936,307 +925,348 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "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.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.2.tgz", - "integrity": "sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.3.tgz", + "integrity": "sha512-UmTdvXnLlqQNOCJnyksjPs1G4GqXNGW1LrzCe8+8QoaLhhDeTXYBgJ3k6x61WIhlHX2U+VzEJ55TtIjR/HTySA==", "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.2.tgz", - "integrity": "sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.3.tgz", + "integrity": "sha512-8NoxqLpXm7VyeI0ocidh335D6OKT0UJ6fHdnIxf3+6oOerZZc+O7r+UhvROji6OspyPm+rrIdb1gTXtVIqn+Sg==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.2.tgz", - "integrity": "sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.3.tgz", + "integrity": "sha512-csnNavqZVs1+7/hUKtgjMECsNG2cdB8F7XBHP6FfQjqhjF8rzMzb3SLyy/1BG7YSfQ+bG75Ph7DyedbUqwq1rA==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.2.tgz", - "integrity": "sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.3.tgz", + "integrity": "sha512-r2MXNjbuYabSIX5yQqnT8SGSQ26XQc8fmp6UhlYJd95PZJkQD1u82fWP7HqvGUf33IsOC6qsiV+vcuD4SDP6iw==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.2.tgz", - "integrity": "sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.3.tgz", + "integrity": "sha512-uluObTmgPJDuJh9xqxyr7MV61Imq+0IvVsAlWyvxAaBSNzCcmZlhfYcRhCdMaCsy46ccZa7vtDDripgs9Jkqsw==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.2.tgz", - "integrity": "sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.3.tgz", + "integrity": "sha512-AVJXEq9RVHQnejdbFvh1eWEoobohUYN3nqJIPI4mNTMpsyYN01VvcAClxflyk2HIxvLpRcRggpX1m9hkXkpC/A==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.2.tgz", - "integrity": "sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.3.tgz", + "integrity": "sha512-byyflM+huiwHlKi7VHLAYTKr67X199+V+mt1iRgJenAI594vcmGGddWlu6eHujmcdl6TqSNnvqaXJqZdnEWRGA==", "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.2.tgz", - "integrity": "sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.3.tgz", + "integrity": "sha512-aLm3NMIjr4Y9LklrH5cu7yybBqoVCdr4Nvnm8WB7PKCn34fMCGypVNpGK0JQWdPAzR/FnoEoFtlRqZbBBLhVoQ==", "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.2.tgz", - "integrity": "sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.3.tgz", + "integrity": "sha512-VtilE6eznJRDIoFOzaagQodUksTEfLIsvXymS+UdJiSXrPW7Ai+WG4uapAc3F7Hgs791TwdGh4xyOzbuzIZrnw==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.2.tgz", - "integrity": "sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.3.tgz", + "integrity": "sha512-dG3JuS6+cRAL0GQ925Vppafi0qwZnkHdPeuZIxIPXqkCLP02l7ka+OCyBoDEv8S+nKHxfjvjW4OZ7hTdHkx8/w==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.2.tgz", - "integrity": "sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.3.tgz", + "integrity": "sha512-iU8DxnxEKJptf8Vcx4XvAUdpkZfaz0KWfRrnIRrOndL0SvzEte+MTM7nDH4A2Now4FvTZ01yFAgj6TX/mZl8hQ==", "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.2.tgz", - "integrity": "sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.3.tgz", + "integrity": "sha512-VrQZp9tkk0yozJoQvQcqlWiqaPnLM6uY1qPYXvukKePb0fqaiQtOdMJSxNFUZFsGw5oA5vvVokjHrx8a9Qsz2A==", "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.2.tgz", - "integrity": "sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.3.tgz", + "integrity": "sha512-uf2eucWSUb+M7b0poZ/08LsbcRgaDYL8NCGjUeFMwCWFwOuFcZ8D9ayPl25P3pl+D2FH45EbHdfyUesQ2Lt9wA==", "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.2.tgz", - "integrity": "sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.3.tgz", + "integrity": "sha512-7tnUcDvN8DHm/9ra+/nF7lLzYHDeODKKKrh6JmZejbh1FnCNZS8zMkZY5J4sEipy2OW1d1Ncc4gNHUd0DLqkSg==", "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.2.tgz", - "integrity": "sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.3.tgz", + "integrity": "sha512-MUpAOallJim8CsJK+4Lc9tQzlfPbHxWDrGXZm2z6biaadNpvh3a5ewcdat478W+tXDoUiHwErX/dOql7ETcLqg==", "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.2.tgz", - "integrity": "sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.3.tgz", + "integrity": "sha512-F42IgZI4JicE2vM2PWCe0N5mR5vR0gIdORPqhGQ32/u1S1v3kLtbZ0C/mi9FFk7C5T0PgdeyWEPajPjaUpyoKg==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.2.tgz", - "integrity": "sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.3.tgz", + "integrity": "sha512-oLc+JrwwvbimJUInzx56Q3ujL3Kkhxehg7O1gWAYzm8hImCd5ld1F2Gry5YDjR21MNb5WCKhC9hXgU7rRlyegQ==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.2.tgz", - "integrity": "sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.3.tgz", + "integrity": "sha512-lOrQ+BVRstruD1fkWg9yjmumhowR0oLAAzavB7yFSaGltY8klttmZtCLvOXCmGE9mLIn8IBV/IFrQOWz5xbFPg==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.2.tgz", - "integrity": "sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.3.tgz", + "integrity": "sha512-vvrVKPRS4GduGR7VMH8EylCBqsDcw6U+/0nPDuIjXQRbHJc6xOBj+frx8ksfZAh6+Fptw5wHrN7etlMmQnPQVg==", "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.2.tgz", - "integrity": "sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.3.tgz", + "integrity": "sha512-fi3cPxCnu3ZeM3EwKZPgXbWoGzm2XHgB/WShKI81uj8wG0+laobmqy5wbgEwzstlbLu4MyO8C19FyhhWseYKNQ==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" ] }, - "node_modules/@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", - "dependencies": { - "@types/deep-eql": "*" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@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": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.12" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "vite": "^5.0.0" }, "peerDependenciesMeta": { "msw": { @@ -1248,70 +1278,89 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" + "@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": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, "dependencies": { - "tinyspy": "^4.0.3" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, "engines": { "node": ">=12" } @@ -1327,6 +1376,20 @@ "node": ">=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==" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1339,9 +1402,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", + "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", "funding": [ { "type": "opencollective", @@ -1357,8 +1420,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", + "caniuse-lite": "^1.0.30001733", + "electron-to-chromium": "^1.5.199", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -1378,14 +1441,15 @@ "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, "engines": { "node": ">=8" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001726", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", - "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", "funding": [ { "type": "opencollective", @@ -1402,9 +1466,10 @@ ] }, "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", + "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", + "dev": true, "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -1413,13 +1478,29 @@ "pathval": "^2.0.0" }, "engines": { - "node": ">=12" + "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==", + "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/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, "engines": { "node": ">= 16" } @@ -1437,12 +1518,28 @@ "node": ">=6" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "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==" + }, "node_modules/commander": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", - "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "engines": { - "node": ">=20" + "node": ">=18" } }, "node_modules/commondir": { @@ -1450,6 +1547,11 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1475,58 +1577,66 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, "engines": { "node": ">=6" } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/electron-to-chromium": { - "version": "1.5.179", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", - "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==" + "version": "1.5.203", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.203.tgz", + "integrity": "sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==" }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==" + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true }, "node_modules/esbuild": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", - "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=18" + "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.6", - "@esbuild/android-arm": "0.25.6", - "@esbuild/android-arm64": "0.25.6", - "@esbuild/android-x64": "0.25.6", - "@esbuild/darwin-arm64": "0.25.6", - "@esbuild/darwin-x64": "0.25.6", - "@esbuild/freebsd-arm64": "0.25.6", - "@esbuild/freebsd-x64": "0.25.6", - "@esbuild/linux-arm": "0.25.6", - "@esbuild/linux-arm64": "0.25.6", - "@esbuild/linux-ia32": "0.25.6", - "@esbuild/linux-loong64": "0.25.6", - "@esbuild/linux-mips64el": "0.25.6", - "@esbuild/linux-ppc64": "0.25.6", - "@esbuild/linux-riscv64": "0.25.6", - "@esbuild/linux-s390x": "0.25.6", - "@esbuild/linux-x64": "0.25.6", - "@esbuild/netbsd-arm64": "0.25.6", - "@esbuild/netbsd-x64": "0.25.6", - "@esbuild/openbsd-arm64": "0.25.6", - "@esbuild/openbsd-x64": "0.25.6", - "@esbuild/openharmony-arm64": "0.25.6", - "@esbuild/sunos-x64": "0.25.6", - "@esbuild/win32-arm64": "0.25.6", - "@esbuild/win32-ia32": "0.25.6", - "@esbuild/win32-x64": "0.25.6" + "@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": { @@ -1553,6 +1663,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "dependencies": { "@types/estree": "^1.0.0" } @@ -1561,10 +1672,34 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, "engines": { "node": ">=12.0.0" } }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "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/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1601,17 +1736,23 @@ } }, "node_modules/flow-parser": { - "version": "0.275.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.275.0.tgz", - "integrity": "sha512-fHNwawoA2LM7FsxhU/1lTRGq9n6/Q8k861eHgN7GKtamYt9Qrxpg/ZSrev8o1WX7fQ2D3Gg3+uvYN15PmsG7Yw==", + "version": "0.279.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.279.0.tgz", + "integrity": "sha512-41VremrzImoLcZuqY18U86ojcVy2Stuq4VnjdAcxHjGanvx3VmKVUITIVMt2PM1RvmRJtgtJWvCxVpQ1E9OGDw==", "engines": { "node": ">=0.4.0" } }, + "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==" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -1629,11 +1770,50 @@ "node": ">=6.9.0" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "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": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "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==", + "engines": { + "node": ">=8" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1642,6 +1822,40 @@ "node": ">=0.8.19" } }, + "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.", + "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==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -1675,9 +1889,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/jscodeshift": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-17.3.0.tgz", - "integrity": "sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow==", + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.16.1.tgz", + "integrity": "sha512-oMQXySazy63awNBzMpXbbVv73u3irdxTeX2L5ueRyFRxi32qb9uzdZdOY5fTBYADBG19l5M/wnGknZSV1dzCdA==", "dependencies": { "@babel/core": "^7.24.7", "@babel/parser": "^7.24.7", @@ -1689,21 +1903,19 @@ "@babel/preset-flow": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "@babel/register": "^7.24.6", + "chalk": "^4.1.2", "flow-parser": "0.*", "graceful-fs": "^4.2.4", "micromatch": "^4.0.7", "neo-async": "^2.5.0", - "picocolors": "^1.0.1", - "recast": "^0.23.11", - "tmp": "^0.2.3", + "node-dir": "^0.1.17", + "recast": "^0.23.9", + "temp": "^0.9.4", "write-file-atomic": "^5.0.1" }, "bin": { "jscodeshift": "bin/jscodeshift.js" }, - "engines": { - "node": ">=16" - }, "peerDependencies": { "@babel/preset-env": "^7.1.6" }, @@ -1756,9 +1968,10 @@ } }, "node_modules/loupe": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", - "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", + "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", + "dev": true }, "node_modules/lru-cache": { "version": "5.1.1", @@ -1772,6 +1985,7 @@ "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } @@ -1796,6 +2010,14 @@ "semver": "bin/semver" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -1808,6 +2030,36 @@ "node": ">=8.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1817,6 +2069,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -1835,20 +2088,39 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "node_modules/node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "dependencies": { + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.10.5" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^1.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1865,6 +2137,20 @@ "node": ">=6" } }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -1881,15 +2167,25 @@ "node": ">=4" } }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, "engines": { "node": ">= 14.16" } @@ -1941,6 +2237,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -1964,6 +2261,39 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/recast": { "version": "0.23.11", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", @@ -1979,10 +2309,32 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/rollup": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.2.tgz", - "integrity": "sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.3.tgz", + "integrity": "sha512-RZn2XTjXb8t5g13f5YclGoilU/kwT696DIkY3sywjdZidNSi3+vseaQov7D7BZXVJCPv3pDWUN69C78GGbXsKw==", + "dev": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -1994,29 +2346,51 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.44.2", - "@rollup/rollup-android-arm64": "4.44.2", - "@rollup/rollup-darwin-arm64": "4.44.2", - "@rollup/rollup-darwin-x64": "4.44.2", - "@rollup/rollup-freebsd-arm64": "4.44.2", - "@rollup/rollup-freebsd-x64": "4.44.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.44.2", - "@rollup/rollup-linux-arm-musleabihf": "4.44.2", - "@rollup/rollup-linux-arm64-gnu": "4.44.2", - "@rollup/rollup-linux-arm64-musl": "4.44.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.44.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.44.2", - "@rollup/rollup-linux-riscv64-gnu": "4.44.2", - "@rollup/rollup-linux-riscv64-musl": "4.44.2", - "@rollup/rollup-linux-s390x-gnu": "4.44.2", - "@rollup/rollup-linux-x64-gnu": "4.44.2", - "@rollup/rollup-linux-x64-musl": "4.44.2", - "@rollup/rollup-win32-arm64-msvc": "4.44.2", - "@rollup/rollup-win32-ia32-msvc": "4.44.2", - "@rollup/rollup-win32-x64-msvc": "4.44.2", + "@rollup/rollup-android-arm-eabi": "4.46.3", + "@rollup/rollup-android-arm64": "4.46.3", + "@rollup/rollup-darwin-arm64": "4.46.3", + "@rollup/rollup-darwin-x64": "4.46.3", + "@rollup/rollup-freebsd-arm64": "4.46.3", + "@rollup/rollup-freebsd-x64": "4.46.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.3", + "@rollup/rollup-linux-arm-musleabihf": "4.46.3", + "@rollup/rollup-linux-arm64-gnu": "4.46.3", + "@rollup/rollup-linux-arm64-musl": "4.46.3", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.3", + "@rollup/rollup-linux-ppc64-gnu": "4.46.3", + "@rollup/rollup-linux-riscv64-gnu": "4.46.3", + "@rollup/rollup-linux-riscv64-musl": "4.46.3", + "@rollup/rollup-linux-s390x-gnu": "4.46.3", + "@rollup/rollup-linux-x64-gnu": "4.46.3", + "@rollup/rollup-linux-x64-musl": "4.46.3", + "@rollup/rollup-win32-arm64-msvc": "4.46.3", + "@rollup/rollup-win32-ia32-msvc": "4.46.3", + "@rollup/rollup-win32-x64-msvc": "4.46.3", "fsevents": "~2.3.2" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2039,7 +2413,8 @@ "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true }, "node_modules/signal-exit": { "version": "4.1.0", @@ -2064,6 +2439,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -2080,28 +2456,37 @@ "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==" + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true }, "node_modules/std-env": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==" + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true }, - "node_modules/strip-literal": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "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==", "dependencies": { - "js-tokens": "^9.0.1" + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "engines": { + "node": ">=8" } }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==" + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } }, "node_modules/tiny-invariant": { "version": "1.3.3", @@ -2111,84 +2496,42 @@ "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==" + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==" - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true }, "node_modules/tinypool": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/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": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/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": "4.0.3", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", - "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/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.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "engines": { - "node": ">=14.14" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2235,22 +2578,20 @@ } }, "node_modules/vite": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.3.tgz", - "integrity": "sha512-y2L5oJZF7bj4c0jgGYgBNSdIu+5HF+m68rn2cQXFbGoShdhV1phX9rbnxy9YXj82aS8MMsCLAAFkRxZeWdldrQ==", + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.6", - "picomatch": "^4.0.2", - "postcss": "^8.5.6", - "rollup": "^4.40.0", - "tinyglobby": "^0.2.14" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -2259,25 +2600,19 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, - "jiti": { - "optional": true - }, "less": { "optional": true }, @@ -2298,104 +2633,72 @@ }, "terser": { "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true } } }, "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/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.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "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 || >=22.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", - "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/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.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "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 || >=22.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, @@ -2403,9 +2706,6 @@ "@edge-runtime/vm": { "optional": true }, - "@types/debug": { - "optional": true - }, "@types/node": { "optional": true }, @@ -2423,21 +2723,11 @@ } } }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/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" @@ -2449,6 +2739,11 @@ "node": ">=8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, "node_modules/write-file-atomic": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", @@ -2465,6 +2760,17 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 83eb1981d8b6363bf330125177a08e4874a8ff94..d863c3e02db6b5f0ee091d9d67187c6d349e3d5a 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,44 @@ { - "name": "openinula_migrator", - "version": "1.0.0", - "description": "", - "main": "index.js", + "name": "@openinula/migrator", + "version": "0.1.0", + "description": "CLI to migrate React 17/18 code to OpenInula 2.0 syntax using jscodeshift", + "private": false, "bin": { - "inula-migrator": "./index.js" - }, - "directories": { - "doc": "docs" + "inula-migrate": "bin/inula-migrate", + "openinula_migrator": "bin/inula-migrate" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "dev": "node src/index.js --help", + "test": "vitest run", + "cli": "node bin/inula-migrate", + "build": "echo 'no build step for JS'", + "format": "prettier --write ." }, - "keywords": [], + "keywords": [ + "openinula", + "react", + "codemod", + "jscodeshift", + "cli", + "migration" + ], "author": "", - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">=18" + }, "dependencies": { - "commander": "^14.0.0", - "jscodeshift": "^17.3.0", - "vitest": "^3.2.4" + "chalk": "^4.1.2", + "commander": "^12.1.0", + "diff": "^5.2.0", + "fast-glob": "^3.3.2", + "jscodeshift": "^0.16.1", + "p-limit": "^5.0.0", + "prettier": "^2.8.8", + "recast": "^0.23.9" + }, + "devDependencies": { + "vitest": "^2.0.5" } } + diff --git a/src/array-state-advanced-example.js b/src/array-state-advanced-example.js deleted file mode 100644 index 06c145ec69f96963ca8fbccbb9188295c6ea927d..0000000000000000000000000000000000000000 --- a/src/array-state-advanced-example.js +++ /dev/null @@ -1,102 +0,0 @@ -import { useState } from 'openinula'; - -function AdvancedTodoList() { - const [todos, setTodos] = useState([ - { id: 1, text: '学习 OpenInula', done: false, priority: 'high' }, - { id: 2, text: '写文档', done: false, priority: 'medium' } - ]); - - const [filter, setFilter] = useState('all'); - - function addTodo(text, priority = 'low') { - setTodos(prevTodos => [ - ...prevTodos, - { - id: Date.now(), - text, - done: false, - priority - } - ]); - } - - function removeTodo(id) { - setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id)); - } - - function toggleTodo(id) { - setTodos(prevTodos => - prevTodos.map(todo => - todo.id === id - ? { ...todo, done: !todo.done } - : todo - ) - ); - } - - function updatePriority(id, priority) { - setTodos(prevTodos => - prevTodos.map(todo => - todo.id === id - ? { ...todo, priority } - : todo - ) - ); - } - - function clearCompleted() { - setTodos(prevTodos => prevTodos.filter(todo => !todo.done)); - } - - const filteredTodos = todos.filter(todo => { - if (filter === 'active') return !todo.done; - if (filter === 'completed') return todo.done; - return true; - }); - - return ( -
-
- - -
- -
- - - -
- -
    - {filteredTodos.map(todo => ( -
  • - toggleTodo(todo.id)} - style={{ - textDecoration: todo.done ? 'line-through' : 'none', - color: todo.priority === 'high' ? 'red' : 'black' - }} - > - {todo.text} ({todo.priority}) - - - -
  • - ))} -
- -

总计: {todos.length}, 已完成: {todos.filter(t => t.done).length}

-
- ); -} \ No newline at end of file diff --git a/src/complex-state-advanced-example.js b/src/complex-state-advanced-example.js deleted file mode 100644 index 92529ceef5d78d10fd2019335792d69934cf34da..0000000000000000000000000000000000000000 --- a/src/complex-state-advanced-example.js +++ /dev/null @@ -1,76 +0,0 @@ -import { useState } from 'openinula'; - -function AdvancedUserProfile() { - const [user, setUser] = useState({ - name: '张三', - age: 25, - preferences: { - theme: 'dark', - language: 'zh', - notifications: { - email: true, - push: false - } - } - }); - - const [settings, setSettings] = useState({ - privacy: 'public', - twoFactor: false - }); - - function updateTheme(newTheme) { - setUser(prevUser => ({ - ...prevUser, - preferences: { - ...prevUser.preferences, - theme: newTheme - } - })); - } - - function updateNotifications(type, value) { - setUser(prevUser => ({ - ...prevUser, - preferences: { - ...prevUser.preferences, - notifications: { - ...prevUser.preferences.notifications, - [type]: value - } - } - })); - } - - function updatePrivacy(newPrivacy) { - setSettings(prevSettings => ({ - ...prevSettings, - privacy: newPrivacy - })); - } - - return ( -
-

{user.name}

-

年龄:{user.age}

-
- 主题:{user.preferences.theme} - -
-
- 邮件通知:{user.preferences.notifications.email ? '开启' : '关闭'} - -
-
- 隐私设置:{settings.privacy} - -
-
- ); -} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6ec7e55a2768fc57d62d23e00462efcbbf718251 --- /dev/null +++ b/src/index.js @@ -0,0 +1,90 @@ +'use strict'; + +const { Command } = require('commander'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); +const chalk = require('chalk'); +const pkg = require('../package.json'); +const { runTransforms } = require('./runner/runner'); + +function collectList(value, previous) { + const parts = value.split(',').map((s) => s.trim()).filter(Boolean); + return previous ? previous.concat(parts) : parts; +} + +async function main(argv) { + const program = new Command(); + + program + .name('inula-migrate') + .description('Migrate React 17/18 code to OpenInula 2.0 syntax (codemod-based)') + .version(pkg.version) + .argument('', 'Files or directories to process') + .option('-p, --parser ', 'Source parser: babel|ts|tsx (default: auto)', 'auto') + .option('-w, --write', 'Write changes to files (default dry-run)') + .option('-r, --recursive', 'Recursively process directories', true) + .option('-e, --extensions ', 'Comma-separated extensions', 'js,jsx,ts,tsx') + .option('-i, --ignore ', 'Ignore glob (can be repeated)', collectList, []) + .option('-t, --transform ', 'Comma-separated rule ids to run (default: all)') + .option('--report ', 'Write JSON report to file') + .option('--report-format ', 'Report format: json|md', 'json') + .option('--config ', 'Config file path (.inularc.*)') + .option('--concurrency ', 'Concurrency (default: CPU cores)', String(os.cpus().length)) + .option('--fail-on-warn', 'Exit with non-zero code when warnings exist') + .option('--no-prettier', 'Skip formatting with Prettier') + .option('-q, --quiet', 'Reduce console output') + .option('-v, --verbose', 'Verbose logging') + .action(async (paths, options) => { + const resolvedPaths = paths.map((p) => path.resolve(process.cwd(), p)); + const start = Date.now(); + try { + const result = await runTransforms({ + inputPaths: resolvedPaths, + write: Boolean(options.write), + recursive: options.recursive !== false, + extensions: options.extensions.split(',').map((s) => s.trim()), + ignore: options.ignore || [], + transform: options.transform, + reportPath: options.report ? path.resolve(process.cwd(), options.report) : null, + reportFormat: options.reportFormat || 'json', + configPath: options.config ? path.resolve(process.cwd(), options.config) : null, + concurrency: Number(options.concurrency) || os.cpus().length, + parser: options.parser || 'auto', + failOnWarn: Boolean(options.failOnWarn), + prettier: Boolean(options.prettier), + quiet: Boolean(options.quiet), + verbose: Boolean(options.verbose), + }); + + const elapsed = ((Date.now() - start) / 1000).toFixed(2); + if (!options.quiet) { + console.log( + chalk.green( + `\nCompleted in ${elapsed}s — processed ${result.summary.filesProcessed} files, changed ${result.summary.filesChanged}.` + ) + ); + } + + if (options.failOnWarn && result.summary.warnings > 0) { + process.exitCode = 2; + } else if (result.summary.errors > 0) { + process.exitCode = 1; + } else { + process.exitCode = 0; + } + } catch (err) { + console.error(chalk.red('Error running migration:'), err && err.stack ? err.stack : err); + process.exitCode = 1; + } + }); + + await program.parseAsync(argv); +} + +if (require.main === module) { + main(process.argv); +} + +module.exports = { main }; + diff --git a/src/runner/runner.js b/src/runner/runner.js new file mode 100644 index 0000000000000000000000000000000000000000..e0e39b9a120a63fb712313d603048abd04d940c7 --- /dev/null +++ b/src/runner/runner.js @@ -0,0 +1,176 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const fg = require('fast-glob'); +// Minimal concurrency limiter to avoid ESM/CJS interop issues +function createLimit(maxConcurrency) { + const concurrency = Math.max(1, Number(maxConcurrency) || 1); + let activeCount = 0; + const queue = []; + const next = () => { + if (activeCount >= concurrency) return; + const task = queue.shift(); + if (!task) return; + activeCount += 1; + Promise.resolve() + .then(task.fn) + .then(task.resolve, task.reject) + .finally(() => { + activeCount -= 1; + next(); + }); + }; + return (fn) => + new Promise((resolve, reject) => { + queue.push({ fn, resolve, reject }); + if (activeCount < concurrency) next(); + }); +} +const diff = require('diff'); +const chalk = require('chalk'); +const prettier = require('prettier'); +const { runJscodeshiftTransforms } = require('../runner/transform-executor'); + +async function scanFiles(inputPaths, { recursive, extensions, ignore }) { + const patterns = []; + const exts = extensions.map((e) => (e.startsWith('.') ? e.slice(1) : e)).join(','); + for (const p of inputPaths) { + const stat = fs.existsSync(p) ? fs.statSync(p) : null; + if (!stat) continue; + if (stat.isDirectory()) { + if (recursive) { + patterns.push(`${p.replace(/\\/g, '/')}/**/*.{${exts}}`); + } else { + patterns.push(`${p.replace(/\\/g, '/')}/*.{${exts}}`); + } + } else { + patterns.push(p.replace(/\\/g, '/')); + } + } + const files = await fg(patterns, { ignore, dot: false, onlyFiles: true, unique: true }); + return files; +} + +function computeUnifiedDiff(oldStr, newStr, filePath) { + return diff.createTwoFilesPatch(filePath, filePath, oldStr, newStr, '', '', { + context: 3, + }); +} + +async function maybeFormat(source, filePath) { + try { + const config = await prettier.resolveConfig(filePath).catch(() => null); + return prettier.format(source, { + ...(config || {}), + filepath: filePath, + }); + } catch { + return source; + } +} + +async function runTransforms(options) { + const { + inputPaths, + write, + recursive, + extensions, + ignore, + transform, + reportPath, + reportFormat, + configPath, + concurrency, + parser, + failOnWarn, + prettier: usePrettier, + quiet, + verbose, + } = options; + + const files = await scanFiles(inputPaths, { recursive, extensions, ignore }); + const limiter = createLimit(concurrency || 1); + + const report = { + version: '0.1.0', + runAt: new Date().toISOString(), + summary: { + filesProcessed: 0, + filesChanged: 0, + errors: 0, + warnings: 0, + rules: {}, + }, + files: [], + }; + + const jobs = files.map((filePath) => + limiter(async () => { + const abs = path.resolve(filePath); + let src = fs.readFileSync(abs, 'utf8'); + let transformed = src; + let appliedRules = []; + let warnings = []; + let errors = []; + try { + const execResult = await runJscodeshiftTransforms({ + filePath: abs, + source: src, + onlyRules: transform ? transform.split(',').map((s) => s.trim()) : null, + parser, + verbose, + }); + transformed = execResult.source; + appliedRules = execResult.appliedRules; + warnings = execResult.warnings || []; + } catch (e) { + errors.push({ message: String(e && e.message ? e.message : e) }); + } + + if (usePrettier !== false) { + transformed = await maybeFormat(transformed, abs); + } + + const changed = transformed !== src; + const fileDiff = changed ? computeUnifiedDiff(src, transformed, abs) : ''; + + if (write && changed) { + fs.writeFileSync(abs, transformed, 'utf8'); + } + + if (!quiet && changed) { + const header = chalk.cyan(`\nFile: ${abs}`); + process.stdout.write(`${header}\n`); + process.stdout.write(fileDiff + '\n'); + } + + report.summary.filesProcessed += 1; + if (changed) report.summary.filesChanged += 1; + report.summary.errors += errors.length; + report.summary.warnings += warnings.length; + for (const r of appliedRules) { + report.summary.rules[r.id] = (report.summary.rules[r.id] || 0) + r.count; + } + report.files.push({ + path: abs, + changed, + diff: changed ? fileDiff : '', + appliedRules, + warnings, + errors, + }); + }) + ); + + await Promise.all(jobs); + + if (reportPath && reportFormat === 'json') { + fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n', 'utf8'); + } + + return report; +} + +module.exports = { runTransforms }; + diff --git a/src/runner/transform-executor.js b/src/runner/transform-executor.js new file mode 100644 index 0000000000000000000000000000000000000000..adefc29deb55c2ba3695e3ad114aa2fe89217a82 --- /dev/null +++ b/src/runner/transform-executor.js @@ -0,0 +1,105 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const baseJscodeshift = require('jscodeshift'); +const recast = require('recast'); + +function loadAllTransforms() { + // In the prototype, load built-in transforms from ../transforms + const base = path.resolve(__dirname, '..', '..', 'transforms'); + const exists = fs.existsSync(base) && fs.statSync(base).isDirectory(); + if (!exists) return []; + const files = fs + .readdirSync(base, { withFileTypes: true }) + .flatMap((d) => { + if (d.isDirectory()) { + const dir = path.join(base, d.name); + return fs + .readdirSync(dir) + .filter((f) => f.endsWith('.js')) + .map((f) => path.join(dir, f)); + } + if (d.isFile() && d.name.endsWith('.js')) return [path.join(base, d.name)]; + return []; + }); + return files.map((p) => ({ id: inferRuleId(base, p), path: p })); +} + +function inferRuleId(base, absPath) { + const rel = path.relative(base, absPath).replace(/\\/g, '/'); + return rel.replace(/\.js$/, ''); +} + +function requireTransformModule(absPath) { + // Clear from cache to allow re-run in tests + delete require.cache[require.resolve(absPath)]; + const mod = require(absPath); + return mod && mod.default ? mod.default : mod; +} + +async function runJscodeshiftTransforms({ filePath, source, onlyRules, parser = 'auto', verbose }) { + let current = source; + const appliedRules = []; + const warnings = []; + + const all = loadAllTransforms(); + let selected; + if (onlyRules && onlyRules.length > 0) { + const byId = new Map(all.map((t) => [t.id, t])); + selected = onlyRules.map((id) => byId.get(id)).filter(Boolean); + } else { + selected = all; + } + + for (const t of selected) { + const transformer = requireTransformModule(t.path); + if (typeof transformer !== 'function') continue; + + const fileInfo = { path: filePath, source: current }; // standard jscodeshift FileInfo + // Determine parser per file + let parserChoice = 'babel'; + if (parser && parser !== 'auto') { + if (parser === 'ts' || parser === 'tsx' || parser === 'babel') parserChoice = parser; + } else { + const ext = path.extname(filePath).toLowerCase(); + if (ext === '.ts') parserChoice = 'ts'; + else if (ext === '.tsx') parserChoice = 'tsx'; + else parserChoice = 'babel'; + } + const j = baseJscodeshift.withParser(parserChoice); + const api = { + jscodeshift: j, + j, + stats: () => {}, + report: (msg) => warnings.push({ code: t.id.toUpperCase(), message: String(msg) }), + // expose recast for printing options if needed by transforms + recast, + }; + const options = {}; + + const before = current; + let after = before; + try { + after = transformer(fileInfo, api, options); + if (typeof after !== 'string') { + after = String(after || before); + } + } catch (e) { + throw new Error(`Transform ${t.id} failed: ${e && e.message ? e.message : e}`); + } + + if (after !== before) { + appliedRules.push({ id: t.id, count: 1 }); + current = after; + if (verbose) { + // no-op, could log per-rule info here + } + } + } + + return { source: current, appliedRules, warnings }; +} + +module.exports = { runJscodeshiftTransforms }; + diff --git a/src/useState-example.js b/src/useState-example.js deleted file mode 100644 index cb68fcac41f4a78344621d1824ac8988b017a899..0000000000000000000000000000000000000000 --- a/src/useState-example.js +++ /dev/null @@ -1,6 +0,0 @@ -import { useState } from 'openinula'; - -const [count, setCount] = useState(0); - -console.log(count); - diff --git a/tests/core-imports.test.js b/tests/core-imports.test.js new file mode 100644 index 0000000000000000000000000000000000000000..99fe39226d446a482807c8e0514c518be09fa4e2 --- /dev/null +++ b/tests/core-imports.test.js @@ -0,0 +1,17 @@ +import { describe, it, expect } from 'vitest'; +import exec from '../src/runner/transform-executor.js'; + +const { runJscodeshiftTransforms } = exec; + +describe('core/imports transform', () => { + it('removes unused default React import', async () => { + const source = "import React from 'react';\nexport const A = () =>
;\n"; + const result = await runJscodeshiftTransforms({ + filePath: '/tmp/A.jsx', + source, + onlyRules: ['core/imports'], + }); + expect(result.source).not.toMatch(/from\s+['\"]react['\"]/); + }); +}); + diff --git a/tests/stateManagement/after/state-update-mul.jsx b/tests/stateManagement/after/state-update-mul.jsx new file mode 100644 index 0000000000000000000000000000000000000000..171e78d9f720110d297e642480d85bf52493fc61 --- /dev/null +++ b/tests/stateManagement/after/state-update-mul.jsx @@ -0,0 +1,50 @@ +import React, { useState } from 'react'; + +function UserForm() { + const [formData, setFormData] = useState({ + username: '', + email: '', + age: 0 + }); + + function resetForm() { + setFormData({ + username: '', + email: '', + age: 0 + }); + } + + function updateField(field, value) { + setFormData(prev => ({ + ...prev, + [field]: value + })); + } + + return ( +
+ updateField('username', e.target.value)} + placeholder="用户名" + /> + updateField('email', e.target.value)} + placeholder="邮箱" + /> + updateField('age', parseInt(e.target.value) || 0)} + placeholder="年龄" + /> + +
+ ); +} + +export default UserForm; diff --git a/tests/stateManagement/after/state-update.jsx b/tests/stateManagement/after/state-update.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9b16815b86be403d1861ad762512563d4ad92f47 --- /dev/null +++ b/tests/stateManagement/after/state-update.jsx @@ -0,0 +1,16 @@ +function Counter() { + let count = 0; + let message = ''; + + function increment() { + count++; + message = `当前计数:${count}`; + } + + return ( +
+

{message}

+ +
+ ); +} \ No newline at end of file diff --git a/tests/stateManagement/after/useState-array.jsx b/tests/stateManagement/after/useState-array.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a03ee1c179e4e0bcbf08625059a111adcfcdbff4 --- /dev/null +++ b/tests/stateManagement/after/useState-array.jsx @@ -0,0 +1,47 @@ +function TodoList() { + let todos = [ + { id: 1, text: '学习 OpenInula', done: false }, + { id: 2, text: '写文档', done: false } + ]; + + function addTodo(text) { + todos = [ + ...todos, + { + id: todos.length + 1, + text, + done: false + } + ]; + } + + function toggleTodo(id) { + todos = todos.map(todo => + todo.id === id + ? { ...todo, done: !todo.done } + : todo + ); + } + + return ( +
+ +
    + + {(todo) => ( +
  • toggleTodo(todo.id)} + style={{ + textDecoration: todo.done ? 'line-through' : 'none' + }} + > + {todo.text} +
  • + )} +
    +
+
+ ); +} \ No newline at end of file diff --git a/tests/stateManagement/after/useState-object.jsx b/tests/stateManagement/after/useState-object.jsx new file mode 100644 index 0000000000000000000000000000000000000000..53e9beb738052d12d73b6120f5b9fce906c9ad14 --- /dev/null +++ b/tests/stateManagement/after/useState-object.jsx @@ -0,0 +1,27 @@ +function UserProfile() { + let user = { + name: '张三', + age: 25, + preferences: { + theme: 'dark', + language: 'zh' + } + }; + + function updateTheme(newTheme) { + user.preferences.theme = newTheme; + } + + return ( +
+

{user.name}

+

年龄:{user.age}

+
+ 主题:{user.preferences.theme} + +
+
+ ); +} \ No newline at end of file diff --git a/tests/stateManagement/after/useState.jsx b/tests/stateManagement/after/useState.jsx new file mode 100644 index 0000000000000000000000000000000000000000..7526d99d79f441669958264691493fd40964bfaa --- /dev/null +++ b/tests/stateManagement/after/useState.jsx @@ -0,0 +1,10 @@ +function Counter() { + let count = 0; + + return ( +
+

计数:{count}

+ +
+ ); +} \ No newline at end of file diff --git a/tests/stateManagement/before/state-update-mul.jsx b/tests/stateManagement/before/state-update-mul.jsx new file mode 100644 index 0000000000000000000000000000000000000000..5516c213da666cd68cd02ff6971c8d7d50d8d3f3 --- /dev/null +++ b/tests/stateManagement/before/state-update-mul.jsx @@ -0,0 +1,41 @@ +function UserForm() { + let formData = { + username: '', + email: '', + age: 0 + }; + + function resetForm() { + // 一次性更新多个字段 + formData = { + username: '', + email: '', + age: 0 + }; + } + + function updateField(field, value) { + formData[field] = value; // 更新单个字段 + } + + return ( +
+ updateField('username', e.target.value)} + /> + updateField('email', e.target.value)} + /> + updateField('age', parseInt(e.target.value))} + /> + +
+ ); +} \ No newline at end of file diff --git a/tests/stateManagement/before/state-update.jsx b/tests/stateManagement/before/state-update.jsx new file mode 100644 index 0000000000000000000000000000000000000000..d8e6524abe3fc2648eb7fba7d01be5195438808f --- /dev/null +++ b/tests/stateManagement/before/state-update.jsx @@ -0,0 +1,18 @@ +import React, { useState } from 'react'; + +function Counter() { + const [count, setCount] = useState(0); + + function increment() { + setCount(prev => prev + 1); + } + + return ( +
+

当前计数:{count}

+ +
+ ); +} + +export default Counter; diff --git a/src/array-state-example.js b/tests/stateManagement/before/useState-array.jsx similarity index 87% rename from src/array-state-example.js rename to tests/stateManagement/before/useState-array.jsx index f7d31e8881c888b3140975141464bad5def35221..513b25e151d32fdddbcae2046914ea27137ec739 100644 --- a/src/array-state-example.js +++ b/tests/stateManagement/before/useState-array.jsx @@ -1,11 +1,11 @@ -import { useState } from 'openinula'; +import React, { useState } from 'react'; function TodoList() { const [todos, setTodos] = useState([ { id: 1, text: '学习 OpenInula', done: false }, { id: 2, text: '写文档', done: false } ]); - + function addTodo(text) { setTodos(prevTodos => [ ...prevTodos, @@ -16,9 +16,9 @@ function TodoList() { } ]); } - + function toggleTodo(id) { - setTodos(prevTodos => + setTodos(prevTodos => prevTodos.map(todo => todo.id === id ? { ...todo, done: !todo.done } @@ -26,7 +26,7 @@ function TodoList() { ) ); } - + return (
); -} \ No newline at end of file +} + +export default TodoList; diff --git a/src/complex-state-example.js b/tests/stateManagement/before/useState-object.jsx similarity index 89% rename from src/complex-state-example.js rename to tests/stateManagement/before/useState-object.jsx index 57cd90639cc46734d67f9c001e8dd9197de056de..08f51096aa981248d4712a1ab3f28c09d170339c 100644 --- a/src/complex-state-example.js +++ b/tests/stateManagement/before/useState-object.jsx @@ -1,4 +1,4 @@ -import { useState } from 'openinula'; +import React, { useState } from 'react'; function UserProfile() { const [user, setUser] = useState({ @@ -9,7 +9,7 @@ function UserProfile() { language: 'zh' } }); - + function updateTheme(newTheme) { setUser(prevUser => ({ ...prevUser, @@ -19,7 +19,7 @@ function UserProfile() { } })); } - + return (

{user.name}

@@ -32,4 +32,6 @@ function UserProfile() {
); -} \ No newline at end of file +} + +export default UserProfile; diff --git a/tests/stateManagement/before/useState.jsx b/tests/stateManagement/before/useState.jsx new file mode 100644 index 0000000000000000000000000000000000000000..c639d6bde8f2e0dc387a159cf236dc999007a6b3 --- /dev/null +++ b/tests/stateManagement/before/useState.jsx @@ -0,0 +1,12 @@ +import React from "react"; + +function A() { + let count = 0; + return

{count}

; +} + +function B() { + let n = 0; + n = n + 1; + return

{n}

; +} diff --git a/tests/stateManagement/e2e-stateManagement.test.js b/tests/stateManagement/e2e-stateManagement.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5374f5b622c2a1825b284e23d1df50cb6e373595 --- /dev/null +++ b/tests/stateManagement/e2e-stateManagement.test.js @@ -0,0 +1,98 @@ +import { describe, it, expect } from 'vitest'; +import fs from 'fs'; +import path from 'path'; +import prettier from 'prettier'; +import exec from '../../src/runner/transform-executor.js'; + +const { runJscodeshiftTransforms } = exec; + +const FIXTURE_DIR = path.resolve(process.cwd(), 'tests/stateManagement'); +const BEFORE_DIR = path.join(FIXTURE_DIR, 'before'); +const AFTER_DIR = path.join(FIXTURE_DIR, 'after'); + +function format(code, filePath) { + try { + const config = prettier.resolveConfig.sync(filePath) || {}; + return prettier.format(code, { ...config, filepath: filePath }); + } catch { + return code; + } +} + +async function transformSource(source, filePath) { + const res = await runJscodeshiftTransforms({ filePath, source }); + return res.source; +} + +function isReactSource(code) { + return /from\s+['"]react['"]/i.test(code) || /useState\s*\(/.test(code); +} + +describe('State Management fixtures (before -> after)', () => { + const beforeFiles = fs.readdirSync(BEFORE_DIR).filter((f) => f.endsWith('.jsx')); + + for (const fileName of beforeFiles) { + const beforePath = path.join(BEFORE_DIR, fileName); + const afterPath = path.join(AFTER_DIR, fileName); + if (!fs.existsSync(afterPath)) continue; + + it(`transforms ${fileName} to expected output`, async () => { + const beforeCode = fs.readFileSync(beforePath, 'utf8'); + const afterCode = fs.readFileSync(afterPath, 'utf8'); + + if (!isReactSource(beforeCode)) { + // Unsupported direction for now; ensure we don't crash and remains stable + const once = await transformSource(beforeCode, beforePath); + expect(typeof once).toBe('string'); + return; + } + + const once = await transformSource(beforeCode, beforePath); + const formattedActual = format(once, beforePath); + const formattedExpected = format(afterCode, afterPath); + expect(formattedActual.trim()).toBe(formattedExpected.trim()); + }); + } +}); + +describe('Idempotency (after files)', () => { + const afterFiles = fs.readdirSync(AFTER_DIR).filter((f) => f.endsWith('.jsx')); + + for (const fileName of afterFiles) { + const afterPath = path.join(AFTER_DIR, fileName); + + it(`does not change ${fileName} when already migrated`, async () => { + const afterCode = fs.readFileSync(afterPath, 'utf8'); + if (isReactSource(afterCode)) { + // If fixture is React, skip because our tool migrates React -> OpenInula + return; + } + const once = await transformSource(afterCode, afterPath); + const formattedOriginal = format(afterCode, afterPath); + const formattedOnce = format(once, afterPath); + expect(formattedOnce.trim()).toBe(formattedOriginal.trim()); + }); + } +}); + +describe('Idempotency (double-run on before files)', () => { + const beforeFiles = fs.readdirSync(BEFORE_DIR).filter((f) => f.endsWith('.jsx')); + + for (const fileName of beforeFiles) { + const filePath = path.join(BEFORE_DIR, fileName); + + it(`second run produces no further changes for ${fileName}`, async () => { + const src = fs.readFileSync(filePath, 'utf8'); + if (!isReactSource(src)) { + // Unsupported direction for now + return; + } + const once = await transformSource(src, filePath); + const twice = await transformSource(once, filePath); + const f1 = format(once, filePath); + const f2 = format(twice, filePath); + expect(f2.trim()).toBe(f1.trim()); + }); + } +}); + diff --git a/tests/stateManagement/state-useState.test.js b/tests/stateManagement/state-useState.test.js new file mode 100644 index 0000000000000000000000000000000000000000..0b9b671d60bc0fd56d344f9515763a70d3230814 --- /dev/null +++ b/tests/stateManagement/state-useState.test.js @@ -0,0 +1,47 @@ +import { describe, it, expect } from 'vitest'; +import exec from '../../src/runner/transform-executor.js'; + +const { runJscodeshiftTransforms } = exec; + +function runAll(source, filePath = '/tmp/tmp.jsx', onlyRules) { + return runJscodeshiftTransforms({ + filePath, + source, + onlyRules, // when null => run all + }); +} + +describe('state/useState transform', () => { + it('decl converts to let and setter to direct assignment', async () => { + const source = "import { useState } from 'react';\nfunction A(){ const [count, setCount] = useState(0); setCount(1); return

{count}

; }"; + const res = await runAll(source, '/tmp/A.jsx', ['state/useState', 'core/imports']); + expect(res.source).toMatch(/let\s+count\s*=\s*0/); + expect(res.source).toMatch(/count\s*=\s*1/); + expect(res.source).not.toMatch(/from\s+['\"]react['\"]/); + }); + + it('functional update becomes assignment using state identifier', async () => { + const source = "import { useState } from 'react';\nfunction A(){ const [n, setN] = useState(0); setN(p => p + 1); return

{n}

; }"; + const res = await runAll(source, '/tmp/B.jsx', ['state/useState', 'core/imports']); + expect(res.source).toMatch(/n\s*=\s*n\s*\+\s*1/); + }); + + it('lazy initializer unwraps arrow function', async () => { + const source = "import { useState } from 'react';\nfunction A(){ const [x, setX] = useState(() => 42); return

{x}

; }"; + const res = await runAll(source, '/tmp/C.jsx', ['state/useState', 'core/imports']); + expect(res.source).toMatch(/let\s+x\s*=\s*42/); + }); + + it('object update remains an assignment of new object', async () => { + const source = "import { useState } from 'react';\nfunction A(){ const [user, setUser] = useState({age: 1}); setUser({...user, age: 2}); return

{user.age}

; }"; + const res = await runAll(source, '/tmp/D.jsx', ['state/useState', 'core/imports']); + expect(res.source).toMatch(/user\s*=\s*\{\s*\.\.\.user,\s*age:\s*2\s*\}/); + }); + + it('array update remains an assignment of new array', async () => { + const source = "import { useState } from 'react';\nfunction A(){ const [todos, setTodos] = useState([]); setTodos([...todos, 1]); return
    }"; + const res = await runAll(source, '/tmp/E.jsx', ['state/useState', 'core/imports']); + expect(res.source).toMatch(/todos\s*=\s*\[\s*\.\.\.todos,\s*1\s*\]/); + }); +}); + diff --git a/transforms/core/imports.js b/transforms/core/imports.js new file mode 100644 index 0000000000000000000000000000000000000000..93fc1d1a8bf0fe39c8782ed419e19cea5dfbf958 --- /dev/null +++ b/transforms/core/imports.js @@ -0,0 +1,18 @@ +'use strict'; + +// jscodeshift transform: remove React imports only +// - Any import declaration with source 'react' will be removed entirely + +module.exports = function transformer(file, api) { + const j = api.jscodeshift; + const root = j(file.source); + + root + .find(j.ImportDeclaration, { source: { value: 'react' } }) + .forEach((p) => { + j(p).remove(); + }); + + return root.toSource({ quote: 'single' }); +}; + diff --git a/transforms/state/useState.js b/transforms/state/useState.js new file mode 100644 index 0000000000000000000000000000000000000000..43b019c6488fa01a674ee1ce860db86fac1e2af1 --- /dev/null +++ b/transforms/state/useState.js @@ -0,0 +1,106 @@ +'use strict'; + +// Transform React useState to OpenInula 2.0 style direct variables +// - const [x, setX] = useState(init) -> let x = init +// - setX(value) -> x = value +// - setX(prev => expr(prev)) -> x = expr(x) +// - Remove named import useState from 'react' + +module.exports = function transformer(file, api) { + const j = api.jscodeshift; + const root = j(file.source); + + const statePairs = []; + + // Handle simple case: single declarator declaration + root.find(j.VariableDeclaration).forEach((path) => { + const decl = path.node; + if (!decl.declarations || decl.declarations.length !== 1) return; + const d0 = decl.declarations[0]; + if (!d0.id || d0.id.type !== 'ArrayPattern') return; + if (!d0.init || d0.init.type !== 'CallExpression') return; + const callee = d0.init.callee; + const isUseState = + (callee.type === 'Identifier' && callee.name === 'useState') || + (callee.type === 'MemberExpression' && !callee.computed && callee.property.type === 'Identifier' && callee.property.name === 'useState'); + if (!isUseState) return; + + const elements = d0.id.elements || []; + if (elements.length < 2) return; + const stateId = elements[0] && elements[0].type === 'Identifier' ? elements[0].name : null; + const setterId = elements[1] && elements[1].type === 'Identifier' ? elements[1].name : null; + if (!stateId || !setterId) return; + + const args = d0.init.arguments || []; + let initExpr = null; + if (args.length === 0) { + initExpr = j.identifier('undefined'); + } else if (args.length >= 1) { + const a0 = args[0]; + if (a0.type === 'ArrowFunctionExpression' || a0.type === 'FunctionExpression') { + if (a0.body.type === 'BlockStatement') { + const ret = a0.body.body.find((s) => s.type === 'ReturnStatement'); + initExpr = (ret && ret.argument) || j.identifier('undefined'); + } else { + initExpr = a0.body; + } + } else { + initExpr = a0; + } + } + + const letDecl = j.variableDeclaration('let', [j.variableDeclarator(j.identifier(stateId), initExpr)]); + j(path).replaceWith(letDecl); + + statePairs.push({ state: stateId, setter: setterId }); + }); + + // Replace setter calls: setX(arg) -> x = transformed(arg) + statePairs.forEach(({ state, setter }) => { + root + .find(j.CallExpression, { callee: { type: 'Identifier', name: setter } }) + .forEach((p) => { + const args = p.node.arguments || []; + if (args.length !== 1) return; + let rhs = args[0]; + + if (rhs.type === 'ArrowFunctionExpression' || rhs.type === 'FunctionExpression') { + // Functional update: setState(prev => body) + const param = rhs.params && rhs.params[0] && rhs.params[0].type === 'Identifier' ? rhs.params[0].name : null; + let bodyExpr = null; + if (rhs.body.type === 'BlockStatement') { + const ret = rhs.body.body.find((s) => s.type === 'ReturnStatement'); + bodyExpr = (ret && ret.argument) || null; + } else { + bodyExpr = rhs.body; + } + if (!bodyExpr) return; + // Replace occurrences of param identifier with state identifier + if (param) { + j(bodyExpr) + .find(j.Identifier, { name: param }) + .forEach((q) => { + q.node.name = state; + }); + } + rhs = bodyExpr; + } + + // Replace the CallExpression with an AssignmentExpression + const assignment = j.assignmentExpression('=', j.identifier(state), rhs); + const exprStmt = j.expressionStatement(assignment); + j(p).replaceWith(exprStmt); + }); + }); + + // Remove named import useState from 'react' + root.find(j.ImportDeclaration, { source: { value: 'react' } }).forEach((p) => { + const before = p.node.specifiers || []; + const after = before.filter((s) => !(s.type === 'ImportSpecifier' && s.imported && s.imported.name === 'useState')); + p.node.specifiers = after; + // If no specifiers left, keep as side-effect import (import 'react') + }); + + return root.toSource({ quote: 'single' }); +}; + diff --git a/transforms/transform-array-state.js b/transforms/transform-array-state.js deleted file mode 100644 index 3f4497d723c074ed85bf60eda9550268ffa330d3..0000000000000000000000000000000000000000 --- a/transforms/transform-array-state.js +++ /dev/null @@ -1,153 +0,0 @@ -const recast = require('recast'); - -module.exports = function (fileInfo, api) { - const j = api.jscodeshift; - const root = j(fileInfo.source); - - let fileModified = false; - const stateVariables = new Map(); // 存储状态变量和其初始值 - - // 1. 找到所有的 useState 调用并收集状态变量信息 - root.find(j.VariableDeclaration).forEach(path => { - const decl = path.node.declarations[0]; - if ( - decl && - j.ArrayPattern.check(decl.id) && - decl.id.elements.length === 2 && - decl.init && - j.CallExpression.check(decl.init) && - decl.init.callee.name === 'useState' - ) { - const varName = decl.id.elements[0].name; - const setterName = decl.id.elements[1].name; - const initValue = decl.init.arguments[0]; - - // 存储状态变量信息 - stateVariables.set(varName, { - setterName, - initValue, - path - }); - } - }); - - // 2. 转换 useState 声明为 let 变量声明 - stateVariables.forEach((info, varName) => { - const newDecl = j.variableDeclaration('let', [ - j.variableDeclarator(j.identifier(varName), info.initValue) - ]); - j(info.path).replaceWith(newDecl); - fileModified = true; - }); - - // 3. 转换状态的 setter 调用为直接赋值 - root.find(j.CallExpression).forEach(path => { - const callExpr = path.node; - - // 查找 setter 调用 - stateVariables.forEach((info, varName) => { - if (callExpr.callee.name === info.setterName && callExpr.arguments.length === 1) { - const arg = callExpr.arguments[0]; - - if (j.ArrowFunctionExpression.check(arg)) { - // 处理函数形式的 setter: setTodos(prevTodos => [...prevTodos, newItem]) - const funcBody = arg.body; - const paramName = arg.params[0].name; - - // 创建一个新的函数体,递归替换参数名 - function replaceIdentifiers(node) { - if (j.Identifier.check(node) && node.name === paramName) { - return j.identifier(varName); - } - - // 递归处理对象的所有属性 - const newNode = { ...node }; - for (const key in newNode) { - if (newNode[key] && typeof newNode[key] === 'object') { - if (Array.isArray(newNode[key])) { - newNode[key] = newNode[key].map(replaceIdentifiers); - } else { - newNode[key] = replaceIdentifiers(newNode[key]); - } - } - } - return newNode; - } - - const bodyWithReplacedParams = replaceIdentifiers(funcBody); - - // 直接将修改后的函数体作为赋值的右侧 - const assignmentExpr = j.assignmentExpression('=', j.identifier(varName), bodyWithReplacedParams); - j(path).replaceWith(assignmentExpr); - fileModified = true; - } else { - // 处理直接值形式的 setter: setFilter('all') 或 setTodos([...todos, newItem]) - const assignmentExpr = j.assignmentExpression('=', j.identifier(varName), arg); - j(path).replaceWith(assignmentExpr); - fileModified = true; - } - } - }); - }); - - // 4. 转换 JSX 中的 map 调用为 语法 - root.find(j.JSXExpressionContainer).forEach(path => { - const expr = path.node.expression; - - // 查找 array.map() 调用 - if (j.CallExpression.check(expr) && - j.MemberExpression.check(expr.callee) && - expr.callee.property.name === 'map') { - - const arrayExpr = expr.callee.object; - const mapCallback = expr.arguments[0]; - - // 处理所有的 map 调用,不仅仅是状态数组 - if (j.ArrowFunctionExpression.check(mapCallback)) { - const param = mapCallback.params[0]; - const callbackBody = mapCallback.body; - - // 创建 元素 - const forElement = j.jsxElement( - j.jsxOpeningElement(j.jsxIdentifier('for'), [ - j.jsxAttribute( - j.jsxIdentifier('each'), - j.jsxExpressionContainer(arrayExpr) - ) - ]), - j.jsxClosingElement(j.jsxIdentifier('for')), - [ - j.jsxText('\n '), - j.jsxExpressionContainer( - j.arrowFunctionExpression([param], callbackBody) - ), - j.jsxText('\n ') - ] - ); - - j(path).replaceWith(forElement); - fileModified = true; - } - } - }); - - // 5. 移除 useState 的导入 - if (fileModified) { - root - .find(j.ImportDeclaration, { source: { value: 'openinula' } }) - .forEach(path => { - const newSpecifiers = path.node.specifiers.filter(s => s.imported.name !== 'useState'); - if (newSpecifiers.length > 0) { - path.node.specifiers = newSpecifiers; - } else { - j(path).remove(); - } - }); - } - - if (fileModified) { - return recast.print(root.get().node, { tabWidth: 2, quote: 'single' }).code; - } - - return fileInfo.source; -}; \ No newline at end of file diff --git a/transforms/transform-complex-state.js b/transforms/transform-complex-state.js deleted file mode 100644 index 0d6de6a87de7e6c87bb32bde8d6351d16193cd5a..0000000000000000000000000000000000000000 --- a/transforms/transform-complex-state.js +++ /dev/null @@ -1,131 +0,0 @@ -const recast = require('recast'); - -module.exports = function (fileInfo, api) { - const j = api.jscodeshift; - const root = j(fileInfo.source); - - let fileModified = false; - const stateVariables = new Map(); // 存储状态变量和其初始值 - - // 1. 找到所有的 useState 调用并收集状态变量信息 - root.find(j.VariableDeclaration).forEach(path => { - const decl = path.node.declarations[0]; - if ( - decl && - j.ArrayPattern.check(decl.id) && - decl.id.elements.length === 2 && - decl.init && - j.CallExpression.check(decl.init) && - decl.init.callee.name === 'useState' - ) { - const varName = decl.id.elements[0].name; - const setterName = decl.id.elements[1].name; - const initValue = decl.init.arguments[0]; - - // 存储状态变量信息 - stateVariables.set(varName, { - setterName, - initValue, - path - }); - } - }); - - // 2. 转换 useState 声明为 let 变量声明 - stateVariables.forEach((info, varName) => { - const newDecl = j.variableDeclaration('let', [ - j.variableDeclarator(j.identifier(varName), info.initValue) - ]); - j(info.path).replaceWith(newDecl); - fileModified = true; - }); - - // 3. 转换复杂的对象更新为直接属性赋值 - root.find(j.CallExpression).forEach(path => { - const callExpr = path.node; - - // 查找 setter 调用 - stateVariables.forEach((info, varName) => { - if (callExpr.callee.name === info.setterName && callExpr.arguments.length === 1) { - const arg = callExpr.arguments[0]; - - if (j.ArrowFunctionExpression.check(arg) && j.ObjectExpression.check(arg.body)) { - const objExpr = arg.body; - - // 分析对象表达式,寻找最深层的非扩展属性 - let deepestAssignment = null; - let targetPath = []; - - function analyzeObjectExpression(obj, currentPath) { - obj.properties.forEach(prop => { - if (j.Property.check(prop) && !j.SpreadElement.check(prop)) { - const key = prop.key.name || prop.key.value; - const newPath = [...currentPath, key]; - - if (j.ObjectExpression.check(prop.value)) { - // 递归分析嵌套对象 - analyzeObjectExpression(prop.value, newPath); - } else { - // 这是最终的赋值 - deepestAssignment = prop.value; - targetPath = newPath; - } - } - }); - } - - analyzeObjectExpression(objExpr, []); - - if (deepestAssignment && targetPath.length > 0) { - // 构建成员表达式链 - let memberExpr = j.identifier(varName); - targetPath.forEach(key => { - memberExpr = j.memberExpression(memberExpr, j.identifier(key)); - }); - - const assignmentExpr = j.assignmentExpression('=', memberExpr, deepestAssignment); - j(path).replaceWith(assignmentExpr); - fileModified = true; - } - } else if (j.ArrowFunctionExpression.check(arg)) { - // 处理简单的对象更新: setSettings(prev => ({ ...prev, key: value })) - const funcBody = arg.body; - if (j.ObjectExpression.check(funcBody)) { - const nonSpreadProps = funcBody.properties.filter(prop => - j.Property.check(prop) && !j.SpreadElement.check(prop) - ); - - if (nonSpreadProps.length === 1) { - const prop = nonSpreadProps[0]; - const key = prop.key.name || prop.key.value; - const memberExpr = j.memberExpression(j.identifier(varName), j.identifier(key)); - const assignmentExpr = j.assignmentExpression('=', memberExpr, prop.value); - j(path).replaceWith(assignmentExpr); - fileModified = true; - } - } - } - } - }); - }); - - // 4. 移除 useState 的导入 - if (fileModified) { - root - .find(j.ImportDeclaration, { source: { value: 'openinula' } }) - .forEach(path => { - const newSpecifiers = path.node.specifiers.filter(s => s.imported.name !== 'useState'); - if (newSpecifiers.length > 0) { - path.node.specifiers = newSpecifiers; - } else { - j(path).remove(); - } - }); - } - - if (fileModified) { - return recast.print(root.get().node, { tabWidth: 2, quote: 'single' }).code; - } - - return fileInfo.source; -}; \ No newline at end of file diff --git a/transforms/transform-useState.js b/transforms/transform-useState.js deleted file mode 100644 index ea73fefbec87a61b1e0478f8b747200d5e9525dc..0000000000000000000000000000000000000000 --- a/transforms/transform-useState.js +++ /dev/null @@ -1,50 +0,0 @@ -const recast = require('recast'); - -module.exports = function (fileInfo, api) { - const j = api.jscodeshift; - const root = j(fileInfo.source); - - let fileModified = false; - - // Transform useState - root.find(j.VariableDeclaration).forEach(path => { - const decl = path.node.declarations[0]; - if ( - decl && - j.ArrayPattern.check(decl.id) && - decl.id.elements.length === 2 && - decl.init && - j.CallExpression.check(decl.init) && - decl.init.callee.name === 'useState' - ) { - const varName = decl.id.elements[0].name; - const initValue = decl.init.arguments[0]; - const newDecl = j.variableDeclaration('let', [ - j.variableDeclarator(j.identifier(varName), initValue) - ]); - j(path).replaceWith(newDecl); - fileModified = true; - } - }); - - // Remove import if needed - if (fileModified) { - root - .find(j.ImportDeclaration, { source: { value: 'openinula' } }) - .forEach(path => { - const newSpecifiers = path.node.specifiers.filter(s => s.imported.name !== 'useState'); - if (newSpecifiers.length > 0) { - path.node.specifiers = newSpecifiers; - } else { - j(path).remove(); - } - }); - } - - if (fileModified) { - // Force a format change to ensure jscodeshift detects the modification. - return recast.print(root.get().node, { tabWidth: 4, quote: 'single' }).code; - } - - return fileInfo.source; -};